pdcomm-vserial commands and lab files
pdcomm-vserial is the command-line program that checks and runs labs. For what a lab is, see How virtual serial ports work.
Commands
Section titled “Commands”| Command | What it does |
|---|---|
pdcomm-vserial run --config <file> |
Starts the lab and handles control commands from standard input. |
pdcomm-vserial check --config <file> |
Only validates the lab file and prints {"valid":true} if it is correct. |
pdcomm-vserial pair [--instant] [--link-a <path>] [--link-b <path>] |
Starts a lab of two pseudo terminals joined by a null modem, at 9600 bit/s 8N1 with realistic pacing. --instant delivers without delay. |
pdcomm-vserial daemon --config <file> |
Runs as a service: reads no standard input, and cleans up and exits on SIGTERM or SIGINT. macOS and Linux only. |
pdcomm-vserial ports list |
Windows: lists the installed ports. |
pdcomm-vserial ports install --inf <INF> [--count N] [--first-com N] |
Windows: creates ports until there are N (default 2) and installs the driver. Requires an administrator. |
pdcomm-vserial ports remove |
Windows: deletes all port devices. Requires an administrator. |
pdcomm-vserial ports uninstall |
Windows: deletes the ports, the driver package, and the filter service. Requires an administrator. |
pdcomm-vserial --help, --version |
Prints the usage or the version to standard output. |
Exit codes:
| Code | Meaning |
|---|---|
0 |
Success. The lab stopped normally. |
1 |
An error such as an invalid lab file or a port that can’t open. The cause goes to standard error as pdcomm-vserial: <cause>. |
2 |
Wrong command or options. The usage goes to standard error. |
Control a running lab
Section titled “Control a running lab”When the lab is ready, run and pair write one line that lists the ports:
{"ports":[{"backend":"pty","link":"/tmp/pdcomm-plc","node":"plc","path":"/dev/ttys011"},{"backend":"pty","link":"/tmp/pdcomm-hmi","node":"hmi","path":"/dev/ttys016"}],"ready":true}| Field | Meaning |
|---|---|
node |
The node name. |
backend |
pty, driver, tcp_server, or rfc2217_server. |
path |
The device path that applications open, for pseudo terminals and Linux driver ports. |
link |
The symbolic link created by link. |
listen |
The address a TCP server actually listens on. With port 0 you see the port the operating system chose. |
After that, each line of standard input is one JSON command, and each command gets a one-line reply:
| Command | Reply |
|---|---|
{"command":"ports"} |
{"ok":true,"ports":[…]} |
{"command":"inject","node":"b","fault":{"xor":1}} |
{"ok":true} |
{"command":"attach","node":"a","attached":false} |
{"ok":true}. false unplugs the cable and true plugs it in. |
{"command":"stop"} |
No reply; the lab stops and the program exits. End of input does the same. |
fault is "drop", {"xor": <0-255>}, {"error": "parity"} (parity, framing, overrun, or break), or {"noise": {"byte": <0-255>, "error": null}}. A command that isn’t valid gets {"ok":false,"error":"…"}, and the lab keeps running.
Lab files
Section titled “Lab files”A lab file is JSON of at most 1 MiB. Unknown fields are rejected, so a misspelled setting never goes unnoticed.
{ "engine": { "pacing": "realistic", "nodes": [ {"name": "plc", "port": {"line": {"baud": 19200, "data_bits": 8, "parity": "even", "stop_bits": "1"}}}, {"name": "hmi", "port": {"line": {"baud": 19200, "data_bits": 8, "parity": "even", "stop_bits": "1"}}} ], "media": [{"kind": "null_modem", "a": "plc", "b": "hmi"}] }, "ports": [ {"node": "plc", "backend": {"type": "pty", "link": "/tmp/pdcomm-plc"}}, {"node": "hmi", "backend": {"type": "pty", "link": "/tmp/pdcomm-hmi"}} ], "trace": "null-modem-trace.jsonl"}Top-level fields
Section titled “Top-level fields”| Field | Description |
|---|---|
engine |
Required. The pacing, nodes, and media. |
ports |
Bindings from nodes to endpoints, at most one per node. |
trace |
File that receives the line events as JSON Lines. Relative paths start at the current directory. |
allow_remote |
true lets TCP servers listen on non-loopback addresses. Defaults to false. |
bridges |
Nodes connected to remote RFC 2217 device servers. |
driver |
Path of the Linux driver’s control device. Defaults to /dev/pdcomm-vserial. |
engine
Section titled “engine”| Field | Description |
|---|---|
pacing |
realistic (default) or instant. |
nodes |
The nodes. |
media |
The media. kind is null_modem, loopback, four_wire, or two_wire. |
{"name": "master", "port": {"line": {"baud": 9600, "data_bits": 8, "parity": "none", "stop_bits": "2"}, "flow": "none", "rts_toggle": {"on_send": true, "delay_before_ns": 0, "delay_after_ns": 0, "rx_during_tx": false}}, "attach": {"driver_enable": {"rts": {"asserted": true}}, "echo": false}, "faults": {"seed": 7, "flip_ppm": 1000, "latency_ns": 200000, "jitter_ns": 50000}, "rx_fifo": {"trigger": 1, "idle_chars": 4}}| Field | Description |
|---|---|
name |
Required. Unique within the lab. |
port.line |
baud (1 to 16000000), data_bits (5 to 8), parity, and stop_bits ("1", "1.5", "2"): the line settings when the lab starts. |
port.flow |
none, rts_cts, or xon_xoff. |
port.rts_toggle |
Raises and lowers RTS around each transmission, with delays in nanoseconds and optional reception while sending (rx_during_tx). |
attach.driver_enable |
When the node may drive a bus: "always", "auto", or {"rts": {"asserted": true}}. |
attach.echo |
The node also receives its own characters while it sends. |
attach.fixed_inputs |
Input signals held active whatever the peer does (cts, dsr, dcd, ri). |
faults |
Random faults and delays: seed, drop_ppm, flip_ppm, parity_ppm, framing_ppm, overrun_ppm, noise_ppm, latency_ns, jitter_ns. |
rx_fifo |
The receive FIFO. Defaults to {"trigger": 14, "idle_chars": 4}; null delivers every character on its own. |
Port bindings
Section titled “Port bindings”backend |
Fields |
|---|---|
{"type": "pty", "link": "/tmp/pdcomm-plc"} |
link is optional. It adds a fixed link to the pseudo terminal, which the lab removes when it stops. |
{"type": "driver", "index": 0} |
The port number (0 to 63) of the installed driver. |
{"type": "tcp_server", "listen": "127.0.0.1:7002"} |
Raw TCP. The line settings stay as the lab file sets them. Accepts one client. |
{"type": "rfc2217_server", "listen": "127.0.0.1:7001"} |
RFC 2217 server. The client changes the line settings and signals. |
Share a port over RFC 2217
Section titled “Share a port over RFC 2217”This lab exposes node device as an RFC 2217 server, opens node app as a pseudo terminal, and joins the two with a null modem:
{ "engine": { "pacing": "realistic", "nodes": [{"name": "device"}, {"name": "app"}], "media": [{"kind": "null_modem", "a": "device", "b": "app"}] }, "ports": [ {"node": "device", "backend": {"type": "rfc2217_server", "listen": "127.0.0.1:7001"}}, {"node": "app", "backend": {"type": "pty", "link": "/tmp/pdcomm-app"}} ]}pdcomm-vserial run --config rfc2217-server.jsonAny RFC 2217 client can connect to 127.0.0.1:7001. For example, with pyserial installed, this command sends hello to the application that opened /tmp/pdcomm-app:
python3 -c 'import serial; s = serial.serial_for_url("rfc2217://127.0.0.1:7001", baudrate=9600, timeout=1); s.write(b"hello\n")'When the client changes the speed, the settings of node device change. If they no longer match the application’s side, garbled characters arrive, just as on a real line.
Connect to a remote device server
Section titled “Connect to a remote device server”bridges connects a local node to an RFC 2217 server elsewhere. Use it to treat real equipment on a serial device server as a local port. A bridged node has a port binding and is not wired to a medium.
{ "engine": {"nodes": [{"name": "remote", "port": {"line": {"baud": 9600}}}]}, "ports": [{"node": "remote", "backend": {"type": "pty", "link": "/tmp/pdcomm-bridge"}}], "bridges": [{"node": "remote", "rfc2217": "127.0.0.1:7001"}]}Set rfc2217 to the host:port of the device server. Speed, format, signals, and break that the application sets on /tmp/pdcomm-bridge pass through to the remote port.
Lab file errors
Section titled “Lab file errors”check and run report these errors:
| Message | Cause |
|---|---|
unknown field `prot`, expected one of `name`, `port`, … |
A field name is misspelled. |
invalid virtual serial configuration: duplicate node a |
Two nodes have the same name. |
invalid virtual serial configuration: node a is wired twice |
A node is wired to two media. |
invalid virtual serial configuration: a two-wire bus needs at least two nodes |
The two-wire bus has too few nodes. |
invalid virtual serial configuration: baud rate must be between 1 and 16000000 |
The speed is out of range. |
invalid virtual serial configuration: data bits must be between 5 and 8 |
The data bits are out of range. |
port binds unknown node x |
A binding names a node that doesn’t exist. |
node a is bound twice |
A node has two bindings. |
driver port 64 is invalid or bound twice |
The driver port number is outside 0 to 63 or used twice. |
0.0.0.0:7002 is not a loopback address; set allow_remote to expose it |
Listening on an external address requires allow_remote. |
bridge node a needs a port binding, no wiring, and one bridge |
The bridged node breaks one of the bridge rules. |
configuration exceeds one MiB |
The file is larger than 1 MiB. |
For errors while a lab runs, see Tools troubleshooting.