@signalk/streams
Version:
Utilities for handling streams of Signal K data
181 lines (179 loc) • 7.86 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const stream_1 = require("stream");
const n2k_signalk_1 = require("@signalk/n2k-signalk");
class N2kToSignalK extends stream_1.Transform {
sourceMeta = {};
notifications = {};
options;
app;
filters;
n2kMapper;
constructor(options) {
super({ objectMode: true });
this.options = options;
this.app = options.app;
if (options.filters && options.filtersEnabled) {
this.filters = options.filters.filter((f) => (f.source && f.source.length) || (f.pgn && f.pgn.length));
}
this.n2kMapper = new n2k_signalk_1.N2kMapper({
...options,
sendMetaData: true
});
const n2kOutEvent = 'nmea2000JsonOut';
this.n2kMapper.on('n2kOut', (pgn) => this.app.emit('nmea2000JsonOut', pgn));
this.n2kMapper.on('n2kSourceMetadata', (n2k, meta) => {
const src = Number(n2k.src);
const existing = this.sourceMeta[src] ?? {};
this.sourceMeta[src] = { ...existing, ...meta };
const delta = {
context: this.app.selfContext,
updates: [
{
source: {
...this.sourceMeta[src],
label: this.options.providerId,
type: 'NMEA2000',
pgn: Number(n2k.pgn),
src: n2k.src.toString()
},
timestamp: n2k.timestamp.substring(0, 10) +
'T' +
n2k.timestamp.substring(11, n2k.timestamp.length),
values: []
}
]
};
this.app.deltaCache.setSourceDelta(`${this.options.providerId}.${n2k.src}`, delta);
});
this.n2kMapper.on('n2kSourceMetadataTimeout', (pgn, src) => {
if (Number(pgn) === 60928) {
console.warn(`n2k-signalk: unable to detect can name for src ${src}`);
const srcNum = Number(src);
const meta = this.sourceMeta[srcNum];
if (meta) {
meta.unknowCanName = true;
}
}
});
this.n2kMapper.on('n2kSourceChanged', (src, from, to) => {
console.warn(`n2k-signalk: address ${src} changed from ${from} ${to}`);
const srcNum = Number(src);
if (this.sourceMeta[srcNum]) {
delete this.sourceMeta[srcNum];
}
});
if (this.app.isNmea2000OutAvailable) {
this.n2kMapper.n2kOutIsAvailable(this.app, n2kOutEvent);
}
else {
this.app.on('nmea2000OutAvailable', () => this.n2kMapper.n2kOutIsAvailable(this.app, n2kOutEvent));
}
}
isFiltered(source) {
if (!this.filters) {
return undefined;
}
return this.filters.find((filter) => {
const sFilter = this.options.useCanName ? source.canName : source.src;
return ((!filter.source ||
filter.source.length === 0 ||
filter.source === sFilter) &&
(!filter.pgn ||
filter.pgn.length === 0 ||
String(filter.pgn) === String(source.pgn)));
});
}
_transform(chunk, encoding, done) {
try {
const delta = this.n2kMapper.toDelta(chunk);
const src = Number(chunk.src);
if (!this.sourceMeta[src]) {
this.sourceMeta[src] = {};
}
const firstUpdate = delta?.updates[0];
if (delta &&
firstUpdate &&
firstUpdate.values.length > 0 &&
!this.isFiltered(firstUpdate.source)) {
if (!this.options.useCanName) {
delete firstUpdate.source.canName;
}
const canName = firstUpdate.source.canName;
if (this.options.useCanName &&
!canName &&
!this.sourceMeta[src]?.unknowCanName) {
done();
return;
}
delta.updates.forEach((update) => {
update.values.forEach((kv) => {
if (kv.path && kv.path.startsWith('notifications.')) {
const pathNotifs = this.notifications[kv.path];
if (kv.value.state === 'normal' &&
pathNotifs &&
pathNotifs[src]) {
clearInterval(pathNotifs[src].interval);
delete pathNotifs[src];
}
else if (kv.value.state !== 'normal') {
if (!this.notifications[kv.path]) {
this.notifications[kv.path] = {};
}
const currentPathNotifs = this.notifications[kv.path];
if (!currentPathNotifs[src]) {
const interval = setInterval(() => {
const entry = currentPathNotifs[src];
if (entry && Date.now() - entry.lastTime > 10000) {
const copy = JSON.parse(JSON.stringify(kv));
copy.value.state = 'normal';
const normalDelta = {
context: delta.context,
updates: [
{
source: update.source,
values: [copy]
}
]
};
delete currentPathNotifs[src];
clearInterval(interval);
this.app.handleMessage(this.options.providerId, normalDelta);
}
}, 5000);
currentPathNotifs[src] = {
lastTime: Date.now(),
interval
};
}
else {
currentPathNotifs[src].lastTime = Date.now();
}
}
}
});
});
this.push(delta);
}
}
catch (ex) {
console.error(ex);
}
done();
}
}
exports.default = N2kToSignalK;