@coremarine/nmea-parser
Version:
Library to parse NMEA 0183 sentences
1,117 lines (1,027 loc) • 42.1 kB
JavaScript
;Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;// src/checksum.ts
var calculateChecksum = (data) => Array.from(data).reduce((acc, cur) => acc ^ cur.charCodeAt(0), 0);
var stringChecksumToNumber = (checksum) => Number.parseInt(checksum, 16);
var numberChecksumToString = (checksum) => checksum.toString(16).padStart(2, "0").toUpperCase();
var getChecksum = (cs) => ({ sample: cs, value: stringChecksumToNumber(cs) });
// src/constants.ts
var FIELD_TYPES = [
// Unsigned Integers
"uint8",
// 'char',
"uint16",
// 'unsigned short',
"uint32",
// 'unsigned int',
"uint64",
// 'unsigned long',
// Integers
"int8",
// 'signed char',
"int16",
// 'short',
"int32",
// 'int',
"int64",
// 'long',
// Floats
"float32",
// 'float',
"float64",
// 'double',
// Strings
"string",
// Boolean
"boolean"
// 'bool'
];
var START_FLAG = "$";
var SEPARATOR = ",";
var DELIMITER = "*";
var END_FLAG = "\r\n";
var START_FLAG_LENGTH = START_FLAG.length;
var SEPARATOR_LENGTH = SEPARATOR.length;
var DELIMITER_LENGTH = DELIMITER.length;
var CHECKSUM_LENGTH = 2;
var END_FLAG_LENGTH = END_FLAG.length;
var MINIMAL_LENGTH = START_FLAG_LENGTH + DELIMITER_LENGTH + CHECKSUM_LENGTH + END_FLAG_LENGTH;
var NMEA_ID_LENGTH = 3;
var NMEA_TALKER_LENGTH = 2;
var NMEA_SENTENCE_LENGTH = NMEA_ID_LENGTH + NMEA_TALKER_LENGTH;
var TALKERS = [
["AB", "Independent AIS Base Station"],
["AD", "Dependent AIS Base Station"],
["AG", "Autopilot - General"],
["AI", "Mobile AIS Station"],
["AN", "AIS Aid to Navigation"],
["AP", "Autopilot - Magnetic"],
["AR", "AIS Receiving Station"],
["AT", "AIS Transmitting Station"],
["AX", "AIS Simplex Repeater"],
["BD", "BeiDou (China)"],
["BI", "Bilge System"],
["BN", "Bridge navigational watch alarm system"],
["CA", "Central Alarm"],
["CC", "Computer - Programmed Calculator (obsolete)"],
["CD", "Communications - Digital Selective Calling (DSC)"],
["CM", "Computer - Memory Data (obsolete)"],
["CR", "Data Receiver"],
["CS", "Communications - Satellite"],
["CT", "Communications - Radio-Telephone (MF/HF)"],
["CV", "Communications - Radio-Telephone (VHF)"],
["CX", "Communications - Scanning Receiver"],
["DE", "DECCA Navigation (obsolete)"],
["DF", "Direction Finder"],
["DM", "Velocity Sensor, Speed Log, Water, Magnetic"],
["DP", "Dynamiv Position"],
["DU", "Duplex repeater station"],
["EC", "Electronic Chart Display & Information System (ECDIS)"],
["EP", "Emergency Position Indicating Beacon (EPIRB)"],
["ER", "Engine Room Monitoring Systems"],
["FD", "Fire Door"],
["FS", "Fire Sprinkler"],
["GA", "Galileo Positioning System"],
["GB", "BeiDou (China)"],
["GI", "NavIC, IRNSS (India)"],
["GL", "GLONASS, according to IEIC 61162-1"],
["GN", "Combination of multiple satellite systems (NMEA 1083)"],
["GP", "Global Positioning System receiver"],
["GQ", "QZSS regional GPS augmentation system (Japan)"],
["HC", "Heading - Magnetic Compass"],
["HD", "Hull Door"],
["HE", "Heading - North Seeking Gyro"],
["HF", "Heading - Fluxgate"],
["HN", "Heading - Non North Seeking Gyro"],
["HS", "Hull Stress"],
["II", "Integrated Instrumentation"],
["IN", "Integrated Navigation"],
["JA", "Alarm and Monitoring"],
["JB", "Water Monitoring"],
["JC", "Power Management"],
["JD", "Propulsion Control"],
["JE", "Engine Control"],
["JF", "Propulsion Boiler"],
["JG", "Aux Boiler"],
["JH", "Engine Governor"],
["LA", "Loran A (obsolete)"],
["LC", "Loran C (obsolete)"],
["MP", "Microwave Positioning System (obsolete)"],
["MX", "Multiplexer"],
["NL", "Navigation light controller"],
["OM", "OMEGA Navigation System (obsolete)"],
["OS", "Distress Alarm System (obsolete)"],
["QZ", "QZSS regional GPS augmentation system (Japan)"],
["RA", "RADAR and/or ARPA"],
["RB", "Record Book"],
["RC", "Propulsion Machinery"],
["RI", "Rudder Angle Indicator"],
["SA", "Physical Shore AUS Station"],
["SD", "Depth Sounder"],
["SG", "Steering Gear"],
["SN", "Electronic Positioning System, other/general"],
["SS", "Scanning Sounder"],
["ST", "Skytraq debug output"],
["TC", "Track Control"],
["TI", "Turn Rate Indicator"],
["TR", "TRANSIT Navigation System"],
["UP", "Microprocessor controller"],
["VA", "VHF Data Exchange System (VDES), ASM"],
["VD", "Velocity Sensor, Doppler, other/general"],
["VM", "Velocity Sensor, Speed Log, Water, Magnetic"],
["VR", "Voyage Data recorder"],
["VS", "VHF Data Exchange System (VDES), Satellite"],
["VT", "VHF Data Exchange System (VDES), Terrestrial"],
["VW", "Velocity Sensor, Speed Log, Water, Mechanical"],
["WD", "Watertight Door"],
["WI", "Weather Instruments"],
["WL", "Water Level"],
["YC", "Transducer - Temperature (obsolete)"],
["YD", "Transducer - Displacement, Angular or Linear (obsolete)"],
["YF", "Transducer - Frequency (obsolete)"],
["YL", "Transducer - Level (obsolete)"],
["YP", "Transducer - Pressure (obsolete)"],
["YR", "Transducer - Flow Rate (obsolete)"],
["YT", "Transducer - Tachometer (obsolete)"],
["YV", "Transducer - Volume (obsolete)"],
["YX", "Transducer"],
["ZA", "Timekeeper - Atomic Clock"],
["ZC", "Timekeeper - Chronometer"],
["ZQ", "Timekeeper - Quartz"],
["ZV", "Timekeeper - Radio Update, WWV or WWVH"]
];
var TALKERS_SPECIAL = {
P: "Vendor specific",
U: "U# where '#' is a digit 0 \u2026\u200B 9; User Configured"
};
var CODE = {
A: "A".charCodeAt(0),
Z: "Z".charCodeAt(0),
a: "a".charCodeAt(0),
z: "z".charCodeAt(0),
0: "0".charCodeAt(0),
9: "9".charCodeAt(0)
};
var UINT8_MAX = Uint8Array.from([255])[0];
var UINT16_MAX = Uint16Array.from([65535])[0];
var UINT32_MAX = Uint32Array.from([4294967295])[0];
var [INT8_MIN, INT8_MAX] = Int8Array.from([128, 127]);
var [INT16_MIN, INT16_MAX] = Int16Array.from([32768, 32767]);
var [INT32_MIN, INT32_MAX] = Int32Array.from([2147483648, 2147483647]);
var MAX_FLOAT = 999999999999999;
var MIN_FLOAT = -999999999999999;
var MAX_CHARACTERS = 1024;
var MAX_NMEA_CHARACTERS = 82;
var UNKNOWN_NMEA_SENTENCE_SCAFOLDING = {
id: "unknown",
protocol: {
name: "unknown",
standard: false
},
description: "unknown nmea sentence"
};
// src/nmea-sentences.ts
var NMEA_SENTENCES = {
protocols: [
{
protocol: "NMEA",
version: "3.1",
standard: true,
sentences: [
{
id: "AAM",
description: "Waypoint Arrival Alarm",
payload: [
{
name: "status",
type: "string",
description: "BOOLEAN\n\nA = arrival circle entered\n\nV = arrival circle not passed"
},
{
name: "status",
type: "string",
description: "BOOLEAN\n\nA = perpendicular passed at waypoint\n\nV = perpendicular not passed"
},
{
name: "arrival_circle_radius",
type: "float32"
},
{
name: "radius_units",
type: "string",
units: "nautic miles"
},
{
name: "waypoint_id",
type: "string"
}
]
},
{
id: "GGA",
description: "Global Positioning System Fix Data",
payload: [
{
name: "utc_position",
type: "string",
units: "ms"
},
{
name: "latitude",
type: "string",
units: "deg-min"
},
{
name: "latitude_direction",
type: "string",
description: "N: North\n S: South"
},
{
name: "longitude",
type: "string",
units: "deg-min"
},
{
name: "longitude_direction",
type: "string",
description: "E - East\n W - West"
},
{
name: "quality",
type: "int8",
description: "0: Fix not valid\n 1: GPS fix\n 2: Differential GPS fix (DGNSS), SBAS, OmniSTAR VBS, Beacon, RTX in GVBS mode\n 3: Not applicable\n 4: RTK Fixed, xFill\n 5: RTK Float, OmniSTAR XP/HP, Location RTK, RTX\n 6: INS Dead reckoning\n 7: Manual Input Mode\n 8: Simulator Mode"
},
{
name: "satellites",
type: "uint8"
},
{
name: "hdop",
type: "float64"
},
{
name: "altitude",
type: "float64",
units: "m",
description: "Orthometric height Mean-Sea-Level (MSL reference)"
},
{
name: "altitude_units",
type: "string",
units: "m"
},
{
name: "geoid_separation",
type: "float64",
units: "m",
description: 'Geoidal Separation: the difference between the WGS-84 earth ellipsoid surface and mean-sea-level (geoid) surface, "-" = mean-sea-level surface below WGS-84 ellipsoid surface.'
},
{
name: "geoid_separation_units",
type: "string",
units: "m"
},
{
name: "age_of_differential_gps_data",
type: "uint32",
units: "sec",
description: "Time in seconds since last SC104 Type 1 or 9 update, null field when DGPS is not used300"
},
{
name: "reference_station_id",
type: "uint16",
description: "Reference station ID, range 0000 to 4095. A null field when any reference station ID is selected and no corrections are received. See table below for a description of the field values.\n\n0002 CenterPoint or ViewPoint RTX\n\n0005 RangePoint RTX\n\n0006 FieldPoint RTX\n\n0100 VBS\n\n1000 HP\n\n1001 HP/XP (Orbits)\n\n1002 HP/G2 (Orbits)\n\n1008 XP (GPS)\n\n1012 G2 (GPS)\n\n1013 G2 (GPS/GLONASS)\n\n1014 G2 (GLONASS)\n\n1016 HP/XP (GPS)\n\n1020 HP/G2 (GPS)\n\n1021 HP/G2 (GPS/GLONASS)"
}
]
},
{
id: "HDT",
description: "Heading - True",
payload: [
{
name: "heading",
type: "float32",
description: "Heading, degrees True"
},
{
name: "true",
type: "string",
description: "T = True"
}
]
},
{
id: "ZDA",
description: "Time & Date - UTC, day, month, year and local time zone",
payload: [
{
name: "utc_time",
type: "string",
description: "UTC time (hours, minutes, seconds, may have fractional subseconds)"
},
{
name: "day",
type: "int8",
description: "Day, 01 to 31"
},
{
name: "month",
type: "int8",
description: "Month, 01 to 12"
},
{
name: "year",
type: "int16",
description: "Year (4 digits)"
},
{
name: "local_zone_hours",
type: "int8",
description: "Local zone description, 00 to +- 13 hours"
},
{
name: "local_zone_minutes",
type: "int8",
description: "Local zone minutes description, 00 to 59, apply same sign as local hours"
}
]
}
]
}
]
};
// src/protocols.ts
var _jsyaml = require('js-yaml'); var _jsyaml2 = _interopRequireDefault(_jsyaml);
var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs);
// src/schemas.ts
var _valibotnumbers = require('@schemasjs/valibot-numbers');
var _validator = require('@schemasjs/validator');
var _valibot = require('valibot'); var v = _interopRequireWildcard(_valibot);
var ValibotStringSchema = v.string();
var StringSchema = _validator.ValibotValidator.call(void 0, ValibotStringSchema);
var ValibotStringArraySchema = v.array(ValibotStringSchema);
var StringArraySchema = _validator.ValibotValidator.call(void 0, ValibotStringArraySchema);
var ValibotBooleanSchema = v.boolean();
var BooleanSchema = _validator.ValibotValidator.call(void 0, ValibotBooleanSchema);
var ValibotNumberSchema = v.number();
var NumberSchema = _validator.ValibotValidator.call(void 0, ValibotNumberSchema);
var IntegerSchema = _validator.ValibotValidator.call(void 0, _valibotnumbers.IntegerSchema);
var Int8Schema = _validator.ValibotValidator.call(void 0, _valibotnumbers.Int8Schema);
var Int16Schema = _validator.ValibotValidator.call(void 0, _valibotnumbers.Int16Schema);
var Int32Schema = _validator.ValibotValidator.call(void 0, _valibotnumbers.Int32Schema);
var ValibotInt64Schema = v.bigint();
var Int64Schema = _validator.ValibotValidator.call(void 0, ValibotInt64Schema);
var UnsignedIntegerSchema = _validator.ValibotValidator.call(void 0, _valibotnumbers.UnsignedIntegerSchema);
var Uint8Schema = _validator.ValibotValidator.call(void 0, _valibotnumbers.Uint8Schema);
var Uint16Schema = _validator.ValibotValidator.call(void 0, _valibotnumbers.Uint16Schema);
var Uint32Schema = _validator.ValibotValidator.call(void 0, _valibotnumbers.Uint32Schema);
var ValibotUint64Schema = v.pipe(v.bigint(), v.minValue(0n));
var Uint64Schema = _validator.ValibotValidator.call(void 0, ValibotUint64Schema);
var Float32Schema = _validator.ValibotValidator.call(void 0, _valibotnumbers.Float32Schema);
var Float64Schema = _validator.ValibotValidator.call(void 0, _valibotnumbers.Float64Schema);
var ValibotProtocolFieldTypeSchema = v.picklist(FIELD_TYPES, "invalid type");
var ProtocolFieldTypeSchema = _validator.ValibotValidator.call(void 0, ValibotProtocolFieldTypeSchema);
var ValibotProtocolFieldSchema = v.object({
name: ValibotStringSchema,
type: ValibotProtocolFieldTypeSchema,
units: v.optional(ValibotStringSchema),
description: v.optional(ValibotStringSchema)
});
var ProtocolFieldSchema = _validator.ValibotValidator.call(void 0, ValibotProtocolFieldSchema);
var ValibotProtocolSentencePayloadSchema = v.array(ValibotProtocolFieldSchema, "invalid payload");
var ProtocolSentencePayloadSchema = _validator.ValibotValidator.call(void 0, ValibotProtocolSentencePayloadSchema);
var ValibotProtocolSentenceSchema = v.object({
id: ValibotStringSchema,
payload: ValibotProtocolSentencePayloadSchema,
description: v.optional(ValibotStringSchema)
});
var ProtocolSentenceSchema = _validator.ValibotValidator.call(void 0, ValibotProtocolSentenceSchema);
var ValibotMaxThreeFields = v.check((input) => input.split(".").length < 4, "VersionSchema: more than 3 fields");
var ValibotValidMajor = v.check((val) => {
const major = Number(val.split(".")[0]);
return !Number.isNaN(major) && major > 0;
}, "VersionSchema: Invalid major");
var ValibotValidMinor = v.check((val) => {
const fields = val.split(".");
if (fields.length < 2) return true;
const minor = Number(fields[1]);
return !Number.isNaN(minor) && minor > 0;
}, "VersionSchema: Invalid major");
var ValibotValidPatch = v.check((val) => {
const fields = val.split(".");
if (fields.length < 3) return true;
const patch = Number.parseInt(fields[2]);
return !Number.isNaN(patch) && patch > 0;
}, "VersionSchema: Invalid patch");
var ValibotVersionSchema = v.pipe(
v.custom((val) => v.is(ValibotStringSchema, val)),
ValibotMaxThreeFields,
ValibotValidMajor,
ValibotValidMinor,
ValibotValidPatch
);
var VersionSchema = _validator.ValibotValidator.call(void 0, ValibotVersionSchema);
var ValibotProtocolSchema = v.object({
protocol: ValibotStringSchema,
version: v.optional(ValibotStringSchema),
standard: v.optional(ValibotBooleanSchema, false),
sentences: v.array(ValibotProtocolSentenceSchema)
});
var ProtocolSchema = _validator.ValibotValidator.call(void 0, ValibotProtocolSchema);
var ValibotProtocolsFileContentSchema = v.object({ protocols: v.array(ValibotProtocolSchema) });
var ProtocolsFileContentSchema = _validator.ValibotValidator.call(void 0, ValibotProtocolsFileContentSchema);
var ValibotProtocolsInputSchema = v.object({
file: v.optional(ValibotStringSchema),
content: v.optional(ValibotStringSchema),
protocols: v.optional(v.array(ValibotProtocolSchema))
});
var ProtocolsInputSchema = _validator.ValibotValidator.call(void 0, ValibotProtocolsInputSchema);
var ValibotStoredSentenceSchema = v.object({
id: ValibotStringSchema,
protocol: v.object({
name: ValibotStringSchema,
standard: v.optional(ValibotBooleanSchema, false),
version: v.optional(ValibotStringSchema)
}),
payload: v.array(ValibotProtocolFieldSchema),
description: v.optional(ValibotStringSchema)
});
var StoredSentenceSchema = _validator.ValibotValidator.call(void 0, ValibotStoredSentenceSchema);
var ValibotMapStoredSentencesSchema = v.map(ValibotStringSchema, ValibotStoredSentenceSchema);
var MapStoredSentencesSchema = _validator.ValibotValidator.call(void 0, ValibotMapStoredSentencesSchema);
var ValibotJSONSchemaInputSchema = v.object({
path: v.optional(ValibotStringSchema),
filename: v.optional(ValibotStringSchema, "nmea_protocols_schema.json")
});
var JSONSchemaInputSchema = _validator.ValibotValidator.call(void 0, ValibotJSONSchemaInputSchema);
var ValibotChecksumSchema = v.object({
sample: ValibotStringSchema,
value: _valibotnumbers.Uint8Schema
});
var ChecksumSchema = _validator.ValibotValidator.call(void 0, ValibotChecksumSchema);
var ValibotValueSchema = v.union([ValibotStringSchema, ValibotBooleanSchema, ValibotNumberSchema, v.bigint(), v.null()], "invalid value");
var ValueSchema = _validator.ValibotValidator.call(void 0, ValibotValueSchema);
var ValibotTalkerSchema = v.object({
value: ValibotStringSchema,
description: ValibotStringSchema
});
var TalkerSchema = _validator.ValibotValidator.call(void 0, ValibotTalkerSchema);
var ValibotNMEALikeSchema = v.custom((input) => {
if (typeof input !== "string") {
return false;
}
if (!input.startsWith(START_FLAG)) {
return false;
}
if (!input.endsWith(END_FLAG)) {
return false;
}
const parts = input.split(DELIMITER);
if (parts.length !== 2) {
return false;
}
const [info, cs] = parts;
if (cs.length !== CHECKSUM_LENGTH + END_FLAG.length) {
return false;
}
const checksum = cs.slice(0, CHECKSUM_LENGTH);
const numChecksum = stringChecksumToNumber(checksum);
if (!v.safeParse(_valibotnumbers.Uint8Schema, numChecksum).success) {
return false;
}
const data = info.slice(START_FLAG.length);
if (data.length < NMEA_SENTENCE_LENGTH) {
return false;
}
return info.includes(SEPARATOR);
});
var NMEALikeSchema = _validator.ValibotValidator.call(void 0, ValibotNMEALikeSchema);
var ValibotNMEAParsedFieldSchema = v.object({
name: v.optional(v.string("payload name bad"), "unknown"),
sample: v.string("payload sample bad"),
value: ValibotValueSchema,
type: v.optional(v.union([v.picklist(FIELD_TYPES), v.literal("unknown")], "payload type bad"), "unknown"),
units: v.optional(v.string("payload units bad"), "unknown"),
description: v.optional(v.string("payload description bad")),
metadata: v.optional(v.any())
});
var NMEAParsedFieldchema = _validator.ValibotValidator.call(void 0, ValibotNMEAParsedFieldSchema);
var ValibotNMEAParsedPayloadSchema = v.array(ValibotNMEAParsedFieldSchema);
var NMEAParsedPayloadSchema = _validator.ValibotValidator.call(void 0, ValibotNMEAParsedPayloadSchema);
var ValibotNMEASentenceSchema = v.object({
received: _valibotnumbers.UnsignedIntegerSchema,
sample: ValibotNMEALikeSchema,
id: ValibotStringSchema,
description: v.optional(ValibotStringSchema),
checksum: ValibotChecksumSchema,
payload: v.array(ValibotNMEAParsedFieldSchema),
metadata: v.optional(v.any()),
protocol: v.optional(
v.object({
name: ValibotStringSchema,
standard: ValibotBooleanSchema,
version: v.optional(ValibotStringSchema)
}),
{ name: "unknown", standard: false }
),
talker: v.optional(ValibotTalkerSchema)
});
var NMEASentenceSchema = _validator.ValibotValidator.call(void 0, ValibotNMEASentenceSchema);
// src/protocols.ts
var readProtocolsYAMLString = (content) => {
const fileData = _jsyaml2.default.load(content);
const parsed = ProtocolsFileContentSchema.safeParse(fileData);
if (!parsed.success) {
throw new Error(_optionalChain([parsed, 'access', _ => _.errors, 'optionalAccess', _2 => _2.toString, 'call', _3 => _3()]));
}
return parsed.data;
};
var readProtocolsYAMLFile = (file) => {
const filename = StringSchema.parse(file);
const content = _fs2.default.readFileSync(filename, "utf-8");
return readProtocolsYAMLString(content);
};
var getStoreSentencesFromProtocol = (protocol) => {
const { protocol: name, standard, version, sentences } = protocol;
const storedSentences = /* @__PURE__ */ new Map();
sentences.forEach((element) => {
const obj = {
id: element.id,
payload: element.payload,
protocol: { name, standard, version },
description: _optionalChain([element, 'optionalAccess', _4 => _4.description])
};
storedSentences.set(element.id, obj);
});
return storedSentences;
};
var getStoreSentences = ({ protocols }) => {
let storedSentences = /* @__PURE__ */ new Map();
protocols.forEach((protocol) => {
storedSentences = new Map([...storedSentences, ...getStoreSentencesFromProtocol(protocol)]);
});
return storedSentences;
};
// src/sentences.ts
var _crypto = require('crypto'); var _crypto2 = _interopRequireDefault(_crypto);
// src/utils.ts
var isBoundedASCII = (char, min, max) => {
const num = char.charCodeAt(0);
return min <= num && num <= max;
};
var isLowerCharASCII = (char) => isBoundedASCII(char, CODE.a, CODE.z);
var isUpperCharASCII = (char) => isBoundedASCII(char, CODE.A, CODE.Z);
var isNumberCharASCII = (char) => isBoundedASCII(char, CODE["0"], CODE["9"]);
// src/nmea-metadata.ts
var metadataGGA = (sentence) => {
const getUTCPosition = (
/**
* Description placeholder
*
* @param {string} utcPosition hhmmss.ss where hh hours, mm minutes and ss.ss seconds
* @returns {Uint32 | null}
*/
(utcPosition) => {
if (utcPosition.length !== 9) {
return null;
}
if (isNaN(Number(utcPosition))) {
return null;
}
const hours = Number(utcPosition.slice(0, 2));
const minutes = Number(utcPosition.slice(2, 4));
const seconds = Number(utcPosition.slice(4, 6));
const millis = Number(utcPosition.slice(7));
const date = /* @__PURE__ */ new Date();
date.setHours(hours, minutes, seconds, millis);
return date.getTime();
}
);
const getLatitudeDegrees = (latitude, letter) => {
const [left, minutesRight] = latitude.split(".");
const degrees = left.slice(0, -2);
const minutesLeft = left.slice(-2);
const sign = letter === "S" ? -1 : 1;
const minutes = `${minutesLeft}.${minutesRight}`;
return sign * (Number(degrees) + Number(minutes) / 60);
};
const getLongitudeDegrees = (longitude, letter) => {
const [left, minutesRight] = longitude.split(".");
const degrees = left.slice(0, -2);
const minutesLeft = left.slice(-2);
const sign = letter === "W" ? -1 : 1;
const minutes = `${minutesLeft}.${minutesRight}`;
return sign * (Number(degrees) + Number(minutes) / 60);
};
const getQuality = (quality) => {
const QUALITIES = {
0: "Fix not valid",
1: "GPS fix",
2: "Differential GPS fix (DGNSS), SBAS, OmniSTAR VBS, Beacon, RTX in GVBS mode",
3: "Not applicable",
4: "RTK Fixed, xFill",
5: "RTK Float, OmniSTAR XP/HP, Location RTK, RTX",
6: "INS Dead reckoning",
7: "Manual Input Mode",
8: "Simulator Mode"
};
return _nullishCoalesce(QUALITIES[quality], () => ( "unknown"));
};
sentence.payload.forEach((field, index) => {
if (field.name === "utc_position") {
const utcPosition = field.value;
const timestamp = getUTCPosition(utcPosition);
if (timestamp !== null) {
sentence.payload[index].metadata = { timestamp };
sentence.metadata = { ...sentence.metadata, timestamp };
}
}
if (field.name === "latitude") {
const latitude = field.value;
const letter = sentence.payload[index + 1].value;
const degrees = getLatitudeDegrees(latitude, letter);
sentence.payload[index].metadata = { degrees };
sentence.metadata = { ...sentence.metadata, latitude: degrees };
return;
}
if (field.name === "longitude") {
const longitude = field.value;
const letter = sentence.payload[index + 1].value;
const degrees = getLongitudeDegrees(longitude, letter);
sentence.payload[index].metadata = { degrees };
sentence.metadata = { ...sentence.metadata, longitude: degrees };
return;
}
if (field.name === "quality") {
sentence.metadata = { ...sentence.metadata, quality: getQuality(field.value) };
}
});
return { ...sentence };
};
var METADATA = {
GGA: metadataGGA
};
var addMetadata = (sentence) => {
if (sentence.id in METADATA) {
return METADATA[sentence.id](sentence);
}
return sentence;
};
// src/sentences.ts
var lastUncompletedFrame = (text) => {
const lastStartIndex = text.lastIndexOf(START_FLAG);
if (lastStartIndex === -1) {
return null;
}
const remainder = text.slice(lastStartIndex);
if (remainder.includes(END_FLAG)) {
return null;
}
return remainder;
};
var getUnparsedNMEAFrames = (text) => {
if ([START_FLAG, SEPARATOR, DELIMITER, END_FLAG].some((flag) => !text.includes(flag))) {
return [];
}
return text.split(END_FLAG).filter((str) => str.length > MINIMAL_LENGTH).filter((str) => str.includes(START_FLAG)).map((str) => str.split(START_FLAG).at(-1)).filter((str) => {
const first = str.indexOf(DELIMITER);
const last = str.lastIndexOf(DELIMITER);
return first !== -1 && first === last;
}).filter((str) => {
const [payload, checksum] = str.split(DELIMITER);
if (checksum.length !== CHECKSUM_LENGTH) {
console.debug(`Invalid sentence: checksum has not two characters -> $${str}`);
return false;
}
if (!/[0-9A-Fa-f]{2}/.test(checksum)) {
console.debug(`Invalid sentence: checksum is not a hexadecimal digit -> $${str}`);
return false;
}
const numChecksum = stringChecksumToNumber(checksum);
const computedChecksum = calculateChecksum(payload);
if (numChecksum !== computedChecksum) {
console.debug(`Invalid sentence: calculated checksum ${numberChecksumToString(computedChecksum)} is not equal to given checksum ${checksum} -> $${str}`);
return false;
}
return true;
}).filter((str) => {
const payload = str.split(DELIMITER).at(0);
if (!payload.includes(SEPARATOR)) {
console.debug(`Invalid sentence: payload has not separator character "${SEPARATOR}" -> $${str}`);
return false;
}
if (["\r", "\n"].some((char) => payload.includes(char))) {
console.debug(`Invalid sentence: payload has invalid characters -> $${str}`);
return false;
}
return true;
}).map((str) => `${START_FLAG}${str}${END_FLAG}`);
};
var getIdPayloadAndChecksum = (frame) => {
const [info, checksum] = frame.slice(START_FLAG.length, -END_FLAG_LENGTH).split(DELIMITER);
const id = info.split(SEPARATOR)[0];
const payload = info.slice(id.length + 1);
return { id, payload, checksum };
};
var hasSameNumberOfFields = (payload, sentence) => payload.split(SEPARATOR).length === sentence.payload.length;
var parseNumber = (value, type) => {
if (type === "int8") return Int8Schema.safeParse(Number(value)).data;
if (type === "int16") return Int16Schema.safeParse(Number(value)).data;
if (type === "int32") return Int32Schema.safeParse(Number(value)).data;
if (type === "int64") return Int64Schema.safeParse(BigInt(value)).data;
if (type === "uint8") return Uint8Schema.safeParse(Number(value)).data;
if (type === "uint16") return Uint16Schema.safeParse(Number(value)).data;
if (type === "uint32") return Uint32Schema.safeParse(Number(value)).data;
if (type === "uint64") return Uint64Schema.safeParse(BigInt(value)).data;
if (type === "float32") return Float32Schema.safeParse(Number(value)).data;
if (type === "float64") return Float64Schema.safeParse(Number(value)).data;
};
var parseBoolean = (value) => {
if (value.toLowerCase() === "false" || value === "0") return false;
if (value.toLowerCase() === "true" || value === "1") return true;
};
var parseValue = (value, type) => {
try {
if (type === "string") {
return value;
}
if (type === "boolean") {
const b = parseBoolean(value);
if (b !== void 0) {
return b;
}
}
const num = parseNumber(value, type);
if (num !== void 0) {
return num;
}
} catch (error) {
console.debug(`Error parsing value: ${value} is not ${type}`);
}
return null;
};
var getKnownNMEASentence = ({ received, sample, sentenceID, sentencePayload, checksum, model }) => {
if (!hasSameNumberOfFields(sentencePayload, model)) return null;
const fields = sentencePayload.split(SEPARATOR);
const payload = model.payload.map(({ name, type, units, description }, index) => {
const sample2 = fields[index];
const value = parseValue(sample2, type);
return { name, sample: sample2, value, type, units: _nullishCoalesce(units, () => ( "unknown")), description };
});
const { protocol } = model;
const nmeaSentence = {
received,
sample,
id: sentenceID,
checksum,
payload,
protocol
};
const nmeaSentenceWithMetadata = addMetadata(nmeaSentence);
return NMEASentenceSchema.parse(nmeaSentenceWithMetadata);
};
var getTalker = (sentenceID) => {
if (sentenceID.length <= NMEA_ID_LENGTH) return null;
const talker = TALKERS.filter(([talkerID, _talkerDescription]) => sentenceID.startsWith(talkerID));
if (talker.length === 1) {
const value = talker[0][0];
return { value, description: talker[0][1] };
}
if (sentenceID.startsWith("U") && !isNaN(Number(sentenceID[1]))) {
const value = sentenceID.slice(0, 2);
return { value, description: TALKERS_SPECIAL.U };
}
if (sentenceID.startsWith("P")) {
return { value: sentenceID, description: TALKERS_SPECIAL.P };
}
return null;
};
var getUnknowNMEASentence = ({ received, sample, sentenceID, sentencePayload, checksum }) => {
const fields = sentencePayload.split(SEPARATOR);
const response = fields.map((field) => ({
name: "unknown",
sample: field,
value: field,
type: "string",
units: "unknown"
}));
const sent = NMEASentenceSchema.parse({
received,
sample,
id: sentenceID,
checksum,
payload: response,
description: "unknown nmea sentence"
});
return sent;
};
var createNumberValue = (type) => {
const sign = Math.random() < 0.5 ? -1 : 1;
const useed = Math.round(Math.random() * (Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER) + Number.MIN_SAFE_INTEGER);
const seed = useed * sign;
const fseed = Math.random() * sign;
const uint64 = new BigUint64Array([0n]);
_crypto2.default.getRandomValues(uint64);
const biguintseed = uint64[0];
const int64 = new BigInt64Array([0n]);
_crypto2.default.getRandomValues(int64);
const bigintseed = int64[0];
switch (type) {
case "uint8":
return new Uint8Array([useed])[0];
case "uint16":
return new Uint16Array([useed])[0];
case "uint32":
return new Uint32Array([useed])[0];
case "uint64":
return biguintseed;
case "int8":
return new Int8Array([seed])[0];
case "int16":
return new Int16Array([seed])[0];
case "int32":
return new Int32Array([seed])[0];
case "int64":
return bigintseed;
case "float32":
return new Float32Array([fseed])[0];
case "float64":
return new Float64Array([fseed])[0];
}
return null;
};
var createStringValue = () => {
const text = Buffer.from(Math.random().toString(36).substring(2)).toString("ascii");
const array2 = Array.from(text).map((letter) => {
if (isLowerCharASCII(letter) || isUpperCharASCII(letter) || isNumberCharASCII(letter)) {
return letter;
}
return "a";
});
return array2.join("");
};
var createValue = (type) => {
switch (type) {
case "boolean":
return Math.random() > 0.5;
case "string":
return createStringValue();
}
return createNumberValue(type);
};
var createPayload = (model) => {
let payload = "";
model.payload.forEach((field) => {
const value = createValue(field.type);
payload += value !== null ? `${value.toString()},` : ",";
});
return payload.slice(0, -1);
};
var createFakeSentence = (model, talker) => {
const id = talker !== void 0 ? `${talker}${model.id}` : model.id;
const payload = createPayload(model);
const info = `${id},${payload}`;
const checksum = numberChecksumToString(calculateChecksum(info));
return `${START_FLAG}${info}${DELIMITER}${checksum}${END_FLAG}`;
};
// src/parser.ts
var Parser = (_class = class {
// Memory - Buffer
__init() {this._memory = true}
get memory() {
return this._memory;
}
set memory(mem) {
this._memory = BooleanSchema.parse(mem);
}
__init2() {this._buffer = ""}
__init3() {this._bufferLength = MAX_CHARACTERS}
get bufferLimit() {
return this._bufferLength;
}
set bufferLimit(limit) {
this._bufferLength = UnsignedIntegerSchema.parse(limit);
}
// Sentences
__init4() {this._sentences = /* @__PURE__ */ new Map()}
// get sentences() { return this._sentences }
constructor(memory = false, limit = MAX_CHARACTERS) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);
this.memory = memory;
this.bufferLimit = limit;
this.readInternalProtocols();
}
// Mandatory --------------------------------------------------------------------------------------------------------
readInternalProtocols() {
const parsed = ProtocolsInputSchema.parse(NMEA_SENTENCES);
this.addProtocols(parsed);
}
readProtocols(input) {
if (input.file !== void 0) return readProtocolsYAMLFile(input.file);
if (input.content !== void 0) return readProtocolsYAMLString(input.content);
if (input.protocols !== void 0) return { protocols: input.protocols };
throw new Error("Invalid protocols to add");
}
addProtocols(input) {
if (!ProtocolsInputSchema.is(input)) {
const error = "Parser: invalid protocols to parse";
console.error(error);
console.error(input);
throw new Error(error);
}
const { protocols } = this.readProtocols(input);
const sentences = getStoreSentences({ protocols });
this._sentences = new Map([...this._sentences, ...sentences]);
}
parseData(text) {
if (!StringSchema.is(text)) return [];
const data = this.memory ? this._buffer + text : text;
return this.getFrames(data);
}
getFrames(text) {
if (this._memory) {
const lastFrame = lastUncompletedFrame(text);
if (lastFrame !== null) {
this._buffer = lastFrame;
}
}
const unparsedFrames = getUnparsedNMEAFrames(text);
return unparsedFrames.map((frame) => this.getFrame(frame));
}
getFrame(text) {
const received = Date.now();
const { id: sentenceID, payload: pl, checksum: cs } = getIdPayloadAndChecksum(text);
const checksum = getChecksum(cs);
const sentence = this._sentences.get(sentenceID);
if (sentence !== void 0) {
const response = getKnownNMEASentence({ received, sample: text, sentenceID, sentencePayload: pl, checksum, model: sentence });
if (response !== null) {
return response;
}
}
const talker = getTalker(sentenceID);
if (talker !== null) {
const id = sentenceID.replace(talker.value, "");
const talkerSentence = this._sentences.get(id);
if (talkerSentence !== void 0) {
const response = getKnownNMEASentence({ received, sample: text, sentenceID: id, sentencePayload: pl, checksum, model: talkerSentence });
if (response !== null) {
return { ...response, talker };
}
}
}
const unknown = getUnknowNMEASentence({ received, sample: text, sentenceID, sentencePayload: pl, checksum });
return talker !== null ? { ...unknown, talker } : unknown;
}
// Nice to have -----------------------------------------------------------------------------------------------------
getSentences() {
return Array.from(this._sentences.values());
}
getSentencesByProtocol() {
const sentences = this.getSentences();
const response = {};
sentences.forEach((sentence) => {
const key = sentence.protocol.name;
if (!(key in response)) {
response[key] = [sentence];
}
response[key].push(sentence);
});
return response;
}
getSentence(id) {
if (!StringSchema.is(id) || id.length < NMEA_ID_LENGTH) {
return null;
}
const sentence = this._sentences.get(id);
if (sentence !== void 0) {
return { ...sentence };
}
const talker = getTalker(id);
if (talker === null) {
return null;
}
const sentenceID = id.slice(talker.value.length);
const sent = this._sentences.get(sentenceID);
if (sent !== void 0) {
return { ...sent, talker };
}
return null;
}
getFakeSentenceByID(id) {
if (!StringSchema.is(id) || id.length < NMEA_ID_LENGTH) {
return null;
}
const sentence = this._sentences.get(id);
if (sentence !== void 0) {
return createFakeSentence(sentence);
}
const talker = getTalker(id);
if (talker !== null) {
const sentenceID = id.slice(talker.value.length);
const sent = this._sentences.get(sentenceID);
if (sent !== void 0) {
return createFakeSentence(sent, talker.value);
}
}
return null;
}
}, _class);
exports.BooleanSchema = BooleanSchema; exports.CHECKSUM_LENGTH = CHECKSUM_LENGTH; exports.CODE = CODE; exports.ChecksumSchema = ChecksumSchema; exports.DELIMITER = DELIMITER; exports.DELIMITER_LENGTH = DELIMITER_LENGTH; exports.END_FLAG = END_FLAG; exports.END_FLAG_LENGTH = END_FLAG_LENGTH; exports.FIELD_TYPES = FIELD_TYPES; exports.Float32Schema = Float32Schema; exports.Float64Schema = Float64Schema; exports.INT16_MAX = INT16_MAX; exports.INT16_MIN = INT16_MIN; exports.INT32_MAX = INT32_MAX; exports.INT32_MIN = INT32_MIN; exports.INT8_MAX = INT8_MAX; exports.INT8_MIN = INT8_MIN; exports.Int16Schema = Int16Schema; exports.Int32Schema = Int32Schema; exports.Int64Schema = Int64Schema; exports.Int8Schema = Int8Schema; exports.IntegerSchema = IntegerSchema; exports.JSONSchemaInputSchema = JSONSchemaInputSchema; exports.MAX_CHARACTERS = MAX_CHARACTERS; exports.MAX_FLOAT = MAX_FLOAT; exports.MAX_NMEA_CHARACTERS = MAX_NMEA_CHARACTERS; exports.MINIMAL_LENGTH = MINIMAL_LENGTH; exports.MIN_FLOAT = MIN_FLOAT; exports.MapStoredSentencesSchema = MapStoredSentencesSchema; exports.NMEALikeSchema = NMEALikeSchema; exports.NMEAParsedFieldchema = NMEAParsedFieldchema; exports.NMEAParsedPayloadSchema = NMEAParsedPayloadSchema; exports.NMEAParser = Parser; exports.NMEASentenceSchema = NMEASentenceSchema; exports.NMEA_ID_LENGTH = NMEA_ID_LENGTH; exports.NMEA_SENTENCE_LENGTH = NMEA_SENTENCE_LENGTH; exports.NMEA_TALKER_LENGTH = NMEA_TALKER_LENGTH; exports.NumberSchema = NumberSchema; exports.ProtocolFieldSchema = ProtocolFieldSchema; exports.ProtocolFieldTypeSchema = ProtocolFieldTypeSchema; exports.ProtocolSchema = ProtocolSchema; exports.ProtocolSentencePayloadSchema = ProtocolSentencePayloadSchema; exports.ProtocolSentenceSchema = ProtocolSentenceSchema; exports.ProtocolsFileContentSchema = ProtocolsFileContentSchema; exports.ProtocolsInputSchema = ProtocolsInputSchema; exports.SEPARATOR = SEPARATOR; exports.SEPARATOR_LENGTH = SEPARATOR_LENGTH; exports.START_FLAG = START_FLAG; exports.START_FLAG_LENGTH = START_FLAG_LENGTH; exports.StoredSentenceSchema = StoredSentenceSchema; exports.StringArraySchema = StringArraySchema; exports.StringSchema = StringSchema; exports.TALKERS = TALKERS; exports.TALKERS_SPECIAL = TALKERS_SPECIAL; exports.TalkerSchema = TalkerSchema; exports.UINT16_MAX = UINT16_MAX; exports.UINT32_MAX = UINT32_MAX; exports.UINT8_MAX = UINT8_MAX; exports.UNKNOWN_NMEA_SENTENCE_SCAFOLDING = UNKNOWN_NMEA_SENTENCE_SCAFOLDING; exports.Uint16Schema = Uint16Schema; exports.Uint32Schema = Uint32Schema; exports.Uint64Schema = Uint64Schema; exports.Uint8Schema = Uint8Schema; exports.UnsignedIntegerSchema = UnsignedIntegerSchema; exports.ValibotProtocolsFileContentSchema = ValibotProtocolsFileContentSchema; exports.ValueSchema = ValueSchema; exports.VersionSchema = VersionSchema;