Bootleg LED control from phone for Linux and Android

tl;dr: This code turns cursor position into color and brightness. X = hue, Y = brightness. Left click rotates X position from hue -> saturation -> locked. Right click to toggle brightness lock. Only works on Linux, should be easy to port. Use KDE Connect to control it from your phone.

Occasionally, I have been known to sleep. Sometimes when I sleep, I’ll wake up in the middle of the night and either have to turn on a light or risk running right into a wall. If I turn on a light I’m going to be instantly awake. A soft red glow would make it possible to see, without waking up. But hooking up a light switch or motion detector or some other fancy crap is too much effort.

So I made a solution with what I had, an Android phone and the Linux PC the LEDs are hooked up to. On my phone I have the KDE Connect app, which I can use as a remote control for my PC. I can play/pause/skip music, send/receive files, and most importantly control the mouse cursor.

I took advantage of that to make this script that grabs the mouse cursor, and sets all connected LED’s brightness and color based on the cursor position. When the script starts, X position of the cursor controls the hue, with Y position controlling brightness. Left clicking once swaps X position from hue to saturation. Clicking twice locks both hue and saturation and leaves brightness as the only control active, making it easy to just swipe up and down on the phone to adjust brightness. Right clicking toggles the brightness lock on and off.

Switching between modes and locking/unlocking the various thing saves and restores cursor position so that there aren’t any sharp changes in anything when switching from mode to mode. The cursor is disabled from clicking on other windows while the program is running to prevent you from accidentally clicking on something you didn’t mean to.

Keep in mind, this code depends on X to grab pointer location, so it will only work on Linux. It should be fairly easy to port to Windows for anyone interested, but you’d have to find an alternate way of controlling the cursor since KDE Connect is also Linux-only. You might just bring a wireless mouse to bed with you.

With all that in mind, here’s the code:

from Xlib import display, X
from blinkstick import blinkstick
from colorsys import hsv_to_rgb
from time import time, sleep

hue = 0
sat = 1
val = 0.5
mode = 0 # left click to switch control from hue/sat/none.
lock_brightness = False # right click to lock brightness.

disp = display.Display() # Get the X Display
screen = disp.screen() # Get the X Screen
root = screen.root # Get the root window

max_y = screen.height_in_pixels # Detect screen height
max_x = screen.width_in_pixels # And width

root.grab_pointer(1, X.ButtonPressMask, X.GrabModeAsync, X.GrabModeAsync, X.NONE, X.NONE, X.CurrentTime) # Grabs the cursor and watches for events.

sticks = blinkstick.find_all() # Find all connected blinksticks

start = time()

while True:    
    y = screen.root.query_pointer().root_y # Get X position of cursor
    x = screen.root.query_pointer().root_x # Get Y position of cursor
    
    if mode == 0: # If cursor controls hue
        hue = x/max_x
    elif mode == 1: # If cursor controls sat
        sat = x/max_x
    if not lock_brightness: # If cursor controls brightness
        val = (max_y - y)/max_y
    
    if disp.pending_events(): # If there are any mouse clicks waiting for us
        new_x = x # Prepare for possible cursor position changes
        new_y = y
        while disp.pending_events(): # Loop through all mouse events
            event = disp.next_event() # Get the current event
            if event.detail == 1: # If it's a left click,
                mode = (mode + 1) % 3 # rotate through modes.
                if mode == 0: # If we're in hue mode now,
                    new_x = max_x*hue # Restore X mouse position
                elif mode == 1: # If we're in satuation mode now,
                    new_x = max_x*sat # Do the same
            elif event.detail == 3: # If this is a right click,
                lock_brightness = not lock_brightness # toggle the brightness lock.
                if not lock_brightness: # And if we unlocked it,
                    new_y = max_y*val # Restore Y mouse position
        root.warp_pointer(int(new_x), int(new_y)) # Set the cursor to its new position
        disp.sync() # And fire away
            
    
    r, g, b = hsv_to_rgb(hue, sat, val) # Set RGB values
    print(round(r, 2), round(g, 2), round(b, 2), end="      \r") # Print it out for fun
    
    now = time()
    if now-start < 0.02: # Make sure we gave the blinkstick enough of a break to catch up
        sleep(0.02 - (now-start)) # I think that math works out.
    
    
    for stick in sticks: # Loop over all blinksticks
        count = stick.get_led_count() # Get LED count
        if count == -1: # If this strip is a dumb single-color strip (Pro w/ LED adapter)
            stick.set_color(0, 0, int(g*255), int(r*255), int(b*255)) # Use set_color instead
        else: # If they're addressable,
            stick.set_led_data(0, [int(g*255), int(r*255), int(b*255)]*count) # Then address them.
    
    start = time()

And with that you should be able to control your blinkstick’s color and brightness from your phone.