Client & CLI for a Raspberry Pi Pico USB‑CDC → HID bridge. Drive keyboard, mouse (and more in future) from your PC using a tiny, line‑based serial protocol.
This README matches the v1 firmware (commands:
PING,INFO,RESET,K,*,M,MW).
- 🔌 Auto‑port discovery: finds the COM port by sending
PINGand expectingPONG. - 🖱️ Mouse: relative move, press/release (bitfield), wheel.
- ⌨️ Keyboard: tap / press / release / combos (e.g.,
CTRL+S). - 🧰 CLI & Python API: scriptable from shell or Python.
- 🛡️ Timeouts, retries, typed errors in the client for reliability.
# from a checkout
python -m pip install -e .
# now the CLI is available
picohid --versionRequires Python 3.8+. The only runtime dependency is pyserial.
picohid ping
picohid info
picohid move 12 0
picohid click L
picohid tap SPACE
picohid combo CTRL+SPython snippet:
from picohid import PicoHID
import time
with PicoHID() as d: # auto‑finds the Pico by PING
print(d.ping()) # True
print(d.info()) # {'FW': 'PicoHID v1.0', 'PROTO': '1'}
for _ in range(25): d.move(8, 0); time.sleep(0.01)
d.click("L"); d.wheel(-3)
d.tap("SPACE"); d.combo("CTRL","S")
d.reset()The repo includes firmware/boot.py and firmware/code.py. Flash CircuitPython to your Pico, then copy:
boot.py
import usb_cdc, usb_hid
usb_cdc.enable(console=False, data=True)
usb_hid.enable((usb_hid.Device.KEYBOARD, usb_hid.Device.MOUSE))code.py implements the protocol and HID actions. You also need the Adafruit HID library in CIRCUITPY/lib/adafruit_hid/ (keyboard.py, keycode.py, mouse.py) from the bundle that matches your CircuitPython version (boot_out.txt shows it).
All commands are ASCII, comma‑separated, terminated by \n (CR or CRLF accepted). Replies are single lines.
Core
PING -> PONG
INFO -> INFO,FW=PicoHID v1.0,PROTO=1
RESET -> OK # releases all keys/buttons
Keyboard
K,TAP,<KEY> -> OK
K,DOWN,<KEY> -> OK
K,UP,<KEY> -> OK
K,COMBO,A+B+C -> OK # e.g. CTRL+SHIFT+ESC
Mouse
M,<dx>,<dy>,<buttons> -> OK # relative move + button press/release
buttons bitfield: 1=L, 2=R, 4=M; >0 press bits, <0 release bits
MW,<delta> -> OK # wheel (+: up, −: down)
Errors
ERR,<code>[,<detail>]
# examples: ERR,K,format | ERR,K,unknown:SPACE | ERR,M,values | ERR,unknown_cmd
Tip: for a click, send
M,0,0,1thenM,0,0,-1(or usepicohid click L).
picohid --version
picohid --port COM6 ping
picohid info
picohid reset
picohid move 20 -5 # move only
picohid move 0 0 -b 1 # press left
picohid move 0 0 -b -1 # release left
picohid click R # right click
picohid wheel -3 # scroll down
picohid tap SPACE
picohid down W; picohid up W
picohid combo CTRL+S # or: picohid combo CTRL S
picohid -h
picohid move -h
from picohid import PicoHID, DeviceNotFound, Timeout, ProtocolError
with PicoHID(port=None) as d: # auto‑find
d.move(0, 0, 1) # press left (drag start)
d.move(60, 0) # drag
d.move(0, 0, -1) # release leftping() -> boolinfo() -> dictreset() -> Nonetap(name),down(name),up(name),combo(*names)move(dx,dy,buttons=0),wheel(delta),click(which='L')
Exceptions: DeviceNotFound, Timeout, ProtocolError.
- No reply / blank line: ensure you are on the DATA COM port (not console), and you send a trailing
\n. No Pico HID bridge found: open the correct COM in Windows Device Manager; or runpicohid --port COMx ....ERR,unknown_cmd: you may be running v1 firmware (which doesn’t support extras likeSTR,CC,GP).- Nothing moves: check that your OS sees a Keyboard and Mouse under the Pico in Device Manager; verify
usb_hid.enableinboot.py. - Library mismatch: make sure
adafruit_hidbundle matches the CircuitPython version on the board.
press/releaseCLI verbs- Clamp
dx,dy(±127) per HID packet to avoid OS clamping - Optional sequence IDs for latency tracing
- Optional extended firmware (
STR,CC,GP,*, absolute mouse)
MIT (see LICENSE).
This tool can send keystrokes and mouse input—don’t aim it at machines you don’t control.