1 /**
2 *******************************************************************************
3 *
4 * @file hrs.c
5 *
6 * @brief Heart Rate Profile Sensor implementation.
7 *
8 *******************************************************************************
9 * @attention
10 #####Copyright (c) 2019 GOODIX
11 All rights reserved.
12
13 Redistribution and use in source and binary forms, with or without
14 modification, are permitted provided that the following conditions are met:
15 * Redistributions of source code must retain the above copyright
16 notice, this list of conditions and the following disclaimer.
17 * Redistributions in binary form must reproduce the above copyright
18 notice, this list of conditions and the following disclaimer in the
19 documentation and/or other materials provided with the distribution.
20 * Neither the name of GOODIX nor the names of its contributors may be used
21 to endorse or promote products derived from this software without
22 specific prior written permission.
23
24 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 POSSIBILITY OF SUCH DAMAGE.
35 *****************************************************************************************
36 */
37
38 /*
39 * INCLUDE FILES
40 *******************************************************************************
41 */
42 #include "hrs.h"
43 #include "ble_prf_types.h"
44 #include "ble_prf_utils.h"
45 #include "utility.h"
46
47 #define DIV_2 2
48
49 /*
50 * DEFINES
51 *******************************************************************************
52 */
53 /** Value for control point characteristic. */
54 #define HRS_CTRL_POINT_ENERGY_EXP 0x01
55
56 /*
57 * ENUMERATIONS
58 *******************************************************************************
59 */
60 /**@brief Heart Rate Service Attributes Indexes. */
61 enum hrs_attr_idx_t {
62 HRS_IDX_SVC,
63 HRS_IDX_HR_MEAS_CHAR,
64 HRS_IDX_HR_MEAS_VAL,
65 HRS_IDX_HR_MEAS_NTF_CFG,
66 HRS_IDX_BODY_SENSOR_LOC_CHAR,
67 HRS_IDX_BODY_SENSOR_LOC_VAL,
68 HRS_IDX_HR_CTNL_PT_CHAR,
69 HRS_IDX_HR_CTNL_PT_VAL,
70 HRS_IDX_NB,
71 };
72
73 /**@brief Heart Rate Service Measurement flag bit. */
74 enum hrs_flag_bit_t {
75 HRS_BIT_RATE_FORMAT = 0x01, /**< Heart Rate Value Format bit. */
76 HRS_BIT_SENSOR_CONTACT_DETECTED = 0x02, /**< Sensor Contact Detected bit. */
77 HRS_BIT_SENSOR_CONTACT_SUPPORTED = 0x04, /**< Sensor Contact Supported bit. */
78 HRS_BIT_ENERGY_EXPENDED_STATUS = 0x08, /**< Energy Expended Status bit. */
79 HRS_BIT_INTERVAL = 0x10, /**< RR-Interval bit. */
80 };
81
82 /*
83 * STRUCT DEFINE
84 *******************************************************************************
85 */
86 /**@brief Heart Rate Measurement characteristic value structure. */
87 struct hr_meas_char_val_t {
88 bool is_sensor_contact_detected; /**< True if sensor contact has been detected. */
89 uint8_t hr_meas_value[HRS_MEAS_MAX_LEN]; /**< The buffer for encoded value of Heart Rate \
90 Measurement characteristic. */
91 uint16_t energy_expended; /**< The accumulated energy expended in kilo Joules since the last time it was reset. */
92 uint16_t rr_interval[HRS_MAX_BUFFERED_RR_INTERVALS]; /**< The buffer for RR Interval measurements. */
93 uint8_t rr_interval_count; /**< The number of RR Interval measurements in the buffer. */
94 };
95
96 /**@brief Heart Rate Service environment variable. */
97 struct hrs_env_t {
98 hrs_init_t hrs_init; /**< Heart Rate Service Init Value. */
99 struct hr_meas_char_val_t hr_meas; /**< The value of Heart Rate Measurement characteristic. */
100 uint16_t start_hdl; /**< Heart Rate Service start handle. */
101 uint16_t
102 ntf_cfg[HRS_CONNECTION_MAX];/**< The configuration of Heart Rate Measurement Notification \
103 which is configured by the peer devices. */
104 };
105
106 /*
107 * LOCAL FUNCTION DECLARATION
108 *******************************************************************************
109 */
110 static sdk_err_t hrs_init(void);
111 static void hrs_read_att_cb(uint8_t conn_idx, const gatts_read_req_cb_t *p_param);
112 static void hrs_write_att_cb(uint8_t conn_idx, const gatts_write_req_cb_t *p_param);
113 static void hrs_cccd_set_cb(uint8_t conn_idx, uint16_t handle, uint16_t cccd_value);
114
115 /*
116 * LOCAL VARIABLE DEFINITIONS
117 *******************************************************************************
118 */
119 static struct hrs_env_t s_hrs_env;
120
121 /**@brief Full HRS Database Description which is used to add attributes into the ATT database. */
122 static const attm_desc_t hrs_attr_tab[HRS_IDX_NB] = {
123 // Heart Rate Service Declaration
124 [HRS_IDX_SVC] = {BLE_ATT_DECL_PRIMARY_SERVICE, READ_PERM_UNSEC, 0, 0},
125
126 // HR Measurement Characteristic - Declaration
127 [HRS_IDX_HR_MEAS_CHAR] = {BLE_ATT_DECL_CHARACTERISTIC, READ_PERM_UNSEC, 0, 0},
128 // HR Measurement Characteristic - Value
129 [HRS_IDX_HR_MEAS_VAL] = {BLE_ATT_CHAR_HEART_RATE_MEAS, NOTIFY_PERM_UNSEC, ATT_VAL_LOC_USER, HRS_MEAS_MAX_LEN},
130 // HR Measurement Characteristic - Client Characteristic Configuration Descriptor
131 [HRS_IDX_HR_MEAS_NTF_CFG] = {BLE_ATT_DESC_CLIENT_CHAR_CFG, READ_PERM_UNSEC | WRITE_REQ_PERM_UNSEC, 0, 0},
132
133 // Body Sensor Location Characteristic - Declaration
134 [HRS_IDX_BODY_SENSOR_LOC_CHAR] = {BLE_ATT_DECL_CHARACTERISTIC, READ_PERM_UNSEC, 0, 0},
135 // Body Sensor Location Characteristic - Value
136 [HRS_IDX_BODY_SENSOR_LOC_VAL] = {BLE_ATT_CHAR_BODY_SENSOR_LOCATION, READ_PERM_UNSEC,
137 ATT_VAL_LOC_USER, sizeof(uint8_t)},
138
139 // Heart Rate Control Point Characteristic - Declaration
140 [HRS_IDX_HR_CTNL_PT_CHAR] = {BLE_ATT_DECL_CHARACTERISTIC, READ_PERM_UNSEC, 0, 0},
141 // Heart Rate Control Point Characteristic - Value
142 [HRS_IDX_HR_CTNL_PT_VAL] = {BLE_ATT_CHAR_HEART_RATE_CNTL_POINT, WRITE_REQ_PERM_UNSEC, 0, sizeof(uint8_t)},
143 };
144
145 /**@brief HRS interface required by profile manager. */
146 static ble_prf_manager_cbs_t hrs_mgr_cbs = {
147 (prf_init_func_t)hrs_init,
148 NULL,
149 NULL
150 };
151
152 /**@brief HRS GATT server Callbacks. */
153 static gatts_prf_cbs_t hrs_gatts_cbs = {
154 hrs_read_att_cb,
155 hrs_write_att_cb,
156 NULL,
157 NULL,
158 hrs_cccd_set_cb
159 };
160
161 /**@brief HRS Information. */
162 static const prf_server_info_t hrs_prf_info = {
163 .max_connection_nb = HRS_CONNECTION_MAX,
164 .manager_cbs = &hrs_mgr_cbs,
165 .gatts_prf_cbs = &hrs_gatts_cbs
166 };
167
168 /*
169 * LOCAL FUNCTION DEFINITIONS
170 *******************************************************************************
171 */
172 /**
173 *****************************************************************************************
174 * @brief Initialize heart rate service, and create DB in ATT.
175 *
176 * @return status code to know if service initialization succeed or not.
177 *****************************************************************************************
178 */
hrs_init(void)179 static sdk_err_t hrs_init(void)
180 {
181 const uint8_t hrs_svc_uuid[] = BLE_ATT_16_TO_16_ARRAY(BLE_ATT_SVC_HEART_RATE);
182 gatts_create_db_t gatts_db;
183 sdk_err_t ret;
184 uint16_t start_hdl = PRF_INVALID_HANDLE; /* The start hanlde is an in/out
185 * parameter of ble_gatts_srvc_db_create()
186 * It must be set with PRF_INVALID_HANDLE
187 * to be allocated automatically by BLE Stack. */
188
189 ret = memset_s(&gatts_db, sizeof(gatts_db), 0, sizeof(gatts_db));
190 if (ret < 0) {
191 return ret;
192 }
193
194 gatts_db.shdl = &start_hdl;
195 gatts_db.uuid = hrs_svc_uuid;
196 gatts_db.attr_tab_cfg = (uint8_t *)&s_hrs_env.hrs_init.char_mask;
197 gatts_db.max_nb_attr = HRS_IDX_NB;
198 gatts_db.srvc_perm = 0;
199 gatts_db.attr_tab_type = SERVICE_TABLE_TYPE_16;
200 gatts_db.attr_tab.attr_tab_16 = hrs_attr_tab;
201
202 sdk_err_t status = ble_gatts_srvc_db_create(&gatts_db);
203 if (SDK_SUCCESS == status) {
204 s_hrs_env.start_hdl = *gatts_db.shdl;
205 }
206
207 return status;
208 }
209
210 /**
211 *****************************************************************************************
212 * @brief Handles reception of the attribute info request message.
213 *
214 * @param[in] conn_idx: Connection index.
215 * @param[in] p_param: Pointer to the parameters of the read request.
216 *****************************************************************************************
217 */
hrs_read_att_cb(uint8_t conn_idx,const gatts_read_req_cb_t * p_param)218 static void hrs_read_att_cb(uint8_t conn_idx, const gatts_read_req_cb_t *p_param)
219 {
220 uint8_t handle = p_param->handle;
221 uint8_t tab_index = prf_find_idx_by_handle(handle, s_hrs_env.start_hdl,
222 HRS_IDX_NB,
223 (uint8_t *)&s_hrs_env.hrs_init.char_mask);
224
225 gatts_read_cfm_t cfm;
226 hrs_evt_t evt;
227
228 cfm.handle = handle;
229 cfm.status = BLE_SUCCESS;
230
231 switch (tab_index) {
232 case HRS_IDX_HR_MEAS_VAL:
233 cfm.length = HRS_MEAS_MAX_LEN;
234 cfm.value = s_hrs_env.hr_meas.hr_meas_value;
235 break;
236
237 case HRS_IDX_HR_MEAS_NTF_CFG:
238 cfm.length = sizeof(uint16_t);
239 cfm.value = (uint8_t *)(&(s_hrs_env.ntf_cfg[conn_idx]));
240 break;
241
242 case HRS_IDX_BODY_SENSOR_LOC_VAL:
243 if (s_hrs_env.hrs_init.evt_handler) {
244 evt.conn_idx = conn_idx;
245 evt.evt_type = HRS_EVT_READ_BODY_SEN_LOCATION;
246 s_hrs_env.hrs_init.evt_handler(&evt);
247 }
248 cfm.length = sizeof(uint8_t);
249 cfm.value = (uint8_t *)(&s_hrs_env.hrs_init.sensor_loc);
250 break;
251
252 default:
253 cfm.length = 0;
254 cfm.status = BLE_ATT_ERR_INVALID_HANDLE;
255 break;
256 }
257
258 ble_gatts_read_cfm(conn_idx, &cfm);
259 }
260
261 /**
262 *****************************************************************************************
263 * @brief Handles reception of the write request.
264 *
265 * @param[in] conn_idx: Connection index.
266 * @param[in] p_param: Pointer to the parameters of the write request.
267 *****************************************************************************************
268 */
hrs_write_att_cb(uint8_t conn_idx,const gatts_write_req_cb_t * p_param)269 static void hrs_write_att_cb(uint8_t conn_idx, const gatts_write_req_cb_t *p_param)
270 {
271 uint16_t handle = p_param->handle;
272 uint8_t tab_index = prf_find_idx_by_handle(handle,
273 s_hrs_env.start_hdl,
274 HRS_IDX_NB,
275 (uint8_t *)&s_hrs_env.hrs_init.char_mask);
276 uint16_t cccd_value;
277 gatts_write_cfm_t cfm;
278 hrs_evt_t evt;
279
280 cfm.handle = handle;
281
282 switch (tab_index) {
283 case HRS_IDX_HR_MEAS_NTF_CFG:
284 cccd_value = le16toh(&p_param->value[0]);
285 s_hrs_env.ntf_cfg[conn_idx] = cccd_value;
286
287 if (s_hrs_env.hrs_init.evt_handler) {
288 evt.conn_idx = conn_idx;
289 evt.evt_type = ((cccd_value == PRF_CLI_START_NTF) ?
290 HRS_EVT_NOTIFICATION_ENABLED :
291 HRS_EVT_NOTIFICATION_DISABLED);
292 s_hrs_env.hrs_init.evt_handler(&evt);
293 }
294
295 cfm.status = BLE_SUCCESS;
296 break;
297
298 case HRS_IDX_HR_CTNL_PT_VAL:
299 if (p_param->value[0] == HRS_CTRL_POINT_ENERGY_EXP) {
300 if (s_hrs_env.hrs_init.evt_handler) {
301 evt.conn_idx = conn_idx;
302 evt.evt_type = HRS_EVT_RESET_ENERGY_EXPENDED;
303 s_hrs_env.hrs_init.evt_handler(&evt);
304 }
305
306 cfm.status = BLE_SUCCESS;
307 } else {
308 cfm.status = 0x80;
309 }
310 break;
311
312 default:
313 cfm.status = BLE_ATT_ERR_INVALID_HANDLE;
314 break;
315 }
316
317 ble_gatts_write_cfm(conn_idx, &cfm);
318 }
319
320 /**
321 *****************************************************************************************
322 * @brief Handles reception of the cccd recover request.
323 *
324 * @param[in]: conn_idx: Connection index
325 * @param[in]: handle: The handle of cccd attribute.
326 * @param[in]: cccd_value: The value of cccd attribute.
327 *****************************************************************************************
328 */
hrs_cccd_set_cb(uint8_t conn_idx,uint16_t handle,uint16_t cccd_value)329 static void hrs_cccd_set_cb(uint8_t conn_idx, uint16_t handle, uint16_t cccd_value)
330 {
331 hrs_evt_t evt;
332
333 if (!prf_is_cccd_value_valid(cccd_value)) {
334 return;
335 }
336
337 uint8_t tab_index = prf_find_idx_by_handle(handle,
338 s_hrs_env.start_hdl,
339 HRS_IDX_NB,
340 (uint8_t *)&s_hrs_env.hrs_init.char_mask);
341
342 switch (tab_index) {
343 case HRS_IDX_HR_MEAS_NTF_CFG:
344 s_hrs_env.ntf_cfg[conn_idx] = cccd_value;
345
346 if (s_hrs_env.hrs_init.evt_handler) {
347 evt.conn_idx = conn_idx;
348 evt.evt_type = ((cccd_value == PRF_CLI_START_NTF) ?
349 HRS_EVT_NOTIFICATION_ENABLED :
350 HRS_EVT_NOTIFICATION_DISABLED);
351 s_hrs_env.hrs_init.evt_handler(&evt);
352 }
353 break;
354
355 default:
356 break;
357 }
358 }
359
360 /**
361 *****************************************************************************************
362 * @brief Encode a Heart Rate Measurement.
363 *
364 * @param[in] heart_rate The heart rate measurement to be encoded.
365 * @param[out] p_encoded_buffer Pointer to the buffer where the encoded data will
366 * be written.
367 * @param[in] is_energy_updated Indicate whether update energy expended.
368 *
369 * @return Size of encoded data.
370 *****************************************************************************************
371 */
hrs_hrm_encode(uint16_t heart_rate,uint8_t * p_encoded_buffer,bool is_energy_updated)372 static uint8_t hrs_hrm_encode(uint16_t heart_rate, uint8_t *p_encoded_buffer, bool is_energy_updated)
373 {
374 uint8_t len = 1;
375 uint8_t temp = 0;
376 uint8_t flags = 0;
377 struct hr_meas_char_val_t *p_meas_char;
378
379 // Set sensor contact related flags
380 if (s_hrs_env.hrs_init.is_sensor_contact_supported) {
381 flags |= HRS_BIT_SENSOR_CONTACT_SUPPORTED;
382 }
383 if (s_hrs_env.hr_meas.is_sensor_contact_detected) {
384 flags |= HRS_BIT_SENSOR_CONTACT_DETECTED;
385 }
386
387 p_meas_char = &s_hrs_env.hr_meas;
388 // Encode heart rate measurement
389 if (heart_rate > 0xff) {
390 flags |= HRS_BIT_RATE_FORMAT;
391 p_encoded_buffer[len++] = LO_U16(heart_rate);
392 p_encoded_buffer[len++] = HI_U16(heart_rate);
393 } else {
394 flags &= ~HRS_BIT_RATE_FORMAT;
395 p_encoded_buffer[len++] = (uint8_t)heart_rate;
396 }
397
398 // Encode heart rate energy
399 if ((s_hrs_env.hrs_init.char_mask & HRS_CHAR_ENGY_EXP_SUP) && is_energy_updated) {
400 flags |= HRS_BIT_ENERGY_EXPENDED_STATUS;
401 p_encoded_buffer[len++] = LO_U16(p_meas_char->energy_expended);
402 p_encoded_buffer[len++] = HI_U16(p_meas_char->energy_expended);
403 }
404
405 // Encode rr_interval values
406 if (p_meas_char->rr_interval_count == 0) {
407 flags &= ~(HRS_BIT_INTERVAL);
408 } else {
409 flags |= HRS_BIT_INTERVAL;
410 temp = ((HRS_MEAS_MAX_LEN - len) / DIV_2);
411 for (uint8_t i = 0; i < temp; i++) {
412 p_encoded_buffer[len++] = LO_U16(p_meas_char->rr_interval[i]);
413 p_encoded_buffer[len++] = HI_U16(p_meas_char->rr_interval[i]);
414 }
415 }
416
417 // Add flags
418 p_encoded_buffer[0] = flags;
419
420 return len;
421 }
422
423 /*
424 * GLOBAL FUNCTION DEFINITIONS
425 *******************************************************************************
426 */
hrs_rr_interval_add(uint16_t rr_interval)427 void hrs_rr_interval_add(uint16_t rr_interval)
428 {
429 struct hr_meas_char_val_t *p_meas_char = &s_hrs_env.hr_meas;
430
431 p_meas_char->rr_interval_count = 0;
432
433 for (uint8_t i = HRS_MAX_BUFFERED_RR_INTERVALS - 1; i > 0; i--) {
434 p_meas_char->rr_interval[i] = p_meas_char->rr_interval[i - 1];
435 }
436
437 p_meas_char->rr_interval[0] = rr_interval;
438 p_meas_char->rr_interval_count++;
439 if (p_meas_char->rr_interval_count > HRS_MAX_BUFFERED_RR_INTERVALS) {
440 p_meas_char->rr_interval_count = HRS_MAX_BUFFERED_RR_INTERVALS;
441 }
442 }
443
hrs_energy_update(uint16_t energy)444 void hrs_energy_update(uint16_t energy)
445 {
446 struct hr_meas_char_val_t *p_meas_char = &s_hrs_env.hr_meas;
447
448 p_meas_char->energy_expended = energy;
449 }
450
451
hrs_sensor_contact_detected_update(bool is_sensor_contact_detected)452 void hrs_sensor_contact_detected_update(bool is_sensor_contact_detected)
453 {
454 s_hrs_env.hr_meas.is_sensor_contact_detected = is_sensor_contact_detected;
455 }
456
hrs_sensor_contact_supported_set(bool is_sensor_contact_supported)457 void hrs_sensor_contact_supported_set(bool is_sensor_contact_supported)
458 {
459 s_hrs_env.hrs_init.is_sensor_contact_supported = is_sensor_contact_supported;
460 }
461
hrs_sensor_location_set(hrs_sensor_loc_t hrs_sensor_loc)462 void hrs_sensor_location_set(hrs_sensor_loc_t hrs_sensor_loc)
463 {
464 s_hrs_env.hrs_init.sensor_loc = hrs_sensor_loc;
465 }
466
467
hrs_heart_rate_measurement_send(uint8_t conn_idx,uint16_t heart_rate,bool is_energy_updated)468 sdk_err_t hrs_heart_rate_measurement_send(uint8_t conn_idx, uint16_t heart_rate, bool is_energy_updated)
469 {
470 sdk_err_t error_code = SDK_ERR_NTF_DISABLED;
471 struct hr_meas_char_val_t *p_meas_char = &s_hrs_env.hr_meas;
472 uint8_t len = hrs_hrm_encode(heart_rate, p_meas_char->hr_meas_value, is_energy_updated);
473
474 if (s_hrs_env.ntf_cfg[conn_idx] == PRF_CLI_START_NTF) {
475 gatts_noti_ind_t hr_noti;
476
477 hr_noti.type = BLE_GATT_NOTIFICATION;
478 hr_noti.handle = prf_find_handle_by_idx(HRS_IDX_HR_MEAS_VAL,
479 s_hrs_env.start_hdl,
480 (uint8_t *)&s_hrs_env.hrs_init.char_mask);
481 hr_noti.length = len;
482 hr_noti.value = p_meas_char->hr_meas_value;
483
484 error_code = ble_gatts_noti_ind(conn_idx, &hr_noti);
485 }
486
487 return error_code;
488 }
489
hrs_service_init(hrs_init_t * p_hrs_init)490 sdk_err_t hrs_service_init(hrs_init_t *p_hrs_init)
491 {
492 sdk_err_t ret;
493 if (p_hrs_init == NULL) {
494 return SDK_ERR_POINTER_NULL;
495 }
496
497 ret = memcpy_s(&s_hrs_env.hrs_init, sizeof(hrs_init_t), p_hrs_init, sizeof(hrs_init_t));
498 if (ret < 0) {
499 return ret;
500 }
501
502 return ble_server_prf_add(&hrs_prf_info);
503 }
504
505