Last active
August 3, 2026 21:08
-
-
Save todbot/f1469fca9bcedf8a34426047ada49a4e to your computer and use it in GitHub Desktop.
Blink a plugged in USB keyboard's LEDs on a USB host-capable CircuitPython board
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Blink a plugged in USB keyboard's LEDs | |
| # Designed for FruitJam but works on any other CircuitPython board with USB host | |
| # 3 Aug 2026 - @todbot / Tod Kurt | |
| import time | |
| import array | |
| import usb.core | |
| import usb_host | |
| import adafruit_usb_host_descriptors | |
| REQTYPE_OUT_CLASS_IFACE = 0x21 | |
| HID_SET_REPORT = 0x09 | |
| HID_REPORT_TYPE_OUTPUT = 0x02 | |
| LED_NUMLOCK = 0x01 | |
| LED_CAPSLOCK = 0x02 | |
| LED_SCROLLLOCK = 0x04 | |
| LED_COMPOSE = 0x08 | |
| LED_KANA = 0x10 | |
| DESC_INTERFACE, DESC_ENDPOINT = 0x04, 0x05 | |
| def find_kbd_boot_interface(device): | |
| cfg = adafruit_usb_host_descriptors.get_configuration_descriptor(device, 0) | |
| i = 0 | |
| iface_num = ep_in = None | |
| while i < len(cfg): | |
| blen, btype = cfg[i], cfg[i + 1] | |
| if btype == DESC_INTERFACE: | |
| # bInterfaceClass 0x03 = HID, bInterfaceSubClass 0x01 = boot, | |
| # bInterfaceProtocol 0x01 = keyboard | |
| if cfg[i + 5] == 0x03 and cfg[i + 6] == 0x01 and cfg[i + 7] == 0x01: | |
| iface_num = cfg[i + 2] | |
| elif iface_num is not None and ep_in is not None: | |
| break # moved past the keyboard interface | |
| elif btype == DESC_ENDPOINT and iface_num is not None and ep_in is None: | |
| if cfg[i + 2] & 0x80: | |
| ep_in = cfg[i + 2] | |
| i += blen | |
| return iface_num, ep_in | |
| def set_kbd_leds(device, interface, leds, report_id=0): | |
| return device.ctrl_transfer( | |
| REQTYPE_OUT_CLASS_IFACE, | |
| HID_SET_REPORT, | |
| (HID_REPORT_TYPE_OUTPUT << 8) | report_id, | |
| interface, | |
| array.array("B", [leds]), | |
| ) | |
| device = usb.core.find(find_all=False) # or filter by VID/PID | |
| iface, ep_in = find_kbd_boot_interface(device) | |
| if device.is_kernel_driver_active(iface): | |
| device.detach_kernel_driver(iface) | |
| device.set_configuration() | |
| while True: | |
| print("hi", time.monotonic()) | |
| set_kbd_leds(device, iface, LED_CAPSLOCK ) | |
| time.sleep(0.2) | |
| set_kbd_leds(device, iface, LED_NUMLOCK) | |
| time.sleep(0.2) | |
| set_kbd_leds(device, iface, LED_CAPSLOCK | LED_NUMLOCK) | |
| time.sleep(0.2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment