js-aprs-fap
Version:
NodeJs library for parsing APRS packets.
1,212 lines (1,211 loc) • 58.5 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 aprsPacket_1 = __importDefault(require("./aprsPacket"));
const ConversionConstantEnum_1 = require("./ConversionConstantEnum");
const ConversionUtil_1 = require("./ConversionUtil");
const digipeater_1 = __importDefault(require("./digipeater"));
const DSTSymbols_1 = require("./DSTSymbols");
const ResultMessages_1 = require("./ResultMessages");
const telemetry_1 = __importDefault(require("./telemetry"));
const wx_1 = __importDefault(require("./wx"));
const PacketTypeEnum_1 = require("./PacketTypeEnum");
class aprsParser {
constructor() { }
addError(packet, errorCode, value) {
packet.resultCode = errorCode;
packet.resultMessage = ((ResultMessages_1.RESULT_MESSAGES[errorCode] !== undefined) ? ResultMessages_1.RESULT_MESSAGES[errorCode] : errorCode)
+ `: ${value}`;
return packet;
}
addWarning(packet, errorCode, value) {
if (packet.warningCodes == undefined || !packet.warningCodes) {
packet.warningCodes = [];
}
packet.warningCodes.push(errorCode);
packet.resultMessage = ((ResultMessages_1.RESULT_MESSAGES[errorCode] !== undefined && ResultMessages_1.RESULT_MESSAGES[errorCode]) ? ResultMessages_1.RESULT_MESSAGES[errorCode] : errorCode)
+ ((value !== undefined && value) ? `: ${value}` : '');
return packet;
}
checkAX25Call(callsign) {
let tempCallsign;
if ((tempCallsign = callsign.match(/^([A-Z0-9]{1,6})(-\d{1,2}|)$/))) {
if (!tempCallsign[2]) {
return tempCallsign[1];
}
else {
let $ssid = 0 - parseInt(tempCallsign[2]);
if ($ssid < 16) {
return tempCallsign[1] + '-' + $ssid;
}
}
}
return null;
}
parseaprs(packet, options) {
let retVal = new aprsPacket_1.default();
let isax25 = (options && options['isax25'] != undefined) ? options['isax25'] : false;
retVal.origpacket = packet;
if (packet === undefined) {
return this.addError(retVal, 'packet_no');
;
}
if (!packet || packet.length < 1) {
return this.addError(retVal, 'packet_short');
}
let [header, body] = packet.split(/:(.*)/);
if (!body) {
return this.addError(retVal, 'packet_nobody');
}
retVal.header = header;
retVal.body = body;
let srcCallsign;
let rest;
let $header;
if (($header = header.match(/^([A-Z0-9-]{1,9})>(.*)$/i))) {
rest = $header[2];
if (isax25 == false) {
srcCallsign = $header[1];
}
else {
srcCallsign = this.checkAX25Call($header[1].toUpperCase());
if (!srcCallsign) {
return this.addError(retVal, 'srccall_noax25');
}
}
}
else {
return this.addError(retVal, 'srccall_badchars');
}
retVal.sourceCallsign = srcCallsign;
let pathcomponents = rest.split(',');
if (isax25 == true) {
if (pathcomponents.length > 9) {
return this.addError(retVal, 'dstpath_toomany');
}
}
if (pathcomponents.length === 1 && pathcomponents[0] === '') {
return this.addError(retVal, 'dstcall_none');
}
let dstcallsign = this.checkAX25Call(pathcomponents.shift());
if (!dstcallsign) {
return this.addError(retVal, 'dstcall_noax25');
}
retVal.destCallsign = dstcallsign;
let digipeaters = [];
if (isax25 == true) {
for (let digi of pathcomponents) {
let d;
if ((d = digi.match(/^([A-Z0-9-]+)(\*|)$/i))) {
let digitested = this.checkAX25Call(d[1].toUpperCase());
if (!digitested) {
return this.addError(retVal, `${digi} digicall_noax25`);
}
digipeaters.push(new digipeater_1.default(digitested, (d[2] == '*')));
}
else {
return this.addError(retVal, 'digicall_badchars');
}
}
}
else {
let seen_qconstr = false;
let tmp = null;
for (let digi of pathcomponents) {
if ((tmp = digi.match(/^([A-Z0-9a-z-]{1,9})(\*|)$/))) {
digipeaters.push(new digipeater_1.default(tmp[1], (tmp[2] == '*')));
seen_qconstr = /^q..$/.test(tmp[1]) || seen_qconstr;
}
else {
if (seen_qconstr == true && (tmp = digi.match(/^([0-9A-F]{32})$/))) {
digipeaters.push(new digipeater_1.default(tmp[1], false));
}
else {
return this.addError(retVal, 'digicall_badchars');
}
}
}
}
retVal.digipeaters = digipeaters;
let packettype = body.charAt(0);
let paclen = body.length;
if (packettype == '\'' || packettype == '`') {
if (paclen >= 9) {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.LOCATION;
retVal = this.miceToDecimal(body.substring(1), dstcallsign, srcCallsign, retVal, options);
}
}
else if (packettype == '!' || packettype == '=' ||
packettype == '/' || packettype == '@') {
retVal.messaging = !(packettype == '!' || packettype == '/');
if (paclen >= 14) {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.LOCATION;
if (packettype == '/' || packettype == '@') {
retVal.timestamp = this.parseTimestamp(options, body.substring(1, 8));
if (retVal.timestamp == 0) {
this.addWarning(retVal, 'timestamp_inv_loc');
}
body = body.substring(7);
}
body = body.substring(1);
let poschar = body.charCodeAt(0);
if (poschar >= 48 && poschar <= 57) {
if (body.length >= 19) {
retVal = this._normalpos_to_decimal(body, srcCallsign, retVal);
if ((retVal.resultCode === undefined && !retVal.resultCode) && retVal.symbolcode != '_') {
retVal = this._comments_to_decimal(body.substring(19), srcCallsign, retVal);
}
else {
retVal = this._wx_parse(body.substring(19), retVal);
}
}
}
else if (poschar == 47 || poschar == 92
|| (poschar >= 65 && poschar <= 90)
|| (poschar >= 97 && poschar <= 106)) {
if (body.length >= 13) {
retVal = this._compressed_to_decimal(body.substring(0, 13), srcCallsign, retVal);
if ((retVal.resultCode === undefined && !retVal.resultCode) && retVal.symbolcode != '_') {
retVal = this._comments_to_decimal(body.substring(13), srcCallsign, retVal);
}
else {
retVal = this._wx_parse(body.substring(13), retVal);
}
}
else {
return this.addError(retVal, 'packet_invalid', 'Body is too short.');
}
}
else if (poschar == 33) {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.WEATHER;
retVal = this._wx_parse_peet_logging(body.substring(1), srcCallsign, retVal);
}
else {
return this.addError(retVal, 'packet_invalid');
}
}
else {
return this.addError(retVal, 'packet_short', 'location');
}
}
else if (packettype == '_') {
if (/_(\d{8})c[\- \.\d]{1,3}s[\- \.\d]{1,3}/.test(body)) {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.WEATHER;
retVal = this._wx_parse(body.substring(9), retVal);
}
else {
return this.addError(retVal, 'wx_unsupp', 'Positionless');
}
}
else if (packettype == ';') {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.OBJECT;
retVal = this.objectToDecimal(options, body, srcCallsign, retVal);
}
else if (packettype == '$') {
if (body.substring(0, 3) == '$GP') {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.LOCATION;
retVal = this._nmea_to_decimal(options, body.substring(1), srcCallsign, dstcallsign, retVal);
}
else if (body.substring(0, 5) == '$ULTW') {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.WEATHER;
retVal = this._wx_parse_peet_packet(body.substring(5), srcCallsign, retVal);
}
}
else if (packettype == ')') {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.ITEM;
retVal = this._item_to_decimal(body, srcCallsign, retVal);
}
else if (packettype === ':') {
if (paclen >= 11) {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.MESSAGE;
retVal = this.messageParse(body, retVal);
}
}
else if (packettype == '<') {
if (paclen >= 2) {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.CAPABILITIES;
retVal = this._capabilities_parse(body.substring(1), srcCallsign, retVal);
}
}
else if (packettype == '>') {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.STATUS;
retVal = this._status_parse(options, body.substring(1), srcCallsign, retVal);
}
else if (/^T#(.*?),(.*)$/.test(body)) {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.TELEMETRY;
retVal = this._telemetry_parse(body.substring(2), retVal);
}
else if (/^\{\{/i.test(body)) {
return this.addError(retVal, 'exp_unsupp');
}
else {
let pos = body.indexOf('!');
if (pos >= 0 && pos <= 39) {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.LOCATION;
retVal.messaging = false;
let pChar = body.substring(pos + 1, pos + 2);
if (/^[\/\\A-Za-j]$/.test(pChar)) {
if (body.length >= (pos + 1 + 13)) {
retVal = this._compressed_to_decimal(body.substring(pos + 1, pos + 13), srcCallsign, retVal);
if (retVal.resultCode === undefined && !retVal.resultCode && retVal.symbolcode != '_') {
retVal = this._comments_to_decimal(body.substring(pos + 14), srcCallsign, retVal);
}
}
}
else if (/^\d$/i.test(pChar)) {
if (body.length >= (pos + 1 + 19)) {
retVal = this._normalpos_to_decimal(body.substring(pos + 1), srcCallsign, retVal);
if (!retVal.resultMessage && retVal.symbolcode != '_') {
retVal = this._comments_to_decimal(body.substring(pos + 20), srcCallsign, retVal);
}
}
}
}
}
return retVal;
}
_status_parse(options, packet, srccallsign, rethash) {
let tmp;
packet = packet.trim();
if ((tmp = packet.match(/^(\d{6}z)/))) {
rethash.timestamp = this.parseTimestamp({}, tmp[1]);
if (rethash.timestamp == 0) {
rethash = this.addWarning(rethash, 'timestamp_inv_sta');
}
packet = packet.substring(7);
}
rethash.status = packet;
return rethash;
}
parseTimestamp = function (options, stamp) {
if (!(stamp = stamp.match(/^(\d{2})(\d{2})(\d{2})(z|h|\/)$/))) {
return 0;
}
if (options && options['raw_timestamp']) {
return stamp[1] + stamp[2] + stamp[3];
}
let stamptype = stamp[4];
if (stamptype == 'h') {
let hour = stamp[1];
let minute = stamp[2];
let second = stamp[3];
if (hour > 23 || minute > 59 || second > 59) {
return 0;
}
let ts = new Date();
let currentTime = Math.floor(ts.getTime() / 1000);
let cYear = ts.getUTCFullYear();
let cMonth = ts.getUTCMonth();
let cDay = ts.getUTCDate();
let tStamp = Math.floor(new Date(Date.UTC(cYear, cMonth, cDay, hour, minute, second, 0)).getTime() / 1000);
if (currentTime + 3900 < tStamp) {
tStamp -= 86400;
}
else if (currentTime - 82500 > tStamp) {
tStamp += 86400;
}
return tStamp;
}
else if (stamptype == 'z' || stamptype == '/') {
let day = parseInt(stamp[1]);
let hour = parseInt(stamp[2]);
let minute = parseInt(stamp[3]);
if (day < 1 || day > 31 || hour > 23 || minute > 59) {
return 0;
}
let ts = new Date();
let currentTime = Math.floor(ts.getTime() / 1000);
let cYear;
let cMonth;
let cDay;
if (stamptype === 'z') {
cYear = ts.getUTCFullYear();
cMonth = ts.getUTCMonth();
cDay = ts.getUTCDate();
}
else {
cYear = ts.getFullYear();
cMonth = ts.getMonth();
cDay = ts.getDate();
}
let tmpDate = new Date(cYear, cMonth, cDay, 0, 0, 0, 0);
tmpDate.setDate(tmpDate.getMonth() + 1);
let fwdYear = tmpDate.getFullYear();
let fwdMonth = tmpDate.getMonth();
tmpDate = new Date(cYear, cMonth, cDay, 0, 0, 0, 0);
tmpDate.setDate(tmpDate.getMonth() - 1);
let backYear = tmpDate.getFullYear();
let backMonth = tmpDate.getMonth();
let fwdtstamp = null;
let currtstamp = null;
let backtstamp = null;
if (ConversionUtil_1.ConversionUtil.CheckDate(cYear, cMonth, day)) {
if (stamptype === 'z') {
currtstamp = Math.floor(new Date(Date.UTC(cYear, cMonth, cDay, hour, minute, 0, 0)).getTime() / 1000);
}
else {
currtstamp = Math.floor(new Date(cYear, cMonth, day, hour, minute, 0, 0).getTime() / 1000);
}
}
if (ConversionUtil_1.ConversionUtil.CheckDate(fwdYear, fwdMonth, day)) {
if (stamptype === 'z') {
fwdtstamp = Math.floor(new Date(Date.UTC(fwdYear, fwdMonth, day, hour, minute, 0, 0)).getTime() / 1000);
}
else {
fwdtstamp = Math.floor(new Date(cYear, cMonth, day, hour, minute, 0, 0).getTime() / 1000);
}
}
if (ConversionUtil_1.ConversionUtil.CheckDate(backYear, backMonth, day)) {
if (stamptype === 'z') {
backtstamp = Math.floor(new Date(Date.UTC(backYear, backMonth, day, hour, minute, 0, 0)).getTime() / 1000);
}
else {
backtstamp = Math.floor(new Date(cYear, cMonth, day, hour, minute, 0, 0).getTime() / 1000);
}
}
if (fwdtstamp && (fwdtstamp - currentTime) < 43400) {
return fwdtstamp;
}
else if (currtstamp && (currtstamp - currentTime) < 43400) {
return currtstamp;
}
else if (backtstamp) {
return backtstamp;
}
}
return 0;
};
messageParse(packet, retVal) {
let tmp;
if ((tmp = packet.match(/^:([A-Za-z0-9_ -]{9}):([ -~]+)$/))) {
const message = tmp[2];
retVal.destination = tmp[1].trim();
if ((tmp = message.match(/^ack([A-Za-z0-9}]{1,5})\s*$/))) {
retVal.messageAck = tmp[1];
return retVal;
}
else if ((tmp = message.match(/^rej([A-Za-z0-9}]{1,5})\s*$/))) {
retVal.messageReject = tmp[1];
return retVal;
}
else if ((tmp = message.match(/^([^{]*)\{([A-Za-z0-9]{1,5})(}[A-Za-z0-9]{1,5}|\}|)\s*$/))) {
retVal.message = tmp[1];
retVal.messageId = tmp[2];
if (tmp.length > 2 && tmp[3] != null && tmp[3].length > 1) {
retVal.messageAck = tmp[3].substring(1);
}
}
else {
retVal.message = message;
}
if (/^(BITS|PARM|UNIT|EQNS)\./i.test(message)) {
retVal.type = PacketTypeEnum_1.PacketTypeEnum.TELEMETRY_MESSAGE;
}
if (/[|~{]+/.test(retVal.message)) {
return this.addError(retVal, 'msg_inv');
}
return retVal;
}
return this.addError(retVal, 'msg_inv');
}
objectToDecimal(options, packet, srcCallsign, retVal) {
let tmp;
if (packet.length < 31) {
return this.addError(retVal, 'obj_short');
}
let timeStamp;
if ((tmp = packet.match(/^;([\x20-\x7e]{9})(\*|_)(\d{6})(z|h|\/)/))) {
retVal.objectname = tmp[1];
retVal.alive = (tmp[2] == '*');
timeStamp = tmp[3] + tmp[4];
}
else {
return this.addError(retVal, 'obj_inv');
}
retVal.timestamp = this.parseTimestamp(options, timeStamp);
if (retVal.timestamp == 0) {
retVal = this.addWarning(retVal, 'timestamp_inv_obj');
}
let locationOffset = 18;
let locationChar = packet.charAt(18);
if (/^[\/\\A-Za-j]$/.test(locationChar)) {
retVal = this._compressed_to_decimal(packet.substring(18, 31), srcCallsign, retVal);
locationOffset = 31;
}
else if (/^\d$/i.test(locationChar)) {
retVal = this._normalpos_to_decimal(packet.substring(18), srcCallsign, retVal);
locationOffset = 37;
}
else {
return this.addError(retVal, 'obj_dec_err');
}
if (retVal.resultCode != undefined && retVal.resultCode) {
return retVal;
}
if (retVal.symbolcode != '_') {
retVal = this._comments_to_decimal(packet.substring(locationOffset), srcCallsign, retVal);
}
else {
retVal = this._wx_parse(packet.substring(locationOffset), retVal);
}
return retVal;
}
get_posresolution(dec) {
return parseFloat((ConversionConstantEnum_1.ConversionConstantEnum.KNOT_TO_KMH * (dec <= -2 ? 600 : 1000) * Math.pow(10, (-1 * dec))).toFixed(4));
}
_nmea_getlatlon(value, sign, rethash) {
let tmp;
let retVal;
sign = sign.toUpperCase();
if ((tmp = value.match(/^\s*(\d{1,3})([0-5][0-9])\.(\d+)\s*$/))) {
let minutes = `${tmp[2]}.${tmp[3]}`;
retVal = parseFloat(tmp[1]) + (parseFloat(minutes) / 60);
rethash.posresolution = this.get_posresolution(tmp[3].length);
}
else {
return [this.addError(rethash, 'nmea_inv_cval', value), null];
}
if (/^\s*[EW]\s*$/.test(sign)) {
if (retVal > 179.999999) {
return [this.addError(rethash, 'nmea_large_ew', value), null];
}
if (/^\s*W\s*$/.test(sign)) {
retVal *= -1;
}
}
else if (/^\s*[NS]\s*$/.test(sign)) {
if (retVal > 89.999999) {
return [this.addError(rethash, 'nmea_large_ns', value), null];
}
if (/^\s*S\s*$/.test(sign)) {
retVal *= -1;
}
}
else {
return [this.addError(rethash, 'nmea_inv_sign', sign), null];
}
return [rethash, retVal];
}
_get_symbol_fromdst(dstCallsign) {
let table;
let code;
let tmp;
if (tmp = dstCallsign.match(/^(GPS|SPC)([A-Z0-9]{2,3})/)) {
let leftoverstring = tmp[2];
let type = leftoverstring.substring(0, 1);
let sublength = leftoverstring.length;
if (sublength === 3) {
if (type === 'C' || type === 'E') {
let numberid = leftoverstring.substring(1, 2);
if (/^(\d{2})$/.test(numberid) && parseInt(numberid) > 0 && parseInt(numberid) < 95) {
code = String.fromCharCode(parseInt(tmp[1]) + 32);
if (type === 'C') {
table = '/';
}
else {
table = "\\";
}
return [table, code];
}
else {
return [null, null];
}
}
else {
let dsttype = leftoverstring.substring(0, 2);
let overlay = leftoverstring.substring(2, 3);
if ((type === 'O' || type === 'A' || type === 'N'
|| type === 'D' || type === 'S' || type === 'Q')
&& (/^[A-Z0-9]$/).test(overlay)) {
if (dsttype in DSTSymbols_1.DST_SYMBOLS) {
code = DSTSymbols_1.DST_SYMBOLS[dsttype].substring(1, 2);
return [overlay, code];
}
else {
return [null, null];
}
}
else {
return [null, null];
}
}
}
else {
if (leftoverstring in DSTSymbols_1.DST_SYMBOLS) {
let dstsymbol = DSTSymbols_1.DST_SYMBOLS[leftoverstring];
table = dstsymbol.substring(0, 1);
code = dstsymbol.substring(1, 2);
return [table, code];
}
else {
return [null, null];
}
}
}
else {
return [null, null];
}
}
_nmea_to_decimal(options, body, srccallsign, dstcallsign, rethash) {
let tmp;
body = body.trimRight();
if ((tmp = body.match(/^([\x20-\x7e]+)\*([0-9A-F]{2})$/i))) {
const checksumarea = tmp[1];
let checksumgiven = parseInt(tmp[2], 16).toString(10);
let checksumcalculated = 0;
for (var i = 0; i < checksumarea.length; i++) {
checksumcalculated ^= checksumarea.charCodeAt(i);
}
if (checksumgiven != checksumcalculated.toString()) {
return this.addError(rethash, 'nmea_inv_cksum');
}
rethash.checksumok = true;
}
rethash.format = 'nmea';
let [symtable, symcode] = this._get_symbol_fromdst(dstcallsign);
if (!symtable || !symcode) {
rethash.symboltable = '/';
rethash.symbolcode = '/';
}
else {
rethash.symboltable = symtable;
rethash.symbolcode = symcode;
}
body = body.replace(/\*[0-9A-F]{2}$/, '');
let nmeafields = body.split(',');
if (nmeafields[0] == 'GPRMC') {
if (nmeafields.length < 10) {
return this.addError(rethash, 'gprmc_fewfields', nmeafields);
}
if (nmeafields[2] != 'A') {
return this.addError(rethash, 'gprmc_nofix');
}
let hour;
let minute;
let second;
if ((tmp = nmeafields[1].match(/^\s*(\d{2})(\d{2})(\d{2})(|\.\d+)\s*$/))) {
if (parseInt(tmp[1]) > 23 || parseInt(tmp[2]) > 59 || parseInt(tmp[3]) > 59) {
return this.addError(rethash, 'gprmc_inv_time', nmeafields[1]);
}
hour = parseInt(tmp[1]);
minute = parseInt(tmp[2]);
second = parseInt(tmp[3]);
}
else {
return this.addError(rethash, 'gprmc_inv_time');
}
let year;
let month;
let day;
if ((tmp = nmeafields[9].match(/^\s*(\d{2})(\d{2})(\d{2})\s*$/))) {
year = 2000 + parseInt(tmp[3]);
if (parseInt(tmp[3]) >= 70) {
year = 1900 + parseInt(tmp[3]);
}
if (!(ConversionUtil_1.ConversionUtil.CheckDate(year, parseInt(tmp[2]) - 1, parseInt(tmp[1])))) {
return this.addError(rethash, 'gprmc_inv_date', `${year} ${parseInt(tmp[2]) - 1} ${tmp[1]}`);
}
month = parseInt(tmp[2]) - 1;
day = parseInt(tmp[1]);
}
else {
return this.addError(rethash, 'gprmc_inv_date');
}
if (year >= 2038 || year < 1970) {
rethash.timestamp = 0;
return this.addError(rethash, 'gprmc_date_out', year);
}
else {
let d = new Date(Date.UTC(year, month, day, hour, minute, second, 0));
rethash.timestamp = d.getTime() / 1000;
}
if ((tmp = nmeafields[7].match(/^\s*(\d+(|\.\d+))\s*$/))) {
rethash.speed = parseFloat(tmp[1]) * ConversionConstantEnum_1.ConversionConstantEnum.KNOT_TO_KMH;
}
if ((tmp = nmeafields[8].match(/^\s*(\d+(|\.\d+))\s*$/))) {
let course = Math.round((parseFloat(tmp[1]) + 0.5));
if (course == 0) {
course = 360;
}
else if (course > 360) {
course = 0;
}
rethash.course = course;
}
else {
rethash.course = 0;
}
let latitude;
[rethash, latitude] = this._nmea_getlatlon(nmeafields[3], nmeafields[4], rethash);
if (latitude === undefined || !latitude) {
return rethash;
}
rethash.latitude = latitude;
let longitude;
[rethash, longitude] = this._nmea_getlatlon(nmeafields[5], nmeafields[6], rethash);
if (longitude === undefined || !longitude) {
return rethash;
}
rethash.longitude = longitude;
return rethash;
}
else {
return this.addError(rethash, 'nmea_unsupp', nmeafields[0].replace(/[\x00-\x1f]/, (x) => { return parseInt(x, 16).toString(16); }));
}
}
_comments_to_decimal(rest, srccallsign, rethash) {
let tmprest;
if (rest.length >= 7) {
if (/^([0-9. ]{3})\/([0-9. ]{3})/.test(rest)) {
let [, course, speed] = rest.match(/^([0-9. ]{3})\/([0-9. ]{3})/);
let match;
if (/^\d{3}$/.test(course) &&
parseInt(course) <= 360 &&
parseInt(course) >= 1) {
rethash.course = parseInt(course);
}
else {
rethash.course = 0;
}
if (/^\d{3}$/.test(speed)) {
rethash.speed = parseInt(speed) * ConversionConstantEnum_1.ConversionConstantEnum.KNOT_TO_KMH;
}
rest = rest.substring(7);
}
else if ((tmprest = rest.match(/^PHG(\d[\x30-\x7e]\d\d[0-9A-Z])\//))) {
rethash.phg = tmprest[1];
rest = rest.substring(8);
}
else if ((tmprest = rest.match(/^PHG(\d[\x30-\x7e]\d\d)/))) {
rethash.phg = tmprest[1];
rest = rest.substring(7);
}
else if ((tmprest = rest.match(/^RNG(\d{4})/))) {
rethash.radiorange = parseInt(tmprest[1]) * ConversionConstantEnum_1.ConversionConstantEnum.MPH_TO_KMH;
rest = rest.substring(7);
}
}
if ((tmprest = rest.match(/^(.*?)\/A=(-\d{5}|\d{6})(.*)$/))) {
rethash.altitude = parseFloat(tmprest[2]) * ConversionConstantEnum_1.ConversionConstantEnum.FEET_TO_METERS;
rest = tmprest[1] + tmprest[3];
}
[rest, rethash] = this._comment_telemetry(rethash, rest);
if ((tmprest = rest.match(/^(.*)\!([\x21-\x7b][\x20-\x7b]{2})\!(.*?)$/))) {
let found = false;
[rethash, found] = this._dao_parse(tmprest[2], srccallsign, rethash);
if (found === true) {
rest = tmprest[1] + tmprest[3];
}
}
rest = rest.replace(/^[\/\s]/, '');
if (rest.length > 0) {
rethash.comment = rest.trim();
}
return rethash;
}
_capabilities_parse(packet, srccallsign, rethash) {
return rethash;
}
_comment_telemetry(rethash, rest) {
rest = rest.replace(/^(.*)\|([!-{]{2})([!-{]{2})([!-{]{2}|)([!-{]{2}|)([!-{]{2}|)([!-{]{2}|)([!-{]{2}|)\|(.*)$/, function (a, b, c, d, e, f, g, h, i, j) {
rethash.telemetry = new telemetry_1.default(((c.charCodeAt(0) - 33) * 91) + ((c.charCodeAt(1) - 33)), [
((d.charCodeAt(0) - 33) * 91) + (d.charCodeAt(1) - 33),
e != '' ? ((e.charCodeAt(0) - 33) * 91) + ((e.charCodeAt(1) - 33)) : null,
f != '' ? ((f.charCodeAt(0) - 33) * 91) + ((f.charCodeAt(1) - 33)) : null,
g != '' ? ((g.charCodeAt(0) - 33) * 91) + ((g.charCodeAt(1) - 33)) : null,
h != '' ? ((h.charCodeAt(0) - 33) * 91) + ((h.charCodeAt(1) - 33)) : null
]);
if (i != '') {
let bitint = (((i.charCodeAt(0) - 33) * 91) + ((i.charCodeAt(1) - 33)));
let bitstr = (bitint << 7).toString(2);
rethash.telemetry.bits = '00000000'.substring(0, 8 - bitstr.length) + bitstr;
}
return b + j;
});
return [rest, rethash];
}
_item_to_decimal(packet, srccallsign, rethash) {
let tmp;
if (packet.length < 18) {
return this.addError(rethash, 'item_short');
}
if ((tmp = packet.match(/^\)([\x20\x22-\x5e\x60-\x7e]{3,9})(!|_)/))) {
rethash.itemname = tmp[1];
if (tmp[2] == '!') {
rethash.alive = true;
}
else {
rethash.alive = false;
}
}
else {
return this.addError(rethash, 'item_inv');
}
let locationoffset = 2 + rethash.itemname.length;
let locationchar = packet.charAt(locationoffset);
if (/^[\/\\A-Za-j]$/.test(locationchar)) {
rethash = this._compressed_to_decimal(packet.substring(locationoffset, locationoffset + 13), srccallsign, rethash);
locationoffset += 13;
}
else if (/^\d$/i.test(locationchar)) {
rethash = this._normalpos_to_decimal(packet.substring(locationoffset), srccallsign, rethash);
locationoffset += 19;
}
else {
return this.addError(rethash, 'item_dec_err');
}
if (rethash.resultCode !== undefined && rethash.resultCode) {
return rethash;
}
if (rethash.symbolcode != '_') {
rethash = this._comments_to_decimal(packet.substring(locationoffset), srccallsign, rethash);
}
return rethash;
}
_normalpos_to_decimal(packet, srccallsign, rethash) {
if (packet.length < 19) {
return this.addError(rethash, 'loc_short');
}
rethash.format = 'uncompressed';
let lon_deg;
let lat_deg;
let lon_min;
let lat_min;
let issouth = 0;
let iswest = 0;
let symboltable;
let matches;
if ((matches = packet.match(/^(\d{2})([0-7 ][0-9 ]\.[0-9 ]{2})([NnSs])(.)(\d{3})([0-7 ][0-9 ]\.[0-9 ]{2})([EeWw])([\x21-\x7b\x7d])/))) {
let sind = matches[3].toUpperCase();
let wind = matches[7].toUpperCase();
symboltable = matches[4];
rethash.symbolcode = matches[8];
if (sind == 'S') {
issouth = 1;
}
if (wind == 'W') {
iswest = 1;
}
lat_deg = matches[1];
lat_min = matches[2];
lon_deg = matches[5];
lon_min = matches[6];
}
else {
return this.addError(rethash, 'loc_inv');
}
if (!symboltable.match(/^[\/\\A-Z0-9]$/)) {
return this.addError(rethash, 'sym_inv_table');
}
rethash.symboltable = symboltable;
if (parseInt(lat_deg) > 89 || parseInt(lon_deg) > 179) {
return this.addError(rethash, 'loc_large');
}
let tmplat = lat_min.replace(/\./, '');
if ((matches = tmplat.match(/^(\d{0,4})( {0,4})$/i))) {
rethash.posambiguity = matches[2].length;
}
else {
return this.addError(rethash, 'loc_amb_inv');
}
let latitude;
let longitude;
if (rethash.posambiguity == 0) {
if (lon_min.match(/ /)) {
return this.addError(rethash, 'loc_amb_inv', 'longitude 0');
}
latitude = parseFloat(lat_deg) + (parseFloat(lat_min) / 60);
longitude = parseFloat(lon_deg) + (parseFloat(lon_min) / 60);
}
else if (rethash.posambiguity == 4) {
latitude = parseFloat(lat_deg) + 0.5;
longitude = parseFloat(lon_deg) + 0.5;
}
else if (rethash.posambiguity == 1) {
lat_min = lat_min.substring(0, 4);
lon_min = lon_min.substring(0, 4);
if (lat_min.match(/ /i) || lon_min.match(/ /i)) {
return this.addError(rethash, 'loc_amb_inv', 'lat/lon 1');
}
latitude = parseFloat(lat_deg) + ((parseFloat(lat_min) + 0.05) / 60);
longitude = parseFloat(lon_deg) + ((parseFloat(lon_min) + 0.05) / 60);
}
else if (rethash.posambiguity == 2) {
lat_min = lat_min.substring(0, 2);
lon_min = lon_min.substring(0, 2);
if (lat_min.match(/ /i) || lon_min.match(/ /i)) {
return this.addError(rethash, 'loc_amb_inv', 'lat/lon 2');
}
latitude = parseFloat(lat_deg) + ((parseFloat(lat_min) + 0.5) / 60);
longitude = parseFloat(lon_deg) + ((parseFloat(lon_min) + 0.5) / 60);
}
else if (rethash.posambiguity == 3) {
lat_min = lat_min.charAt(0) + '5';
lon_min = lon_min.charAt(0) + '5';
if (lat_min.match(/ /i) || lon_min.match(/ /i)) {
return this.addError(rethash, 'loc_amb_inv', 'lat/lon 3');
}
latitude = parseFloat(lat_deg) + (parseFloat(lat_min) / 60);
longitude = parseFloat(lon_deg) + (parseFloat(lon_min) / 60);
}
else {
return this.addError(rethash, 'loc_amb_inv');
}
if (issouth == 1) {
latitude = 0 - latitude;
}
if (iswest == 1) {
longitude = 0 - longitude;
}
rethash.latitude = latitude;
rethash.longitude = longitude;
rethash.posresolution = this.get_posresolution(2 - rethash.posambiguity);
return rethash;
}
miceToDecimal(packet, dstcallsign, srccallsign, rethash, options) {
let tmp;
rethash.format = 'mice';
dstcallsign = dstcallsign.replace(/-\d+$/, '');
if (packet.length < 8 || dstcallsign.length != 6) {
return this.addError(rethash, 'mice_short');
}
if (!(/^[0-9A-LP-Z]{3}[0-9LP-Z]{3}$/i.test(dstcallsign))) {
return this.addError(rethash, 'mice_inv');
}
let mice_fixed = false;
let symboltable = packet.charAt(7);
if (!(tmp = packet.match(/^[\x26-\x7f][\x26-\x61][\x1c-\x7f]{2}[\x1c-\x7d][\x1c-\x7f][\x21-\x7b\x7d][\/\\A-Z0-9]/))) {
if (options && options['accept_broken_mice']
&& (packet = packet.replace(/^([\x26-\x7f][\x26-\x61][\x1c-\x7f]{2})\x20([\x21-\x7b\x7d][\/\\A-Z0-9])(.*)/, '$1\x20\x20$2$3'))) {
mice_fixed = true;
symboltable = packet.charAt(7);
if (!/^[\/\\A-Z0-9]$/.test(symboltable)) {
return this.addError(rethash, 'sym_inv_table');
}
}
else {
if (!(/^[\/\\A-Z0-9]$/.test(symboltable))) {
return this.addError(rethash, 'sym_inv_table');
}
else {
return this.addError(rethash, 'mice_inv_info');
}
}
}
let tmplat = dstcallsign.toUpperCase();
tmplat = tmplat.split('');
tmp = '';
tmplat.forEach(function (c) {
if (/[A-J]/.test(c)) {
tmp += (c.charCodeAt(0) - 65);
}
else if (/[P-Y]/.test(c)) {
tmp += (c.charCodeAt(0) - 80);
}
else if (/[KLZ]/.test(c)) {
tmp += '_';
}
else {
tmp += c;
}
});
tmplat = tmp;
if ((tmp = tmplat.match(/^(\d+)(_*)$/i))) {
let amount = 6 - tmp[1].length;
if (amount > 4) {
return this.addError(rethash, 'mice_amb_large');
}
rethash.posambiguity = amount;
rethash.posresolution = this.get_posresolution(2 - amount);
}
else {
return this.addError(rethash, 'mice_amb_inv');
}
if (rethash.posambiguity >= 4) {
tmplat = tmplat.replace('_', '3');
}
else {
tmplat = tmplat.replace('_', '5');
}
tmplat = tmplat.replace(/_/g, '0');
let latitude = tmplat.substring(0, 2);
let latminutes = tmplat.substring(2, 4) + '.' + tmplat.substring(4, 6);
latitude = parseFloat(latitude) + (parseFloat(latminutes) / 60);
let nschar = dstcallsign.charCodeAt(3);
if (nschar <= 0x4c) {
latitude = (0 - parseFloat(latitude));
}
rethash.latitude = latitude;
let mbitstring = dstcallsign.substring(0, 3);
mbitstring = mbitstring.replace(/[0-9L]/g, '0');
mbitstring = mbitstring.replace(/[P-Z]/g, '1');
mbitstring = mbitstring.replace(/[A-K]/g, '2');
rethash.mbits = mbitstring;
let longitude = packet.charCodeAt(0) - 28;
let longoffsetchar = dstcallsign.charCodeAt(4);
if (longoffsetchar >= 80) {
longitude = longitude + 100;
}
if (longitude >= 180 && longitude <= 189) {
longitude = longitude - 80;
}
else if (longitude >= 190 && longitude <= 199) {
longitude = longitude - 190;
}
let longminutes = packet.charCodeAt(1) - 28;
if (longminutes >= 60) {
longminutes -= 60;
}
longminutes = longminutes + '.' + (packet.charCodeAt(2) - 28).toString().padStart(2, '0');
if (rethash.posambiguity == 4) {
longitude += 0.5;
}
else if (rethash.posambiguity == 3) {
let $lontmp = longminutes.charAt(0) + '5';
longitude = longitude + (parseFloat($lontmp) / 60);
}
else if (rethash.posambiguity == 2) {
let $lontmp = longminutes.substring(0, 2) + '.5';
longitude = longitude + (parseFloat($lontmp) / 60);
}
else if (rethash.posambiguity == 1) {
let $lontmp = longminutes.substring(0, 4) + '5';
longitude = (longitude + (parseFloat($lontmp) / 60));
}
else if (rethash.posambiguity == 0) {
longitude = longitude + (parseFloat(longminutes) / 60);
}
else {
return this.addError(rethash, 'mice_amb_odd', rethash.posambiguity.toString());
}
if (dstcallsign.charCodeAt(5) >= 80) {
longitude = longitude * -1;
}
rethash.longitude = longitude;
if (mice_fixed == false) {
let speed = ((packet.charCodeAt(3)) - 28) * 10;
let coursespeed = (packet.charCodeAt(4)) - 28;
let coursespeedtmp = Math.floor(coursespeed / 10);
speed += coursespeedtmp;
coursespeed -= coursespeedtmp * 10;
let course = (100 * coursespeed) + (packet.charCodeAt(5) - 28);
if (course >= 400) {
course -= 400;
}
if (course >= 0) {
rethash.course = course;
}
if (speed >= 800) {
speed -= 800;
}
rethash.speed = speed * ConversionConstantEnum_1.ConversionConstantEnum.KNOT_TO_KMH;
}
rethash.symbolcode = packet.charAt(6);
rethash.symboltable = symboltable;
if (packet.length > 8) {
let rest = packet.substring(8);
if ((tmp = rest.match(/^'([0-9a-f]{2})([0-9a-f]{2})(.*)$/i))) {
rest = tmp[3];
rethash.telemetry = new telemetry_1.default(null, [parseInt(tmp[1], 16), 0, parseInt(tmp[2], 16)]);
}
if ((tmp = rest.match(/^‘([0-9a-f]{10})(.*)$/i))) {
rest = tmp[2];
tmp[1] = tmp[1].match(/.{2}/g);
tmp[1].forEach(function (item, index) { tmp[1][index] = parseInt(tmp[1][index], 16); });
rethash.telemetry = new telemetry_1.default(null, tmp[1]);
}
if ((tmp = rest.match(/^(.*?)([\x21-\x7b])([\x21-\x7b])([\x21-\x7b])\}(.*)$/))) {
rethash.altitude = (((tmp[2].charCodeAt(0) - 33) * Math.pow(91, 2))
+ ((tmp[3].charCodeAt(0) - 33) * 91)
+ (tmp[4].charCodeAt(0) - 33))
- 10000;
rest = tmp[1] + tmp[5];
}
[rest, rethash] = this._comment_telemetry(rethash, rest);
if ((tmp = rest.match(/^(.*)\!([\x21-\x7b][\x20-\x7b]{2})\!(.*?)$/))) {
let daofound = false;
[rethash, daofound] = this._dao_parse(tmp[2], srccallsign, rethash);
if (daofound === true) {
rest = tmp[1] + tmp[3];
}
}
if (rest.length > 0) {
rethash.comment = rest.trim();
}
}
if (mice_fixed == true) {
rethash.mice_mangled = true;
}
return rethash;
}
_compressed_to_decimal(packet, srccallsign, rethash) {
if (!(/^[\/\\A-Za-j]{1}[\x21-\x7b]{8}[\x21-\x7b\x7d]{1}[\x20-\x7b]{3}/.test(packet))) {
return this.addError(rethash, 'comp_inv');
}
rethash.format = 'compressed';
let lat1 = packet.charCodeAt(1) - 33;
let lat2 = packet.charCodeAt(2) - 33;
let lat3 = packet.charCodeAt(3) - 33;
let lat4 = packet.charCodeAt(4) - 33;
let long1 = packet.charCodeAt(5) - 33;
let long2 = packet.charCodeAt(6) - 33;
let long3 = packet.charCodeAt(7) - 33;
let long4 = packet.charCodeAt(8) - 33;
let symbolcode = packet.charAt(9);
let c1 = packet.charCodeAt(10) - 33;
let s1 = packet.charCodeAt(11) - 33;
let comptype = packet.charCodeAt(12) - 33;
rethash.symbolcode = symbolcode;
if (/a-j/.test(packet.charAt(0))) {
rethash.symboltable = (packet.charCodeAt(0) - 97).toString();
}
else {
rethash.symboltable = packet.charAt(0);
}
rethash.latitude = 90 - ((lat1 * Math.pow(91, 3)
+ lat2 * Math.pow(91, 2)
+ lat3 * 91
+ lat4) / 380926);
rethash.longitude = -180 + ((long1 * Math.pow(91, 3)
+ long2 * Math.pow(91, 2)
+ long3 * 91
+ long4) / 190463);
rethash.posresolution = 0.291;
if (c1 != -1) {
if ((comptype & 0x20) == 0x20) {
rethash.gpsfixstatus = true;
}
else {
rethash.gpsfixstatus = false;
}
}
if (c1 == -1 || s1 == -1) {
}
else if ((comptype & 0x18) == 0x10) {
let cs = c1 * 91 + s1;
rethash.altitude = Math.pow(1.002, cs) * ConversionConstantEnum_1.ConversionConstantEnum.FEET_TO_METERS;
}
else if (c1 >= 0 && c1 <= 89) {
if (c1 == 0) {
rethash.course = 360;
}
else {
rethash.course = c1 * 4;
}
rethash.speed = (Math.pow(1.08, s1) - 1) * ConversionConstantEnum_1.ConversionConstantEnum.KNOT_TO_KMH;
}
else if (c1 == 90) {
rethash.radiorange = (2 * Math.pow(1.08, s1)) * ConversionConstantEnum_1.ConversionConstantEnum.MPH_TO_KMH;
}
return rethash;
}
_dao_parse(daocandidate, srccallsign, rethash) {
let latoff;
let lonoff;
let tmp;
if ((tmp = daocandidate.match(/^([A-Z])(\d)(\d)$/))) {
rethash.posresolution = this.get_posresolution(3);
rethash.daodatumbyte = tmp[1];
latoff = parseInt(tmp[2]) * 0.001 / 60;
lonoff = parseInt(tmp[3]) * 0.001 / 60;
}
else if ((tmp = daocandidate.match(/^([a-z])([\x21-\x7b])([\x21-\x7b])$/))) {
rethash.daodatumbyte = tmp[1].toUpperCase();
rethash.posresolution = this.get_posresolution(4);
latoff = (tmp[2].charCodeAt(0) - 33) / 91 * 0.01 / 60;
lonoff = (tmp[3].charCodeAt(0) - 33) / 91 * 0.01 / 60;
}
else if ((tmp = daocandidate.match(/^([\x21-\x7b])\s\s$/))) {
rethash.daodatumbyte = tmp[1].toUpperCase();
return [rethash, true];
}
else {
return [rethash, false];
}
if (rethash.latitude < 0) {
rethash.latitude -= latoff;
}
else {
rethash.latitude += latoff;
}
if (rethash.longitude < 0) {
rethash.longitude -= lonoff;
}
else {
rethash.longitude += lonoff;
}
return [rethash, true];
}
_wx_parse(s, rethash) {
let w = new wx_1.default();
let wind_dir;
let wind_speed;
let temp;
let wind_gust;
let tmp;
if ((tmp = s.match(/^_{0,1}([\d \.\-]{3})\/([\d \.]{3})g([\d \.]+)t(-{0,1}[\d \.]+)/))
|| (tmp = s.match(/^_{0,1}c([\d \.\-]{3})s([\d \.]{3})g([\d \.]+)t(-{0,1}[\d \.]+)/))) {
wind_dir = tmp[1];
wind_speed = tmp[2];
wind_gust = tmp[3];
if (tmp[0]) {
s = s.replace(tmp[0], '');
}
temp = tmp[4];
}
else if ((tmp = s.match(/^_{0,1}([\d \.\-]{3})\/([\d \.]{3})t(-{0,1}[\d \.]+)/))) {
wind_dir = tmp[1];
wind_speed = tmp[2];
if (tmp[0]) {
s = s.replace(tmp[0], '');
}
temp = tmp[3];
}
else if ((tmp = s.match(/^_{0,1}([\d \.\-]{3})\/([\d \.]{3})g([\d \.]+)/))) {
wind_dir = tmp[1];
wind_speed = tmp[2];
wind_gust = tmp[3];
if (tmp[0]) {
s = s.replace(tmp[0], '');
}
}
else if ((tmp = s.match(/^g(\d+)t(-{0,1}[\d \.]+)/))) {
wind_gust = tmp[1];
if (tmp[0]) {
s = s.replace(tmp[0], '');
}
temp = tmp[2];
}
else {
return rethash;
}
if (!temp) {
s = s.replace(/t(-{0,1}\d{1,3})/, function (a, b) {
if (b) {
temp = b;
}
return '';
});
}
if (/^\d+$/.test(wind_gust)) {
w.wind_gust = (parseFloat(wind_gust) * ConversionConstantEnum_1.ConversionConstantEnum.MPH_TO_MS).toFixed(1);
}
if (/^\d+$/.test(wind_dir)) {
w.wind_direction = parseFloat(wind_dir).toFixed(0);
}
if (/^\d+$/.test(wind_speed)) {
w.wind_speed = (parseFloat(wind_speed) * ConversionConstantEnum_1.ConversionConstantEnum.MPH_TO_MS).toFixed(1);
}
if (/^-{0,1}\d+$/.test(temp)) {
w.temp = ConversionUtil_1.ConversionUtil.FahrenheitToCelsius(parseInt(temp)).toFixed(1);
}
s = s.replace(/r(\d{1,3})/, function ($a, b) {
if (b) {
w.rain_1h = (parseFloat(b) * ConversionConstantEnum_1.ConversionConstantEnum.HINCH_TO_MM).toFixed(1);
}
return '';
});
s = s.replace(/p(\d{1,3})/, function (a, b) {
if (b) {