Skip to content

Commit

Permalink
Ensure flushing doesn't cause a read error which closes the port
Browse files Browse the repository at this point in the history
  • Loading branch information
reconbot committed Jul 10, 2017
1 parent 2ed7b11 commit 1888efc
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 48 deletions.
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ node_modules
npm-debug.log
coverage
.mailmap
package-lock.json
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,15 @@ var parser = port.pipe(Readline({delimiter: '\r\n'}));
parser.on('data', console.log);
```

To use the `Ready` parser provide a byte start sequence. After the bytes have been received data events will be passed through.
```js
var SerialPort = require('serialport');
var Ready = SerialPort.parsers.Ready;
var port = new SerialPort('/dev/tty-usbserial1');
var parser = port.pipe(Ready({data: 'READY'}));
parser.on('data', console.log); // all data after READY is received
```

* * *

<a name="module_serialport--SerialPort.list"></a>
Expand Down
84 changes: 41 additions & 43 deletions binding.gyp
Original file line number Diff line number Diff line change
@@ -1,49 +1,47 @@
{
'targets': [
{
'target_name': 'serialport',
'sources': [
'src/serialport.cpp'
],
'include_dirs': [
'<!(node -e "require(\'nan\')")'
],
'conditions': [
['OS=="win"',
{
'sources': [
'src/serialport_win.cpp'
],
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': '2',
'DisableSpecificWarnings': [ '4530', '4506' ],
},
},
},
],
['OS=="mac"',
{
'sources': [
'src/serialport_unix.cpp',
'src/poller.cpp'
],
'xcode_settings': {
'OTHER_LDFLAGS': [
'-framework CoreFoundation -framework IOKit'
]
'targets': [{
'target_name': 'serialport',
'sources': [
'src/serialport.cpp'
],
'include_dirs': [
'<!(node -e "require(\'nan\')")'
],
'conditions': [
['OS=="win"',
{
'sources': [
'src/serialport_win.cpp'
],
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': '2',
'DisableSpecificWarnings': [ '4530', '4506' ],
}
}
],
['OS!="win"',
{
'sources': [
'src/serialport_unix.cpp',
'src/poller.cpp'
],
}
],
['OS=="mac"',
{
'sources': [
'src/serialport_unix.cpp',
'src/poller.cpp'
],
'xcode_settings': {
'OTHER_LDFLAGS': [
'-framework CoreFoundation -framework IOKit'
]
}
],
}
],
}
],
['OS!="win"',
{
'sources': [
'src/serialport_unix.cpp',
'src/poller.cpp'
]
}
]
]
}],
}
12 changes: 11 additions & 1 deletion lib/parsers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,22 @@ var Readline = SerialPort.parsers.Readline;
var port = new SerialPort('/dev/tty-usbserial1');
var parser = port.pipe(Readline({delimiter: '\r\n'}));
parser.on('data', console.log);
```
To use the `Ready` parser provide a byte start sequence. After the bytes have been received data events will be passed through.
```js
var SerialPort = require('serialport');
var Ready = SerialPort.parsers.Ready;
var port = new SerialPort('/dev/tty-usbserial1');
var parser = port.pipe(Ready({data: 'READY'}));
parser.on('data', console.log); // all data after READY is received
```
*/

module.exports = {
Readline: require('./readline'),
Delimiter: require('./delimiter'),
ByteLength: require('./byte-length'),
Regex: require('./regex')
Regex: require('./regex'),
Ready: require('./ready')
};
55 changes: 55 additions & 0 deletions lib/parsers/ready.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
'use strict';
const Buffer = require('safe-buffer').Buffer;
const inherits = require('util').inherits;
const Transform = require('stream').Transform;

function ReadyParser(options) {
if (!(this instanceof ReadyParser)) {
return new ReadyParser(options);
}
Transform.call(this, options);

options = options || {};

if (options.delimiter === undefined) {
throw new TypeError('"delimiter" is not a bufferable object');
}

if (options.delimiter.length === 0) {
throw new TypeError('"delimiter" has a 0 or undefined length');
}

this.delimiter = Buffer.from(options.delimiter);
this.readOffset = 0;
this.ready = false;
}

inherits(ReadyParser, Transform);

ReadyParser.prototype._transform = function(chunk, encoding, cb) {
if (this.ready) {
this.push(chunk);
return cb();
}
const delimiter = this.delimiter;
let chunkOffset = 0;
while (this.readOffset < delimiter.length && chunkOffset < chunk.length) {
if (delimiter[this.readOffset] === chunk[chunkOffset]) {
this.readOffset++;
} else {
this.readOffset = 0;
}
chunkOffset++;
}
if (this.readOffset === delimiter.length) {
this.ready = true;
this.emit('ready');
const chunkRest = chunk.slice(chunkOffset);
if (chunkRest.length > 0) {
this.push(chunkRest);
}
}
cb();
};

