-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Parses: * Flight Number * Route Status * Departure Airport * Departure Runway * Arrivial Airport * First Waypoint * Message Checksum Does not Parse: * Departure Procedure * Arrival Procedure * Current Route(?) Also does not put multiple 'First Waypoints' into context
- Loading branch information
Showing
6 changed files
with
306 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { MessageDecoder } from '../MessageDecoder'; | ||
import { Label_H1_FPN } from './Label_H1_FPN'; | ||
|
||
test('decodes Label H1 Preamble FPN landing', () => { | ||
const decoder = new MessageDecoder(); | ||
const decoderPlugin = new Label_H1_FPN(decoder); | ||
|
||
expect(decoderPlugin.decode).toBeDefined(); | ||
expect(decoderPlugin.name).toBe('label-h1-fpn'); | ||
expect(decoderPlugin.qualifiers).toBeDefined(); | ||
expect(decoderPlugin.qualifiers()).toEqual({ | ||
labels: ['H1'], | ||
preambles: ['FPN'], | ||
}); | ||
|
||
const text = 'FPN/RI:DA:KEWR:AA:KDFW:CR:EWRDFW01(17L)..SAAME.J6.HVQ.Q68.LITTR..MEEOW..FEWWW:A:SEEVR4.FEWWW:F:VECTOR..DISCO..RIVET:AP:ILS 17L.RIVET:F:TACKEC8B5'; | ||
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-fpn'); | ||
expect(decodeResult.formatted.description).toBe('Flight Plan'); | ||
expect(decodeResult.formatted.items.length).toBe(6); | ||
expect(decodeResult.formatted.items[0].label).toBe('Route Status'); | ||
expect(decodeResult.formatted.items[0].value).toBe('Route Inactive'); | ||
expect(decodeResult.formatted.items[1].label).toBe('Origin'); | ||
expect(decodeResult.formatted.items[1].value).toBe('KEWR'); | ||
expect(decodeResult.formatted.items[2].label).toBe('Destination'); | ||
expect(decodeResult.formatted.items[2].value).toBe('KDFW'); | ||
expect(decodeResult.formatted.items[3].label).toBe('Aircraft Route'); | ||
expect(decodeResult.formatted.items[3].value).toBe('VECTOR >> DISCO >> RIVET'); | ||
expect(decodeResult.formatted.items[4].label).toBe('Aircraft Route'); | ||
expect(decodeResult.formatted.items[4].value).toBe('TACKE'); | ||
expect(decodeResult.formatted.items[5].label).toBe('Message Checksum'); | ||
expect(decodeResult.formatted.items[5].value).toBe('0xc8b5'); | ||
}); | ||
test('decodes Label H1 Preamble FPN full flight', () => { | ||
const decoder = new MessageDecoder(); | ||
const decoderPlugin = new Label_H1_FPN(decoder); | ||
|
||
expect(decoderPlugin.decode).toBeDefined(); | ||
expect(decoderPlugin.name).toBe('label-h1-fpn'); | ||
expect(decoderPlugin.qualifiers).toBeDefined(); | ||
expect(decoderPlugin.qualifiers()).toEqual({ | ||
labels: ['H1'], | ||
preambles: ['FPN'], | ||
}); | ||
|
||
// https://app.airframes.io/messages/2161768398 | ||
const text = 'FPN/FNAAL1956/RP:DA:KPHL:AA:KPHX:CR:PHLPHX61:R:27L(26O):D:PHL3:A:EAGUL6.ZUN:AP:ILS26..AIR,N40010W080490.J110.BOWRR..VLA,N39056W089097..STL,N38516W090289..GIBSN,N38430W092244..TYGER,N38410W094050..GCK,N37551W100435..DIXAN,N36169W105573..ZUN,N34579W109093293B'; | ||
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-fpn'); | ||
expect(decodeResult.raw.flight_number).toBe('AAL1956') | ||
expect(decodeResult.formatted.description).toBe('Flight Plan'); | ||
expect(decodeResult.formatted.items.length).toBe(5); | ||
expect(decodeResult.formatted.items[0].label).toBe('Route Status'); | ||
expect(decodeResult.formatted.items[0].value).toBe('Route Planned'); | ||
expect(decodeResult.formatted.items[1].label).toBe('Origin'); | ||
expect(decodeResult.formatted.items[1].value).toBe('KPHL'); | ||
expect(decodeResult.formatted.items[2].label).toBe('Destination'); | ||
expect(decodeResult.formatted.items[2].value).toBe('KPHX'); | ||
expect(decodeResult.formatted.items[3].label).toBe('Runway'); | ||
expect(decodeResult.formatted.items[3].value).toBe('27L(26O)'); | ||
expect(decodeResult.formatted.items[4].label).toBe('Message Checksum'); | ||
expect(decodeResult.formatted.items[4].value).toBe('0x293b'); | ||
}); | ||
|
||
test('decodes Label H1 Preamble FPN in-flight', () => { | ||
const decoder = new MessageDecoder(); | ||
const decoderPlugin = new Label_H1_FPN(decoder); | ||
|
||
expect(decoderPlugin.decode).toBeDefined(); | ||
expect(decoderPlugin.name).toBe('label-h1-fpn'); | ||
expect(decoderPlugin.qualifiers).toBeDefined(); | ||
expect(decoderPlugin.qualifiers()).toEqual({ | ||
labels: ['H1'], | ||
preambles: ['FPN'], | ||
}); | ||
|
||
// https://app.airframes.io/messages/2161761202 | ||
const text = 'FPN/FNUAL1187/RP:DA:KSFO:AA:KPHX:F:KAYEX,N36292W120569..LOSHN,N35509W120000..BOILE,N34253W118016..BLH,N33358W114457DDFB'; | ||
const decodeResult = decoderPlugin.decode({ text: text }); | ||
console.log(JSON.stringify(decodeResult, null, 2)); | ||
|
||
expect(decodeResult.decoded).toBe(true); | ||
expect(decodeResult.decoder.decodeLevel).toBe('full'); | ||
expect(decodeResult.decoder.name).toBe('label-h1-fpn'); | ||
expect(decodeResult.raw.flight_number).toBe('UAL1187') | ||
expect(decodeResult.formatted.description).toBe('Flight Plan'); | ||
expect(decodeResult.formatted.items.length).toBe(5); | ||
expect(decodeResult.formatted.items[0].label).toBe('Route Status'); | ||
expect(decodeResult.formatted.items[0].value).toBe('Route Planned'); | ||
expect(decodeResult.formatted.items[1].label).toBe('Origin'); | ||
expect(decodeResult.formatted.items[1].value).toBe('KSFO'); | ||
expect(decodeResult.formatted.items[2].label).toBe('Destination'); | ||
expect(decodeResult.formatted.items[2].value).toBe('KPHX'); | ||
expect(decodeResult.formatted.items[3].label).toBe('Aircraft Route'); | ||
expect(decodeResult.formatted.items[3].value).toBe('KAYEX(36.292 N, 120.569 W) >> LOSHN(35.509 N, 120 W) >> BOILE(34.253 N, 118.016 W) >> BLH(33.358 N, 114.457 W)'); | ||
expect(decodeResult.formatted.items[4].label).toBe('Message Checksum'); | ||
expect(decodeResult.formatted.items[4].value).toBe('0xddfb'); | ||
}); | ||
|
||
test('decodes Label H1 Preamble FPN <invalid>', () => { | ||
const decoder = new MessageDecoder(); | ||
const decoderPlugin = new Label_H1_FPN(decoder); | ||
|
||
expect(decoderPlugin.decode).toBeDefined(); | ||
expect(decoderPlugin.name).toBe('label-h1-fpn'); | ||
expect(decoderPlugin.qualifiers).toBeDefined(); | ||
expect(decoderPlugin.qualifiers()).toEqual({ | ||
labels: ['H1'], | ||
preambles: ['FPN'], | ||
}); | ||
|
||
const text = 'FPN Bogus message'; | ||
const decodeResult = decoderPlugin.decode({ text: text }); | ||
console.log(JSON.stringify(decodeResult, null, 2)); | ||
|
||
expect(decodeResult.decoded).toBe(false); | ||
expect(decodeResult.decoder.decodeLevel).toBe('none'); | ||
expect(decodeResult.decoder.name).toBe('label-h1-fpn'); | ||
expect(decodeResult.formatted.description).toBe('Flight Plan'); | ||
expect(decodeResult.message.text).toBe(text); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import { decode } from 'base85'; | ||
import { DateTimeUtils } from '../DateTimeUtils'; | ||
import { DecoderPlugin } from '../DecoderPlugin'; | ||
import { CoordinateUtils } from '../utils/coordinate_utils'; | ||
import { RouteUtils } from '../utils/route_utils'; | ||
|
||
export class Label_H1_FPN extends DecoderPlugin { | ||
name = 'label-h1-fpn'; | ||
|
||
qualifiers() { // eslint-disable-line class-methods-use-this | ||
return { | ||
labels: ["H1"], | ||
preambles: ['FPN'], | ||
}; | ||
} | ||
|
||
decode(message: any, options: any = {} ): any { | ||
let decodeResult: any = this.defaultResult; | ||
decodeResult.decoder.name = this.name; | ||
decodeResult.formatted.description = 'Flight Plan'; | ||
decodeResult.message = message; | ||
|
||
const checksum = message.text.slice(-4); | ||
const data = message.text.slice(0, message.text.length-4).split(':'); | ||
|
||
if(data[0].startsWith('FPN/')) { | ||
let allKnownFields = parseHeader(decodeResult, data[0]); | ||
for(let i=1; i< data.length; i+=2) { | ||
const key = data[i]; | ||
const value = data[i+1]; | ||
// TODO: discuss how store commented out bits as both raw and formatted | ||
switch(key) { | ||
// case 'A': // Arrival Procedure (?) | ||
// break; | ||
case 'AA': | ||
addArrivalAirport(decodeResult, value); | ||
break; | ||
// case 'CR': // Current Route (?) | ||
// break; | ||
// case 'D': // Departure Procedure | ||
// break; | ||
case 'DA': | ||
addDepartureAirport(decodeResult, value); | ||
break; | ||
case 'F': // First Waypoint | ||
addRoute(decodeResult, value); | ||
break; | ||
case 'R': | ||
addDepartureRunway(decodeResult, value); | ||
default: | ||
if(allKnownFields) { | ||
decodeResult.remaining.text = ''; | ||
allKnownFields = false; | ||
} | ||
decodeResult.remaining.text += `,${data[i]}`; | ||
decodeResult.decoder.decodeLevel = 'partial'; | ||
} | ||
} | ||
|
||
addChecksum(decodeResult, checksum); | ||
decodeResult.decoded = true; | ||
if(allKnownFields) { | ||
decodeResult.decoder.decodeLevel = 'full'; | ||
} | ||
} else {// Unknown | ||
if (options?.debug) { | ||
console.log(`Decoder: Unknown H1 message: ${message.text}`); | ||
} | ||
decodeResult.remaining.text = message.text; | ||
decodeResult.decoded = false; | ||
decodeResult.decoder.decodeLevel = 'none'; | ||
} | ||
return decodeResult; | ||
} | ||
} | ||
|
||
export default {}; | ||
|
||
function parseHeader(decodeResult: any, header: string): boolean { | ||
let allKnownFields = true; | ||
const fields = header.split('/'); | ||
if(fields.length == 3) { | ||
decodeResult.raw.flight_number = fields[1].substring(2); // Strip off 'FN' | ||
} else if(fields.length > 3) { | ||
decodeResult.remaining.text = fields.slice(2,-1).join('/'); | ||
allKnownFields = false | ||
} | ||
decodeResult.raw.route_status = fields[fields.length - 1]; | ||
|
||
var text; | ||
if(decodeResult.raw.route_status == 'RP'){ | ||
text = 'Route Planned'; | ||
} else if(decodeResult.raw.route_status == 'RI') { | ||
text = 'Route Inactive'; | ||
} else { | ||
text = decodeResult.raw.route_status; | ||
} | ||
decodeResult.formatted.items.push({ | ||
type: 'status', | ||
code: 'ROUTE_STATUS', | ||
label: 'Route Status', | ||
value: text, | ||
}); | ||
return allKnownFields; | ||
}; | ||
|
||
function addChecksum(decodeResult: any, value: string) { | ||
decodeResult.raw.checksum = Number("0x"+value); | ||
decodeResult.formatted.items.push({ | ||
type: 'message_checksum', | ||
code: 'CHECKSUM', | ||
label: 'Message Checksum', | ||
value: '0x' + ('0000' + decodeResult.raw.checksum.toString(16)).slice(-4), | ||
}); | ||
}; | ||
|
||
function addArrivalAirport(decodeResult: any, value: string) { | ||
decodeResult.raw.arrival_icao = value; | ||
decodeResult.formatted.items.push({ | ||
type: 'destination', | ||
code: 'DST', | ||
label: 'Destination', | ||
value: decodeResult.raw.arrival_icao, | ||
}); | ||
}; | ||
|
||
function addDepartureAirport(decodeResult: any, value: string) { | ||
decodeResult.raw.departure_icao = value; | ||
decodeResult.formatted.items.push({ | ||
type: 'origin', | ||
code: 'ORG', | ||
label: 'Origin', | ||
value: decodeResult.raw.departure_icao, | ||
}); | ||
}; | ||
|
||
function addDepartureRunway(decodeResult: any, value: string) { | ||
decodeResult.raw.runway = value; | ||
decodeResult.formatted.items.push({ | ||
type: 'runway', | ||
label: 'Runway', | ||
value: decodeResult.raw.runway, | ||
}); | ||
}; | ||
|
||
function addRoute(decodeResult: any, value: string) { | ||
const route = value.split('.'); | ||
decodeResult.raw.route = route.map((leg)=> CoordinateUtils.getWaypoint(leg)); | ||
decodeResult.formatted.items.push({ | ||
type: 'aircraft_route', | ||
code: 'ROUTE', | ||
label: 'Aircraft Route', | ||
value: RouteUtils.routeToString(decodeResult.raw.route), | ||
}); | ||
}; | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters