xref: /btstack/src/ble/gatt-service/battery_service_client.c (revision 1707474d4963569c4305dfdcf0e8d12ea8ff1909)
1 /*
2  * Copyright (C) 2021 BlueKitchen GmbH
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the copyright holders nor the names of
14  *    contributors may be used to endorse or promote products derived
15  *    from this software without specific prior written permission.
16  * 4. Any redistribution, use, or modification is done solely for
17  *    personal benefit and not for any commercial purpose or for
18  *    monetary gain.
19  *
20  * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN
24  * GMBH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
27  * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
30  * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * Please inquire about commercial licensing options at
34  * [email protected]
35  *
36  */
37 
38 #define BTSTACK_FILE__ "battery_service_client.c"
39 
40 #include "btstack_config.h"
41 
42 #ifdef ENABLE_TESTING_SUPPORT
43 #include <stdio.h>
44 #include <unistd.h>
45 #endif
46 
47 #include <stdint.h>
48 #include <string.h>
49 
50 
51 #include "ble/gatt-service/battery_service_client.h"
52 
53 #include "btstack_memory.h"
54 #include "ble/core.h"
55 #include "ble/gatt_client.h"
56 #include "bluetooth_gatt.h"
57 #include "btstack_debug.h"
58 #include "btstack_event.h"
59 #include "btstack_run_loop.h"
60 #include "gap.h"
61 
62 #define BATTERY_SERVICE_INVALID_INDEX 0xFF
63 
64 static btstack_linked_list_t clients;
65 static uint16_t battery_service_cid_counter = 0;
66 static btstack_packet_callback_registration_t hci_event_callback_registration;
67 
68 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
69 static void battery_service_poll_timer_start(battery_service_client_t * client);
70 
71 static uint16_t battery_service_get_next_cid(void){
72     battery_service_cid_counter = btstack_next_cid_ignoring_zero(battery_service_cid_counter);
73     return battery_service_cid_counter;
74 }
75 
76 static battery_service_client_t * battery_service_create_client(hci_con_handle_t con_handle, uint16_t cid){
77     battery_service_client_t * client = btstack_memory_battery_service_client_get();
78     if (!client){
79         log_error("Not enough memory to create client");
80         return NULL;
81     }
82     client->cid = cid;
83     client->con_handle = con_handle;
84     client->poll_interval_ms = 0;
85     client->num_instances = 0;
86     client->service_index = 0;
87     client->poll_bitmap = 0;
88     client->need_poll_bitmap = 0;
89     client->polled_service_index = BATTERY_SERVICE_INVALID_INDEX;
90     client->state = BATTERY_SERVICE_CLIENT_STATE_IDLE;
91 
92     btstack_linked_list_add(&clients, (btstack_linked_item_t *) client);
93     return client;
94 }
95 
96 static void battery_service_finalize_client(battery_service_client_t * client){
97     // stop listening
98     uint8_t i;
99     for (i = 0; i < client->num_instances; i++){
100         gatt_client_stop_listening_for_characteristic_value_updates(&client->services[i].notification_listener);
101     }
102 
103     // remove timer
104     btstack_run_loop_remove_timer(&client->poll_timer);
105 
106     btstack_linked_list_remove(&clients, (btstack_linked_item_t *) client);
107     btstack_memory_battery_service_client_free(client);
108 }
109 
110 static battery_service_client_t * battery_service_get_client_for_con_handle(hci_con_handle_t con_handle){
111     btstack_linked_list_iterator_t it;
112     btstack_linked_list_iterator_init(&it, &clients);
113     while (btstack_linked_list_iterator_has_next(&it)){
114         battery_service_client_t * client = (battery_service_client_t *)btstack_linked_list_iterator_next(&it);
115         if (client->con_handle != con_handle) continue;
116         return client;
117     }
118     return NULL;
119 }
120 
121 static battery_service_client_t * battery_service_get_client_for_cid(uint16_t battery_service_cid){
122     btstack_linked_list_iterator_t it;
123     btstack_linked_list_iterator_init(&it, &clients);
124     while (btstack_linked_list_iterator_has_next(&it)){
125         battery_service_client_t * client = (battery_service_client_t *)btstack_linked_list_iterator_next(&it);
126         if (client->cid != battery_service_cid) continue;
127         return client;
128     }
129     return NULL;
130 }
131 
132 static void battery_service_emit_connection_established(battery_service_client_t * client, uint8_t status){
133     uint8_t event[8];
134     int pos = 0;
135     event[pos++] = HCI_EVENT_GATTSERVICE_META;
136     event[pos++] = sizeof(event) - 2;
137     event[pos++] = GATTSERVICE_SUBEVENT_BATTERY_SERVICE_CONNECTED;
138     little_endian_store_16(event, pos, client->cid);
139     pos += 2;
140     event[pos++] = status;
141     event[pos++] = client->num_instances;
142     event[pos++] = client->poll_bitmap;
143 
144     (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
145 }
146 
147 static void battery_service_emit_battery_level(battery_service_client_t * client, uint16_t value_handle, uint8_t att_status, uint8_t battery_level){
148     uint8_t event[8];
149     int pos = 0;
150     event[pos++] = HCI_EVENT_GATTSERVICE_META;
151     event[pos++] = sizeof(event) - 2;
152     event[pos++] = GATTSERVICE_SUBEVENT_BATTERY_SERVICE_LEVEL;
153     little_endian_store_16(event, pos, client->cid);
154     pos += 2;
155 
156     uint8_t i;
157     for (i = 0; i < client->num_instances; i++){
158         if (value_handle == client->services[i].value_handle){
159             event[pos++] = i;
160             event[pos++] = att_status;
161             event[pos++] = battery_level;
162             (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
163             break;
164         }
165     }
166 }
167 
168 static bool battery_service_registered_notification(battery_service_client_t * client, uint16_t service_index){
169     gatt_client_characteristic_t characteristic;
170     // if there are services without notification, register pool timer,
171     // otherwise register for notifications
172     characteristic.value_handle = client->services[service_index].value_handle;
173     characteristic.properties   = client->services[service_index].properties;
174     characteristic.end_handle   = client->services[service_index].end_handle;
175 
176     uint8_t status = gatt_client_write_client_characteristic_configuration(
177                 &handle_gatt_client_event,
178                 client->con_handle,
179                 &characteristic,
180                 GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
181 
182     // notification supported, register for value updates
183     if (status == ERROR_CODE_SUCCESS){
184         gatt_client_listen_for_characteristic_value_updates(
185             &client->services[service_index].notification_listener,
186             &handle_gatt_client_event,
187             client->con_handle, &characteristic);
188     } else {
189         client->poll_bitmap |= 1u << client->service_index;
190     }
191     return status;
192 }
193 
194 static void battery_service_start_polling_if_needed(battery_service_client_t * client){
195     if (client->poll_interval_ms > 0){
196         client->need_poll_bitmap = client->poll_bitmap;
197         battery_service_poll_timer_start(client);
198     }
199 }
200 
201 static void battery_service_run_for_client(battery_service_client_t * client){
202     uint8_t status;
203     uint8_t i;
204     gatt_client_characteristic_t characteristic;
205 
206     switch (client->state){
207         case BATTERY_SERVICE_CLIENT_STATE_CONNECTED:
208             for (i = 0; i < client->num_instances; i++){
209                 if ( ((client->need_poll_bitmap >> i) & 0x01) == 0x01 ){
210                     // clear bit of polled service
211                     client->need_poll_bitmap &= ~(1u << i);
212                     client->polled_service_index = i;
213 
214                     // poll value of characteristic
215                     characteristic.value_handle = client->services[i].value_handle;
216                     characteristic.properties   = client->services[i].properties;
217                     characteristic.end_handle   = client->services[i].end_handle;
218                     gatt_client_read_value_of_characteristic(&handle_gatt_client_event, client->con_handle, &characteristic);
219                     break;
220                 }
221             }
222             break;
223 
224         case BATTERY_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE:
225             client->state = BATTERY_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT;
226             status = gatt_client_discover_primary_services_by_uuid16(&handle_gatt_client_event, client->con_handle, ORG_BLUETOOTH_SERVICE_BATTERY_SERVICE);
227             // TODO handle status
228             break;
229 
230         case BATTERY_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS:
231             client->state = BATTERY_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT;
232 
233             gatt_client_discover_characteristics_for_handle_range_by_uuid16(
234                 &handle_gatt_client_event,
235                 client->con_handle,
236                 client->services[client->service_index].start_handle,
237                 client->services[client->service_index].end_handle,
238                 ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL);
239 
240             break;
241 
242 #ifdef ENABLE_TESTING_SUPPORT
243         case BATTERY_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS:
244             client->state = BATTERY_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT;
245             // if there are services without notification, register pool timer,
246             // othervise register for notifications
247             characteristic.value_handle = client->services[client->service_index].value_handle;
248             characteristic.properties   = client->services[client->service_index].properties;
249             characteristic.end_handle   = client->services[client->service_index].end_handle;
250 
251             (void) gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, client->con_handle, &characteristic);
252             break;
253 
254         case BATTERY_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION:
255             printf("Read client characteristic value [Service %d, handle 0x%04X]:\n",
256                 client->service_index,
257                 client->services[client->service_index].ccc_handle);
258 
259             client->state = BATTERY_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT;
260 
261             // result in GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT
262             (void) gatt_client_read_characteristic_descriptor_using_descriptor_handle(
263                 &handle_gatt_client_event,
264                 client->con_handle,
265                 client->services[client->service_index].ccc_handle);
266             break;
267 #endif
268 
269         case BATTERY_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION:
270             client->state = BATTERY_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED;
271 
272             for (; client->service_index < client->num_instances; client->service_index++){
273                 status = battery_service_registered_notification(client, client->service_index);
274                 if (status == ERROR_CODE_SUCCESS) return;
275             }
276 
277 
278 #ifdef ENABLE_TESTING_SUPPORT
279             for (client->service_index = 0; client->service_index < client->num_instances; client->service_index++){
280                 bool need_polling = (client->poll_bitmap & (1 << client->service_index)) != 0;
281                 if ( (client->services[client->service_index].ccc_handle != 0) && !need_polling ){
282                     client->state = BATTERY_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION;
283                     break;
284                 }
285             }
286 #endif
287             client->state = BATTERY_SERVICE_CLIENT_STATE_CONNECTED;
288             battery_service_emit_connection_established(client, ERROR_CODE_SUCCESS);
289             battery_service_start_polling_if_needed(client);
290             break;
291         default:
292             break;
293     }
294 }
295 
296 static void battery_service_poll_timer_timeout_handler(btstack_timer_source_t * timer){
297     uint16_t battery_service_cid = (uint16_t)(uintptr_t) btstack_run_loop_get_timer_context(timer);
298 
299     battery_service_client_t * client =  battery_service_get_client_for_cid(battery_service_cid);
300     btstack_assert(client != NULL);
301 
302     client->need_poll_bitmap = client->poll_bitmap;
303     battery_service_poll_timer_start(client);
304     battery_service_run_for_client(client);
305 }
306 
307 static void battery_service_poll_timer_start(battery_service_client_t * client){
308     btstack_run_loop_set_timer_handler(&client->poll_timer,  battery_service_poll_timer_timeout_handler);
309     btstack_run_loop_set_timer_context(&client->poll_timer, (void *)(uintptr_t)client->cid);
310 
311     btstack_run_loop_set_timer(&client->poll_timer, client->poll_interval_ms);
312     btstack_run_loop_add_timer(&client->poll_timer);
313 }
314 
315 static void battery_service_client_validate_service(battery_service_client_t * client){
316     // remove all services without characteristic (array in-place)
317     uint8_t src_index  = 0;  // next entry to check
318     uint8_t dest_index = 0;  // to store entry
319     for (src_index = 0; src_index < client->num_instances; src_index++){
320         if (client->services[src_index].value_handle != 0){
321             if (src_index != dest_index) {
322                 client->services[dest_index] = client->services[src_index];
323             }
324             dest_index++;
325         }
326     }
327     client->num_instances = dest_index;
328 }
329 
330 // @return true if client valid / run function should be called
331 static bool battery_service_client_handle_query_complete(battery_service_client_t * client, uint8_t status){
332     switch (client->state){
333         case BATTERY_SERVICE_CLIENT_STATE_W4_SERVICE_RESULT:
334             if (status != ATT_ERROR_SUCCESS){
335                 battery_service_emit_connection_established(client, status);
336                 battery_service_finalize_client(client);
337                 return false;
338             }
339 
340             if (client->num_instances == 0){
341                 battery_service_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
342                 battery_service_finalize_client(client);
343                 return false;
344             }
345 
346             client->service_index = 0;
347             client->state = BATTERY_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS;
348             break;
349 
350         case BATTERY_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_RESULT:
351             if (status != ATT_ERROR_SUCCESS){
352                 battery_service_emit_connection_established(client, status);
353                 battery_service_finalize_client(client);
354                 return false;
355             }
356 
357             // check if there is another service to query
358             if ((client->service_index + 1) < client->num_instances){
359                 client->service_index++;
360                 client->state = BATTERY_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTICS;
361                 break;
362             }
363 
364             battery_service_client_validate_service(client);
365 
366             if (client->num_instances == 0){
367                 battery_service_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE);
368                 battery_service_finalize_client(client);
369                 return false;
370             }
371 
372             // we are done with querying all services
373             client->service_index = 0;
374 
375 #ifdef ENABLE_TESTING_SUPPORT
376             client->state = BATTERY_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS;
377 #else
378             // wait for notification registration
379             // to send connection established event
380             client->state = BATTERY_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION;
381 #endif
382             break;
383 
384 #ifdef ENABLE_TESTING_SUPPORT
385             case BATTERY_SERVICE_CLIENT_STATE_W4_CHARACTERISTIC_DESCRIPTORS_RESULT:
386                     if ((client->service_index + 1) < client->num_instances){
387                         client->service_index++;
388                         client->state = BATTERY_SERVICE_CLIENT_STATE_W2_QUERY_CHARACTERISTIC_DESCRIPTORS;
389                         break;
390                     }
391                     client->service_index = 0;
392                     client->state = BATTERY_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION;
393                     break;
394 
395                 case BATTERY_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT:
396                     client->state = BATTERY_SERVICE_CLIENT_STATE_CONNECTED;
397                     battery_service_emit_connection_established(client, ERROR_CODE_SUCCESS);
398                     battery_service_start_polling_if_needed(client);
399                     break;
400 #endif
401         case BATTERY_SERVICE_CLIENT_STATE_W4_NOTIFICATION_REGISTERED:
402             if ((client->service_index + 1) < client->num_instances){
403                 client->service_index++;
404                 client->state = BATTERY_SERVICE_CLIENT_STATE_W2_REGISTER_NOTIFICATION;
405                 break;
406             }
407 #ifdef ENABLE_TESTING_SUPPORT
408             for (client->service_index = 0; client->service_index < client->num_instances; client->service_index++){
409                         bool need_polling = (client->poll_bitmap & (1 << client->service_index)) != 0;
410                         printf("read CCC 1 0x%02x, polling %d \n", client->services[client->service_index].ccc_handle, (int) need_polling);
411                         if ( (client->services[client->service_index].ccc_handle != 0) && !need_polling ) {
412                             client->state = BATTERY_SERVICE_CLIENT_W2_READ_CHARACTERISTIC_CONFIGURATION;
413                             break;
414                         }
415                     }
416 #endif
417             client->state = BATTERY_SERVICE_CLIENT_STATE_CONNECTED;
418             battery_service_emit_connection_established(client, ERROR_CODE_SUCCESS);
419             battery_service_start_polling_if_needed(client);
420             break;
421 
422         case BATTERY_SERVICE_CLIENT_STATE_CONNECTED:
423             if (client->polled_service_index != BATTERY_SERVICE_INVALID_INDEX){
424                 if (status != ATT_ERROR_SUCCESS){
425                     battery_service_emit_battery_level(client, client->services[client->polled_service_index].value_handle, status, 0);
426                 }
427                 client->polled_service_index = BATTERY_SERVICE_INVALID_INDEX;
428             }
429             break;
430 
431         default:
432             break;
433 
434     }
435     return true;
436 }
437 
438 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
439     UNUSED(packet_type);
440     UNUSED(channel);
441     UNUSED(size);
442 
443     battery_service_client_t * client = NULL;
444     gatt_client_service_t service;
445     gatt_client_characteristic_t characteristic;
446     bool call_run = true;
447 
448     switch(hci_event_packet_get_type(packet)){
449         case GATT_EVENT_SERVICE_QUERY_RESULT:
450             client = battery_service_get_client_for_con_handle(gatt_event_service_query_result_get_handle(packet));
451             btstack_assert(client != NULL);
452 
453             if (client->num_instances < MAX_NUM_BATTERY_SERVICES){
454                 gatt_event_service_query_result_get_service(packet, &service);
455                 client->services[client->num_instances].start_handle = service.start_group_handle;
456                 client->services[client->num_instances].end_handle = service.end_group_handle;
457 
458 #ifdef ENABLE_TESTING_SUPPORT
459                 printf("Battery Service: start handle 0x%04X, end handle 0x%04X\n", client->services[client->num_instances].start_handle, client->services[client->num_instances].end_handle);
460 #endif
461                 client->num_instances++;
462             } else {
463                 log_info("Found more then %d, Battery Service instances. Increase MAX_NUM_BATTERY_SERVICES to store all.", MAX_NUM_BATTERY_SERVICES);
464             }
465             break;
466 
467         case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
468             client = battery_service_get_client_for_con_handle(gatt_event_characteristic_query_result_get_handle(packet));
469             btstack_assert(client != NULL);
470 
471             gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
472             btstack_assert(client->service_index < client->num_instances);
473             btstack_assert(characteristic.uuid16 == ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL);
474 
475             client->services[client->service_index].value_handle = characteristic.value_handle;
476             client->services[client->service_index].properties = characteristic.properties;
477 
478 #ifdef ENABLE_TESTING_SUPPORT
479             printf("Battery Level Characteristic:\n    Attribute Handle 0x%04X, Properties 0x%02X, Handle 0x%04X, UUID 0x%04X, service %d\n",
480                 // hid_characteristic_name(characteristic.uuid16),
481                 characteristic.start_handle,
482                 characteristic.properties,
483                 characteristic.value_handle, characteristic.uuid16,
484                 client->service_index);
485 #endif
486             break;
487 
488         case GATT_EVENT_NOTIFICATION:
489             if (gatt_event_notification_get_value_length(packet) != 1) break;
490 
491             client = battery_service_get_client_for_con_handle(gatt_event_notification_get_handle(packet));
492             btstack_assert(client != NULL);
493 
494             battery_service_emit_battery_level(client,
495                 gatt_event_notification_get_value_handle(packet),
496                 ATT_ERROR_SUCCESS,
497                 gatt_event_notification_get_value(packet)[0]);
498             break;
499 
500         case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT:
501             client = battery_service_get_client_for_con_handle(gatt_event_characteristic_value_query_result_get_handle(packet));
502             btstack_assert(client != NULL);
503 
504 #ifdef ENABLE_TESTING_SUPPORT
505             if (client->state == BATTERY_SERVICE_CLIENT_W4_CHARACTERISTIC_CONFIGURATION_RESULT){
506                 printf("    Received CCC value: ");
507                 printf_hexdump(gatt_event_characteristic_value_query_result_get_value(packet),  gatt_event_characteristic_value_query_result_get_value_length(packet));
508                 break;
509             }
510 #endif
511             if (gatt_event_characteristic_value_query_result_get_value_length(packet) != 1) break;
512 
513             battery_service_emit_battery_level(client,
514                 gatt_event_characteristic_value_query_result_get_value_handle(packet),
515                 ATT_ERROR_SUCCESS,
516                 gatt_event_characteristic_value_query_result_get_value(packet)[0]);
517             break;
518 
519 #ifdef ENABLE_TESTING_SUPPORT
520         case GATT_EVENT_ALL_CHARACTERISTIC_DESCRIPTORS_QUERY_RESULT:{
521             gatt_client_characteristic_descriptor_t characteristic_descriptor;
522 
523             client = battery_service_get_client_for_con_handle(gatt_event_all_characteristic_descriptors_query_result_get_handle(packet));
524             btstack_assert(client != NULL);
525             gatt_event_all_characteristic_descriptors_query_result_get_characteristic_descriptor(packet, &characteristic_descriptor);
526 
527             if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_GATT_CLIENT_CHARACTERISTIC_CONFIGURATION){
528                 client->services[client->service_index].ccc_handle = characteristic_descriptor.handle;
529 
530                 printf("    Battery Level Client Characteristic Configuration Descriptor[%d]:  Handle 0x%04X, UUID 0x%04X\n",
531                     client->service_index,
532                     characteristic_descriptor.handle,
533                     characteristic_descriptor.uuid16);
534             }
535             break;
536         }
537 #endif
538 
539         case GATT_EVENT_QUERY_COMPLETE:
540             client = battery_service_get_client_for_con_handle(gatt_event_query_complete_get_handle(packet));
541             btstack_assert(client != NULL);
542             call_run = battery_service_client_handle_query_complete(client, gatt_event_query_complete_get_att_status(packet));
543             break;
544 
545         default:
546             break;
547     }
548 
549     if (call_run && (client != NULL)){
550         battery_service_run_for_client(client);
551     }
552 }
553 
554 
555 static void handle_hci_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
556     UNUSED(packet_type); // ok: only hci events
557     UNUSED(channel);     // ok: there is no channel
558     UNUSED(size);        // ok: fixed format events read from HCI buffer
559 
560     hci_con_handle_t con_handle;
561     battery_service_client_t * client;
562 
563     switch (hci_event_packet_get_type(packet)) {
564         case HCI_EVENT_DISCONNECTION_COMPLETE:
565             con_handle = hci_event_disconnection_complete_get_connection_handle(packet);
566             client = battery_service_get_client_for_con_handle(con_handle);
567             if (client != NULL){
568                 // finalize
569                 uint16_t cid = client->cid;
570                 battery_service_finalize_client(client);
571                 // TODO: emit disconnected event
572                 UNUSED(cid);
573             }
574             break;
575         default:
576             break;
577     }
578 }
579 
580 uint8_t battery_service_client_connect(hci_con_handle_t con_handle, btstack_packet_handler_t packet_handler, uint32_t poll_interval_ms, uint16_t * battery_service_cid){
581     btstack_assert(packet_handler != NULL);
582 
583     battery_service_client_t * client = battery_service_get_client_for_con_handle(con_handle);
584     if (client != NULL){
585         return ERROR_CODE_COMMAND_DISALLOWED;
586     }
587 
588     uint16_t cid = battery_service_get_next_cid();
589     if (battery_service_cid != NULL) {
590         *battery_service_cid = cid;
591     }
592 
593     client = battery_service_create_client(con_handle, cid);
594     if (client == NULL) {
595         return BTSTACK_MEMORY_ALLOC_FAILED;
596     }
597 
598     client->client_handler = packet_handler;
599     client->poll_interval_ms = poll_interval_ms;
600     client->state = BATTERY_SERVICE_CLIENT_STATE_W2_QUERY_SERVICE;
601     battery_service_run_for_client(client);
602     return ERROR_CODE_SUCCESS;
603 }
604 
605 uint8_t battery_service_client_disconnect(uint16_t battery_service_cid){
606     battery_service_client_t * client = battery_service_get_client_for_cid(battery_service_cid);
607     if (client == NULL){
608         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
609     }
610     // finalize connections
611     battery_service_finalize_client(client);
612     return ERROR_CODE_SUCCESS;
613 }
614 
615 uint8_t battery_service_client_read_battery_level(uint16_t battery_service_cid, uint8_t service_index){
616     battery_service_client_t * client = battery_service_get_client_for_cid(battery_service_cid);
617     if (client == NULL) {
618         return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER;
619     }
620     if (client->state != BATTERY_SERVICE_CLIENT_STATE_CONNECTED) {
621         return GATT_CLIENT_IN_WRONG_STATE;
622     }
623     if (service_index >= client->num_instances) {
624         return ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE;
625     }
626 
627     client->need_poll_bitmap |= (1u << service_index);
628     battery_service_run_for_client(client);
629     return ERROR_CODE_SUCCESS;
630 }
631 
632 void battery_service_client_init(void){
633     hci_event_callback_registration.callback = &handle_hci_event;
634     hci_add_event_handler(&hci_event_callback_registration);
635 }
636 
637 void battery_service_client_deinit(void){
638     battery_service_cid_counter = 0;
639     clients = NULL;
640 }
641 
642