Skip to content

activity: measure sleep interruptions and naps - #2082

Open
TheFallen058 wants to merge 4 commits into
coredevices:mainfrom
TheFallen058:feature/sleep-tracking
Open

TheFallen058 wants to merge 4 commits into
coredevices:mainfrom
TheFallen058:feature/sleep-tracking

Conversation

@TheFallen058

@TheFallen058 TheFallen058 commented Sep 16, 2026

Copy link
Copy Markdown

The sleep card reports one opaque total, so a night broken by a trip to the kitchen reads the same as an unbroken one, and naps have been absent from the watch UI since 4.0 folded them into the total. Users have asked for both: interrupted nights in Pebble doesn't track multi-phase sleep (PT2) ("I sometimes sleep in phases … the PT2 only seems to register the last phase") and Pebble Time 2 says that I slept less than I actually did ("I do have a young daughter, so sometimes I have to wake up for her"), naps back on the watch in No more naps? ("Naps now only show on the mobile app").

This measures the time spent up during a session and surfaces it, and breaks naps out of the total. How sessions are stitched together is untouched, so the reports of a dropped second sleep phase are not addressed here.

Changes

  • Steps reach the sleep detector: kalg_activities_update() already receives the minute's step count for the walk/run detectors, but the sleep detector only ever saw filtered VMC and orientation. The count now travels through minute_history, since the minute being scored trails utc_now by KALG_SLEEP_HALF_WIDTH + 1 and has to stay attributed to the right minute.
  • Interruption detection: an awake run inside a session counts once the user walks during it (wakeup_min_steps, 15 in a single minute) or once it lasts wakeup_min_run_minutes (8) without walking, which catches short interruptions that leave no steps. The score filter smears one active minute across nine, so run length overstates how long the user was really up; only minutes whose own VMC reaches wakeup_min_movement_vmc (400, 600 on the stricter asterix parameters) add to the reported total. Thresholds were swept against the sleep fixtures, and the not-worn nights stay at zero.
  • Two new metrics: ActivityMetricSleepAwakeSeconds and ActivityMetricSleepNapSeconds. The algorithm keeps its awake minutes in RAM and starts from zero after a reboot while the metric is restored from the settings file, so only the growth since the last update is folded in. The day rollover clears the algorithm's total through its own call rather than through activity_algorithm_metrics_changed_notification(), which also fires on every step and calorie write and would clear it mid-night.
  • Sleep detail card: awake time beside the deep sleep figure, and on displays with room for a third heading also the naps and the night left once naps and interruptions are taken out, so the headline total can be read. The third heading is gated on display height, so obelix (200x228) and getafix (260x260) show it while asterix (144x168) keeps two, since a third row pushes the weekly graph off screen there. Asterix also takes the short DEEP wording, since sharing the row halves the label area.

Verification

Full ./pbl test suite. Fixture expectations moved from a wake-up count to awake minutes (TEST_AWAKE_MIN), test_activity gained a regression test that the day's awake minutes survive the algorithm restarting from zero, and the sleep detail card baselines were regenerated; asterix was added to that test's platforms, which is what caught the label truncation and the graph falling off the fold.

Built obelix@pvt and asterix. Ran on a Pebble Time 2 across three nights: the awake figure matched the interruptions I remembered, naps matched an afternoon sleep, and the value survived a Back+Select hard reset.

Cost: the activity settings file goes from 9 to 11 history records per 15-minute write, so roughly a fifth more write volume there and proportionally more compaction, on the order of tens of millijoules a day. No new sensor use, no extra accelerometer wakeups, and nothing added to the phone link.

🤖 Co-authored with Claude Opus 5 via Cursor; see the Co-authored-by trailers on each commit.

The sleep detector only ever saw filtered VMC and orientation, even though
kalg_activities_update() already receives the minute's step count for the
walk/run detectors. Store steps in the per-minute sleep history and hand the
scored minute's value to the session state machine.

The minute being scored trails utc_now by KALG_SLEEP_HALF_WIDTH + 1, so the
step count has to travel through minute_history to stay attributed to the
right minute. No behaviour change; the value is only logged for now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Aliaksandr Karnilovich <alexander.karnilovich@capital.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Track how long the user is genuinely up during a sleep session, so the
health app can tell a night broken by a trip to the kitchen from an
unbroken one.

An awake run inside a session counts once the user walks during it
(wakeup_min_steps in a single minute) or once it lasts wakeup_min_run_minutes
without walking, which catches the short interruptions that leave no steps.
Because the score filter smears one active minute across nine, the run
length overstates how long the user was really up, so only minutes whose
own VMC reaches wakeup_min_movement_vmc add to the reported total.

Thresholds were swept against the sleep fixtures; the not-worn nights stay
at zero.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Aliaksandr Karnilovich <alexander.karnilovich@capital.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Expose the awake time the algorithm now measures, plus the share of the
day's sleep that came from naps, so the health app can show both.

The algorithm keeps its awake minutes in RAM and starts from zero after a
reboot, while the metric is restored from the settings file, so only the
growth since the last update is folded in. The day rollover clears the
algorithm's total through its own call rather than through
activity_algorithm_metrics_changed_notification(), which also fires on
every step and calorie write and would clear it mid-night.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Aliaksandr Karnilovich <alexander.karnilovich@capital.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Report the awake time beside the deep sleep figure, and on the displays
with room for a third heading also the naps and the night left once naps
and interruptions are taken out, so the headline total can be read.

The 144x168 display keeps two headings, since a third pushes the weekly
graph off screen, and asks for the short DEEP wording, since sharing the
row halves the label area.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Aliaksandr Karnilovich <alexander.karnilovich@capital.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
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