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 1 commit
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
45 changes: 30 additions & 15 deletions lib/plugins/Label_H1_FPN.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,28 @@ test('decodes Label H1 Preamble FPN landing', () => {
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();
Expand All @@ -53,21 +59,30 @@ test('decodes Label H1 Preamble FPN full flight', () => {
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', () => {
Expand Down
70 changes: 63 additions & 7 deletions lib/plugins/Label_H1_FPN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,21 @@ export class Label_H1_FPN extends DecoderPlugin {
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 'A': // Arrival Procedure (?)
addProcedure(decodeResult, value, 'arrival');
break;
case 'AA':
addArrivalAirport(decodeResult, value);
break;
// case 'CR': // Current Route (?)
// break;
// case 'D': // Departure Procedure
// break;
case 'AP':
addProcedure(decodeResult, value, 'approach');
break;
case 'CR':
addCompanyRoute(decodeResult, value);
break;
case 'D': // Departure Procedure
addProcedure(decodeResult, value, 'departure');
break;
case 'DA':
addDepartureAirport(decodeResult, value);
break;
Expand All @@ -47,6 +53,7 @@ export class Label_H1_FPN extends DecoderPlugin {
break;
case 'R':
addDepartureRunway(decodeResult, value);
break;
default:
if(allKnownFields) {
decodeResult.remaining.text = '';
Expand Down Expand Up @@ -124,6 +131,55 @@ function addArrivalAirport(decodeResult: any, value: string) {
});
};

function addProcedure(decodeResult: any, 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: any, value: string) {
const segments = value.split('.');
const parens_idx = segments[0].indexOf('(');
let name;
let runway;
if(parens_idx === -1) {
name = segments[0];
} else {
name = segments[0].slice(0, parens_idx);
runway = segments[0].slice(parens_idx+1, segments[0].indexOf(')'));
}
let waypoints;
if(segments.length > 1) {
waypoints = segments.slice(1).map((leg) => RouteUtils.getWaypoint(leg));
}
decodeResult.raw.company_route = {
name: name,
runway: runway,
waypoints: waypoints,
};
decodeResult.formatted.items.push({
type: 'company_route',
code: 'CR',
label: 'Company Route',
value: RouteUtils.routeToString(decodeResult.raw.company_route),
});
};

function addDepartureAirport(decodeResult: any, value: string) {
decodeResult.raw.departure_icao = value;
decodeResult.formatted.items.push({
Expand All @@ -145,7 +201,7 @@ function addDepartureRunway(decodeResult: any, value: string) {

function addRoute(decodeResult: any, value: string) {
const route = value.split('.');
decodeResult.raw.route = route.map((leg)=> RouteUtils.getWaypoint(leg));
decodeResult.raw.route = {waypoints: route.map((leg)=> RouteUtils.getWaypoint(leg))};
decodeResult.formatted.items.push({
type: 'aircraft_route',
code: 'ROUTE',
Expand Down
7 changes: 4 additions & 3 deletions lib/plugins/Label_H1_M1BPOS.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DecoderPlugin } from '../DecoderPlugin';
import { CoordinateUtils } from '../utils/coordinate_utils';
import { RouteUtils } from '../utils/route_utils';

export class Label_H1_M1BPOS extends DecoderPlugin { // eslint-disable-line camelcase
name = 'label-h1-m1bpos';
Expand Down Expand Up @@ -33,9 +34,9 @@ export class Label_H1_M1BPOS extends DecoderPlugin { // eslint-disable-line came
});
}

let route = items.slice(1).filter((part: any) => !/^\d(.+)$/.test(part));
route = route.map((hop: any) => hop || '?');
decodeResult.raw.route = route;
const route = items.slice(1).filter((part: any) => !/^\d(.+)$/.test(part));
const waypoints = route.map((hop: any) => RouteUtils.getWaypoint(hop || '?'));
decodeResult.raw.route = {waypoints: waypoints};

decodeResult.formatted.description = 'Position Report';

Expand Down
18 changes: 10 additions & 8 deletions lib/plugins/Label_H1_POS.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DateTimeUtils } from '../DateTimeUtils';
import { DecoderPlugin } from '../DecoderPlugin';
import { Waypoint } from '../types/waypoint';
import { RouteUtils } from '../utils/route_utils';

export class Label_H1_POS extends DecoderPlugin {
Expand All @@ -24,7 +25,7 @@ export class Label_H1_POS extends DecoderPlugin {
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.raw.route = {waypoints: data.substring(3,data.length).split('.').map((leg: string) => RouteUtils.getWaypoint(leg))};
decodeResult.formatted.items.push({
type: 'aircraft_route',
code: 'ROUTE',
Expand Down Expand Up @@ -126,16 +127,17 @@ export class Label_H1_POS extends DecoderPlugin {
longitude: decodeResult.raw.longitude * (decodeResult.raw.longitude_direction === 'W' ? -1 : 1),
};

let waypoints : Waypoint[];
if(fields.length == 11) {//variant 1
decodeResult.raw.route = [{name: fields[1] || '?,', time: convertDateTimeToEpoch(fields[2], fields[10]), timeFormat: 'epoch'},
{name: fields[4] || '?', time: convertDateTimeToEpoch(fields[5], fields[10]), timeFormat: 'epoch'},
{name: fields[6] || '?'}];
waypoints = [{name: fields[1] || '?,', time: convertDateTimeToEpoch(fields[2], fields[10]), timeFormat: 'epoch'},
{name: fields[4] || '?', time: convertDateTimeToEpoch(fields[5], fields[10]), timeFormat: 'epoch'},
{name: fields[6] || '?'}]
} else {
decodeResult.raw.route = [{name: fields[1] || '?,', time: convertHHMMSSToTod(fields[2]), timeFormat: 'tod'},
{name: fields[4] || '?', time: convertHHMMSSToTod(fields[5]), timeFormat: 'tod'},
{name: fields[6] || '?'}];
waypoints = [{name: fields[1] || '?,', time: convertHHMMSSToTod(fields[2]), timeFormat: 'tod'},
{name: fields[4] || '?', time: convertHHMMSSToTod(fields[5]), timeFormat: 'tod'},
{name: fields[6] || '?'}];
}

decodeResult.raw.route = {waypoints: waypoints};
decodeResult.raw.outside_air_temperature = Number(fields[7].substring(1)) * (fields[7].charAt(0) === 'M' ? -1 : 1);

decodeResult.formatted.items.push({
Expand Down
17 changes: 17 additions & 0 deletions lib/types/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Waypoint } from "./waypoint";

/**
* Representation of a route
*
* Typically a list of waypoints, this can also be a named company route.
*/
export interface Route {
/** optional name. If not set, `waypoints` is required */
name?: string,

/** optional runway */
runway?: string,

/** optional list of waypoints. If not set, `name` is required */
waypoints?: Waypoint[],
}
34 changes: 30 additions & 4 deletions lib/utils/route_utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
import { Route } from "../types/route";
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( ' > ').replaceAll('> >', '>>');
public static routeToString(route: Route): string {
let str = '';
if(route.name) {
str += route.name;
}
if(route.runway) {
str += `(${route.runway})`;
}
if(str.length!==0 && route.waypoints && route.waypoints.length === 1) {
str += ' starting at '
}
else if(str.length!==0 && route.waypoints) {
str += ': ';
}

if(route.waypoints) {
str += RouteUtils.waypointsToString(route.waypoints);
}

return str;
}

public static waypointToString(waypoint: Waypoint): string {
Expand Down Expand Up @@ -33,11 +52,18 @@ export class RouteUtils {

// move out if we want public
private static timestampToString(time: number, format: 'tod' | 'epoch'): string {
const date = new Date(time * 1000);
if(format == 'tod') {
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";
}

private static waypointsToString(waypoints: Waypoint[]): string {
let str = waypoints.map((x) => RouteUtils.waypointToString(x)).join( ' > ').replaceAll('> >', '>>');
if(str.startsWith(' > ')) {
str = '>>' + str.slice(2);
}
return str;
}
}
Loading