Skip to content

Commit 1888efc

Browse files
committed
Ensure flushing doesn't cause a read error which closes the port
1 parent 2ed7b11 commit 1888efc

File tree

10 files changed

+243
-48
lines changed

10 files changed

+243
-48
lines changed

.npmignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ node_modules
2121
npm-debug.log
2222
coverage
2323
.mailmap
24+
package-lock.json

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,15 @@ var parser = port.pipe(Readline({delimiter: '\r\n'}));
759759
parser.on('data', console.log);
760760
```
761761

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

764773
<a name="module_serialport--SerialPort.list"></a>

binding.gyp

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,47 @@
11
{
2-
'targets': [
3-
{
4-
'target_name': 'serialport',
5-
'sources': [
6-
'src/serialport.cpp'
7-
],
8-
'include_dirs': [
9-
'<!(node -e "require(\'nan\')")'
10-
],
11-
'conditions': [
12-
['OS=="win"',
13-
{
14-
'sources': [
15-
'src/serialport_win.cpp'
16-
],
17-
'msvs_settings': {
18-
'VCCLCompilerTool': {
19-
'ExceptionHandling': '2',
20-
'DisableSpecificWarnings': [ '4530', '4506' ],
21-
},
22-
},
23-
},
24-
],
25-
['OS=="mac"',
26-
{
27-
'sources': [
28-
'src/serialport_unix.cpp',
29-
'src/poller.cpp'
30-
],
31-
'xcode_settings': {
32-
'OTHER_LDFLAGS': [
33-
'-framework CoreFoundation -framework IOKit'
34-
]
2+
'targets': [{
3+
'target_name': 'serialport',
4+
'sources': [
5+
'src/serialport.cpp'
6+
],
7+
'include_dirs': [
8+
'<!(node -e "require(\'nan\')")'
9+
],
10+
'conditions': [
11+
['OS=="win"',
12+
{
13+
'sources': [
14+
'src/serialport_win.cpp'
15+
],
16+
'msvs_settings': {
17+
'VCCLCompilerTool': {
18+
'ExceptionHandling': '2',
19+
'DisableSpecificWarnings': [ '4530', '4506' ],
3520
}
3621
}
37-
],
38-
['OS!="win"',
39-
{
40-
'sources': [
41-
'src/serialport_unix.cpp',
42-
'src/poller.cpp'
43-
],
22+
}
23+
],
24+
['OS=="mac"',
25+
{
26+
'sources': [
27+
'src/serialport_unix.cpp',
28+
'src/poller.cpp'
29+
],
30+
'xcode_settings': {
31+
'OTHER_LDFLAGS': [
32+
'-framework CoreFoundation -framework IOKit'
33+
]
4434
}
45-
],
35+
}
4636
],
47-
}
48-
],
37+
['OS!="win"',
38+
{
39+
'sources': [
40+
'src/serialport_unix.cpp',
41+
'src/poller.cpp'
42+
]
43+
}
44+
]
45+
]
46+
}],
4947
}

lib/parsers/index.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,22 @@ var Readline = SerialPort.parsers.Readline;
4949
var port = new SerialPort('/dev/tty-usbserial1');
5050
var parser = port.pipe(Readline({delimiter: '\r\n'}));
5151
parser.on('data', console.log);
52+
```
53+
54+
To use the `Ready` parser provide a byte start sequence. After the bytes have been received data events will be passed through.
55+
```js
56+
var SerialPort = require('serialport');
57+
var Ready = SerialPort.parsers.Ready;
58+
var port = new SerialPort('/dev/tty-usbserial1');
59+
var parser = port.pipe(Ready({data: 'READY'}));
60+
parser.on('data', console.log); // all data after READY is received
5261
```
5362
*/
5463

5564
module.exports = {
5665
Readline: require('./readline'),
5766
Delimiter: require('./delimiter'),
5867
ByteLength: require('./byte-length'),
59-
Regex: require('./regex')
68+
Regex: require('./regex'),
69+
Ready: require('./ready')
6070
};

lib/parsers/ready.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
const Buffer = require('safe-buffer').Buffer;
3+
const inherits = require('util').inherits;
4+
const Transform = require('stream').Transform;
5+
6+
function ReadyParser(options) {
7+
if (!(this instanceof ReadyParser)) {
8+
return new ReadyParser(options);
9+
}
10+
Transform.call(this, options);
11+
12+
options = options || {};
13+
14+
if (options.delimiter === undefined) {
15+
throw new TypeError('"delimiter" is not a bufferable object');
16+
}
17+
18+
if (options.delimiter.length === 0) {
19+
throw new TypeError('"delimiter" has a 0 or undefined length');
20+
}
21+
22+
this.delimiter = Buffer.from(options.delimiter);
23+
this.readOffset = 0;
24+
this.ready = false;
25+
}
26+
27+
inherits(ReadyParser, Transform);
28+
29+
ReadyParser.prototype._transform = function(chunk, encoding, cb) {
30+
if (this.ready) {
31+
this.push(chunk);
32+
return cb();
33+
}
34+
const delimiter = this.delimiter;
35+
let chunkOffset = 0;
36+
while (this.readOffset < delimiter.length && chunkOffset < chunk.length) {
37+
if (delimiter[this.readOffset] === chunk[chunkOffset]) {
38+
this.readOffset++;
39+
} else {
40+
this.readOffset = 0;
41+
}
42+
chunkOffset++;
43+
}
44+
if (this.readOffset === delimiter.length) {
45+
this.ready = true;
46+
this.emit('ready');
47+
const chunkRest = chunk.slice(chunkOffset);
48+
if (chunkRest.length > 0) {
49+
this.push(chunkRest);
50+
}
51+
}
52+
cb();
53+
};
54+
55+
module.exports = ReadyParser;

