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

Add Label 22 Takeoff Report #180

Merged
merged 3 commits into from
Oct 30, 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/MessageDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export class MessageDecoder {
this.registerPlugin(new Plugins.Label_1L_Slash(this));
this.registerPlugin(new Plugins.Label_20_POS(this));
this.registerPlugin(new Plugins.Label_21_POS(this));
this.registerPlugin(new Plugins.Label_22(this));
this.registerPlugin(new Plugins.Label_22_OFF(this));
this.registerPlugin(new Plugins.Label_22_POS(this));
this.registerPlugin(new Plugins.Label_24_Slash(this));
this.registerPlugin(new Plugins.Label_30_Slash_EA(this));
this.registerPlugin(new Plugins.Label_44_ETA(this));
Expand Down
112 changes: 112 additions & 0 deletions lib/plugins/Label_22_OFF.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { MessageDecoder } from '../MessageDecoder';
import { Label_22_OFF } from './Label_22_OFF';

describe('Label 22 OFF', () => {
let plugin: Label_22_OFF;

beforeEach(() => {
const decoder = new MessageDecoder();
plugin = new Label_22_OFF(decoder);
});

test('matches qualifiers', () => {
expect(plugin.decode).toBeDefined();
expect(plugin.name).toBe('label-22-off');
expect(plugin.qualifiers).toBeDefined();
expect(plugin.qualifiers()).toEqual({
labels: ['22'],
preambles: ['OFF'],
});
});

test('decodes variant 1', () => {
const text = 'OFF01YX3661/25251712KIADKPWM171207 92'
const decodeResult = plugin.decode({ text: text });
expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
expect(decodeResult.raw.flight_number).toBe('YX3661');
expect(decodeResult.raw.departure_day_of_month).toBe(25);
expect(decodeResult.raw.arrival_day_of_month).toBe(25);
expect(decodeResult.raw.time_of_day).toBe(61920);
expect(decodeResult.raw.off_time).toBe(61927);
expect(decodeResult.raw.departure_icao).toBe('KIAD');
expect(decodeResult.raw.arrival_icao).toBe('KPWM');
expect(decodeResult.formatted.items.length).toBe(7);
expect(decodeResult.formatted.items[0].label).toBe('Flight Number');
expect(decodeResult.formatted.items[0].value).toBe('YX3661');
expect(decodeResult.formatted.items[1].label).toBe('Departure Day');
expect(decodeResult.formatted.items[1].value).toBe('25');
expect(decodeResult.formatted.items[2].label).toBe('Arrival Day');
expect(decodeResult.formatted.items[2].value).toBe('25');
expect(decodeResult.formatted.items[3].label).toBe('Message Timestamp');
expect(decodeResult.formatted.items[3].value).toBe('17:12:00');
Comment on lines +41 to +42
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 is a little weird, but what I'm calling the timestamp has minute resolution, but the actual takeoff time has second resolution. Open to ideas

Copy link
Contributor

Choose a reason for hiding this comment

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

I would say that as long as the raw value is still defined with seconds, the formatted can be whatever.

expect(decodeResult.formatted.items[4].label).toBe('Origin');
expect(decodeResult.formatted.items[4].value).toBe('KIAD');
expect(decodeResult.formatted.items[5].label).toBe('Destination');
expect(decodeResult.formatted.items[5].value).toBe('KPWM');
expect(decodeResult.formatted.items[6].label).toBe('Takeoff Time');
expect(decodeResult.formatted.items[6].value).toBe('17:12:07');
expect(decodeResult.remaining.text).toBe(' 92');
});

test('decodes variant 2', () => {
const text = 'OFF02XA0000/N38568 W077261251152KIADEPRZ1152****1958'
const decodeResult = plugin.decode({ text: text });
expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
expect(decodeResult.raw.position.latitude).toBe(38.946666666666665);
expect(decodeResult.raw.position.longitude).toBe(-77.435);
expect(decodeResult.raw.day_of_month).toBe(25);
expect(decodeResult.raw.time_of_day).toBe(42720);
expect(decodeResult.raw.off_time).toBe(42720);
expect(decodeResult.raw.departure_icao).toBe('KIAD');
expect(decodeResult.raw.arrival_icao).toBe('EPRZ');
expect(decodeResult.formatted.items.length).toBe(8);
expect(decodeResult.formatted.items[0].label).toBe('Flight Number');
expect(decodeResult.formatted.items[0].value).toBe('XA0000');
expect(decodeResult.formatted.items[1].label).toBe('Aircraft Position');
expect(decodeResult.formatted.items[1].value).toBe('38.947 N, 77.435 W');
expect(decodeResult.formatted.items[2].label).toBe('Day of Month');
expect(decodeResult.formatted.items[2].value).toBe('25');
expect(decodeResult.formatted.items[3].label).toBe('Message Timestamp');
expect(decodeResult.formatted.items[3].value).toBe('11:52:00');
Comment on lines +71 to +72
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

in this variant, both the timestamp and takeoff time have minute resolution - i could be parsing backwards but it probably doesn't matter

expect(decodeResult.formatted.items[4].label).toBe('Origin');
expect(decodeResult.formatted.items[4].value).toBe('KIAD');
expect(decodeResult.formatted.items[5].label).toBe('Destination');
expect(decodeResult.formatted.items[5].value).toBe('EPRZ');
expect(decodeResult.formatted.items[6].label).toBe('Takeoff Time');
expect(decodeResult.formatted.items[6].value).toBe('11:52:00');
expect(decodeResult.formatted.items[7].label).toBe('Estimated Time of Arrival');
expect(decodeResult.formatted.items[7].value).toBe('19:58:00');
expect(decodeResult.remaining.text).toBe('****');
});

test('decodes variant 3', () => {
const text = 'OFF02\r\nKBWI,KIND,1237,18.4'
const decodeResult = plugin.decode({ text: text });
expect(decodeResult.decoded).toBe(true);
expect(decodeResult.decoder.decodeLevel).toBe('partial');
expect(decodeResult.raw.departure_icao).toBe('KBWI');
expect(decodeResult.raw.arrival_icao).toBe('KIND');
expect(decodeResult.raw.off_time).toBe(45420);
expect(decodeResult.formatted.items.length).toBe(3);
expect(decodeResult.formatted.items[0].label).toBe('Origin');
expect(decodeResult.formatted.items[0].value).toBe('KBWI');
expect(decodeResult.formatted.items[1].label).toBe('Destination');
expect(decodeResult.formatted.items[1].value).toBe('KIND');
expect(decodeResult.formatted.items[2].label).toBe('Takeoff Time');
expect(decodeResult.formatted.items[2].value).toBe('12:37:00');
expect(decodeResult.remaining.text).toBe('18.4');
});

test('does not decode invalid', () => {

const text = 'POS Bogus message';
const decodeResult = plugin.decode({ text: text });

expect(decodeResult.decoded).toBe(false);
expect(decodeResult.decoder.decodeLevel).toBe('none');
expect(decodeResult.formatted.description).toBe('Takeoff Report');
expect(decodeResult.message.text).toBe(text);
});
});
96 changes: 96 additions & 0 deletions lib/plugins/Label_22_OFF.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { DateTimeUtils } from '../DateTimeUtils';
import { DecoderPlugin } from '../DecoderPlugin';
import { DecodeResult, Message, Options } from '../DecoderPluginInterface';
import { CoordinateUtils } from '../utils/coordinate_utils';
import { ResultFormatter } from '../utils/result_formatter';

// Position Report
export class Label_22_OFF extends DecoderPlugin {
name = 'label-22-off';

qualifiers() { // eslint-disable-line class-methods-use-this
return {
labels: ['22'],
preambles: ['OFF'],
};
}

decode(message: Message, options: Options = {}): DecodeResult {
const decodeResult = this.defaultResult();
decodeResult.decoder.name = this.name;
decodeResult.formatted.description = 'Takeoff Report';
decodeResult.message = message;

if (message.text.startsWith('OFF01')) { // variant 1
const fields = message.text.substring(5).split('/');

if (fields.length != 2) {
decodeResult.decoded = false;
decodeResult.decoder.decodeLevel = 'none';
return decodeResult;
}

ResultFormatter.flightNumber(decodeResult, fields[0]);
ResultFormatter.departureDay(decodeResult, Number(fields[1].substring(0, 2))); // departure day
ResultFormatter.arrivalDay(decodeResult, Number(fields[1].substring(2,4))); // arrival day
ResultFormatter.time_of_day(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[1].substring(4, 8) + '00')); //HHMM
ResultFormatter.departureAirport(decodeResult, fields[1].substring(8, 12));
ResultFormatter.arrivalAirport(decodeResult, fields[1].substring(12, 16));
ResultFormatter.off(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[1].substring(16,22)));
ResultFormatter.unknown(decodeResult, fields[1].substring(22));

decodeResult.decoded = true;
decodeResult.decoder.decodeLevel = 'partial';

} else if (message.text.startsWith('OFF02\r\n')) { // varaint 3
const fields = message.text.substring(7).split(',');
if (fields.length != 4) {
decodeResult.decoded = false;
decodeResult.decoder.decodeLevel = 'none';
return decodeResult;
}

ResultFormatter.departureAirport(decodeResult, fields[0]);
ResultFormatter.arrivalAirport(decodeResult, fields[1]);
ResultFormatter.off(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[2] + '00'));
ResultFormatter.unknown(decodeResult, fields[3]);

decodeResult.decoded = true;
decodeResult.decoder.decodeLevel = 'partial';

} else if (message.text.startsWith('OFF02')) { // varaint 2
const fields = message.text.substring(5).split('/');
if (fields.length != 2) {
decodeResult.decoded = false;
decodeResult.decoder.decodeLevel = 'none';
return decodeResult;
}

ResultFormatter.flightNumber(decodeResult, fields[0]);
const position = CoordinateUtils.decodeStringCoordinatesDecimalMinutes(fields[1].substring(0, 14));
if (position) {
ResultFormatter.position(decodeResult, position);
}
ResultFormatter.day(decodeResult, Number(fields[1].substring(14, 16)));
ResultFormatter.time_of_day(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[1].substring(16, 20) + '00'));
ResultFormatter.departureAirport(decodeResult, fields[1].substring(20, 24));
ResultFormatter.arrivalAirport(decodeResult, fields[1].substring(24, 28));
ResultFormatter.off(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[1].substring(28, 32) + '00'));
ResultFormatter.unknown(decodeResult, fields[1].substring(32, 36));
ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(fields[1].substring(36,40) + '00'));
decodeResult.decoded = true;
decodeResult.decoder.decodeLevel = 'partial';


} else {
if (options.debug) {
console.log(`DEBUG: ${this.name}: Unknown variation.`);
}
decodeResult.decoded = false;
decodeResult.decoder.decodeLevel = 'none';
}
return decodeResult;
}
}

