Pick a parser
| If your protocol… | Use |
|---|---|
| ends each message with a newline | ReadlineParser |
| uses any other byte sequence as a separator | DelimiterParser |
| has fixed-size messages | ByteLengthParser |
splits on a regex (mixed line endings, e.g. \r?\n) | RegexParser |
| length-prefixes each packet | PacketLengthParser |
| relies on inter-byte silence (Modbus RTU) | InterByteTimeoutParser |
| prints a banner before it's ready | ReadyParser |
| frames binary with SLIP escapes | SlipEncoder/Decoder |
| brackets data with start/end markers | StartEndParser |
| is ccTalk (coin/bill validators) | CCTalkParser |
| is CCSDS Space Packet | SpacePacketParser |
ReadlineParser
Emits one string per line. The line-oriented sweet spot for most Arduino-class boards.
import { SerialPilot, ReadlineParser } from 'serialpilot'
const port = new SerialPilot({ path: ‘/dev/ttyUSB0’, baudRate: 9600 }) const parser = port.pipe(new ReadlineParser({ delimiter: ‘\r\n’ })) parser.on(‘data’, line => console.log(line))
| Option | Type | Default | Notes |
|---|---|---|---|
delimiter | string | Buffer | '\n' | Line terminator. |
encoding | string | 'utf8' | Text encoding for emitted strings. |
includeDelimiter | boolean | false | Whether emitted lines keep the delimiter. |
DelimiterParser
Generic version of ReadlineParser — split on any byte sequence and emit Buffers (or strings if you set encoding downstream).
const parser = port.pipe(new DelimiterParser({
delimiter: Buffer.from([0xAA, 0x55]),
}))
| Option | Type | Notes |
|---|---|---|
delimiter | string | Buffer | number[] | Required. Length ≥ 1. |
includeDelimiter | boolean | Default false. |
ByteLengthParser
Emits a Buffer every time a fixed number of bytes have been received.
const parser = port.pipe(new ByteLengthParser({ length: 8 }))
parser.on('data', buf => console.log(buf)) // 8 bytes each time
Throws if length is missing, zero, or negative.
RegexParser
Like ReadlineParser, but the splitter is a RegExp. Useful when devices send mixed line endings:
const parser = port.pipe(new RegexParser({ regex: /\r?\n/ }))
| Option | Type | Default |
|---|---|---|
regex | RegExp | required |
encoding | string | 'utf8' |
PacketLengthParser
For protocols that prefix each packet with its length. Reads the length field at a configured offset and waits for the full packet to land before emitting.
const parser = port.pipe(new PacketLengthParser({
delimiter: 0xa5, // header byte
packetOverhead: 5, // non-payload bytes
lengthBytes: 1, // size of the length field
lengthOffset: 2, // offset of the length field
maxLen: 0xff,
}))
| Option | Default | Notes |
|---|---|---|
delimiter | 0xaa | Header byte that starts a packet. |
packetOverhead | 2 | Bytes outside the payload. |
lengthBytes | 1 | Size of the length field. |
lengthOffset | 1 | Position of the length field. |
maxLen | 0xff | Largest valid packet — guards against runaway frames. |
InterByteTimeoutParser
Emits buffered data when the line stays quiet for interval milliseconds. Modbus RTU's classic "3.5-character silence" framing fits here.
const parser = port.pipe(new InterByteTimeoutParser({ interval: 30 }))
| Option | Default | Notes |
|---|---|---|
interval | required | Milliseconds of silence (≥ 1). |
maxBufferSize | 65536 | Bytes buffered before forced emit. |
ReadyParser
Buffers everything until a configured ready sequence arrives, then emits ready and forwards bytes from that point on. Most Arduino-class boards print a banner on reset; this parser tells you when they're done.
const parser = port.pipe(new ReadyParser({ delimiter: 'READY' }))
parser.on('ready', () => port.write('PING\n'))
parser.on('data', console.log) // data after the banner
SlipEncoder / SlipDecoder
Two complementary streams that implement RFC 1055 SLIP framing. Common in ESP-IDF tools and embedded MCUs.
const decoder = port.pipe(new SlipDecoder()) decoder.on('data', frame => console.log('frame:', frame))
const encoder = new SlipEncoder() encoder.pipe(port) encoder.write(Buffer.from([0x01, 0x02, 0xc0])) // 0xc0 escaped automatically
Both honour the standard SLIP byte values: END=0xC0, ESC=0xDB, ESC_END=0xDC, ESC_ESC=0xDD.
StartEndParser
Emits the bytes between a configured start delimiter and a configured end delimiter. Suits NMEA, custom binary frames, or anything that uses opening/closing markers.
const parser = port.pipe(new StartEndParser({
startDelimiter: Buffer.from([0xa5]),
endDelimiter: Buffer.from([0x5a]),
}))
CCTalkParser
Parses the ccTalk protocol used by coin acceptors and bill validators in vending and gaming hardware. Emits each fully-received frame as a Buffer.
const parser = port.pipe(new CCTalkParser())
parser.on('data', frame => console.log(frame))
SpacePacketParser
Parses CCSDS Space Packet framing — the international standard used by spacecraft telemetry/telecommand links. Emits objects with parsed primary header fields and the payload buffer.
const parser = port.pipe(new SpacePacketParser())
parser.on('data', packet => {
console.log(packet.header.apid, packet.data)
})
Header fields exposed: version, type, secondaryHeaderFlag, apid, sequenceFlags, sequenceCount, length.
Writing your own
A parser is just a Node Transform stream. Roll your own by extending stream.Transform and implementing _transform(chunk, _, cb) — most of the parsers above are under 100 lines of source.