Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more FPN parsing #29

Merged
merged 9 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions lib/DateTimeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,32 @@ export class DateTimeUtils {
}
return utcDate.toUTCString();
}

/**
*
* @param time HHMMSS
* @returns seconds since midnight
*/
public static 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
*/
public static 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;
}
}

export default {};
164 changes: 125 additions & 39 deletions lib/plugins/Label_H1_FPN.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { MessageDecoder } from '../MessageDecoder';
import { Label_H1_FPN } from './Label_H1_FPN';

test('decodes Label H1 Preamble FPN landing', () => {
test('Label H1 Preamble FPN decoder has right qualifiers', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_H1_FPN(decoder);

Expand All @@ -12,76 +12,81 @@ test('decodes Label H1 Preamble FPN landing', () => {
labels: ['H1'],
preambles: ['FPN'],
});
});

test('decodes Label H1 Preamble FPN landing', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_H1_FPN(decoder);

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.decodeLevel).toBe('full');
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.length).toBe(9);
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');
expect(decodeResult.formatted.items[3].label).toBe('Company Route');
expect(decodeResult.formatted.items[3].value).toBe('EWRDFW01(17L): >> SAAME > J6 > HVQ > Q68 > LITTR >> MEEOW >> FEWWW');
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a little funky, open to suggestions to changes in route_utils.ts

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine for now. Once we see how the formatted versions look on Airframes and AcarsHub and/or get other feedback, we can always refine it. I'll keep you posted.

expect(decodeResult.formatted.items[4].label).toBe('Arrival Procedure');
expect(decodeResult.formatted.items[4].value).toBe('SEEVR4 starting at FEWWW');
expect(decodeResult.formatted.items[5].label).toBe('Aircraft Route');
expect(decodeResult.formatted.items[5].value).toBe('VECTOR >> DISCO >> RIVET');
expect(decodeResult.formatted.items[6].label).toBe('Approach Procedure');
expect(decodeResult.formatted.items[6].value).toBe('ILS 17L starting at RIVET');
expect(decodeResult.formatted.items[7].label).toBe('Aircraft Route');
expect(decodeResult.formatted.items[7].value).toBe('TACKE');
expect(decodeResult.formatted.items[8].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[8].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.decodeLevel).toBe('full');
expect(decodeResult.decoder.name).toBe('label-h1-fpn');
expect(decodeResult.raw.flight_number).toBe('AAL1956')
expect(decodeResult.raw.flight_number).toBe('AAL1956');
expect(decodeResult.raw.company_route.waypoints).toBeUndefined();
expect(decodeResult.formatted.description).toBe('Flight Plan');
expect(decodeResult.formatted.items.length).toBe(5);
expect(decodeResult.formatted.items.length).toBe(9);
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');
expect(decodeResult.formatted.items[3].label).toBe('Company Route');
expect(decodeResult.formatted.items[3].value).toBe('PHLPHX61');
expect(decodeResult.formatted.items[4].label).toBe('Runway');
expect(decodeResult.formatted.items[4].value).toBe('27L(26O)');
expect(decodeResult.formatted.items[5].label).toBe('Departure Procedure');
expect(decodeResult.formatted.items[5].value).toBe('PHL3');
expect(decodeResult.formatted.items[6].label).toBe('Arrival Procedure');
expect(decodeResult.formatted.items[6].value).toBe('EAGUL6 starting at ZUN');
expect(decodeResult.formatted.items[7].label).toBe('Approach Procedure');
expect(decodeResult.formatted.items[7].value).toBe('ILS26: >> AIR(40.01 N, 80.49 W) > J110 > BOWRR >> VLA(39.056 N, 89.097 W) >> STL(38.516 N, 90.289 W) >> GIBSN(38.43 N, 92.244 W) >> TYGER(38.41 N, 94.05 W) >> GCK(37.551 N, 100.435 W) >> DIXAN(36.169 N, 105.573 W) >> ZUN(34.579 N, 109.093 W)');
makrsmark marked this conversation as resolved.
Show resolved Hide resolved
expect(decodeResult.formatted.items[8].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[8].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 });
Expand All @@ -105,17 +110,98 @@ test('decodes Label H1 Preamble FPN in-flight', () => {
expect(decodeResult.formatted.items[4].value).toBe('0xddfb');
});

