/*---------------------------------------------------------------------------- * Copyright (c) TJD Technologies Co., Ltd. 2020. All rights reserved. * * Description: service_hrsensor.c * * Author: liangjianfei * * Create: 2024-4-26 *--------------------------------------------------------------------------*/ #include "service_hrsensor.h" #include "cmsis_os2.h" #include "common_def.h" #include "hr_api.h" #include "pm.h" #include "rtc_api.h" #include "securec.h" #include "soc_osal.h" #include "sql_fit.h" #include "sys_config.h" #include "sys_typedef.h" #include "task_service_timer.h" #include #include #include #include "cwm_port.h" #define ENABLE_PRINT_INFO 1 #define ENABLE_DEBUG 0 #if ENABLE_PRINT_INFO #define static_print_info(...) sys_hr_log_i(__VA_ARGS__) // 一般信息打印宏控制 #define static_print_warn(...) sys_hr_log_w(__VA_ARGS__) // 警告信息打印一般常开 #define static_print_error(...) sys_hr_log_e(__VA_ARGS__) // 错误信息打印一般常开 #if ENABLE_DEBUG #define static_print_debug(...) sys_hr_log_d(__VA_ARGS__) // debug #else #define static_print_debug(...) #endif #else #define static_print_info(...) #define static_print_warn(...) #define static_print_error(...) #endif #define LOOP_PERIOD_TIME 1000 * 30 #define HRV_LOOP_PERIOD_TIME 1000 * 60 static const uint16_t HR_INTERVAL_MIN = 5; static const uint16_t HRV_INTERVAL_MIN = 60; static const uint16_t SPO2_INTERVAL_MIN = 60; hrsensor_data_t g_hr_data = {0}; static uint8_t g_ui_cur_hrvalue = 0; static uint8_t g_ui_cur_spo2value = 0; static uint8_t g_ui_cur_hrvvalue = 0; static osTimerId_t g_hr_servise_timer; bool g_hr_is_running = false; bool g_hr_service_inited = false; hr_service_id_t g_hr_service_id = 0; static int32_t tjd_service_hrsensor_open(hr_api_info_t hr_api_info); static int32_t tjd_service_hrsensor_close(void); static void tjd_protocol_measure_data_send(const uint8_t id); static void hr_service_timer_callback(void *argument) { uint8_t *p_hr_array; uint8_t *p_spo2_array; uint8_t *p_hrv_array; sql_fit_get_hr_daydata(&p_hr_array); sql_fit_get_spo2_daydata(&p_spo2_array); sql_fit_get_sterss_daydata(&p_hrv_array); memset(p_hr_array, 0, sizeof(uint8_t) * HR_DAY_MAX_NUM); memset(p_spo2_array, 0, sizeof(uint8_t) * SPO2_DAY_MAX_NUM); memset(p_hrv_array, 0, sizeof(uint8_t) * STRESS_DAY_MAX_NUM); struct rtc_time local_time; tjd_driver_rtc_get_ops()->get_rtc_time(&local_time); if (local_time.tm_hour == 0) sql_fit_clear_health_day_data(local_time.tm_wday); else if (local_time.tm_hour == 23) sql_fit_clear_health_day_data(local_time.tm_wday+1); uint32_t next_sec = ((24-local_time.tm_hour)*3600) - (local_time.tm_min*60) - local_time.tm_sec; osStatus_t ret = osTimerStart(g_hr_servise_timer, next_sec*1000); if (ret != osOK) { static_print_error("hr_service_timer_callback osTimerStart failed:%d", ret); return; } static_print_info("hr_service_timer_callback succ!"); } static uint32_t hr_get_curtime_index(uint64_t *timestamp) { struct rtc_time local_time; struct rtc_class_ops *handle = tjd_driver_rtc_get_ops(); handle->get_rtc_time(&local_time); uint32_t totalMinutes = local_time.tm_hour * 60 + local_time.tm_min; // 计算索引值(每5分钟一个索引) uint32_t index = totalMinutes / 5; handle->get_timestamp(timestamp); return index; } static uint32_t spo2_hrv_get_curtime_index(uint64_t *timestamp) { struct rtc_time local_time; struct rtc_class_ops *handle = tjd_driver_rtc_get_ops(); handle->get_rtc_time(&local_time); // 计算索引值(每60分钟一个索引) uint32_t index = local_time.tm_hour; handle->get_timestamp(timestamp); return index; } static int32_t tjd_task_service_hrsensor_store_data(hr_service_id_t id) { uint8_t hrValue = 0; uint8_t spo2Value = 0; uint8_t hrvValue = 0; uint64_t timestamp = 0; static uint8_t stop_count = 0; struct rtc_time local_time; tjd_driver_rtc_get_ops()->get_rtc_time(&local_time); uint8_t wearStatus = g_hr_data.wear_status; static_print_debug("wearStatus:%d hr value:%d spo2:%d%% stress:%d%%", wearStatus, g_hr_data.hr_result, g_hr_data.spo2_result, g_hr_data.hrv_result); if (wearStatus == 1) { //佩戴状态,需要连续发正常范围内的数据给算法。 cwm_set_device_status(wearStatus); if(g_hr_data.hr_result >30 && g_hr_data.hr_result < 200) { cwm_set_hr_value(g_hr_data.hr_result); } if (id != HR_SERVICE_BO_ALLDAY && id != HR_SERVICE_UI_BO && id != HR_SERVICE_STRESS_ALLDAY) { hrValue = g_hr_data.hr_result; hrValue = hrValue < 40 || hrValue > 200 ? (hrValue < 40 ? 0 : 200) : hrValue; if (hrValue > sql_fit_get_hr_max(local_time.tm_wday)) { sql_fit_set_hr_max(hrValue, local_time.tm_wday); } if (hrValue != 0 && hrValue < sql_fit_get_hr_min(local_time.tm_wday)) { sql_fit_set_hr_min(hrValue, local_time.tm_wday); } uint32_t index = hr_get_curtime_index(×tamp); if (hrValue >= 40 && hrValue <= 200) { sql_fit_set_hr_curdata(index, hrValue); sql_fit_set_last_hr(hrValue, local_time.tm_wday); sql_fit_set_last_hr_timestamp(timestamp, local_time.tm_wday); } } else if (id == HR_SERVICE_BO_ALLDAY || id == HR_SERVICE_UI_BO) { spo2Value = g_hr_data.spo2_result; hrValue = g_hr_data.hr_result; spo2Value = spo2Value < 70 || spo2Value > 99 ? (spo2Value < 70 ? 0 : 99) : spo2Value; hrValue = hrValue < 40 || hrValue > 200 ? (hrValue < 40 ? 0 : 200) : hrValue; // 存心率 if (hrValue > sql_fit_get_hr_max(local_time.tm_wday)) { sql_fit_set_hr_max(hrValue, local_time.tm_wday); } if (hrValue != 0 && hrValue < sql_fit_get_hr_min(local_time.tm_wday)) { sql_fit_set_hr_min(hrValue, local_time.tm_wday); } uint32_t index = hr_get_curtime_index(×tamp); if (hrValue >= 40 && hrValue <= 200) { sql_fit_set_hr_curdata(index, hrValue); sql_fit_set_last_hr(hrValue, local_time.tm_wday); sql_fit_set_last_hr_timestamp(timestamp, local_time.tm_wday); } // 存血氧 if (spo2Value > sql_fit_get_spo2_max(local_time.tm_wday)) { sql_fit_set_spo2_max(spo2Value, local_time.tm_wday); } if (spo2Value != 0 && spo2Value < sql_fit_get_spo2_min(local_time.tm_wday)) { sql_fit_set_spo2_min(spo2Value, local_time.tm_wday); } if (spo2Value >= 70 && spo2Value <= 100) { uint32_t index = spo2_hrv_get_curtime_index(×tamp); sql_fit_set_spo2_curdata(index, spo2Value); sql_fit_set_last_spo2(spo2Value, local_time.tm_wday); // sql_fit_set_last_spo2_timestamp(timestamp); } } else if (id == HR_SERVICE_STRESS_ALLDAY) { hrvValue = g_hr_data.hrv_result; hrvValue = hrvValue < 0? 0:hrvValue; if (hrvValue > sql_fit_get_stress_max(local_time.tm_wday)) { sql_fit_set_stress_max(hrvValue, local_time.tm_wday); } if (hrvValue != 0 && hrvValue < sql_fit_get_stress_min(local_time.tm_wday)) { sql_fit_set_stress_min(hrvValue, local_time.tm_wday); } if (hrvValue > 0 && hrvValue < 100) { uint32_t index = spo2_hrv_get_curtime_index(×tamp); sql_fit_set_stress_curdata(index, hrvValue); sql_fit_set_last_stress(hrvValue, local_time.tm_wday); } } } else { if (id == HR_SERVICE_HR_ALLDAY || id == HR_SERVICE_BO_ALLDAY || id == HR_SERVICE_STRESS_ALLDAY) { stop_count++; if (stop_count == 3) { stop_count = 0; return -1; } } } g_ui_cur_hrvalue = hrValue; g_ui_cur_spo2value = spo2Value; g_ui_cur_hrvvalue = hrvValue; return 0; } static signed int service_hrsensor_handle(void *param) { bool need_close = true; hr_service_info_t *hr_service_info = (hr_service_info_t *)param; if (hr_service_info->time_ms == 0xffffffff) { if (g_hr_is_running) { tjd_task_service_hrsensor_store_data(hr_service_info->id); need_close = false; } } else if (hr_service_info->time_ms >= 1000) { hr_service_info->time_ms -= 1000; need_close = false; // 还有需要运行的心率的模块 if (tjd_task_service_hrsensor_store_data(hr_service_info->id) == -1) { need_close = true; } } else { hr_service_info->time_ms = 0; hr_service_info->status = HRS_STATUS_OFF; need_close = true; } if (need_close) { g_ui_cur_hrvalue = 0; g_ui_cur_spo2value = 0; g_ui_cur_hrvvalue = 0; tjd_service_hrsensor_close(); tjd_protocol_measure_data_send(hr_service_info->id); return 0; } return 1000; } static signed int tjd_service_timer_hrsensor_start_send(void *param) { static_print_info("tjd_service_timer_hrsensor_start_send"); static hr_service_info_t hr_service_info = {0}; hr_service_info_t temp_hr_service_info = {0}; temp_hr_service_info = *(hr_service_info_t *)param; if (temp_hr_service_info.id >= HR_SERVICE_MAX) return HRS_STATUS_ERROR; if (g_hr_is_running == false) { g_hr_is_running = true; } else { if (hr_service_info.time_ms != 0) { tjd_service_hrsensor_close(); g_hr_is_running = true; } } if (temp_hr_service_info.id == HR_SERVICE_BO_ALLDAY || temp_hr_service_info.id == HR_SERVICE_UI_BO) tjd_driver_hr_api_get_ops()->mode_set(HR3695_SPO2_MODE); else if (temp_hr_service_info.id == HR_SERVICE_STRESS_ALLDAY) tjd_driver_hr_api_get_ops()->mode_set(HR3695_HRV_MODE); else tjd_driver_hr_api_get_ops()->mode_set(HR3695_HRS_MODE); hr_service_info = temp_hr_service_info; queue_default_info_t msg_data = {service_hrsensor_handle, &hr_service_info, 1000, NULL}; int ret = osal_msg_queue_write_copy(tjd_task_service_timer_get_queue_id(), (void *)&msg_data, sizeof(queue_default_info_t), 0); static_print_info("service_timer_hrsensor_start succ."); return HRS_STATUS_ON; } static signed int tjd_service_timer_hrsensor_end_send(void *param) { static_print_info("tjd_service_timer_hrsensor_end_send"); queue_default_info_t msg_data = {service_hrsensor_handle, NULL, 0, NULL}; int ret = osal_msg_queue_write_copy(tjd_task_service_timer_get_queue_id(), (void *)&msg_data, sizeof(queue_default_info_t), 0); if (ret != OSAL_SUCCESS) { static_print_error("[%s] osal_msg_queue_write_copy fail", __func__); return ret; } g_hr_is_running = false; static_print_info("service_timer_hrsensor_end succ."); return HRS_STATUS_OFF; } // 整点每5分钟测量 static signed int tjd_task_service_timer_hrsensor_hr_all_day_handle(void *param) { static bool exit_flag = true; struct rtc_time local_time; tjd_driver_rtc_get_ops()->get_rtc_time(&local_time); // 整点不测量(和整点血氧一起出值) if ((local_time.tm_min == 0 && exit_flag)||(tjd_service_charger_get_charger_status())) { uint32_t trigger_sec = local_time.tm_min % HR_INTERVAL_MIN == 0 ? (HR_INTERVAL_MIN * 60 - local_time.tm_sec) : ((HR_INTERVAL_MIN * 60) - (local_time.tm_min % HR_INTERVAL_MIN * 60 - local_time.tm_sec)); return trigger_sec * 1000; } if (g_hr_is_running) { exit_flag = false; return 120 * 1000; // 推迟120s再运行 } //更新时间给算法。 struct algo_datetime date; date.year = local_time.tm_year; date.mon = local_time.tm_mon; date.day = local_time.tm_mday; date.hour = local_time.tm_hour; date.min = local_time.tm_min; date.sec = local_time.tm_sec; cwm_set_datetime_to_algo(&date, sizeof(struct algo_datetime)); static_print_debug("tjd_task_service_timer_hrsensor_hr_all_day_handle"); hr_service_info_t hr_service_info = {HR_SERVICE_HR_ALLDAY, LOOP_PERIOD_TIME, HRS_STATUS_ON}; tjd_service_timer_hrsensor_start_send((void *)&hr_service_info); exit_flag = true; uint32_t trigger_sec = local_time.tm_min % HR_INTERVAL_MIN == 0 ? (HR_INTERVAL_MIN * 60 - local_time.tm_sec) : ((HR_INTERVAL_MIN * 60) - (local_time.tm_min % HR_INTERVAL_MIN * 60 - local_time.tm_sec)); return trigger_sec * 1000; } // 整点每60分钟测量 static signed int tjd_task_service_timer_hrsensor_spo2_all_day_handle(void *param) { if (g_hr_is_running) { return 120 * 1000; // 推迟120s再运行 } struct rtc_time local_time; tjd_driver_rtc_get_ops()->get_rtc_time(&local_time); uint32_t trigger_sec = local_time.tm_min % SPO2_INTERVAL_MIN == 0 ? (SPO2_INTERVAL_MIN * 60 - local_time.tm_sec) : ((SPO2_INTERVAL_MIN * 60)-(local_time.tm_min % SPO2_INTERVAL_MIN * 60 - local_time.tm_sec)); if (tjd_service_charger_get_charger_status()) { return trigger_sec * 1000; } static_print_debug("tjd_task_service_timer_hrsensor_spo2_all_day_handle"); hr_service_info_t hr_service_info = {HR_SERVICE_BO_ALLDAY, LOOP_PERIOD_TIME, HRS_STATUS_ON}; tjd_service_timer_hrsensor_start_send((void *)&hr_service_info); return trigger_sec * 1000; } // 整点每60分钟测量 static signed int tjd_task_service_timer_hrsensor_hrv_all_day_handle(void *param) { if (g_hr_is_running) { return 120 * 1000; // 推迟120s再运行 } struct rtc_time local_time; tjd_driver_rtc_get_ops()->get_rtc_time(&local_time); uint32_t trigger_sec = local_time.tm_min % HRV_INTERVAL_MIN == 0 ? (HRV_INTERVAL_MIN * 60 - local_time.tm_sec) : ((HRV_INTERVAL_MIN * 60)-(local_time.tm_min % HRV_INTERVAL_MIN * 60 - local_time.tm_sec)); if (tjd_service_charger_get_charger_status()) { return trigger_sec * 1000; } hr_service_info_t hr_service_info = {HR_SERVICE_STRESS_ALLDAY, HRV_LOOP_PERIOD_TIME, HRS_STATUS_ON}; tjd_service_timer_hrsensor_start_send((void *)&hr_service_info); return trigger_sec * 1000; } static int32_t tjd_service_hrsensor_hr_allday_open(void) { if (g_hr_service_inited == false) { static_print_error("tjd_service_hrsensor_hr_allday_open fail. g_hr_service_inited is false"); return HRS_STATUS_ERROR; } struct rtc_time local_time; tjd_driver_rtc_get_ops()->get_rtc_time(&local_time); uint32_t trigger_sec = local_time.tm_min % HR_INTERVAL_MIN == 0 ? (HR_INTERVAL_MIN * 60 - local_time.tm_sec) : ((HR_INTERVAL_MIN * 60)-(local_time.tm_min % HR_INTERVAL_MIN * 60 - local_time.tm_sec)); static_print_debug("tjd_service_hrsensor_hr_allday_open trigger_sec:%d", trigger_sec); queue_default_info_t msg_data = {tjd_task_service_timer_hrsensor_hr_all_day_handle, NULL, trigger_sec * 1000, NULL}; int ret = osal_msg_queue_write_copy(tjd_task_service_timer_get_queue_id(), (void *)&msg_data, sizeof(queue_default_info_t), 0); static_print_info("service_hrsensor_spo2_allday_open succ!"); return 0; } static int32_t tjd_service_hrsensor_hr_allday_close(void) { if (g_hr_service_inited == false) { static_print_error("tjd_service_hrsensor_hr_allday_close fail. g_hr_service_inited is false"); return HRS_STATUS_ERROR; } queue_default_info_t msg_data = {tjd_task_service_timer_hrsensor_hr_all_day_handle, NULL, 0, NULL}; int ret = osal_msg_queue_write_copy(tjd_task_service_timer_get_queue_id(), (void *)&msg_data, sizeof(queue_default_info_t), 0); static_print_info("service_hrsensor_spo2_allday_close succ!"); return 0; } static int32_t service_hrsensor_spo2_allday_open(void) { if (g_hr_service_inited == false) { static_print_error("service_hrsensor_spo2_allday_open fail. g_hr_service_inited is false"); return HRS_STATUS_ERROR; } struct rtc_time local_time; tjd_driver_rtc_get_ops()->get_rtc_time(&local_time); uint32_t trigger_sec = local_time.tm_min % SPO2_INTERVAL_MIN == 0 ? (SPO2_INTERVAL_MIN * 60 - local_time.tm_sec) : ((SPO2_INTERVAL_MIN * 60)-(local_time.tm_min % SPO2_INTERVAL_MIN * 60 - local_time.tm_sec)); queue_default_info_t msg_data = {tjd_task_service_timer_hrsensor_spo2_all_day_handle, NULL, trigger_sec * 1000, NULL}; int ret = osal_msg_queue_write_copy(tjd_task_service_timer_get_queue_id(), (void *)&msg_data, sizeof(queue_default_info_t), 0); static_print_info("service_hrsensor_spo2_allday_open succ!"); return 0; } static int32_t service_hrsensor_spo2_allday_close(void) { if (g_hr_service_inited == false) { static_print_error("service_hrsensor_spo2_allday_close fail. g_hr_service_inited is false"); return HRS_STATUS_ERROR; } queue_default_info_t msg_data = {tjd_task_service_timer_hrsensor_spo2_all_day_handle, NULL, 0, NULL}; int ret = osal_msg_queue_write_copy(tjd_task_service_timer_get_queue_id(), (void *)&msg_data, sizeof(queue_default_info_t), 0); static_print_info("service_hrsensor_spo2_allday_close succ!"); return 0; } static int32_t service_hrsensor_hrv_allday_open(void) { if (g_hr_service_inited == false) { static_print_error("service_hrsensor_hrv_allday_open fail. g_hr_service_inited is false"); return HRS_STATUS_ERROR; } struct rtc_time local_time; tjd_driver_rtc_get_ops()->get_rtc_time(&local_time); uint32_t trigger_sec = local_time.tm_min % HRV_INTERVAL_MIN == 0 ? (HRV_INTERVAL_MIN * 60 - local_time.tm_sec) : ((HRV_INTERVAL_MIN * 60)-(local_time.tm_min % HRV_INTERVAL_MIN * 60 - local_time.tm_sec)); static_print_debug("service_hrsensor_hrv_allday_open trigger_sec:%d", trigger_sec); queue_default_info_t msg_data = {tjd_task_service_timer_hrsensor_hrv_all_day_handle, NULL, trigger_sec * 1000, NULL}; int ret = osal_msg_queue_write_copy(tjd_task_service_timer_get_queue_id(), (void *)&msg_data, sizeof(queue_default_info_t), 0); static_print_info("service_hrsensor_hrv_allday_open succ!"); return 0; } static int32_t service_hrsensor_hrv_allday_close(void) { if (g_hr_service_inited == false) { static_print_error("service_hrsensor_hrv_allday_close fail. g_hr_service_inited is false"); return HRS_STATUS_ERROR; } queue_default_info_t msg_data = {tjd_task_service_timer_hrsensor_hrv_all_day_handle, NULL, 0, NULL}; int ret = osal_msg_queue_write_copy(tjd_task_service_timer_get_queue_id(), (void *)&msg_data, sizeof(queue_default_info_t), 0); static_print_info("service_hrsensor_hrv_allday_close succ!"); return 0; } static int32_t tjd_service_hrsensor_open(hr_api_info_t hr_api_info) { if (g_hr_service_inited == false) { static_print_error("tjd_service_hrsensor_open fail. g_hr_service_inited is false"); return HRS_STATUS_ERROR; } g_hr_service_id = hr_api_info.id; hr_service_info_t hr_service_info = {hr_api_info.id, hr_api_info.time_ms, HRS_STATUS_ON}; tjd_service_timer_hrsensor_start_send((void *)&hr_service_info); return HRS_STATUS_ON; } static int32_t tjd_service_hrsensor_close(void) { if (g_hr_service_inited == false) { static_print_error("tjd_service_hrsensor_close fail. g_hr_service_inited is false"); return HRS_STATUS_ERROR; } //未佩戴,发送一次状态 if(!g_hr_data.wear_status) { cwm_set_device_status(g_hr_data.wear_status); cwm_set_hr_value(0); } tjd_service_timer_hrsensor_end_send(NULL); tjd_driver_hr_api_get_ops()->mode_set(HR3695_CLOSE); return HRS_STATUS_OFF; } uint8_t tjd_service_hrsensor_get_cur_hrvalue(void) { return g_ui_cur_hrvalue; } uint8_t tjd_service_hrsensor_get_cur_spo2value(void) { return g_ui_cur_spo2value; } uint8_t tjd_service_hrsensor_get_cur_stressvalue(void) { return g_ui_cur_hrvvalue; } static void tjd_service_hrsensor_init(void) { if (g_hr_service_inited == true) { static_print_error("hr_service has been opened"); return; } g_hr_servise_timer = osTimerNew(hr_service_timer_callback, osTimerOnce, NULL, NULL); if (!g_hr_servise_timer) { static_print_error("g_hr_servise_timer osTimerNew failed"); return; } struct rtc_time localTime; tjd_driver_rtc_get_ops()->get_rtc_time(&localTime); uint32_t next_sec = ((24-localTime.tm_hour)*3600) - (localTime.tm_min*60) - localTime.tm_sec; osStatus_t ret = osTimerStart(g_hr_servise_timer, next_sec*1000); if (ret != osOK) { static_print_error("tjd_service_hrsensor_init osTimerStart failed:%d", ret); return; } g_hr_service_inited = true; static_print_info("hr_service open suceess"); } static void tjd_protocol_measure_data_send(const uint8_t id) { uint32_t index = 0; uint64_t timestamp = 0; static send_measure_data_t send_data = {0}; struct rtc_time local_time; uint8_t stress_min = 0, stress_max = 0; uint8_t *heartrate_array = NULL; uint8_t *spo2_array = NULL; uint8_t *stress_array = NULL; tjd_driver_rtc_get_ops()->get_rtc_time(&local_time); if(id ==HR_SERVICE_BO_ALLDAY || id == HR_SERVICE_UI_BO){ index = spo2_hrv_get_curtime_index(×tamp); sql_fit_get_spo2_daydata(&spo2_array); send_data.mode = MeasureMode_Spo2; send_data.vlue = spo2_array[index]; }else if(id == HR_SERVICE_STRESS_ALLDAY){ index = spo2_hrv_get_curtime_index(×tamp); stress_max = sql_fit_get_stress_max(local_time.tm_wday); stress_min = sql_fit_get_stress_min(local_time.tm_wday); sql_fit_get_sterss_daydata(&stress_array); send_data.mode = MeasureMode_Blood; send_data.vlue = ((stress_max<<8)&0xFF00) | ((stress_min)&0xFF); }else{ index = hr_get_curtime_index(×tamp); sql_fit_get_hr_daydata(&heartrate_array); send_data.mode = MeasureMode_Heart; send_data.vlue = heartrate_array[index]; } tjd_service_send_current_measurement_data(&send_data); } static void tjd_service_hrsensor_deinit(void) { g_hr_service_inited = false; } static hr_wear_status_t tjd_service_hrsensor_get_wear_status(void) { return g_hr_data.wear_status; } static hr_service_id_t tjd_service_hrsensor_get_service_id(void) { return g_hr_service_id; } static service_hrs_api g_service_hrs_info = { .init = tjd_service_hrsensor_init, .deinit = tjd_service_hrsensor_deinit, .open = tjd_service_hrsensor_open, .close = tjd_service_hrsensor_close, .hr_allday_open = tjd_service_hrsensor_hr_allday_open, .hr_allday_close = tjd_service_hrsensor_hr_allday_close, .spo2_allday_open = service_hrsensor_spo2_allday_open, .spo2_allday_close = service_hrsensor_spo2_allday_close, .hrv_allday_open = service_hrsensor_hrv_allday_open, .hrv_allday_close = service_hrsensor_hrv_allday_close, .hrs_wear_status = tjd_service_hrsensor_get_wear_status, .hrs_get_service_id = tjd_service_hrsensor_get_service_id, }; service_hrs_api *tjd_service_hrs_get_ops(void) { return &g_service_hrs_info; }