@airframes/acars-decoder 1.6.13 → 1.6.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2150,775 +2150,807 @@ var Label_4A_Slash_01 = class extends DecoderPlugin {
2150
2150
  }
2151
2151
  };
2152
2152
 
2153
- // lib/plugins/Label_4N.ts
2154
- var Label_4N = class extends DecoderPlugin {
2155
- name = "label-4n";
2156
- qualifiers() {
2157
- return {
2158
- labels: ["4N"]
2159
- };
2160
- }
2161
- decode(message, options = {}) {
2162
- const decodeResult = this.defaultResult();
2163
- decodeResult.decoder.name = this.name;
2164
- decodeResult.message = message;
2165
- decodeResult.formatted.description = "Airline Defined";
2166
- let text = message.text;
2167
- if (text.match(/^M\d{2}A\w{6}/)) {
2168
- ResultFormatter.flightNumber(decodeResult, message.text.substring(4, 10).replace(/^([A-Z]+)0*/g, "$1"));
2169
- text = text.substring(10);
2170
- }
2171
- decodeResult.decoded = true;
2172
- const fields = text.split(",");
2173
- if (text.length === 51) {
2174
- decodeResult.raw.day_of_month = text.substring(0, 2);
2175
- ResultFormatter.departureAirport(decodeResult, text.substring(8, 11));
2176
- ResultFormatter.arrivalAirport(decodeResult, text.substring(13, 16));
2177
- ResultFormatter.position(decodeResult, CoordinateUtils.decodeStringCoordinatesDecimalMinutes(text.substring(30, 45).replace(/^(.)0/, "$1")));
2178
- ResultFormatter.altitude(decodeResult, Number(text.substring(48, 51)) * 100);
2179
- ResultFormatter.unknownArr(decodeResult, [text.substring(2, 4), text.substring(19, 29)], " ");
2180
- } else if (fields.length === 33) {
2181
- decodeResult.raw.date = fields[3];
2182
- if (fields[1] === "B") {
2183
- ResultFormatter.position(decodeResult, { latitude: Number(fields[4]), longitude: Number(fields[5]) });
2184
- ResultFormatter.altitude(decodeResult, Number(fields[6]));
2185
- }
2186
- ResultFormatter.departureAirport(decodeResult, fields[8]);
2187
- ResultFormatter.arrivalAirport(decodeResult, fields[9]);
2188
- ResultFormatter.alternateAirport(decodeResult, fields[10]);
2189
- ResultFormatter.arrivalRunway(decodeResult, fields[11].split("/")[0]);
2190
- if (fields[12].length > 1) {
2191
- ResultFormatter.alternateRunway(decodeResult, fields[12].split("/")[0]);
2153
+ // lib/utils/flight_plan_utils.ts
2154
+ var FlightPlanUtils = class _FlightPlanUtils {
2155
+ /**
2156
+ * Processes flight plan data
2157
+ *
2158
+ * Expected format is [header, key1, val1, ... keyN, valN]
2159
+ *
2160
+ * @param decodeResult - results
2161
+ * @param data - original message split by ':'
2162
+ * @returns whether all fields were processed or not
2163
+ */
2164
+ static processFlightPlan(decodeResult, data) {
2165
+ let allKnownFields = _FlightPlanUtils.parseHeader(decodeResult, data[0]);
2166
+ for (let i = 1; i < data.length; i += 2) {
2167
+ const key = data[i];
2168
+ const value = data[i + 1];
2169
+ switch (key) {
2170
+ case "A":
2171
+ addProcedure(decodeResult, value, "arrival");
2172
+ break;
2173
+ case "AA":
2174
+ addArrivalAirport(decodeResult, value);
2175
+ break;
2176
+ case "AP":
2177
+ addProcedure(decodeResult, value, "approach");
2178
+ break;
2179
+ case "CR":
2180
+ addCompanyRoute(decodeResult, value);
2181
+ break;
2182
+ case "D":
2183
+ addProcedure(decodeResult, value, "departure");
2184
+ break;
2185
+ case "DA":
2186
+ addDepartureAirport(decodeResult, value);
2187
+ break;
2188
+ case "F":
2189
+ addRoute(decodeResult, value);
2190
+ break;
2191
+ case "R":
2192
+ addRunway(decodeResult, value);
2193
+ break;
2194
+ // case 'WS': // something about routes, has altitude, so current parsing won't work
2195
+ // break;
2196
+ default:
2197
+ if (allKnownFields) {
2198
+ decodeResult.remaining.text = "";
2199
+ allKnownFields = false;
2200
+ }
2201
+ decodeResult.remaining.text += `:${key}:${value}`;
2202
+ decodeResult.decoder.decodeLevel = "partial";
2192
2203
  }
2193
- ResultFormatter.checksum(decodeResult, fields[32]);
2194
- ResultFormatter.unknownArr(decodeResult, [...fields.slice(1, 3), fields[7], ...fields.slice(13, 32)].filter((f) => f != ""));
2195
- } else {
2196
- decodeResult.decoded = false;
2197
- ResultFormatter.unknown(decodeResult, text);
2198
2204
  }
2199
- if (decodeResult.decoded) {
2200
- if (!decodeResult.remaining.text)
2201
- decodeResult.decoder.decodeLevel = "full";
2202
- else
2203
- decodeResult.decoder.decodeLevel = "partial";
2205
+ return allKnownFields;
2206
+ }
2207
+ static parseHeader(decodeResult, header) {
2208
+ let allKnownFields = true;
2209
+ if (header.startsWith("RF")) {
2210
+ decodeResult.formatted.items.push({
2211
+ type: "status",
2212
+ code: "ROUTE_STATUS",
2213
+ label: "Route Status",
2214
+ value: "Route Filed"
2215
+ });
2216
+ decodeResult.raw.route_status = "RF";
2217
+ if (header.length > 2) {
2218
+ addRoute(decodeResult, header.substring(2));
2219
+ }
2220
+ } else if (header.startsWith("RP")) {
2221
+ decodeResult.raw.route_status = "RP";
2222
+ decodeResult.formatted.items.push({
2223
+ type: "status",
2224
+ code: "ROUTE_STATUS",
2225
+ label: "Route Status",
2226
+ value: "Route Planned"
2227
+ });
2228
+ decodeResult.raw.route_status = header;
2229
+ } else if (header.startsWith("RI")) {
2230
+ decodeResult.raw.route_status = "RI";
2231
+ decodeResult.formatted.items.push({
2232
+ type: "status",
2233
+ code: "ROUTE_STATUS",
2234
+ label: "Route Status",
2235
+ value: "Route Inactive"
2236
+ });
2204
2237
  } else {
2205
- decodeResult.decoder.decodeLevel = "none";
2238
+ decodeResult.remaining.text += header;
2239
+ allKnownFields = false;
2206
2240
  }
2207
- return decodeResult;
2241
+ return allKnownFields;
2208
2242
  }
2209
2243
  };
2210
-
2211
- // lib/plugins/Label_80.ts
2212
- var Label_80 = class extends DecoderPlugin {
2213
- name = "label-80";
2214
- descriptions = {
2215
- ALT: "Altitude",
2216
- DWND: "Wind Direction",
2217
- ETA: "Estimated Time of Arrival",
2218
- FOB: "Fuel on Board",
2219
- FL: "Flight Level",
2220
- HDG: "Heading",
2221
- MCH: "Aircraft Speed",
2222
- NWYP: "Next Waypoint",
2223
- POS: "Aircraft Position",
2224
- SAT: "Static Air Temperature",
2225
- SWND: "Wind Speed",
2226
- TAS: "True Airspeed",
2227
- WYP: "Waypoint"
2228
- };
2229
- qualifiers() {
2230
- return {
2231
- labels: ["80"],
2232
- preambles: ["3N01 POSRPT"]
2233
- };
2244
+ function addArrivalAirport(decodeResult, value) {
2245
+ ResultFormatter.arrivalAirport(decodeResult, value);
2246
+ }
2247
+ function addDepartureAirport(decodeResult, value) {
2248
+ ResultFormatter.departureAirport(decodeResult, value);
2249
+ }
2250
+ function addRunway(decodeResult, value) {
2251
+ if (value.length === 8) {
2252
+ ResultFormatter.arrivalRunway(decodeResult, value.substring(4, 7));
2234
2253
  }
2235
- decode(message, options = {}) {
2236
- const decodeResult = this.defaultResult();
2237
- decodeResult.decoder.name = this.name;
2238
- decodeResult.formatted.description = "Airline Defined Position Report";
2239
- const parts = message.text.split("\n");
2240
- let posRptRegex = /^3N01 POSRPT \d\d\d\d\/\d\d (?<orig>\w+)\/(?<dest>\w+) \.(?<tail>[\w-]+)(\/(?<agate>.+) (?<sta>\w+:\w+))*/;
2241
- let results = parts[0].match(posRptRegex);
2242
- if (!results?.groups) {
2243
- decodeResult.decoded = false;
2244
- decodeResult.decoder.decodeLevel = "none";
2245
- return decodeResult;
2246
- }
2247
- if (results && results.length > 0) {
2248
- ResultFormatter.departureAirport(decodeResult, results.groups.orig);
2249
- ResultFormatter.arrivalAirport(decodeResult, results.groups.dest);
2250
- ResultFormatter.tail(decodeResult, results.groups.tail);
2251
- if (results.groups.agate) {
2252
- decodeResult.raw.arrival_gate = results.groups.agate;
2253
- decodeResult.formatted.items.push({
2254
- type: "arrival_gate",
2255
- code: "ARG",
2256
- label: "Arrival Gate",
2257
- value: `${results.groups.agate}`
2258
- });
2259
- decodeResult.raw.scheduled_time_of_arrival = results.groups.sta;
2260
- decodeResult.formatted.items.push({
2261
- type: "scheduled_time_of_arrival",
2262
- code: "STA",
2263
- label: "Scheduled Time of Arrival",
2264
- value: `${results.groups.sta}`
2265
- });
2266
- }
2267
- posRptRegex = /\/(?<field>\w+)\s(?<value>[\w\+\-:\.]+)\s*/gi;
2268
- const remainingParts = parts.slice(1);
2269
- for (const part of remainingParts) {
2270
- const matches = part.matchAll(posRptRegex);
2271
- for (const match of matches) {
2272
- switch (match.groups?.field) {
2273
- case "ALT": {
2274
- ResultFormatter.altitude(decodeResult, Number(match.groups.value));
2275
- break;
2276
- }
2277
- case "DWND": {
2278
- decodeResult.raw.wind_direction = Number(match.groups.value);
2279
- decodeResult.formatted.items.push({
2280
- type: "wind_direction",
2281
- code: "DWND",
2282
- label: this.descriptions[match.groups.field],
2283
- value: decodeResult.raw.wind_direction
2284
- });
2285
- break;
2286
- }
2287
- case "FL": {
2288
- const flight_level = Number(match.groups.value);
2289
- ResultFormatter.altitude(decodeResult, flight_level * 100);
2290
- break;
2291
- }
2292
- case "FOB": {
2293
- const fob = Number(match.groups.value);
2294
- if (!isNaN(fob)) {
2295
- ResultFormatter.currentFuel(decodeResult, fob);
2296
- }
2297
- break;
2298
- }
2299
- case "HDG": {
2300
- ResultFormatter.heading(decodeResult, Number(match.groups.value));
2301
- break;
2302
- }
2303
- case "MCH": {
2304
- decodeResult.raw.mach = Number(match.groups.value) / 1e3;
2305
- decodeResult.formatted.items.push({
2306
- type: "mach",
2307
- code: "MCH",
2308
- label: this.descriptions[match.groups.field],
2309
- value: `${decodeResult.raw.mach} Mach`
2310
- });
2311
- break;
2312
- }
2313
- case "NWYP": {
2314
- decodeResult.raw.next_waypoint = match.groups.value;
2315
- decodeResult.formatted.items.push({
2316
- type: "next_waypoint",
2317
- code: "NWYP",
2318
- label: this.descriptions[match.groups.field],
2319
- value: decodeResult.raw.next_waypoint
2320
- });
2321
- break;
2322
- }
2323
- case "POS": {
2324
- const posRegex = /^(?<latd>[NS])(?<lat>.+)(?<lngd>[EW])(?<lng>.+)/;
2325
- const posResult = match.groups.value.match(posRegex);
2326
- const lat = Number(posResult?.groups?.lat) * (posResult?.groups?.lngd === "S" ? -1 : 1);
2327
- const lon = Number(posResult?.groups?.lng) * (posResult?.groups?.lngd === "W" ? -1 : 1);
2328
- const position = {
2329
- latitude: Number.isInteger(lat) ? lat / 1e3 : lat / 100,
2330
- longitude: Number.isInteger(lon) ? lon / 1e3 : lon / 100
2331
- };
2332
- ResultFormatter.position(decodeResult, position);
2333
- break;
2334
- }
2335
- case "SWND": {
2336
- decodeResult.raw.wind_speed = Number(match.groups.value);
2337
- decodeResult.formatted.items.push({
2338
- type: "wind_speed",
2339
- code: "SWND",
2340
- label: this.descriptions[match.groups.field],
2341
- value: decodeResult.raw.wind_speed
2342
- });
2343
- break;
2344
- }
2345
- default: {
2346
- if (match.groups?.field != void 0) {
2347
- const description = this.descriptions[match.groups.field] ? this.descriptions[match.groups.field] : "Unknown";
2348
- decodeResult.formatted.items.push({
2349
- type: match.groups.field,
2350
- code: match.groups.field,
2351
- label: description || `Unknown (${match.groups.field})`,
2352
- value: `${match.groups.value}`
2353
- });
2354
- }
2355
- }
2356
- }
2357
- }
2358
- }
2359
- decodeResult.decoded = true;
2360
- decodeResult.decoder.decodeLevel = "partial";
2361
- }
2362
- return decodeResult;
2254
+ ResultFormatter.departureRunway(decodeResult, value.substring(0, 3));
2255
+ }
2256
+ function addRoute(decodeResult, value) {
2257
+ const route = value.split(".");
2258
+ ResultFormatter.route(decodeResult, { waypoints: route.map((leg) => RouteUtils.getWaypoint(leg)) });
2259
+ }
2260
+ function addProcedure(decodeResult, value, type) {
2261
+ if (decodeResult.raw.procedures === void 0) {
2262
+ decodeResult.raw.procedures = [];
2363
2263
  }
2364
- };
2365
-
2366
- // lib/plugins/Label_83.ts
2367
- var Label_83 = class extends DecoderPlugin {
2368
- name = "label-83";
2369
- qualifiers() {
2370
- return {
2371
- labels: ["83"]
2372
- };
2264
+ const data = value.split(".");
2265
+ let waypoints;
2266
+ if (data.length > 1) {
2267
+ waypoints = data.slice(1).map((leg) => RouteUtils.getWaypoint(leg));
2373
2268
  }
2374
- decode(message, options = {}) {
2375
- const decodeResult = this.defaultResult();
2376
- decodeResult.decoder.name = this.name;
2377
- decodeResult.message = message;
2378
- decodeResult.formatted.description = "Airline Defined";
2379
- let text = message.text;
2380
- if (text.match(/^M\d{2}A\w{6}/)) {
2381
- ResultFormatter.flightNumber(decodeResult, message.text.substring(4, 10).replace(/^([A-Z]+)0*/g, "$1"));
2382
- text = text.substring(10);
2383
- }
2384
- decodeResult.decoded = true;
2385
- if (text.substring(0, 10) === "4DH3 ETAT2") {
2386
- const fields = text.split(/\s+/);
2387
- if (fields[2].length > 5) {
2388
- decodeResult.raw.day_of_month = fields[2].substring(5);
2389
- }
2390
- ResultFormatter.unknown(decodeResult, fields[2].substring(0, 4));
2391
- const subfields = fields[3].split("/");
2392
- ResultFormatter.departureAirport(decodeResult, subfields[0]);
2393
- ResultFormatter.arrivalAirport(decodeResult, subfields[1]);
2394
- ResultFormatter.tail(decodeResult, fields[4].replace(/\./g, ""));
2395
- ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[6] + "00"));
2396
- } else if (text.substring(0, 5) === "001PR") {
2397
- decodeResult.raw.day_of_month = text.substring(5, 7);
2398
- const position = CoordinateUtils.decodeStringCoordinatesDecimalMinutes(text.substring(13, 28).replace(/\./g, ""));
2399
- if (position) {
2400
- ResultFormatter.position(decodeResult, position);
2401
- }
2402
- ResultFormatter.altitude(decodeResult, Number(text.substring(28, 33)));
2403
- ResultFormatter.unknown(decodeResult, text.substring(33));
2404
- } else {
2405
- const fields = text.replace(/\s/g, "").split(",");
2406
- if (fields.length === 9) {
2407
- ResultFormatter.departureAirport(decodeResult, fields[0]);
2408
- ResultFormatter.arrivalAirport(decodeResult, fields[1]);
2409
- decodeResult.raw.day_of_month = fields[2].substring(0, 2);
2410
- decodeResult.raw.time = fields[2].substring(2);
2411
- ResultFormatter.position(
2412
- decodeResult,
2413
- {
2414
- latitude: Number(fields[3].replace(/\s/g, "")),
2415
- longitude: Number(fields[4].replace(/\s/g, ""))
2416
- }
2417
- );
2418
- ResultFormatter.altitude(decodeResult, Number(fields[5]));
2419
- ResultFormatter.groundspeed(decodeResult, Number(fields[6]));
2420
- ResultFormatter.heading(decodeResult, Number(fields[7]));
2421
- ResultFormatter.unknown(decodeResult, fields[8]);
2269
+ const route = { name: data[0], waypoints };
2270
+ decodeResult.raw.procedures.push({ type, route });
2271
+ const procedureName = type.substring(0, 1).toUpperCase() + type.slice(1);
2272
+ let procedureValue = route.name;
2273
+ decodeResult.formatted.items.push({
2274
+ type: `procedure`,
2275
+ code: "proc",
2276
+ label: `${procedureName} Procedure`,
2277
+ value: RouteUtils.routeToString(route)
2278
+ });
2279
+ }
2280
+ function addCompanyRoute(decodeResult, value) {
2281
+ const segments = value.split(".");
2282
+ const parens_idx = segments[0].indexOf("(");
2283
+ let name;
2284
+ let runway;
2285
+ if (parens_idx === -1) {
2286
+ name = segments[0];
2287
+ } else {
2288
+ name = segments[0].slice(0, parens_idx);
2289
+ runway = segments[0].slice(parens_idx + 1, segments[0].indexOf(")"));
2290
+ }
2291
+ let waypoints;
2292
+ if (segments.length > 1) {
2293
+ waypoints = segments.slice(1).map((leg) => RouteUtils.getWaypoint(leg));
2294
+ }
2295
+ decodeResult.raw.company_route = {
2296
+ name,
2297
+ runway,
2298
+ waypoints
2299
+ };
2300
+ decodeResult.formatted.items.push({
2301
+ type: "company_route",
2302
+ code: "CR",
2303
+ label: "Company Route",
2304
+ value: RouteUtils.routeToString(decodeResult.raw.company_route)
2305
+ });
2306
+ }
2307
+
2308
+ // lib/utils/h1_helper.ts
2309
+ var H1Helper = class {
2310
+ static decodeH1Message(decodeResult, message) {
2311
+ const checksum = message.slice(-4);
2312
+ const data = message.slice(0, message.length - 4);
2313
+ const fields = data.split("/");
2314
+ parseMessageType(decodeResult, fields[0]);
2315
+ for (let i = 1; i < fields.length; ++i) {
2316
+ if (fields[i].startsWith("FN")) {
2317
+ decodeResult.raw.flight_number = fields[i].substring(2);
2318
+ } else if (fields[i].startsWith("SN")) {
2319
+ decodeResult.raw.serial_number = fields[i].substring(2);
2320
+ } else if (fields[i].startsWith("DC")) {
2321
+ processDC(decodeResult, fields[i].substring(2).split(","));
2322
+ } else if (fields[i].startsWith("TS")) {
2323
+ const ts = fields[i].substring(2).split(",");
2324
+ let time = DateTimeUtils.convertDateTimeToEpoch(ts[0], ts[1]);
2325
+ if (Number.isNaN(time)) {
2326
+ const date = ts[1].substring(2, 4) + ts[1].substring(0, 2) + ts[1].substring(4, 6);
2327
+ time = DateTimeUtils.convertDateTimeToEpoch(ts[0], date);
2328
+ }
2329
+ decodeResult.raw.message_date = ts[1];
2330
+ decodeResult.raw.message_timestamp = time;
2331
+ } else if (fields[i].startsWith("PS")) {
2332
+ const pos = processPS(decodeResult, fields[i].substring(2).split(","));
2333
+ } else if (fields[i].startsWith("DT")) {
2334
+ const data2 = fields[i].substring(2).split(",");
2335
+ processDT(decodeResult, data2);
2336
+ } else if (fields[i].startsWith("ID")) {
2337
+ processIdentification(decodeResult, fields[i].substring(2).split(","));
2338
+ } else if (fields[i].startsWith("LR")) {
2339
+ const data2 = fields[i].substring(2).split(",");
2340
+ processLR(decodeResult, data2);
2341
+ } else if (fields[i].startsWith("RI") || fields[i].startsWith("RF") || fields[i].startsWith("RP")) {
2342
+ FlightPlanUtils.processFlightPlan(decodeResult, fields[i].split(":"));
2343
+ } else if (fields[i].startsWith("PR")) {
2344
+ ResultFormatter.unknown(decodeResult, fields[i], "/");
2345
+ } else if (fields[i].startsWith("AF")) {
2346
+ processAirField(decodeResult, fields[i].substring(2).split(","));
2347
+ } else if (fields[i].startsWith("TD")) {
2348
+ processTimeOfDeparture(decodeResult, fields[i].substring(2).split(","));
2349
+ } else if (fields[i].startsWith("FX")) {
2350
+ ResultFormatter.freetext(decodeResult, fields[i].substring(2));
2351
+ } else if (fields[i].startsWith("ET")) {
2352
+ if (fields[i].length === 7) {
2353
+ ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[i].substring(3) + "00"));
2354
+ } else if (fields[i].length === 8) {
2355
+ ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[i].substring(4) + "00"));
2356
+ } else {
2357
+ ResultFormatter.unknown(decodeResult, fields[i], "/");
2358
+ }
2422
2359
  } else {
2423
- decodeResult.decoded = false;
2424
- ResultFormatter.unknown(decodeResult, message.text);
2360
+ ResultFormatter.unknown(decodeResult, fields[i], "/");
2425
2361
  }
2426
2362
  }
2427
- if (decodeResult.decoded) {
2428
- if (!decodeResult.remaining.text)
2429
- decodeResult.decoder.decodeLevel = "full";
2430
- else
2431
- decodeResult.decoder.decodeLevel = "partial";
2432
- } else {
2433
- decodeResult.decoder.decodeLevel = "none";
2363
+ if (decodeResult.formatted.items.length > 0) {
2364
+ ResultFormatter.checksum(decodeResult, checksum);
2434
2365
  }
2435
- return decodeResult;
2366
+ return true;
2436
2367
  }
2437
2368
  };
2438
-
2439
- // lib/plugins/Label_8E.ts
2440
- var Label_8E = class extends DecoderPlugin {
2441
- name = "label-8e";
2442
- qualifiers() {
2443
- return {
2444
- labels: ["8E"]
2445
- };
2446
- }
2447
- decode(message, options = {}) {
2448
- const decodeResult = this.defaultResult();
2449
- decodeResult.decoder.name = this.name;
2450
- decodeResult.formatted.description = "ETA Report";
2451
- decodeResult.message = message;
2452
- const regex = /^(?<arrival_icao>\w{4}),(?<arrival_eta>\d{4})$/;
2453
- const results = message.text.match(regex);
2454
- if (results?.groups) {
2455
- if (options.debug) {
2456
- console.log(`Label 8E ETA: groups`);
2457
- console.log(results.groups);
2458
- }
2459
- ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(results.groups.arrival_eta + "00"));
2460
- ResultFormatter.arrivalAirport(decodeResult, results.groups.arrival_icao);
2461
- }
2462
- decodeResult.decoded = true;
2463
- decodeResult.decoder.decodeLevel = "full";
2464
- return decodeResult;
2369
+ function processAirField(decodeResult, data) {
2370
+ if (data.length === 2) {
2371
+ ResultFormatter.departureAirport(decodeResult, data[0]);
2372
+ ResultFormatter.arrivalAirport(decodeResult, data[1]);
2373
+ } else {
2374
+ ResultFormatter.unknown(decodeResult, data.join(","), "AF/");
2465
2375
  }
2466
- };
2467
-
2468
- // lib/plugins/Label_B6.ts
2469
- var Label_B6_Forwardslash = class extends DecoderPlugin {
2470
- name = "label-b6-forwardslash";
2471
- qualifiers() {
2472
- return {
2473
- labels: ["B6"],
2474
- preambles: ["/"]
2376
+ }
2377
+ function processTimeOfDeparture(decodeResult, data) {
2378
+ if (data.length === 2) {
2379
+ decodeResult.raw.plannedDepartureTime = data[0];
2380
+ decodeResult.formatted.items.push({
2381
+ type: "ptd",
2382
+ code: "ptd",
2383
+ label: "Planned Departure Time",
2384
+ value: `YYYY-MM-${data[0].substring(0, 2)}T${data[0].substring(2, 4)}:${data[0].substring(4)}:00Z`
2385
+ });
2386
+ decodeResult.raw.plannedDepartureTime = data[1];
2387
+ decodeResult.formatted.items.push({
2388
+ type: "etd",
2389
+ code: "etd",
2390
+ label: "Estimated Departure Time",
2391
+ value: `${data[1].substring(0, 2)}:${data[1].substring(2)}`
2392
+ });
2393
+ } else {
2394
+ ResultFormatter.unknown(decodeResult, data.join(","), "/TD");
2395
+ }
2396
+ }
2397
+ function processIdentification(decodeResult, data) {
2398
+ ResultFormatter.tail(decodeResult, data[0]);
2399
+ if (data.length > 1) {
2400
+ decodeResult.raw.flight_number = data[1];
2401
+ }
2402
+ if (data.length > 2) {
2403
+ decodeResult.raw.mission_number = data[2];
2404
+ }
2405
+ }
2406
+ function processDT(decodeResult, data) {
2407
+ if (!decodeResult.raw.arrival_icao) {
2408
+ ResultFormatter.arrivalAirport(decodeResult, data[0]);
2409
+ } else if (decodeResult.raw.arrival_icao != data[0]) {
2410
+ ResultFormatter.unknownArr(decodeResult, data);
2411
+ }
2412
+ if (data.length > 1) {
2413
+ ResultFormatter.arrivalRunway(decodeResult, data[1]);
2414
+ }
2415
+ if (data.length > 2) {
2416
+ ResultFormatter.currentFuel(decodeResult, Number(data[2]));
2417
+ }
2418
+ if (data.length > 3) {
2419
+ ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(data[3]));
2420
+ }
2421
+ if (data.length > 4) {
2422
+ ResultFormatter.remainingFuel(decodeResult, Number(data[4]));
2423
+ }
2424
+ if (data.length > 5) {
2425
+ ResultFormatter.unknownArr(decodeResult, data);
2426
+ }
2427
+ }
2428
+ function processLR(decodeResult, data) {
2429
+ if (data.length === 19) {
2430
+ ResultFormatter.unknown(decodeResult, data[1]);
2431
+ ResultFormatter.flightNumber(decodeResult, data[2]);
2432
+ ResultFormatter.departureAirport(decodeResult, data[3]);
2433
+ ResultFormatter.arrivalAirport(decodeResult, data[4]);
2434
+ ResultFormatter.arrivalRunway(decodeResult, data[5]);
2435
+ ResultFormatter.unknown(decodeResult, data.slice(6, 19).join(","));
2436
+ } else {
2437
+ ResultFormatter.unknown(decodeResult, data.join(","));
2438
+ }
2439
+ }
2440
+ function parseMessageType(decodeResult, messageType) {
2441
+ const parts = messageType.split("#");
2442
+ if (parts.length == 1) {
2443
+ const type = parts[0].substring(0, 3);
2444
+ if (type === "POS" && parts[0].length !== 3) {
2445
+ processPosition2(decodeResult, parts[0].substring(3).split(","));
2446
+ }
2447
+ return processMessageType(decodeResult, type);
2448
+ } else if (parts.length == 2) {
2449
+ if (parts[0].length > 0) {
2450
+ ResultFormatter.unknown(decodeResult, parts[0].substring(0, 3));
2451
+ decodeResult.raw.flight_number = parts[0].substring(3);
2452
+ ResultFormatter.unknown(decodeResult, parts[1].length == 5 ? parts[1].substring(0, 2) : parts[1].substring(0, 3), "#");
2453
+ }
2454
+ const type = parts[1].length == 5 ? parts[1].substring(2, 5) : parts[1].substring(3, 6);
2455
+ if (parts[1].substring(3, 6) === "POS" && parts[1].length > 6) {
2456
+ processPosition2(decodeResult, parts[1].substring(6).split(","));
2457
+ }
2458
+ processMessageType(decodeResult, type);
2459
+ } else {
2460
+ ResultFormatter.unknown(decodeResult, messageType);
2461
+ }
2462
+ }
2463
+ function processMessageType(decodeResult, type) {
2464
+ if (type === "FPN") {
2465
+ decodeResult.formatted.description = "Flight Plan";
2466
+ } else if (type === "FTX") {
2467
+ decodeResult.formatted.description = "Free Text";
2468
+ } else if (type === "INI") {
2469
+ decodeResult.formatted.description = "Flight Plan Initial Report";
2470
+ } else if (type === "POS") {
2471
+ decodeResult.formatted.description = "Position Report";
2472
+ } else if (type === "PRG") {
2473
+ decodeResult.formatted.description = "Progress Report";
2474
+ } else {
2475
+ decodeResult.formatted.description = "Unknown H1 Message";
2476
+ }
2477
+ }
2478
+ function processDC(decodeResult, data) {
2479
+ decodeResult.raw.message_date = data[0];
2480
+ if (data.length === 1) {
2481
+ } else if (data.length === 2) {
2482
+ const date = data[0].substring(2, 4) + data[0].substring(0, 2) + data[0].substring(4, 6);
2483
+ const time = DateTimeUtils.convertDateTimeToEpoch(data[1], data[0]);
2484
+ decodeResult.raw.message_timestamp = time;
2485
+ }
2486
+ }
2487
+ function processPS(decodeResult, data) {
2488
+ const position = CoordinateUtils.decodeStringCoordinatesDecimalMinutes(data[0]);
2489
+ if (position) {
2490
+ decodeResult.raw.position = position;
2491
+ decodeResult.formatted.items.push({
2492
+ type: "aircraft_position",
2493
+ code: "POS",
2494
+ label: "Aircraft Position",
2495
+ value: CoordinateUtils.coordinateString(position)
2496
+ });
2497
+ }
2498
+ if (data.length === 9) {
2499
+ processRoute(decodeResult, data[3], data[1], data[5], data[4], void 0);
2500
+ ResultFormatter.altitude(decodeResult, Number(data[2]) * 100);
2501
+ ResultFormatter.temperature(decodeResult, data[6]);
2502
+ ResultFormatter.unknown(decodeResult, data[7]);
2503
+ ResultFormatter.unknown(decodeResult, data[8]);
2504
+ }
2505
+ if (data.length === 14) {
2506
+ ResultFormatter.altitude(decodeResult, Number(data[3]) * 100);
2507
+ processRoute(decodeResult, data[4], data[2], data[6], data[5], void 0);
2508
+ ResultFormatter.temperature(decodeResult, data[7]);
2509
+ ResultFormatter.groundspeed(decodeResult, Number(data[10]));
2510
+ ResultFormatter.unknown(decodeResult, data[1]);
2511
+ ResultFormatter.unknown(decodeResult, data[8]);
2512
+ ResultFormatter.unknown(decodeResult, data[9]);
2513
+ ResultFormatter.unknown(decodeResult, data.slice(11).join(","));
2514
+ }
2515
+ }
2516
+ function processPosition2(decodeResult, data) {
2517
+ const position = CoordinateUtils.decodeStringCoordinatesDecimalMinutes(data[0]);
2518
+ if (position) {
2519
+ decodeResult.raw.position = position;
2520
+ decodeResult.formatted.items.push({
2521
+ type: "aircraft_position",
2522
+ code: "POS",
2523
+ label: "Aircraft Position",
2524
+ value: CoordinateUtils.coordinateString(position)
2525
+ });
2526
+ }
2527
+ if (data.length >= 10) {
2528
+ ResultFormatter.altitude(decodeResult, Number(data[3]) * 100);
2529
+ processRoute(decodeResult, data[1], data[2], data[4], data[5], data[6]);
2530
+ ResultFormatter.temperature(decodeResult, data[7]);
2531
+ ResultFormatter.unknown(decodeResult, data[8]);
2532
+ ResultFormatter.unknown(decodeResult, data[9]);
2533
+ }
2534
+ if (data.length >= 14) {
2535
+ ResultFormatter.groundspeed(decodeResult, Number(data[10]));
2536
+ ResultFormatter.unknown(decodeResult, data[11]);
2537
+ ResultFormatter.unknown(decodeResult, data[12]);
2538
+ ResultFormatter.unknown(decodeResult, data[13]);
2539
+ }
2540
+ }
2541
+ function processRoute(decodeResult, last, time, next, eta, then, date) {
2542
+ const lastTime = date ? DateTimeUtils.convertDateTimeToEpoch(time, date) : DateTimeUtils.convertHHMMSSToTod(time);
2543
+ const nextTime = date ? DateTimeUtils.convertDateTimeToEpoch(eta, date) : DateTimeUtils.convertHHMMSSToTod(eta);
2544
+ const timeFormat = date ? "epoch" : "tod";
2545
+ const lastWaypoint = RouteUtils.getWaypoint(last);
2546
+ lastWaypoint.time = lastTime;
2547
+ lastWaypoint.timeFormat = timeFormat;
2548
+ const nextWaypoint = RouteUtils.getWaypoint(next);
2549
+ nextWaypoint.time = nextTime;
2550
+ nextWaypoint.timeFormat = timeFormat;
2551
+ const thenWaypoint = RouteUtils.getWaypoint(then || "?");
2552
+ const waypoints = [lastWaypoint, nextWaypoint, thenWaypoint];
2553
+ decodeResult.raw.route = { waypoints };
2554
+ decodeResult.formatted.items.push({
2555
+ type: "aircraft_route",
2556
+ code: "ROUTE",
2557
+ label: "Aircraft Route",
2558
+ value: RouteUtils.routeToString(decodeResult.raw.route)
2559
+ });
2560
+ }
2561
+
2562
+ // lib/plugins/Label_4J_POS.ts
2563
+ var Label_4J_POS = class extends DecoderPlugin {
2564
+ name = "label-4j-pos";
2565
+ qualifiers() {
2566
+ return {
2567
+ labels: ["4J"],
2568
+ preambles: ["POS/"]
2475
2569
  };
2476
2570
  }
2571
+ // copied from Label_H1.ts since i don't really want to have to have
2572
+ // something named like that decode more than 1 type
2573
+ // if we figure out a good name, i'll combine them
2477
2574
  decode(message, options = {}) {
2478
- const decodeResult = this.defaultResult();
2575
+ let decodeResult = this.defaultResult();
2479
2576
  decodeResult.decoder.name = this.name;
2480
- decodeResult.formatted.description = "CPDLC Message";
2481
2577
  decodeResult.message = message;
2482
- if (options.debug) {
2483
- console.log("CPDLC: " + message);
2578
+ const msg = message.text.replace(/\n|\r/g, "");
2579
+ const decoded = H1Helper.decodeH1Message(decodeResult, msg);
2580
+ decodeResult.decoded = decoded;
2581
+ decodeResult.decoder.decodeLevel = !decodeResult.remaining.text ? "full" : "partial";
2582
+ if (decodeResult.formatted.items.length === 0) {
2583
+ if (options.debug) {
2584
+ console.log(`Decoder: Unknown H1 message: ${message.text}`);
2585
+ }
2586
+ ResultFormatter.unknown(decodeResult, message.text);
2587
+ decodeResult.decoded = false;
2588
+ decodeResult.decoder.decodeLevel = "none";
2484
2589
  }
2485
2590
  return decodeResult;
2486
2591
  }
2487
2592
  };
2488
2593
 
2489
- // lib/plugins/Label_ColonComma.ts
2490
- var Label_ColonComma = class extends DecoderPlugin {
2491
- name = "label-colon-comma";
2594
+ // lib/plugins/Label_4N.ts
2595
+ var Label_4N = class extends DecoderPlugin {
2596
+ name = "label-4n";
2492
2597
  qualifiers() {
2493
2598
  return {
2494
- labels: [":;"]
2599
+ labels: ["4N"]
2495
2600
  };
2496
2601
  }
2497
2602
  decode(message, options = {}) {
2498
2603
  const decodeResult = this.defaultResult();
2499
2604
  decodeResult.decoder.name = this.name;
2500
- decodeResult.raw.frequency = Number(message.text) / 1e3;
2501
- decodeResult.formatted.description = "Aircraft Transceiver Frequency Change";
2502
- decodeResult.formatted.items.push({
2503
- type: "frequency",
2504
- label: "Frequency",
2505
- value: `${decodeResult.raw.frequency} MHz`,
2506
- code: "FREQ"
2507
- });
2605
+ decodeResult.message = message;
2606
+ decodeResult.formatted.description = "Airline Defined";
2607
+ let text = message.text;
2608
+ if (text.match(/^M\d{2}A\w{6}/)) {
2609
+ ResultFormatter.flightNumber(decodeResult, message.text.substring(4, 10).replace(/^([A-Z]+)0*/g, "$1"));
2610
+ text = text.substring(10);
2611
+ }
2508
2612
  decodeResult.decoded = true;
2509
- decodeResult.decoder.decodeLevel = "full";
2510
- return decodeResult;
2511
- }
2512
- };
2513
-
2514
- // lib/utils/flight_plan_utils.ts
2515
- var FlightPlanUtils = class _FlightPlanUtils {
2516
- /**
2517
- * Processes flight plan data
2518
- *
2519
- * Expected format is [header, key1, val1, ... keyN, valN]
2520
- *
2521
- * @param decodeResult - results
2522
- * @param data - original message split by ':'
2523
- * @returns whether all fields were processed or not
2524
- */
2525
- static processFlightPlan(decodeResult, data) {
2526
- let allKnownFields = _FlightPlanUtils.parseHeader(decodeResult, data[0]);
2527
- for (let i = 1; i < data.length; i += 2) {
2528
- const key = data[i];
2529
- const value = data[i + 1];
2530
- switch (key) {
2531
- case "A":
2532
- addProcedure(decodeResult, value, "arrival");
2533
- break;
2534
- case "AA":
2535
- addArrivalAirport(decodeResult, value);
2536
- break;
2537
- case "AP":
2538
- addProcedure(decodeResult, value, "approach");
2539
- break;
2540
- case "CR":
2541
- addCompanyRoute(decodeResult, value);
2542
- break;
2543
- case "D":
2544
- addProcedure(decodeResult, value, "departure");
2545
- break;
2546
- case "DA":
2547
- addDepartureAirport(decodeResult, value);
2548
- break;
2549
- case "F":
2550
- addRoute(decodeResult, value);
2551
- break;
2552
- case "R":
2553
- addRunway(decodeResult, value);
2554
- break;
2555
- // case 'WS': // something about routes, has altitude, so current parsing won't work
2556
- // break;
2557
- default:
2558
- if (allKnownFields) {
2559
- decodeResult.remaining.text = "";
2560
- allKnownFields = false;
2561
- }
2562
- decodeResult.remaining.text += `:${key}:${value}`;
2563
- decodeResult.decoder.decodeLevel = "partial";
2613
+ const fields = text.split(",");
2614
+ if (text.length === 51) {
2615
+ decodeResult.raw.day_of_month = text.substring(0, 2);
2616
+ ResultFormatter.departureAirport(decodeResult, text.substring(8, 11));
2617
+ ResultFormatter.arrivalAirport(decodeResult, text.substring(13, 16));
2618
+ ResultFormatter.position(decodeResult, CoordinateUtils.decodeStringCoordinatesDecimalMinutes(text.substring(30, 45).replace(/^(.)0/, "$1")));
2619
+ ResultFormatter.altitude(decodeResult, Number(text.substring(48, 51)) * 100);
2620
+ ResultFormatter.unknownArr(decodeResult, [text.substring(2, 4), text.substring(19, 29)], " ");
2621
+ } else if (fields.length === 33) {
2622
+ decodeResult.raw.date = fields[3];
2623
+ if (fields[1] === "B") {
2624
+ ResultFormatter.position(decodeResult, { latitude: Number(fields[4]), longitude: Number(fields[5]) });
2625
+ ResultFormatter.altitude(decodeResult, Number(fields[6]));
2564
2626
  }
2565
- }
2566
- return allKnownFields;
2567
- }
2568
- static parseHeader(decodeResult, header) {
2569
- let allKnownFields = true;
2570
- if (header.startsWith("RF")) {
2571
- decodeResult.formatted.items.push({
2572
- type: "status",
2573
- code: "ROUTE_STATUS",
2574
- label: "Route Status",
2575
- value: "Route Filed"
2576
- });
2577
- decodeResult.raw.route_status = "RF";
2578
- if (header.length > 2) {
2579
- addRoute(decodeResult, header.substring(2));
2627
+ ResultFormatter.departureAirport(decodeResult, fields[8]);
2628
+ ResultFormatter.arrivalAirport(decodeResult, fields[9]);
2629
+ ResultFormatter.alternateAirport(decodeResult, fields[10]);
2630
+ ResultFormatter.arrivalRunway(decodeResult, fields[11].split("/")[0]);
2631
+ if (fields[12].length > 1) {
2632
+ ResultFormatter.alternateRunway(decodeResult, fields[12].split("/")[0]);
2580
2633
  }
2581
- } else if (header.startsWith("RP")) {
2582
- decodeResult.raw.route_status = "RP";
2583
- decodeResult.formatted.items.push({
2584
- type: "status",
2585
- code: "ROUTE_STATUS",
2586
- label: "Route Status",
2587
- value: "Route Planned"
2588
- });
2589
- decodeResult.raw.route_status = header;
2590
- } else if (header.startsWith("RI")) {
2591
- decodeResult.raw.route_status = "RI";
2592
- decodeResult.formatted.items.push({
2593
- type: "status",
2594
- code: "ROUTE_STATUS",
2595
- label: "Route Status",
2596
- value: "Route Inactive"
2597
- });
2634
+ ResultFormatter.checksum(decodeResult, fields[32]);
2635
+ ResultFormatter.unknownArr(decodeResult, [...fields.slice(1, 3), fields[7], ...fields.slice(13, 32)].filter((f) => f != ""));
2598
2636
  } else {
2599
- decodeResult.remaining.text += header;
2600
- allKnownFields = false;
2601
- }
2602
- return allKnownFields;
2603
- }
2604
- };
2605
- function addArrivalAirport(decodeResult, value) {
2606
- ResultFormatter.arrivalAirport(decodeResult, value);
2607
- }
2608
- function addDepartureAirport(decodeResult, value) {
2609
- ResultFormatter.departureAirport(decodeResult, value);
2610
- }
2611
- function addRunway(decodeResult, value) {
2612
- if (value.length === 8) {
2613
- ResultFormatter.arrivalRunway(decodeResult, value.substring(4, 7));
2614
- }
2615
- ResultFormatter.departureRunway(decodeResult, value.substring(0, 3));
2616
- }
2617
- function addRoute(decodeResult, value) {
2618
- const route = value.split(".");
2619
- ResultFormatter.route(decodeResult, { waypoints: route.map((leg) => RouteUtils.getWaypoint(leg)) });
2620
- }
2621
- function addProcedure(decodeResult, value, type) {
2622
- if (decodeResult.raw.procedures === void 0) {
2623
- decodeResult.raw.procedures = [];
2624
- }
2625
- const data = value.split(".");
2626
- let waypoints;
2627
- if (data.length > 1) {
2628
- waypoints = data.slice(1).map((leg) => RouteUtils.getWaypoint(leg));
2629
- }
2630
- const route = { name: data[0], waypoints };
2631
- decodeResult.raw.procedures.push({ type, route });
2632
- const procedureName = type.substring(0, 1).toUpperCase() + type.slice(1);
2633
- let procedureValue = route.name;
2634
- decodeResult.formatted.items.push({
2635
- type: `procedure`,
2636
- code: "proc",
2637
- label: `${procedureName} Procedure`,
2638
- value: RouteUtils.routeToString(route)
2639
- });
2640
- }
2641
- function addCompanyRoute(decodeResult, value) {
2642
- const segments = value.split(".");
2643
- const parens_idx = segments[0].indexOf("(");
2644
- let name;
2645
- let runway;
2646
- if (parens_idx === -1) {
2647
- name = segments[0];
2648
- } else {
2649
- name = segments[0].slice(0, parens_idx);
2650
- runway = segments[0].slice(parens_idx + 1, segments[0].indexOf(")"));
2651
- }
2652
- let waypoints;
2653
- if (segments.length > 1) {
2654
- waypoints = segments.slice(1).map((leg) => RouteUtils.getWaypoint(leg));
2655
- }
2656
- decodeResult.raw.company_route = {
2657
- name,
2658
- runway,
2659
- waypoints
2660
- };
2661
- decodeResult.formatted.items.push({
2662
- type: "company_route",
2663
- code: "CR",
2664
- label: "Company Route",
2665
- value: RouteUtils.routeToString(decodeResult.raw.company_route)
2666
- });
2667
- }
2668
-
2669
- // lib/utils/h1_helper.ts
2670
- var H1Helper = class {
2671
- static decodeH1Message(decodeResult, message) {
2672
- const checksum = message.slice(-4);
2673
- const data = message.slice(0, message.length - 4);
2674
- const fields = data.split("/");
2675
- parseMessageType(decodeResult, fields[0]);
2676
- for (let i = 1; i < fields.length; ++i) {
2677
- if (fields[i].startsWith("FN")) {
2678
- decodeResult.raw.flight_number = fields[i].substring(2);
2679
- } else if (fields[i].startsWith("SN")) {
2680
- decodeResult.raw.serial_number = fields[i].substring(2);
2681
- } else if (fields[i].startsWith("DC")) {
2682
- processDC(decodeResult, fields[i].substring(2).split(","));
2683
- } else if (fields[i].startsWith("TS")) {
2684
- const ts = fields[i].substring(2).split(",");
2685
- let time = DateTimeUtils.convertDateTimeToEpoch(ts[0], ts[1]);
2686
- if (Number.isNaN(time)) {
2687
- const date = ts[1].substring(2, 4) + ts[1].substring(0, 2) + ts[1].substring(4, 6);
2688
- time = DateTimeUtils.convertDateTimeToEpoch(ts[0], date);
2689
- }
2690
- decodeResult.raw.message_date = ts[1];
2691
- decodeResult.raw.message_timestamp = time;
2692
- } else if (fields[i].startsWith("PS")) {
2693
- const pos = processPS(decodeResult, fields[i].substring(2).split(","));
2694
- } else if (fields[i].startsWith("DT")) {
2695
- const data2 = fields[i].substring(2).split(",");
2696
- processDT(decodeResult, data2);
2697
- } else if (fields[i].startsWith("ID")) {
2698
- processIdentification(decodeResult, fields[i].substring(2).split(","));
2699
- } else if (fields[i].startsWith("LR")) {
2700
- const data2 = fields[i].substring(2).split(",");
2701
- processLR(decodeResult, data2);
2702
- } else if (fields[i].startsWith("RI") || fields[i].startsWith("RF") || fields[i].startsWith("RP")) {
2703
- FlightPlanUtils.processFlightPlan(decodeResult, fields[i].split(":"));
2704
- } else if (fields[i].startsWith("PR")) {
2705
- ResultFormatter.unknown(decodeResult, fields[i], "/");
2706
- } else if (fields[i].startsWith("AF")) {
2707
- processAirField(decodeResult, fields[i].substring(2).split(","));
2708
- } else if (fields[i].startsWith("TD")) {
2709
- processTimeOfDeparture(decodeResult, fields[i].substring(2).split(","));
2710
- } else if (fields[i].startsWith("FX")) {
2711
- ResultFormatter.freetext(decodeResult, fields[i].substring(2));
2712
- } else if (fields[i].startsWith("ET")) {
2713
- if (fields[i].length === 7) {
2714
- ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[i].substring(3) + "00"));
2715
- } else if (fields[i].length === 8) {
2716
- ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[i].substring(4) + "00"));
2717
- } else {
2718
- ResultFormatter.unknown(decodeResult, fields[i], "/");
2719
- }
2720
- } else {
2721
- ResultFormatter.unknown(decodeResult, fields[i], "/");
2722
- }
2637
+ decodeResult.decoded = false;
2638
+ ResultFormatter.unknown(decodeResult, text);
2723
2639
  }
2724
- if (decodeResult.formatted.items.length > 0) {
2725
- ResultFormatter.checksum(decodeResult, checksum);
2640
+ if (decodeResult.decoded) {
2641
+ if (!decodeResult.remaining.text)
2642
+ decodeResult.decoder.decodeLevel = "full";
2643
+ else
2644
+ decodeResult.decoder.decodeLevel = "partial";
2645
+ } else {
2646
+ decodeResult.decoder.decodeLevel = "none";
2726
2647
  }
2727
- return true;
2648
+ return decodeResult;
2728
2649
  }
2729
2650
  };
2730
- function processAirField(decodeResult, data) {
2731
- if (data.length === 2) {
2732
- ResultFormatter.departureAirport(decodeResult, data[0]);
2733
- ResultFormatter.arrivalAirport(decodeResult, data[1]);
2734
- } else {
2735
- ResultFormatter.unknown(decodeResult, data.join(","), "AF/");
2736
- }
2737
- }
2738
- function processTimeOfDeparture(decodeResult, data) {
2739
- if (data.length === 2) {
2740
- decodeResult.raw.plannedDepartureTime = data[0];
2741
- decodeResult.formatted.items.push({
2742
- type: "ptd",
2743
- code: "ptd",
2744
- label: "Planned Departure Time",
2745
- value: `YYYY-MM-${data[0].substring(0, 2)}T${data[0].substring(2, 4)}:${data[0].substring(4)}:00Z`
2746
- });
2747
- decodeResult.raw.plannedDepartureTime = data[1];
2748
- decodeResult.formatted.items.push({
2749
- type: "etd",
2750
- code: "etd",
2751
- label: "Estimated Departure Time",
2752
- value: `${data[1].substring(0, 2)}:${data[1].substring(2)}`
2753
- });
2754
- } else {
2755
- ResultFormatter.unknown(decodeResult, data.join(","), "/TD");
2756
- }
2757
- }
2758
- function processIdentification(decodeResult, data) {
2759
- ResultFormatter.tail(decodeResult, data[0]);
2760
- if (data.length > 1) {
2761
- decodeResult.raw.flight_number = data[1];
2762
- }
2763
- if (data.length > 2) {
2764
- decodeResult.raw.mission_number = data[2];
2765
- }
2766
- }
2767
- function processDT(decodeResult, data) {
2768
- if (!decodeResult.raw.arrival_icao) {
2769
- ResultFormatter.arrivalAirport(decodeResult, data[0]);
2770
- } else if (decodeResult.raw.arrival_icao != data[0]) {
2771
- ResultFormatter.unknownArr(decodeResult, data);
2772
- }
2773
- if (data.length > 1) {
2774
- ResultFormatter.arrivalRunway(decodeResult, data[1]);
2775
- }
2776
- if (data.length > 2) {
2777
- ResultFormatter.currentFuel(decodeResult, Number(data[2]));
2778
- }
2779
- if (data.length > 3) {
2780
- ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(data[3]));
2781
- }
2782
- if (data.length > 4) {
2783
- ResultFormatter.remainingFuel(decodeResult, Number(data[4]));
2784
- }
2785
- if (data.length > 5) {
2786
- ResultFormatter.unknownArr(decodeResult, data);
2651
+
2652
+ // lib/plugins/Label_80.ts
2653
+ var Label_80 = class extends DecoderPlugin {
2654
+ name = "label-80";
2655
+ descriptions = {
2656
+ ALT: "Altitude",
2657
+ DWND: "Wind Direction",
2658
+ ETA: "Estimated Time of Arrival",
2659
+ FOB: "Fuel on Board",
2660
+ FL: "Flight Level",
2661
+ HDG: "Heading",
2662
+ MCH: "Aircraft Speed",
2663
+ NWYP: "Next Waypoint",
2664
+ POS: "Aircraft Position",
2665
+ SAT: "Static Air Temperature",
2666
+ SWND: "Wind Speed",
2667
+ TAS: "True Airspeed",
2668
+ WYP: "Waypoint"
2669
+ };
2670
+ qualifiers() {
2671
+ return {
2672
+ labels: ["80"],
2673
+ preambles: ["3N01 POSRPT"]
2674
+ };
2787
2675
  }
2788
- }
2789
- function processLR(decodeResult, data) {
2790
- if (data.length === 19) {
2791
- ResultFormatter.unknown(decodeResult, data[1]);
2792
- ResultFormatter.flightNumber(decodeResult, data[2]);
2793
- ResultFormatter.departureAirport(decodeResult, data[3]);
2794
- ResultFormatter.arrivalAirport(decodeResult, data[4]);
2795
- ResultFormatter.arrivalRunway(decodeResult, data[5]);
2796
- ResultFormatter.unknown(decodeResult, data.slice(6, 19).join(","));
2797
- } else {
2798
- ResultFormatter.unknown(decodeResult, data.join(","));
2676
+ decode(message, options = {}) {
2677
+ const decodeResult = this.defaultResult();
2678
+ decodeResult.decoder.name = this.name;
2679
+ decodeResult.formatted.description = "Airline Defined Position Report";
2680
+ const parts = message.text.split("\n");
2681
+ let posRptRegex = /^3N01 POSRPT \d\d\d\d\/\d\d (?<orig>\w+)\/(?<dest>\w+) \.(?<tail>[\w-]+)(\/(?<agate>.+) (?<sta>\w+:\w+))*/;
2682
+ let results = parts[0].match(posRptRegex);
2683
+ if (!results?.groups) {
2684
+ decodeResult.decoded = false;
2685
+ decodeResult.decoder.decodeLevel = "none";
2686
+ return decodeResult;
2687
+ }
2688
+ if (results && results.length > 0) {
2689
+ ResultFormatter.departureAirport(decodeResult, results.groups.orig);
2690
+ ResultFormatter.arrivalAirport(decodeResult, results.groups.dest);
2691
+ ResultFormatter.tail(decodeResult, results.groups.tail);
2692
+ if (results.groups.agate) {
2693
+ decodeResult.raw.arrival_gate = results.groups.agate;
2694
+ decodeResult.formatted.items.push({
2695
+ type: "arrival_gate",
2696
+ code: "ARG",
2697
+ label: "Arrival Gate",
2698
+ value: `${results.groups.agate}`
2699
+ });
2700
+ decodeResult.raw.scheduled_time_of_arrival = results.groups.sta;
2701
+ decodeResult.formatted.items.push({
2702
+ type: "scheduled_time_of_arrival",
2703
+ code: "STA",
2704
+ label: "Scheduled Time of Arrival",
2705
+ value: `${results.groups.sta}`
2706
+ });
2707
+ }
2708
+ posRptRegex = /\/(?<field>\w+)\s(?<value>[\w\+\-:\.]+)\s*/gi;
2709
+ const remainingParts = parts.slice(1);
2710
+ for (const part of remainingParts) {
2711
+ const matches = part.matchAll(posRptRegex);
2712
+ for (const match of matches) {
2713
+ switch (match.groups?.field) {
2714
+ case "ALT": {
2715
+ ResultFormatter.altitude(decodeResult, Number(match.groups.value));
2716
+ break;
2717
+ }
2718
+ case "DWND": {
2719
+ decodeResult.raw.wind_direction = Number(match.groups.value);
2720
+ decodeResult.formatted.items.push({
2721
+ type: "wind_direction",
2722
+ code: "DWND",
2723
+ label: this.descriptions[match.groups.field],
2724
+ value: decodeResult.raw.wind_direction
2725
+ });
2726
+ break;
2727
+ }
2728
+ case "FL": {
2729
+ const flight_level = Number(match.groups.value);
2730
+ ResultFormatter.altitude(decodeResult, flight_level * 100);
2731
+ break;
2732
+ }
2733
+ case "FOB": {
2734
+ const fob = Number(match.groups.value);
2735
+ if (!isNaN(fob)) {
2736
+ ResultFormatter.currentFuel(decodeResult, fob);
2737
+ }
2738
+ break;
2739
+ }
2740
+ case "HDG": {
2741
+ ResultFormatter.heading(decodeResult, Number(match.groups.value));
2742
+ break;
2743
+ }
2744
+ case "MCH": {
2745
+ decodeResult.raw.mach = Number(match.groups.value) / 1e3;
2746
+ decodeResult.formatted.items.push({
2747
+ type: "mach",
2748
+ code: "MCH",
2749
+ label: this.descriptions[match.groups.field],
2750
+ value: `${decodeResult.raw.mach} Mach`
2751
+ });
2752
+ break;
2753
+ }
2754
+ case "NWYP": {
2755
+ decodeResult.raw.next_waypoint = match.groups.value;
2756
+ decodeResult.formatted.items.push({
2757
+ type: "next_waypoint",
2758
+ code: "NWYP",
2759
+ label: this.descriptions[match.groups.field],
2760
+ value: decodeResult.raw.next_waypoint
2761
+ });
2762
+ break;
2763
+ }
2764
+ case "POS": {
2765
+ const posRegex = /^(?<latd>[NS])(?<lat>.+)(?<lngd>[EW])(?<lng>.+)/;
2766
+ const posResult = match.groups.value.match(posRegex);
2767
+ const lat = Number(posResult?.groups?.lat) * (posResult?.groups?.lngd === "S" ? -1 : 1);
2768
+ const lon = Number(posResult?.groups?.lng) * (posResult?.groups?.lngd === "W" ? -1 : 1);
2769
+ const position = {
2770
+ latitude: Number.isInteger(lat) ? lat / 1e3 : lat / 100,
2771
+ longitude: Number.isInteger(lon) ? lon / 1e3 : lon / 100
2772
+ };
2773
+ ResultFormatter.position(decodeResult, position);
2774
+ break;
2775
+ }
2776
+ case "SWND": {
2777
+ decodeResult.raw.wind_speed = Number(match.groups.value);
2778
+ decodeResult.formatted.items.push({
2779
+ type: "wind_speed",
2780
+ code: "SWND",
2781
+ label: this.descriptions[match.groups.field],
2782
+ value: decodeResult.raw.wind_speed
2783
+ });
2784
+ break;
2785
+ }
2786
+ default: {
2787
+ if (match.groups?.field != void 0) {
2788
+ const description = this.descriptions[match.groups.field] ? this.descriptions[match.groups.field] : "Unknown";
2789
+ decodeResult.formatted.items.push({
2790
+ type: match.groups.field,
2791
+ code: match.groups.field,
2792
+ label: description || `Unknown (${match.groups.field})`,
2793
+ value: `${match.groups.value}`
2794
+ });
2795
+ }
2796
+ }
2797
+ }
2798
+ }
2799
+ }
2800
+ decodeResult.decoded = true;
2801
+ decodeResult.decoder.decodeLevel = "partial";
2802
+ }
2803
+ return decodeResult;
2799
2804
  }
2800
- }
2801
- function parseMessageType(decodeResult, messageType) {
2802
- const parts = messageType.split("#");
2803
- if (parts.length == 1) {
2804
- const type = parts[0].substring(0, 3);
2805
- if (type === "POS" && parts[0].length !== 3) {
2806
- processPosition2(decodeResult, parts[0].substring(3).split(","));
2805
+ };
2806
+
2807
+ // lib/plugins/Label_83.ts
2808
+ var Label_83 = class extends DecoderPlugin {
2809
+ name = "label-83";
2810
+ qualifiers() {
2811
+ return {
2812
+ labels: ["83"]
2813
+ };
2814
+ }
2815
+ decode(message, options = {}) {
2816
+ const decodeResult = this.defaultResult();
2817
+ decodeResult.decoder.name = this.name;
2818
+ decodeResult.message = message;
2819
+ decodeResult.formatted.description = "Airline Defined";
2820
+ let text = message.text;
2821
+ if (text.match(/^M\d{2}A\w{6}/)) {
2822
+ ResultFormatter.flightNumber(decodeResult, message.text.substring(4, 10).replace(/^([A-Z]+)0*/g, "$1"));
2823
+ text = text.substring(10);
2807
2824
  }
2808
- return processMessageType(decodeResult, type);
2809
- } else if (parts.length == 2) {
2810
- if (parts[0].length > 0) {
2811
- ResultFormatter.unknown(decodeResult, parts[0].substring(0, 3));
2812
- decodeResult.raw.flight_number = parts[0].substring(3);
2813
- ResultFormatter.unknown(decodeResult, parts[1].length == 5 ? parts[1].substring(0, 2) : parts[1].substring(0, 3), "#");
2825
+ decodeResult.decoded = true;
2826
+ if (text.substring(0, 10) === "4DH3 ETAT2") {
2827
+ const fields = text.split(/\s+/);
2828
+ if (fields[2].length > 5) {
2829
+ decodeResult.raw.day_of_month = fields[2].substring(5);
2830
+ }
2831
+ ResultFormatter.unknown(decodeResult, fields[2].substring(0, 4));
2832
+ const subfields = fields[3].split("/");
2833
+ ResultFormatter.departureAirport(decodeResult, subfields[0]);
2834
+ ResultFormatter.arrivalAirport(decodeResult, subfields[1]);
2835
+ ResultFormatter.tail(decodeResult, fields[4].replace(/\./g, ""));
2836
+ ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[6] + "00"));
2837
+ } else if (text.substring(0, 5) === "001PR") {
2838
+ decodeResult.raw.day_of_month = text.substring(5, 7);
2839
+ const position = CoordinateUtils.decodeStringCoordinatesDecimalMinutes(text.substring(13, 28).replace(/\./g, ""));
2840
+ if (position) {
2841
+ ResultFormatter.position(decodeResult, position);
2842
+ }
2843
+ ResultFormatter.altitude(decodeResult, Number(text.substring(28, 33)));
2844
+ ResultFormatter.unknown(decodeResult, text.substring(33));
2845
+ } else {
2846
+ const fields = text.replace(/\s/g, "").split(",");
2847
+ if (fields.length === 9) {
2848
+ ResultFormatter.departureAirport(decodeResult, fields[0]);
2849
+ ResultFormatter.arrivalAirport(decodeResult, fields[1]);
2850
+ decodeResult.raw.day_of_month = fields[2].substring(0, 2);
2851
+ decodeResult.raw.time = fields[2].substring(2);
2852
+ ResultFormatter.position(
2853
+ decodeResult,
2854
+ {
2855
+ latitude: Number(fields[3].replace(/\s/g, "")),
2856
+ longitude: Number(fields[4].replace(/\s/g, ""))
2857
+ }
2858
+ );
2859
+ ResultFormatter.altitude(decodeResult, Number(fields[5]));
2860
+ ResultFormatter.groundspeed(decodeResult, Number(fields[6]));
2861
+ ResultFormatter.heading(decodeResult, Number(fields[7]));
2862
+ ResultFormatter.unknown(decodeResult, fields[8]);
2863
+ } else {
2864
+ decodeResult.decoded = false;
2865
+ ResultFormatter.unknown(decodeResult, message.text);
2866
+ }
2814
2867
  }
2815
- const type = parts[1].length == 5 ? parts[1].substring(2, 5) : parts[1].substring(3, 6);
2816
- if (parts[1].substring(3, 6) === "POS" && parts[1].length > 6) {
2817
- processPosition2(decodeResult, parts[1].substring(6).split(","));
2868
+ if (decodeResult.decoded) {
2869
+ if (!decodeResult.remaining.text)
2870
+ decodeResult.decoder.decodeLevel = "full";
2871
+ else
2872
+ decodeResult.decoder.decodeLevel = "partial";
2873
+ } else {
2874
+ decodeResult.decoder.decodeLevel = "none";
2818
2875
  }
2819
- processMessageType(decodeResult, type);
2820
- } else {
2821
- ResultFormatter.unknown(decodeResult, messageType);
2876
+ return decodeResult;
2822
2877
  }
2823
- }
2824
- function processMessageType(decodeResult, type) {
2825
- if (type === "FPN") {
2826
- decodeResult.formatted.description = "Flight Plan";
2827
- } else if (type === "FTX") {
2828
- decodeResult.formatted.description = "Free Text";
2829
- } else if (type === "INI") {
2830
- decodeResult.formatted.description = "Flight Plan Initial Report";
2831
- } else if (type === "POS") {
2832
- decodeResult.formatted.description = "Position Report";
2833
- } else if (type === "PRG") {
2834
- decodeResult.formatted.description = "Progress Report";
2835
- } else {
2836
- decodeResult.formatted.description = "Unknown H1 Message";
2878
+ };
2879
+
2880
+ // lib/plugins/Label_8E.ts
2881
+ var Label_8E = class extends DecoderPlugin {
2882
+ name = "label-8e";
2883
+ qualifiers() {
2884
+ return {
2885
+ labels: ["8E"]
2886
+ };
2837
2887
  }
2838
- }
2839
- function processDC(decodeResult, data) {
2840
- decodeResult.raw.message_date = data[0];
2841
- if (data.length === 1) {
2842
- } else if (data.length === 2) {
2843
- const date = data[0].substring(2, 4) + data[0].substring(0, 2) + data[0].substring(4, 6);
2844
- const time = DateTimeUtils.convertDateTimeToEpoch(data[1], data[0]);
2845
- decodeResult.raw.message_timestamp = time;
2888
+ decode(message, options = {}) {
2889
+ const decodeResult = this.defaultResult();
2890
+ decodeResult.decoder.name = this.name;
2891
+ decodeResult.formatted.description = "ETA Report";
2892
+ decodeResult.message = message;
2893
+ const regex = /^(?<arrival_icao>\w{4}),(?<arrival_eta>\d{4})$/;
2894
+ const results = message.text.match(regex);
2895
+ if (results?.groups) {
2896
+ if (options.debug) {
2897
+ console.log(`Label 8E ETA: groups`);
2898
+ console.log(results.groups);
2899
+ }
2900
+ ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(results.groups.arrival_eta + "00"));
2901
+ ResultFormatter.arrivalAirport(decodeResult, results.groups.arrival_icao);
2902
+ }
2903
+ decodeResult.decoded = true;
2904
+ decodeResult.decoder.decodeLevel = "full";
2905
+ return decodeResult;
2846
2906
  }
2847
- }
2848
- function processPS(decodeResult, data) {
2849
- const position = CoordinateUtils.decodeStringCoordinatesDecimalMinutes(data[0]);
2850
- if (position) {
2851
- decodeResult.raw.position = position;
2852
- decodeResult.formatted.items.push({
2853
- type: "aircraft_position",
2854
- code: "POS",
2855
- label: "Aircraft Position",
2856
- value: CoordinateUtils.coordinateString(position)
2857
- });
2907
+ };
2908
+
2909
+ // lib/plugins/Label_B6.ts
2910
+ var Label_B6_Forwardslash = class extends DecoderPlugin {
2911
+ name = "label-b6-forwardslash";
2912
+ qualifiers() {
2913
+ return {
2914
+ labels: ["B6"],
2915
+ preambles: ["/"]
2916
+ };
2858
2917
  }
2859
- if (data.length === 9) {
2860
- processRoute(decodeResult, data[3], data[1], data[5], data[4], void 0);
2861
- ResultFormatter.altitude(decodeResult, Number(data[2]) * 100);
2862
- ResultFormatter.temperature(decodeResult, data[6]);
2863
- ResultFormatter.unknown(decodeResult, data[7]);
2864
- ResultFormatter.unknown(decodeResult, data[8]);
2918
+ decode(message, options = {}) {
2919
+ const decodeResult = this.defaultResult();
2920
+ decodeResult.decoder.name = this.name;
2921
+ decodeResult.formatted.description = "CPDLC Message";
2922
+ decodeResult.message = message;
2923
+ if (options.debug) {
2924
+ console.log("CPDLC: " + message);
2925
+ }
2926
+ return decodeResult;
2865
2927
  }
2866
- if (data.length === 14) {
2867
- ResultFormatter.altitude(decodeResult, Number(data[3]) * 100);
2868
- processRoute(decodeResult, data[4], data[2], data[6], data[5], void 0);
2869
- ResultFormatter.temperature(decodeResult, data[7]);
2870
- ResultFormatter.groundspeed(decodeResult, Number(data[10]));
2871
- ResultFormatter.unknown(decodeResult, data[1]);
2872
- ResultFormatter.unknown(decodeResult, data[8]);
2873
- ResultFormatter.unknown(decodeResult, data[9]);
2874
- ResultFormatter.unknown(decodeResult, data.slice(11).join(","));
2928
+ };
2929
+
2930
+ // lib/plugins/Label_ColonComma.ts
2931
+ var Label_ColonComma = class extends DecoderPlugin {
2932
+ name = "label-colon-comma";
2933
+ qualifiers() {
2934
+ return {
2935
+ labels: [":;"]
2936
+ };
2875
2937
  }
2876
- }
2877
- function processPosition2(decodeResult, data) {
2878
- const position = CoordinateUtils.decodeStringCoordinatesDecimalMinutes(data[0]);
2879
- if (position) {
2880
- decodeResult.raw.position = position;
2938
+ decode(message, options = {}) {
2939
+ const decodeResult = this.defaultResult();
2940
+ decodeResult.decoder.name = this.name;
2941
+ decodeResult.raw.frequency = Number(message.text) / 1e3;
2942
+ decodeResult.formatted.description = "Aircraft Transceiver Frequency Change";
2881
2943
  decodeResult.formatted.items.push({
2882
- type: "aircraft_position",
2883
- code: "POS",
2884
- label: "Aircraft Position",
2885
- value: CoordinateUtils.coordinateString(position)
2944
+ type: "frequency",
2945
+ label: "Frequency",
2946
+ value: `${decodeResult.raw.frequency} MHz`,
2947
+ code: "FREQ"
2886
2948
  });
2949
+ decodeResult.decoded = true;
2950
+ decodeResult.decoder.decodeLevel = "full";
2951
+ return decodeResult;
2887
2952
  }
2888
- if (data.length >= 10) {
2889
- ResultFormatter.altitude(decodeResult, Number(data[3]) * 100);
2890
- processRoute(decodeResult, data[1], data[2], data[4], data[5], data[6]);
2891
- ResultFormatter.temperature(decodeResult, data[7]);
2892
- ResultFormatter.unknown(decodeResult, data[8]);
2893
- ResultFormatter.unknown(decodeResult, data[9]);
2894
- }
2895
- if (data.length >= 14) {
2896
- ResultFormatter.groundspeed(decodeResult, Number(data[10]));
2897
- ResultFormatter.unknown(decodeResult, data[11]);
2898
- ResultFormatter.unknown(decodeResult, data[12]);
2899
- ResultFormatter.unknown(decodeResult, data[13]);
2900
- }
2901
- }
2902
- function processRoute(decodeResult, last, time, next, eta, then, date) {
2903
- const lastTime = date ? DateTimeUtils.convertDateTimeToEpoch(time, date) : DateTimeUtils.convertHHMMSSToTod(time);
2904
- const nextTime = date ? DateTimeUtils.convertDateTimeToEpoch(eta, date) : DateTimeUtils.convertHHMMSSToTod(eta);
2905
- const timeFormat = date ? "epoch" : "tod";
2906
- const lastWaypoint = RouteUtils.getWaypoint(last);
2907
- lastWaypoint.time = lastTime;
2908
- lastWaypoint.timeFormat = timeFormat;
2909
- const nextWaypoint = RouteUtils.getWaypoint(next);
2910
- nextWaypoint.time = nextTime;
2911
- nextWaypoint.timeFormat = timeFormat;
2912
- const thenWaypoint = RouteUtils.getWaypoint(then || "?");
2913
- const waypoints = [lastWaypoint, nextWaypoint, thenWaypoint];
2914
- decodeResult.raw.route = { waypoints };
2915
- decodeResult.formatted.items.push({
2916
- type: "aircraft_route",
2917
- code: "ROUTE",
2918
- label: "Aircraft Route",
2919
- value: RouteUtils.routeToString(decodeResult.raw.route)
2920
- });
2921
- }
2953
+ };
2922
2954
 
2923
2955
  // lib/plugins/Label_H1.ts
2924
2956
  var Label_H1 = class extends DecoderPlugin {
@@ -4257,6 +4289,7 @@ var MessageDecoder = class {
4257
4289
  this.registerPlugin(new Label_4A_DIS(this));
4258
4290
  this.registerPlugin(new Label_4A_DOOR(this));
4259
4291
  this.registerPlugin(new Label_4A_Slash_01(this));
4292
+ this.registerPlugin(new Label_4J_POS(this));
4260
4293
  this.registerPlugin(new Label_4N(this));
4261
4294
  this.registerPlugin(new Label_B6_Forwardslash(this));
4262
4295
  this.registerPlugin(new Label_H1_FLR(this));