@v5x/serial talks to a V5 brain through the Web Serial API. Browsers provide
that API; Node.js and Bun do not. @v5x/node provides it, so servers, Electron
apps, test rigs, and the v5x CLI can use the same protocol library the browser
workflow uses.
For Node.js, install the Node SerialPort backend:
serialport for Node.js on Linux, macOS, and Windows. Bun keeps using
bun-serialport on Linux and macOS and its built-in Win32 FFI backend on
Windows. You can supply your own backend when a different runtime or native
transport is required.
Connect to a brain
serial is a drop-in replacement for navigator.serial, so anything that
accepts a Web Serial Serial accepts it:
Enumerate ports
requestPort() resolves the first matching port instead of prompting, because
a host process has no port picker to show.
Port info is a superset of the browser’s. A host process can see the device
path and USB serial number that a browser deliberately hides, so ports also
carry path, serialNumber, and a stable id — the USB serial number when
the platform reports one, otherwise the path. The CLI’s --port selector
matches against exactly these.
Port objects are stable across enumerations. Calling getPorts() twice returns
the same object for a port you already opened, so its open state is preserved.
A closed port is replaced when its discovered USB identity changes, and dropped
once it disappears.
Bring your own backend
Every platform detail lives behindSerialBackend. Supporting a new runtime or
operating system is a backend rather than a change to the transport.
vendorId and productId are hexadecimal strings without a 0x prefix. The
transport parses them into the numeric usbVendorId and usbProductId that
Web Serial filters match against.
A backend that implements pause() and resume() gets backpressure handling:
the transport pauses native reads while the readable stream is full and resumes
when it drains. A backend without them errors the stream rather than buffering
without bound.
NodeSerial refuses to enumerate ports on a platform outside the backend’s
declared platforms, so the failure names the backend instead of surfacing as
an opaque native error.
The default backends
createDefaultSerialBackend() picks the backend that can drive the host, and
createNodeSerial() uses it when you pass no backend of your own. Neither
native package loads until a port is enumerated or opened, so importing
@v5x/node is safe on any platform.
Node.js
createNodeSerialportBackend() drives the serialport package on Linux,
macOS, and Windows. On Windows it opens COM ports through that package and
uses the registry for enumeration, because Windows reports USB vendor and
product ids through the device enumeration tree rather than through the
serial API.
Linux and macOS
createBunSerialportBackend() drives bun-serialport, which ships native code
for Linux and macOS.
On Linux it enumerates ports from /sys/class/tty rather than through the
library, because the library reports device paths without the USB vendor and
product ids that Web Serial filters need. The walk is bounded to eight
concurrent sysfs reads and caches attributes per USB device, so a machine with
many ttys enumerates quickly. readLinuxUsbDeviceAttributes and
listLinuxPorts are exported for backends that want the same walk.
Windows
On Bun,createWindowsSerialBackend() talks to the Win32 communications API
through Bun’s FFI, so Windows needs no additional native package. It opens a
COM port with CreateFileW through the \\.\ device namespace — the only way
COM10 and above resolve — configures it for raw 8-N-1 traffic with DTR and
RTS asserted, and polls ReadFile for buffered bytes. The poll is suspended
while the readable stream is full, which is how the transport applies
backpressure.
On Node.js, the default createNodeSerialportBackend() uses serialport for
the same COM-port I/O, so the Windows backend does not import bun:ffi.
Enumeration reads the registry rather than the serial API, because Windows
reports a port’s USB vendor and product ids through the device enumeration tree
instead of through the port itself. HKLM\HARDWARE\DEVICEMAP\SERIALCOMM lists
the ports that are attached right now, and the PortName values under
HKLM\SYSTEM\CurrentControlSet\Enum\USB supply their identity. That second
walk is cached and repeated only when a COM port the backend has not resolved
appears, so hotplug polling stays cheap.
parseComPortNames, parseUsbPortAttributes, and createWindowsPortLister
are exported for backends that want the same enumeration without the FFI.