Skip to content

lefun ring/band: pairs and connects, reports nothing yet - #341

Open
abdulsaheel wants to merge 4 commits into
mainfrom
lefun-protocol
Open

lefun ring/band: pairs and connects, reports nothing yet#341
abdulsaheel wants to merge 4 commits into
mainfrom
lefun-protocol

Conversation

@abdulsaheel

@abdulsaheel abdulsaheel commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

User description

adds the lefun-protocol family (a bunch of ~$10 storefront rings/bands sharing one reference design) as a pairable device. plain notify-class pairing, one battery poll, everything else it sends gets archived raw. no signals declared, nothing derived — same shape as the oura/hrs entries.

needs the openstrap/protocol lefun-protocol PR merged and the git pin in pubspec.yaml bumped before this can merge (using a local pubspec_overrides.yaml for now).

Summary by Sourcery

Integrate experimental Lefun rings and bands into pairing and synchronization while preserving their un-derived data as raw archives.

New Features:

  • Add experimental Lefun-protocol ring and band support for pairing, bounded battery polling, background synchronization, and raw frame archiving without exposing physiological metrics.

Bug Fixes:

  • Prevent forgetting an unrelated device from disarming an active chest-strap session.

Build:

  • Update the pinned openstrap protocol dependency to include Lefun frame and battery support.

Tests:

  • Add coverage for Lefun adapter behavior, signal registration, and device registry membership.

PR Type

Enhancement, Bug fix


Description

  • Add experimental Lefun ring and band support.

    • Integrates Lefun devices into the pairing UI and background sync flows.
    • Uses a bounded poll connection with plain notify-class pairing.
  • Archive raw Lefun frames without deriving metrics.

    • Polls battery status but abstains from exposing any physiological signals.
    • Saves undecoded bytes to the raw archive with null timestamps.
  • Fix chest-strap disarm bug during device removal.

    • Prevents forgetting an unrelated device from stopping an active HRS session.

Diagram Walkthrough

flowchart LR
  DevicePicker["Device Picker"] -- "Pairs" --> LefunLink["Lefun Link"]
  BackgroundSync["Background Sync"] -- "Triggers" --> LefunLink
  LefunLink -- "Drives" --> LefunAdapter["Lefun Adapter"]
  LefunAdapter -- "Banks raw frames" --> LocalDb["Local DB"]
Loading

File Walkthrough

Relevant files
Enhancement
6 files
_registry.dart
Add Lefun GATT service and registry entries                           
+28/-0   
lefun.dart
Implement LefunAdapter for bounded battery polling             
+80/-0   
lefun_link.dart
Implement LefunLink host for connection and archiving       
+223/-0 
background_sync.dart
Add Lefun sync to headless background sync flow                   
+10/-0   
device_picker.dart
Add UI blurb for Lefun devices in picker                                 
+4/-0     
devices.dart
Add Lefun to sensor pairing options and icons                       
+17/-2   
Bug fix
1 files
hrs_link.dart
Fix HRS disarm bug and allow null tier                                     
+21/-3   
Tests
3 files
adapter_signals_registry_test.dart
Add Lefun to adapter signals registry test                             
+2/-0     
lefun_adapter_test.dart
Add tests for LefunAdapter behavior and frames                     
+96/-0   
band_registry_test.dart
Update band registry test to include Lefun                             
+1/-1     

@sourcery-ai

sourcery-ai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds experimental support for pairing Lefun-protocol rings and bands, performing a single battery poll, archiving valid notifications as raw records, and intentionally exposing no derived health signals; the implementation also wires lifecycle, background sync, UI pairing, teardown, and replay coverage. The PR depends on the openstrap/protocol Lefun implementation and a corresponding pubspec Git pin update before merging.

Sequence diagram for Lefun pairing and bounded battery sync

sequenceDiagram
    participant User
    participant DevicePicker
    participant HrsLink
    participant LefunLink
    participant LefunAdapter
    participant LefunDevice
    participant Archive

    User->>DevicePicker: pairNotifySensor(kLefun, device, tier: null)
    DevicePicker->>HrsLink: pairNotifySensor
    HrsLink-->>User: paired device with no measurement tier
    LefunLink->>LefunDevice: connect()
    LefunLink->>LefunDevice: discoverServices()
    LefunLink->>LefunAdapter: run(link)
    LefunAdapter->>LefunDevice: write(kLefunWriteChar, buildLefunFrame(kLefunReportBattery))
    LefunDevice-->>LefunAdapter: notify(kLefunNotifyChar)
    LefunAdapter->>Archive: SampleBatch([], raw: frames)
    LefunAdapter-->>LefunLink: BandNote(battery, pct)
    LefunLink->>LefunDevice: disconnect()
Loading

File-Level Changes

Change Details Files
Registers the Lefun protocol as an experimental, non-metric band adapter.
  • Adds the shared Lefun GATT service and characteristic UUIDs.
  • Declares arrival-time anchoring, no supported signals, and registry coverage.
  • Adds picker/profile presentation and signal-registry assertions.
lib/ble/adapters/_registry.dart
lib/ui2/pairing/device_picker.dart
lib/ui2/profile/devices.dart
test/adapter_signals_registry_test.dart
test/band_registry_test.dart
Implements bounded battery polling with raw notification archiving.
  • Subscribes to notifications, sends one battery request, and waits for a configurable timeout.
  • Decodes valid battery frames into a BandNote while archiving each valid frame verbatim.
  • Drops invalid frames and intentionally exposes no physiological samples.
  • Adds adapter replay tests for requests, battery notes, raw archives, corruption, empty replies, and refused writes.
