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 H1 POS parsing #60

Merged
merged 3 commits into from
Mar 21, 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
3 changes: 2 additions & 1 deletion lib/plugins/Label_H1_FPN.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ test('decodes Label H1 Preamble FPN with SN and TS', () => {
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.message_timestamp).toBe(1708185391);
expect(decodeResult.raw.serial_number).toBe('155631');
expect(decodeResult.formatted.description).toBe('Flight Plan');
expect(decodeResult.formatted.items.length).toBe(6);
Expand Down Expand Up @@ -247,6 +247,7 @@ test('decodes Label H1 #M1BFPN No Preamble', () => {
expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('full'); // should be partial
expect(decodeResult.decoder.name).toBe('label-h1-fpn');
expect(decodeResult.raw.flight_number).toBe('AKL0767');
expect(decodeResult.raw.message_timestamp).toBe(1708730408);
expect(decodeResult.formatted.description).toBe('Flight Plan');
expect(decodeResult.formatted.items.length).toBe(7);
Expand Down
81 changes: 75 additions & 6 deletions lib/plugins/Label_H1_POS.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ test('matches Label H1 Preamble POS qualifiers', () => {
expect(decoderPlugin.qualifiers).toBeDefined();
expect(decoderPlugin.qualifiers()).toEqual({
labels: ['H1'],
preambles: ['POS', '#M1BPOS'],
preambles: ['POS', '#M1BPOS', '/.POS'],
});
});
test('decodes Label H1 Preamble POS variant 1', () => {
Expand Down Expand Up @@ -293,7 +293,7 @@ test('decodes Label H1 Preamble #M1BPOS long variant', () => {
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('Departure Runway');
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');
Expand All @@ -314,7 +314,7 @@ test('decodes Label H1 Preamble #M1BPOS long variant', () => {
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('decodes Label H1 Preamble POS variant 6', () => {
test('decodes Label H1 Preamble POS variant 7', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_H1_POS(decoder);

Expand All @@ -338,10 +338,10 @@ test('decodes Label H1 Preamble POS variant 6', () => {
expect(decodeResult.formatted.items[3].value).toBe('-28');
expect(decodeResult.formatted.items[4].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[4].value).toBe('0x9071');
expect(decodeResult.remaining.text).toBe(',/ID91459S,BANKR31,142813/MR64,0,/ET31539,27619,MT370/CG311,160,350/FB732/VR32');
expect(decodeResult.remaining.text).toBe('/ID91459S,BANKR31,142813/MR64,0,/ET31539,27619,MT370/CG311,160,350/FB732/VR32');
});

test('decodes Label H1 Preamble #M1BPOS variant 6', () => {
test('decodes Label H1 Preamble #M1BPOS variant 7', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_H1_POS(decoder);

Expand All @@ -354,6 +354,7 @@ test('decodes Label H1 Preamble #M1BPOS variant 6', () => {
expect(decodeResult.decoder.decodeLevel).toBe('partial');
expect(decodeResult.decoder.name).toBe('label-h1-pos');
expect(decodeResult.formatted.description).toBe('Position Report');
expect(decodeResult.raw.flight_number).toBe('AMCLL93');
expect(decodeResult.formatted.items.length).toBe(5);
expect(decodeResult.formatted.items[0].label).toBe('Aircraft Position');
expect(decodeResult.formatted.items[0].value).toBe('42.579 N, 108.090 W');
Expand All @@ -365,7 +366,75 @@ test('decodes Label H1 Preamble #M1BPOS variant 6', () => {
expect(decodeResult.formatted.items[3].value).toBe('-49');
expect(decodeResult.formatted.items[4].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[4].value).toBe('0x4e17');
expect(decodeResult.remaining.text).toBe(',F37AMCLL93/ID746026,,173207/MR1,,/ET031846,267070,T468/CG264,110,360/FB742/VR32');
expect(decodeResult.remaining.text).toBe('F37#M1B/ID746026,,173207/MR1,,/ET031846,267070,T468/CG264,110,360/FB742/VR32');
});

test('decodes Label H1 Preamble POS variant 8', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_H1_POS(decoder);

// https://app.airframes.io/messages/2500335076
const text = 'POS/TS080616,210324/DTMMGL,29O,64,103316/PR1754,231,350,189,,0,0,,M45,185,,,P16,P0,36000,,1565,250/RP:DA:MMTJ:AA:MMGL:R:27O:D:TUMA2B..SANFE.UT4..LMM:A:LONV1D:AP:ILSZ29.PLADE(29O)9D1C';
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.formatted.items.length).toBe(11);
expect(decodeResult.formatted.items[0].label).toBe('Destination');
expect(decodeResult.formatted.items[0].value).toBe('MMGL');
expect(decodeResult.formatted.items[1].label).toBe('Arrival Runway');
expect(decodeResult.formatted.items[1].value).toBe('29O');
expect(decodeResult.formatted.items[2].label).toBe('Outside Air Temperature (C)');
expect(decodeResult.formatted.items[2].value).toBe('-45');
expect(decodeResult.formatted.items[3].label).toBe('Route Status');
expect(decodeResult.formatted.items[3].value).toBe('Route Planned');
expect(decodeResult.formatted.items[4].label).toBe('Origin');
expect(decodeResult.formatted.items[4].value).toBe('MMTJ');
expect(decodeResult.formatted.items[5].label).toBe('Destination');
expect(decodeResult.formatted.items[5].value).toBe('MMGL');
expect(decodeResult.formatted.items[6].label).toBe('Departure Runway');
expect(decodeResult.formatted.items[6].value).toBe('27O');
expect(decodeResult.formatted.items[7].label).toBe('Departure Procedure');
expect(decodeResult.formatted.items[7].value).toBe('TUMA2B: >> SANFE > UT4 >> LMM');
expect(decodeResult.formatted.items[8].label).toBe('Arrival Procedure');
expect(decodeResult.formatted.items[8].value).toBe('LONV1D');
expect(decodeResult.formatted.items[9].label).toBe('Approach Procedure');
expect(decodeResult.formatted.items[9].value).toBe('ILSZ29 starting at PLADE(29O)');
expect(decodeResult.formatted.items[10].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[10].value).toBe('0x9d1c');
expect(decodeResult.remaining.text).toBe(',64,103316/PR1754,231,350,189,,0,0,,185,,,P16,P0,36000,,1565');
});
test('decodes Label H1 Preamble /.POS variant 2', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_H1_POS(decoder);

// https://app.airframes.io/messages/2500488708
const text = '/.POS/TS100316,210324/PSS35333W058220,,100316,250,S37131W059150,101916,S39387W060377,M23,27282,241,780,MANUAL,0,813E711';
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.raw.message_timestamp).toBe(1711015396);
expect(decodeResult.formatted.items.length).toBe(6);
expect(decodeResult.formatted.items[0].label).toBe('Aircraft Position');
expect(decodeResult.formatted.items[0].value).toBe('35.333 S, 58.220 W');
expect(decodeResult.formatted.items[1].label).toBe('Altitude');
expect(decodeResult.formatted.items[1].value).toBe('25000 feet');
expect(decodeResult.formatted.items[2].label).toBe('Aircraft Route');
expect(decodeResult.formatted.items[2].value).toBe('@10:03:16 > (37.131 S, 59.150 W)@10:19:16 > (39.387 S, 60.377 W)');
expect(decodeResult.formatted.items[3].label).toBe('Outside Air Temperature (C)');
expect(decodeResult.formatted.items[3].value).toBe('-23');
expect(decodeResult.formatted.items[4].label).toBe('Aircraft Groundspeed');
expect(decodeResult.formatted.items[4].value).toBe('780');
expect(decodeResult.formatted.items[5].label).toBe('Message Checksum');
expect(decodeResult.formatted.items[5].value).toBe('0xe711');
expect(decodeResult.remaining.text).toBe('/.POS,27282,241,MANUAL,0,813');
});

test('decodes Label H1 Preamble #M1BPOS <invalid>', () => {
Expand Down
111 changes: 52 additions & 59 deletions lib/plugins/Label_H1_POS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class Label_H1_POS extends DecoderPlugin {
qualifiers() { // eslint-disable-line class-methods-use-this
return {
labels: ["H1"],
preambles: ['POS', '#M1BPOS'], //TODO - support data before #
preambles: ['POS', '#M1BPOS', '/.POS'], //TODO - support data before #
};
}

Expand All @@ -21,33 +21,27 @@ export class Label_H1_POS extends DecoderPlugin {
decodeResult.decoder.name = this.name;
decodeResult.formatted.description = 'Position Report';
decodeResult.message = message;
decodeResult.remaining.text = '';

const checksum = message.text.slice(-4);
const header = findHeader(message.text.slice(0,-4));
const decoded = FlightPlanUtils.processFlightPlan(decodeResult, header.split(':'));
//strip POS and checksum
const parts = message.text.replace('#M1B', '').replace('POS', '').slice(0,-4).split(',');
if(parts.length==1 && parts[0].startsWith('/RF')) {
// TODO - use FlightPlanUtils to parse
decodeResult.raw.route_status == 'RF'
decodeResult.formatted.items.push({
type: 'status',
code: 'ROUTE_STATUS',
label: 'Route Status',
value: 'Route Filed',
});
decodeResult.raw.route = {waypoints: parts[0].substring(3,parts[0].length).split('.').map((leg: string) => RouteUtils.getWaypoint(leg))};
decodeResult.formatted.items.push({
type: 'aircraft_route',
code: 'ROUTE',
label: 'Aircraft Route',
value: RouteUtils.routeToString(decodeResult.raw.route),
});

processChecksum(decodeResult, checksum);
decodeResult.decoded = true;
decodeResult.decoder.decodeLevel = 'full';
const parts = message.text.replace(header, '').slice(0,-4).split(',');
if(parts.length==1) {
if(decoded) {
processChecksum(decodeResult, checksum);
decodeResult.decoded = true;
decodeResult.decoder.decodeLevel = 'full';
} else if(decodeResult.remaining.text.length > 0) {
processChecksum(decodeResult, checksum);
decodeResult.decoded = true;
decodeResult.decoder.decodeLevel = 'partial';
} else {
decodeResult.decoded = false;
decodeResult.decoder.decodeLevel = 'none';
}
} else if(parts.length === 10) { // variant 4
decodeResult.remaining.text = ''
processPosition(decodeResult, parts[0]);
processAlt(decodeResult, parts[3]);
processRoute(decodeResult, parts[1], parts[2], parts[4], parts[5], parts[6]);
processTemp(decodeResult, parts[7]);
Expand All @@ -59,8 +53,6 @@ export class Label_H1_POS extends DecoderPlugin {
decodeResult.decoder.decodeLevel = 'partial';
} else if(parts.length === 11) { // variant 1

decodeResult.remaining.text = ''
processPosition(decodeResult, parts[0]);
processAlt(decodeResult, parts[3]);
processRoute(decodeResult, parts[1], parts[2], parts[4], parts[5], parts[6], parts[10]);
processTemp(decodeResult, parts[7]);
Expand All @@ -72,8 +64,6 @@ export class Label_H1_POS extends DecoderPlugin {
decodeResult.decoder.decodeLevel = 'partial';
} else if(parts.length === 14) { // variant 2

decodeResult.remaining.text = ''
processPosition(decodeResult, parts[0]);
processAlt(decodeResult, parts[3]);
processRoute(decodeResult, parts[1], parts[2], parts[4], parts[5], parts[6]);
processTemp(decodeResult, parts[7]);
Expand All @@ -88,8 +78,6 @@ export class Label_H1_POS extends DecoderPlugin {
decodeResult.decoded = true;
decodeResult.decoder.decodeLevel = 'partial';
} else if(parts.length === 15) { // variant 6
decodeResult.remaining.text = ''
processUnknown(decodeResult, parts[0]);
processUnknown(decodeResult, parts[1]);
let date = undefined;
if(parts[2].startsWith('/DC')) {
Expand Down Expand Up @@ -122,9 +110,17 @@ export class Label_H1_POS extends DecoderPlugin {

decodeResult.decoded = true;
decodeResult.decoder.decodeLevel = 'partial';
} else if(parts.length === 32) { // #M1B long variant
decodeResult.remaining.text = ''
processPosition(decodeResult, parts[0]);
} else if(parts.length === 21) { // variant 8
processRunway(decodeResult, parts[1]);
processUnknown(decodeResult,parts.slice(2,11).join(','));
processTemp(decodeResult, parts[11]);
processUnknown(decodeResult,parts.slice(12,20).join(','));
FlightPlanUtils.processFlightPlan(decodeResult, parts[20].split(':'));
processChecksum(decodeResult, checksum);

decodeResult.decoded = true;
decodeResult.decoder.decodeLevel = 'partial';
}else if(parts.length === 32) { // #M1B long variant
processRunway(decodeResult, parts[1]);
const time = parts[2];
processUnknown(decodeResult, parts[3]);
Expand Down Expand Up @@ -153,7 +149,7 @@ export class Label_H1_POS extends DecoderPlugin {
if (options.debug) {
console.log(`Decoder: Unknown H1 message: ${message.text}`);
}
decodeResult.remaining.text = message.text;
decodeResult.remaining.text += message.text;
decodeResult.decoded = false;
decodeResult.decoder.decodeLevel = 'none';
}
Expand Down Expand Up @@ -200,31 +196,11 @@ function processTemp(decodeResult: any, value: string) {
}

function processRunway(decodeResult: any, value: string) {
decodeResult.raw.departure_runway = value.replace('RW', '');
decodeResult.raw.arrival_runway = value.replace('RW', '');
decodeResult.formatted.items.push({
type: 'runway',
label: 'Departure Runway',
value: decodeResult.raw.departure_runway,
});
};

function processDeptApt(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 processArrvApt(decodeResult: any, value: string) {
decodeResult.raw.arrival_icao = value;
decodeResult.formatted.items.push({
type: 'destination',
code: 'DST',
label: 'Destination',
value: decodeResult.raw.arrival_icao,
label: 'Arrival Runway',
value: decodeResult.raw.arrival_runway,
});
};

Expand All @@ -241,15 +217,16 @@ function processGndspd(decodeResult: any, value: string) {
function processRoute(decodeResult: any, last: string, time: string, next: string, eta: string, then?: string, date?: string) {
const lastCoords = CoordinateUtils.decodeStringCoordinates(last);
const nextCoords = CoordinateUtils.decodeStringCoordinates(next);
const thenCoords = then? CoordinateUtils.decodeStringCoordinates(then) : undefined;
const lastTime = date ? DateTimeUtils.convertDateTimeToEpoch(time, date) : DateTimeUtils.convertHHMMSSToTod(time);
const nextTime = date ? DateTimeUtils.convertDateTimeToEpoch(eta, date) : DateTimeUtils.convertHHMMSSToTod(eta);
const timeFormat = date ? 'epoch' : 'tod';
const lastWaypoint: Waypoint = lastCoords ? {name: '', latitude: lastCoords.latitude, longitude: lastCoords.longitude, time: lastTime, timeFormat: timeFormat}
: {name: last, time: lastTime, timeFormat: timeFormat};
const nextWaypoint: Waypoint = nextCoords ? {name: '', latitude: nextCoords.latitude, longitude: nextCoords.longitude, time: nextTime, timeFormat: timeFormat}
: {name: next, time: nextTime, timeFormat: timeFormat};

const waypoints : Waypoint[] = [lastWaypoint, nextWaypoint, {name: then || '?'}];
const thenWaypoint: Waypoint = thenCoords ? {name: '', latitude: thenCoords.latitude, longitude: thenCoords.longitude} : {name: then || '?'}
const waypoints : Waypoint[] = [lastWaypoint, nextWaypoint, thenWaypoint];
decodeResult.raw.route = {waypoints: waypoints};
decodeResult.formatted.items.push({
type: 'aircraft_route',
Expand All @@ -268,3 +245,19 @@ function processChecksum(decodeResult: any, value: string) {
value: '0x' + ('0000' + decodeResult.raw.checksum.toString(16)).slice(-4),
});
}

/**
* naive implementation to find the end of the
*
* corrently only lookint at `/TS in which the format is `/TSHHMMSS,DDMMYY
* @param text
* @returns
*/
function findHeader(text: string) {
const parts = text.split(',');
const header = parts[0];
if(header.indexOf('/TS') === -1) {
return header;
}
return parts[0] + ',' + parts[1];
}
Loading