A Python USB device monitoring and communication framework built on pyusb/libusb
Project description
usb-helper
A cross-platform Python framework for USB device monitoring, bulk transfer, and SCSI-over-Bulk communication. Built on pyusb/libusb.
Includes a CLI tool for quick device diagnostics, plug/unplug monitoring (JSONL output), and TOML-based device profile management.
Installation
% pip install usb-helper
Platform prerequisites
| Platform | Requirement |
|---|---|
| macOS | brew install libusb |
| Linux | sudo apt install libusb-1.0-0-dev |
| Windows | Install WinUSB driver via Zadig |
Verify your setup:
% usb-helper --check
CLI Usage
List connected USB devices
% usb-helper
Found 3 device(s):
usb:1-3 1234:abcd "TestDev" [Bulk mode] (mode=bulk)
usb:1-5 5678:1001
usb:2-1 9abc:def0
% usb-helper --json
{"status": true, "action": "scan", "meta": {...}, "data": [{"device_id": "usb:1-3", "vid": "1234", "pid": "abcd", ...}]}
Filter devices
% usb-helper --vid 1234 # single vendor ID
% usb-helper --vid 1234 --vid 5678 # multiple vendor IDs
% usb-helper --vid 1234 --pid abcd # vendor + product ID
% usb-helper --name "Test*" # product name glob pattern
Multiple --vid, --pid, and --name values are cross-producted into match rules. For example, --vid 1234 --vid 5678 --pid abcd creates two rules: 1234:abcd and 5678:abcd.
Monitor plug/unplug events
% usb-helper --listen
Outputs JSONL (one JSON object per line) — designed for piping into AI agents or automation scripts:
{"status": true, "action": "init", "meta": {...}, "data": [{"device_id": "usb:1-3", "vid": "1234", "pid": "abcd", "name": "TestDev", ...}]}
{"status": true, "action": "plug", "meta": {...}, "data": [{"device_id": "usb:2-5", "vid": "1234", "pid": "abcd", ...}]}
{"status": true, "action": "unplug", "meta": {...}, "data": [{"device_id": "usb:2-5", "vid": "1234", "pid": "abcd", ...}]}
{"status": true, "action": "stop", "meta": {...}, "data": []}
Events: init (startup device list), plug, unplug, error, stop (Ctrl+C).
Combine with filters:
% usb-helper --listen --vid 1234 --interval 1000
% usb-helper --listen --profile sample
Error output
When libusb or pyusb is missing, all modes (including --listen) emit a structured error:
{"status": false, "action": "error", "meta": {...}, "error": -1, "errorMessage": "No libusb backend found. Install libusb: macOS: brew install libusb | Linux: ..."}
Device Profiles
Profiles are TOML files that define named sets of device match rules. Instead of typing --vid and --pid every time, save your device definitions once and reference them by name.
Profile search directories (first match wins)
./usb-helper.d/— current working directory (project-level)~/.config/usb-helper/— user config (shared across projects)
Create a profile
% mkdir -p ~/.config/usb-helper
% cat > ~/.config/usb-helper/sample.toml << 'EOF'
description = "My USB devices"
[[rules]]
vid = "1234"
pid = "abcd"
label = "Bulk mode"
[rules.metadata]
mode = "bulk"
[[rules]]
vid = "1234"
pid = "0002"
label = "Storage mode"
[rules.metadata]
mode = "storage"
EOF
Each [[rules]] entry supports these optional fields:
| Field | Description | Example |
|---|---|---|
vid |
Vendor ID (hex string) | "1234" |
pid |
Product ID (hex string) | "abcd" |
label |
Human-readable rule name | "Bulk mode" |
name |
Product name glob pattern | "Test*" |
serial |
Serial number glob pattern | "SN-*" |
metadata |
Arbitrary key-value pairs | {mode = "bulk"} |
Use a profile
% usb-helper --profile sample
% usb-helper --profile sample --listen
% usb-helper --profile sample --json
Or specify a TOML file directly:
% usb-helper --config ./my-devices.toml
Profile rules and CLI flags (--vid, --pid, --name) are merged — you can add extra filters on top of a profile.
List available profiles
% usb-helper profiles
Config directories (search order):
./usb-helper.d
/Users/you/.config/usb-helper
Available profiles (1):
sample
My USB devices
6 rule(s) — /Users/you/.config/usb-helper/sample.toml
% usb-helper profiles --json
{"status": true, "action": "profiles", "meta": {...}, "data": {"config_dirs": [...], "profiles": [{"name": "sample", ...}]}}
Project-level profiles
Place TOML files in usb-helper.d/ in your project root. These take priority over user-level profiles with the same name:
my-project/
usb-helper.d/
my-devices.toml ← project-specific config
src/
main.py
Python API
Device monitoring
from usb_helper import USBMonitor, DeviceMatchRule
rules = [
DeviceMatchRule(vid=0x1234, pid=0xABCD, label="Bulk mode"),
DeviceMatchRule(vid=0x1234, pid=0x0002, label="Storage mode"),
]
monitor = USBMonitor(match_rules=rules, poll_interval_ms=500)
# One-time scan
for identity, rule in monitor.scan_once():
print(f"Found: {identity} (rule: {rule.label})")
# Continuous monitoring
monitor.on_device_event = lambda event: print(f"[{event.event_type.value}] {event.device}")
monitor.run_forever() # Ctrl+C to stop
Bulk transfer
from usb_helper import BulkDevice, DeviceIdentity
identity = DeviceIdentity(vid=0x1234, pid=0xABCD, bus=1, address=3)
with BulkDevice(identity, frame_size=65536) as device:
# Write (auto-splits into frames)
result = device.bulk_write(data)
# Read
result = device.bulk_read(512, timeout_ms=5000)
if result.ok:
print(f"Received {len(result.data)} bytes")
SCSI-over-Bulk
from usb_helper import SCSIDevice, DeviceIdentity
identity = DeviceIdentity(vid=0x1234, pid=0x0002, bus=1, address=5)
with SCSIDevice(identity) as device:
# Send SCSI command (CBW → data → CSW)
cdb = bytes([0xCB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
result = device.send_command(cdb=cdb, data_in_length=2, timeout_ms=5000)
if result.ok:
print(f"Response: {result.data.hex()}")
Load profiles programmatically
from usb_helper import load_profile, load_profile_by_name, list_profiles
# By name (searches config dirs)
profile = load_profile_by_name("sample")
# By path
profile = load_profile("./my-devices.toml")
# List all
for p in list_profiles():
print(f"{p.name}: {p.description} ({p.rule_count} rules)")
# Use profile rules with USBMonitor
monitor = USBMonitor(match_rules=profile.rules)
Runtime metadata
from usb_helper import get_meta
meta = get_meta()
# {"usb_helper": "0.1.0", "python": "3.12.3", "platform": "...", "os": "Darwin", "arch": "arm64", "pyusb": "1.2.1", "libusb": "usb.backend.libusb1"}
Running Tests
Tests are fully mock-based — no real USB hardware needed.
% git clone https://github.com/changyy/py-usb-helper.git
% cd py-usb-helper
% pip install -e ".[dev]"
% python -m pytest tests/ -v
Current test suite: 102 tests covering types, CBW/CSW protocol, BulkDevice, SCSIDevice, USBMonitor, config loader, CLI, and meta.
To run with coverage:
% python -m pytest tests/ --cov=usb_helper --cov-report=term-missing
Project Structure
py-usb-helper/
src/usb_helper/
__init__.py Package exports
types.py DeviceIdentity, DeviceMatchRule, DeviceEvent, TransferResult
_cbw.py CBW/CSW binary protocol (SCSI command blocks)
device.py USBDevice abstract base class
bulk_device.py BulkDevice — pyusb wrapper with frame-based writes
scsi_device.py SCSIDevice — SCSI-over-Bulk with CBW→data→CSW
monitor.py USBMonitor — polling-based attach/detach detection
config.py TOML profile loader
cli.py CLI entry point (usb-helper command)
tests/
test_types.py DeviceIdentity, DeviceMatchRule matching
test_cbw.py CBW build/CSW parse, tag wrapping
test_bulk_device.py BulkDevice open/close/read/write (22 tests)
test_scsi_device.py SCSIDevice command/error/retry (8 tests)
test_monitor.py USBMonitor scan/attach/detach (11 tests)
test_config.py TOML loader, profile search, sample validation (18 tests)
test_cli.py CLI argument parsing, rule building, meta (23 tests)
examples/
sample.toml Sample device profile example
pyproject.toml
README.md
Requirements
- Python 3.10+
- libusb (platform-specific, see installation)
- pyusb >= 1.2.1
License
MIT © Yuan-Yi Chang
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file usb_helper-1.1.0.tar.gz.
File metadata
- Download URL: usb_helper-1.1.0.tar.gz
- Upload date:
- Size: 47.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e5e6f67f2104bff8b3fd561d91651662eb066c7c2c58f2b555b87ece1dea2b2
|
|
| MD5 |
c4f508aed76e11efed30d9a12532c7c5
|
|
| BLAKE2b-256 |
0b9536c4a4219c18ea7f97d57b47338a284f8f377115dd3626fa629eeb0662f8
|
Provenance
The following attestation bundles were made for usb_helper-1.1.0.tar.gz:
Publisher:
python-publish.yml on changyy/py-usb-helper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
usb_helper-1.1.0.tar.gz -
Subject digest:
1e5e6f67f2104bff8b3fd561d91651662eb066c7c2c58f2b555b87ece1dea2b2 - Sigstore transparency entry: 1199322479
- Sigstore integration time:
-
Permalink:
changyy/py-usb-helper@2b95c05847baabf7b46fd40a95659efad93a04a5 -
Branch / Tag:
refs/tags/1.1.0 - Owner: https://github.com/changyy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@2b95c05847baabf7b46fd40a95659efad93a04a5 -
Trigger Event:
release
-
Statement type:
File details
Details for the file usb_helper-1.1.0-py3-none-any.whl.
File metadata
- Download URL: usb_helper-1.1.0-py3-none-any.whl
- Upload date:
- Size: 37.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
34e1258f27f940361bcccd7afe2888adcc6dcac755c4d311daafc4ddbb982957
|
|
| MD5 |
a685b63c773f34837620743bd40bd1d9
|
|
| BLAKE2b-256 |
48dac782a27f226bbe9d16e765d4ebd8a0f72a03007cd12d60af5178eef1263c
|
Provenance
The following attestation bundles were made for usb_helper-1.1.0-py3-none-any.whl:
Publisher:
python-publish.yml on changyy/py-usb-helper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
usb_helper-1.1.0-py3-none-any.whl -
Subject digest:
34e1258f27f940361bcccd7afe2888adcc6dcac755c4d311daafc4ddbb982957 - Sigstore transparency entry: 1199322481
- Sigstore integration time:
-
Permalink:
changyy/py-usb-helper@2b95c05847baabf7b46fd40a95659efad93a04a5 -
Branch / Tag:
refs/tags/1.1.0 - Owner: https://github.com/changyy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@2b95c05847baabf7b46fd40a95659efad93a04a5 -
Trigger Event:
release
-
Statement type: