Skip to content

fix: step calibration counts 0 on Android — sticky standard-HR fallback suppressed the IMU stream - #103

Merged
abdulsaheel merged 2 commits into
OpenStrap:mainfrom
dannymcc:fix/live-fallback-blocks-calibration
Jul 20, 2026
Merged

fix: step calibration counts 0 on Android — sticky standard-HR fallback suppressed the IMU stream#103
abdulsaheel merged 2 commits into
OpenStrap:mainfrom
dannymcc:fix/live-fallback-blocks-calibration

Conversation

@dannymcc

@dannymcc dannymcc commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Symptom

Android users (myself included) run the step-calibration walk and the gauge never moves off 0 of 250, no matter how far they walk. iOS users don't hit it. The same starvation affects the workout screen's live step count.

Root cause — traced on a real device

I debugged this on a Pixel with a field bug report. At the exact moment calibration started, the app logged:

07-19 19:50:40.372  I flutter : [OpenStrap] Live streams: standard-HR only (marginal-radio fallback).

That's the early-out at the top of enableLiveStreams: when state.standardHrFallback is set, it arms realtime HR (0x28) only and silently skips the R10/R11 + IMU (0x33) toggles. The calibration pedometer counts exclusively over those high-rate accel frames, so it has literally nothing to count — while the screen keeps saying "Keep walking…".

The flag is set by two radio-health detectors (MarginalRadioDetector: 2 consecutive arm→quick-timeout cycles; FrameCorruptionDetector: ≥20% CRC failures over a 50-frame window). The design flaw is the combination of three properties:

  1. It never resets — nothing clears standardHrFallback short of killing the app process.
  2. It degrades silently — no UI anywhere reflects it; enableLiveStreams callers can't tell they got the degraded set.
  3. Android trips it far more readily than iOS — Android BLE stacks handle the high-rate flood less gracefully (connection-interval and buffering differences make CRC corruption and post-arm drops much more common), which is why this presents as an Android-only bug.

So one rough patch of radio early in a session permanently kills step calibration and workout step counts until the next app restart. The fallback itself is doing its job — protecting a struggling link from a flood it can't sustain — but it's over-scoped: it also vetoes a deliberate, foreground, user-initiated action where the flood is the entire point.

The fix

  • BleEngine.retryFullLiveStreams() — clears standardHrFallback, resets both detectors, and re-arms the full live set. If the radio genuinely can't sustain it, the detectors re-trip (and re-downgrade) within seconds — the protection isn't weakened, it just gets re-evaluated when the user explicitly asks for the stream.
  • startStepCalibration and startWorkout use it — these are the two features that need the 100 Hz accel. Spot check and breathing only consume 0x28 RR frames, which the fallback still delivers, so they're deliberately unchanged.
  • The calibration screen surfaces a re-trip — if the fallback trips again mid-walk, the user now sees a "Bluetooth can't keep up" card with a retry action (which re-runs the same clear-and-retry path) instead of "Keep walking…" over a count that will never move.

Testing

Verified the mechanism end-to-end on-device via logs (fallback line firing at calibration start on v0.9.15, Pixel). I couldn't run the app from this machine, so the changes are intentionally minimal and pattern-matched to the surrounding code: both detectors already had reset(), the engine already mutates+broadcasts state the same way on the trip path, and the screen reuses the existing StateCard. Would appreciate a maintainer smoke-test of: trip the fallback (walk out of range mid-live twice), then start a calibration — pre-patch it counts 0, post-patch it should re-arm and count.

Happy to split the UI card into a separate PR if you'd rather keep the engine change alone.

Summary by CodeRabbit

  • New Features

    • Added a “Retry stream” action during step calibration when Bluetooth performance is degraded.
    • Workouts and step calibration now automatically retry full live sensor streaming when the device is in a standard-HR fallback state or otherwise not running the expected full-IMU stream.
  • Bug Fixes

    • Restored full-rate live sensor streams after a temporary Bluetooth fallback, improving live step counting and calibration reliability.
    • Provided clearer on-screen guidance when Bluetooth can’t keep up during calibration.

