pebble/tests/fw/services/test_hrm_manager.c
2025-01-27 11:38:16 -08:00

747 lines
28 KiB
C

/*
* 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 <stdio.h>
#include <services/common/hrm/hrm_manager.h>
// -----------------------------------------------------------------------------
// 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));
}