Skip to content

Heart rate continues to be reported when the watch is off-wrist: constant in hrPerMinute, fluctuating in FIT recordings #282

Description

@tobymurray

Two observations about heart rate output when the watch has no live PPG input. They may be one underlying issue seen through two recording paths, two related issues, or two unrelated ones. I can't tell from outside the firmware, so both are below and I'm happy to split this into separate issues if that's the right shape.

Note on scope: /DailyHealth/*.json and the CCS service (554E4100-A2CF-4DF8-…) don't appear in the published Docs/BLE-Services-Overview.md

A. hrPerMinute holds a constant value for hours when off-wrist

Reading per-minute heart rate from /DailyHealth/<YYYYMM>/dh_<YYYYMMDD>.json over FTS and from the CCS daily-health commands: hrPerMinute frequently contains long runs of a single repeated value. Two examples:

Run value Length (min) Starts Reading before Reading after
63 679 2026-08-08 10:40 65 0
91 64 2026-08-02 13:14 92 110

These correspond to periods when I wasn't wearing the watch. The values are read from completed dh_*.json files, so they are persisted to storage.

The run value is never exactly the preceding reading, but in 6 of the 9 runs it is within 2 bpm below it, so it looks like it settles slightly, then freezes.

The same condition is encoded two different ways. On days I didn't wear the watch at all, hrPerMinute is all zeros for the full 1440 minutes, and 0 also appears for isolated minutes mid-hour during normal wear. But when the watch is taken off partway through a day, that same off-wrist state produces a plausible-looking heart rate instead.

B. FIT recordings made off-wrist contain fluctuating heart rate

Separately: a FIT activity file recorded while the watch was off-wrist contains heart rate that cannot be real. Unlike A, it fluctuates rather than holding constant.

This observation is less characterised than A; I have the file and can provide the record series if useful. The relevant part is the contrast: a FIT file is written by the watch to its own flash with no BLE involved, so whatever produces this sits upstream of any reporting path.

The question this raises

A fluctuating output looks like an estimator processing a meaningless signal. A value held constant for 679 minutes looks like a register that stopped being updated. Those are different behaviours, which is why I can't tell whether this is:

  1. One issue: the estimator emits without valid input, and A is simply B sampled once a minute from a slowly-varying output.
  2. Two related issues: a common root (no off-wrist gating on the HR pipeline) surfacing differently because the sensor is actively driven during a recording and idle during passive monitoring.
  3. Two unrelated issues: B in the estimator, A a separate staleness problem specific to the daily-health path.

If (1), A's constancy is surprising given B fluctuates. If (3), A is worth fixing independently of B.

PR #167 notes that HR detection is "a frequency-domain algorithm, not per-beat detection", which would fit an estimator continuing to emit its last converged output when the input goes away, but that's a guess doesn't explain the difference between the two paths.

Why it matters for consumers

There's no per-sample confidence field, so a client can't distinguish "measured, genuinely stable" from "not measuring, still reporting". The only mitigation available from outside is a run-length heuristic, which deletes real data whenever a genuine resting period is flat enough to trip it.

Is off-wrist state available to the HR pipeline, and could 0 (or a validity/confidence signal) be emitted when there's no live input?

Reproduction

A: wear the watch until HR readings are being produced, take it off and leave it stationary for an hour or more, not on charge. Once the day closes, read /DailyHealth/<YYYYMM>/dh_<YYYYMMDD>.json. hrPerMinute shows one repeated value across the off-wrist period rather than 0.

B: record an activity with the watch off-wrist and inspect the heart_rate field of the resulting FIT file.

Environment: Firmware 1.3.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions