mirror of
https://github.com/google/pebble.git
synced 2025-03-21 11:21:21 +00:00
747 lines
28 KiB
C
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));
|
|
}
|
|
|