module.exports = ReadyParser;
18 changes: 17 additions & 1 deletion test/integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,26 @@ function integrationTest(platform, testPort, binding) {
} catch (e) {
return done(e);
}
done();
port.close(done);
});
}));
});
it('deals with flushing during a read', (done) => {
const port = new SerialPort(testPort);
port.on('error', done);
const ready = port.pipe(new SerialPort.parsers.Ready({ delimiter: 'READY' }));
ready.on('ready', () => {
// we should have a pending read now since we're in flowing mode
port.flush((err) => {
try {
assert.isNull(err);
} catch (e) {
return done(e);
}
port.close(done);
});
});
});
});
});
}
2 changes: 1 addition & 1 deletion test/parser-delimiter.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('DelimiterParser', () => {
it('throws when called with a 0 length delimiter', () => {
assert.throws(() => {
new DelimiterParser({
delimiter: Buffer.from(0)
delimiter: Buffer.alloc(0)
});
});

Expand Down
2 changes: 1 addition & 1 deletion test/parser-readline.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe('ReadlineParser', () => {
it('throws when called with a 0 length delimiter', () => {
assert.throws(() => {
new ReadlineParser({
delimiter: Buffer.from(0)
delimiter: Buffer.alloc(0)
});
});

Expand Down
106 changes: 106 additions & 0 deletions test/parser-ready.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
'use strict';
/* eslint-disable no-new */

const Buffer = require('safe-buffer').Buffer;
const assert = require('chai').assert;
const sinon = require('sinon');

const ReadyParser = require('../lib/parsers/ready');

describe('ReadyParser', () => {
it('works without new', () => {
// eslint-disable-next-line new-cap
const parser = ReadyParser({ delimiter: Buffer.from([0]) });
assert.instanceOf(parser, ReadyParser);
});

it('emits data received after the ready data', () => {
const spy = sinon.spy();
const parser = new ReadyParser({
delimiter: Buffer.from('\n')
});
parser.on('data', spy);
parser.write(Buffer.from('which will you get?'));
parser.write(Buffer.from('garbage\ngold'));
parser.write(Buffer.from('just for you'));

assert.deepEqual(spy.getCall(0).args[0], Buffer.from('gold'));
assert.deepEqual(spy.getCall(1).args[0], Buffer.from('just for you'));
assert(spy.calledTwice);
});

it('emits the ready event before the data event', () => {
const spy = sinon.spy();
const parser = new ReadyParser({ delimiter: '!' });
parser.on('ready', () => {
parser.on('data', spy);
});
parser.write(Buffer.from('!hi'));
assert(spy.calledOnce);
});

it('has a ready property', () => {
const parser = new ReadyParser({
delimiter: Buffer.from('\n')
});
parser.resume();
assert.isFalse(parser.ready);
parser.write(Buffer.from('not the new line'));
assert.isFalse(parser.ready);
parser.write(Buffer.from('this is the \n'));
assert.isTrue(parser.ready);
});

it('throws when not provided with a delimiter', () => {
assert.throws(() => {
new ReadyParser({});
});
});

it('throws when called with a 0 length delimiter', () => {
assert.throws(() => {
new ReadyParser({
delimiter: Buffer.alloc(0)
});
});

assert.throws(() => {
new ReadyParser({
delimiter: ''
});
});

assert.throws(() => {
new ReadyParser({
delimiter: []
});
});
});

it('allows setting of the delimiter with a string', () => {
new ReadyParser({ delimiter: 'string' });
});

it('allows setting of the delimiter with a buffer', () => {
new ReadyParser({ delimiter: Buffer.from([1]) });
});

it('allows setting of the delimiter with an array of bytes', () => {
new ReadyParser({ delimiter: [1] });
});

it('allows receiving the delimiter over small writes', () => {
const spy = sinon.spy();
const parser = new ReadyParser({
delimiter: Buffer.from('READY')
});
parser.on('data', spy);
parser.write(Buffer.from('bad data then REA'));
parser.write(Buffer.from('D'));
parser.write(Buffer.from('Y'));
parser.write(Buffer.from('!!!!!!'));

assert.deepEqual(spy.getCall(0).args[0], Buffer.from('!!!!!!'));
assert(spy.calledOnce);
});
});
2 changes: 1 addition & 1 deletion test/parser-regex.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('RegexParser', () => {
it('throws when called with a 0 length delimiter', () => {
assert.throws(() => {
new RegexParser({
delimiter: Buffer.from(0)
delimiter: Buffer.alloc(0)
});
});

Expand Down

0 comments on commit 1888efc

Please sign in to comment.