@signalk/streams
Version:
Utilities for handling streams of Signal K data
187 lines (185 loc) • 6.67 kB
JavaScript
"use strict";
/*
* Copyright 2016 Teppo Kurki <teppo.kurki@iki.fi>
*
* 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.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const stream_1 = require("stream");
const timestamp_throttle_1 = __importDefault(require("./timestamp-throttle"));
const n2k_signalk_1 = __importDefault(require("./n2k-signalk"));
const n2kAnalyzer_1 = __importDefault(require("./n2kAnalyzer"));
const canboatjs_1 = __importDefault(require("./canboatjs"));
const nmea0183_signalk_1 = __importDefault(require("./nmea0183-signalk"));
class ToTimestamped extends stream_1.Transform {
deMultiplexer;
options;
multiplexedFormat = false;
constructor(deMultiplexer, options) {
super({ objectMode: true });
this.deMultiplexer = deMultiplexer;
this.options = options;
}
_transform(msg, encoding, done) {
const str = msg.toString();
if (str.trim().length === 0) {
done();
return;
}
this.multiplexedFormat =
str.length > 16 && str.charAt(13) === ';' && str.split(';').length >= 3;
if (this.multiplexedFormat) {
if (this.options.noThrottle) {
this.deMultiplexer.toTimestamped.pipe(this.deMultiplexer.splitter);
}
else {
this.deMultiplexer.toTimestamped
.pipe(this.deMultiplexer.timestampThrottle)
.pipe(this.deMultiplexer.splitter);
}
this._transform = this.handleMultiplexed;
}
else {
this._transform = this.handleMixed;
}
this._transform(msg, encoding, done);
}
handleMixed(msg, encoding, done) {
const line = msg.toString();
const res = {
timestamp: new Date().getTime(),
data: line,
discriminator: 'I'
};
if (line.charAt(0) === '{') {
res.discriminator = 'I';
}
else if ((line.charAt(0) === '$' || line.charAt(0) === '!') &&
!line.startsWith('!PDGY')) {
res.discriminator = 'N';
}
else {
res.discriminator = 'A';
}
this.push(res);
done();
}
handleMultiplexed(msg, encoding, done) {
const line = msg.toString();
const parts = line.split(';');
this.push({
timestamp: parts[0],
discriminator: parts[1],
data: parts.slice(2).join(';')
});
done();
}
}
class Splitter extends stream_1.Transform {
demuxEmitData;
fromN2KJson;
fromActisenseSerial;
fromNMEA0183;
constructor(deMultiplexer, options) {
super({ objectMode: true });
this.demuxEmitData = (msg) => {
deMultiplexer.emit('data', msg);
};
this.fromN2KJson = new n2k_signalk_1.default(options);
this.fromN2KJson.on('data', this.demuxEmitData);
if (options.useCanboatjs === undefined || options.useCanboatjs) {
this.fromActisenseSerial = new canboatjs_1.default(options);
}
else {
this.fromActisenseSerial = new n2kAnalyzer_1.default(options);
}
this.fromActisenseSerial.pipe(this.fromN2KJson);
this.fromNMEA0183 = new nmea0183_signalk_1.default(options);
this.fromNMEA0183.on('data', this.demuxEmitData);
}
_transform(msg, encoding, done) {
let actualDone = done;
try {
switch (msg.discriminator) {
case 'A': {
msg.fromFile = true;
const result = this.fromActisenseSerial.write(msg, encoding);
if (!result) {
this.fromActisenseSerial.once('drain', done);
actualDone = () => { };
}
break;
}
case 'C':
case 'N':
case 'G':
case 'M':
this.fromNMEA0183.write({ line: msg.data, timestamp: msg.timestamp }, encoding);
break;
case 'I':
default:
try {
const parsed = JSON.parse(msg.data);
const timestamp = new Date(Number(msg.timestamp));
if (parsed.updates) {
parsed.updates.forEach((update) => {
update.timestamp = timestamp;
});
}
this.push(parsed);
this.demuxEmitData(parsed);
}
catch (e) {
console.error(e);
}
break;
}
}
finally {
actualDone();
}
}
pipe(target) {
this.fromN2KJson.pipe(target);
this.fromNMEA0183.pipe(target);
return stream_1.Transform.prototype.pipe.call(this, target);
}
}
class DeMultiplexer extends stream_1.Writable {
toTimestamped;
timestampThrottle;
splitter;
constructor(options) {
super();
this.toTimestamped = new ToTimestamped(this, options);
this.timestampThrottle = new timestamp_throttle_1.default({
getMilliseconds: (msg) => typeof msg.timestamp === 'string'
? Number(msg.timestamp)
: msg.timestamp
});
this.splitter = new Splitter(this, options);
this.toTimestamped.on('drain', this.emit.bind(this, 'drain'));
}
pipe(target) {
return this.splitter.pipe(target);
}
write(chunk, encodingOrCallback, callback) {
if (typeof encodingOrCallback === 'function') {
return this.toTimestamped.write(chunk, encodingOrCallback);
}
return this.toTimestamped.write(chunk, encodingOrCallback ?? 'utf8', callback);
}
}
exports.default = DeMultiplexer;