fw/services/activity: fire sleep summary notification once per sleep window#1767
Merged
jplexer merged 2 commits intoJul 21, 2026
Conversation
…window The "Good morning" sleep summary re-fired hours after the real morning notification whenever the day's sleep bounds changed. Two paths re-armed it: a pin update for an extended sleep exit cleared the notified flag, and any shift of sleep_enter_utc was treated as a new day and reset the whole pin state. A falsely detected evening sleep session (sedentary awake time misclassified by the algorithm, tracked separately) was enough to trigger either path, buzzing the user with last night's summary in the evening or in the middle of the night. Key the pin state to the sleep-day window (the 9pm local cutoff used by activity_sessions_prv_get_sleep_bounds_utc, now exposed as activity_sessions_prv_get_sleep_window_start_utc) instead of comparing exact enter timestamps. Within one window the notification fires at most once: pin updates no longer clear the notified flag, and only a new window resets the state. Additionally, only notify when the sleep exit lands at a plausible local wake-up time ([4am, 6pm)); skipping outside that window does not consume the once-per-window notification, so a mid-night false exit stays silent and the real morning wake-up still notifies. The timeline pin keeps updating silently in all cases. The persisted SleepPinState grows by a trailing window_utc field. Older persisted blobs fail the settings read cleanly (E_RANGE zeroes the output), and a zero window is re-seeded at init as already-notified so the state layout change cannot re-fire a summary the user already saw; the cost is at most one missed summary right after the update. Fixes FIRM-3101 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Joshua Jun <lets@throw.rocks>
AttributeIdIconTiny is a ResourceId-typed attribute, but the insight notification builder added it with attribute_list_add_uint32, producing the "Adding attribute with type uint32 for non-uint32_t attribute" warning in field logs on every insight notification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Joshua Jun <lets@throw.rocks>
gmarull
approved these changes
Jul 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Users receive their "Good morning" sleep summary notification again hours after the real one — in the field trace, a duplicate arrived at 22:12 local, ~15 hours after the morning summary, and again the next day without any reboot in between (same symptom family as the mid-night summary complaints).
Root cause
Two layers:
Detection accuracy (out of scope here, tracked in FIRM-1969): Kraepelin falsely detects ~40-60 min of "sleep" during sedentary-awake evening time. Sessions ending after 21:00 local (or starting before 12:00) escape nap reclassification and count as night sleep.
Insights layer (this fix):
prv_do_sleep_summary()treated any change of the day's sleep bounds as new-sleep-to-report:sleep_exit_utccleareds_sleep_pin_state.notified, re-arming the notification;sleep_enter_utcwas treated as "new day" and reset the whole pin state.Either path re-fires the full summary notification 30 minutes after the (false) exit, once the user is briefly active. The field's false 19:48-20:42Z session produced the duplicate at exactly exit+30min.
Fix design
activity_sessions_prv_get_sleep_bounds_utc(), extracted asactivity_sessions_prv_get_sleep_window_start_utc()). Within one window the notification fires at most once: pin updates no longer clearnotified, and only a new window resets the state. Exact-timestamp matching is gone, so bounds/enter shifts (which fw/services/activity: keep short sleep cycles inside a fragmented night #1742 makes more common) and settings-state "time travel" resets can no longer re-arm it. The timeline pin still updates silently.sleep_exit_utclands in [04:00, 18:00) local. Skipping outside that window does not consume the once-per-window notification, so a mid-night false exit stays silent and the real morning wake-up still notifies. The pin is unaffected.SleepPinStategrows by a trailingwindow_utcfield, persisted via the existingActivitySettingsKeyInsightSleepSummaryStatekey. Older persisted blobs fail the settings read cleanly (E_RANGEzeroes the output — no partial misread of old-layout bytes), and a zeroed state is re-seeded at init as already-notified for the in-progress window, so the layout change cannot re-fire a summary the user already saw. Worst case is one missed summary right after the update.AttributeIdIconTinyis added withattribute_list_add_resource_id()instead ofattribute_list_add_uint32(), silencing the "Adding attribute with type uint32 for non-uint32_t attribute" warning seen in field logs.Test plan
tests/fw/services/activity/test_activity_insights.c:sleep_summary_no_renotify_same_window: night sleep → morning summary fires once → same-window evening session extension (analog of the field's false session) → pin updates in place, no second notification.sleep_summary_notify_next_window: the next night past the 21:00 cutoff starts a new window with a new pin, and the notification fires again.sleep_summary_notification_wake_window_gate: a 02:00-local exit does not notify and does not consume the window's notification; the later 07:00 wake still notifies../waf test -M '.*activity.*': 12/12 suites pass (includestest_activity, which compiles the refactoredactivity_sessions.c)../waf build(obelix@pvt, normal variant) succeeds.Fixes FIRM-3101
🤖 Generated with Claude Code