test/integration.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,26 @@ function integrationTest(platform, testPort, binding) {
223223
} catch (e) {
224224
return done(e);
225225
}
226-
done();
226+
port.close(done);
227227
});
228228
}));
229229
});
230+
it('deals with flushing during a read', (done) => {
231+
const port = new SerialPort(testPort);
232+
port.on('error', done);
233+
const ready = port.pipe(new SerialPort.parsers.Ready({ delimiter: 'READY' }));
234+
ready.on('ready', () => {
235+
// we should have a pending read now since we're in flowing mode
236+
port.flush((err) => {
237+
try {
238+
assert.isNull(err);
239+
} catch (e) {
240+
return done(e);
241+
}
242+
port.close(done);
243+
});
244+
});
245+
});
230246
});
231247
});
232248
}

test/parser-delimiter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('DelimiterParser', () => {
4949
it('throws when called with a 0 length delimiter', () => {
5050
assert.throws(() => {
5151
new DelimiterParser({
52-
delimiter: Buffer.from(0)
52+
delimiter: Buffer.alloc(0)
5353
});
5454
});
5555

test/parser-readline.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ describe('ReadlineParser', () => {
8282
it('throws when called with a 0 length delimiter', () => {
8383
assert.throws(() => {
8484
new ReadlineParser({
85-
delimiter: Buffer.from(0)
85+
delimiter: Buffer.alloc(0)
8686
});
8787
});
8888

test/parser-ready.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
'use strict';
2+
/* eslint-disable no-new */
3+
4+
const Buffer = require('safe-buffer').Buffer;
5+
const assert = require('chai').assert;
6+
const sinon = require('sinon');
7+
8+
const ReadyParser = require('../lib/parsers/ready');
9+
10+
describe('ReadyParser', () => {
11+
it('works without new', () => {
12+
// eslint-disable-next-line new-cap
13+
const parser = ReadyParser({ delimiter: Buffer.from([0]) });
14+
assert.instanceOf(parser, ReadyParser);
15+
});
16+
17+
it('emits data received after the ready data', () => {
18+
const spy = sinon.spy();
19+
const parser = new ReadyParser({
20+
delimiter: Buffer.from('\n')
21+
});
22+
parser.on('data', spy);
23+
parser.write(Buffer.from('which will you get?'));
24+
parser.write(Buffer.from('garbage\ngold'));
25+
parser.write(Buffer.from('just for you'));
26+
27+
assert.deepEqual(spy.getCall(0).args[0], Buffer.from('gold'));
28+
assert.deepEqual(spy.getCall(1).args[0], Buffer.from('just for you'));
29+
assert(spy.calledTwice);
30+
});
31+
32+
it('emits the ready event before the data event', () => {
33+
const spy = sinon.spy();
34+
const parser = new ReadyParser({ delimiter: '!' });
35+
parser.on('ready', () => {
36+
parser.on('data', spy);
37+
});
38+
parser.write(Buffer.from('!hi'));
39+
assert(spy.calledOnce);
40+
});
41+
42+
it('has a ready property', () => {
43+
const parser = new ReadyParser({
44+
delimiter: Buffer.from('\n')
45+
});
46+
parser.resume();
47+
assert.isFalse(parser.ready);
48+
parser.write(Buffer.from('not the new line'));
49+
assert.isFalse(parser.ready);
50+
parser.write(Buffer.from('this is the \n'));
51+
assert.isTrue(parser.ready);
52+
});
53+
54+
it('throws when not provided with a delimiter', () => {
55+
assert.throws(() => {
56+
new ReadyParser({});
57+
});
58+
});
59+
60+
it('throws when called with a 0 length delimiter', () => {
61+
assert.throws(() => {
62+
new ReadyParser({
63+
delimiter: Buffer.alloc(0)
64+
});
65+
});
66+
67+
assert.throws(() => {
68+
new ReadyParser({
69+
delimiter: ''
70+
});
71+
});
72+
73+
assert.throws(() => {
74+
new ReadyParser({
75+
delimiter: []
76+
});
77+
});
78+
});
79+
80+
it('allows setting of the delimiter with a string', () => {
81+
new ReadyParser({ delimiter: 'string' });
82+
});
83+
84+
it('allows setting of the delimiter with a buffer', () => {
85+
new ReadyParser({ delimiter: Buffer.from([1]) });
86+
});
87+
88+
it('allows setting of the delimiter with an array of bytes', () => {
89+
new ReadyParser({ delimiter: [1] });
90+
});
91+
92+
it('allows receiving the delimiter over small writes', () => {
93+
const spy = sinon.spy();
94+
const parser = new ReadyParser({
95+
delimiter: Buffer.from('READY')
96+
});
97+
parser.on('data', spy);
98+
parser.write(Buffer.from('bad data then REA'));
99+
parser.write(Buffer.from('D'));
100+
parser.write(Buffer.from('Y'));
101+
parser.write(Buffer.from('!!!!!!'));
102+
103+
assert.deepEqual(spy.getCall(0).args[0], Buffer.from('!!!!!!'));
104+
assert(spy.calledOnce);
105+
});
106+
});

test/parser-regex.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('RegexParser', () => {
4949
it('throws when called with a 0 length delimiter', () => {
5050
assert.throws(() => {
5151
new RegexParser({
52-
delimiter: Buffer.from(0)
52+
delimiter: Buffer.alloc(0)
5353
});
5454
});
5555

0 commit comments

Comments
 (0)