SerialPilot

/01 — Start

Getting started

Install the package, open a port, read a line. If everything works, you'll see your device's first message inside five minutes — and if it doesn't, the troubleshooting checklist at the bottom covers the usual suspects.

Requirements

  • Node.js ≥ 20. SerialPilot uses native bindings compiled against modern N-API.
  • Linux, macOS, or Windows. Prebuilt binaries ship for the common architectures; on anything exotic, the package falls back to a source build (needs a C++ toolchain).
  • OS-level access. On Linux that means your user is in the dialout group; on macOS and Windows there's nothing to configure.

Install

$ npm install serialpilot

That single package pulls in the native bindings, the stream wrapper, and every parser. If you want to hand-pick what you depend on:

$ npm install @serialpilot/bindings-cpp @serialpilot/stream
$ npm install @serialpilot/parser-readline

Hello, port

The shortest useful program — list available ports, open one, write a byte, and read whatever comes back as lines:

import { SerialPilot, ReadlineParser } from 'serialpilot'

// 1. discover const ports = await SerialPilot.list() console.log(ports)

// 2. open const port = new SerialPilot({ path: ‘/dev/tty.usbmodem1421’, baudRate: 115200, })

// 3. parse incoming bytes into lines const lines = port.pipe(new ReadlineParser({ delimiter: ‘\n’ })) lines.on(‘data’, line => console.log(’<-’, line))

// 4. write port.write(‘PING\n’)

That's the whole shape of the library — every other feature wraps these four moves: discover, open, parse, write.

Anatomy of a port

A SerialPilot instance is a Node.js Duplex stream that wraps a binding:

┌──────────────────────────────────────────────┐
│  your code                                   │
│   port.write(...)        port.on('data',...) │
│                                              │
│  ┌────────────────────────────────────────┐  │
│  │  SerialPilot (Duplex stream)           │  │
│  │  high-water marks · backpressure · ↑↓  │  │
│  └────────────────────────────────────────┘  │
│                  │                           │
│  ┌────────────────────────────────────────┐  │
│  │  Binding  (bindings-cpp / mock / rust) │  │
│  │  open · close · read · write · drain   │  │
│  └────────────────────────────────────────┘  │
│                  │                           │
│           operating system                   │
└──────────────────────────────────────────────┘

You almost never reach for the binding directly — but knowing it's there explains how mocking works, why prebuilds matter, and where to look when an error mentions a system call.

Try it without hardware

You can run the example above with no device plugged in by swapping the binding for the mock:

import { SerialPilotMock, ReadlineParser } from 'serialpilot'
import { MockBinding } from '@serialpilot/binding-mock'

MockBinding.createPort(‘/dev/ROBOT’, { echo: true })

const port = new SerialPilotMock({ path: ‘/dev/ROBOT’, baudRate: 9600 }) const lines = port.pipe(new ReadlineParser({ delimiter: ‘\n’ })) lines.on(‘data’, line => console.log(line.toString()))

port.write(‘hello\n’) // echo: lines emits ‘hello’

The same parsers work against a mock and against silicon — that's the whole point of the abstraction. See Testing & mocking for the deeper version.

Discover ports from your shell

Sometimes the fastest path is the terminal. serialpilot ships three small CLIs:

$ npx serialpilot-list --format json
[{"path":"/dev/tty.usbmodem1421","manufacturer":"Arduino LLC"}]

$ npx serialpilot-terminal -p /dev/tty.usbmodem1421 -b 115200 # interactive console — type, see live response

$ npx serialpilot-repl -p /dev/tty.usbmodem1421 # scriptable Node REPL with port and SerialPilot in scope

When things don't work

SymptomLikely causeFix
PortNotFoundErrorPath is wrong or device unpluggedSerialPilot.list() to enumerate; or use findPorts({ vendorId: '…' })
PermissionDeniedErrorLinux user not in dialoutsudo usermod -aG dialout $USER & log out / back in
PortBusyErrorArduino IDE / PuTTY / screen has the portClose the other app; only one process can hold the port
Garbled bytesBaud rate mismatch9600 / 115200 are the usual; consult the device datasheet
Native build fails on installNo prebuild for your arch + no toolchainInstall Xcode CLT / build-essential / Visual Studio C++ Build Tools
Tip Many Arduino-class boards reset when a serial connection opens. If your first write() seems to vanish, pipe through ReadyParser and wait for the boot banner before you start sending.

Edit this page on GitHub