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...
% 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.0.tar.gz (32.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.0-py3-none-any.whl (27.5 kB view details)

Uploaded Python 3

File details

Details for the file uart_helper-1.0.0.tar.gz.

File metadata

  • Download URL: uart_helper-1.0.0.tar.gz
  • Upload date:
  • Size: 32.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.0.tar.gz
Algorithm Hash digest
SHA256 3363423160d6eb2a3c94967069eeef368a306cbc0264a8ceec5812cd20443307
MD5 cf71e32df636b30cff2aa6cda5416ea6
BLAKE2b-256 0546e300e74c1d71d832a03e548ea1d42caa337176a2d2cc8561852701256d60

See more details on using hashes here.

Provenance

The following attestation bundles were made for uart_helper-1.0.0.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.0-py3-none-any.whl.

File metadata

  • Download URL: uart_helper-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 27.5 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.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4f3948d58d3c80ca9640fb9429078356003fb44bc0b7ea52acd0a3089ec6751f
MD5 88bd586af0ba273b765a70414e3d16e7
BLAKE2b-256 de6aaf794b4d21955fb31c6364fa73b3ac3dcb5d8436137a78f1ca61a4f65e8a

See more details on using hashes here.

Provenance

The following attestation bundles were made for uart_helper-1.0.0-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