@serialpilot/modbus-rtu
v0.1.0
Published
Pure-function Modbus RTU frame encode/decode (CRC-16/Modbus, little-endian). Framing only — pair with @serialpilot/parser-inter-byte-timeout for live streams. Browser-clean — no node:* imports.
Maintainers
Readme
@serialpilot/modbus-rtu
Pure-function Modbus RTU framing only — encode/decode of address + function-code + PDU + CRC-16/Modbus, with the trailing CRC transmitted little-endian per the spec. No node:* imports.
This package deliberately stops at the framing layer. It does not implement function-code semantics, register maps, or master/slave behaviour; modbus-serial owns that and is excellent. Use this package when you want to:
- Build a custom master/slave on top of a clean codec.
- Re-frame Modbus RTU over a non-serial transport (e.g. TCP relay, Web Serial, WebSocket gateway).
- Validate or fuzz wire-level frames.
Install
npm install @serialpilot/modbus-rtuUse
import { encode, decode, silenceMillisFor } from '@serialpilot/modbus-rtu'
// Build a Read Holding Registers (FC=0x03) request: slave 1, start 0, count 10.
const request = encode({
address: 1,
fc: 0x03,
pdu: new Uint8Array([0x00, 0x00, 0x00, 0x0a]),
})
// → [0x01, 0x03, 0x00, 0x00, 0x00, 0x0A, 0xC5, 0xCD]
// ^^^^^^^^^^ CRC-16/Modbus little-endian
// Decode any RTU frame:
const frame = decode(request)
// → { address: 1, fc: 3, pdu: Uint8Array(4) [0, 0, 0, 10] }Inter-frame silence helper
Modbus RTU specifies that frames are separated by 3.5 character-times of silence on the line. Computed at the host, that's silenceMillisFor(baudRate) — the same helper your favourite SCADA stack uses internally:
silenceMillisFor(9600) // ≈ 4.01 ms
silenceMillisFor(19200) // 1.75 ms (fixed minimum per spec at higher baud)
silenceMillisFor(115200) // 1.75 msPairing with a stream
For a live stream, frame boundaries are detected by inter-byte silence rather than a delimiter byte. Use the existing @serialpilot/parser-inter-byte-timeout from the main serialpilot toolkit:
import { SerialPilot } from 'serialpilot'
import { InterByteTimeoutParser } from '@serialpilot/parser-inter-byte-timeout'
import { decode, silenceMillisFor } from '@serialpilot/modbus-rtu'
const port = new SerialPilot({ path: '/dev/ttyUSB0', baudRate: 9600 })
const frames = port.pipe(new InterByteTimeoutParser({
interval: silenceMillisFor(9600),
}))
frames.on('data', (frame: Buffer) => {
try {
console.log(decode(frame))
} catch (err) {
// ModbusFrameError — bad CRC, bad address, or short frame
}
})A first-class companion parser-modbus-rtu package may land in v0.2 once the integration shape settles.
Errors
decode throws ModbusFrameError for malformed input:
| code | meaning |
| --------------- | ------------------------------------------------------ |
| TOO_SHORT | fewer than 4 bytes (minimum: address + fc + 2-byte CRC). |
| BAD_ADDRESS | address byte outside the valid 0..247 range. |
| CRC_MISMATCH | computed CRC ≠ received CRC. |
License
MIT.
