Skip to main content

A Python UART/serial device monitoring and communication framework built on pyserial

Project description

uart-helper

PyPI PyPI Downloads

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)

  1. ./uart-helper.d/ — current working directory (project-level)
  2. ~/.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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

uart_helper-1.0.1.tar.gz (35.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

uart_helper-1.0.1-py3-none-any.whl (29.8 kB view details)

Uploaded Python 3

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

Hashes for uart_helper-1.0.1.tar.gz
Algorithm Hash digest
SHA256 4f913f8f15adb034617ebe8a7f3b8d265d86b27c1972947fe556db49cd330802
MD5 1936bb1bcd1085c66127e3664ed4e4ce
BLAKE2b-256 715f61b53e3fd747c949af0a404a1d7d708cd722d7f22360b33fec09077473c6

See more details on using hashes here.

Provenance

The following attestation bundles were made for uart_helper-1.0.1.tar.gz:

Publisher: python-publish.yml on changyy/py-uart-helper

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for uart_helper-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ec0619383b4a9a42600eb17f59f20f0955493e7318b9552a0ae4c5a41809bd94
MD5 ea273b39a8b8efa30a27ada5ab44552d
BLAKE2b-256 4a0f0b3a502f61fccc82f9130ce967cba3a2ee7ab1f5035732d40cf05b7a2993

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page