Skip to content

Adding label 2P decoding#225

Merged
makrsmark merged 3 commits intoairframesio:masterfrom
makrsmark:feature/label-2p
Feb 18, 2025
Merged

Adding label 2P decoding#225
makrsmark merged 3 commits intoairframesio:masterfrom
makrsmark:feature/label-2p

Conversation

@makrsmark
Copy link
Collaborator

@makrsmark makrsmark commented Feb 16, 2025

  • FM3, FM4, FM5, and POS

Summary by CodeRabbit

  • New Features

    • Expanded flight message decoding capabilities with multiple new report decoders (Label_2P_FM3, Label_2P_FM4, Label_2P_FM5, Label_2P_POS) to enhance accuracy and support varied message formats.
    • Improved underlying message parsing to better handle specific message types, particularly those ending with 'POS'.
  • Tests

    • Added comprehensive test suites for each new decoder, covering both valid and invalid scenarios to ensure reliable performance.

- FM3, FM4, FM5, and POS
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request introduces four new decoder plugins (FM3, FM4, FM5, and POS) into the message decoding system. The plugins extend the decoding functionality for various flight report message formats by adding dedicated decoding logic and corresponding unit tests for each plugin. Additionally, the official exports and helper functions have been updated to support these new plugins, without changing any public API declarations.

Changes

File(s) Change Summary
lib/MessageDecoder.ts Added four new plugin registrations in the constructor to support FM3, FM4, FM5, and POS message types.
lib/plugins/Label_2P_FM3.ts
lib/plugins/Label_2P_FM3.test.ts
Introduced a new FM3 plugin class with its decoding logic and a test suite validating valid/invalid flight report messages.
lib/plugins/Label_2P_FM4.ts
lib/plugins/Label_2P_FM4.test.ts
Added a new FM4 plugin class along with tests to assess the accurate decoding of flight messages through various scenarios.
lib/plugins/Label_2P_FM5.ts
lib/plugins/Label_2P_FM5.test.ts
Created a new FM5 plugin class with appropriate decoding logic and a test suite that covers both valid and invalid message cases.
lib/plugins/Label_2P_POS.ts
lib/plugins/Label_2P_POS.test.ts
Developed a new POS plugin class and associated tests to decode messages, verifying proper extraction of flight details and handling of unknown formats.
lib/plugins/official.ts Updated the file to export the newly added FM3, FM4, FM5, and POS plugins, expanding the available plugin modules.
lib/utils/h1_helper.ts Extended the parseMessageType function with a new branch to better handle message types ending with "POS", ensuring proper extraction and logging when processing such messages.

Sequence Diagram(s)

sequenceDiagram
    participant MD as MessageDecoder
    participant Plugin as DecoderPlugin (FM3/FM4/FM5/POS)
    participant Utils as Utility Functions
    MD->>MD: Initialize and register plugins
    MD->>Plugin: Invoke decode(message)
    Plugin->>Utils: Process message components
    Utils-->>Plugin: Return formatted data
    Plugin-->>MD: Return DecodeResult
Loading

Suggested reviewers

  • kevinelliott
  • fredclausen

Poem

In the soft glow of code’s delight,
I, the rabbit, hop through the night.
New plugins join the digital parade,
Decoding messages finely made.
With tests so neat and features bright,
My ears twitch in joyful byte!
🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

plugin = new Label_2P_POS(decoder);
});

