hrm: add HRV support for GH3X2X (obelix)#1670
Conversation
Night 2 (2026-07-07→08) — release-config firmware, duty-cycled samplingTwo things changed from night 1, so these numbers are not apples-to-apples:
Both battery and RMSSD move for two reasons at once — flagging so nothing here is read as a controlled comparison. Battery (headline):
RR / HRV (duty-cycled → far fewer accepted beats):
Reference (Garmin Instinct Crossover, other wrist): overnight avg HRV 45 ms, highest 5-min avg 64 ms. The rejection rate is high (expected for wrist optical + duty-cycling); accepted-beat count is low enough that I'd treat the single-night RMSSD as indicative only. Collecting more nights. |
|
Status update — pausing hardware testing Third overnight test. Battery figures across all three runs, for context:
So debug UART was the dominant cost, and on release config the continuous-vs-duty-cycled difference accounts for roughly ~1%/hr. Also confirmed this run:
I'll be away from my test hardware for a few weeks, so the draft stays as-is until I'm back — will follow up with a clean continuous-vs-duty-cycle battery comparison then. |
7d87042 to
dd32839
Compare
Transport validation on hardware (Pebble Time 2, obelix@pvt)Firmware: 3-minute awake test at rest:
Subscribe/unsubscribe:
HR regression check:
All three transport criteria met: correctly-scaled PPI on |
Overnight validation + sparsity root cause (100 Hz fix,
|
| Metric | 25 Hz | 100 Hz |
|---|---|---|
| Awake delivery (5 min sitting) | — | ~92 intervals/min |
| Overnight delivery | ~2/min | 64/min (38,244 intervals / 9h57m) |
| Overnight rejection ratio | ~50% | 1.3% |
| Overnight RMSSD | — | 63 ms (physiologically sane) |
Battery. At the fixed 100 Hz HRV registration rate, overnight draw with continuous sampling is ~0.9 %/hr (release build, static screen), vs ~0.4–0.8 %/hr at 25 Hz — roughly +4–5% per night for the rate change. For nightly recording this is a meaningful cost: a user seeing ~4 weeks of standby would see it roughly halved. Flagging it as a real trade-off of running the HRV function at its design rate.
Add HealthEventHRVUpdate carrying peak-to-peak intervals (PPI) from the HRM, a health_service_peek_hrv_ppi_ms() accessor for the most recent value, and health_service_set_hrv_sample_period() which lets an app request HRV collection the same way it requests a heart rate sample period. The last cached PPI lives in HealthServiceState rather than an applib static so it is safe in app code paths. Part of coredevices#1630. Signed-off-by: karthakon <33638614+karthakon@users.noreply.github.com> Co-Authored-By: Claude <noreply@anthropic.com>
Change hrm_enable() to take the requested HRMFeature set (matching the direction of coredevices#1607) and a low_latency flag (reserved, unused here). hrm_manager computes the union of active subscriber features, passes it to the driver, and restarts the sensor when the needed feature set changes, so HRV is only collected while a subscriber holds HRMFeature_HRV. In the GH3X2X driver, when HRV is requested: enable the HRV function, mirror the HR PPG channel map onto it (the shipped register config only maps channels for HR/SpO2/ADT, so HRV otherwise comes up with zero channels and never receives frames), and run it at the HR sampling rate. gh3x2x_hrv_result_report() forwards each valid RR interval from the Goodix HRV algorithm to hrm_manager. hrm_manager broadcasts HRV results as HealthEventHRVUpdate so apps subscribed through health_service receive them without a raw HRM session. Everything is behind the new HRM_HRV Kconfig, default n. Part of coredevices#1630. Signed-off-by: karthakon <33638614+karthakon@users.noreply.github.com> Co-Authored-By: Claude <noreply@anthropic.com>
Turn on CONFIG_HRM_HRV for obelix (PT2) and bump pebbleos-nonfree to pick up the GH3X2X HRV algorithm hook (companion PR). Tested on a retail PT2: RR intervals flow end to end from the sensor to a watchapp via HealthEventHRVUpdate only while the app holds an HRV sample period; HR-only operation is unchanged when no app requests HRV. Closes coredevices#1630. Signed-off-by: karthakon <33638614+karthakon@users.noreply.github.com> Co-Authored-By: Claude <noreply@anthropic.com>
The Goodix HRV algorithm's vendor config declares fs = 100, but the driver was registering the HRV function at the HR rate (25 Hz), and GH3x2xHrvAlgoInit overwrites the config fs with the function's actual rate. Running the algorithm at a quarter of its design rate is the leading suspect for the sparse overnight RRI stream (~2/min observed vs ~100/min expected). HR remains at 25 Hz; the rate is per-function. Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: karthakon <33638614+karthakon@users.noreply.github.com>
Implements HRV support per the plan in #1630.
Three commits: applib/SDK surface (HealthEventHRVUpdate, health_service_peek_hrv_ppi_ms(), health_service_set_hrv_sample_period()), the feature-gated driver/manager work, and the obelix enable + nonfree bump.
hrm_enable() now takes the requested HRMFeature set (aligned with the direction of #1607; the low_latency parameter is reserved and unused here). hrm_manager passes the union of active subscriber features to the driver and restarts the sensor when that set changes, so HRV is only sampled while an app holds an HRV sample period. HRV results are broadcast as HealthEventHRVUpdate.
Tested on a retail PT2: RR intervals flow end to end to a watchapp built against an SDK from this tree. First full-night run (~8h): nightly RMSSD 39 ms; same-night Garmin Instinct Crossover on the other wrist reported overnight HRV 26 ms (both optical PRV; difference under investigation, more nights being collected). Battery 63% -> 28% overnight with continuous HRV sampling and the test app recording. Per-minute session detail from this run was lost to a UI mishap in the test app, not a firmware issue. HR-only operation is unchanged when no app requests HRV. Test app: https://github.com/karthakon/hrv-monitor
Note: my test unit's touchscreen failed during this period, including on stock firmware, PRF, and after factory reset — a hardware fault unrelated to these changes (nothing here touches the touch stack). Mentioned for full transparency.
Companion nonfree PR (required — firmware calls into the HRV algorithm result-report filled there): coredevices/pebbleos-nonfree#6
Fixes #1630.