-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add slip decoder to parser-slip-encoder (#2196)
- Add option to specify slip escape codes - Add optional start and start escape
- Loading branch information
Showing
4 changed files
with
402 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
const { Transform } = require('stream') | ||
|
||
/** | ||
* A transform stream that decodes slip encoded data. | ||
* @extends Transform | ||
* @summary Runs in O(n) time, stripping out slip encoding and emitting decoded data. Optionally, | ||
* custom slip escape and delimiters can be provided. | ||
* @example | ||
// Receive slip encoded data from a serialport and log decoded data | ||
const SerialPort = require('serialport') | ||
const { SlipDecoder } = require('@serialport/parser-slip-encoder') | ||
const port = new SerialPort('/dev/tty-usbserial1') | ||
const parser = port.pipe(new SlipDecoder()) | ||
parser.on('data', console.log) | ||
*/ | ||
class SlipDecoder extends Transform { | ||
constructor(options = {}) { | ||
super(options) | ||
|
||
const opts = { | ||
START: undefined, | ||
ESC: 0xdb, | ||
END: 0xc0, | ||
|
||
ESC_START: undefined, | ||
ESC_END: 0xdc, | ||
ESC_ESC: 0xdd, | ||
|
||
...options, | ||
} | ||
this.opts = opts | ||
|
||
this.buffer = Buffer.alloc(0) | ||
this.escape = false | ||
this.start = false | ||
} | ||
|
||
_transform(chunk, encoding, cb) { | ||
for (let ndx = 0; ndx < chunk.length; ndx++) { | ||
let byte = chunk[ndx] | ||
|
||
if (byte === this.opts.START) { | ||
this.start = true | ||
continue | ||
} else if (undefined == this.opts.START) { | ||
this.start = true | ||
} | ||
|
||
if (this.escape) { | ||
if (byte === this.opts.ESC_START) { | ||
byte = this.opts.START | ||
} else if (byte === this.opts.ESC_ESC) { | ||
byte = this.opts.ESC | ||
} else if (byte === this.opts.ESC_END) { | ||
byte = this.opts.END | ||
} else { | ||
this.escape = false | ||
this.push(this.buffer) | ||
this.buffer = Buffer.alloc(0) | ||
} | ||
} else { | ||
if (byte === this.opts.ESC) { | ||
this.escape = true | ||
continue | ||
} | ||
|
||
if (byte === this.opts.END) { | ||
this.push(this.buffer) | ||
this.buffer = Buffer.alloc(0) | ||
|
||
this.escape = false | ||
this.start = false | ||
continue | ||
} | ||
} | ||
|
||
this.escape = false | ||
|
||
if (true === this.start) { | ||
this.buffer = Buffer.concat([this.buffer, Buffer.from([byte])]) | ||
} | ||
} | ||
|
||
cb() | ||
} | ||
|
||
_flush(cb) { | ||
this.push(this.buffer) | ||
this.buffer = Buffer.alloc(0) | ||
cb() | ||
} | ||
} | ||
|
||
module.exports = SlipDecoder |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
const { Transform } = require('stream') | ||
|
||
/** | ||
* A transform stream that emits SLIP-encoded data for each incoming packet. | ||
* @extends Transform | ||
* @summary Runs in O(n) time, adding a 0xC0 character at the end of each | ||
* received packet and escaping characters, according to RFC 1055. Adds another | ||
* 0xC0 character at the beginning if the `bluetoothQuirk` option is truthy (as | ||
* per the Bluetooth Core Specification 4.0, Volume 4, Part D, Chapter 3 "SLIP Layer"). | ||
* Optionally, custom slip escape and delimiters can be provided. | ||
* @example | ||
// Read lines from a text file, then SLIP-encode each and send them to a serial port | ||
const SerialPort = require('serialport') | ||
const { SlipEncoder } = require('@serialport/parser-slip-encoder') | ||
const Readline = require('parser-readline') | ||
const fileReader = require('fs').createReadStream('/tmp/some-file.txt'); | ||
const port = new SerialPort('/dev/tty-usbserial1') | ||
const lineParser = fileReader.pipe(new Readline({ delimiter: '\r\n' })); | ||
const encoder = fileReader.pipe(new SlipEncoder({ bluetoothQuirk: false })); | ||
encoder.pipe(port); | ||
*/ | ||
class SlipEncoder extends Transform { | ||
constructor(options = {}) { | ||
super(options) | ||
|
||
const opts = { | ||
START: undefined, | ||
ESC: 0xdb, | ||
END: 0xc0, | ||
|
||
ESC_START: undefined, | ||
ESC_END: 0xdc, | ||
ESC_ESC: 0xdd, | ||
|
||
...options, | ||
} | ||
this.opts = opts | ||
|
||
if (options.bluetoothQuirk) { | ||
this._bluetoothQuirk = true | ||
} | ||
} | ||
|
||
_transform(chunk, encoding, cb) { | ||
const chunkLength = chunk.length | ||
|
||
if (this._bluetoothQuirk && chunkLength === 0) { | ||
// Edge case: push no data. Bluetooth-quirky SLIP parsers don't like | ||
// lots of 0xC0s together. | ||
return cb() | ||
} | ||
|
||
// Allocate memory for the worst-case scenario: all bytes are escaped, | ||
// plus start and end separators. | ||
const encoded = Buffer.alloc(chunkLength * 2 + 2) | ||
let j = 0 | ||
|
||
if (this._bluetoothQuirk == true) { | ||
encoded[j++] = this.opts.END | ||
} | ||
|
||
if (this.opts.START !== undefined) { | ||
encoded[j++] = this.opts.START | ||
} | ||
|
||
for (let i = 0; i < chunkLength; i++) { | ||
let byte = chunk[i] | ||
|
||
if (byte === this.opts.START) { | ||
encoded[j++] = this.opts.ESC | ||
byte = this.opts.ESC_START | ||
} else if (byte === this.opts.END) { | ||
encoded[j++] = this.opts.ESC | ||
byte = this.opts.ESC_END | ||
} else if (byte === this.opts.ESC) { | ||
encoded[j++] = this.opts.ESC | ||
byte = this.opts.ESC_ESC | ||
} | ||
|
||
encoded[j++] = byte | ||
} | ||
|
||
encoded[j++] = this.opts.END | ||
|
||
cb(null, encoded.slice(0, j)) | ||
} | ||
} | ||
|
||
module.exports = SlipEncoder |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,73 +1,4 @@ | ||
const { Transform } = require('stream') | ||
|
||
const END = 0xc0 | ||
const ESC = 0xdb | ||
const ESC_END = 0xdc | ||
const ESC_ESC = 0xdd | ||
|
||
/** | ||
* A transform stream that emits SLIP-encoded data for each incoming packet. | ||
* @extends Transform | ||
* @summary Runs in O(n) time, adding a 0xC0 character at the end of each | ||
* received packet and escaping characters, according to RFC 1055. Adds another | ||
* 0xC0 character at the beginning if the `bluetoothQuirk` option is truthy (as | ||
* per the Bluetooth Core Specification 4.0, Volume 4, Part D, Chapter 3 "SLIP Layer"). | ||
* Runs in O(n) time. | ||
* @example | ||
// Read lines from a text file, then SLIP-encode each and send them to a serial port | ||
const SerialPort = require('serialport') | ||
const SlipEncoder = require('@serialport/parser-slip-encoder') | ||
const Readline = require('parser-readline') | ||
const fileReader = require('fs').createReadStream('/tmp/some-file.txt'); | ||
const port = new SerialPort('/dev/tty-usbserial1') | ||
const lineParser = fileReader.pipe(new Readline({ delimiter: '\r\n' })); | ||
const encoder = fileReader.pipe(new SlipEncoder({ bluetoothQuirk: false })); | ||
encoder.pipe(port); | ||
*/ | ||
class SlipEncoderParser extends Transform { | ||
constructor(options = {}) { | ||
super(options) | ||
|
||
if (options.bluetoothQuirk) { | ||
this._bluetoothQuirk = true | ||
} | ||
} | ||
|
||
_transform(chunk, encoding, cb) { | ||
const chunkLength = chunk.length | ||
|
||
if (this._bluetoothQuirk && chunkLength === 0) { | ||
// Edge case: push no data. Bluetooth-quirky SLIP parsers don't like | ||
// lots of 0xC0s together. | ||
return cb() | ||
} | ||
|
||
// Allocate memory for the worst-case scenario: all bytes are escaped, | ||
// plus start and end separators. | ||
const encoded = Buffer.alloc(chunkLength * 2 + 2) | ||
let j = 0 | ||
|
||
if (this._bluetoothQuirk) { | ||
encoded[j++] = END | ||
} | ||
|
||
for (let i = 0; i < chunkLength; i++) { | ||
let byte = chunk[i] | ||
if (byte === END) { | ||
encoded[j++] = ESC | ||
byte = ESC_END | ||
} else if (byte === ESC) { | ||
encoded[j++] = ESC | ||
byte = ESC_ESC | ||
} | ||
|
||
encoded[j++] = byte | ||
} | ||
|
||
encoded[j++] = END | ||
|
||
cb(null, encoded.slice(0, j)) | ||
} | ||
module.exports = { | ||
SlipEncoder: require('./encoder'), | ||
SlipDecoder: require('./decoder'), | ||
} | ||
|
||
module.exports = SlipEncoderParser |
Oops, something went wrong.