• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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