diff --git a/lib/plugins/Label_15.ts b/lib/plugins/Label_15.ts index 43eacb5..b621d39 100644 --- a/lib/plugins/Label_15.ts +++ b/lib/plugins/Label_15.ts @@ -1,4 +1,5 @@ import { DecoderPlugin } from '../DecoderPlugin'; +import { CoordinateUtils } from '../utils/coordinate_utils'; // General Aviation Position Report export class Label_15 extends DecoderPlugin { @@ -21,13 +22,13 @@ export class Label_15 extends DecoderPlugin { if (results) { // Style: (2N38111W 82211266 76400-64(Z // console.log(`Label 15 Position Report: between = ${results.groups.between}`); - decodeResult.raw.position = this.decodeStringCoordinates(results.groups.between.substr(0,13)); + decodeResult.raw.position = CoordinateUtils.decodeStringCoordinates(results.groups.between.substr(0,13)); if(decodeResult.raw.position) { decodeResult.formatted.items.push({ type: 'position', code: 'POS' , label: 'Position', - value: this.coordinateString(decodeResult.raw.position), + value: CoordinateUtils.coordinateString(decodeResult.raw.position), }); } } diff --git a/lib/plugins/Label_15_FST.ts b/lib/plugins/Label_15_FST.ts index 6d8677f..13df801 100644 --- a/lib/plugins/Label_15_FST.ts +++ b/lib/plugins/Label_15_FST.ts @@ -1,4 +1,5 @@ import { DecoderPlugin } from '../DecoderPlugin'; +import { CoordinateUtils } from '../utils/coordinate_utils'; // Position Report export class Label_15_FST extends DecoderPlugin { @@ -43,7 +44,7 @@ export class Label_15_FST extends DecoderPlugin { decodeResult.formatted.items.push({ type: 'position', label: 'Position', - value: this.coordinateString(decodeResult.raw.position) + value: CoordinateUtils.coordinateString(decodeResult.raw.position) }); decodeResult.formatted.items.push({ diff --git a/lib/plugins/Label_20_CFB.01.ts b/lib/plugins/Label_20_CFB.01.ts index f432b33..8cd6195 100644 --- a/lib/plugins/Label_20_CFB.01.ts +++ b/lib/plugins/Label_20_CFB.01.ts @@ -1,4 +1,5 @@ import { DecoderPlugin } from '../DecoderPlugin'; +import { CoordinateUtils } from '../utils/coordinate_utils'; // In Air Report export class Label_20_CFB01 extends DecoderPlugin { @@ -27,7 +28,7 @@ export class Label_20_CFB01 extends DecoderPlugin { console.log(results.groups); } - decodeResult.raw.position = this.decodeStringCoordinates(results.groups.unsplit_coords); + decodeResult.raw.position = CoordinateUtils.decodeStringCoordinates(results.groups.unsplit_coords); decodeResult.raw.departure_icao = results.groups.departure_icao; decodeResult.raw.arrival_icao = results.groups.arrival_icao; @@ -48,7 +49,7 @@ export class Label_20_CFB01 extends DecoderPlugin { type: 'position', code: 'POS' , label: 'Position', - value: this.coordinateString(decodeResult.raw.position), + value: CoordinateUtils.coordinateString(decodeResult.raw.position), }); } diff --git a/lib/plugins/Label_20_POS.ts b/lib/plugins/Label_20_POS.ts index 05934b2..8981a2a 100644 --- a/lib/plugins/Label_20_POS.ts +++ b/lib/plugins/Label_20_POS.ts @@ -1,4 +1,5 @@ import { DecoderPlugin } from '../DecoderPlugin'; +import { CoordinateUtils } from '../utils/coordinate_utils'; // Position Report export class Label_20_POS extends DecoderPlugin { @@ -31,12 +32,12 @@ export class Label_20_POS extends DecoderPlugin { // Field 1: Coordinates const rawCoords = fields[0]; - decodeResult.raw.position = this.decodeStringCoordinates(rawCoords); + decodeResult.raw.position = CoordinateUtils.decodeStringCoordinates(rawCoords); if(decodeResult.raw.position) { decodeResult.formatted.items.push({ type: 'position', label: 'Position', - value: this.coordinateString(decodeResult.raw.position), + value: CoordinateUtils.coordinateString(decodeResult.raw.position), }); } @@ -48,11 +49,11 @@ export class Label_20_POS extends DecoderPlugin { // Field 1: Coordinates const rawCoords = fields[0]; - decodeResult.raw.position = this.decodeStringCoordinates(rawCoords); + decodeResult.raw.position = CoordinateUtils.decodeStringCoordinates(rawCoords); decodeResult.formatted.items.push({ type: 'position', label: 'Position', - value: this.coordinateString(decodeResult.raw.position), + value: CoordinateUtils.coordinateString(decodeResult.raw.position), }); decodeResult.decoded = true; diff --git a/lib/plugins/Label_44_ETA.ts b/lib/plugins/Label_44_ETA.ts index bb24382..e1d4ec2 100644 --- a/lib/plugins/Label_44_ETA.ts +++ b/lib/plugins/Label_44_ETA.ts @@ -1,4 +1,5 @@ import { DecoderPlugin } from '../DecoderPlugin'; +import { CoordinateUtils } from '../utils/coordinate_utils'; // In Air Report export class Label_44_ETA extends DecoderPlugin { @@ -27,7 +28,7 @@ export class Label_44_ETA extends DecoderPlugin { console.log(results.groups); } - decodeResult.raw.position = this.decodeStringCoordinates(results.groups.unsplit_coords); + decodeResult.raw.position = CoordinateUtils.decodeStringCoordinates(results.groups.unsplit_coords); decodeResult.raw.departure_icao = results.groups.departure_icao; decodeResult.raw.arrival_icao = results.groups.arrival_icao; @@ -48,7 +49,7 @@ export class Label_44_ETA extends DecoderPlugin { type: 'position', code: 'POS' , label: 'Position', - value: this.coordinateString(decodeResult.raw.position), + value: CoordinateUtils.coordinateString(decodeResult.raw.position), }); } diff --git a/lib/plugins/Label_44_IN.ts b/lib/plugins/Label_44_IN.ts index 16ae0c2..186056a 100644 --- a/lib/plugins/Label_44_IN.ts +++ b/lib/plugins/Label_44_IN.ts @@ -1,4 +1,5 @@ import { DecoderPlugin } from '../DecoderPlugin'; +import { CoordinateUtils } from '../utils/coordinate_utils'; // In Air Report export class Label_44_IN extends DecoderPlugin { @@ -27,7 +28,7 @@ export class Label_44_IN extends DecoderPlugin { console.log(results.groups); } - decodeResult.raw.position = this.decodeStringCoordinates(results.groups.unsplit_coords); + decodeResult.raw.position = CoordinateUtils.decodeStringCoordinates(results.groups.unsplit_coords); decodeResult.raw.departure_icao = results.groups.departure_icao; decodeResult.raw.arrival_icao = results.groups.arrival_icao; @@ -48,7 +49,7 @@ export class Label_44_IN extends DecoderPlugin { type: 'position', code: 'POS' , label: 'Position', - value: this.coordinateString(decodeResult.raw.position), + value: CoordinateUtils.coordinateString(decodeResult.raw.position), }); } diff --git a/lib/plugins/Label_44_OFF.ts b/lib/plugins/Label_44_OFF.ts index c8e8334..8f03ac1 100644 --- a/lib/plugins/Label_44_OFF.ts +++ b/lib/plugins/Label_44_OFF.ts @@ -1,4 +1,5 @@ import { DecoderPlugin } from '../DecoderPlugin'; +import { CoordinateUtils } from '../utils/coordinate_utils'; // Off Runway Report export class Label_44_OFF extends DecoderPlugin { @@ -48,13 +49,13 @@ export class Label_44_OFF extends DecoderPlugin { decodeResult.raw.fuel_in_tons = Number(results.groups.fuel_in_tons); } - decodeResult.raw.position = this.decodeStringCoordinates(results.groups.unsplit_coords); + decodeResult.raw.position = CoordinateUtils.decodeStringCoordinates(results.groups.unsplit_coords); if(decodeResult.raw.position) { decodeResult.formatted.items.push({ type: 'position', code: 'POS' , label: 'Position', - value: this.coordinateString(decodeResult.raw.position), + value: CoordinateUtils.coordinateString(decodeResult.raw.position), }); } diff --git a/lib/plugins/Label_44_ON.ts b/lib/plugins/Label_44_ON.ts index 88f8108..1a74488 100644 --- a/lib/plugins/Label_44_ON.ts +++ b/lib/plugins/Label_44_ON.ts @@ -1,4 +1,5 @@ import { DecoderPlugin } from '../DecoderPlugin'; +import { CoordinateUtils } from '../utils/coordinate_utils'; // On Runway Report export class Label_44_ON extends DecoderPlugin { @@ -27,7 +28,7 @@ export class Label_44_ON extends DecoderPlugin { console.log(results.groups); } - decodeResult.raw.position = this.decodeStringCoordinates(results.groups.unsplit_coords); + decodeResult.raw.position = CoordinateUtils.decodeStringCoordinates(results.groups.unsplit_coords); decodeResult.raw.departure_icao = results.groups.departure_icao; decodeResult.raw.arrival_icao = results.groups.arrival_icao; @@ -48,7 +49,7 @@ export class Label_44_ON extends DecoderPlugin { type: 'position', code: 'POS' , label: 'Position', - value: this.coordinateString(decodeResult.raw.position), + value: CoordinateUtils.coordinateString(decodeResult.raw.position), }); } diff --git a/lib/plugins/Label_44_POS.ts b/lib/plugins/Label_44_POS.ts index f88e82b..405d9f7 100644 --- a/lib/plugins/Label_44_POS.ts +++ b/lib/plugins/Label_44_POS.ts @@ -1,4 +1,5 @@ import { DecoderPlugin } from '../DecoderPlugin'; +import { CoordinateUtils } from '../utils/coordinate_utils'; // General Aviation Position Report export class Label_44_POS extends DecoderPlugin { @@ -27,7 +28,7 @@ export class Label_44_POS extends DecoderPlugin { console.log(results.groups); } - decodeResult.raw.position = this.decodeStringCoordinates(results.groups.unsplit_coords); + decodeResult.raw.position = CoordinateUtils.decodeStringCoordinates(results.groups.unsplit_coords); decodeResult.raw.flight_level = results.groups.flight_level_or_ground == 'GRD' || results.groups.flight_level_or_ground == '***' ? '0' : Number(results.groups.flight_level_or_ground); decodeResult.raw.departure_icao = results.groups.departure_icao; decodeResult.raw.arrival_icao = results.groups.arrival_icao; @@ -55,7 +56,7 @@ export class Label_44_POS extends DecoderPlugin { type: 'position', code: 'POS' , label: 'Position', - value: this.coordinateString(decodeResult.raw.position), + value: CoordinateUtils.coordinateString(decodeResult.raw.position), }); } diff --git a/lib/plugins/Label_H1_M1BPOS.ts b/lib/plugins/Label_H1_M1BPOS.ts index b6f679b..4f06aac 100644 --- a/lib/plugins/Label_H1_M1BPOS.ts +++ b/lib/plugins/Label_H1_M1BPOS.ts @@ -1,4 +1,5 @@ import { DecoderPlugin } from '../DecoderPlugin'; +import { CoordinateUtils } from '../utils/coordinate_utils'; export class Label_H1_M1BPOS extends DecoderPlugin { // eslint-disable-line camelcase name = 'label-h1-m1bpos'; @@ -22,13 +23,13 @@ export class Label_H1_M1BPOS extends DecoderPlugin { // eslint-disable-line came const secondHalf = parts[1]; const items = firstHalf.split(','); - decodeResult.raw.position = this.decodeStringCoordinates(items[0]); + decodeResult.raw.position = CoordinateUtils.decodeStringCoordinates(items[0]); if(decodeResult.raw.position) { decodeResult.formatted.items.push({ type: 'position', code: 'POS' , label: 'Position', - value: this.coordinateString(decodeResult.raw.position), + value: CoordinateUtils.coordinateString(decodeResult.raw.position), }); } diff --git a/lib/plugins/Label_H1_POS.test.ts b/lib/plugins/Label_H1_POS.test.ts index 004884e..509aa55 100644 --- a/lib/plugins/Label_H1_POS.test.ts +++ b/lib/plugins/Label_H1_POS.test.ts @@ -26,24 +26,27 @@ test('decodes Label H1 Preamble POS variant 1', () => { expect(decodeResult.raw.latitude).toBe(43.312); expect(decodeResult.raw.longitude_direction).toBe('W'); expect(decodeResult.raw.longitude).toBe(123.174); + expect(decodeResult.raw.altitude).toBe(37000); expect(decodeResult.raw.outside_air_temperature).toBe(-48); - expect(decodeResult.formatted.items.length).toBe(4); + expect(decodeResult.formatted.items.length).toBe(5); expect(decodeResult.formatted.items[0].type).toBe('aircraft_position'); expect(decodeResult.formatted.items[0].code).toBe('POS'); expect(decodeResult.formatted.items[0].label).toBe('Aircraft Position'); expect(decodeResult.formatted.items[0].value).toBe('43.312 N, 123.174 W'); - expect(decodeResult.formatted.items[1].type).toBe('aircraft_route'); - expect(decodeResult.formatted.items[1].code).toBe('ROUTE'); - expect(decodeResult.formatted.items[1].label).toBe('Aircraft Route'); - expect(decodeResult.formatted.items[1].value).toBe('EASON > EBINY > ELENN'); - expect(decodeResult.formatted.items[2].type).toBe('outside_air_temperature'); - expect(decodeResult.formatted.items[2].code).toBe('OATEMP'); - expect(decodeResult.formatted.items[2].label).toBe('Outside Air Temperature (C)'); - expect(decodeResult.formatted.items[2].value).toBe('-48'); - expect(decodeResult.formatted.items[3].type).toBe('aircraft_timestamp'); - expect(decodeResult.formatted.items[3].code).toBe('TIMESTAMP'); - expect(decodeResult.formatted.items[3].label).toBe('Aircraft Timestamp'); - expect(decodeResult.formatted.items[3].value).toBe('Sun, 09 Oct 2022 21:57:54 GMT'); + expect(decodeResult.formatted.items[1].type).toBe('altitude'); + expect(decodeResult.formatted.items[1].code).toBe('ALT'); + expect(decodeResult.formatted.items[1].label).toBe('Altitude'); + expect(decodeResult.formatted.items[1].value).toBe('37000 feet'); + expect(decodeResult.formatted.items[2].type).toBe('aircraft_route'); + expect(decodeResult.formatted.items[2].code).toBe('ROUTE'); + expect(decodeResult.formatted.items[2].label).toBe('Aircraft Route'); + expect(decodeResult.formatted.items[2].value).toBe('EASON@2022-09-21T21:57:54Z > EBINY@2022-09-21T22:06:01Z > ELENN'); + expect(decodeResult.formatted.items[3].type).toBe('outside_air_temperature'); + expect(decodeResult.formatted.items[3].code).toBe('OATEMP'); + expect(decodeResult.formatted.items[3].label).toBe('Outside Air Temperature (C)'); + expect(decodeResult.formatted.items[3].value).toBe('-48'); + expect(decodeResult.formatted.items[4].label).toBe('Message Checksum'); + expect(decodeResult.formatted.items[4].value).toBe('0x7a40'); }); test('decodes Label H1 Preamble POS variant 2', () => { @@ -71,25 +74,32 @@ test('decodes Label H1 Preamble POS variant 2', () => { expect(decodeResult.raw.latitude).toBe(45.209); expect(decodeResult.raw.longitude_direction).toBe('W'); expect(decodeResult.raw.longitude).toBe(122.55); + expect(decodeResult.raw.altitude).toBe(13400); expect(decodeResult.raw.groundspeed).toBe(366); expect(decodeResult.raw.outside_air_temperature).toBe(-6); - expect(decodeResult.formatted.items.length).toBe(4); + expect(decodeResult.formatted.items.length).toBe(6); expect(decodeResult.formatted.items[0].type).toBe('aircraft_position'); expect(decodeResult.formatted.items[0].code).toBe('POS'); expect(decodeResult.formatted.items[0].label).toBe('Aircraft Position'); expect(decodeResult.formatted.items[0].value).toBe('45.209 N, 122.55 W'); - expect(decodeResult.formatted.items[1].type).toBe('aircraft_route'); - expect(decodeResult.formatted.items[1].code).toBe('ROUTE'); - expect(decodeResult.formatted.items[1].label).toBe('Aircraft Route'); - expect(decodeResult.formatted.items[1].value).toBe('PEGTY > MINNE > HISKU'); - expect(decodeResult.formatted.items[2].type).toBe('outside_air_temperature'); - expect(decodeResult.formatted.items[2].code).toBe('OATEMP'); - expect(decodeResult.formatted.items[2].label).toBe('Outside Air Temperature (C)'); - expect(decodeResult.formatted.items[2].value).toBe('-6'); - expect(decodeResult.formatted.items[3].type).toBe('aircraft_groundspeed'); - expect(decodeResult.formatted.items[3].code).toBe('GSPD'); - expect(decodeResult.formatted.items[3].label).toBe('Aircraft Groundspeed'); - expect(decodeResult.formatted.items[3].value).toBe('366'); + expect(decodeResult.formatted.items[1].type).toBe('altitude'); + expect(decodeResult.formatted.items[1].code).toBe('ALT'); + expect(decodeResult.formatted.items[1].label).toBe('Altitude'); + expect(decodeResult.formatted.items[1].value).toBe('13400 feet'); + expect(decodeResult.formatted.items[2].type).toBe('aircraft_route'); + expect(decodeResult.formatted.items[2].code).toBe('ROUTE'); + expect(decodeResult.formatted.items[2].label).toBe('Aircraft Route'); + expect(decodeResult.formatted.items[2].value).toBe('PEGTY@22:03:09 > MINNE@22:04:24 > HISKU'); + expect(decodeResult.formatted.items[3].type).toBe('outside_air_temperature'); + expect(decodeResult.formatted.items[3].code).toBe('OATEMP'); + expect(decodeResult.formatted.items[3].label).toBe('Outside Air Temperature (C)'); + expect(decodeResult.formatted.items[3].value).toBe('-6'); + expect(decodeResult.formatted.items[4].type).toBe('aircraft_groundspeed'); + expect(decodeResult.formatted.items[4].code).toBe('GSPD'); + expect(decodeResult.formatted.items[4].label).toBe('Aircraft Groundspeed'); + expect(decodeResult.formatted.items[4].value).toBe('366'); + expect(decodeResult.formatted.items[5].label).toBe('Message Checksum'); + expect(decodeResult.formatted.items[5].value).toBe('0x0a5b'); }); test('decodes Label H1 Preamble POS variant 3', () => { @@ -117,24 +127,27 @@ test('decodes Label H1 Preamble POS variant 3', () => { expect(decodeResult.raw.latitude).toBe(43.03); expect(decodeResult.raw.longitude_direction).toBe('W'); expect(decodeResult.raw.longitude).toBe(122.406); + expect(decodeResult.raw.altitude).toBe(38000); expect(decodeResult.raw.outside_air_temperature).toBe(-47); - expect(decodeResult.formatted.items.length).toBe(4); + expect(decodeResult.formatted.items.length).toBe(5); expect(decodeResult.formatted.items[0].type).toBe('aircraft_position'); expect(decodeResult.formatted.items[0].code).toBe('POS'); expect(decodeResult.formatted.items[0].label).toBe('Aircraft Position'); expect(decodeResult.formatted.items[0].value).toBe('43.03 N, 122.406 W'); - expect(decodeResult.formatted.items[1].type).toBe('aircraft_route'); - expect(decodeResult.formatted.items[1].code).toBe('ROUTE'); - expect(decodeResult.formatted.items[1].label).toBe('Aircraft Route'); - expect(decodeResult.formatted.items[1].value).toBe('IBALL > AARON > MOXEE'); - expect(decodeResult.formatted.items[2].type).toBe('outside_air_temperature'); - expect(decodeResult.formatted.items[2].code).toBe('OATEMP'); - expect(decodeResult.formatted.items[2].label).toBe('Outside Air Temperature (C)'); - expect(decodeResult.formatted.items[2].value).toBe('-47'); - expect(decodeResult.formatted.items[3].type).toBe('aircraft_timestamp'); - expect(decodeResult.formatted.items[3].code).toBe('TIMESTAMP'); - expect(decodeResult.formatted.items[3].label).toBe('Aircraft Timestamp'); - expect(decodeResult.formatted.items[3].value).toBe('Sun, 09 Oct 2022 22:05:16 GMT'); + expect(decodeResult.formatted.items[1].type).toBe('altitude'); + expect(decodeResult.formatted.items[1].code).toBe('ALT'); + expect(decodeResult.formatted.items[1].label).toBe('Altitude'); + expect(decodeResult.formatted.items[1].value).toBe('38000 feet'); + expect(decodeResult.formatted.items[2].type).toBe('aircraft_route'); + expect(decodeResult.formatted.items[2].code).toBe('ROUTE'); + expect(decodeResult.formatted.items[2].label).toBe('Aircraft Route'); + expect(decodeResult.formatted.items[2].value).toBe('IBALL@2022-09-21T22:05:16Z > AARON@2022-09-21T22:08:16Z > MOXEE'); + expect(decodeResult.formatted.items[3].type).toBe('outside_air_temperature'); + expect(decodeResult.formatted.items[3].code).toBe('OATEMP'); + expect(decodeResult.formatted.items[3].label).toBe('Outside Air Temperature (C)'); + expect(decodeResult.formatted.items[3].value).toBe('-47'); + expect(decodeResult.formatted.items[4].label).toBe('Message Checksum'); + expect(decodeResult.formatted.items[4].value).toBe('0xbf64'); }); test('decodes Label H1 Preamble POS variant 4', () => { @@ -162,20 +175,107 @@ test('decodes Label H1 Preamble POS variant 4', () => { expect(decodeResult.raw.latitude).toBe(33.225); expect(decodeResult.raw.longitude_direction).toBe('W'); expect(decodeResult.raw.longitude).toBe(79.428); + expect(decodeResult.raw.altitude).toBe(34000); expect(decodeResult.raw.outside_air_temperature).toBe(-42); - expect(decodeResult.formatted.items.length).toBe(3); + expect(decodeResult.formatted.items.length).toBe(5); expect(decodeResult.formatted.items[0].type).toBe('aircraft_position'); expect(decodeResult.formatted.items[0].code).toBe('POS'); expect(decodeResult.formatted.items[0].label).toBe('Aircraft Position'); expect(decodeResult.formatted.items[0].value).toBe('33.225 N, 79.428 W'); - expect(decodeResult.formatted.items[1].type).toBe('aircraft_route'); - expect(decodeResult.formatted.items[1].code).toBe('ROUTE'); - expect(decodeResult.formatted.items[1].label).toBe('Aircraft Route'); - expect(decodeResult.formatted.items[1].value).toBe('SCOOB > ENEME > FETAL'); - expect(decodeResult.formatted.items[2].type).toBe('outside_air_temperature'); - expect(decodeResult.formatted.items[2].code).toBe('OATEMP'); - expect(decodeResult.formatted.items[2].label).toBe('Outside Air Temperature (C)'); - expect(decodeResult.formatted.items[2].value).toBe('-42'); + expect(decodeResult.formatted.items[1].type).toBe('altitude'); + expect(decodeResult.formatted.items[1].code).toBe('ALT'); + expect(decodeResult.formatted.items[1].label).toBe('Altitude'); + expect(decodeResult.formatted.items[1].value).toBe('34000 feet'); + expect(decodeResult.formatted.items[2].type).toBe('aircraft_route'); + expect(decodeResult.formatted.items[2].code).toBe('ROUTE'); + expect(decodeResult.formatted.items[2].label).toBe('Aircraft Route'); + expect(decodeResult.formatted.items[2].value).toBe('SCOOB@23:29:33 > ENEME@23:57:12 > FETAL'); + expect(decodeResult.formatted.items[3].type).toBe('outside_air_temperature'); + expect(decodeResult.formatted.items[3].code).toBe('OATEMP'); + expect(decodeResult.formatted.items[3].label).toBe('Outside Air Temperature (C)'); + expect(decodeResult.formatted.items[3].value).toBe('-42'); + expect(decodeResult.formatted.items[4].label).toBe('Message Checksum'); + expect(decodeResult.formatted.items[4].value).toBe('0x57f6'); +}); + + +test('decodes Label H1 Preamble POS variant 5', () => { + const decoder = new MessageDecoder(); + const decoderPlugin = new Label_H1_POS(decoder); + + expect(decoderPlugin.decode).toBeDefined(); + expect(decoderPlugin.name).toBe('label-h1-pos'); + expect(decoderPlugin.qualifiers).toBeDefined(); + expect(decoderPlugin.qualifiers()).toEqual({ + labels: ['H1'], + preambles: ['POS'], + }); + + // https://app.airframes.io/messages/2184441420 + const text = 'POSN38531W078000,CSN-01,112309,310,CYN-02,114151,ACK,M40,26067,22479226'; + const decodeResult = decoderPlugin.decode({ text: text }); + console.log(JSON.stringify(decodeResult, null, 2)); + + expect(decodeResult.decoded).toBe(true); + expect(decodeResult.decoder.decodeLevel).toBe('partial'); + expect(decodeResult.decoder.name).toBe('label-h1-pos'); + expect(decodeResult.formatted.description).toBe('Position Report'); + expect(decodeResult.message.text).toBe(text); + expect(decodeResult.raw.latitude_direction).toBe('N'); + expect(decodeResult.raw.latitude).toBe(38.531); + expect(decodeResult.raw.longitude_direction).toBe('W'); + expect(decodeResult.raw.longitude).toBe(78.000); + expect(decodeResult.raw.altitude).toBe(31000); + expect(decodeResult.raw.outside_air_temperature).toBe(-40); + expect(decodeResult.formatted.items.length).toBe(5); + expect(decodeResult.formatted.items[0].type).toBe('aircraft_position'); + expect(decodeResult.formatted.items[0].code).toBe('POS'); + expect(decodeResult.formatted.items[0].label).toBe('Aircraft Position'); + expect(decodeResult.formatted.items[0].value).toBe('38.531 N, 78 W'); + expect(decodeResult.formatted.items[1].type).toBe('altitude'); + expect(decodeResult.formatted.items[1].code).toBe('ALT'); + expect(decodeResult.formatted.items[1].label).toBe('Altitude'); + expect(decodeResult.formatted.items[1].value).toBe('31000 feet'); + expect(decodeResult.formatted.items[2].type).toBe('aircraft_route'); + expect(decodeResult.formatted.items[2].code).toBe('ROUTE'); + expect(decodeResult.formatted.items[2].label).toBe('Aircraft Route'); + expect(decodeResult.formatted.items[2].value).toBe('CSN-01@11:23:09 > CYN-02@11:41:51 > ACK'); + expect(decodeResult.formatted.items[3].type).toBe('outside_air_temperature'); + expect(decodeResult.formatted.items[3].code).toBe('OATEMP'); + expect(decodeResult.formatted.items[3].label).toBe('Outside Air Temperature (C)'); + expect(decodeResult.formatted.items[3].value).toBe('-40'); + expect(decodeResult.formatted.items[4].label).toBe('Message Checksum'); + expect(decodeResult.formatted.items[4].value).toBe('0x9226'); +}); + +test('decodes Label H1 Preamble POS variant 6', () => { + const decoder = new MessageDecoder(); + const decoderPlugin = new Label_H1_POS(decoder); + + expect(decoderPlugin.decode).toBeDefined(); + expect(decoderPlugin.name).toBe('label-h1-pos'); + expect(decoderPlugin.qualifiers).toBeDefined(); + expect(decoderPlugin.qualifiers()).toEqual({ + labels: ['H1'], + preambles: ['POS'], + }); + + // https://app.airframes.io/messages/2295027018 + const text = 'POS/RFSCOOB.KEMPR.ECG.OHPEA.TOMMZ.OXANA.ZZTOP.OMALA.WILYY.KANUX.GALVN.KASAR.LNHOM.SLUKA.FIPEK.PUYYA.PLING.KOLAO.JETSSF2FC'; + const decodeResult = decoderPlugin.decode({ text: text }); + console.log(JSON.stringify(decodeResult, null, 2)); + + expect(decodeResult.decoded).toBe(true); + expect(decodeResult.decoder.decodeLevel).toBe('partial'); + expect(decodeResult.decoder.name).toBe('label-h1-pos'); + expect(decodeResult.formatted.description).toBe('Position Report'); + expect(decodeResult.message.text).toBe(text); + expect(decodeResult.formatted.items.length).toBe(2); + expect(decodeResult.formatted.items[0].label).toBe('Aircraft Route'); + expect(decodeResult.formatted.items[0].value).toBe('SCOOB > KEMPR > ECG > OHPEA > TOMMZ > OXANA > ZZTOP > OMALA > WILYY > KANUX > GALVN > KASAR > LNHOM > SLUKA > FIPEK > PUYYA > PLING > KOLAO > JETSS'); + expect(decodeResult.formatted.items[1].label).toBe('Message Checksum'); + expect(decodeResult.formatted.items[1].value).toBe('0xf2fc'); + }); test('decodes Label H1 Preamble POS ', () => { diff --git a/lib/plugins/Label_H1_POS.ts b/lib/plugins/Label_H1_POS.ts index 2ad173f..27032a5 100644 --- a/lib/plugins/Label_H1_POS.ts +++ b/lib/plugins/Label_H1_POS.ts @@ -1,5 +1,6 @@ import { DateTimeUtils } from '../DateTimeUtils'; import { DecoderPlugin } from '../DecoderPlugin'; +import { RouteUtils } from '../utils/route_utils'; export class Label_H1_POS extends DecoderPlugin { name = 'label-h1-pos'; @@ -17,59 +18,80 @@ export class Label_H1_POS extends DecoderPlugin { decodeResult.formatted.description = 'Position Report'; decodeResult.message = message; - // Style: POSN43312W123174,EASON,215754,370,EBINY,220601,ELENN,M48,02216,185/TS215754,0921227A40 - let variant1Regex = /^POS(?[NS])(?[0-9]+)(?[EW])(?[0-9]+),(?[a-zA-Z0-9]*),(?[0-9]*),(?[0-9]*),(?[a-zA-Z0-9]*),(?[0-9]*),(?[a-zA-Z0-9]*).(?[MP])(?[0-9]*),(?[0-9]*),(?[0-9]*)\/TS(?[0-9][0-9][0-9][0-9][0-9][0-9]),(?[0-9][0-9][0-9][0-9][0-9][0-9])(?.*)$/; - - // Style: POSN45209W122550,PEGTY,220309,134,MINNE,220424,HISKU,M6,060013,269,366,355K,292K,730A5B - let variant2Regex = /^POS(?[NS])(?[0-9]+)(?[EW])(?[0-9]+),(?[a-zA-Z0-9]*),(?[0-9]*),(?[0-9]*),(?[a-zA-Z0-9]*),(?[0-9]*),(?[a-zA-Z0-9]*).(?[MP])(?[0-9]*),(?[0-9]*),(?[0-9]*),(?[0-9]*),(?[a-zA-Z0-9]*),(?[a-zA-Z0-9]*),(?[a-zA-Z0-9]*)$/; - - // Style: POSN33225W079428,SCOOB,232933,340,ENEME,235712,FETAL,M42,003051,15857F6 - let variant4Regex = /^POS(?[NS])(?[0-9]+)(?[EW])(?[0-9]+),(?[a-zA-Z0-9]*),(?[0-9]*),(?[0-9]*),(?[a-zA-Z0-9]*),(?[0-9]*),(?[a-zA-Z0-9]*).(?[MP])(?[0-9]*),(?[0-9]*),(?[a-zfA-Z0-9]*)$/; - - let results; - if (results = message.text.match(variant1Regex)) { - - decodeResult = this.decodePositionRoute(decodeResult, results, options); - + const checksum = message.text.slice(-4); + //strip POS and checksum + const data = message.text.substring(3, message.text.length-4); + const fields = data.split(','); + + if(fields.length==1 && data.startsWith('/RF')) { + decodeResult.raw.route = data.substring(3,data.length).split('.').map((leg: string) => {return {name: leg}}); decodeResult.formatted.items.push({ - type: 'aircraft_timestamp', - code: 'TIMESTAMP', - label: 'Aircraft Timestamp', - value: DateTimeUtils.UTCDateTimeToString(results.groups.date, results.groups.timestamp), + type: 'aircraft_route', + code: 'ROUTE', + label: 'Aircraft Route', + value: RouteUtils.routeToString(decodeResult.raw.route), }); - decodeResult.remaining.text = `${results.groups.unknown1},${results.groups.unknown2},${results.groups.unknown3},${results.groups.unknown4},${results.groups.unknown5},${results.groups.unknown6},${results.groups.unknown7}`; - - decodeResult.decoded = true; - decodeResult.decoder.decodeLevel = 'partial'; - - } else if (results = message.text.match(variant2Regex)) { - - decodeResult = this.decodePositionRoute(decodeResult, results, options); - - decodeResult.raw.groundspeed = Number(results.groups.groundspeed); - + decodeResult.raw.checksum = Number("0x"+checksum); decodeResult.formatted.items.push({ - type: 'aircraft_groundspeed', - code: 'GSPD', - label: 'Aircraft Groundspeed', - value: `${decodeResult.raw.groundspeed}` - }); - - decodeResult.remaining.text = `${results.groups.unknown1},${results.groups.unknown2},${results.groups.unknown3},${results.groups.unknown4},${results.groups.unknown5},${results.groups.unknown6},${results.groups.unknown7},${results.groups.unknown8},${results.groups.unknown9}`; - + type: 'message_checksum', + code: 'CHECKSUM', + label: 'Message Checksum', + value: '0x' + ('0000' + decodeResult.raw.checksum.toString(16)).slice(-4), + }); + decodeResult.decoded = true; + // Once we know what RF stands for, I feel comfortable marking this full decodeResult.decoder.decodeLevel = 'partial'; - - } else if (results = message.text.match(variant4Regex)) { - - decodeResult = this.decodePositionRoute(decodeResult, results, options); - - decodeResult.remaining.text = `${results.groups.unknown1},${results.groups.unknown2},${results.groups.unknown3},${results.groups.unknown4},${results.groups.unknown5},${results.groups.unknown6}`; + decodeResult.remaining.text += 'RF' + } else if(fields.length>9) { + // idx - value + // 0 - position in millidegrees + // 1 - waypoint 1 + // 2 - waypoint 1 valid at HHMMSS + // 3 - baro alititude + // 4 - waypoint 2 + // 5 - waypoint 2 eta HHMMSS + // 6 - waypoint 3 + // 7 - temp + // 8 - ? + // 9 - ? or variant 1 ? + /TS + valid at HHMMSS + // 10 - ? or variant 1 date MMDDYY or variant 2 gspd (opt) + // 11 - ? (opt) + // 12 - ? (opt) + // 13 - ? (opt) + this.decodePositionRoute(decodeResult, options, fields); + + decodeResult.remaining.text = `${fields[2]},${fields[5]},${fields[8]}`; decodeResult.decoded = true; decodeResult.decoder.decodeLevel = 'partial'; + // variant 2 + if (fields.length==14) { + decodeResult.raw.groundspeed = Number(fields[10]); + + decodeResult.formatted.items.push({ + type: 'aircraft_groundspeed', + code: 'GSPD', + label: 'Aircraft Groundspeed', + value: `${decodeResult.raw.groundspeed}` + }); + + decodeResult.remaining.text += `,${fields[9]},${fields[11]},${fields[12]},${fields[13]}`; + } else { + for(let i=9; i ')}`, + value: RouteUtils.routeToString(decodeResult.raw.route), }); decodeResult.formatted.items.push({ @@ -131,3 +172,24 @@ export class Label_H1_POS extends DecoderPlugin { } export default {}; +function convertHHMMSSToTod(time: string): number{ + const h = Number(time.substring(0,2)); + const m = Number(time.substring(2,4)); + const s = Number(time.substring(4,6)); + const tod = (h*3600 )+ (m*60) + s; + return tod; +} + +/** + * + * @param time - HHMMSS + * @param date - MMDDYY + * @returns seconds since epoch + */ +function convertDateTimeToEpoch(time: string, date: string):number { + //YYYY-MM-DDTHH:mm:ss.sssZ + const timestamp = `20${date.substring(4,6)}-${date.substring(0,2)}-${date.substring(2,4)}T${time.substring(0,2)}:${time.substring(2,4)}:${time.substring(4,6)}.000Z` + const millis = Date.parse(timestamp); + return millis / 1000; +} + diff --git a/lib/types/waypoint.ts b/lib/types/waypoint.ts new file mode 100644 index 0000000..165d0c5 --- /dev/null +++ b/lib/types/waypoint.ts @@ -0,0 +1,39 @@ +import { CoordinateUtils } from "../utils/coordinate_utils"; + +/** + * Represenation of a waypoint. + * + * Usually used in Routes which is an array of waypoints. + * Airways/Jetways can also be represented as a waypoint, by just the name. + * There is no distinction between the two currently because there is no difference from the current messages + * Distinction must be determined from tne name. + * In the event that a waypoint is a GPS position, the name can be the raw position string (N12345W012345) + */ +export interface Waypoint { + /** unique identifier of the waypoint*/ + name: string; + /** + * latitude in decimal degrees + * + * if set, longitude must be provided + */ + latitude?: number; + /** longitude in decimal degrees + * + * if set, latitude must be provided + */ + longitude?: number; + /** + * time of arrival. If in future, it is an ETA. + * + * if set, timeFormat must be provided + */ + time?: number; + /** + * tod = 'Time of Day. seoconds since midnight', epoch = 'unix time. seconds since Jan 1, 1970 UTC' + * + * if set, time must be provided + */ + timeFormat?: 'tod' | 'epoch' + +} \ No newline at end of file diff --git a/lib/utils/coordinate_utils.ts b/lib/utils/coordinate_utils.ts new file mode 100644 index 0000000..e083069 --- /dev/null +++ b/lib/utils/coordinate_utils.ts @@ -0,0 +1,33 @@ +export class CoordinateUtils { + public static decodeStringCoordinates(stringCoords: String) : any { // eslint-disable-line class-methods-use-this + var results : any = {}; + // format: N12345W123456 or N12345 W123456 + const firstChar = stringCoords.substring(0, 1); + let middleChar = stringCoords.substring(6, 7); + let longitudeChars = stringCoords.substring(7, 13); + if (middleChar ==' ') { + middleChar = stringCoords.substring(7, 8); + longitudeChars = stringCoords.substring(8, 14); + } + if ((firstChar === 'N' || firstChar === 'S') && (middleChar === 'W' || middleChar === 'E')) { + results.latitudeDirection = firstChar; + results.latitude = (Number(stringCoords.substring(1, 6)) / 1000) * (firstChar === 'S' ? -1 : 1); + results.longitudeDirection = middleChar; + results.longitude = (Number(longitudeChars) / 1000) * (middleChar === 'W' ? -1 : 1); + } else { + return; + } + + return results; + } + + public static coordinateString(coords: any) : String { + return `${Math.abs(coords.latitude)} ${coords.latitudeDirection}, ${Math.abs(coords.longitude)} ${coords.longitudeDirection}`; + } + + public static latLonToCoordinateString(lat: number, lon: number) : String { + const latDir = lat > 0 ? 'N' : 'S'; + const lonDir = lon > 0 ? 'E' : 'W'; + return `${Math.abs(lat)} ${latDir}, ${Math.abs(lon)} ${lonDir}`; + } +} \ No newline at end of file diff --git a/lib/utils/route_utils.ts b/lib/utils/route_utils.ts new file mode 100644 index 0000000..c85aac6 --- /dev/null +++ b/lib/utils/route_utils.ts @@ -0,0 +1,29 @@ +import { Waypoint } from "../types/waypoint"; +import { CoordinateUtils } from "./coordinate_utils"; + +export class RouteUtils { + + public static routeToString(route: Waypoint[]): string { + return route.map((x) => RouteUtils.waypointToString(x)).join( ' > '); + } + + public static waypointToString(waypoint: Waypoint): string { + let s = waypoint.name; + if(waypoint.latitude && waypoint.longitude) { + s += `(${CoordinateUtils.latLonToCoordinateString(waypoint.latitude, waypoint.longitude)})`; + } + if(waypoint.time && waypoint.timeFormat) { + s +=`@${RouteUtils.timestampToString(waypoint.time, waypoint.timeFormat)}`; + } + return s; + } + // move out if we want public + private static timestampToString(time: number, format: 'tod' | 'epoch'): string { + const date = new Date(time * 1000); + if(format == 'tod') { + return date.toISOString().slice(11, 19); + } + //strip off millis + return date.toISOString().slice(0,-5)+"Z"; + } +} \ No newline at end of file