From 41aecca8adc89cab7d6310b1a6763cd5cc2d844e Mon Sep 17 00:00:00 2001 From: Mark Bumiller Date: Thu, 22 Jan 2026 19:48:39 -0500 Subject: [PATCH 1/4] Adding Label 44 /FB Parsing --- lib/MessageDecoder.ts | 3 +- lib/plugins/Label_44_Slash.test.ts | 63 ++++++++++++++++++++++++ lib/plugins/Label_44_Slash.ts | 78 ++++++++++++++++++++++++++++++ lib/plugins/official.ts | 1 + lib/utils/flight_plan_utils.ts | 49 ++++++++++--------- 5 files changed, 169 insertions(+), 25 deletions(-) create mode 100644 lib/plugins/Label_44_Slash.test.ts create mode 100644 lib/plugins/Label_44_Slash.ts diff --git a/lib/MessageDecoder.ts b/lib/MessageDecoder.ts index 8ff6dbb..85546ce 100644 --- a/lib/MessageDecoder.ts +++ b/lib/MessageDecoder.ts @@ -45,7 +45,8 @@ export class MessageDecoder { this.registerPlugin(new Plugins.Label_44_IN(this)); this.registerPlugin(new Plugins.Label_44_OFF(this)); this.registerPlugin(new Plugins.Label_44_ON(this)); - this.registerPlugin(new Plugins.Label_44_POS(this)); + this.registerPlugin(new Plugins.Label_44_POS(this)); + this.registerPlugin(new Plugins.Label_44_Slash(this)); this.registerPlugin(new Plugins.Label_4A(this)); this.registerPlugin(new Plugins.Label_4A_01(this)); this.registerPlugin(new Plugins.Label_4A_DIS(this)); diff --git a/lib/plugins/Label_44_Slash.test.ts b/lib/plugins/Label_44_Slash.test.ts new file mode 100644 index 0000000..eadbf38 --- /dev/null +++ b/lib/plugins/Label_44_Slash.test.ts @@ -0,0 +1,63 @@ +import { MessageDecoder } from "../MessageDecoder"; +import { Label_44_Slash } from "./Label_44_Slash"; + +describe("Label 44 Slash", () => { + let plugin: Label_44_Slash; + + beforeEach(() => { + const decoder = new MessageDecoder(); + plugin = new Label_44_Slash(decoder); + }); + + test("matches qualifiers", () => { + expect(plugin.decode).toBeDefined(); + expect(plugin.name).toBe("label-44-slash"); + expect(plugin.qualifiers).toBeDefined(); + expect(plugin.qualifiers()).toEqual({ + labels: ["44"], + preambles: [" /FB"], + }); + }); + + test("decodes variant 1", () => { + // https://app.airframes.io/messages/3563679058 + const text = " /FB 0160/AD KORH/N 38.655,W 75.325,JBU2834,INA03,KORH,2043"; + const decodeResult = plugin.decode({ text: text }); + expect(decodeResult.decoded).toBe(true); + expect(decodeResult.decoder.decodeLevel).toBe("partial"); + expect(decodeResult.raw.flight_number).toBe("JBU2834"); + expect(decodeResult.raw.arrival_icao).toBe("KORH"); + expect(decodeResult.raw.eta_time).toBe(74580); + expect(decodeResult.raw.position.latitude).toBeCloseTo(38.655, 3); + expect(decodeResult.raw.position.longitude).toBeCloseTo(-75.325, 3); + expect(decodeResult.formatted.items.length).toBe(4); + expect(decodeResult.remaining.text).toBe(" /FB 0160,INA03"); + }); + + test("decodes variant 2", () => { + const text = + " /FB ----/AD KTPA/N 27.971,W 82.558,JBU91,FLS03,KTPA,53988,53988,----,1943,1943,1,1L,VIS1L,0,2,0,,"; + const decodeResult = plugin.decode({ text: text }); + expect(decodeResult.decoded).toBe(true); + expect(decodeResult.decoder.decodeLevel).toBe("partial"); + expect(decodeResult.raw.flight_number).toBe("JBU91"); + expect(decodeResult.raw.arrival_icao).toBe("KTPA"); + expect(decodeResult.raw.eta_time).toBe(196688); + expect(decodeResult.raw.arrival_runway).toBe("1L"); + expect(decodeResult.raw.procedures[0].route.name).toBe("VIS1L"); + expect(decodeResult.raw.fuel_remaining).toBe(1943); + expect(decodeResult.raw.position.latitude).toBeCloseTo(27.971, 3); + expect(decodeResult.raw.position.longitude).toBeCloseTo(-82.558, 3); + expect(decodeResult.formatted.items.length).toBe(7); + expect(decodeResult.remaining.text).toBe(" /FB ----,FLS03,53988,----,1943,1,0,2,0,,"); + }); + + test("does not decode invalid", () => { + const text = "00OFF01 Bogus message"; + const decodeResult = plugin.decode({ text: text }); + + expect(decodeResult.decoded).toBe(false); + expect(decodeResult.decoder.decodeLevel).toBe("none"); + expect(decodeResult.message.text).toBe(text); + }); +}); diff --git a/lib/plugins/Label_44_Slash.ts b/lib/plugins/Label_44_Slash.ts new file mode 100644 index 0000000..7639b2c --- /dev/null +++ b/lib/plugins/Label_44_Slash.ts @@ -0,0 +1,78 @@ +import { DateTimeUtils } from '../DateTimeUtils'; +import { DecoderPlugin } from '../DecoderPlugin'; +import { DecodeResult, Message, Options } from '../DecoderPluginInterface'; +import { CoordinateUtils } from '../utils/coordinate_utils'; +import { FlightPlanUtils } from '../utils/flight_plan_utils'; +import { ResultFormatter } from '../utils/result_formatter'; + +// On Runway Report +export class Label_44_Slash extends DecoderPlugin { + name = 'label-44-slash'; + + qualifiers() { // eslint-disable-line class-methods-use-this + return { + labels: ['44'], + preambles: [' /FB'], + }; + } + + decode(message: Message, options: Options = {}): DecodeResult { + const decodeResult = this.defaultResult(); + decodeResult.decoder.name = this.name; + decodeResult.formatted.description = 'Flight Briefing'; + decodeResult.message = message; + + const fields = message.text.split('/'); + if(fields.length != 4) { + if (options.debug) { + console.log(`Decoder: Unknown 44 message: ${message.text}`); + } + ResultFormatter.unknown(decodeResult, message.text); + decodeResult.decoded = false; + decodeResult.decoder.decodeLevel = 'none'; + return decodeResult; + } + ResultFormatter.unknownArr(decodeResult, fields.slice(0,2), '/'); // 0 is a space + // 1 is the briefing id + // 2 is arrival airport, but repeated later + const data = fields[3].split(','); + if (data.length >= 6) { + if (options.debug) { + console.log(`Label 44 Slash: groups`); + console.log(data); + } + + ResultFormatter.position(decodeResult, { + latitude: ((data[0].charAt(0) === 'S') ? -1 : 1) * parseFloat(data[0].slice(1).trim()), + longitude: ((data[1].charAt(0) === 'W') ? -1 : 1) * parseFloat(data[1].slice(1).trim())}); + ResultFormatter.flightNumber(decodeResult, data[2]); + ResultFormatter.unknown(decodeResult, data[3]); // Status - need more info to decode + ResultFormatter.arrivalAirport(decodeResult, data[4]); + ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(data[5])); + if (data.length === 18) { + ResultFormatter.unknownArr(decodeResult, data.slice(6,8), ','); // 6 is repeated arrival airport + ResultFormatter.remainingFuel(decodeResult, parseFloat(data[8])); + ResultFormatter.unknownArr(decodeResult, data.slice(9,11), ','); + ResultFormatter.arrivalRunway(decodeResult, data[11]); + FlightPlanUtils.addProcedure(decodeResult ,data[12], 'arrival'); + ResultFormatter.unknownArr(decodeResult, data.slice(13,18), ','); + } + + } else { + if (options.debug) { + console.log(`Decoder: Unknown 44 message: ${message.text}`); + } + ResultFormatter.unknown(decodeResult, message.text); + decodeResult.decoded = false; + decodeResult.decoder.decodeLevel = 'none'; + return decodeResult; + } + + decodeResult.decoded = true; + decodeResult.decoder.decodeLevel = 'partial'; + + return decodeResult; + } +} + +export default {}; diff --git a/lib/plugins/official.ts b/lib/plugins/official.ts index cbd5e7a..acddf44 100644 --- a/lib/plugins/official.ts +++ b/lib/plugins/official.ts @@ -31,6 +31,7 @@ export * from './Label_44_IN'; export * from './Label_44_OFF'; export * from './Label_44_ON'; export * from './Label_44_POS'; +export * from './Label_44_Slash'; export * from './Label_4A'; export * from './Label_4A_01'; export * from './Label_4A_DIS'; diff --git a/lib/utils/flight_plan_utils.ts b/lib/utils/flight_plan_utils.ts index c0ac794..79075b9 100644 --- a/lib/utils/flight_plan_utils.ts +++ b/lib/utils/flight_plan_utils.ts @@ -20,19 +20,19 @@ export class FlightPlanUtils { // TODO: discuss how store commented out bits as both raw and formatted switch (key) { case 'A': // Arrival Procedure (?) - addProcedure(decodeResult, value, 'arrival'); + FlightPlanUtils.addProcedure(decodeResult, value, 'arrival'); break; case 'AA': addArrivalAirport(decodeResult, value); break; case 'AP': - addProcedure(decodeResult, value, 'approach'); + FlightPlanUtils.addProcedure(decodeResult, value, 'approach'); break; case 'CR': addCompanyRoute(decodeResult, value); break; case 'D': // Departure Procedure - addProcedure(decodeResult, value, 'departure'); + FlightPlanUtils.addProcedure(decodeResult, value, 'departure'); break; case 'DA': addDepartureAirport(decodeResult, value); @@ -92,6 +92,28 @@ export class FlightPlanUtils { } return allKnownFields; }; + + +public static addProcedure(decodeResult: DecodeResult, value: string, type: string) { + if (decodeResult.raw.procedures === undefined) { + decodeResult.raw.procedures = []; + } + const data = value.split('.'); + let waypoints; + if (data.length > 1) { + waypoints = data.slice(1).map((leg) => RouteUtils.getWaypoint(leg)); + } + const route = { name: data[0], waypoints: waypoints }; + decodeResult.raw.procedures.push({ type: type, route: route }); + const procedureName = type.substring(0, 1).toUpperCase() + type.slice(1); + let procedureValue = route.name; + decodeResult.formatted.items.push({ + type: `procedure`, + code: 'proc', + label: `${procedureName} Procedure`, + value: RouteUtils.routeToString(route), + }); +}; } function addArrivalAirport(decodeResult: DecodeResult, value: string) { @@ -116,27 +138,6 @@ function addRoute(decodeResult: DecodeResult, value: string) { ResultFormatter.route(decodeResult, { waypoints: route.map((leg) => RouteUtils.getWaypoint(leg)) }); }; -function addProcedure(decodeResult: DecodeResult, value: string, type: string) { - if (decodeResult.raw.procedures === undefined) { - decodeResult.raw.procedures = []; - } - const data = value.split('.'); - let waypoints; - if (data.length > 1) { - waypoints = data.slice(1).map((leg) => RouteUtils.getWaypoint(leg)); - } - const route = { name: data[0], waypoints: waypoints }; - decodeResult.raw.procedures.push({ type: type, route: route }); - const procedureName = type.substring(0, 1).toUpperCase() + type.slice(1); - let procedureValue = route.name; - decodeResult.formatted.items.push({ - type: `procedure`, - code: 'proc', - label: `${procedureName} Procedure`, - value: RouteUtils.routeToString(route), - }); -}; - function addCompanyRoute(decodeResult: DecodeResult, value: string) { const segments = value.split('.'); const parens_idx = segments[0].indexOf('('); From 82cec0177d6fd1450b3458f1bf764fc99f8f44f8 Mon Sep 17 00:00:00 2001 From: Mark Bumiller Date: Thu, 22 Jan 2026 19:51:52 -0500 Subject: [PATCH 2/4] PR feedback --- lib/plugins/Label_44_Slash.ts | 64 +++++++++++++++++++--------------- lib/utils/flight_plan_utils.ts | 1 - 2 files changed, 36 insertions(+), 29 deletions(-) diff --git a/lib/plugins/Label_44_Slash.ts b/lib/plugins/Label_44_Slash.ts index 7639b2c..a3fd932 100644 --- a/lib/plugins/Label_44_Slash.ts +++ b/lib/plugins/Label_44_Slash.ts @@ -1,41 +1,42 @@ -import { DateTimeUtils } from '../DateTimeUtils'; -import { DecoderPlugin } from '../DecoderPlugin'; -import { DecodeResult, Message, Options } from '../DecoderPluginInterface'; -import { CoordinateUtils } from '../utils/coordinate_utils'; -import { FlightPlanUtils } from '../utils/flight_plan_utils'; -import { ResultFormatter } from '../utils/result_formatter'; +import { DateTimeUtils } from "../DateTimeUtils"; +import { DecoderPlugin } from "../DecoderPlugin"; +import { DecodeResult, Message, Options } from "../DecoderPluginInterface"; +import { CoordinateUtils } from "../utils/coordinate_utils"; +import { FlightPlanUtils } from "../utils/flight_plan_utils"; +import { ResultFormatter } from "../utils/result_formatter"; // On Runway Report export class Label_44_Slash extends DecoderPlugin { - name = 'label-44-slash'; + name = "label-44-slash"; - qualifiers() { // eslint-disable-line class-methods-use-this + qualifiers() { + // eslint-disable-line class-methods-use-this return { - labels: ['44'], - preambles: [' /FB'], + labels: ["44"], + preambles: [" /FB"], }; } decode(message: Message, options: Options = {}): DecodeResult { const decodeResult = this.defaultResult(); decodeResult.decoder.name = this.name; - decodeResult.formatted.description = 'Flight Briefing'; + decodeResult.formatted.description = "Flight Briefing"; decodeResult.message = message; - const fields = message.text.split('/'); - if(fields.length != 4) { + const fields = message.text.split("/"); + if (fields.length !== 4) { if (options.debug) { console.log(`Decoder: Unknown 44 message: ${message.text}`); } ResultFormatter.unknown(decodeResult, message.text); decodeResult.decoded = false; - decodeResult.decoder.decodeLevel = 'none'; + decodeResult.decoder.decodeLevel = "none"; return decodeResult; } - ResultFormatter.unknownArr(decodeResult, fields.slice(0,2), '/'); // 0 is a space - // 1 is the briefing id - // 2 is arrival airport, but repeated later - const data = fields[3].split(','); + ResultFormatter.unknownArr(decodeResult, fields.slice(0, 2), "/"); // 0 is a space + // 1 is the briefing id + // 2 is arrival airport, but repeated later + const data = fields[3].split(","); if (data.length >= 6) { if (options.debug) { console.log(`Label 44 Slash: groups`); @@ -43,33 +44,40 @@ export class Label_44_Slash extends DecoderPlugin { } ResultFormatter.position(decodeResult, { - latitude: ((data[0].charAt(0) === 'S') ? -1 : 1) * parseFloat(data[0].slice(1).trim()), - longitude: ((data[1].charAt(0) === 'W') ? -1 : 1) * parseFloat(data[1].slice(1).trim())}); + latitude: + (data[0].charAt(0) === "S" ? -1 : 1) * + parseFloat(data[0].slice(1).trim()), + longitude: + (data[1].charAt(0) === "W" ? -1 : 1) * + parseFloat(data[1].slice(1).trim()), + }); ResultFormatter.flightNumber(decodeResult, data[2]); ResultFormatter.unknown(decodeResult, data[3]); // Status - need more info to decode ResultFormatter.arrivalAirport(decodeResult, data[4]); - ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(data[5])); + ResultFormatter.eta( + decodeResult, + DateTimeUtils.convertHHMMSSToTod(data[5]), + ); if (data.length === 18) { - ResultFormatter.unknownArr(decodeResult, data.slice(6,8), ','); // 6 is repeated arrival airport + ResultFormatter.unknownArr(decodeResult, data.slice(6, 8), ","); // 6 is repeated arrival airport ResultFormatter.remainingFuel(decodeResult, parseFloat(data[8])); - ResultFormatter.unknownArr(decodeResult, data.slice(9,11), ','); + ResultFormatter.unknownArr(decodeResult, data.slice(9, 11), ","); ResultFormatter.arrivalRunway(decodeResult, data[11]); - FlightPlanUtils.addProcedure(decodeResult ,data[12], 'arrival'); - ResultFormatter.unknownArr(decodeResult, data.slice(13,18), ','); + FlightPlanUtils.addProcedure(decodeResult, data[12], "arrival"); + ResultFormatter.unknownArr(decodeResult, data.slice(13, 18), ","); } - } else { if (options.debug) { console.log(`Decoder: Unknown 44 message: ${message.text}`); } ResultFormatter.unknown(decodeResult, message.text); decodeResult.decoded = false; - decodeResult.decoder.decodeLevel = 'none'; + decodeResult.decoder.decodeLevel = "none"; return decodeResult; } decodeResult.decoded = true; - decodeResult.decoder.decodeLevel = 'partial'; + decodeResult.decoder.decodeLevel = "partial"; return decodeResult; } diff --git a/lib/utils/flight_plan_utils.ts b/lib/utils/flight_plan_utils.ts index 79075b9..0538248 100644 --- a/lib/utils/flight_plan_utils.ts +++ b/lib/utils/flight_plan_utils.ts @@ -106,7 +106,6 @@ public static addProcedure(decodeResult: DecodeResult, value: string, type: stri const route = { name: data[0], waypoints: waypoints }; decodeResult.raw.procedures.push({ type: type, route: route }); const procedureName = type.substring(0, 1).toUpperCase() + type.slice(1); - let procedureValue = route.name; decodeResult.formatted.items.push({ type: `procedure`, code: 'proc', From 73a1d94b180addb35bb888bd08625810f876cf55 Mon Sep 17 00:00:00 2001 From: Mark Bumiller Date: Thu, 22 Jan 2026 20:03:42 -0500 Subject: [PATCH 3/4] PR feedback --- lib/plugins/Label_44_Slash.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/plugins/Label_44_Slash.ts b/lib/plugins/Label_44_Slash.ts index a3fd932..99ba508 100644 --- a/lib/plugins/Label_44_Slash.ts +++ b/lib/plugins/Label_44_Slash.ts @@ -5,7 +5,7 @@ import { CoordinateUtils } from "../utils/coordinate_utils"; import { FlightPlanUtils } from "../utils/flight_plan_utils"; import { ResultFormatter } from "../utils/result_formatter"; -// On Runway Report +// Flight Briefing Report export class Label_44_Slash extends DecoderPlugin { name = "label-44-slash"; From 0e7dfa6910c237986b0410ec941933057190a8dc Mon Sep 17 00:00:00 2001 From: Mark Bumiller Date: Thu, 22 Jan 2026 20:06:23 -0500 Subject: [PATCH 4/4] remove unused import --- lib/plugins/Label_44_Slash.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/plugins/Label_44_Slash.ts b/lib/plugins/Label_44_Slash.ts index 99ba508..2b9dbee 100644 --- a/lib/plugins/Label_44_Slash.ts +++ b/lib/plugins/Label_44_Slash.ts @@ -1,7 +1,6 @@ import { DateTimeUtils } from "../DateTimeUtils"; import { DecoderPlugin } from "../DecoderPlugin"; import { DecodeResult, Message, Options } from "../DecoderPluginInterface"; -import { CoordinateUtils } from "../utils/coordinate_utils"; import { FlightPlanUtils } from "../utils/flight_plan_utils"; import { ResultFormatter } from "../utils/result_formatter";