export default {};
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { MessageDecoder } from '../MessageDecoder';
import { Label_22 } from './Label_22';
import { Label_22_POS } from './Label_22_POS';

describe('Label 22', () => {
let plugin: Label_22;
let plugin: Label_22_POS;

beforeEach(() => {
const decoder = new MessageDecoder();
plugin = new Label_22(decoder);
plugin = new Label_22_POS(decoder);
});

test('matches qualifiers', () => {
expect(plugin.decode).toBeDefined();
expect(plugin.name).toBe('label-22');
expect(plugin.name).toBe('label-22-pos');
expect(plugin.qualifiers).toBeDefined();
expect(plugin.qualifiers()).toEqual({
labels: ['22'],
preambles: ['N', 'S'],
});
});

Expand Down
5 changes: 3 additions & 2 deletions lib/plugins/Label_22.ts → lib/plugins/Label_22_POS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { CoordinateUtils } from '../utils/coordinate_utils';
import { ResultFormatter } from '../utils/result_formatter';

// Position Report
export class Label_22 extends DecoderPlugin {
name = 'label-22';
export class Label_22_POS extends DecoderPlugin {
name = 'label-22-pos';

qualifiers() { // eslint-disable-line class-methods-use-this
return {
labels: ['22'],
preambles: ['N', 'S'],
};
}

Expand Down
3 changes: 2 additions & 1 deletion lib/plugins/official.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export * from './Label_1L_660';
export * from './Label_1L_Slash';
export * from './Label_20_POS';
export * from './Label_21_POS';
export * from './Label_22';
export * from './Label_22_OFF';
export * from './Label_22_POS';
export * from './Label_24_Slash';
export * from './Label_30_Slash_EA';
export * from './Label_44_ETA';
Expand Down
30 changes: 30 additions & 0 deletions lib/utils/result_formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,36 @@ export class ResultFormatter {
});
}

static day(decodeResult: DecodeResult, day: number) {
decodeResult.raw.day_of_month = day;
decodeResult.formatted.items.push({
type: 'day_of_month',
code: 'MSG_DAY',
label: 'Day of Month',
value: `${day}`,
});
}

static departureDay(decodeResult: DecodeResult, day: number) {
decodeResult.raw.departure_day_of_month = day;
decodeResult.formatted.items.push({
type: 'day_of_month',
code: 'DEP_DAY',
label: 'Departure Day',
value: `${day}`,
});
}

static arrivalDay(decodeResult: DecodeResult, day: number) {
decodeResult.raw.arrival_day_of_month = day;
decodeResult.formatted.items.push({
type: 'day_of_month',
code: 'ARR_DAY',
label: 'Arrival Day',
value: `${day}`,
});
}

static text(decodeResult: DecodeResult, text: string) {
decodeResult.raw.text = text;
decodeResult.formatted.items.push({
Expand Down