1 /**
2 *****************************************************************************************
3 *
4 * @file bas.c
5 *
6 * @brief Battery Server 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 "bas.h"
43 #include "ble_prf_types.h"
44 #include "ble_prf_utils.h"
45 #include "utility.h"
46
47 /*
48 * ENUMERATIONS
49 *****************************************************************************************
50 */
51 /**@brief Battery Service Attributes Indexes. */
52 enum bas_attr_idx_t {
53 BAS_IDX_SVC,
54
55 BAS_IDX_BATT_LVL_CHAR,
56 BAS_IDX_BATT_LVL_VAL,
57 BAS_IDX_BATT_LVL_NTF_CFG,
58 BAS_IDX_BATT_LVL_PRES_FMT,
59
60 BAS_IDX_NB,
61 };
62
63 /*
64 * STRUCTURES
65 *****************************************************************************************
66 */
67 /**@brief Battery Service environment variable. */
68 struct bas_env_t {
69 bas_init_t bas_init; /**< Battery Service initialization variables. */
70 uint16_t start_hdl; /**< Battery Service start handle. */
71 uint16_t
72 ntf_cfg[BAS_CONNECTION_MAX]; /**< The configuration of Battery Level Notification \
73 which is configured by the peer devices. */
74 prf_char_pres_fmt_t
75 batt_level_pres_format; /**< Battery Level Characteristic Presentation Format \
76 which should not change during connection. */
77 };
78
79 /*
80 * LOCAL FUNCTION DECLARATION
81 *****************************************************************************************
82 */
83 static sdk_err_t bas_init(void);
84 static void bas_read_att_cb(uint8_t conn_idx, const gatts_read_req_cb_t *p_param);
85 static void bas_write_att_cb(uint8_t conn_idx, const gatts_write_req_cb_t *p_param);
86 static void bas_cccd_set_cb(uint8_t conn_idx, uint16_t handle, uint16_t cccd_value);
87 /*
88 * LOCAL VARIABLE DEFINITIONS
89 *****************************************************************************************
90 */
91 static struct bas_env_t s_bas_env[BAS_INSTANCE_MAX]; /**< Battery service instance. */
92 static uint8_t s_bas_ins_cnt = 0; /**< Number of Battery Server task instances. */
93
94 /**@brief Full BAS Database Description which is used to add attributes into the ATT database. */
95 static const attm_desc_t bas_attr_tab[BAS_IDX_NB] = {
96 // Battery Service Declaration
97 [BAS_IDX_SVC] = {BLE_ATT_DECL_PRIMARY_SERVICE, READ_PERM_UNSEC, 0, 0},
98
99 // Battery Level Characteristic - Declaration
100 [BAS_IDX_BATT_LVL_CHAR] = {BLE_ATT_DECL_CHARACTERISTIC, READ_PERM_UNSEC, 0, 0},
101 // Battery Level Characteristic - Value
102 [BAS_IDX_BATT_LVL_VAL] = {
103 BLE_ATT_CHAR_BATTERY_LEVEL,
104 READ_PERM_UNSEC | NOTIFY_PERM_UNSEC,
105 ATT_VAL_LOC_USER,
106 BAS_LVL_MAX_LEN
107 },
108 // Battery Level Characteristic - Client Characteristic Configuration Descriptor
109 [BAS_IDX_BATT_LVL_NTF_CFG] = {
110 BLE_ATT_DESC_CLIENT_CHAR_CFG,
111 READ_PERM_UNSEC | WRITE_REQ_PERM_UNSEC,
112 ATT_VAL_LOC_USER,
113 0
114 },
115 // Battery Level Characteristic - Characteristic Presentation Format Descriptor
116 [BAS_IDX_BATT_LVL_PRES_FMT] = {BLE_ATT_DESC_CHAR_PRES_FORMAT, READ_PERM_UNSEC, ATT_VAL_LOC_USER, 0},
117 };
118
119 /**@brief Battery Service interface required by profile manager. */
120 static ble_prf_manager_cbs_t bas_mgr_cbs = {
121 (prf_init_func_t)bas_init,
122 NULL,
123 NULL
124 };
125
126 /**@brief Battery GATT Server Callbacks. */
127 static gatts_prf_cbs_t bas_gatts_cbs = {
128 bas_read_att_cb,
129 bas_write_att_cb,
130 NULL,
131 NULL,
132 bas_cccd_set_cb
133 };
134
135 /**@brief Battery Service Information. */
136 static const prf_server_info_t bas_prf_info = {
137 .max_connection_nb = BAS_CONNECTION_MAX,
138 .manager_cbs = &bas_mgr_cbs,
139 .gatts_prf_cbs = &bas_gatts_cbs
140 };
141
142 /*
143 * LOCAL FUNCTION DEFINITIONS
144 *****************************************************************************************
145 */
146 /**
147 *****************************************************************************************
148 * @brief Initialize Battery service and create DB in ATT.
149 *
150 * @return Error code to know if service initialization succeed or not.
151 *****************************************************************************************
152 */
bas_init(void)153 static sdk_err_t bas_init(void)
154 {
155 const uint8_t bas_svc_uuid[] = BLE_ATT_16_TO_16_ARRAY(BLE_ATT_SVC_BATTERY_SERVICE);
156 sdk_err_t error_code = SDK_SUCCESS;
157 uint16_t start_hdl;
158 gatts_create_db_t gatts_db;
159
160 error_code = memset_s(&gatts_db, sizeof(gatts_db), 0, sizeof(gatts_db));
161 if (error_code < 0) {
162 return error_code;
163 }
164
165 for (uint8_t i = 0; i < s_bas_ins_cnt; i++) {
166 // The start hanlde must be set with PRF_INVALID_HANDLE to be allocated automatically by BLE Stack.
167 start_hdl = PRF_INVALID_HANDLE;
168 gatts_db.shdl = &start_hdl;
169 gatts_db.uuid = bas_svc_uuid;
170 gatts_db.attr_tab_cfg = (uint8_t *)&s_bas_env[i].bas_init.char_mask;
171 gatts_db.max_nb_attr = BAS_IDX_NB;
172 gatts_db.srvc_perm = 0;
173 gatts_db.attr_tab_type = SERVICE_TABLE_TYPE_16;
174 gatts_db.attr_tab.attr_tab_16 = bas_attr_tab;
175
176 error_code = ble_gatts_srvc_db_create(&gatts_db);
177 if (SDK_SUCCESS == error_code) {
178 s_bas_env[i].start_hdl = *(gatts_db.shdl);
179 } else {
180 return error_code;
181 }
182 }
183
184 return error_code;
185 }
186
187 /**
188 *****************************************************************************************
189 * @brief Handles reception of the attribute info request message.
190 *
191 * @param[in] conn_idx: Connection index.
192 * @param[in] p_param: Point to the parameters of the read request.
193 *****************************************************************************************
194 */
bas_read_att_cb(uint8_t conn_idx,const gatts_read_req_cb_t * p_param)195 static void bas_read_att_cb(uint8_t conn_idx, const gatts_read_req_cb_t *p_param)
196 {
197 uint8_t handle = p_param->handle;
198 uint8_t tab_index;
199 uint8_t char_pres_value[PRF_CHAR_PRES_FMT_SIZE];
200 uint8_t i;
201 gatts_read_cfm_t cfm;
202
203 cfm.handle = handle;
204 cfm.status = BLE_SUCCESS;
205
206 for (i = 0; i < s_bas_ins_cnt; i++) {
207 tab_index = prf_find_idx_by_handle(handle,
208 s_bas_env[i].start_hdl,
209 BAS_IDX_NB,
210 &s_bas_env[i].bas_init.char_mask);
211 if (tab_index > 0) {
212 break;
213 }
214 }
215
216 switch (tab_index) {
217 case BAS_IDX_BATT_LVL_VAL:
218 cfm.length = sizeof(uint8_t);
219 cfm.value = (uint8_t *)(&s_bas_env[i].bas_init.batt_lvl);
220 break;
221
222 case BAS_IDX_BATT_LVL_NTF_CFG:
223 cfm.length = sizeof(uint16_t);
224 cfm.value = (uint8_t *)(&(s_bas_env[i].ntf_cfg[conn_idx]));
225 break;
226
227 case BAS_IDX_BATT_LVL_PRES_FMT:
228 cfm.length = PRF_CHAR_PRES_FMT_SIZE;
229 prf_pack_char_pres_fmt(char_pres_value, &(s_bas_env[i].batt_level_pres_format));
230 cfm.value = char_pres_value;
231 break;
232
233 default:
234 cfm.length = 0;
235 cfm.status = BLE_ATT_ERR_INVALID_HANDLE;
236 break;
237 }
238
239 ble_gatts_read_cfm(conn_idx, &cfm);
240 }
241
242 /**
243 *****************************************************************************************
244 * @brief Handles reception of the write request.
245 *
246 * @param[in] conn_idx Connection index.
247 * @param[in] p_param Point to the parameters of the write request.
248 *****************************************************************************************
249 */
bas_write_att_cb(uint8_t conn_idx,const gatts_write_req_cb_t * p_param)250 static void bas_write_att_cb(uint8_t conn_idx, const gatts_write_req_cb_t *p_param)
251 {
252 uint16_t handle = p_param->handle;
253 uint16_t cccd_value = 0;
254 uint8_t tab_index = 0;
255 uint8_t i = 0;
256 bas_evt_t bas_evt;
257 gatts_write_cfm_t cfm;
258
259 cfm.handle = handle;
260 cfm.status = BLE_SUCCESS;
261
262 for (i = 0; i < s_bas_ins_cnt; i++) {
263 tab_index = prf_find_idx_by_handle(handle,
264 s_bas_env[i].start_hdl,
265 BAS_IDX_NB,
266 &s_bas_env[i].bas_init.char_mask);
267 if (tab_index > 0) {
268 break;
269 }
270 }
271
272 switch (tab_index) {
273 case BAS_IDX_BATT_LVL_NTF_CFG:
274 cccd_value = le16toh(&p_param->value[0]);
275 s_bas_env[i].ntf_cfg[conn_idx] = cccd_value;
276 bas_evt.evt_type = ((PRF_CLI_START_NTF == cccd_value) ? \
277 BAS_EVT_NOTIFICATION_ENABLED : \
278 BAS_EVT_NOTIFICATION_DISABLED);
279 break;
280
281 default:
282 cfm.status = BLE_ATT_ERR_INVALID_HANDLE;
283 break;
284 }
285
286 if (BLE_ATT_ERR_INVALID_HANDLE != cfm.status && \
287 BAS_EVT_INVALID != bas_evt.evt_type && \
288 s_bas_env[i].bas_init.evt_handler) {
289 bas_evt.conn_idx = conn_idx;
290 s_bas_env[i].bas_init.evt_handler(&bas_evt);
291 }
292
293 ble_gatts_write_cfm(conn_idx, &cfm);
294 }
295
296 /**
297 *****************************************************************************************
298 * @brief Handles reception of the cccd recover request.
299 *
300 * @param[in]: conn_idx: Connection index
301 * @param[in]: handle: The handle of cccd attribute.
302 * @param[in]: cccd_value: The value of cccd attribute.
303 *****************************************************************************************
304 */
bas_cccd_set_cb(uint8_t conn_idx,uint16_t handle,uint16_t cccd_value)305 static void bas_cccd_set_cb(uint8_t conn_idx, uint16_t handle, uint16_t cccd_value)
306 {
307 uint8_t tab_index = 0;
308 uint8_t i = 0;
309 bas_evt_t bas_evt;
310
311 if (!prf_is_cccd_value_valid(cccd_value)) {
312 return;
313 }
314
315 for (i = 0; i < s_bas_ins_cnt; i++) {
316 tab_index = prf_find_idx_by_handle(handle,
317 s_bas_env[i].start_hdl,
318 BAS_IDX_NB,
319 &s_bas_env[i].bas_init.char_mask);
320 if (tab_index > 0) {
321 break;
322 }
323 }
324
325 switch (tab_index) {
326 case BAS_IDX_BATT_LVL_NTF_CFG:
327 s_bas_env[i].ntf_cfg[conn_idx] = cccd_value;
328 bas_evt.evt_type = ((PRF_CLI_START_NTF == cccd_value) ? \
329 BAS_EVT_NOTIFICATION_ENABLED : \
330 BAS_EVT_NOTIFICATION_DISABLED);
331 break;
332
333 default:
334 bas_evt.evt_type = BAS_EVT_INVALID;
335 break;
336 }
337
338 if (BAS_EVT_INVALID != bas_evt.evt_type && s_bas_env[i].bas_init.evt_handler) {
339 bas_evt.conn_idx = conn_idx;
340 s_bas_env[i].bas_init.evt_handler(&bas_evt);
341 }
342 }
343
344 /*
345 * GLOBAL FUNCTION DEFINITIONS
346 *******************************************************************************
347 */
bas_batt_lvl_update(uint8_t conn_idx,uint8_t ins_idx,uint8_t batt_lvl)348 sdk_err_t bas_batt_lvl_update(uint8_t conn_idx, uint8_t ins_idx, uint8_t batt_lvl)
349 {
350 sdk_err_t error_code = SDK_ERR_NTF_DISABLED;
351 gatts_noti_ind_t send_cmd;
352
353 if (ins_idx <= s_bas_ins_cnt) {
354 s_bas_env[ins_idx].bas_init.batt_lvl = batt_lvl;
355
356 if (PRF_CLI_START_NTF == s_bas_env[ins_idx].ntf_cfg[conn_idx]) {
357 // Fill in the parameter structure
358 send_cmd.type = BLE_GATT_NOTIFICATION;
359 send_cmd.handle = prf_find_handle_by_idx(BAS_IDX_BATT_LVL_VAL,
360 s_bas_env[ins_idx].start_hdl,
361 &s_bas_env[ins_idx].bas_init.char_mask);
362 // pack measured value in database
363 send_cmd.length = sizeof(uint8_t);
364 send_cmd.value = &s_bas_env[ins_idx].bas_init.batt_lvl;
365
366 // send notification to peer device
367 error_code = ble_gatts_noti_ind(conn_idx, &send_cmd);
368 }
369 }
370
371 return error_code;
372 }
373
bas_service_init(bas_init_t * p_bas_init,uint8_t ins_num)374 sdk_err_t bas_service_init(bas_init_t *p_bas_init, uint8_t ins_num)
375 {
376 sdk_err_t ret;
377 if (p_bas_init == NULL) {
378 return SDK_ERR_POINTER_NULL;
379 }
380
381 if (ins_num > BAS_INSTANCE_MAX) {
382 return SDK_ERR_INVALID_PARAM;
383 }
384
385 for (uint8_t i = 0; i < ins_num; i++) {
386 ret = memcpy_s(&s_bas_env[i].bas_init, sizeof(bas_init_t), &p_bas_init[i], sizeof(bas_init_t));
387 if (ret < 0) {
388 return ret;
389 }
390 }
391
392 s_bas_ins_cnt = ins_num;
393
394 return ble_server_prf_add(&bas_prf_info);
395 }
396
397