@signalk/streams
Version:
Utilities for handling streams of Signal K data
126 lines (125 loc) • 4.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const child_process_1 = require("child_process");
const stream_1 = require("stream");
const any_shell_escape_1 = __importDefault(require("any-shell-escape"));
class SerialStream extends stream_1.Transform {
options;
debug;
serial = null;
reconnectEnabled;
reconnectDelay = 1000;
isFirstError = true;
maxPendingWrites;
reconnectTimeout = null;
constructor(options) {
super();
this.options = options;
this.reconnectEnabled = options.reconnect ?? true;
this.maxPendingWrites = options.maxPendingWrites ?? 5;
const createDebug = options.createDebug ?? require('debug');
this.debug = createDebug('signalk:streams:serialport');
let pendingWrites = 0;
const stdOutEvents = Array.isArray(this.options.toStdout)
? [...this.options.toStdout]
: [this.options.toStdout].filter(Boolean);
const standardOutEventName = `serial-${this.options.providerId}-toStdout`;
stdOutEvents.push(standardOutEventName);
const onDrain = () => {
pendingWrites--;
};
for (const event of stdOutEvents) {
this.options.app.on(event, (d) => {
if (pendingWrites > this.maxPendingWrites) {
this.debug('Buffer overflow, not writing:' + d);
return;
}
this.debug('Writing:' + d);
if (Buffer.isBuffer(d)) {
this.serial?.write(d);
}
else {
this.serial?.write(d + '\r\n');
}
setImmediate(() => {
this.options.app.emit('connectionwrite', {
providerId: this.options.providerId
});
});
pendingWrites++;
this.serial?.drain(onDrain);
});
}
this.options.app.emitPropertyValue('serialport', {
id: this.options.providerId,
eventNames: {
toStdout: standardOutEventName
}
});
this.start();
}
start() {
if (this.serial !== null) {
this.serial.unpipe(this);
this.serial.removeAllListeners();
this.serial = null;
}
if (this.reconnectEnabled === false) {
return;
}
if (process.env['PRESERIALCOMMAND']) {
(0, child_process_1.execSync)(`${process.env['PRESERIALCOMMAND']} ${(0, any_shell_escape_1.default)(this.options.device)}`);
}
const { SerialPort } = require('serialport');
const { ReadlineParser } = require('@serialport/parser-readline');
this.serial = new SerialPort({
path: this.options.device,
baudRate: this.options.baudrate
});
this.serial.on('open', () => {
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);
});
this.serial.on('error', (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();
});
this.serial.on('close', () => {
this.options.app.setProviderError(this.options.providerId, 'Closed, reconnecting...');
this.scheduleReconnect();
});
}
end() {
this.reconnectEnabled = false;
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
}
if (this.serial) {
this.serial.close();
}
return this;
}
_transform(chunk, encoding, done) {
this.push(chunk);
done();
}
scheduleReconnect() {
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.reconnectTimeout = setTimeout(() => this.start(), this.reconnectDelay);
}
}
exports.default = SerialStream;