diff --git a/lib/plugins/Label_4J_POS.test.ts b/lib/plugins/Label_4J_POS.test.ts index 839eb2c..acc5ac3 100644 --- a/lib/plugins/Label_4J_POS.test.ts +++ b/lib/plugins/Label_4J_POS.test.ts @@ -20,8 +20,7 @@ describe('Label 4J POS', () => { }); }); - // Disabled due to checksum mismatch. Possibly non-ascii characters in message? - test.skip('decodes msg 1', () => { + test('decodes inmarsat', () => { // https://app.airframes.io/messages/2434848463 message.text = 'POS/ID91459S,BANKR31,/DC03032024,142813/MR64,0/ET31539/PSN39277W077359,142800,240,N39300W077110,031430,N38560W077150,M28,27619,MT370/CG311,160,350/FB732/VR329071'; @@ -31,35 +30,24 @@ describe('Label 4J POS', () => { expect(decodeResult.decoder.decodeLevel).toBe('partial'); expect(decodeResult.formatted.description).toBe('Position Report'); expect(decodeResult.raw.message_timestamp).toBe(1709476093); + expect(decodeResult.raw.tail).toBe('91459S'); + expect(decodeResult.raw.flight_number).toBe('BANKR31'); expect(decodeResult.raw.mission_number).toBe(''); - expect(decodeResult.formatted.items.length).toBe(9); - expect(decodeResult.formatted.items[0].label).toBe('Tail'); - expect(decodeResult.formatted.items[0].value).toBe('91459S'); - expect(decodeResult.formatted.items[1].label).toBe('Flight Number'); - expect(decodeResult.formatted.items[1].value).toBe('BANKR31'); - expect(decodeResult.formatted.items[2].label).toBe('Day of Month'); - expect(decodeResult.formatted.items[2].value).toBe('3'); - expect(decodeResult.formatted.items[3].label).toBe( - 'Estimated Time of Arrival', - ); - expect(decodeResult.formatted.items[3].value).toBe('15:39:00'); - expect(decodeResult.formatted.items[4].label).toBe('Aircraft Position'); - expect(decodeResult.formatted.items[4].value).toBe('39.462 N, 77.598 W'); - expect(decodeResult.formatted.items[5].label).toBe('Aircraft Route'); - expect(decodeResult.formatted.items[5].value).toBe( - '(39.500 N, 77.183 W)@14:28:00 > (38.933 N, 77.250 W)@03:14:30 > ?', - ); - expect(decodeResult.formatted.items[6].label).toBe('Altitude'); - expect(decodeResult.formatted.items[6].value).toBe('24000 feet'); - expect(decodeResult.formatted.items[7].label).toBe( - 'Outside Air Temperature (C)', - ); - expect(decodeResult.formatted.items[7].value).toBe('-28 degrees'); - expect(decodeResult.formatted.items[8].label).toBe('Message Checksum'); - expect(decodeResult.formatted.items[8].value).toBe('0x9071'); - expect(decodeResult.remaining.text).toBe( - 'MR64,0,27619,MT370/CG311,160,350/FB732/VR32', - ); + expect(decodeResult.raw.message_date).toBe('03032024'); + expect(decodeResult.raw.day).toBe(3); + expect(decodeResult.raw.eta_time).toBe(56340); + expect(decodeResult.raw.position.latitude).toBeCloseTo(39.462, 3); + expect(decodeResult.raw.position.longitude).toBeCloseTo(-77.598, 3); + expect(decodeResult.raw.altitude).toBe(24000); + expect(decodeResult.raw.route.waypoints.length).toBe(3); + expect(decodeResult.raw.outside_air_temperature).toBe(-28); + expect(decodeResult.raw.center_of_gravity).toBe(31.1); + expect(decodeResult.raw.cg_lower_limit).toBe(16); + expect(decodeResult.raw.cg_upper_limit).toBe(35); + expect(decodeResult.raw.fuel_on_board).toBe(732); + expect(decodeResult.raw.version).toBe(3.2); + expect(decodeResult.formatted.items.length).toBe(14); + expect(decodeResult.remaining.text).toBe('MR64,0,27619,MT370'); }); test('decodes ', () => { diff --git a/lib/plugins/Label_4N.ts b/lib/plugins/Label_4N.ts index 3727147..4eedd35 100644 --- a/lib/plugins/Label_4N.ts +++ b/lib/plugins/Label_4N.ts @@ -58,7 +58,7 @@ export class Label_4N extends DecoderPlugin { if (fields[12].length > 1) { ResultFormatter.alternateRunway(decodeResult, fields[12].split('/')[0]); } - ResultFormatter.checksum(decodeResult, fields[32]); + ResultFormatter.checksum(decodeResult, parseInt(fields[32], 16)); ResultFormatter.unknownArr( decodeResult, [...fields.slice(1, 3), fields[7], ...fields.slice(13, 32)].filter( diff --git a/lib/plugins/Label_H1.ts b/lib/plugins/Label_H1.ts index 101a98e..57bb7d4 100644 --- a/lib/plugins/Label_H1.ts +++ b/lib/plugins/Label_H1.ts @@ -36,7 +36,8 @@ export class Label_H1 extends DecoderPlugin { } else if (parts.length === 1) { decoded = H1Helper.decodeH1Message(decodeResult, msg); } else if (parts.length == 2) { - const offset = isNaN(parseInt(parts[1][1])) ? 3 : 4; + // need a better way to figure this out + const offset = parts[0] === '- ' || isNaN(parseInt(parts[1][1])) ? 3 : 4; decoded = H1Helper.decodeH1Message( decodeResult, msg.slice(parts[0].length + offset), diff --git a/lib/plugins/Label_H1_FPN.test.ts b/lib/plugins/Label_H1_FPN.test.ts index fd80ae8..54c881e 100644 --- a/lib/plugins/Label_H1_FPN.test.ts +++ b/lib/plugins/Label_H1_FPN.test.ts @@ -144,7 +144,7 @@ describe('Label_H1 FPN', () => { expect(decodeResult.formatted.items[8].label).toBe('Message Checksum'); expect(decodeResult.formatted.items[8].value).toBe('0x156d'); expect(decodeResult.remaining.text).toBe( - ':WS:N61000W030000,370..N61000W040000..N60000W050000..URTAK:WS:URTAK,380..LAKES:WS:LAKES,400..N57000W070000..N54300W080000..N49000W090000..DLH..COLDD/PR4356,344,360,1060,,,13,,,30,,,P50,M40,36090,,3296,292,KMSP,30L,172,215117', + ':WS:N61000W030000,370..N61000W040000..N60000W050000..URTAK:WS:URTAK,380..LAKES:WS:LAKES,400..N57000W070000..N54300W080000..N49000W090000..DLH..COLDD/PR4356,344,360,1060,,,13,,,30,,,P50,M40,36090,,3296,292', ); }); @@ -339,4 +339,23 @@ describe('Label_H1 FPN', () => { expect(decodeResult.raw.checksum).toBe(0xeee6); expect(decodeResult.formatted.items.length).toBe(1); }); + + test('decodes inmarsat', () => { + message.text = + 'FPN/ID80094S,RCH411,8JZ41NG3S048/MR3,5/RP:DA:KMCF:AA:LBSF:F:PIE..BRUTS.Q109.CAMJO.Q109.YURCK.Q97.PAACK.Q97.BLENO.Q97.FRIAR..TOPPS..FROSS..RIKAL..N53000W050000..N55000W040000..N56000W030000..N56000W020000..PIKIL..SOVED..MIMKU..MAC..BELOX.L603.DOLAS..NAVPI..MAVAS..SOGPO..TIVUN..ESAMA..OSBIT..KOMIB..SULUS.Z650.VEMUT..PEPIK..BERVA..ERGOM..TEGRI..OSTOV..GOL:V:CAMJO,301,AT3100,,:V:PAACK,282,AT3300,,:V:BLENO,258,AT3700,,:V:N53000W050000,256,AT3700,,:V:N55000W040000,260,AT3700,,:V:SOVED,303,AT2700,,:V:MIMKU,302,AT2700,,:V:DOLAS,290,AT2900,,:V:PEPIK,242,AT3700,,:V:BERVA,260,AT3700,,7147/WD,,,,C850'; + const decodeResult = plugin.decode(message); + + expect(decodeResult.decoded).toBe(true); + expect(decodeResult.decoder.decodeLevel).toBe('partial'); + expect(decodeResult.raw.tail).toBe('80094S'); + expect(decodeResult.raw.flight_number).toBe('RCH411'); + expect(decodeResult.raw.mission_number).toBe('8JZ41NG3S048'); + expect(decodeResult.raw.route_status).toBe('RP'); + expect(decodeResult.raw.departure_icao).toBe('KMCF'); + expect(decodeResult.raw.arrival_icao).toBe('LBSF'); + expect(decodeResult.raw.route.waypoints.length).toBe(69); //nice + expect(decodeResult.raw.checksum).toBe(0xc850); + expect(decodeResult.formatted.description).toBe('Flight Plan'); + expect(decodeResult.formatted.items.length).toBe(7); + }); }); diff --git a/lib/plugins/Label_H1_FTX.test.ts b/lib/plugins/Label_H1_FTX.test.ts index be3dcf7..bbe97c7 100644 --- a/lib/plugins/Label_H1_FTX.test.ts +++ b/lib/plugins/Label_H1_FTX.test.ts @@ -10,8 +10,7 @@ describe('Label_H1 FTX', () => { plugin = new Label_H1(decoder); }); - // disabled due to checksum failure. could be hidden characters in the source message - test.skip('decodes Label H1 Preamble FTX valid', () => { + test('decodes inmarsat', () => { // https://app.airframes.io/messages/3402014738 message.text = 'FTX/ID23544S,HIFI21,7VZ007B1S276/MR2,/FXFYI .. TAF KSUX 021720Z 0218 0318 20017G28KT P6SM SKC FM022200 22012G18KT P6SM SKC .. PUTS YOUR CXWIND AT 26KT ON RWY 13 .. REDUCES TO 18KT AT 22Z4FEF'; diff --git a/lib/plugins/Label_H1_LDI.test.ts b/lib/plugins/Label_H1_LDI.test.ts new file mode 100644 index 0000000..b0b1c7a --- /dev/null +++ b/lib/plugins/Label_H1_LDI.test.ts @@ -0,0 +1,78 @@ +import { MessageDecoder } from '../MessageDecoder'; +import { Label_H1 } from './Label_H1'; + +describe('Label H1 LDI', () => { + let plugin: Label_H1; + const message = { label: 'H1', text: '' }; + + beforeEach(() => { + const decoder = new MessageDecoder(); + plugin = new Label_H1(decoder); + }); + + test('decodes variant 2', () => { + message.text = + 'LDI/RW27R,,,,,M160,1676,,,,202003,2,,,,P8,137147150,0,,M160,,146149150,P48,2,2.27L,,,,,M160,1676,,,,202003,2,,,,P8,137147150,0,,M160,,147150151,P52,2,2.35O,,,,,M160,1676,,,,202003,2,,,,P8,127133138,0,,,,,,3:KPHL,H1007,1036,1036,1036/CG225/SN69EAE8'; + const decodeResult = plugin.decode(message); + + expect(decodeResult.decoded).toBe(true); + expect(decodeResult.decoder.decodeLevel).toBe('partial'); + expect(decodeResult.raw.departure_icao).toBe('KPHL'); + expect(decodeResult.raw.departure_runway).toBe('27R'); + expect(decodeResult.raw.center_of_gravity).toBe(22.5); + expect(decodeResult.raw.checksum).toBe(0xeae8); + expect(decodeResult.formatted.description).toBe( + 'Load Distribution Information', + ); + expect(decodeResult.formatted.items.length).toBe(4); + expect(decodeResult.remaining.text).toBe( + ',,,,M160,1676,,,,202003,2,,,,P8,137147150,0,,M160,,146149150,P48,2,2.27L,,,,,M160,1676,,,,202003,2,,,,P8,137147150,0,,M160,,147150151,P52,2,2.35O,,,,,M160,1676,,,,202003,2,,,,P8,127133138,0,,,,,,3,H1007,1036,1036,1036', + ); + }); + + test('decodes variant 1', () => { + message.text = + 'LDI/RW16L,,,,,,1552,,P4,D7,164008,1,1,0,,P60,155159161:,,800,800,800/CG2356485'; + const decodeResult = plugin.decode(message); + + expect(decodeResult.decoded).toBe(true); + expect(decodeResult.decoder.decodeLevel).toBe('partial'); + expect(decodeResult.raw.departure_runway).toBe('16L'); + expect(decodeResult.raw.center_of_gravity).toBe(23.5); + expect(decodeResult.raw.checksum).toBe(0x6485); + expect(decodeResult.formatted.description).toBe( + 'Load Distribution Information', + ); + expect(decodeResult.formatted.items.length).toBe(4); + expect(decodeResult.remaining.text).toBe( + ',,,,,1552,,P4,D7,164008,1,1,0,,P60,155159161.,,800,800,800', + ); + }); + + test('decodes response', () => { + message.text = 'RESLDI/AK,7151B9'; + const decodeResult = plugin.decode(message); + + expect(decodeResult.decoded).toBe(true); + expect(decodeResult.decoder.decodeLevel).toBe('partial'); + expect(decodeResult.raw.checksum).toBe(0x51b9); + expect(decodeResult.formatted.description).toBe( + 'Response for Load Distribution Information', + ); + expect(decodeResult.formatted.items.length).toBe(1); + expect(decodeResult.remaining.text).toBe('AK,71'); + }); + + test('decodes request', () => { + message.text = '#MDREQLDI57CC'; + const decodeResult = plugin.decode(message); + + expect(decodeResult.decoded).toBe(true); + expect(decodeResult.decoder.decodeLevel).toBe('full'); + expect(decodeResult.raw.checksum).toBe(0x57cc); + expect(decodeResult.formatted.description).toBe( + 'Request for Load Distribution Information', + ); + expect(decodeResult.formatted.items.length).toBe(1); + }); +}); diff --git a/lib/plugins/Label_H1_OHMA.test.ts b/lib/plugins/Label_H1_OHMA.test.ts index ded06bb..a97be9e 100644 --- a/lib/plugins/Label_H1_OHMA.test.ts +++ b/lib/plugins/Label_H1_OHMA.test.ts @@ -64,6 +64,7 @@ describe('Label H1 Preamble OHMA', () => { expect(decodeResult.formatted.items[0].value).toBe('undefined'); }); + // disabled because parser currently does not handle before OHMA, but here as an example. test.skip('decodes Label H1 Preamble OHMA partial', () => { // https://app.airframes.io/messages/3126673935 message.text = diff --git a/lib/plugins/Label_H1_POS.test.ts b/lib/plugins/Label_H1_POS.test.ts index 6393b83..71faa14 100644 --- a/lib/plugins/Label_H1_POS.test.ts +++ b/lib/plugins/Label_H1_POS.test.ts @@ -270,44 +270,6 @@ describe('Label_H1 POS', () => { expect(decodeResult.remaining.text).toBe('272100,157'); }); - // broken as there is no checksum - test.skip('# long variant', () => { - // https://app.airframes.io/messages/2366921571 - message.text = - '#M1BPOSN29510W098448,RW04,140407,188,TATAR,4,140445,ALISS,M12,246048,374K,282K,1223,133,KSAT,KELP,,70,151437,415,73/PR1223,222,240,133,,44,40,252074,M22,180,P0,P0/RI:DA:KSAT:AA:KELP..TATAR:D:ALISS6:F:ALISS..FST'; - const decodeResult = plugin.decode(message); - - expect(decodeResult.decoded).toBe(true); - expect(decodeResult.decoder.decodeLevel).toBe('partial'); - expect(decodeResult.formatted.description).toBe('Position Report'); - expect(decodeResult.formatted.items.length).toBe(10); - expect(decodeResult.formatted.items[0].label).toBe('Aircraft Position'); - expect(decodeResult.formatted.items[0].value).toBe('29.510 N, 98.448 W'); - expect(decodeResult.formatted.items[1].label).toBe('Arrival Runway'); - expect(decodeResult.formatted.items[1].value).toBe('04'); - expect(decodeResult.formatted.items[2].label).toBe('Aircraft Groundspeed'); - expect(decodeResult.formatted.items[2].value).toBe('415'); - expect(decodeResult.formatted.items[3].label).toBe('Altitude'); - expect(decodeResult.formatted.items[3].value).toBe('24000 feet'); - expect(decodeResult.formatted.items[4].label).toBe('Route Status'); - expect(decodeResult.formatted.items[4].value).toBe('Route Inactive'); - expect(decodeResult.formatted.items[5].label).toBe('Origin'); - expect(decodeResult.formatted.items[5].value).toBe('KSAT'); - expect(decodeResult.formatted.items[6].label).toBe('Destination'); - expect(decodeResult.formatted.items[6].value).toBe('KELP..TATAR'); // FIXME- should be just kelp - expect(decodeResult.formatted.items[7].label).toBe('Departure Procedure'); - expect(decodeResult.formatted.items[7].value).toBe('ALISS6'); - expect(decodeResult.formatted.items[8].label).toBe('Aircraft Route'); - expect(decodeResult.formatted.items[8].value).toBe('ALISS >> FST'); - expect(decodeResult.formatted.items[9].label).toBe('Aircraft Route'); - expect(decodeResult.formatted.items[9].value).toBe( - 'TATAR@14:04:07 > ALISS@14:04:45 > ?', - ); // FIXME - ? should be FST - expect(decodeResult.remaining.text).toBe( - '188,4,M12,246048,374K,282K,1223,133,,70,151437,73/PR1223,222,133,,44,40,252074,M22,180,P0', - ); - }); - test('# variant 7', () => { // https://app.airframes.io/messages/2434835903 message.text = @@ -484,7 +446,7 @@ describe('Label_H1 POS', () => { expect(decodeResult.formatted.items[11].label).toBe('Message Checksum'); expect(decodeResult.formatted.items[11].value).toBe('0x53b2'); expect(decodeResult.remaining.text).toBe( - '290016,191/PR1496,150,370,191,,55,10,248028,M47,30,P19,P0/FHCIV,105208,273K,3226,175,M41,252027,450,N,221,62.MEDIL,105411,267K,3439,172,M44,250028,459,N,203,15.PITHI,105533,259K,3584,170,M47,249028,456,N,203,10.LESDO,105859,252K,3700,167,M47,248028,456,N,203,25.KOVIN,110153,252K,3700,164,M47,248028,456,N,203,21.DUCRA,110705,252K,3700,160,M47,248028,456,N,213,37.RESMI,111101,251K,3700,156,M47,248028,455,N,213,28.DEKOD,111325,251K,3700,154,M47,248028,455,N,192,17.DISAK,111438,251K,3700,153,M47,248028,454,N,172,9.DIRMO,112306,251K,3700,145,M47,248028,454,N,178,63.ETAMO,112514,250K,3700,143,M47,248028,453,N,158,16.ADEKA,113339,250K,3700,136,M47,248028,454,N,147,64.MOKDI,114139,251K,3700,129,M47,248028,454,N,181,59.MEN,114429,251K,3700,127,M47,248028,454,N,181,21.BADAM,114843,251K,3700,123,M47,248028,454,N,179,31.KANIG,120154,250K,3700,111,M47,248028,453,N,185,97.KENAS,121800,250K,3700,98,M47,248028,453,N,177,119.POS,122257,250K,3018,96,M45,248023,395,N,182,34.LEIB,124503,150K,2,89,P15,000000,161,N,231,103,LEIB,,89,124503,73', + '290016,191/PR1496,150,370,191,,55,10,248028,M47,30,P19,P0/FHCIV,105208,273K,3226,175,M41,252027,450,N,221,62.MEDIL,105411,267K,3439,172,M44,250028,459,N,203,15.PITHI,105533,259K,3584,170,M47,249028,456,N,203,10.LESDO,105859,252K,3700,167,M47,248028,456,N,203,25.KOVIN,110153,252K,3700,164,M47,248028,456,N,203,21.DUCRA,110705,252K,3700,160,M47,248028,456,N,213,37.RESMI,111101,251K,3700,156,M47,248028,455,N,213,28.DEKOD,111325,251K,3700,154,M47,248028,455,N,192,17.DISAK,111438,251K,3700,153,M47,248028,454,N,172,9.DIRMO,112306,251K,3700,145,M47,248028,454,N,178,63.ETAMO,112514,250K,3700,143,M47,248028,453,N,158,16.ADEKA,113339,250K,3700,136,M47,248028,454,N,147,64.MOKDI,114139,251K,3700,129,M47,248028,454,N,181,59.MEN,114429,251K,3700,127,M47,248028,454,N,181,21.BADAM,114843,251K,3700,123,M47,248028,454,N,179,31.KANIG,120154,250K,3700,111,M47,248028,453,N,185,97.KENAS,121800,250K,3700,98,M47,248028,453,N,177,119.POS,122257,250K,3018,96,M45,248023,395,N,182,34.LEIB,124503,150K,2,89,P15,000000,161,N,231,103', ); }); diff --git a/lib/plugins/Label_H1_PRG.test.ts b/lib/plugins/Label_H1_PRG.test.ts index 09d1d10..2b63b86 100644 --- a/lib/plugins/Label_H1_PRG.test.ts +++ b/lib/plugins/Label_H1_PRG.test.ts @@ -131,8 +131,7 @@ describe('Label H1 Preamble PRG', () => { ); }); - // TODO Fix this test - test.skip('decodes named runway', () => { + test('decodes named runway inmarsat', () => { message.text = 'PRG/DT,KMDW,31R,62,031854,524,N38584W077333,171,732B3C'; const decodeResult = plugin.decode(message); @@ -142,9 +141,10 @@ describe('Label H1 Preamble PRG', () => { expect(decodeResult.raw.arrival_runway).toBe('31R'); expect(decodeResult.raw.position.latitude).toBe(38.584); expect(decodeResult.raw.position.longitude).toBe(-77.333); - + expect(decodeResult.raw.fuel_on_board).toBe(62); + expect(decodeResult.raw.checksum).toBe(0x2b3c); expect(decodeResult.formatted.description).toBe('Progress Report'); - expect(decodeResult.formatted.items.length).toBe(0); + expect(decodeResult.formatted.items.length).toBe(6); }); test('decodes Label H1 Preamble PRG ', () => { diff --git a/lib/plugins/Label_H1_REQ.test.ts b/lib/plugins/Label_H1_REQ.test.ts index 0e4f2fa..6bcfdf9 100644 --- a/lib/plugins/Label_H1_REQ.test.ts +++ b/lib/plugins/Label_H1_REQ.test.ts @@ -55,6 +55,22 @@ describe('Label H1 preamble REQ', () => { expect(decodeResult.remaining.text).toBeUndefined(); }); + test('decodes REQ POS inmarsat', () => { + message.text = '#MDREQPOS/ID55150A,RCH892,LVZF1185C049/MR1,/AU39310'; + const decodeResult = plugin.decode(message); + + expect(decodeResult.decoded).toBe(true); + expect(decodeResult.decoder.decodeLevel).toBe('partial'); + expect(decodeResult.raw.tail).toBe('55150A'); + expect(decodeResult.raw.flight_number).toBe('RCH892'); + expect(decodeResult.raw.checksum).toBe(0x9310); + expect(decodeResult.formatted.description).toBe( + 'Request for Position Report', + ); + expect(decodeResult.formatted.items.length).toBe(3); + expect(decodeResult.remaining.text).toBe('MR1,/AU3'); + }); + test('decodes FPN', () => { message.text = 'REQFPN/RN10001/RS:FP:Z5585 9736'; const decodeResult = plugin.decode(message); diff --git a/lib/utils/h1_helper.ts b/lib/utils/h1_helper.ts index 4200ed5..c8f7f0e 100644 --- a/lib/utils/h1_helper.ts +++ b/lib/utils/h1_helper.ts @@ -9,12 +9,18 @@ import { RouteUtils } from './route_utils'; export class H1Helper { public static decodeH1Message(decodeResult: DecodeResult, message: string) { - const checksum = message.slice(-4); + const checksum = parseInt(message.slice(-4), 16); const data = message.slice(0, message.length - 4); - if (calculateChecksum(data) !== checksum) { + let checksumAlgorithmUsed = ''; + if (crc16IbmSdlcRev(data) === checksum) { + // ACARS/VDL2/HFDL/Iridium + checksumAlgorithmUsed = 'IBM-SDLC reversed'; + } else if (crc16Genibus(data) === checksum) { + // inmarsat + checksumAlgorithmUsed = 'GENIBUS'; + } else { decodeResult.decoded = false; decodeResult.decoder.decodeLevel = 'none'; - return false; } const fields = data.split('/'); @@ -24,7 +30,6 @@ export class H1Helper { decodeResult.decoder.decodeLevel = 'none'; return false; } - for (let i = 1; i < fields.length; ++i) { const key = fields[i].substring(0, 2); const data = fields[i].substring(2); @@ -95,6 +100,9 @@ export class H1Helper { case 'RN': ResultFormatter.routeNumber(decodeResult, data); break; + case 'RW': + processRunway(decodeResult, data.split(':')); + break; case 'SN': decodeResult.raw.serial_number = data; break; @@ -121,6 +129,7 @@ export class H1Helper { } } + ResultFormatter.checksumAlgorithm(decodeResult, checksumAlgorithmUsed); ResultFormatter.checksum(decodeResult, checksum); return true; @@ -261,31 +270,42 @@ function processIdentification(decodeResult: DecodeResult, data: string[]) { } function processDT(decodeResult: DecodeResult, data: string[]) { - if (!decodeResult.raw.arrival_icao) { - ResultFormatter.arrivalAirport(decodeResult, data[0]); - } else if (decodeResult.raw.arrival_icao != data[0]) { - ResultFormatter.unknownArr(decodeResult, data); - } // else duplicate - don't do anything - - if (data.length > 1) { - ResultFormatter.arrivalRunway(decodeResult, data[1]); - } - if (data.length > 2) { - ResultFormatter.currentFuel(decodeResult, Number(data[2])); + if (data.length === 9) { + ResultFormatter.unknown(decodeResult, data[0]); + if (!decodeResult.raw.arrival_icao) { + ResultFormatter.arrivalAirport(decodeResult, data[1]); + } + ResultFormatter.arrivalRunway(decodeResult, data[2]); + ResultFormatter.currentFuel(decodeResult, parseInt(data[3], 10)); + ResultFormatter.eta( + decodeResult, + DateTimeUtils.convertHHMMSSToTod(data[4]), + ); + ResultFormatter.unknown(decodeResult, data[5]); + ResultFormatter.position( + decodeResult, + CoordinateUtils.decodeStringCoordinates(data[6]), + ); + ResultFormatter.unknown(decodeResult, data[7]); + ResultFormatter.unknown(decodeResult, data[8]); + return; } - if (data.length > 3) { + if (data.length === 4 || data.length === 5) { + if (!decodeResult.raw.arrival_icao) { + ResultFormatter.arrivalAirport(decodeResult, data[0]); + } + ResultFormatter.arrivalRunway(decodeResult, data[1]); + ResultFormatter.currentFuel(decodeResult, parseInt(data[2], 10)); ResultFormatter.eta( decodeResult, DateTimeUtils.convertHHMMSSToTod(data[3]), ); + if (data.length > 4) { + ResultFormatter.remainingFuel(decodeResult, Number(data[4])); + } + return; } - if (data.length > 4) { - ResultFormatter.remainingFuel(decodeResult, Number(data[4])); - } - if (data.length > 5) { - //TODO: figure out what this is - ResultFormatter.unknownArr(decodeResult, data); - } + ResultFormatter.unknownArr(decodeResult, data, ','); } function processLandingReport(decodeResult: DecodeResult, data: string[]) { @@ -361,7 +381,9 @@ function processMessageType(decodeResult: DecodeResult, type: string): boolean { } else if (type === 'FTX') { decodeResult.formatted.description = 'Free Text'; } else if (type === 'INI') { - decodeResult.formatted.description = 'Flight Plan Initial Report'; + decodeResult.formatted.description = 'Initial Report'; + } else if (type === 'LDI') { + decodeResult.formatted.description = 'Load Distribution Information'; } else if (type === 'PER') { decodeResult.formatted.description = 'Performance Report'; } else if (type === 'POS') { @@ -457,11 +479,15 @@ function processWindData(decodeResult: DecodeResult, message: string) { const flightLevel = Number(message.slice(0, 3)); const fields = message.slice(4).split('.'); // strip off altitude and comma fields.forEach((field) => { + if (field.length < 4) { + // probably need a more robust check to determine to skip + return; + } const data = field.split(','); const waypoint = { name: data[0] }; const windData = data[1]; - const windDirection = Number(windData.slice(0, 3)); - const windSpeed = Number(windData.slice(3)); + const windDirection = parseInt(windData.slice(0, 3), 10); + const windSpeed = parseInt(windData.slice(3), 10); if (data.length === 3) { const tempData = data[2]; @@ -493,8 +519,21 @@ function processWindData(decodeResult: DecodeResult, message: string) { ResultFormatter.windData(decodeResult, wind); } +function processRunway(decodeResult: DecodeResult, data: string[]) { + //FIXME - figure out names + const parts = data[0].split('.'); + const more = parts[0].split(','); + + ResultFormatter.departureRunway(decodeResult, more[0]); + ResultFormatter.unknownArr(decodeResult, more.slice(1), ','); + ResultFormatter.unknownArr(decodeResult, parts.slice(1), '.'); + ResultFormatter.departureAirport(decodeResult, data[1].split(',')[0]); + ResultFormatter.unknownArr(decodeResult, data[1].split(',').slice(1), ','); + //ResultFormatter.unknownArr(decodeResult, data.slice(2), ':'); +} + // CRC-16/IBM-SDLC but nibbles are reversed -function calculateChecksum(data: string): string { +function crc16IbmSdlcRev(data: string): number { let crc = 0xffff; const bytes = Buffer.from(data, 'ascii'); @@ -515,5 +554,31 @@ function calculateChecksum(data: string): string { const nibble3 = (crc >> 4) & 0xf; const nibble4 = crc & 0xf; - return `${nibble4.toString(16)}${nibble3.toString(16)}${nibble2.toString(16)}${nibble1.toString(16)}`.toUpperCase(); + return (nibble4 << 12) | (nibble3 << 8) | (nibble2 << 4) | nibble1; +} + +/** + * Calculates the CRC-16/GENIBUS checksum for a given Uint8Array. + * @param data The input data as a byte array. + * @returns The 16-bit checksum as a number. + */ +function crc16Genibus(data: string): number { + let crc = 0xffff; + const polynomial = 0x1021; + + const bytes = Buffer.from(data, 'ascii'); + + for (const byte of bytes) { + crc ^= byte << 8; + + for (let i = 0; i < 8; i++) { + if ((crc & 0x8000) !== 0) { + crc = ((crc << 1) ^ polynomial) & 0xffff; + } else { + crc = (crc << 1) & 0xffff; + } + } + } + + return (crc ^ 0xffff) & 0xffff; } diff --git a/lib/utils/result_formatter.ts b/lib/utils/result_formatter.ts index 14f0fd7..33ecb23 100644 --- a/lib/utils/result_formatter.ts +++ b/lib/utils/result_formatter.ts @@ -252,8 +252,18 @@ export class ResultFormatter { }); } - static checksum(decodeResult: DecodeResult, value: string) { - decodeResult.raw.checksum = Number('0x' + value); + static checksumAlgorithm(decodeResult: DecodeResult, value: string) { + decodeResult.raw.checksum_algorithm = value; + // decodeResult.formatted.items.push({ + // type: 'message_checksum_algorithm', + // code: 'CHECKSUM_ALGO', + // label: 'Checksum Algorithm', + // value: decodeResult.raw.checksum_algorithm, + // }); + } + + static checksum(decodeResult: DecodeResult, value: number) { + decodeResult.raw.checksum = value; decodeResult.formatted.items.push({ type: 'message_checksum', code: 'CHECKSUM',