A Python UART/serial device monitoring and communication framework built on pyserial
Project description
uart-helper
A cross-platform Python framework for UART/serial port monitoring, command sending, and data receiving. Built on pyserial.
Includes a CLI tool for quick serial port diagnostics, plug/unplug monitoring (JSONL output), text/hex command communication, and TOML-based device profile management.
Installation
% pip install uart-helper
Verify your setup:
% uart-helper --check
CLI Usage
List connected serial ports
% uart-helper
Found 2 port(s):
/dev/ttyUSB0 aaaa:0001 "USB Serial" serial=0001 [Temp Sensor] (role=sensor)
/dev/ttyS0
% uart-helper --json
{"status": true, "action": "scan", "meta": {...}, "data": [{"device": "/dev/ttyUSB0", "vid": "aaaa", "pid": "0001", ...}]}
Filter devices
% uart-helper --vid aaaa # single vendor ID
% uart-helper --vid aaaa --vid bbbb # multiple vendor IDs
% uart-helper --vid aaaa --pid 0001 # vendor + product ID
% uart-helper --name "USB*" # description glob pattern
% uart-helper --device "/dev/ttyUSB*" # device path glob pattern
Multiple --vid, --pid, --name, and --device values are cross-producted into match rules. For example, --vid aaaa --vid bbbb --pid 0001 creates two rules: aaaa:0001 and bbbb:0001.
Monitor plug/unplug events
% uart-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": "/dev/ttyUSB0", "vid": "aaaa", "pid": "0001", ...}]}
{"status": true, "action": "plug", "meta": {...}, "data": [{"device": "/dev/ttyUSB1", "vid": "bbbb", "pid": "0002", ...}]}
{"status": true, "action": "unplug", "meta": {...}, "data": [{"device": "/dev/ttyUSB1", "vid": "bbbb", "pid": "0002", ...}]}
{"status": true, "action": "stop", "meta": {...}, "data": []}
Events: init (startup port list), plug, unplug, error, stop (Ctrl+C).
Combine with filters:
% uart-helper --listen --vid aaaa --interval 1000
% uart-helper --listen --profile sample
Send commands
% uart-helper send --port /dev/ttyUSB0 --crlf "AT"
Sent 4 bytes to /dev/ttyUSB0
Received 4 bytes:
TEXT: OK
HEX: 4f 4b 0d 0a
% uart-helper send --port /dev/ttyUSB0 --hex "FF 01 02 03"
Sent 4 bytes to /dev/ttyUSB0
Received 2 bytes:
HEX: 06 00
Hex input supports multiple formats (case-insensitive):
% uart-helper send --port /dev/ttyUSB0 --hex "FF 01 02" # space-separated
% uart-helper send --port /dev/ttyUSB0 --hex "ff0102" # continuous (no spaces)
% uart-helper send --port /dev/ttyUSB0 --hex "Ff:01:aB" # colon-separated, mixed case
% uart-helper send --port /dev/ttyUSB0 --hex "0xFF 0x01 0x02" # 0x-prefixed
Specify UART parameters:
% uart-helper send --port /dev/ttyUSB0 --baudrate 9600 --crlf "AT"
% uart-helper send --port /dev/ttyUSB0 --baudrate 9600 --parity E --stopbits 2 --crlf "AT"
% uart-helper send --port /dev/ttyUSB0 --crlf "AT" --json
{"status": true, "action": "send", "meta": {...}, "data": {"port": "/dev/ttyUSB0", "sent_bytes": 4, "received_bytes": 4, "received_hex": "4f 4b 0d 0a", "received_text": "OK\r\n"}}
Interactive serial monitor
% uart-helper monitor --port /dev/ttyUSB0
Monitoring /dev/ttyUSB0 (baud=115200) — Ctrl+C to stop
──────────────────────────────────────────────────
Hello from device...
Screen-style CLI compatibility:
% uart-helper screen -U /dev/tty.usbserial-110 115200
Monitoring /dev/tty.usbserial-110 (baud=115200) — Ctrl+C to stop
──────────────────────────────────────────────────
In screen mode, stdin is key-by-key (no Enter needed) and shortcuts are supported:
Ctrl+A, H toggle HEX mirror output on/off
Ctrl+A, A send a literal Ctrl+A byte (0x01)
HEX mirror can include line number and datetime:
% uart-helper screen -U /dev/tty.usbserial-110 115200 --datetime-format "%Y-%m-%d_%H:%M:%S"
# Example HEX mirror line:
[HEX #000123 2026-03-26_22:45:10] 48 65 6c 6c 6f
Single-file debug logging:
% uart-helper screen -U /dev/tty.usbserial-110 115200 --log-file /tmp/uart-debug.log
# log line format:
# ts=2026-03-26T14:45:10.123456+00:00 bytes=5 hex=48 65 6c 6c 6f text=Hello\r\n
% uart-helper monitor --port /dev/ttyUSB0 --hex
[2026-03-26T12:00:00+00:00] 48 65 6c 6c 6f
% uart-helper monitor --port /dev/ttyUSB0 --output-hex
48 65 6c 6c 6f
% uart-helper monitor --port /dev/ttyUSB0 --stdin
Monitoring /dev/ttyUSB0 (baud=115200) — Ctrl+C to stop
──────────────────────────────────────────────────
# type into terminal and press Enter to send each line to UART
% uart-helper monitor --port /dev/ttyUSB0 --stdin --output-hex
48 65 6c 6c 6f
% uart-helper monitor --port /dev/ttyUSB0 --output-merge
48 65 6c 6c 6f
Hello
% uart-helper monitor --port /dev/ttyUSB0 --stdin --output-file /tmp/output.log --output-file-hex /tmp/output.hex
# stdout keeps printing, while text/hex are also appended to files
% uart-helper monitor --port /dev/ttyUSB0 --output-file-hex-mixed /tmp/output_mixed.log
# each chunk appends:
# line 1: hex
# line 2: text/binary
% uart-helper monitor --port /dev/ttyUSB0 --json
{"status": true, "action": "monitor_start", "meta": {...}, "data": {"port": "/dev/ttyUSB0", "baudrate": 115200}}
{"status": true, "action": "data", "meta": {...}, "data": {"timestamp": "...", "bytes": 5, "hex": "48 65 6c 6c 6f", "text": "Hello"}}
Error output
When pyserial is missing, all modes emit a structured error:
{"status": false, "action": "error", "meta": {...}, "error": -1, "errorMessage": "pyserial is not installed. Install with: pip install pyserial"}
Device Profiles
Profiles are TOML files that define named sets of port match rules and default UART settings. Instead of typing --vid, --pid, and --baudrate every time, save your device definitions once and reference them by name.
Profile search directories (first match wins)
./uart-helper.d/— current working directory (project-level)~/.config/uart-helper/— user config (shared across projects)
Create a profile
% mkdir -p ~/.config/uart-helper
% cat > ~/.config/uart-helper/sample.toml << 'EOF'
description = "My UART devices"
[defaults]
baudrate = 115200
bytesize = 8
parity = "N"
stopbits = 1
timeout = 1.0
[[rules]]
vid = "aaaa"
pid = "0001"
label = "Temp Sensor"
[rules.metadata]
role = "sensor"
[[rules]]
vid = "bbbb"
pid = "0002"
label = "Motor Controller"
[rules.metadata]
role = "controller"
EOF
Each [[rules]] entry supports these optional fields:
| Field | Description | Example |
|---|---|---|
vid |
USB Vendor ID (hex string) | "aaaa" |
pid |
USB Product ID (hex string) | "0001" |
label |
Human-readable rule name | "Temp Sensor" |
name |
Port description glob pattern | "USB*" |
serial |
Serial number glob pattern | "SN-*" |
device |
Device path glob pattern | "/dev/ttyUSB*" |
metadata |
Arbitrary key-value pairs | {role = "sensor"} |
The [defaults] section sets UART parameters for the profile:
| Field | Description | Default |
|---|---|---|
baudrate |
Baud rate | 115200 |
bytesize |
Data bits (5, 6, 7, 8) | 8 |
parity |
Parity ("N", "E", "O", "M", "S") |
"N" |
stopbits |
Stop bits (1, 1.5, 2) | 1 |
timeout |
Read timeout in seconds | 1.0 |
Use a profile
% uart-helper --profile sample
% uart-helper --profile sample --listen
% uart-helper --profile sample --json
% uart-helper send --port /dev/ttyUSB0 --profile sample --crlf "AT"
Or specify a TOML file directly:
% uart-helper --config ./my-devices.toml
Profile rules and CLI flags (--vid, --pid, --name, --device) are merged — you can add extra filters on top of a profile.
List available profiles
% uart-helper profiles
Config directories (search order):
./uart-helper.d
/Users/you/.config/uart-helper
Available profiles (1):
sample
My UART devices
2 rule(s), baud=115200 — /Users/you/.config/uart-helper/sample.toml
% uart-helper profiles --json
{"status": true, "action": "profiles", "meta": {...}, "data": {"config_dirs": [...], "profiles": [{"name": "sample", ...}]}}
Project-level profiles
Place TOML files in uart-helper.d/ in your project root. These take priority over user-level profiles with the same name:
my-project/
uart-helper.d/
my-devices.toml ← project-specific config
src/
main.py
Python API
Port monitoring
from uart_helper import SerialMonitor, PortMatchRule
rules = [
PortMatchRule(vid=0xAAAA, pid=0x0001, label="Temp Sensor"),
PortMatchRule(vid=0xBBBB, pid=0x0002, label="Motor Controller"),
]
monitor = SerialMonitor(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_port_event = lambda event: print(f"[{event.event_type.value}] {event.port}")
monitor.run_forever() # Ctrl+C to stop
UART communication
from uart_helper import UARTDevice, PortIdentity, UARTConfig
identity = PortIdentity(device="/dev/ttyUSB0")
config = UARTConfig(baudrate=115200)
with UARTDevice(identity, config) as dev:
# Send text command (e.g., AT command)
result = dev.send_command("AT\r\n")
print(result.text)
# Send hex bytes
result = dev.send_hex("FF 01 02 03")
if result.ok:
print(f"Received {len(result.data)} bytes: {result.data.hex()}")
# Low-level write + read
dev.write(b"\x01\x02\x03")
result = dev.read(1024, timeout_ms=2000)
print(result.data)
# Read until terminator
result = dev.read_until(terminator=b"\r\n")
print(result.text)
Load profiles programmatically
from uart_helper import load_profile, load_profile_by_name, list_profiles
# By name (searches config dirs)
profile = load_profile_by_name("sample")
print(f"Default baud: {profile.defaults.baudrate}")
# 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 SerialMonitor
monitor = SerialMonitor(match_rules=profile.rules)
Runtime metadata
from uart_helper import get_meta
meta = get_meta()
# {"uart_helper": "1.0.0", "python": "3.12.3", "platform": "...", "os": "Darwin", "arch": "arm64", "pyserial": "3.5"}
Running Tests
Tests are fully mock-based — no real serial hardware needed.
% git clone https://github.com/changyy/py-uart-helper.git
% cd py-uart-helper
% pip install -e ".[dev]"
% python -m pytest tests/ -v
To run with coverage:
% python -m pytest tests/ --cov=uart_helper --cov-report=term-missing
Project Structure
py-uart-helper/
src/uart_helper/
__init__.py Package exports
types.py PortIdentity, PortMatchRule, UARTConfig, TransferResult, PortEvent
device.py SerialDevice abstract base class
uart_device.py UARTDevice — pyserial wrapper with text/hex send/receive
monitor.py SerialMonitor — polling-based attach/detach detection
config.py TOML profile loader with UART defaults
cli.py CLI entry point (uart-helper command)
tests/
examples/
sample.toml Sample device profile
pyproject.toml
README.md
Requirements
- Python 3.10+
- pyserial >= 3.5
Related
- py-usb-helper — USB device monitoring and bulk/SCSI communication
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 uart_helper-1.0.1.tar.gz.
File metadata
- Download URL: uart_helper-1.0.1.tar.gz
- Upload date:
- Size: 35.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f913f8f15adb034617ebe8a7f3b8d265d86b27c1972947fe556db49cd330802
|
|
| MD5 |
1936bb1bcd1085c66127e3664ed4e4ce
|
|
| BLAKE2b-256 |
715f61b53e3fd747c949af0a404a1d7d708cd722d7f22360b33fec09077473c6
|
Provenance
The following attestation bundles were made for uart_helper-1.0.1.tar.gz:
Publisher:
python-publish.yml on changyy/py-uart-helper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
uart_helper-1.0.1.tar.gz -
Subject digest:
4f913f8f15adb034617ebe8a7f3b8d265d86b27c1972947fe556db49cd330802 - Sigstore transparency entry: 1185958880
- Sigstore integration time:
-
Permalink:
changyy/py-uart-helper@008b95e84ddbdf004ba5ecd31097227a4340e0b6 -
Branch / Tag:
refs/tags/1.0.1 - Owner: https://github.com/changyy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@008b95e84ddbdf004ba5ecd31097227a4340e0b6 -
Trigger Event:
release
-
Statement type:
File details
Details for the file uart_helper-1.0.1-py3-none-any.whl.
File metadata
- Download URL: uart_helper-1.0.1-py3-none-any.whl
- Upload date:
- Size: 29.8 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 |
ec0619383b4a9a42600eb17f59f20f0955493e7318b9552a0ae4c5a41809bd94
|
|
| MD5 |
ea273b39a8b8efa30a27ada5ab44552d
|
|
| BLAKE2b-256 |
4a0f0b3a502f61fccc82f9130ce967cba3a2ee7ab1f5035732d40cf05b7a2993
|
Provenance
The following attestation bundles were made for uart_helper-1.0.1-py3-none-any.whl:
Publisher:
python-publish.yml on changyy/py-uart-helper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
uart_helper-1.0.1-py3-none-any.whl -
Subject digest:
ec0619383b4a9a42600eb17f59f20f0955493e7318b9552a0ae4c5a41809bd94 - Sigstore transparency entry: 1185958889
- Sigstore integration time:
-
Permalink:
changyy/py-uart-helper@008b95e84ddbdf004ba5ecd31097227a4340e0b6 -
Branch / Tag:
refs/tags/1.0.1 - Owner: https://github.com/changyy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-publish.yml@008b95e84ddbdf004ba5ecd31097227a4340e0b6 -
Trigger Event:
release
-
Statement type: