UNPKG

js-aprs-fap

Version:

NodeJs library for parsing APRS packets.

1,212 lines (1,211 loc) 58.5 kB
"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) {