Once the marginal-radio or frame-corruption detector tripped, the
standard-HR fallback flag was never reset and enableLiveStreams honoured
it silently — so every later calibration walk and workout counted 0 steps
for the rest of the process lifetime (the R10/IMU toggles were never
sent). Android trips the detectors far more readily than iOS, which is
why the 0-step calibration reports were Android-only.

- BleEngine.retryFullLiveStreams(): clears the fallback + resets both
  detectors, then re-arms the full live set. If the radio genuinely
  can't sustain the flood the detectors re-trip within seconds.
- startStepCalibration/startWorkout call it: an explicit foreground user
  action whose feature needs the 100 Hz stream is exactly the moment to
  retry (spot check + breathing only need 0x28, which the fallback
  still delivers, so they're unchanged).
- Calibration screen: if the fallback re-trips mid-walk, show a
  'Bluetooth can't keep up' card with a retry action instead of
  'Keep walking…' over a count that will never move.
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d6fba523-2cda-46e7-9cde-a6e1161d1a2c

📥 Commits

Reviewing files that changed from the base of the PR and between 2127948 and 5244572.

📒 Files selected for processing (1)
  • lib/state/app_state.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/state/app_state.dart

📝 Walkthrough

Walkthrough

Adds an explicit full live-stream retry API, invokes it during step calibration and workout startup after degraded streaming, and displays a retry action when calibration detects Bluetooth radio degradation.

Changes

Full live stream retry

Layer / File(s) Summary
BleEngine retry operation
lib/ble/ble_engine.dart
Adds retryFullLiveStreams() to clear standard-HR fallback, reset detectors, notify state observers, and re-enable live streams.
Calibration and workout integration
lib/state/app_state.dart
Invokes the retry operation when starting calibration or a connected workout while full IMU live streaming is unavailable.
Degraded-radio calibration feedback
lib/ui/today/step_calibration_screen.dart
Shows a Bluetooth degradation card with a “Retry stream” action during calibration when the fallback flag is active.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant StepCalibrationScreen
  participant AppState
  participant BleEngine
  participant LiveStreams
  User->>StepCalibrationScreen: Select “Retry stream”
  StepCalibrationScreen->>AppState: Restart calibration
  AppState->>BleEngine: retryFullLiveStreams()
  BleEngine->>BleEngine: Clear fallback and reset detectors
  BleEngine->>LiveStreams: enableLiveStreams()
  LiveStreams-->>AppState: Re-enable full live stream set
Loading
🚥 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 describes the main fix: Android step calibration counts were stuck at zero because the standard-HR fallback blocked the IMU stream.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/state/app_state.dart`:
- Around line 2926-2932: Update the workout-start retry condition around
engine.retryFullLiveStreams() to run for connected devices when
!engine.liveEnabled, engine.liveHrOnly, or device.standardHrFallback is true.
Preserve the existing unawaited retry behavior so every workout explicitly
re-arms the full live stream set.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 12ed35e0-b7a7-471c-8ba6-b768a484f76a

📥 Commits

Reviewing files that changed from the base of the PR and between 14c6075 and 2127948.

📒 Files selected for processing (3)
  • lib/ble/ble_engine.dart
  • lib/state/app_state.dart
  • lib/ui/today/step_calibration_screen.dart

Comment thread lib/state/app_state.dart
@dannymcc

Copy link
Copy Markdown
Contributor Author

Tested this on the affected Pixel with a side-by-side debug build of this branch — calibration now counts properly on a device where it previously sat at 0 (the fallback had tripped and was being cleared on walk start, as designed).

@abdulsaheel
abdulsaheel merged commit 1f47b32 into OpenStrap:main Jul 20, 2026
1 check 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.

2 participants