Skip to content

fix: garmin MLR routing byte, ultrahuman decode copy, dead battery check - #70

Merged
abdulsaheel merged 1 commit into
mainfrom
fix/review-findings-2026-09-21
Sep 22, 2026
Merged

abdulsaheel merged 1 commit into
mainfrom
fix/review-findings-2026-09-21

Conversation

@abdulsaheel

@abdulsaheel abdulsaheel commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

found while doing a full-codebase pass. three things:

  • garminDecodeMlr wasn't stripping the routing byte before handing GFDI bytes off — would've corrupted COBS framing on every data frame from a paired garmin
  • parseUltrahumanRecord copied the whole payload on every single record instead of once per response
  • dead >= 0 check on an already-unsigned battery value in the gen5 hello parser

all green locally (dart test, 631 passing).

🤖 Generated with Claude Code

Summary by Sourcery

Fix Garmin data-frame decoding and reduce unnecessary Ultrahuman parsing work while simplifying Gen5 battery validation.

Bug Fixes:

  • Correct Garmin MLR data-frame decoding by removing the routing byte before processing payloads.
  • Avoid copying Ultrahuman response data for every parsed record.
  • Simplify Gen5 battery validation by removing an ineffective unsigned-value check.

Tests:

  • Add coverage verifying that Garmin MLR payloads exclude the routing byte.

Summary by CodeRabbit

  • Bug Fixes

    • Corrected Garmin MLR data handling so decoded watch data now excludes its internal routing byte, providing the expected payload values to applications.
    • Preserved existing battery-status parsing behavior and Ultrahuman record decoding while improving processing efficiency for already-optimized input data.
  • Tests

    • Added coverage confirming that Garmin MLR payloads are returned without the routing byte.

…r-record copy in ultrahuman decode

garminDecodeMlr wasn't stripping the routing byte before wrapping GFDI
data in GarminMlrData, corrupting COBS framing on every watch-to-host
data frame (the class doc already said it should be stripped).

Gen5HelloInfo's battery gate had a dead `>= 0` check on an always
non-negative value.

parseUltrahumanRecord copied the whole input on every call; the one
caller (parseUltrahumanRecords) already passes a Uint8List, so skip
the copy when we already have one instead of copying per-record.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @abdulsaheel, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 3 days and 23 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@coderabbitai

coderabbitai Bot commented Sep 21, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Understand this PR’s impact

Explore downstream dependencies and potential security impact with Blast Radius.

View blast radius →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: c33ea862-877f-4dad-beac-78571c223343

📥 Commits

Reviewing files that changed from the base of the PR and between 6d740b3 and c22925e.

📒 Files selected for processing (4)
  • lib/src/control.dart
  • lib/src/garmin.dart
  • lib/src/ultrahuman.dart
  • test/garmin_test.dart

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The PR strips the Garmin MLR routing byte from decoded payloads, adds a matching test assertion, simplifies the battery check, and avoids unnecessary copying for Uint8List inputs.

Changes

Garmin MLR payload decoding

Layer / File(s) Summary
Strip MLR routing byte
lib/src/garmin.dart, test/garmin_test.dart
garminDecodeMlr now passes data.sublist(1) as the payload. The flagged-frame test verifies [1, 2, 3].

Parser simplification

Layer / File(s) Summary
Simplify parser operations
lib/src/control.dart, lib/src/ultrahuman.dart
The battery check removes a redundant non-negative condition. parseUltrahumanRecord reuses Uint8List inputs and converts other list types.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes all three substantive changes: the Garmin MLR routing-byte fix, the Ultrahuman decode-copy optimization, and the redundant battery check removal.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Sep 21, 2026

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Fixes Garmin MLR payload framing by removing the routing byte, reduces unnecessary Ultrahuman buffer copying during record decoding, and simplifies Gen5 battery validation by removing an impossible unsigned-value check.

Sequence diagram for Garmin MLR payload decoding

sequenceDiagram
    participant MLR as Garmin MLR
    participant Decoder as garminDecodeMlr
    participant GFDI as GFDI decoder
    MLR->>Decoder: garminDecodeMlr(data)
    Decoder->>Decoder: Uint8List.fromList(data.sublist(1))
    Decoder->>GFDI: Hand off payload without routing byte
    GFDI-->>Decoder: Decode COBS-framed data
Loading

Flow diagram for Gen5 battery validation

flowchart LR
    A[Gen5HelloInfo parser] --> B["batteryRaw = u32(body, 1) / 10"]
    B --> C{batteryRaw <= 100}
    C -->|yes| D[Set batteryPct]
    C -->|no| E[Set batteryPct to null]
Loading

File-Level Changes

Change Details Files
Strip the Garmin MLR routing byte before exposing the data payload.
  • Preserve the routing handle while removing the leading routing byte from the payload.
  • Add a regression assertion verifying the decoded payload excludes byte 0.
lib/src/garmin.dart
test/garmin_test.dart
Avoid repeatedly copying the Ultrahuman response buffer during record parsing.
  • Reuse an existing Uint8List and only convert non-Uint8List inputs once per parser invocation.
lib/src/ultrahuman.dart
Simplify Gen5 battery percentage validation for an unsigned value.
  • Remove the redundant nonnegative comparison while retaining the upper-bound check.
lib/src/control.dart

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@abdulsaheel
abdulsaheel merged commit 202174f into main Sep 22, 2026
4 checks passed
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