From a982db1e4eb390a235d81f921b3223c6cd1185c0 Mon Sep 17 00:00:00 2001 From: Ara Michelle Date: Sat, 20 Jun 2026 02:53:19 +0000 Subject: [PATCH 1/2] activity: re-sync charging gate so step tracking can't wedge off Step tracking is gated by enabled_charging_state (steps are disabled while plugged in), which is only ever updated from battery state-change events. If such an event is missed -- as happened when the battery service was sampling too sparsely -- the gate stays stuck "as if plugged in" and step counting stays off until reboot. Add an always-on once-a-minute timer (independent of the tracking-gated cron, which is torn down while tracking is stopped) that re-derives the charging gate from the live battery state and re-kicks a start that never happened. prv_set_enable_cb is idempotent, so the re-kick is safe. Signed-off-by: Ara Michelle (cherry picked from commit 5485b8368a6969a23b980c27b2ac625bcdd2d43f) --- src/fw/services/activity/activity.c | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/fw/services/activity/activity.c b/src/fw/services/activity/activity.c index ac8507de5..2d5a3fcca 100644 --- a/src/fw/services/activity/activity.c +++ b/src/fw/services/activity/activity.c @@ -15,6 +15,7 @@ #include "process_management/worker_manager.h" #include "pbl/services/battery/battery_state.h" #include "pbl/services/hrm/hrm_manager_private.h" +#include "pbl/services/regular_timer.h" #include "pbl/services/system_task.h" #include "pbl/services/vibe_pattern.h" #include "pbl/services/alarms/alarm.h" @@ -891,6 +892,37 @@ static void prv_handle_activity_enabled_change(void) { system_task_add_callback(prv_set_enable_cb, NULL); } +// The charging gate (enabled_charging_state) is otherwise only updated from +// battery state-change events. A missed event can leave step tracking wedged +// off "as if plugged in" until reboot. Re-derive it from the live battery state +// once a minute so it self-heals, and re-kick a start that never happened. +static void prv_charging_resync_system_task_cb(void *data) { +#ifndef CONFIG_IS_BIGBOARD + const bool charging_ok = !battery_is_usb_connected(); + bool needs_reeval = false; + mutex_lock_recursive(s_activity_state.mutex); + if (charging_ok != s_activity_state.enabled_charging_state) { + s_activity_state.enabled_charging_state = charging_ok; + needs_reeval = true; + } else if (s_activity_state.should_be_started && !s_activity_state.started && + prv_activity_allowed_to_be_enabled()) { + needs_reeval = true; + } + mutex_unlock_recursive(s_activity_state.mutex); + if (needs_reeval) { + prv_handle_activity_enabled_change(); + } +#endif +} + +static void prv_charging_resync_timer_cb(void *unused) { + system_task_add_callback(prv_charging_resync_system_task_cb, NULL); +} + +static RegularTimerInfo s_charging_resync_timer = { + .cb = prv_charging_resync_timer_cb, +}; + static void prv_charger_event_cb(PebbleEvent *e, void *context) { #ifndef CONFIG_IS_BIGBOARD // Since bigboards are usually plugged in, don't react to a battery connection event @@ -1010,6 +1042,9 @@ bool activity_init(void) { .handler = prv_charger_event_cb, }; event_service_client_subscribe(&s_activity_state.charger_subscription); + // Safety net: re-sync the charging gate from live state in case a battery + // state-change event is ever missed. + regular_timer_add_minutes_callback(&s_charging_resync_timer); #ifdef CONFIG_IS_BIGBOARD s_activity_state.enabled_charging_state = true; #else @@ -1416,6 +1451,7 @@ bool activity_test_reset(bool reset_settings, bool tracking_on, sys_psleep(1); } cron_job_unschedule(&s_activity_job); + regular_timer_remove_callback(&s_charging_resync_timer); mutex_destroy((PebbleMutex *)s_activity_state.mutex); if (reset_settings) { pfs_remove(ACTIVITY_SETTINGS_FILE_NAME); From 32066a35b901c0a97eb3a6ed42a038b69d2ac46d Mon Sep 17 00:00:00 2001 From: Ara Michelle Date: Sat, 20 Jun 2026 03:48:57 +0000 Subject: [PATCH 2/2] tests: init regular_timer in activity test activity_init() now registers a regular-timer minute callback for the charging-gate re-sync. The activity test links the real regular_timer service but never initialized it, so the call asserted on its uninitialized callback-list semaphore. Initialize the service once in the test setup (it asserts on re-init, hence the guard) so the registration succeeds. Signed-off-by: Ara Michelle (cherry picked from commit aff5eafec5bee0507e68fd11b93d4e4935356ed8) --- tests/fw/services/activity/test_activity.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/fw/services/activity/test_activity.c b/tests/fw/services/activity/test_activity.c index debe0f245..4b439647f 100644 --- a/tests/fw/services/activity/test_activity.c +++ b/tests/fw/services/activity/test_activity.c @@ -16,6 +16,7 @@ #include "pbl/services/data_logging/data_logging_service.h" #include "pbl/services/filesystem/pfs.h" #include "pbl/services/protobuf_log/protobuf_log.h" +#include "pbl/services/regular_timer.h" #include "shell/prefs.h" #include "system/logging.h" #include "system/passert.h" @@ -1008,6 +1009,15 @@ void test_activity__initialize(void) { pfs_init(false); pfs_format(false); + // The real regular_timer service is linked into this test; initialize it once + // (it asserts on re-init) so the charging-gate re-sync timer that activity_init + // now registers doesn't assert on an uninitialized callback list. + static bool s_regular_timer_inited = false; + if (!s_regular_timer_inited) { + regular_timer_init(); + s_regular_timer_inited = true; + } + prv_activity_algorithm_erase_minute_data(); prv_activity_init_and_set_enabled(true);