Skip to content
Start free evaluation

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.

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.

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.

A lab file is JSON of at most 1 MiB. Unknown fields are rejected, so a misspelled setting never goes unnoticed.

null-modem.json
{
"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"
}
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.
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.
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.

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:

rfc2217-server.json
{
"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.json

Any 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.

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.

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.