Split Buffer frames from streams
$ npm install split-frames
Works very well with, for example, serialport for industrial protocols like Concert
/!\ this version only split frames properly, and does not remove start & end tags or escape bits anymore.
All these options are optionnal
type ControlBits = "none" | "end+1" | "end+2";
type Tag: number | Buffer | Array< number | Buffer >
javascript "startWith": Tag
javascript "startTimeout": integer
(default: 200)javascript "endWith": Tag
javascript "escapeWith": number
javascript "escaped": Array<number>
javascript "specifics": object
javascript "controlBits": ControlBits
(default: "none")"startTimeout" is a timeout (in milliseconds) which end frame in "start only" mode, if no second "start" bit is encountered after the first one
"specifics" is a [ key: string => value: Tag ] object which fire a "key" event when a "value" tag is found out of the message and not escaped ex : { "specifics": { "nak": 0x25 } } will fire an "nak" event when 0x25 bit is encountered
In the following exemples, "Splitter" and "Readable" classes are defined like that :
// typescript
import Splitter = require("split-frames");
import { Readable } from "stream";
// javascript
const Splitter = require("split-frames");
const { Readable } = require("stream");
The "createReadStream" function is defined like that :
function createReadStream () {
return new Readable({
read () { }
});
}
And the "STX", "DLE" and "ETX" constants are defined like that :
const STX = 0x02, ETX = 0x03, DLE = 0x10, ACK = 0x06, NAK = 0x15, WAK = 0x13;
const stream = createReadStream();
stream.pipe(new Splitter({
"startWith": STX
})).on("data", (chunk) => {
// Buffer([ STX, 0x24, 0x25, 0x26 ])
// Buffer([ STX, 0x24, 0x25 ])
});
stream.push(Buffer.from([ 0x20, STX, 0x24, 0x25, 0x26, STX, 0x24 ]));
stream.push(Buffer.from([ 0x25, STX ]));
const stream = createReadStream();
stream.pipe(new Splitter({
"endWith": ETX
})).on("data", (chunk) => {
// Buffer([ 0x24, 0x25, 0x26, ETX ])
// Buffer([ 0x24, 0x25, ETX ])
});
stream.push(Buffer.from([ 0x24, 0x25, 0x26, ETX, 0x24, 0x25 ]));
stream.push(Buffer.from([ ETX ]));
const stream = createReadStream();
stream.pipe(new Splitter({
"startWith": STX, "endWith": ETX
})).on("data", (chunk) => {
// Buffer([ STX, 0x24, 0x25, 0x26, ETX ])
// Buffer([ STX, 0x24, 0x25, ETX ])
});
stream.push(Buffer.from([ 0x20, STX, 0x24, 0x25, 0x26, ETX, 0x24, 0x25, STX ]));
stream.push(Buffer.from([ 0x24, 0x25, ETX ]));
const stream = createReadStream();
stream.pipe(new Splitter({
"startWith": STX, "endWith": Buffer.from([ DLE, ETX ])
})).on("data", (chunk) => {
// Buffer([ STX, 0x24, 0x25, 0x26, DLE, ETX ])
});
stream.push(Buffer.from([ 0x20, STX, 0x24, 0x25, 0x26, DLE, ETX, STX, 0x24, 0x25 ]));
const stream = createReadStream();
stream.pipe(new Splitter({
"startWith": STX, "endWith": ETX,
"escapeWith": DLE, "escaped": [ DLE, STX, ETX ]
})).on("data", (chunk) => {
// Buffer([ STX, 0x24, DLE, STX, 0x25, 0x26, DLE, DLE, 0x27, DLE, ETX, 0x28, ETX ])
});
stream.push(Buffer.from([ 0x20, STX, 0x24, DLE, STX, 0x25, 0x26 ]));
stream.push(Buffer.from([ DLE, DLE, 0x27, DLE, ETX, 0x28, ETX, STX, 0x24, 0x25 ]));
For even parity in seriaport, for example
const STX2 = 0x82;
const stream = createReadStream();
stream.pipe(new Splitter({
"startWith": [ STX, STX2 ], "endWith": ETX,
"escapeWith": DLE, "escaped": [ DLE, STX, ETX ]
})).on("data", (chunk) => {
// Buffer([ STX, 0x24, DLE, STX, 0x25, 0x26, DLE, DLE, 0x27, DLE, ETX, 0x28, ETX ])
// Buffer([ STX2, 0x24, DLE, STX, 0x25, 0x26, DLE, DLE, 0x27, DLE, ETX, 0x28, ETX ])
});
stream.push(Buffer.from([ 0x24, STX, 0x24, DLE, STX, 0x25, ACK ]));
stream.push(Buffer.from([ DLE, DLE, 0x27, DLE, ETX, 0x28, ETX, ACK, 0x24, 0x25 ]));
stream.push(Buffer.from([ STX2, 0x24, DLE, STX, 0x25, ACK ]));
stream.push(Buffer.from([ DLE, DLE, 0x27, DLE, ETX, 0x28, ETX, ACK, 0x24, 0x25 ]));
positive acknowledgement, negative acknowledgement, waiting for acknowledgement, whatever... only with no tags || start AND end tags || end tags (for firsts bits)
const stream = createReadStream();
stream.pipe(new Splitter({
"startWith": STX, "endWith": ETX,
"specifics": {
"ack": ACK, "nak": NAK, "wak": WAK, "whatever": 0x51
},
"escapeWith": DLE, "escaped": [ DLE, ACK, NAK, WAK ]
})).on("ack", () => {
console.log("ack received"); // (only 1x) -> good, escaped, in data
}).on("nak", () => {
console.log("nak received"); // (only 1x) -> in data, good, escaped
}).on("wak", () => {
console.log("wak received"); // (only 1x) -> in data, good, escaped
}).on("whatever", () => {
console.log("whatever received"); // (only 1x)
}).on("data", (chunk) => {
// Buffer([ STX, 0x20, 0x21, 0x22, ACK, NAK, WAK, 0x23, ETX ]) (x1)
});
stream.push(Buffer.from([ 0x51, 0x24, ACK, DLE, ACK, STX, 0x20, 0x21, 0x22, ACK, NAK, WAK ]));
stream.push(Buffer.from([ 0x23, ETX, NAK, DLE, NAK, WAK, DLE, WAK, 0x20, 0x21 ]));
used to compute LRC, MSB, LSB, etc... this example is for a structure like STX ETX LRC, where LRC compute all bits
function _computeLRC (frame) {
let lrc = 0x00;
for (let i = 0; i < frame.length; ++i) {
lrc ^= frame[i];
}
return lrc;
}
const stream = createReadStream();
stream.pipe(new Splitter({
"startWith": STX, "endWith": ETX,
"controlBits": "end+1"
})).on("data", (chunk) => {
// Buffer([ STX, 0x20, 0x21, 0x22, 0x24, ETX, 0x07 ]) (x1)
const data = chunk.slice(1, chunk.length - 2); // Buffer([ 0x20, 0x21, 0x22, 0x24 ])
const LRC = chunk[chunk.length - 1];
if (_computeLRC(data) === LRC) {
console.log("OK");
}
else {
console.error(new Error("not well-computed data :" + data.toString("hex") + "|" + Buffer.from([ LRC ]).toString("hex")));
}
});
stream.push(Buffer.from([ 0x51, 0x24, STX, 0x20, 0x21, 0x22, 0x24, ETX, 0x07, 0x24 ]));
$ npm run-script tests