@canboat/canboatjs
Version:
Native javascript version of canboat
1,103 lines • 40.9 kB
JavaScript
"use strict";
/**
* Copyright 2018 Scott Bender (scott@scottbender.net)
*
* 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 });
exports.Parser = void 0;
exports.getField = getField;
const ts_pgns_1 = require("@canboat/ts-pgns");
const utilities_1 = require("./utilities");
//import { EventEmitter } from 'node:events'
// eslint-disable-next-line @typescript-eslint/no-require-imports
const EventEmitter = require('events');
const package_json_1 = __importDefault(require("../package.json"));
const lodash_1 = __importDefault(require("lodash"));
const pgns_1 = require("./pgns");
const bit_buffer_1 = require("bit-buffer");
const int64_buffer_1 = require("int64-buffer");
const stringMsg_1 = require("./stringMsg");
const debug = (0, utilities_1.createDebug)('canboatjs:fromPgn');
const trace = (0, utilities_1.createDebug)('canboatjs:fromPgn:trace');
const fieldTypeReaders = {};
const fieldTypePostProcessors = {};
const FORMAT_PLAIN = 0;
const FORMAT_COALESCED = 1;
const RES_BINARY = 'Binary data';
const FASTPACKET_INDEX = 0;
const FASTPACKET_SIZE = 1;
const FASTPACKET_BUCKET_0_SIZE = 6;
const FASTPACKET_BUCKET_N_SIZE = 7;
const FASTPACKET_BUCKET_0_OFFSET = 2;
const FASTPACKET_BUCKET_N_OFFSET = 1;
const FASTPACKET_MAX_INDEX = 0x1f;
class Parser extends EventEmitter {
options;
name;
version;
author;
license;
format;
devices;
mixedFormat;
constructor(opts = {}) {
super();
this.options = opts === undefined ? {} : opts;
if (this.options.returnNulls === undefined) {
this.options.returnNulls = false;
}
if (this.options.useCamel === undefined) {
this.options.useCamel = true;
}
if (this.options.useCamelCompat === undefined) {
this.options.useCamelCompat = false;
}
if (this.options.returnNonMatches === undefined) {
this.options.returnNonMatches = false;
}
if (this.options.createPGNObjects === undefined) {
this.options.createPGNObjects = false;
}
this.name = package_json_1.default.name;
this.version = package_json_1.default.version;
this.author = package_json_1.default.author;
this.license = package_json_1.default.license;
this.format = this.options.format === undefined ? -1 : this.options.format;
this.devices = {};
this.mixedFormat = this.options.mixedFormat || false;
if (this.options.onPropertyValues) {
this.options.onPropertyValues('canboat-custom-pgns', (values) => {
values
.filter((v) => v != null)
.forEach((pv) => {
(0, pgns_1.addCustomPgns)(pv.value, pv.setter);
});
});
}
}
_parse(pgn, bs, len, coalesced, cb, sourceString = undefined) {
if (pgn.src === undefined) {
throw new Error('invalid pgn, missing src');
}
const customPgns = (0, pgns_1.getCustomPgn)(pgn.pgn);
let pgnList = (0, pgns_1.getPgn)(pgn.pgn);
if (!pgnList && !customPgns) {
this.emit('warning', pgn, `no conversion found for pgn ${JSON.stringify(pgn)}`);
return undefined;
}
if (customPgns) {
pgnList = [...customPgns.definitions, ...(pgnList || [])];
}
if (!pgnList || pgnList.length === 0) {
this.emit('warning', pgn, `no conversion found for pgn ${JSON.stringify(pgn)}`);
return undefined;
}
if (pgnList === undefined) {
return;
}
if (pgn.pgn === 59392) {
pgnList = pgnList.filter((pgn) => pgn.Fallback === undefined || pgn.Fallback === false);
}
let pgnData;
const origPGNList = pgnList;
if (pgnList.length > 1) {
pgnData = this.findMatchPgn(pgnList);
if (pgnData === null) {
pgnData = pgnList[0];
}
}
else {
pgnData = pgnList[0];
}
if (pgnData === undefined) {
return;
}
let couldBeMulti = false;
if (pgnList.length > 0 && len == 8) {
pgnList.forEach((pgnD) => {
if (pgnD.Length && pgnD.Length > 8) {
couldBeMulti = true;
}
});
}
trace(`${pgn.pgn} ${len} ${pgnData.Length} ${couldBeMulti}`);
if (coalesced ||
len > 0x8 ||
(this.format == FORMAT_COALESCED && !this.mixedFormat)) {
this.format = FORMAT_COALESCED;
if (sourceString) {
pgn.input = [sourceString];
}
//} else if ( pgnData.Length > 0x8 || (len == 0x8 && (pgnData.RepeatingFields || couldBeMulti))) {
}
else if (pgnData.Type === 'Fast') {
//partial packet
this.format = FORMAT_PLAIN;
if (this.devices[pgn.src] === undefined) {
this.devices[pgn.src] = {};
}
let packet = this.devices[pgn.src][pgn.pgn];
if (!packet) {
packet = { bufferSize: 0, lastPacket: 0, src: [] };
this.devices[pgn.src][pgn.pgn] = packet;
}
if (sourceString) {
packet.src.push(sourceString);
}
const start = bs.byteIndex;
const packetIndex = bs.view.buffer.readUInt8(FASTPACKET_INDEX);
const bucket = packetIndex & FASTPACKET_MAX_INDEX;
trace(`${pgn.pgn} partial ${packetIndex} ${bucket} ${packet.size}`);
if (bucket == 0) {
packet.size = bs.view.buffer.readUInt8(FASTPACKET_SIZE);
const newSize = packet.size + FASTPACKET_BUCKET_N_SIZE;
if (newSize > packet.bufferSize) {
const newBuf = Buffer.alloc(newSize);
packet.bufferSize = newSize;
if (packet.buffer) {
packet.buffer.copy(newBuf);
}
packet.buffer = newBuf;
}
bs.view.buffer.copy(packet.buffer, 0, FASTPACKET_BUCKET_0_OFFSET, 8);
trace(`${pgn.pgn} targetStart: 0 sourceStart: ${FASTPACKET_BUCKET_0_OFFSET}`);
}
else if (!packet.buffer) {
//we got a non-zero bucket, but we never got the zero bucket
debug(`PGN ${pgn.pgn} malformed packet for ${pgn.src} received; got a non-zero bucket first`);
cb && cb(`Could not parse ${JSON.stringify(pgn)}`, undefined);
bs.byteIndex = start;
delete this.devices[pgn.src][pgn.pgn];
return;
}
else {
if (packet.lastPacket + 1 != packetIndex) {
debug(`PGN ${pgn.pgn} malformed packet for ${pgn.src} received; expected ${packet.lastPacket + 1} but got ${packetIndex}`);
cb && cb(`Could not parse ${JSON.stringify(pgn)}`, undefined);
bs.byteIndex = start;
delete this.devices[pgn.src][pgn.pgn];
return;
}
else {
trace(`${pgn.pgn} targetStart: ${FASTPACKET_BUCKET_0_SIZE + FASTPACKET_BUCKET_N_SIZE * (bucket - 1)} sourceStart: ${FASTPACKET_BUCKET_N_OFFSET} sourceEned: ${FASTPACKET_BUCKET_N_SIZE}`);
bs.view.buffer.copy(packet.buffer, FASTPACKET_BUCKET_0_SIZE + FASTPACKET_BUCKET_N_SIZE * (bucket - 1), FASTPACKET_BUCKET_N_OFFSET, 8);
}
}
packet.lastPacket = packetIndex;
if (FASTPACKET_BUCKET_0_SIZE + FASTPACKET_BUCKET_N_SIZE * bucket <
packet.size) {
// Packet is not complete yet
trace(`${pgn.pgn} not complete`);
return;
}
const view = new bit_buffer_1.BitView(packet.buffer);
bs = new bit_buffer_1.BitStream(view);
trace(`${pgn.pgn} done`);
pgn.input = packet.src;
delete this.devices[pgn.src][pgn.pgn];
}
else if (sourceString) {
pgn.input = [sourceString];
}
let RepeatingFields = pgnData.RepeatingFieldSet1Size
? pgnData.RepeatingFieldSet1Size
: 0;
pgn.fields = {};
try {
let fields = pgnData.Fields;
const continueReading = true;
let unknownPGN = false;
for (let i = 0; i < fields.length - RepeatingFields && continueReading; i++) {
const field = fields[i];
const hasMatch = field.Match !== undefined;
let [value] = readField(pgnData, this.options, !hasMatch, pgn, field, bs, fields);
if (hasMatch) {
//console.log(`looking for ${field.Name} == ${value}`)
//console.log(JSON.stringify(pgnList, null, 2))
pgnList = pgnList.filter((f) => f.Fields[i].Match == value);
if (pgnList.length == 0) {
if (this.options.returnNonMatches) {
//this.emit('warning', pgn, `no conversion found for pgn`)
trace('warning no conversion found for pgn %j', pgn);
//continueReading = false
const nonMatch = this.findNonMatchPgn(origPGNList);
if (nonMatch) {
pgnList = [nonMatch];
pgnData = pgnList[0];
fields = pgnData.Fields;
}
else {
if (pgn.pgn >= 0xff00 && pgn.pgn <= 0xffff) {
pgnData = (0, ts_pgns_1.getPGNWithId)('0xff000xffffManufacturerProprietarySingleFrameNonAddressed');
}
else if (pgn.pgn >= 0x1ed00 && pgn.pgn <= 0x1ee00) {
pgnData = (0, ts_pgns_1.getPGNWithId)('0x1ed000x1ee00StandardizedFastPacketAddressed');
}
else {
unknownPGN = true;
}
}
const postProcessor = fieldTypePostProcessors[field.FieldType];
if (postProcessor) {
value = postProcessor(field, value);
}
else if (field.FieldType === 'LOOKUP' &&
(lodash_1.default.isUndefined(this.options.resolveEnums) ||
this.options.resolveEnums)) {
value = lookup(field, value);
}
}
else {
return undefined;
}
}
else {
pgnData = pgnList[0];
fields = pgnData.Fields;
//console.log(`using ${JSON.stringify(pgnData, null, 2)}`)
value = pgnData.Fields[i].Description;
if (value == null) {
value = pgnData.Fields[i].Match;
}
RepeatingFields = pgnData.RepeatingFieldSet1Size
? pgnData.RepeatingFieldSet1Size
: 0;
}
}
if (value !== undefined &&
(value != null || this.options.returnNulls)) {
this.setField(pgn.fields, field, value);
}
}
if (RepeatingFields > 0 && continueReading) {
const repeating = fields.slice(fields.length - RepeatingFields);
const fany = pgn.fields;
fany.list = [];
let count;
if (pgnData.RepeatingFieldSet1CountField !== undefined) {
const rfield = pgnData.Fields[pgnData.RepeatingFieldSet1CountField - 1];
const dataKey = this.options.useCamel ? rfield.Id : rfield.Name;
count = pgn.fields[dataKey];
}
else {
count = 2048;
}
while (bs.bitsLeft > 0 && --count >= 0) {
const group = {};
repeating.forEach((field) => {
if (bs.bitsLeft > 0) {
const [value, refField] = readField(pgnData, this.options, true, pgn, field, bs, fields);
if (refField) {
group.parameterId = refField.Id;
}
if (value !== undefined &&
(value != null || this.options.returnNulls)) {
this.setField(group, field, value);
}
}
});
if (lodash_1.default.keys(group).length > 0) {
fany.list.push(group);
}
}
}
/*
if ( pgnData.callback ) {
pgnData.callback(pgn)
}
*/
const res = this.options.createPGNObjects === false
? pgn
: unknownPGN === false
? (0, ts_pgns_1.createPGN)(pgnData.Id, pgn.fields)
: new PGN_Uknown(pgn.fields);
if (res === undefined) {
this.emit('error', pgn, 'no class');
cb && cb('no class', undefined);
return;
}
if (unknownPGN) {
res.description = 'Unknown PGN';
res.pgn = pgn.pgn;
}
else {
res.description = pgnData.Description;
}
res.src = pgn.src;
res.dst = pgn.dst;
res.prio = pgn.prio;
const apgn = pgn;
if (apgn.canId !== undefined) {
;
res.canId = apgn.canId;
}
if (apgn.time !== undefined) {
;
res.time = apgn.time;
}
if (apgn.timer !== undefined) {
;
res.timer = apgn.timer;
}
if (apgn.direction !== undefined) {
;
res.direction = apgn.direction;
}
if (apgn.input !== undefined) {
;
res.input = apgn.input;
}
// Stringify timestamp because SK Server needs it that way.
const ts = lodash_1.default.get(pgn, 'timestamp', new Date());
res.timestamp = lodash_1.default.isDate(ts) ? ts.toISOString() : ts;
this.emit('pgn', res);
cb && cb(undefined, res);
return res;
}
catch (error) {
this.emit('error', pgn, error);
cb && cb(error, undefined);
return;
}
}
setField(res, field, value) {
if (this.options.useCamelCompat) {
res[field.Id] = value;
res[field.Name] = value;
}
else if (this.options.useCamel) {
res[field.Id] = value;
}
else {
res[field.Name] = value;
}
}
getField(res, field) {
if (this.options.useCamelCompat || this.options.useCamel) {
return res[field.Id];
}
else {
return res[field.Name];
}
}
findNonMatchPgn(pgnList) {
return pgnList.find((f) => {
return !f.Fields.find((f) => f.Match !== undefined);
});
}
findMatchPgn(pgnList) {
return pgnList.find((f) => {
return f.Fields.find((f) => f.Match !== undefined);
});
}
parse(data, cb = undefined) {
if (lodash_1.default.isString(data)) {
return this.parseString(data, cb);
}
else if (lodash_1.default.isBuffer(data)) {
return this.parseBuffer(data, cb);
}
else {
return this.parsePgnData(data.pgn, data.length, data.data, data.coalesced === true, cb, data.sourceString);
}
}
parsePgnData(pgn, length, data, coalesced, cb, sourceString) {
try {
let buffer = data;
if (!lodash_1.default.isBuffer(data)) {
const array = new Int16Array(length);
const strings = data;
strings.forEach((num, index) => {
array[index] = parseInt(num, 16);
});
buffer = Buffer.from(array);
}
const bv = new bit_buffer_1.BitView(buffer);
const bs = new bit_buffer_1.BitStream(bv);
const res = this._parse(pgn, bs, length, coalesced, cb, sourceString);
if (res) {
debug('parsed pgn %j', pgn);
}
return res;
}
catch (error) {
cb && cb(error, undefined);
this.emit('error', pgn, error);
}
}
isN2KOver0183(sentence) {
return (0, stringMsg_1.isN2KOver0183)(sentence);
}
parseN2KOver0183(sentence, cb) {
return this.parseString(sentence, cb);
}
/*
// Venus MQTT-N2K
parseVenusMQTT(pgn_data: any, cb: FromPgnCallback) {
try {
const pgn = {
pgn: pgn_data.pgn,
timestamp: new Date().toISOString(),
src: pgn_data.src,
dst: pgn_data.dst,
prio: pgn_data.prio,
fields: {}
}
const bs = new BitStream(Buffer.from(pgn_data.data, 'base64'))
delete pgn_data.data
const res = this._parse(pgn, bs, 8, false, cb)
if (res) {
debug('parsed pgn %j', pgn)
}
return res
} catch (error) {
cb && cb(error, undefined)
this.emit('error', pgn_data, error)
}
}
*/
//Yacht Devices NMEA2000 Wifi gateway
parseYDGW02(pgn_data, cb) {
try {
const { data, direction, error, ...pgn } = (0, stringMsg_1.parseYDRAW)(pgn_data);
if (!error && direction === 'R') {
const bs = new bit_buffer_1.BitStream(data);
delete pgn.format;
const res = this._parse(pgn, bs, data.length, false, cb, pgn_data);
if (res) {
debug('parsed ydgw02 pgn %j', pgn_data);
return res;
}
}
else if (error) {
cb && cb(error, undefined);
this.emit('error', pgn_data, error);
}
}
catch (error) {
cb && cb(error, undefined);
this.emit('error', pgn_data, error);
}
return undefined;
}
//Actisense W2k-1
parseActisenceN2KAscii(pgn_data, cb) {
try {
const { data, error, ...pgn } = (0, stringMsg_1.parseActisenseN2KASCII)(pgn_data);
if (!error) {
const bs = new bit_buffer_1.BitStream(data);
delete pgn.format;
const res = this._parse(pgn, bs, data.length, false, cb, pgn_data);
if (res) {
debug('parsed n2k ascii pgn %j', pgn_data);
return res;
}
}
else if (error) {
cb && cb(error, undefined);
this.emit('error', pgn_data, error);
}
}
catch (error) {
cb && cb(error, undefined);
this.emit('error', pgn_data, error);
}
return undefined;
}
parsePDGY(pgn_data, cb) {
if (pgn_data[0] != '!') {
return;
}
try {
const { coalesced, data, error, len, ...pgn } = (0, stringMsg_1.parsePDGY)(pgn_data);
if (error) {
cb && cb(error, undefined);
this.emit('error', pgn, error);
return;
}
const bs = new bit_buffer_1.BitStream(data);
delete pgn.format;
delete pgn.type;
delete pgn.prefix;
const res = this._parse(pgn, bs, len || data.length, coalesced, cb, pgn_data);
if (res) {
debug('parsed pgn %j', pgn);
}
return res;
}
catch (error) {
cb && cb(error, undefined);
this.emit('error', pgn_data, error);
}
}
parseString(pgn_data, cb = undefined) {
try {
const { coalesced, data, error, len, ...pgn } = (0, stringMsg_1.parseN2kString)(pgn_data, this.options);
if (error) {
cb && cb(error, undefined);
this.emit('error', pgn, error);
return;
}
const bs = new bit_buffer_1.BitStream(data);
delete pgn.format;
delete pgn.type;
delete pgn.prefix;
const res = this._parse(pgn, bs, len || data.length, coalesced, cb, pgn_data);
if (res) {
debug('parsed pgn %j', pgn);
}
return res;
}
catch (error) {
cb && cb(error, undefined);
this.emit('error', pgn_data, error);
}
}
parseBuffer(pgn_data, cb) {
try {
const bv = new bit_buffer_1.BitView(pgn_data);
const bs = new bit_buffer_1.BitStream(bv);
const pgn = {};
// This might be good to move to canId.js ?
pgn.prio = bs.readUint8();
pgn.pgn = bs.readUint8() + 256 * (bs.readUint8() + 256 * bs.readUint8());
pgn.dst = bs.readUint8();
pgn.src = bs.readUint8();
pgn.timestamp = new Date().toISOString();
//const timestamp = FIXME?? use timestamp?
bs.readUint32();
const len = bs.readUint8();
const res = this._parse(pgn, bs, len, true, cb);
if (res) {
debug('parsed pgn %j', pgn);
}
return res;
}
catch (error) {
const err = new Error(`error reading pgn ${JSON.stringify(pgn_data)} ${error}`);
cb && cb(err, undefined);
this.emit('error', pgn_data, error);
console.error(err);
return;
}
}
}
exports.Parser = Parser;
function getField(pgn_number, index, data) {
let pgnList = (0, pgns_1.getPgn)(pgn_number);
if (pgnList) {
pgnList = pgnList.filter((pgn) => pgn.Fallback === undefined || pgn.Fallback === false);
let pgn = pgnList[0];
const dataList = data.list ? data.list : data.fields.list;
if (pgnList.length > 1) {
let idx = 0;
while (idx < pgn.Fields.length) {
const field = pgn.Fields[idx];
const hasMatch = !lodash_1.default.isUndefined(field.Match);
if (hasMatch && dataList.length > 0) {
const param = dataList.find((f) => {
const param = f.parameter !== undefined ? f.parameter : f.Parameter;
return param === idx + 1;
});
if (param) {
const value = param.value !== undefined ? param.value : param.Value;
pgnList = pgnList.filter((f) => {
return (f.Fields[idx].Match == value ||
f.Fields[idx].Description == value);
});
if (pgnList.length == 0) {
throw new Error('unable to read: ' + JSON.stringify(data));
return;
}
else {
pgn = pgnList[0];
}
}
}
idx++;
}
}
if (index >= 0 && index < pgn.Fields.length) {
return pgn.Fields[index];
}
const RepeatingFields = pgn.RepeatingFieldSet1Size
? pgn.RepeatingFieldSet1Size
: 0;
if (RepeatingFields) {
const startOfRepeatingFields = pgn.Fields.length - RepeatingFields;
index =
startOfRepeatingFields +
((index - startOfRepeatingFields) % RepeatingFields);
return pgn.Fields[index];
}
}
return null;
}
function pad2(x) {
const s = x.toString();
return s.length === 1 ? '0' + x : x;
}
function lookup(field, value) {
let name;
if (field.LookupEnumeration) {
name = (0, ts_pgns_1.getEnumerationName)(field.LookupEnumeration, value);
}
else {
name = (0, ts_pgns_1.getFieldTypeEnumerationName)(field.LookupFieldTypeEnumeration, value);
}
return name ? name : value;
}
function readField(definition, options, runPostProcessor, pgn, field, bs, fields = undefined) {
let value;
let refField = undefined;
const reader = fieldTypeReaders[field.FieldType];
if (reader) {
value = reader(pgn, field, bs);
}
else {
if (field.FieldType !== ts_pgns_1.FieldType.Binary &&
field.BitLength !== undefined &&
bs.bitsLeft < field.BitLength) {
//no more data
bs.readBits(bs.bitsLeft, false);
return [null, undefined];
}
;
[value, refField] = readValue(definition, options, pgn, field, bs, fields);
}
if (refField === undefined) {
return [convertField(field, value, runPostProcessor, options), undefined];
}
else {
return [value, refField];
}
}
function convertField(field, value, runPostProcessor, options) {
if (value != null && value !== undefined) {
const type = field.FieldType; //hack, missing type
const postProcessor = fieldTypePostProcessors[type];
if (postProcessor) {
if (runPostProcessor) {
value = postProcessor(field, value);
}
}
else {
if (field.Offset) {
value += field.Offset;
}
let max;
if (typeof field.RangeMax !== 'undefined' && field.Resolution) {
max = field.RangeMax / field.Resolution;
}
if (options.checkForInvalidFields !== false &&
max !== undefined &&
field.FieldType !== 'LOOKUP' &&
field.FieldType !== 'DYNAMIC_FIELD_KEY' &&
field.FieldType !== 'PGN' &&
field.BitLength !== undefined &&
field.BitLength > 1 &&
max - value < 0) {
//console.log(`Bad field ${field.Name} ${max - value}`)
value = null;
}
if (field.Resolution && typeof value === 'number') {
let resolution = field.Resolution;
if (lodash_1.default.isString(resolution)) {
resolution = Number.parseFloat(resolution);
}
value = value * resolution;
let precision = 0;
for (let r = resolution; r > 0.0 && r < 1.0; r = r * 10.0) {
precision++;
}
value = Number.parseFloat(value.toFixed(precision));
}
if ((field.FieldType === 'LOOKUP' ||
field.FieldType === 'DYNAMIC_FIELD_KEY') &&
runPostProcessor &&
(lodash_1.default.isUndefined(options.resolveEnums) || options.resolveEnums)) {
if (field.Id === 'timeStamp' && value < 60) {
value = value.toString();
}
else {
value = lookup(field, value);
}
}
/*
if ( field.Name === 'Industry Code' && _.isNumber(value) && runPostProcessor ) {
const name = getIndustryName(value)
if ( name ) {
value = name
}
}
*/
if (field.Unit === 'kWh') {
value *= 3.6e6; // 1 kWh = 3.6 MJ.
}
else if (field.Unit === 'Ah') {
value *= 3600.0; // 1 Ah = 3600 C.
}
}
}
return value;
}
function readValue(definition, options, pgn, field, bs, fields, bitLength = undefined) {
if (field.FieldType == 'VARIABLE') {
return readVariableLengthField(definition, options, pgn, field, bs);
}
else {
let value;
if (bitLength === undefined) {
if (field.BitLengthVariable &&
field.FieldType === 'DYNAMIC_FIELD_VALUE') {
bitLength = lookupKeyBitLength(pgn.fields, fields);
}
else {
bitLength = field.BitLength;
}
if (bitLength === undefined) {
//FIXME?? error? mesg? should never happen
return [null, undefined];
}
}
if (field.FieldType === ts_pgns_1.FieldType.Binary && definition.Fallback === true) {
bitLength = bs.bitsLeft < bitLength ? bs.bitsLeft : bitLength;
const data = bs.readArrayBuffer(Math.floor(bitLength / 8));
return [(0, utilities_1.byteString)(Buffer.from(data), ' '), undefined];
}
else if (bitLength === 8) {
if (field.Signed) {
value = bs.readInt8();
value = value === 0x7f ? null : value;
}
else {
value = bs.readUint8();
value = value === 0xff ? null : value;
}
}
else if (bitLength == 16) {
if (field.Signed) {
value = bs.readInt16();
value = value === 0x7fff ? null : value;
}
else {
value = bs.readUint16();
value = value === 0xffff ? null : value;
}
}
else if (bitLength == 24) {
const b1 = bs.readUint8();
const b2 = bs.readUint8();
const b3 = bs.readUint8();
//debug(`24 bit ${b1.toString(16)} ${b2.toString(16)} ${b3.toString(16)}`)
value = (b3 << 16) + (b2 << 8) + b1;
//debug(`value ${value.toString(16)}`)
}
else if (bitLength == 32) {
if (field.Signed) {
value = bs.readInt32();
value = value === 0x7fffffff ? null : value;
}
else {
value = bs.readUint32();
value = value === 0xffffffff ? null : value;
}
}
else if (bitLength == 48) {
const a = bs.readUint32();
const b = bs.readUint16();
if (field.Signed) {
value = a == 0xffffffff && b == 0x7fff ? null : new int64_buffer_1.Int64LE(b, a);
}
else {
value = a == 0xffffffff && b == 0xffff ? null : new int64_buffer_1.Int64LE(b, a);
}
}
else if (bitLength == 64) {
const x = bs.readUint32();
const y = bs.readUint32();
if (field.Signed) {
value =
(x === 0xffffffff || x === 0xfffffffe) && y == 0x7fffffff
? null
: new int64_buffer_1.Int64LE(y, x);
}
else {
value =
(x === 0xffffffff || x === 0xfffffffe) && y == 0xffffffff
? null
: new int64_buffer_1.Uint64LE(y, x);
}
}
else if (bitLength <= 64) {
value = bs.readBits(bitLength, field.Signed);
if (bitLength > 1 && isMax(bitLength, value, field.Signed)) {
value = null;
}
}
else {
if (bs.bitsLeft < bitLength) {
bitLength = bs.bitsLeft;
if (bitLength === undefined) {
return [null, undefined];
}
}
value = bs.readArrayBuffer(bitLength / 8); //, field.Signed)
const arr = [];
value = new Uint32Array(value)
.reduce(function (acc, i) {
acc.push(i.toString(16));
return acc;
}, arr)
.map((x) => (x.length === 1 ? '0' + x : x))
.join(' ');
return [value, undefined];
}
if (value != null &&
typeof value !== 'undefined' &&
typeof value !== 'number') {
value = Number(value);
}
return [value, undefined];
}
}
function isMax(numBits, value, signed) {
if (signed) {
numBits--;
}
while (numBits--) {
if ((value & 1) == 0) {
return false;
}
value = value >> 1;
}
return signed ? (value & 1) == 0 : true;
}
function readVariableLengthField(definition, options, pgn, field, bs) {
/* PGN 126208 contains variable field length.
* The field length can be derived from the PGN mentioned earlier in the message,
* plus the field number.
*/
/*
* This is rather hacky. We know that the 'data' pointer points to the n-th variable field
* length and thus that the field number is exactly one byte earlier.
*/
try {
const refField = getField(pgn.fields.pgn || pgn.fields.PGN, bs.view.buffer[bs.byteIndex - 1] - 1, pgn);
if (refField) {
const [res] = readField(definition, options, true, pgn, refField, bs);
if (refField.BitLength !== undefined) {
const bits = (refField.BitLength + 7) & ~7; // Round # of bits in field refField up to complete bytes: 1->8, 7->8, 8->8 etc.
if (bits > refField.BitLength) {
bs.readBits(bits - refField.BitLength, false);
}
}
return [res, refField];
}
}
catch (error) {
debug(error);
}
return [null, undefined];
}
fieldTypeReaders['STRING_LAU'
//'ASCII or UNICODE string starting with length and control byte'
] = (pgn, field, bs) => {
if (bs.bitsLeft >= 16) {
const len = bs.readUint8() - 2;
const control = bs.readUint8();
let nameLen = len;
if (field.Name === 'AtoN Name' && len > 20) {
nameLen = 20;
}
else if (len <= 0) {
return null;
}
const buf = Buffer.alloc(len);
let idx = 0;
for (; idx < len && bs.bitsLeft >= 8; idx++) {
const c = bs.readUint8();
buf.writeUInt8(c, idx);
}
if (buf[buf.length - 1] === 0) {
nameLen = nameLen - 1;
}
return buf
.toString(control == 0 ? 'utf8' : 'ascii', 0, idx < nameLen ? idx : nameLen)
.trim();
}
else {
return null;
}
};
fieldTypeReaders['STRING_LZ'
//'ASCII string starting with length byte'
] = (pgn, field, bs) => {
const len = bs.readUint8();
const buf = Buffer.alloc(len);
let idx = 0;
for (; idx < len && bs.bitsLeft >= 8; idx++) {
const c = bs.readUint8();
buf.writeUInt8(c, idx);
}
return buf.toString('utf-8', 0, idx);
};
fieldTypeReaders['String with start/stop byte'] = (pgn, field, bs) => {
const first = bs.readUint8();
if (first == 0xff) {
// no name, stop reading
return '';
}
else if (first == 0x02) {
const buf = Buffer.alloc(255);
let c;
let idx = 0;
while ((c = bs.readUint8()) != 0x01) {
buf.writeUInt8(c, idx++);
}
return buf.toString('ascii', 0, idx);
}
else if (first > 0x02) {
let len = first;
const second = bs.readUint8();
const buf = Buffer.alloc(len);
let idx = 0;
if (second == 0x01) {
len -= 2;
}
else {
buf.writeUInt8(second);
idx = 1;
}
for (; idx < len; idx++) {
const c = bs.readUint8();
buf.writeUInt8(c, idx);
}
return buf.toString('ascii', 0, idx);
}
};
fieldTypeReaders['STRING_FIX'] = (pgn, field, bs) => {
let len = field.BitLength / 8;
const buf = Buffer.alloc(len);
for (let i = 0; i < len && bs.bitsLeft >= 8; i++) {
buf.writeUInt8(bs.readUint8(), i);
}
let lastbyte = buf[len - 1];
while (len > 0 &&
(lastbyte == 0xff || lastbyte == 32 || lastbyte == 0 || lastbyte == 64)) {
len--;
lastbyte = buf[len - 1];
}
//look for a zero byte, some proprietary Raymarine pgns do this
let zero = 0;
while (zero < len) {
if (buf[zero] == 0) {
len = zero;
break;
}
zero++;
}
len = zero;
return len > 0 ? buf.toString('ascii', 0, len) : undefined;
};
fieldTypeReaders['BITLOOKUP'] = (pgn, field, bs) => {
const value = [];
for (let i = 0; i < field.BitLength; i++) {
if (bs.readBits(1, false)) {
value.push((0, ts_pgns_1.getBitEnumerationName)(field.LookupBitEnumeration, i));
}
}
return value;
};
function lookupKeyBitLength(data, fields) {
const field = fields.find((field) => field.Name === 'Key');
if (field) {
let val = data['Key'] || data['key'];
if (typeof val === 'string') {
val = (0, ts_pgns_1.getFieldTypeEnumerationValue)(field.LookupFieldTypeEnumeration, val);
}
return (0, ts_pgns_1.getFieldTypeEnumerationBits)(field.LookupFieldTypeEnumeration, val);
}
}
fieldTypePostProcessors['DATE'] = (field, value) => {
if (value >= 0xfffd) {
value = undefined;
}
else {
const date = new Date(value * 86400 * 1000);
//const date = moment.unix(0).add(value+1, 'days').utc().toDate()
value = `${date.getUTCFullYear()}.${pad2(date.getUTCMonth() + 1)}.${pad2(date.getUTCDate())}`;
}
return value;
};
fieldTypePostProcessors['TIME'] = (field, value) => {
if (value >= 0xfffffffd) {
value = undefined;
}
else {
let seconds = value * field.Resolution;
let minutes = seconds / 60;
seconds = seconds % 60;
const hours = Math.floor(minutes / 60);
minutes = Math.floor(minutes % 60);
value = `${pad2(hours)}:${pad2(minutes)}:${pad2(Math.floor(seconds))}`;
if (seconds % 1 > 0) {
value = value + (seconds % 1).toFixed(5).substring(1);
}
}
return value;
};
fieldTypePostProcessors['DURATION'] = fieldTypePostProcessors['TIME'];
fieldTypePostProcessors['Pressure'] = (field, value) => {
if (field.Unit) {
switch (field.Unit[0]) {
case 'h':
case 'H':
value *= 100;
break;
case 'k':
case 'K':
value *= 1000;
break;
case 'd':
value /= 10;
break;
}
}
return value;
};
fieldTypePostProcessors[RES_BINARY] = (field, value) => {
return value.toString();
};
class PGN_Uknown extends ts_pgns_1.PGN {
constructor(fields) {
super({});
this.fields = fields;
}
getDefinition() {
return {
PGN: this.pgn,
Id: 'unknown',
Description: 'Unknown PGN',
Type: ts_pgns_1.Type.Single,
Complete: false,
Priority: 3,
Fields: []
};
}
}
//# sourceMappingURL=fromPgn.js.map