lib/ble/adapters/lefun.dart
test/adapters/lefun_adapter_test.dart
Adds connection lifecycle and background synchronization for paired Lefun devices.
  • Connects paired devices through the secondary-link slot, validates required characteristics, runs the bounded adapter session, and disconnects safely.
  • Persists raw archive records with report type and capture time but no device clock or counter.
  • Adds test ingestion support and prevents sync collisions with the primary device ID.
  • Invokes Lefun synchronization from headless background sync.
lib/ble/lefun_link.dart
lib/sync/background_sync.dart
Integrates Lefun pairing and teardown without affecting active HRS sessions.
  • Uses plain notify-class pairing with a null measurement tier.
  • Adds dedicated Lefun forget handling and gates the fallback disarm path to actual HRS rows.
  • Allows nullable pairing tiers for adapters that declare no signals.
lib/ble/hrs_link.dart
lib/ui2/profile/devices.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

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 9 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Team

Run ID: b5d0d6f0-faad-4b02-80e1-34ee42261f6f

📥 Commits

Reviewing files that changed from the base of the PR and between b98cae6 and fbd1197.

⛔ Files ignored due to path filters (4)
  • pubspec.lock is excluded by !**/*.lock
  • test/adapter_signals_registry_test.dart is excluded by !test/**
  • test/adapters/lefun_adapter_test.dart is excluded by !test/**
  • test/band_registry_test.dart is excluded by !test/**
📒 Files selected for processing (9)
  • lib/ble/adapters/_registry.dart
  • lib/ble/adapters/lefun.dart
  • lib/ble/hrs_link.dart
  • lib/ble/lefun_link.dart
  • lib/compute/derivation_engine.dart
  • lib/sync/background_sync.dart
  • lib/ui2/pairing/device_picker.dart
  • lib/ui2/profile/devices.dart
  • pubspec.yaml

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 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="lib/ble/hrs_link.dart" line_range="589-595" />
<code_context>
       await OuraLink.forgetRing(id);
       return;
     }
+    if (row?['adapter_id'] == kLefun.id) {
+      // No secret to drop — the envelope this device speaks has no key
+      // exchange — so this is a plain stop-and-delete, same shape as Oura's
+      // forget minus the keychain half.
+      await LefunLink.instance.stop();
+      await LocalDb.deleteDevice(id);
+      return;
+    }
     // Before the row goes, not after: a live session would keep writing rows
</code_context>
<issue_to_address>
**issue (bug_risk):** Forgetting any Lefun row stops the singleton Lefun session, regardless of which device ID is being forgotten. If another Lefun device is currently syncing, unpairing an inactive or different Lefun row disconnects that other device and interrupts its archive flush.

**Triggers:** When multiple Lefun devices are paired and one is forgotten while another device has an active session.

**Suggested fix:** Associate the live session with its device ID and stop it only when the forgotten row is the active session; otherwise only delete the requested row.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and the new path writes and persists raw Lefun notifications and battery data even though the device protocol is unencrypted and unauthenticated, so a protocol or device-identification mistake could create incorrect archived records. Reverting stops future polling, while already stored records are bounded and can be removed or repaired.

Blocking findings: lib/ble/hrs_link.dart:595


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread lib/ble/hrs_link.dart
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

(Review updated until commit 2127edb)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Battery level dropped

The BandHost is instantiated with onNote: (key, value) => debugPrint(...). This means the battery level polled from the Lefun device is only printed to the console and never persisted to the database or exposed to the UI, contradicting the PR's claim of "reporting battery status". It should likely call a method to update the device's battery state (e.g., LocalDb.updateDeviceState).

onNote: (key, value) => debugPrint('[lefun] $key = $value'),
Typo in adapter ID check

The code checks row?['adapter_id'] == kBleHrsAdapter.id. The registry constant for the BLE HRS adapter is kBleHrs (which has .id), and the class is BleHrsAdapter. kBleHrsAdapter appears to be a typo that will cause a compilation error or runtime failure. It should likely be kBleHrs.id.

if (row?['adapter_id'] == kBleHrsAdapter.id) {
  await instance.disarm();
}

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Latest suggestions up to 2127edb

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix compilation error by using the correct BandEntry constant

kBleHrsAdapter is undefined and will cause a compilation error. The correct constant
for the BandEntry exported from _registry.dart is kBleHrs.

lib/ble/hrs_link.dart [609-611]

-    if (row?['adapter_id'] == kBleHrsAdapter.id) {
+    if (row?['adapter_id'] == kBleHrs.id) {
       await instance.disarm();
     }
Suggestion importance[1-10]: 10

__

Why: The constant kBleHrsAdapter is undefined and will cause a compilation error. The correct constant exported from _registry.dart is kBleHrs. Fixing this is critical for the code to compile.

High

Previous suggestions

Suggestions up to commit a7290a8
CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix undefined variable kBleHrsAdapter to kBleHrs

kBleHrsAdapter is undefined and will cause a compilation error. The correct registry
entry name is kBleHrs.

lib/ble/hrs_link.dart [603-605]

-    if (row?['adapter_id'] == kBleHrsAdapter.id) {
+    if (row?['adapter_id'] == kBleHrs.id) {
       await instance.disarm();
     }
Suggestion importance[1-10]: 9

__

Why: kBleHrsAdapter is undefined and will cause a compilation error, as the correct registry entry name is kBleHrs. Fixing this prevents a build failure and ensures the correct adapter ID is checked.

High

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 2127edb

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Failed to generate code suggestions for PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant