@canboat/visual-analyzer
Version:
NMEA 2000 data visualization utility (requires SK Server >= 2.15)
171 lines (167 loc) • 5.87 kB
JavaScript
"use strict";
/*
* Copyright 2014-2015 Fabian Tollenaar <fabian@starting-point.nl>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Usage: This is the first pipeElement in a PipedProvider. Used to pass data input from Serial
* to the next pipeElement.
* Reads data from a serial device and allows writing back to serial with the "toStdout" option
* It takes two options; "device" and "baudrate".
*
* The "toStdout" option is not mandatory. It routes events emitted on app with that name to
* serial output, followed by newline. toStdout can be a string or an array of strings.
*
* You can run arbitrary shell command that get the configured serial port as the parameter
* by setting the environment variable PRESERIALCOMMAND. The command is invoked once per each
* configured serial port.
*
* For example running the server having run
*
* export PRESERIALCOMMAND="echo >>/tmp/serialports"
*
* will append all configured serial port devices to the file /tmp/serialports
* every time the server is started.
*
* Example:
{
"type": "providers/serialport",
"options": {
"device": "/dev/ttyUSB0",
"baudrate": 4800,
"toStdout": "nmea0183out1"
},
"optionMappings": [
{
"fromAppProperty": "argv.nmeadevice",
"toOption": "device"
},
{
"fromAppProperty": "argv.nmeabaudrate",
"toOption": "baudrate"
}
]
},
*/
const Transform = require('stream').Transform;
const { SerialPort } = require('serialport');
const { ReadlineParser } = require('@serialport/parser-readline');
function SerialStream(options) {
if (!(this instanceof SerialStream)) {
return new SerialStream(options);
}
Transform.call(this, options);
this.reconnect = options.reconnect || true;
this.reconnectDelay = 1000;
this.serial = null;
this.options = options;
this.maxPendingWrites = options.maxPendingWrites || 5;
this.start();
this.isFirstError = true;
const createDebug = options.createDebug || require('debug');
this.debug = createDebug('signalk:streams:serialport');
}
require('util').inherits(SerialStream, Transform);
SerialStream.prototype.start = function () {
const that = this;
if (this.serial !== null) {
this.serial.unpipe(this);
this.serial.removeAllListeners();
this.serial = null;
}
if (this.reconnect === false) {
return;
}
this.serial = new SerialPort({
path: this.options.device,
baudRate: this.options.baudrate,
});
this.serial.on('open', function () {
this.reconnectDelay = 1000;
this.options.app.setProviderStatus(this.options.providerId, `Connected to ${this.options.device}`);
this.isFirstError = true;
const parser = new ReadlineParser();
this.serial.pipe(parser).pipe(this);
}.bind(this));
this.serial.on('error', function (x) {
this.options.app.setProviderError(this.options.providerId, x.message);
if (this.isFirstError) {
console.log(x.message);
}
this.debug(x.message);
this.isFirstError = false;
this.scheduleReconnect();
}.bind(this));
this.serial.on('close', function () {
if (this.serial) {
this.options.app.setProviderError(this.options.providerId, 'Closed, reconnecting...');
this.scheduleReconnect();
}
}.bind(this));
let pendingWrites = 0;
const stdOutEvent = this.options.toStdout;
if (stdOutEvent) {
;
(Array.isArray(stdOutEvent) ? stdOutEvent : [stdOutEvent]).forEach((event) => {
const onDrain = () => {
pendingWrites--;
};
that.options.app.on(event, (d) => {
if (pendingWrites > that.maxPendingWrites) {
that.debug('Buffer overflow, not writing:' + d);
return;
}
that.debug('Writing:' + d);
if (Buffer.isBuffer(d)) {
that.serial.write(d);
}
else {
that.serial.write(d + '\r\n');
}
setImmediate(() => {
that.options.app.emit('connectionwrite', {
providerId: that.options.providerId,
});
});
pendingWrites++;
that.serial.drain(onDrain);
});
});
}
};
SerialStream.prototype.end = function () {
if (this.serial) {
const serial = this.serial;
this.serial = null;
serial.close();
}
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = null;
};
SerialStream.prototype._transform = function (chunk, encoding, done) {
this.push(chunk);
done();
};
SerialStream.prototype.scheduleReconnect = function () {
if (this.serial === null)
return;
this.serial = null;
this.reconnectDelay *= this.reconnectDelay < 60 * 1000 ? 1.5 : 1;
const msg = `Not connected (retry delay ${(this.reconnectDelay / 1000).toFixed(0)} s)`;
this.debug(msg);
this.options.app.setProviderStatus(this.options.providerId, msg);
this.timeout = setTimeout(this.start.bind(this), this.reconnectDelay);
};
module.exports = SerialStream;
//# sourceMappingURL=serialport.js.map