USB HID Website Launcher
Goal
I wanted to create a small USB device that could automatically open the FabLabTV Live website when plugged into a computer. The idea was that anyone could simply insert the device and immediately access the live dashboard without installing any software or drivers.
The solution should work as a standard USB Human Interface Device (HID) so the operating system would recognize it as a normal keyboard.
Flashing CircuitPython
I used a Seeed XIAO RP2040 and flashed it with CircuitPython. Once connected, the board appeared as a USB drive called CIRCUITPY.
This allows Python files to be copied directly onto the microcontroller.
First Attempt - Pico-Ducky
My first approach was to use Pico-Ducky, which executes Ducky Script payloads.
The payload looked like this:
The files copied successfully to the board, but although the XIAO mounted correctly as a USB drive, it never emulated a keyboard.
After some investigation I discovered that Pico-Ducky is primarily written for the Raspberry Pi Pico and was not behaving correctly on the XIAO RP2040.
Rather than spending more time debugging the firmware, I decided to write the payload directly in Python.
CircuitPython HID
CircuitPython includes support for USB HID through the adafruit_hid library.
Instead of interpreting Ducky Script, the program now sends the keyboard events directly.
import time
import board
import usb_hid
import digitalio
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
from adafruit_hid.keycode import Keycode
URL = "https://andrisaem.fabcloud.io/fablabtvlive/"
button = digitalio.DigitalInOut(board.D2)
button.direction = digitalio.Direction.INPUT
button.pull = digitalio.Pull.UP
keyboard = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(keyboard)
time.sleep(4)
if not button.value:
keyboard.press(Keycode.ALT, Keycode.F2)
keyboard.release_all()
time.sleep(1)
layout.write(f"xdg-open {URL}")
else:
keyboard.press(Keycode.WINDOWS, Keycode.R)
keyboard.release_all()
time.sleep(1)
layout.write(URL)
keyboard.press(Keycode.ENTER)
keyboard.release_all()
while True:
time.sleep(60)
The program waits a few seconds for the operating system to recognize the keyboard before sending any key presses.
Selecting the Operating System
One limitation of a USB keyboard is that it has no way of knowing which operating system it has been plugged into. To solve this I added a push button connected between GPIO D2 and GND.
CircuitPython enables the internal pull-up resistor so no external resistor is required.
button = digitalio.DigitalInOut(board.D2)
button.direction = digitalio.Direction.INPUT
button.pull = digitalio.Pull.UP
At startup the button determines which payload is executed.
| Button | Action |
|---|---|
| Released | Windows (Win + R) |
| Held while plugging in | Linux (Alt + F2) |
This keeps the device simple while allowing it to work on multiple operating systems.
How it Works
Windows
The XIAO opens the Run dialog and types.
Windows automatically opens the default web browser and navigates to the website.
Linux
The XIAO opens the **Run Command and types.
xdg-open launches the default web browser and opens the same website.
Result
The final device behaves as a standard USB keyboard and requires no special drivers or software.
Depending on whether the push button is held during startup, it automatically launches the FabLabTV Live website on either Windows or Linux.
Writing the payload directly in CircuitPython turned out to be a much cleaner solution than relying on Ducky Script. It also makes the project easy to extend with additional operating systems, LEDs, status indicators, or more advanced keyboard automation in the future.