test('decodes Label H1 Preamble FPN <invalid>', () => {

test('decodes Label H1 Preamble FPN with WS', () => {
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/2372685289
const text = 'FPN/TS140017,021724/RP:DA:EHAM:AA:KMSP..N55064W000477..N55163W001141..ERAKA..N60000W020000..N61000W030000:WS:N61000W030000,370..N61000W040000..N60000W050000..URTAK:WS:URTAK,380..LAKES:WS:LAKES,400..N57000W070000..N54300W080000..N49000W090000..DLH..COLDD:A:BAINY3:AP:ILS30L(30L)/PR4356,344,360,1060,,,13,,,30,,,P50,M40,36090,,3296,292/DTKMSP,30L,172,215117156D';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one is a beast - not sure how to approach it. adding it as a reference

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how you added a comment to the URL of the message for further info if we need it later.

Yeah, this is huge.

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.message_timestamp).toBe(1708178417);
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 Planned');
expect(decodeResult.formatted.items[1].label).toBe('Origin');
expect(decodeResult.formatted.items[1].value).toBe('EHAM');
expect(decodeResult.formatted.items[2].label).toBe('Destination');
expect(decodeResult.formatted.items[2].value).toBe('KMSP..N55064W000477..N55163W001141..ERAKA..N60000W020000..N61000W030000'); // FIXME - just 'KMSP'
expect(decodeResult.formatted.items[3].label).toBe('Arrival Procedure');
expect(decodeResult.formatted.items[3].value).toBe('BAINY3');
expect(decodeResult.formatted.items[4].label).toBe('Approach Procedure');
expect(decodeResult.formatted.items[4].value).toBe('ILS30L(30L)/PR4356,344,360,1060,,,13,,,30,,,P50,M40,36090,,3296,292/DTKMSP,30L,172,215117'); //FIXME - just 'ILS30L'
expect(decodeResult.formatted.items[5].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[5].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');
});

test('decodes Label H1 Preamble FPN with newlines', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_H1_FPN(decoder);

// https://app.airframes.io/messages/2381936957
const text = 'FPN/SN2125/FNQFA780/RI:DA:YPPH:CR:PERMEL001:AA:YMML..MEMUP,S33451E\r\n120525.Y53.WENDY0560'
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('QFA780');
expect(decodeResult.raw.serial_number).toBe('2125');
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 Inactive');
expect(decodeResult.formatted.items[1].label).toBe('Origin');
expect(decodeResult.formatted.items[1].value).toBe('YPPH');
expect(decodeResult.formatted.items[2].label).toBe('Company Route');
expect(decodeResult.formatted.items[2].value).toBe('PERMEL001');
expect(decodeResult.formatted.items[3].label).toBe('Destination');
expect(decodeResult.formatted.items[3].value).toBe('YMML..MEMUP,S33451E120525.Y53.WENDY'); //TODO - pull out route
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this item the Destination? It would be an ICAO, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it should be just YMML - and then there's the route that's "how to get there"

>> MEMUP(33.451 S, 120.525 E) > Y53 > WENDY)

I'm not sure where to put the route right now because if you look above, there can be more route information - i probably should merge but punted on the hard stuff right now

expect(decodeResult.formatted.items[4].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[4].value).toBe('0x0560');
});

test('decodes Label H1 Preamble FPN with SN and TS', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_H1_FPN(decoder);

// https://app.airframes.io/messages/2372685289
const text = 'FPN/TS155631,170224/SN155631/RP:DA:PHNL:AA:KASE:D:MKK5.KOLEA:F:KOLEA,N22354W155133..CLUTS,N23002W154393.R465.CINNY,N36109W124456..OAL,N38002W117462.J58.ILC,N38150W114237..EYELO,N38455W110469..SAKES,N38500W110163.J80.DBL,N39264W106537F5E1'
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.message_timestamp).toBe(Number.NaN); // DDMMYY instead of MMDDYY - need to figure out how to determine
expect(decodeResult.raw.serial_number).toBe('155631');
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 Planned');
expect(decodeResult.formatted.items[1].label).toBe('Origin');
expect(decodeResult.formatted.items[1].value).toBe('PHNL');
expect(decodeResult.formatted.items[2].label).toBe('Destination');
expect(decodeResult.formatted.items[2].value).toBe('KASE');
expect(decodeResult.formatted.items[3].label).toBe('Departure Procedure');
expect(decodeResult.formatted.items[3].value).toBe('MKK5 starting at KOLEA');
expect(decodeResult.formatted.items[4].label).toBe('Aircraft Route');
expect(decodeResult.formatted.items[4].value).toBe('KOLEA(22.354 N, 155.133 W) >> CLUTS(23.002 N, 154.393 W) > R465 > CINNY(36.109 N, 124.456 W) >> OAL(38.002 N, 117.462 W) > J58 > ILC(38.15 N, 114.237 W) >> EYELO(38.455 N, 110.469 W) >> SAKES(38.5 N, 110.163 W) > J80 > DBL(39.264 N, 106.537 W)');
expect(decodeResult.formatted.items[5].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[5].value).toBe('0xf5e1');
});

test('decodes Label H1 Preamble FPN <invalid>', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_H1_FPN(decoder);

const text = 'FPN Bogus message';
const decodeResult = decoderPlugin.decode({ text: text });
Expand Down
Loading
Loading