/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "clar.h" #include "drivers/hrm.h" #include "os/tick.h" #include "services/common/hrm/hrm_manager.h" #include "services/common/hrm/hrm_manager_private.h" #include "util/size.h" #include "fake_app_manager.h" #include "fake_events.h" #include "fake_new_timer.h" #include "fake_pbl_malloc.h" #include "fake_system_task.h" #include "fake_queue.h" #include "fake_rtc.h" #include "stubs_accel_manager.h" #include "stubs_analytics.h" #include "stubs_event_service_client.h" #include "stubs_logging.h" #include "stubs_mutex.h" #include "stubs_passert.h" #include "stubs_prompt.h" #include "stubs_worker_manager.h" #include #include // ----------------------------------------------------------------------------- // T_STATIC functions // ----------------------------------------------------------------------------- extern HRMSubscriberState * prv_get_subscriber_state_from_ref(HRMSessionRef session); extern HRMSubscriberState * prv_get_subscriber_state_from_app_id(PebbleTask task, AppInstallId app_id); extern void prv_read_event_from_buffer_and_consume(CircularBuffer *buffer, PebbleHRMEvent *event); extern uint32_t prv_num_system_task_events_queued(void); extern TimerID prv_get_timer_id(void); extern bool prv_can_turn_sensor_on(void); extern void prv_charger_event_cb(PebbleEvent *e); // ----------------------------------------------------------------------------- // HRM Driver fakes // ----------------------------------------------------------------------------- static struct { bool enabled; } s_hrm_state; void hrm_enable(HRMDevice *dev) { s_hrm_state.enabled = true; } void hrm_disable(HRMDevice *dev) { s_hrm_state.enabled = false; } bool hrm_is_enabled(HRMDevice *dev) { return s_hrm_state.enabled; } // ----------------------------------------------------------------------------- // Queue Fakes // ----------------------------------------------------------------------------- static const QueueHandle_t FAKE_APP_QUEUE = (QueueHandle_t) 1337; static uint32_t s_event_count; static PebbleEvent s_events_received[16]; signed portBASE_TYPE xQueueGenericSend(QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, portBASE_TYPE xCopyPosition) { cl_assert_equal_i((intptr_t) xQueue, (intptr_t) FAKE_APP_QUEUE); if (s_event_count < ARRAY_LENGTH(s_events_received)) { s_events_received[s_event_count] = *((PebbleEvent *)pvItemToQueue); } ++s_event_count; return pdTRUE; } QueueHandle_t pebble_task_get_to_queue(PebbleTask task) { switch (task) { case PebbleTask_App: return FAKE_APP_QUEUE; case PebbleTask_KernelBackground: return NULL; default: WTF; } } // ----------------------------------------------------------------------------- // Fakes // ----------------------------------------------------------------------------- bool mfg_info_is_hrm_present(void) { return true; } static bool s_activity_prefs_heart_rate_is_enabled = true; bool activity_prefs_heart_rate_is_enabled(void) { return s_activity_prefs_heart_rate_is_enabled; } bool battery_is_usb_connected(void) { return false; } // ----------------------------------------------------------------------------- // Test Helpers // ----------------------------------------------------------------------------- #define TO_SESSION_REF(x) ((HRMSessionRef)(long)(x)) static const HRMData s_hrm_event_data = { .led_current_ua = 243, .hrm_bpm = 82, .hrm_quality = HRMQuality_Excellent, }; static void prv_fake_send_new_data(void) { hrm_manager_new_data_cb(&s_hrm_event_data); } static PebbleHRMEvent s_cb_events_1[16]; static int s_num_cb_events_1 = 0; static void prv_fake_hrm_1_cb(PebbleHRMEvent *event, void *context) { if (s_num_cb_events_1 >= ARRAY_LENGTH(s_cb_events_1)) { return; } s_cb_events_1[s_num_cb_events_1++] = *event; } static PebbleHRMEvent s_cb_events_2[16]; static int s_num_cb_events_2 = 0; static void prv_fake_hrm_2_cb(PebbleHRMEvent *event, void *context) { if (s_num_cb_events_2 >= ARRAY_LENGTH(s_cb_events_2)) { return; } s_cb_events_2[s_num_cb_events_2++] = *event; } static void prv_put_battery_state_change_event(bool is_plugged_in) { PebbleEvent e = { .type = PEBBLE_BATTERY_STATE_CHANGE_EVENT, .battery_state.new_state.is_plugged = is_plugged_in, }; event_put(&e); } // ----------------------------------------------------------------------------- // Tests // ----------------------------------------------------------------------------- void test_hrm_manager__initialize(void) { // Init time fake_rtc_init(100 /*initial_ticks*/, 1465243370); stub_pebble_tasks_set_current(PebbleTask_App); app_manager_get_task_context()->to_process_event_queue = (void *)0x1; fake_system_task_callbacks_cleanup(); s_activity_prefs_heart_rate_is_enabled = true; s_event_count = 0; s_num_cb_events_1 = 0; s_num_cb_events_2 = 0; memset(&s_hrm_state, 0, sizeof(s_hrm_state)); hrm_manager_init(); hrm_manager_enable(true); fake_event_init(); } void test_hrm_manager__subscription(void) { AppInstallId app_id = 1; const uint32_t update_interval_s = 1; const uint16_t expire_s = SECONDS_PER_MINUTE; HRMFeature features = HRMFeature_BPM; HRMSessionRef session_ref = sys_hrm_manager_app_subscribe(app_id, update_interval_s, expire_s, features); fake_system_task_callbacks_invoke_pending(); HRMSubscriberState *subscriber = prv_get_subscriber_state_from_ref(session_ref); cl_assert(subscriber); cl_assert(subscriber->session_ref == session_ref); cl_assert(subscriber->expire_utc == rtc_get_time() + expire_s); cl_assert(subscriber->update_interval_s == update_interval_s); cl_assert(subscriber->features == HRMFeature_BPM); cl_assert_equal_b(hrm_is_enabled(HRM), true); // We should be able to find it by app ad as well cl_assert(sys_hrm_manager_get_app_subscription(app_id) == session_ref); // We should be able to get info on it AppInstallId ret_app_id; uint32_t ret_update_interval_s; uint16_t ret_expire_s; HRMFeature ret_features; cl_assert(sys_hrm_manager_get_subscription_info(session_ref, &ret_app_id, &ret_update_interval_s, &ret_expire_s, &ret_features)); cl_assert(ret_app_id == app_id); cl_assert(ret_update_interval_s == update_interval_s); cl_assert(ret_expire_s == expire_s); cl_assert(ret_features == features); sys_hrm_manager_unsubscribe(session_ref); fake_system_task_callbacks_invoke_pending(); cl_assert(prv_get_subscriber_state_from_ref(session_ref) == NULL); cl_assert_equal_b(hrm_is_enabled(HRM), false); } // When we cleanup after an app process, its subscription, if any, should get an expriration time // placed on it void test_hrm_manager__app_cleanup(void) { stub_pebble_tasks_set_current(PebbleTask_App); AppInstallId app_id = 1; const uint32_t update_interval_s = 1; uint16_t expire_s = 0; HRMFeature features = HRMFeature_BPM; // If we subscribe with no expiration, we should get 0 back HRMSessionRef session_ref = sys_hrm_manager_app_subscribe(app_id, update_interval_s, expire_s, features); cl_assert(sys_hrm_manager_get_app_subscription(app_id) == session_ref); uint16_t ret_expire_s; sys_hrm_manager_get_subscription_info(session_ref, NULL, NULL, &ret_expire_s, NULL); cl_assert_equal_i(ret_expire_s, 0); // Now, call the process cleanup. This should place an expiration time on the subscription hrm_manager_process_cleanup(PebbleTask_App, app_id); cl_assert(sys_hrm_manager_get_app_subscription(app_id) == session_ref); sys_hrm_manager_get_subscription_info(session_ref, NULL, NULL, &ret_expire_s, NULL); cl_assert_equal_i(ret_expire_s, HRM_MANAGER_APP_EXIT_EXPIRATION_SEC); sys_hrm_manager_unsubscribe(session_ref); } // Test that app subscriptions expire correctly void test_hrm_manager__app_expiration(void) { AppInstallId app_id = 1; const uint16_t expire_s = SECONDS_PER_MINUTE; HRMSessionRef session_ref = sys_hrm_manager_app_subscribe(app_id, 1, expire_s, HRMFeature_BPM); cl_assert(sys_hrm_manager_get_app_subscription(app_id) == session_ref); prv_fake_send_new_data(); // We should get the BPM event cl_assert_equal_i(s_event_count, 1); cl_assert_equal_i(s_events_received[0].type, PEBBLE_HRM_EVENT); cl_assert_equal_i(s_events_received[0].hrm.event_type, HRMEvent_BPM); // Subscribe again before we expire, should get the same session ref back HRMSessionRef new_session_ref = sys_hrm_manager_app_subscribe(app_id, 1, expire_s, HRMFeature_BPM); cl_assert(new_session_ref == session_ref); // Now advance time past the expiration time rtc_set_time(rtc_get_time() + expire_s + 1); // Send more data, the subscription should get expired now prv_fake_send_new_data(); cl_assert_equal_i(s_event_count, 3); cl_assert_equal_i(s_events_received[1].type, PEBBLE_HRM_EVENT); cl_assert_equal_i(s_events_received[1].hrm.event_type, HRMEvent_BPM); cl_assert_equal_i(s_events_received[2].type, PEBBLE_HRM_EVENT); cl_assert_equal_i(s_events_received[2].hrm.event_type, HRMEvent_SubscriptionExpiring); // Subscription should be gone cl_assert(prv_get_subscriber_state_from_ref(session_ref) == NULL); cl_assert(sys_hrm_manager_get_app_subscription(app_id) == HRM_INVALID_SESSION_REF); } // Test that system subscriptions expire correctly void test_hrm_manager__kernel_expiration(void) { stub_pebble_tasks_set_current(PebbleTask_KernelBackground); const uint16_t expire_s = SECONDS_PER_MINUTE; HRMSessionRef session_ref = hrm_manager_subscribe_with_callback(INSTALL_ID_INVALID, 1, expire_s, HRMFeature_BPM, prv_fake_hrm_1_cb, NULL); prv_fake_send_new_data(); fake_system_task_callbacks_invoke_pending(); // Make sure we got the expected data cl_assert_equal_i(s_num_cb_events_1, 1); cl_assert_equal_i(s_cb_events_1[0].event_type, HRMEvent_BPM); cl_assert_equal_i(s_cb_events_1[0].bpm.bpm, s_hrm_event_data.hrm_bpm); cl_assert_equal_i(s_cb_events_1[0].bpm.quality, s_hrm_event_data.hrm_quality); // Now advance time to just before the expiration time rtc_set_time(rtc_get_time() + expire_s - 1); // Send more data, the callback should get the expiring event prv_fake_send_new_data(); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_i(s_num_cb_events_1, 3); cl_assert_equal_i(s_cb_events_1[1].event_type, HRMEvent_SubscriptionExpiring); cl_assert_equal_i(s_cb_events_1[2].event_type, HRMEvent_BPM); // Now advance time to past expiration time rtc_set_time(rtc_get_time() + expire_s + 1); // Send more data, the subscription should go away now prv_fake_send_new_data(); fake_system_task_callbacks_invoke_pending(); cl_assert(prv_get_subscriber_state_from_ref(session_ref) == NULL); } void test_hrm_manager__subscribe_multiple(void) { const int num_refs = 3; HRMSessionRef session_refs[num_refs]; AppInstallId app_ids[num_refs]; stub_pebble_tasks_set_current(PebbleTask_App); AppInstallId app_id = 1; for (int i = 0; i < num_refs; ++i, app_id++) { const uint16_t expire_s = SECONDS_PER_MINUTE; session_refs[i] = sys_hrm_manager_app_subscribe(app_id, 1, expire_s, HRMFeature_BPM); app_ids[i] = app_id; } // Ensure sure all can be found for (int i = 0; i < num_refs; ++i) { cl_assert(prv_get_subscriber_state_from_ref(session_refs[i])); cl_assert(prv_get_subscriber_state_from_app_id(PebbleTask_App, app_ids[i])); } cl_assert(prv_get_subscriber_state_from_ref(HRM_INVALID_SESSION_REF) == NULL); cl_assert(prv_get_subscriber_state_from_app_id(PebbleTask_App, INSTALL_ID_INVALID) == NULL); // Unsubscribe, HRM should be disabled after for (int i = 0; i < num_refs; ++i) { sys_hrm_manager_unsubscribe(session_refs[i]); cl_assert(prv_get_subscriber_state_from_ref(session_refs[i]) == NULL); cl_assert(prv_get_subscriber_state_from_app_id(PebbleTask_App, app_ids[i]) == NULL); } cl_assert_equal_b(hrm_is_enabled(HRM), false); } void test_hrm_manager__feature_callbacks(void) { const int num_refs = 2; HRMSessionRef session_refs[num_refs]; AppInstallId app_id = 1; for (int i = 0; i < num_refs; ++i, app_id++) { const uint16_t expire_s = SECONDS_PER_MINUTE; session_refs[i] = sys_hrm_manager_app_subscribe(app_id, 1, expire_s, HRMFeature_BPM); } prv_fake_send_new_data(); // One event for each app subscriber cl_assert_equal_i(s_event_count, num_refs); for (int i = 0; i < num_refs; ++i) { sys_hrm_manager_unsubscribe(session_refs[i]); } } void test_hrm_manager__no_feature_callbacks(void) { // Subscribe and fake data being sent AppInstallId app_id = 1; const uint16_t expire_s = SECONDS_PER_MINUTE; HRMSessionRef session_ref = sys_hrm_manager_app_subscribe(app_id, 1, expire_s, 0 /* No feature */); prv_fake_send_new_data(); fake_system_task_callbacks_invoke_pending(); // HRM should be enabled, subscriber should exist, no callbacks triggered. cl_assert_equal_b(hrm_is_enabled(HRM), true); cl_assert(prv_get_subscriber_state_from_ref(session_ref)); cl_assert_equal_i(s_event_count, 0); sys_hrm_manager_unsubscribe(session_ref); } void test_hrm_manager__different_feature_callbacks(void) { AppInstallId app_id = 1; const uint16_t expire_s = SECONDS_PER_MINUTE; HRMSessionRef bpm_session = sys_hrm_manager_app_subscribe(app_id, 1, expire_s, HRMFeature_BPM); HRMSessionRef led_session = sys_hrm_manager_app_subscribe(app_id + 1, 1, expire_s, HRMFeature_LEDCurrent); HRMSessionRef all_session = sys_hrm_manager_app_subscribe(app_id + 2, 1, expire_s, HRMFeature_BPM|HRMFeature_LEDCurrent); HRMSessionRef no_session = sys_hrm_manager_app_subscribe(app_id + 3, 1, expire_s, 0 /* no features */); prv_fake_send_new_data(); fake_system_task_callbacks_invoke_pending(); // Expect 4 events: 1 for BPM, 1 for LED, 2 for subscribing to all, none for no feature. cl_assert_equal_i(s_event_count, 4); sys_hrm_manager_unsubscribe(bpm_session); sys_hrm_manager_unsubscribe(led_session); sys_hrm_manager_unsubscribe(all_session); sys_hrm_manager_unsubscribe(no_session); } void test_hrm_manager__multiple_feature_callbacks(void) { const int num_refs = 2; HRMSessionRef session_refs[num_refs]; AppInstallId app_id = 1; for (int i = 0; i < num_refs; ++i, app_id++) { session_refs[i] = TO_SESSION_REF(i+1); const uint16_t expire_s = SECONDS_PER_MINUTE; sys_hrm_manager_app_subscribe(app_id, 1, expire_s, HRMFeature_BPM|HRMFeature_LEDCurrent); } prv_fake_send_new_data(); // Two events for each app subscriber cl_assert_equal_i(s_event_count, num_refs * 2); for (int i = 0; i < num_refs; ++i) { sys_hrm_manager_unsubscribe(session_refs[i]); } } void test_hrm_manager__system_task_data_callback(void) { stub_pebble_tasks_set_current(PebbleTask_KernelBackground); s_num_cb_events_1 = 0; const uint16_t expire_s = SECONDS_PER_MINUTE; HRMSessionRef session_ref = hrm_manager_subscribe_with_callback(INSTALL_ID_INVALID, 1, expire_s, HRMFeature_BPM, prv_fake_hrm_1_cb, NULL); fake_system_task_callbacks_invoke_pending(); prv_fake_send_new_data(); // Make sure event is queued up cl_assert_equal_i(prv_num_system_task_events_queued(), 1); // Make sure we successfully consume the event fake_system_task_callbacks_invoke_pending(); // Make sure we got the expected data cl_assert_equal_i(s_num_cb_events_1, 1); cl_assert_equal_i(s_cb_events_1[0].event_type, HRMEvent_BPM); cl_assert_equal_i(s_cb_events_1[0].bpm.bpm, s_hrm_event_data.hrm_bpm); cl_assert_equal_i(s_cb_events_1[0].bpm.quality, s_hrm_event_data.hrm_quality); sys_hrm_manager_unsubscribe(session_ref); } // Test having 2 different KernelBG subscribers. The data should only get pushed into the // circular buffer once, but both clients should get it void test_hrm_manager__multiple_system_task_data_callbacks(void) { stub_pebble_tasks_set_current(PebbleTask_KernelBackground); s_num_cb_events_1 = 0; s_num_cb_events_2 = 0; const uint16_t expire_s = SECONDS_PER_MINUTE; HRMSessionRef session_ref_1 = hrm_manager_subscribe_with_callback(INSTALL_ID_INVALID, 1, expire_s, HRMFeature_BPM, prv_fake_hrm_1_cb, NULL); fake_system_task_callbacks_invoke_pending(); HRMSessionRef session_ref_2 = hrm_manager_subscribe_with_callback(INSTALL_ID_INVALID, 1, expire_s, HRMFeature_BPM, prv_fake_hrm_2_cb, NULL); fake_system_task_callbacks_invoke_pending(); prv_fake_send_new_data(); // Make sure only 1 callback (and hence one circular buffer entry) got queued up cl_assert_equal_i(prv_num_system_task_events_queued(), 1); // Make sure we successfully get the event sent to both subscribers fake_system_task_callbacks_invoke_pending(); // Make sure we got the expected data to both clients cl_assert_equal_i(s_num_cb_events_1, 1); cl_assert_equal_i(s_cb_events_1[0].event_type, HRMEvent_BPM); cl_assert_equal_i(s_cb_events_1[0].bpm.bpm, s_hrm_event_data.hrm_bpm); cl_assert_equal_i(s_cb_events_1[0].bpm.quality, s_hrm_event_data.hrm_quality); cl_assert_equal_i(s_num_cb_events_1, 1); cl_assert_equal_i(s_cb_events_1[0].event_type, HRMEvent_BPM); cl_assert_equal_i(s_cb_events_1[0].bpm.bpm, s_hrm_event_data.hrm_bpm); cl_assert_equal_i(s_cb_events_1[0].bpm.quality, s_hrm_event_data.hrm_quality); sys_hrm_manager_unsubscribe(session_ref_1); sys_hrm_manager_unsubscribe(session_ref_2); } void test_hrm_manager__set_features(void) { AppInstallId app_id = 1; const uint16_t expire_s = SECONDS_PER_MINUTE; const HRMSessionRef session_ref = sys_hrm_manager_app_subscribe(app_id, 1, expire_s, HRMFeature_BPM); HRMSubscriberState *state = prv_get_subscriber_state_from_ref(session_ref); // Starts off with BPM enabled cl_assert_equal_i(state->features, HRMFeature_BPM); // Change to only LED Current sys_hrm_manager_set_features(session_ref, HRMFeature_LEDCurrent); cl_assert_equal_i(state->features, HRMFeature_LEDCurrent); // Change to LEDCurrent + BPM sys_hrm_manager_set_features(session_ref, HRMFeature_LEDCurrent | HRMFeature_BPM); cl_assert_equal_i(state->features, HRMFeature_LEDCurrent | HRMFeature_BPM); } void test_hrm_manager__set_update_internal(void) { AppInstallId app_id = 1; const uint16_t expire_a_s = SECONDS_PER_MINUTE; uint32_t update_interval_a_s = 1; HRMSessionRef session_ref = sys_hrm_manager_app_subscribe(app_id, update_interval_a_s, expire_a_s, HRMFeature_BPM); HRMSubscriberState *state = prv_get_subscriber_state_from_ref(session_ref); cl_assert(state->update_interval_s == update_interval_a_s); cl_assert(state->expire_utc == rtc_get_time() + expire_a_s); // Change update interval and expiration const uint16_t expire_b_s = 2 * SECONDS_PER_MINUTE; // TODO: PBL-37298 Support subscribing to different data rates uint32_t update_interval_b_s = 1; sys_hrm_manager_set_update_interval(session_ref, update_interval_b_s, expire_b_s); cl_assert(prv_get_subscriber_state_from_ref(session_ref) == state); cl_assert(state->update_interval_s == update_interval_b_s); cl_assert(state->expire_utc == rtc_get_time() + expire_b_s); } #define NUM_TEST_EVENTS 2 void test_hrm_manager__circular_buffer_event_copy(void) { // Make sure there will be unaligned data const uint16_t buf_size = sizeof(PebbleHRMEvent) * 2 + sizeof(PebbleHRMEvent) / 2; uint8_t buffer[buf_size]; CircularBuffer cb; circular_buffer_init(&cb, buffer, buf_size); PebbleHRMEvent event[NUM_TEST_EVENTS] = { { .event_type = HRMEvent_BPM, .bpm = { .bpm = 65, .quality = 5 } }, { .event_type = HRMEvent_LEDCurrent, .led = { .current_ua = 243 } }, }; { // These events will insert properly aligned in the buffer for (int i = 0; i < NUM_TEST_EVENTS; ++i) { circular_buffer_write(&cb, (const uint8_t *)&event[i], sizeof(PebbleHRMEvent)); } PebbleHRMEvent out_event[NUM_TEST_EVENTS]; for (int i = 0; i < NUM_TEST_EVENTS; ++i) { prv_read_event_from_buffer_and_consume(&cb, &out_event[i]); cl_assert_equal_b(memcmp(&event[i], &out_event[i], sizeof(PebbleHRMEvent)), false); } } { // Test reading back unaligned for (int i = 0; i < NUM_TEST_EVENTS; ++i) { circular_buffer_write(&cb, (const uint8_t *)&event[i], sizeof(PebbleHRMEvent)); } PebbleHRMEvent out_event[NUM_TEST_EVENTS]; for (int i = 0; i < NUM_TEST_EVENTS; ++i) { prv_read_event_from_buffer_and_consume(&cb, &out_event[i]); cl_assert_equal_b(memcmp(&event[i], &out_event[i], sizeof(PebbleHRMEvent)), false); } } } #undef NUM_TEST_EVENTS // Test the enable and disable functionality across subscriptions void test_hrm_manager__enable_disable(void) { // 1. Disabling and then enabling with no subscriber should leave the HRM off hrm_manager_enable(false); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(hrm_is_enabled(HRM), false); hrm_manager_enable(true); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(hrm_is_enabled(HRM), false); // 2. Subscribing while disabled should not enable the hrm hrm_manager_enable(false); fake_system_task_callbacks_invoke_pending(); HRMSessionRef session_ref = sys_hrm_manager_app_subscribe(1, 1, SECONDS_PER_MINUTE, HRMFeature_BPM); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(hrm_is_enabled(HRM), false); // 3. Enabling with a subscriber should turn HRM on hrm_manager_enable(true); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(hrm_is_enabled(HRM), true); // 4. Disabling with a Subscriber should disable the HRM hrm_manager_enable(false); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(hrm_is_enabled(HRM), false); sys_hrm_manager_unsubscribe(session_ref); } // Advance time the given number of milliseconds static void prv_advance_time_ms(uint32_t ms) { RtcTicks delta_ticks = milliseconds_to_ticks(ms); fake_rtc_set_ticks(rtc_get_ticks() + delta_ticks); rtc_set_time(rtc_get_time() + ms / MS_PER_SECOND); } // Test that we handle different update intervals correctly void test_hrm_manager__update_interval(void) { AppInstallId app_id = 1; uint32_t update_interval_s = 600; const uint16_t expire_s = 30 * SECONDS_PER_MINUTE; HRMFeature features = HRMFeature_BPM; HRMSessionRef session_ref = sys_hrm_manager_app_subscribe(app_id, update_interval_s, expire_s, features); fake_system_task_callbacks_invoke_pending(); // Should start out enabled before we get the first good reading cl_assert_equal_b(hrm_is_enabled(HRM), true); // Send some data while enabled int num_updates; for (num_updates = 0; num_updates < 1000 && hrm_is_enabled(HRM); num_updates++) { prv_fake_send_new_data(); fake_system_task_callbacks_invoke_pending(); } // Should be disabled relatively quickly since we don't need another reading for another 600 // seconds cl_assert(num_updates <= HRM_CHECK_SENSOR_DISABLE_COUNT); // The timer should be set to fire just before we need another update uint32_t timeout_ms = stub_new_timer_timeout(prv_get_timer_id()); cl_assert_equal_i(timeout_ms, (update_interval_s - HRM_SENSOR_SPIN_UP_SEC) * MS_PER_SECOND); // Fire the timer after the elapsed time, make sure we are re-enabled after that prv_advance_time_ms(timeout_ms); stub_new_timer_fire(prv_get_timer_id()); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(hrm_is_enabled(HRM), true); // Send the next data, should be disabled again after that for (num_updates = 0; num_updates < 1000 && hrm_is_enabled(HRM); num_updates++) { prv_fake_send_new_data(); fake_system_task_callbacks_invoke_pending(); } cl_assert(num_updates <= HRM_CHECK_SENSOR_DISABLE_COUNT); prv_advance_time_ms(1000); // Now, change the update interval to 10 seconds. That should re-enable the sensor immediately update_interval_s = 10; cl_assert(sys_hrm_manager_set_update_interval(session_ref, update_interval_s, expire_s)); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(hrm_is_enabled(HRM), true); prv_advance_time_ms(1000); // Send the next data, should still be enabled since the interval is less that the spin-up // time prv_fake_send_new_data(); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(hrm_is_enabled(HRM), true); prv_advance_time_ms(1000); // Now add a 10 minute subscription back in. AppInstallId app_id_2 = 2; sys_hrm_manager_app_subscribe(app_id_2, 600, expire_s, features); fake_system_task_callbacks_invoke_pending(); // We should stay enabled after each update because we still have the 10 second subscription // too prv_fake_send_new_data(); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(hrm_is_enabled(HRM), true); prv_advance_time_ms(1000); prv_fake_send_new_data(); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(hrm_is_enabled(HRM), true); prv_advance_time_ms(1000); // Remove the 10 second subscription - We should get disabled after the next update now sys_hrm_manager_unsubscribe(session_ref); prv_fake_send_new_data(); fake_system_task_callbacks_invoke_pending(); cl_assert_equal_b(hrm_is_enabled(HRM), false); } void test_hrm_manager__can_turn_sensor_on(void) { fake_event_set_callback(prv_charger_event_cb); cl_assert(prv_can_turn_sensor_on()); // Add a subscription so we have a reason to turn the sensor on (if the conditions are right) sys_hrm_manager_app_subscribe(1, 1, 60, HRMFeature_BPM); fake_system_task_callbacks_invoke_pending(); // Test run level changes hrm_manager_enable(false); cl_assert(!prv_can_turn_sensor_on()); fake_system_task_callbacks_invoke_pending(); cl_assert(!hrm_is_enabled(HRM)); hrm_manager_enable(true); cl_assert(prv_can_turn_sensor_on()); fake_system_task_callbacks_invoke_pending(); cl_assert(hrm_is_enabled(HRM)); // Test the pref changes s_activity_prefs_heart_rate_is_enabled = false; hrm_manager_handle_prefs_changed(); cl_assert(!prv_can_turn_sensor_on()); fake_system_task_callbacks_invoke_pending(); cl_assert(!hrm_is_enabled(HRM)); s_activity_prefs_heart_rate_is_enabled = true; hrm_manager_handle_prefs_changed(); cl_assert(prv_can_turn_sensor_on()); fake_system_task_callbacks_invoke_pending(); cl_assert(hrm_is_enabled(HRM)); // Test charging state changes prv_put_battery_state_change_event(true /* is plugged in */); cl_assert(!prv_can_turn_sensor_on()); fake_system_task_callbacks_invoke_pending(); cl_assert(!hrm_is_enabled(HRM)); prv_put_battery_state_change_event(false /* is plugged in */); cl_assert(prv_can_turn_sensor_on()); fake_system_task_callbacks_invoke_pending(); cl_assert(hrm_is_enabled(HRM)); }