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

pull up c-band prefix decoding #174

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
14 changes: 14 additions & 0 deletions lib/MessageDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DecodeResult, DecoderPluginInterface, Message, Options } from './Decode

import * as Plugins from './plugins/official';
import { MIAMCoreUtils } from './utils/miam';
import { ResultFormatter } from './utils/result_formatter';

export class MessageDecoder {
name: string;
Expand Down Expand Up @@ -95,6 +96,14 @@ export class MessageDecoder {
}
}

// C-Band puts a 10 char header in front of some message types
// First 4 chars are some kind of message number
// Last 6 chars are the flight number
let cband = message.text.match(/^(?<msgno>[A-Z]\d{2}[A-Z])(?<airline>[A-Z0-9]{2})(?<number>[0-9]{4})/);
if (cband?.groups) {
message.text = message.text.substring(10);
}

// console.log('All plugins');
// console.log(this.plugins);
const usablePlugins = this.plugins.filter((plugin) => {
Expand Down Expand Up @@ -150,6 +159,11 @@ export class MessageDecoder {
}
}

if (cband?.groups && cband?.input) {
ResultFormatter.flightNumber(result, cband.groups.airline + Number(cband.groups.number));
message.text = cband.input;
}

if (options.debug) {
console.log('Result');
console.log(result);
Expand Down
2 changes: 1 addition & 1 deletion lib/plugins/Label_1L_Slash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,4 @@ export class Label_1L_Slash extends DecoderPlugin { // eslint-disable-line camel
}
}

export default {};
export default {};
2 changes: 1 addition & 1 deletion lib/plugins/Label_30_Slash_EA.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ test('decodes Label 30 sample 1', () => {
});

const text = '/EA1719/DSKSFO/SK23';
const decodeResult = decoderPlugin.decode({ text: text });
const decodeResult = decoder.decode({ label: "30", text: text });

expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
Expand Down
52 changes: 23 additions & 29 deletions lib/plugins/Label_4A.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,18 @@ test('matches Label 4A qualifiers', () => {

test('decodes Label 4A, variant 1', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_4A(decoder);

// https://app.airframes.io/messages/3451492279
const text = '063200,1910,.N343FR,FFT2028,KSLC,KORD,1,0632,RT0,LT0,';
const decodeResult = decoderPlugin.decode({ text: text });
const decodeResult = decoder.decode({ label: "4A", text: text });

expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
expect(decodeResult.decoder.name).toBe('label-4a');
expect(decodeResult.formatted.description).toBe('Latest New Format');
expect(decodeResult.message.text).toBe(text);
expect(decodeResult.remaining.text).toBe('RT0,LT0,');
expect(decodeResult.formatted.items.length).toBe(6);
expect(decodeResult.formatted.items.length).toBe(5);
expect(decodeResult.formatted.items[0].code).toBe('MSG_TOD');
expect(decodeResult.formatted.items[0].value).toBe('06:32:00');
expect(decodeResult.formatted.items[1].code).toBe('TAIL');
Expand All @@ -42,19 +41,18 @@ test('decodes Label 4A, variant 1', () => {

test('decodes Label 4A, variant 1, no callsign', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_4A(decoder);

// https://app.airframes.io/messages/3452310240
const text = '101606,1910,.N317FR,,KMDW,----,1,1016,RT0,LT1,';
const decodeResult = decoderPlugin.decode({ text: text });
const decodeResult = decoder.decode({ label: "4A", text: text });
Copy link
Collaborator

Choose a reason for hiding this comment

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

this isn't a bad idea, but it's going to make the PR huge. Do we want a separate PR that just makes this change? Or do we want to move the c-band messages to the MessageDecoder suite? I'm leaning towards moving the c-band tests

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Moving the c band message tests makes sense with two drawbacks:

  1. Need to remember to add tests in a second location for types with this prefix
  2. More importantly, there's no way to check if the cband prefix accidentally catches non cband messages and incorrectly truncates the message that it passes into a plugin.

Copy link
Collaborator

Choose a reason for hiding this comment

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

do we have messages (minus the prefix) that are unique to c-band? If not, i don't think we need to have c-band label-specific tests other than a couple to test the prefix extraction.

As for the second point, idk. Is it worth slowing down the test suite by making every test run through the plugin selection code just so we can verify this edge case that we don't even know exists?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not sure about any cband specific messages, but the regex for the prefix is pretty permissive. i would be surprised if it never matched any non cband messages. maybe it would be better to send more metadata about the source of the message into the decoders and just tell it explicitly if it's cband?

or we could try to decode as cband if the prefix is detected then retry with the prefix still attached if it fails to decode. that wouldn't necessarily catch every edge case though

Copy link
Collaborator

Choose a reason for hiding this comment

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

@kevinelliott and @andermatt64 - Do you have any opinions here?


expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
expect(decodeResult.decoder.name).toBe('label-4a');
expect(decodeResult.formatted.description).toBe('Latest New Format');
expect(decodeResult.message.text).toBe(text);
expect(decodeResult.remaining.text).toBe('RT0,LT1,');
expect(decodeResult.formatted.items.length).toBe(5);
expect(decodeResult.formatted.items.length).toBe(4);
expect(decodeResult.formatted.items[0].code).toBe('MSG_TOD');
expect(decodeResult.formatted.items[0].value).toBe('10:16:06');
expect(decodeResult.formatted.items[1].code).toBe('TAIL');
Expand All @@ -67,11 +65,10 @@ test('decodes Label 4A, variant 1, no callsign', () => {

test('decodes Label 4A, variant 2', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_4A(decoder);

// https://app.airframes.io/messages/3461807403
const text = 'N45129W093113MSP/07 ,204436123VECTORS,,P04,268044858,46904221';
const decodeResult = decoderPlugin.decode({ text: text });
const decodeResult = decoder.decode({ label: "4A", text: text });

expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
Expand All @@ -92,11 +89,10 @@ test('decodes Label 4A, variant 2', () => {

test('decodes Label 4A, variant 2, C-Band', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_4A(decoder);

// https://app.airframes.io/messages/3461407615
const text = 'M60ALH0752N22456E077014OSE35 ,192027370VEX36 ,192316,M46,275043309,85220111';
const decodeResult = decoderPlugin.decode({ text: text });
const decodeResult = decoder.decode({ label: "4A", text: text });

expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
Expand All @@ -105,25 +101,24 @@ test('decodes Label 4A, variant 2, C-Band', () => {
expect(decodeResult.message.text).toBe(text);
expect(decodeResult.remaining.text).toBe('275043309,85220111');
expect(decodeResult.formatted.items.length).toBe(5);
expect(decodeResult.formatted.items[0].code).toBe('FLIGHT');
expect(decodeResult.formatted.items[0].value).toBe('LH752');
expect(decodeResult.formatted.items[1].code).toBe('POS');
expect(decodeResult.formatted.items[1].value).toBe('22.456 N, 77.014 E');
expect(decodeResult.formatted.items[2].code).toBe('ALT');
expect(decodeResult.formatted.items[2].value).toBe('37000 feet');
expect(decodeResult.formatted.items[3].code).toBe('ROUTE');
expect(decodeResult.formatted.items[3].value).toBe('OSE35@19:20:27 > VEX36@19:23:16');
expect(decodeResult.formatted.items[4].code).toBe('OATEMP');
expect(decodeResult.formatted.items[4].value).toBe('-46 degrees');
expect(decodeResult.formatted.items[0].code).toBe('POS');
expect(decodeResult.formatted.items[0].value).toBe('22.456 N, 77.014 E');
expect(decodeResult.formatted.items[1].code).toBe('ALT');
expect(decodeResult.formatted.items[1].value).toBe('37000 feet');
expect(decodeResult.formatted.items[2].code).toBe('ROUTE');
expect(decodeResult.formatted.items[2].value).toBe('OSE35@19:20:27 > VEX36@19:23:16');
expect(decodeResult.formatted.items[3].code).toBe('OATEMP');
expect(decodeResult.formatted.items[3].value).toBe('-46 degrees');
expect(decodeResult.formatted.items[4].code).toBe('FLIGHT');
expect(decodeResult.formatted.items[4].value).toBe('LH752');
});

test('decodes Label 4A, variant 3', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_4A(decoder);

// https://globe.adsbexchange.com/?icao=A39AC6&showTrace=2024-09-22&timestamp=1727009085
const text = '124442,1320, 138,33467,N 41.093,W 72.677';
const decodeResult = decoderPlugin.decode({ text: text });
const decodeResult = decoder.decode({ label: "4A", text: text });

expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
Expand All @@ -144,15 +139,14 @@ test('decodes Label 4A, variant 3', () => {

test('decodes Label 4A_DIS <invalid>', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_4A(decoder);

// https://app.airframes.io/messages/3449413366
const text = 'DIS01,182103,WEN3100,WRONG CREW HAHAHA';
const decodeResult = decoderPlugin.decode({ text: text });
const decodeResult = decoder.decode({ label: "4A", text: text });

expect(decodeResult.decoded).toBe(false);
expect(decodeResult.decoder.decodeLevel).toBe('none');
expect(decodeResult.decoder.name).toBe('label-4a');
expect(decodeResult.formatted.description).toBe('Latest New Format');
expect(decodeResult.formatted.items.length).toBe(0);
// expect(decodeResult.decoded).toBe(false);
// expect(decodeResult.decoder.decodeLevel).toBe('none');
expect(decodeResult.decoder.name).not.toBe('label-4a');
// expect(decodeResult.formatted.description).toBe('Latest New Format');
// expect(decodeResult.formatted.items.length).toBe(0);
});
13 changes: 2 additions & 11 deletions lib/plugins/Label_4A.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,8 @@ export class Label_4A extends DecoderPlugin {
decodeResult.message = message;
decodeResult.formatted.description = 'Latest New Format';


// Inmarsat C-band seems to prefix normal messages with a message number and flight number
let text = message.text;
if (text.match(/^M\d{2}A\w{6}/)) {
ResultFormatter.flightNumber(decodeResult, message.text.substring(4, 10).replace(/^([A-Z]+)0*/g, "$1"));
text = text.substring(10);
}

decodeResult.decoded = true;
const fields = text.split(",");
const fields = message.text.split(",");
if (fields.length === 11) {
// variant 1
ResultFormatter.time_of_day(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[0]));
Expand All @@ -38,7 +30,6 @@ export class Label_4A extends DecoderPlugin {
ResultFormatter.callsign(decodeResult, fields[3]);
ResultFormatter.departureAirport(decodeResult, fields[4]);
ResultFormatter.arrivalAirport(decodeResult, fields[5]);
ResultFormatter.altitude(decodeResult, Number(text.substring(48, 51)) * 100);
ResultFormatter.unknownArr(decodeResult, fields.slice(8));
} else if (fields.length === 6) {
if (fields[0].match(/^[NS]/)) {
Expand Down Expand Up @@ -68,7 +59,7 @@ export class Label_4A extends DecoderPlugin {
}
} else {
decodeResult.decoded = false;
ResultFormatter.unknown(decodeResult, text);
ResultFormatter.unknown(decodeResult, message.text);
}

if (decodeResult.decoded) {
Expand Down
41 changes: 18 additions & 23 deletions lib/plugins/Label_4N.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ test('matches Label 4N qualifiers', () => {

test('decodes Label 4N variant 1', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_4N(decoder);

// https://globe.adsbexchange.com/?icao=A15027&showTrace=2024-09-23&timestamp=1727057017
const text = '22024N MCI JFK1\r\n0013 0072 N040586 W074421 230';
const decodeResult = decoderPlugin.decode({ text: text });
const decodeResult = decoder.decode({ label: "4N", text: text });

expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
Expand All @@ -37,11 +36,10 @@ test('decodes Label 4N variant 1', () => {

test('decodes Label 4N variant 2B', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_4N(decoder);

// https://app.airframes.io/messages/3421601874
const text = '285,B,69005074-507,10/12,+36.081,-094.810,35014,002.3,ELP,SDF,SDF,17R/,17L/,0,0,,,,,,0,0,0,0,1,,,,,247.0,014.2,261.2,421A';
const decodeResult = decoderPlugin.decode({ text: text });
const decodeResult = decoder.decode({ label: "4N", text: text });

expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
Expand Down Expand Up @@ -71,11 +69,10 @@ test('decodes Label 4N variant 2B', () => {

test('decodes Label 4N variant 2C', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_4N(decoder);

// https://globe.adsbexchange.com/?icao=A3E08D&showTrace=2024-09-24&timestamp=1727181643
const text = '285,C,,09/24,,,,,EWR,PHL,PHL,09R/,/,0,0,,,,,,1,0,0,0,1,0,,0,0,198.5,014.5,213.0,9BCD';
const decodeResult = decoderPlugin.decode({ text: text });
const decodeResult = decoder.decode({ label: "4N", text: text });

expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
Expand All @@ -99,11 +96,10 @@ test('decodes Label 4N variant 2C', () => {

test('decodes Label 4N variant 2C (C-band)', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_4N(decoder);

// https://app.airframes.io/messages/3422221702
const text = 'M85AUP0109285,C,,10/12,,,,,NRT,ANC,ANC,07R/,33/,0,0,,,,,,0,0,0,0,1,0,,0,0,709.8,048.7,758.5,75F3';
const decodeResult = decoderPlugin.decode({ text: text });
const decodeResult = decoder.decode({ label: "4N", text: text });

expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
Expand All @@ -114,28 +110,27 @@ test('decodes Label 4N variant 2C (C-band)', () => {
expect(decodeResult.raw.date).toBe('10/12');
expect(decodeResult.remaining.text).toBe('C,0,0,0,0,0,0,1,0,0,0,709.8,048.7,758.5');
expect(decodeResult.formatted.items.length).toBe(7);
expect(decodeResult.formatted.items[0].code).toBe('FLIGHT');
expect(decodeResult.formatted.items[0].value).toBe('UP109');
expect(decodeResult.formatted.items[1].code).toBe('ORG');
expect(decodeResult.formatted.items[1].value).toBe('NRT');
expect(decodeResult.formatted.items[2].code).toBe('DST');
expect(decodeResult.formatted.items[0].code).toBe('ORG');
expect(decodeResult.formatted.items[0].value).toBe('NRT');
expect(decodeResult.formatted.items[1].code).toBe('DST');
expect(decodeResult.formatted.items[1].value).toBe('ANC');
expect(decodeResult.formatted.items[2].code).toBe('ALT_DST');
expect(decodeResult.formatted.items[2].value).toBe('ANC');
expect(decodeResult.formatted.items[3].code).toBe('ALT_DST');
expect(decodeResult.formatted.items[3].value).toBe('ANC');
expect(decodeResult.formatted.items[4].code).toBe('ARWY');
expect(decodeResult.formatted.items[4].value).toBe('07R');
expect(decodeResult.formatted.items[5].code).toBe('ALT_ARWY');
expect(decodeResult.formatted.items[5].value).toBe('33');
expect(decodeResult.formatted.items[6].code).toBe('CHECKSUM');
expect(decodeResult.formatted.items[6].value).toBe('0x75f3');
expect(decodeResult.formatted.items[3].code).toBe('ARWY');
expect(decodeResult.formatted.items[3].value).toBe('07R');
expect(decodeResult.formatted.items[4].code).toBe('ALT_ARWY');
expect(decodeResult.formatted.items[4].value).toBe('33');
expect(decodeResult.formatted.items[5].code).toBe('CHECKSUM');
expect(decodeResult.formatted.items[5].value).toBe('0x75f3');
expect(decodeResult.formatted.items[6].code).toBe('FLIGHT');
expect(decodeResult.formatted.items[6].value).toBe('UP109');
});

test('decodes Label 4N <invalid>', () => {
const decoder = new MessageDecoder();
const decoderPlugin = new Label_4N(decoder);

const text = '4N Bogus message';
const decodeResult = decoderPlugin.decode({ text: text });
const decodeResult = decoder.decode({ label: "4N", text: text });

expect(decodeResult.decoded).toBe(false);
expect(decodeResult.decoder.decodeLevel).toBe('none');
Expand Down
25 changes: 9 additions & 16 deletions lib/plugins/Label_4N.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,16 @@ export class Label_4N extends DecoderPlugin {
decodeResult.message = message;
decodeResult.formatted.description = 'Airline Defined';

// Inmarsat C-band seems to prefix normal messages with a message number and flight number
let text = message.text;
if (text.match(/^M\d{2}A\w{6}/)) {
ResultFormatter.flightNumber(decodeResult, message.text.substring(4, 10).replace(/^([A-Z]+)0*/g, "$1"));
text = text.substring(10);
}

decodeResult.decoded = true;
const fields = text.split(",");
if (text.length === 51) {
const fields = message.text.split(",");
if (message.text.length === 51) {
// variant 1
decodeResult.raw.day = text.substring(0, 2);
ResultFormatter.departureAirport(decodeResult, text.substring(8, 11));
ResultFormatter.arrivalAirport(decodeResult, text.substring(13, 16));
ResultFormatter.position(decodeResult, CoordinateUtils.decodeStringCoordinatesDecimalMinutes(text.substring(30, 45).replace(/^(.)0/, "$1")));
ResultFormatter.altitude(decodeResult, Number(text.substring(48, 51)) * 100);
ResultFormatter.unknownArr(decodeResult, [text.substring(2, 4), text.substring(19, 29)], " ");
decodeResult.raw.day = message.text.substring(0, 2);
ResultFormatter.departureAirport(decodeResult, message.text.substring(8, 11));
ResultFormatter.arrivalAirport(decodeResult, message.text.substring(13, 16));
ResultFormatter.position(decodeResult, CoordinateUtils.decodeStringCoordinatesDecimalMinutes(message.text.substring(30, 45).replace(/^(.)0/, "$1")));
ResultFormatter.altitude(decodeResult, Number(message.text.substring(48, 51)) * 100);
ResultFormatter.unknownArr(decodeResult, [message.text.substring(2, 4), message.text.substring(19, 29)], " ");
} else if (fields.length === 33) {
// variant 2
decodeResult.raw.date = fields[3];
Expand All @@ -53,7 +46,7 @@ export class Label_4N extends DecoderPlugin {
ResultFormatter.unknownArr(decodeResult, [...fields.slice(1,3), fields[7], ...fields.slice(13, 32)].filter((f) => f != ""));
} else {
decodeResult.decoded = false;
ResultFormatter.unknown(decodeResult, text);
ResultFormatter.unknown(decodeResult, message.text);
}

if (decodeResult.decoded) {
Expand Down
Loading