Setup
Open the port with parity even — that's the Modbus RTU default. Then wrap it in a SerialCommandQueue:
import { SerialPilot } from 'serialpilot' import { SerialCommandQueue } from '@serialpilot/command-queue'const port = new SerialPilot({ path: ‘/dev/ttyUSB0’, baudRate: 9600, dataBits: 8, parity: ‘even’, stopBits: 1, })
const queue = new SerialCommandQueue({ port, timeout: 500 })
Send and wait
Each call to queue.command() writes the request, waits for a response, and resolves with what came back. Subsequent calls queue up:
const response = await queue.command(':010300000001F8\r\n')
console.log('Modbus response:', response)
Validate the response shape
Pass expect to require a particular pattern. The promise rejects if the response doesn't match — handy for protocols that interleave OK/ERROR replies:
const response = await queue.command('AT+CSQ', { expect: /OK/ })
Production tips
- Timeouts: 500 ms is fine for 9600 bps; bump to 2–3 s for 1200 bps or noisy lines.
- Retries: set
retryCount: 3on the queue when running over RS-485 with multiple devices on the bus. - Parity matters: Modbus RTU is
evenby spec; some equipment usesnonewith two stop bits as a workaround. - Inter-byte timing: if you need raw frames (no
\r\ntermination), pipe throughInterByteTimeoutParserinstead of relying on a delimiter.
Queue options
| Option | Default | Notes |
|---|---|---|
timeout | 3000 | Milliseconds per command. |
lineEnding | '\r\n' | Appended to each request. |
delimiter | '\n' | Splits responses. |
retryCount | 0 | Retries on timeout. |
retryDelay | 1000 | Milliseconds between retries. |
Events
response—(command, response)when a request resolves.error—(error)on timeout or transport failure.idle— when the queue empties.
DEBUG=serialpilot* node script.js turns on per-binding tracing — invaluable when a Modbus device looks idle but is actually replying with garbage.