Skip to content

changyy/py-uart-helper

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

About

uart-helper is a Python UART/serial utility for fast port scanning, live monitoring, command send/receive, and screen-style interactive sessions.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages