Agent-friendly USB power control for the embedded bench, built for the Agentic Firmware Workbench. Switch, monitor, and power-cycle USB devices — from the command line or from an AI agent via MCP.
Typical use: an agent flashing and testing firmware hits a hung debug probe or
wedged dev board. Instead of asking a human to unplug it, the agent runs
power_cycle and carries on.
devusb drives two kinds of hub through one interface.
Yepkit YKUSH family — dedicated bench hardware, spoken to directly over
USB HID (hidapi). No vendor utility needed. Addressed by serial number.
| Hub | Switched ports | Status |
|---|---|---|
| YKUSH3 | 3 | verified on hardware |
| YKUSH | 3 | same protocol, untested |
| YKUSH XS | 1 | same protocol, untested |
Generic power-switchable hubs — any hub advertising per-port power
switching (ppps), driven through
uhubctl. Many ordinary hubs and docks
qualify, so this often works with hardware you already own. Addressed by
uhubctl location (e.g. 35-2.1.4), since ordinary hubs rarely carry
unique serial numbers. Verified on Realtek RTS5411 and GenesysLogic hubs.
pip install .Requires Python 3.10+. For generic hub support, also install uhubctl:
brew install uhubctl # macOS
sudo apt install uhubctl # Debian/UbuntuYKUSH control never depends on uhubctl — if it is missing, generic hubs are simply absent from the listing.
On Linux, non-root access to the hub's HID interface needs a udev rule:
sudo cp udev/99-devusb.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm triggerThen unplug and replug the hub. macOS and Windows need no extra setup.
devusb list # every controllable hub, both backends
devusb list --ykush-only # skip generic hubs
devusb status # power state of each port
devusb off 1 # cut power to port 1
devusb on 1 # restore power
devusb cycle 1 --hold 10 # power-cycle: off 10 s, back on
devusb on all # every port
devusb --json status # machine-readable output--hub picks which hub to talk to — a YKUSH serial or a generic hub
location:
devusb --hub Y3N14127 status # YKUSH by serial
devusb --hub 35-2.1.4 status # generic hub by location
devusb --hub 35-2.1.4 cycle 2 # cycle a port on that hubWith exactly one YKUSH attached, --hub is optional. Generic hubs must
always be named: they share the listing with the rest of the machine, so
devusb will not pick one for you.
Cutting power to the wrong port has real consequences — an unmounted disk, a dark monitor, a dropped network link. Two rules apply everywhere:
- Never cut power to a device mid-flash.
- After power returns, give the device a few seconds to re-enumerate before talking to it.
Generic hubs get an extra guard, because a typical machine lists a lot of hubs that have nothing to do with the bench. devusb refuses to switch off a port whose downstream subtree contains storage, a display, input hardware, or networking:
$ devusb --hub 35-4.1 off 1
error: refusing to cut power to 35-4.1 port 1: network device attached
(0bda:8153 Realtek USB 10/100/1000 LAN 1010000CD). Pass force to override.The check follows the whole subtree, not just the directly attached device —
cutting a port kills everything below it, including devices on a downstream
hub. devusb list marks guarded ports, and devusb status shows what is
attached to each port so you can see what a port number really means:
$ devusb --hub 35-4.1 status
port 1: on [0bda:8153 Realtek USB 10/100/1000 LAN 1010000CD] (protected)
port 2: on [05e3:0620 GenesysLogic USB3.2 Hub, USB 3.20, 4 ports, ppps] (protected)
port 3: on
port 4: on [0bda:0411 Generic 4-Port USB 3.0 Hub, USB 3.00, 4 ports, ppps]Pass --force (CLI) or force=True (MCP) to override. Detection is
best-effort keyword matching on what uhubctl reports, so treat it as a
seatbelt, not a proof: check status before switching an unfamiliar port.
YKUSH hubs have no guard — every port is a dedicated bench port.
The YKUSH3 is built on Microchip hub silicon (USB5744/USB2744) that also
supports generic per-port power switching, so a YKUSH can appear under both
backends. Don't drive the same hub both ways: the YKUSH firmware doesn't
track changes made behind its back, and port numbering differs between the
two paths (e.g. YKUSH port 1 is internal hub port 3). Pick one controller per
hub — prefer the ykush backend, which is purpose-built.
devusb-mcp runs an MCP server over stdio exposing list_hubs,
port_status, port_on, port_off, and power_cycle — with agent-facing
safety guidance baked into the tool descriptions, including the instruction
to call port_status before switching a generic hub port and to confirm with
the user before forcing past the guard.
Register with Claude Code:
claude mcp add --scope user devusb devusb-mcpAny MCP-capable agent host works the same way — point it at the
devusb-mcp executable.
- Acroname programmable hubs — the premium lab option.
- Paired USB 2.0 / 3.x port switching for generic hubs: a physical port often appears as separate hub instances per speed, and cutting only one leaves the device half-powered. uhubctl handles this pairing; devusb currently exposes each instance separately.
pip install -e ".[dev]"
pytestThe suite runs against fake HID and uhubctl layers — no hardware required. The uhubctl fake renders real uhubctl output so the parser is exercised.
Before pushing, run it once with the host's tools hidden:
env PATH=/usr/bin:/bin "$(command -v python3)" -m pytest -qThe fakes are meant to be hermetic, but a developer machine has uhubctl
installed and CI does not — so a test that quietly reaches past the fake to
the real binary still passes locally and fails in CI. Stripping PATH catches
that before you push. ($(command -v python3) resolves the interpreter
before PATH is emptied; a bare pytest would no longer be findable.)
MIT