PySerial Driver¶
jumpstarter-driver-pyserial provides functionality for serial port
communication.
Installation¶
$ pip3 install --extra-index-url https://pkg.jumpstarter.dev/simple jumpstarter-driver-pyserial
Configuration¶
Example configuration:
export:
serial:
type: jumpstarter_driver_pyserial.driver.PySerial
config:
url: "/dev/ttyUSB0"
baudrate: 115200
cps: 10 # Optional: throttle to 10 characters per second
Example configuration to send commands to a MCU with DTR/RTS controlling boot process over serial port, with –no-output (fire-and-forget mode):
export:
serial:
type: jumpstarter_driver_pyserial.driver.PySerial
config:
url: "/dev/ttyUSB0"
baudrate: 115200
disable_hupcl: true # Prevents MCU reset on each command/close.
#cps: Avoid using cps when using --no-output.
Config parameters¶
Parameter |
Description |
Type |
Required |
Default |
|---|---|---|---|---|
url |
The serial port to connect to, in pyserial format |
str |
yes |
|
baudrate |
The baudrate to use for the serial connection |
int |
no |
115200 |
check_present |
Check if the serial port exists during exporter initialization, disable if you are connecting to a dynamically created port (i.e. USB from your DUT) |
bool |
no |
True |
cps |
Characters per second throttling limit. When set, data transmission will be throttled to simulate slow typing. Useful for devices that can’t handle fast input |
float |
no |
None |
disable_hupcl |
Disable HUPCL on POSIX systems to avoid toggling DTR/RTS on close (can prevent MCU reset on serial disconnect) |
bool |
no |
False |
always_on |
Keep the serial port open even when no clients are connected. When false (the default), the port is opened on first client attach and closed when the last client detaches, freeing it for other tools (e.g. esptool). When true, the port stays open for the lifetime of the exporter, which can be useful to avoid HUPCL-triggered resets between sessions |
bool |
no |
False |
NVDemuxSerial Driver¶
The NVDemuxSerial driver provides serial access to NVIDIA Tegra demultiplexed UART channels using the nv_tcu_demuxer tool. It automatically handles device reconnection when the target device restarts.
The nv_tcu_demuxer tool can be obtained from the NVIDIA Jetson BSP, at this path: Linux_for_Tegra/tools/demuxer/nv_tcu_demuxer.
Multi-Instance Support¶
Multiple driver instances can share a single demuxer process by specifying different target channels. This allows simultaneous access to multiple UART channels (CCPLEX, BPMP, SCE, etc.) from the same physical device.
Configuration¶
Single channel example:¶
export:
ccplex:
type: jumpstarter_driver_pyserial.nvdemux.driver.NVDemuxSerial
config:
demuxer_path: "/opt/nvidia/nv_tcu_demuxer"
# device defaults to auto-detect NVIDIA Tegra On-Platform Operator
# chip defaults to T264 (Thor), use T234 for Orin
Multiple channels example:¶
export:
ccplex:
type: jumpstarter_driver_pyserial.nvdemux.driver.NVDemuxSerial
config:
demuxer_path: "/opt/nvidia/nv_tcu_demuxer"
target: "CCPLEX: 0"
chip: "T264"
bpmp:
type: jumpstarter_driver_pyserial.nvdemux.driver.NVDemuxSerial
config:
demuxer_path: "/opt/nvidia/nv_tcu_demuxer"
target: "BPMP: 1"
chip: "T264"
sce:
type: jumpstarter_driver_pyserial.nvdemux.driver.NVDemuxSerial
config:
demuxer_path: "/opt/nvidia/nv_tcu_demuxer"
target: "SCE: 2"
chip: "T264"
Config parameters¶
Parameter |
Description |
Type |
Required |
Default |
|---|---|---|---|---|
demuxer_path |
Path to the |
str |
yes |
|
device |
Device path or glob pattern for auto-detection |
str |
no |
|
target |
Target channel to extract from demuxer output |
str |
no |
|
chip |
Chip type for demuxer ( |
str |
no |
|
baudrate |
Baud rate for the serial connection |
int |
no |
115200 |
cps |
Characters per second throttling limit |
float |
no |
None |
timeout |
Timeout in seconds waiting for demuxer to detect pts |
float |
no |
10.0 |
poll_interval |
Interval in seconds to poll for device reappearance after disconnect |
float |
no |
1.0 |
Device Auto-Detection¶
The device parameter supports glob patterns for automatic device discovery:
# Auto-detect any NVIDIA Tegra On-Platform Operator device (default)
device: "/dev/serial/by-id/usb-NVIDIA_Tegra_On-Platform_Operator_*-if01"
# Specific serial number
device: "/dev/serial/by-id/usb-NVIDIA_Tegra_On-Platform_Operator_ABC123-if01"
# Direct device path (no glob)
device: "/dev/ttyUSB0"
Auto-Recovery¶
When the target device restarts (e.g., power cycle), the serial device disappears and the demuxer exits. The driver automatically:
Detects the device disconnection
Polls for the device to reappear
Restarts the demuxer with the new device
Discovers the new pts path (which changes on each restart)
Active connections will receive errors when the device disconnects. Clients should reconnect, and the driver will wait for the device to be available again.
Configuration Validation / Limitations¶
When using multiple driver instances, all instances must have compatible configurations:
demuxer_path: Must be identical across all instances
device: Must be identical across all instances
chip: Must be identical across all instances
target: Must be unique for each instance (no duplicates allowed)
If these requirements are not met, the driver will raise a ValueError during initialization.
Usage¶
The pyserial driver provides two CLI commands for interacting with serial ports:
console¶
Start an interactive serial console with direct terminal access.
j serial console
Exit the console by pressing CTRL+B three times.
Use --observe to attach in read-only (watch-only) mode. Observers receive
serial output but cannot write, so they can watch a session another client is
driving without interfering:
j serial console --observe
pipe¶
Pipe serial port data to stdout or a file. Automatically detects if stdin is piped and enables bidirectional mode.
When stdin is used, commands are sent until EOF, then continues monitoring serial output until Ctrl+C.
Use --no-output for fire-and-forget mode: send stdin to serial and exit at EOF without reading serial output.
# Log serial output to stdout
j serial pipe
# Log serial output to a file
j serial pipe -o serial.log
# Send command to serial, then continue monitoring output
echo "hello" | j serial pipe
# Send commands from file, then continue monitoring output
cat commands.txt | j serial pipe -o serial.log
# Force bidirectional mode (interactive)
j serial pipe -i
# Append to log file instead of overwriting
j serial pipe -o serial.log -a
# Disable stdin input even when piped
cat data.txt | j serial pipe --no-input
# Fire-and-forget: send stdin to serial and exit at EOF (no serial output)
cat commands.txt | j serial pipe --no-output
Options¶
-o, --output FILE: Write serial output to a file instead of stdout-i, --input: Force enable stdin to serial port (auto-detected if piped)--no-input: Disable stdin to serial port, even if stdin is piped-a, --append: Append to output file instead of overwriting--no-output: Disable serial output handling (stdin -> serial only, exits at EOF)--observe: Watch-only mode (read-only). Use when another session holds exclusive write access.
Notes:
--no-outputcannot be combined with--outputor--append.--no-outputrequires stdin input (piped stdin or--input).--observecannot be combined with--inputor--no-output.
Exit with Ctrl+C.
Serial console sharing and observe mode¶
The serial console fans out to multiple clients so that several holders of a shared lease can attach to the same port at once. This pairs well with lease sharing: the lease owner shares access, and everyone can watch the console while one client drives it.
Access is coordinated by an exclusive write token:
The first client to open an interactive session (
consoleorpipewith input) acquires the write token and may write to the port.Additional clients attach as observers, receiving all serial output (including a replay of recent scrollback on attach) but unable to write. A client observes when it passes
--observe, or when apipehas no stdin to forward (a plain logging/monitoring pipe never takes the write token, so it cannot lock out an interactive user).There is no silent fallback for interactive sessions: running
console(orpipe -i) while another client holds the token fails with an error telling you to retry with--observe. Running an interactive command means you intend to type, so we surface the conflict instead of quietly dropping you to read-only.When the write-token holder disconnects, the token is released and another client may take it.
release-console¶
Force-release the write token if the holder disconnected uncleanly and left it stuck, freeing another client to take over writing:
j serial release-console
console-status¶
Inspect the current session: whether the write token is held, how many observers are attached, and how much scrollback is buffered:
j serial console-status
Write token held: yes
Observers: 2
Total clients: 3
Reader running: True
Scrollback: 4096 bytes
With always_on: false, disconnecting or kicking the last client closes the
serial port and clears the scrollback. Closing the port can toggle DTR and
reset some boards unless disable_hupcl is set.
API Reference¶
- class jumpstarter_driver_pyserial.client.PySerialClient¶
A client for handling serial communication using pexpect.
- close()¶
Close the open stream session without a context manager.
- open() fdspawn¶
Open a pexpect session. You can find the pexpect documentation here: https://pexpect.readthedocs.io/en/stable/api/pexpect.html#spawn-class
- Returns:
fdspawn: The pexpect session object.
- open_stream() BlockingStream¶
Open a blocking stream session without a context manager.
- Returns:
blocking stream session object.
- Return type:
BlockingStream
- pexpect()¶
Create a pexpect adapter context manager.
- Yields:
PexpectAdapter: The pexpect adapter object.
- stream(method='connect')¶
Open a blocking stream session with a context manager.
- Parameters:
method (str) – method name of streaming driver call
- Returns:
blocking stream session object context manager.
Examples¶
Using expect with a context manager
with pyserialclient.pexpect() as session:
session.sendline("Hello, world!")
session.expect("Hello, world!")
Using expect without a context manager
session = pyserialclient.open()
session.sendline("Hello, world!")
session.expect("Hello, world!")
pyserialclient.close()
Using a simple BlockingStream with a context manager
with pyserialclient.stream() as stream:
stream.send(b"Hello, world!")
data = stream.receive()
Using a simple BlockingStream without a context manager
stream = pyserialclient.open_stream()
stream.send(b"Hello, world!")
data = stream.receive()