test('variant 1', () => {
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 test will pass when #224 is merged

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (15)
lib/plugins/Label_2P_POS.ts (3)

9-13: Consider adding regex pattern for more specific message matching.

The qualifiers method only checks for label "2P" which might be too broad. Consider adding a regex pattern to specifically match POS messages.

 qualifiers() { // eslint-disable-line class-methods-use-this
   return {
     labels: ["2P"],
+    pattern: /^POS/i,
   };
 }

15-34: Add documentation for the decode method.

The decode method implementation looks correct but lacks documentation explaining the expected message format and decoding logic.

+/**
+ * Decodes a 2P POS message using H1 message format.
+ * @param message - The message to decode
+ * @param options - Optional decoding options
+ * @returns DecodeResult containing the decoded message information
+ */
 decode(message: Message, options: Options = {}) : DecodeResult {

Also, consider extracting the debug logging logic into a separate helper method for better code organization.


37-38: Remove empty default export.

The empty default export is unnecessary and should be removed.

-export default {};
lib/plugins/Label_2P_FM5.ts (3)

38-41: Standardize time format handling.

The time padding logic is inconsistent between message timestamp and ETA handling. Consider extracting to a helper method.

+  private padTime(time: string): string {
+    return time.length === 4 ? time + '00' : time;
+  }

   // Usage:
-  if(time.length === 4) {
-    time += '00';
-  }
-  ResultFormatter.time_of_day(decodeResult, DateTimeUtils.convertHHMMSSToTod(time));
-  ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(parts[3]+'00'));
+  ResultFormatter.time_of_day(decodeResult, DateTimeUtils.convertHHMMSSToTod(this.padTime(time)));
+  ResultFormatter.eta(decodeResult, DateTimeUtils.convertHHMMSSToTod(this.padTime(parts[3])));

Also applies to: 42-42


46-50: Document unknown fields and implement TODO.

The TODO comment indicates missing field decodings. Please document the expected format of these fields and implement their decoding.

Would you like me to help implement the decoding for the unknown fields (speed, heading, etc.)?


56-58: Fix incorrect debug message.

The debug message incorrectly refers to an "H1 message" when this is an FM5 message decoder.

-        console.log(`Decoder: Unknown H1 message: ${message.text}`);
+        console.log(`Decoder: Unknown FM5 message: ${message.text}`);
lib/plugins/Label_2P_FM5.test.ts (1)

40-49: Improve invalid message test case.

The invalid test uses an FM4 message which doesn't properly test the FM5 decoder's error handling. Consider adding more specific test cases.

-  test('<invalid>', () => {
+  describe('invalid messages', () => {
+    test.each([
+      ['wrong format', 'FM4 Bogus message'],
+      ['missing fields', 'FM5 EIDW,OMAA'],
+      ['invalid coordinates', 'FM5 EIDW,OMAA,113522,1540,invalid,invalid,35002,116.24,502,36900,ETD23N,'],
+    ])('%s', (_, text) => {
       const decodeResult = plugin.decode({ text: text });
       expect(decodeResult.decoded).toBe(false);
       expect(decodeResult.decoder.decodeLevel).toBe('none');
       expect(decodeResult.formatted.description).toBe('Flight Report');
       expect(decodeResult.formatted.items.length).toBe(0);
+    });
+  });
lib/plugins/Label_2P_FM4.ts (3)

25-25: Use strict equality comparison.

Replace == with === for consistent type checking.

-      if(header.length == 0) {
+      if(header.length === 0) {

54-56: Consider adding error handling for invalid coordinates.

The position formatting could fail if the latitude/longitude values are not valid numbers.

-      ResultFormatter.position(decodeResult, {latitude: Number(lat), longitude: Number(lon)});
+      const latitude = Number(lat);
+      const longitude = Number(lon);
+      if (isNaN(latitude) || isNaN(longitude)) {
+        ResultFormatter.unknown(decodeResult, `Invalid coordinates: ${lat}, ${lon}`);
+      } else {
+        ResultFormatter.position(decodeResult, {latitude, longitude});
+      }

45-46: Extract coordinate parsing into a reusable utility function.

The coordinate parsing logic appears in multiple decoders. Consider extracting it into a utility function.

+  private parseCoordinates(lat: string, lon: string): {latitude: number, longitude: number} | null {
+    const cleanLat = lat.replaceAll(' ', '');
+    const cleanLon = lon.replaceAll(' ', '');
+    try {
+      return {
+        latitude: Number(cleanLat),
+        longitude: Number(cleanLon)
+      };
+    } catch (e) {
+      return null;
+    }
+  }

Also applies to: 47-48

lib/plugins/Label_2P_POS.test.ts (1)

44-53: Add more edge cases to invalid message tests.

Consider adding test cases for:

  • Invalid coordinates (non-numeric values)
  • Missing required fields
  • Malformed message format
test('invalid coordinates', () => {
  const text = 'M01AFN1234POS/ID50007B,RCH4086,ABB02R70E037/PSNinvalid,invalid';
  const decodeResult = plugin.decode({ text: text });
  expect(decodeResult.decoded).toBe(false);
  expect(decodeResult.decoder.decodeLevel).toBe('none');
  expect(decodeResult.formatted.description).toBe('Unknown H1 Message');
});
lib/plugins/Label_2P_FM3.ts (1)

40-40: Remove debug console.log statement.

Remove the console.log statement from production code.

-      console.log(header[1]);
lib/plugins/Label_2P_FM4.test.ts (1)

73-82: Enhance invalid message test coverage.

Add test cases for:

  • Messages with incorrect number of parts
  • Invalid coordinate formats
  • Missing or malformed timestamps
test('invalid coordinate format', () => {
  const text = 'FM4KIAD,OMAA,140256,1448,invalid,invalid,23228,328,43.5,72500';
  const decodeResult = plugin.decode({ text: text });
  expect(decodeResult.decoded).toBe(false);
  expect(decodeResult.decoder.decodeLevel).toBe('none');
});

test('invalid timestamp format', () => {
  const text = 'FM4KIAD,OMAA,999999,9999,39.43,-75.62,23228,328,43.5,72500';
  const decodeResult = plugin.decode({ text: text });
  expect(decodeResult.decoded).toBe(false);
  expect(decodeResult.decoder.decodeLevel).toBe('none');
});
lib/plugins/Label_2P_FM3.test.ts (1)

79-88: Consider adding more invalid test cases.

While the current invalid test case is good, consider adding more edge cases such as:

  • Malformed coordinates
  • Missing required fields
  • Invalid timestamps
lib/utils/h1_helper.ts (1)

215-218: Consider extracting magic numbers and improving readability.

The slice operations with magic numbers make the code harder to maintain.

Consider this refactoring:

-        } else if(parts[0].length > 3 && parts[0].slice(-3) === 'POS') {
-            ResultFormatter.unknown(decodeResult, parts[0].substring(0, 4));
-            ResultFormatter.flightNumber(decodeResult, parts[0].slice(4, -3));
+        } else if(parts[0].length > 3 && parts[0].endsWith('POS')) {
+            const PREFIX_LENGTH = 4;
+            const POS_LENGTH = 3;
+            ResultFormatter.unknown(decodeResult, parts[0].substring(0, PREFIX_LENGTH));
+            ResultFormatter.flightNumber(decodeResult, parts[0].slice(PREFIX_LENGTH, -POS_LENGTH));
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2763d51 and 41179f6.

📒 Files selected for processing (11)
  • lib/MessageDecoder.ts (1 hunks)
  • lib/plugins/Label_2P_FM3.test.ts (1 hunks)
  • lib/plugins/Label_2P_FM3.ts (1 hunks)
  • lib/plugins/Label_2P_FM4.test.ts (1 hunks)
  • lib/plugins/Label_2P_FM4.ts (1 hunks)
  • lib/plugins/Label_2P_FM5.test.ts (1 hunks)
  • lib/plugins/Label_2P_FM5.ts (1 hunks)
  • lib/plugins/Label_2P_POS.test.ts (1 hunks)
  • lib/plugins/Label_2P_POS.ts (1 hunks)
  • lib/plugins/official.ts (1 hunks)
  • lib/utils/h1_helper.ts (1 hunks)
🔇 Additional comments (6)
lib/plugins/Label_2P_POS.ts (1)

6-8: LGTM! Class structure follows plugin pattern.

The class correctly extends DecoderPlugin and sets a unique name identifier.

lib/plugins/official.ts (1)

20-23: LGTM! Exports are correctly organized.

The new exports are added in the correct alphabetical order and follow the existing pattern.

lib/plugins/Label_2P_FM3.test.ts (2)

1-11: LGTM! Well-structured test setup.

The test file follows best practices with proper imports, beforeEach setup, and clean initialization.


13-77: LGTM! Comprehensive test coverage with real-world examples.

The test cases are well-documented with:

  • Real-world message examples with URLs for reference
  • Thorough assertions for all decoded fields
  • Multiple variants to ensure robust decoding
lib/utils/h1_helper.ts (1)

215-218:

Details

✅ Verification successful

Verify the robustness of the POS message handling.

The new condition assumes specific message formats. Let's verify this assumption with real-world examples.


🏁 Script executed:

#!/bin/bash
# Search for examples of messages ending with 'POS'
rg -A 5 '[A-Z0-9]+POS' lib/plugins/Label_2P_POS.test.ts

Length of output: 740


Re: Robustness of POS Message Handling Verified

The POS branch in lib/utils/h1_helper.ts (lines 215-218) has been validated against real-world examples in lib/plugins/Label_2P_POS.test.ts. The tests show that:

  • A valid POS message (e.g., 'M80AMC4086POS/ID50007B,...') is correctly decomposed into an unknown code (substring(0, 4)) and a flight number (slice(4, -3)), then processed as a position report.
  • An invalid or bogus POS message (e.g., 'M01AFN1234POS Bogus message') properly results in a decoding failure, triggering the expected "Unknown H1 Message" outcome.

No further modifications are required.

lib/MessageDecoder.ts (1)

36-39:

Details

✅ Verification successful

LGTM! New plugin registrations follow established patterns.

The new Label 2P decoders are registered consistently with existing plugins and maintain proper alphabetical grouping.

Let's verify the plugin implementations:


🏁 Script executed:

#!/bin/bash
# Description: Verify that the new plugins are properly exported and implement required methods

# Check if plugins are exported in official.ts
echo "Checking exports in official.ts:"
rg "export.*Label_2P_(FM3|FM4|FM5|POS)" lib/plugins/official.ts

# Check if plugins implement required interface methods
echo "Checking plugin implementations:"
rg -A 5 "class Label_2P_(FM3|FM4|FM5|POS).*extends.*{" lib/plugins/

Length of output: 565



🏁 Script executed:

[run_scripts]
#!/bin/bash
# Re-run plugin implementation check with corrected regex (escape the '{')
echo "Checking plugin implementations with corrected regex:"
rg -A 5 "class Label_2P_(FM3|FM4|FM5|POS).*extends.*\\{" lib/plugins/

Length of output: 1686


Plugin Implementation Verified: No Further Modifications Needed

The new Label 2P decoders in lib/MessageDecoder.ts are correctly registered, and verification confirms they are properly exported and implemented. Specifically:

  • The export statements in lib/plugins/official.ts correctly export:

    • Label_2P_FM3
    • Label_2P_FM4
    • Label_2P_FM5
    • Label_2P_POS
  • Each corresponding plugin file (e.g., lib/plugins/Label_2P_FM3.ts, etc.) properly extends DecoderPlugin and implements the required methods.

LGTM—the changes align with established patterns with no further action required.

Comment on lines +23 to +33
if(parts.length === 12){

const header = parts[0].split('FM5 ');
if(header.length == 0) {
// can't use preambles, as there can be info before `FM4`
// so let's check if we want to decode it here
ResultFormatter.unknown(decodeResult, message.text);
decodeResult.decoded = false;
decodeResult.decoder.decodeLevel = 'none';
return decodeResult;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix header length check logic.

The header length check is incorrect. When split returns no matches, the array will have length 1, not 0.

-      if(header.length == 0) {
+      if(header.length !== 2 || !header[1]) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(parts.length === 12){
const header = parts[0].split('FM5 ');
if(header.length == 0) {
// can't use preambles, as there can be info before `FM4`
// so let's check if we want to decode it here
ResultFormatter.unknown(decodeResult, message.text);
decodeResult.decoded = false;
decodeResult.decoder.decodeLevel = 'none';
return decodeResult;
}
if(parts.length === 12){
const header = parts[0].split('FM5 ');
- if(header.length == 0) {
+ if(header.length !== 2 || !header[1]) {
// can't use preambles, as there can be info before `FM4`
// so let's check if we want to decode it here
ResultFormatter.unknown(decodeResult, message.text);
decodeResult.decoded = false;
decodeResult.decoder.decodeLevel = 'none';
return decodeResult;
}

Comment on lines +47 to +57
const lat = parts[2].replaceAll(' ','');
const lon = parts[3].replaceAll(' ','');
if(lat[0] === 'N' || lat[0] === 'S') {
ResultFormatter.position(decodeResult, {
latitude: CoordinateUtils.getDirection(lat[0]) * Number(lat.substring(1)),
longitude: CoordinateUtils.getDirection(lon[0]) * Number(lon.substring(1)),
});
} else {
ResultFormatter.position(decodeResult, {latitude: Number(lat), longitude: Number(lon)});
}
ResultFormatter.altitude(decodeResult, Number(parts[4]));
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Extract common coordinate parsing logic.

This coordinate parsing logic is duplicated across multiple decoders. Consider creating a shared utility function.

Create a new utility function in CoordinateUtils:

// In CoordinateUtils.ts
export function parseCoordinates(lat: string, lon: string): {latitude: number, longitude: number} | null {
  const cleanLat = lat.replaceAll(' ', '');
  const cleanLon = lon.replaceAll(' ', '');
  
  if (cleanLat[0] === 'N' || cleanLat[0] === 'S') {
    return {
      latitude: getDirection(cleanLat[0]) * Number(cleanLat.substring(1)),
      longitude: getDirection(cleanLon[0]) * Number(cleanLon.substring(1)),
    };
  }
  
  const latitude = Number(cleanLat);
  const longitude = Number(cleanLon);
  return isNaN(latitude) || isNaN(longitude) ? null : {latitude, longitude};
}

@makrsmark makrsmark merged commit bdbc50d into airframesio:master Feb 18, 2025
4 checks passed
@makrsmark makrsmark deleted the feature/label-2p branch February 18, 2025 00:21
This was referenced Jan 23, 2026
This was referenced Jan 30, 2026
@coderabbitai coderabbitai bot mentioned this pull request Feb 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant