UNPKG

@signalk/streams

Version:

Utilities for handling streams of Signal K data

181 lines (179 loc) 7.86 kB
"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;