1 /* 2 * Copyright (C) 2014 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_v1_server.c" 39 40 /** 41 * Implementation of the GATT Battery Service Server 42 * To use with your application, add `#import <battery_service.gatt>` to your .gatt file 43 */ 44 45 #include "btstack_defines.h" 46 #include "ble/att_db.h" 47 #include "ble/att_server.h" 48 #include "btstack_util.h" 49 #include "bluetooth_gatt.h" 50 #include "btstack_debug.h" 51 52 #include "ble/gatt-service/battery_service_v1_server.h" 53 54 #define BATTERY_SERVICE_TASK_BATTERY_VALUE_CHANGED 0x0001 55 56 // list of uuids 57 static const uint16_t bas_uuid16s[BAS_CHARACTERISTIC_INDEX_NUM] = { 58 ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL, 59 ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_LEVEL_STATUS, 60 ORG_BLUETOOTH_CHARACTERISTIC_ESTIMATED_SERVICE_DATE, 61 ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_CRITCAL_STATUS, 62 ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_ENERGY_STATUS, 63 ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_TIME_STATUS, 64 ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_HEALTH_STATUS, 65 ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_HEALTH_INFORMATION, 66 ORG_BLUETOOTH_CHARACTERISTIC_BATTERY_INFORMATION, 67 ORG_BLUETOOTH_CHARACTERISTIC_MANUFACTURER_NAME_STRING, 68 ORG_BLUETOOTH_CHARACTERISTIC_MODEL_NUMBER_STRING, 69 ORG_BLUETOOTH_CHARACTERISTIC_SERIAL_NUMBER_STRING, 70 }; 71 72 static btstack_linked_list_t battery_services; 73 74 75 static battery_service_v1_server_connection_t * battery_service_server_connection_for_con_handle(battery_service_v1_t * service, hci_con_handle_t con_handle){ 76 if (service == NULL){ 77 return NULL; 78 } 79 80 uint8_t i; 81 for (i = 0; i < service->connections_max_num; i++){ 82 if (service->connections[i].con_handle == con_handle){ 83 return &service->connections[i]; 84 } 85 } 86 return NULL; 87 } 88 89 static battery_service_v1_server_connection_t * battery_service_server_add_connection_for_con_handle(battery_service_v1_t * service, hci_con_handle_t con_handle){ 90 if (service == NULL){ 91 return NULL; 92 } 93 94 uint8_t i; 95 for (i = 0; i < service->connections_max_num; i++){ 96 if (service->connections[i].con_handle == HCI_CON_HANDLE_INVALID){ 97 service->connections[i].con_handle = con_handle; 98 service->connections[i].service = service; 99 return &service->connections[i]; 100 } 101 } 102 return NULL; 103 } 104 105 106 static battery_service_v1_t * battery_service_service_for_attribute_handle(uint16_t attribute_handle){ 107 btstack_linked_list_iterator_t it; 108 btstack_linked_list_iterator_init(&it, &battery_services); 109 while (btstack_linked_list_iterator_has_next(&it)){ 110 battery_service_v1_t * item = (battery_service_v1_t*) btstack_linked_list_iterator_next(&it); 111 if (attribute_handle < item->start_handle) continue; 112 if (attribute_handle > item->end_handle) continue; 113 return item; 114 } 115 return NULL; 116 } 117 118 119 static battery_service_v1_t * battery_service_service_for_con_handle(hci_con_handle_t con_handle){ 120 btstack_linked_list_iterator_t it; 121 btstack_linked_list_iterator_init(&it, &battery_services); 122 while (btstack_linked_list_iterator_has_next(&it)){ 123 battery_service_v1_t * service = (battery_service_v1_t*) btstack_linked_list_iterator_next(&it); 124 uint8_t i; 125 for (i = 0; i < service->connections_max_num; i++){ 126 if (service->connections[i].con_handle == con_handle){ 127 return service; 128 } 129 } 130 } 131 return NULL; 132 } 133 134 static uint8_t bas_serialize_characteristic(battery_service_v1_t * service, bas_characteristic_index_t index, uint8_t * event, uint8_t event_size){ 135 uint8_t pos = 0; 136 switch ((bas_characteristic_index_t) index){ 137 case BAS_CHARACTERISTIC_INDEX_BATTERY_LEVEL: 138 event[pos++] = service->battery_value; 139 break; 140 141 case BAS_CHARACTERISTIC_INDEX_BATTERY_LEVEL_STATUS: 142 if (service->level_status == NULL){ 143 return 0; 144 } 145 event[pos++] = service->level_status->flags; 146 little_endian_store_16(event, pos, service->level_status->power_state_flags); 147 pos += 2; 148 if ((service->level_status->flags & BATTERY_LEVEL_STATUS_BITMASK_IDENTIFIER_PRESENT) > 0u){ 149 little_endian_store_16(event, pos, service->level_status->identifier); 150 pos += 2; 151 } 152 if ((service->level_status->flags & BATTERY_LEVEL_STATUS_BITMASK_BATTERY_LEVEL_PRESENT) > 0u){ 153 event[pos++] = service->level_status->battery_level; 154 } 155 if ((service->level_status->flags & BATTERY_LEVEL_STATUS_BITMASK_ADDITIONAL_STATUS_PRESENT) > 0u){ 156 event[pos++] = service->level_status->additional_status_flags; 157 } 158 break; 159 160 case BAS_CHARACTERISTIC_INDEX_ESTIMATED_SERVICE_DATE: 161 little_endian_store_24(event, pos, service->estimated_service_date_days); 162 pos += 3; 163 break; 164 165 case BAS_CHARACTERISTIC_INDEX_BATTERY_CRITCAL_STATUS: 166 event[pos++] = service->battery_critcal_status_flags; 167 break; 168 169 case BAS_CHARACTERISTIC_INDEX_BATTERY_ENERGY_STATUS: 170 if (service->energy_status == NULL){ 171 return 0; 172 } 173 event[pos++] = service->energy_status->flags; 174 if ((service->energy_status->flags & BATTERY_ENERGY_STATUS_BITMASK_EXTERNAL_SOURCE_POWER_PRESENT) > 0u){ 175 little_endian_store_16(event, pos, service->energy_status->external_source_power_medfloat16); 176 pos += 2; 177 } 178 if ((service->energy_status->flags & BATTERY_ENERGY_STATUS_BITMASK_PRESENT_VOLTAGE_PRESENT) > 0u){ 179 little_endian_store_16(event, pos, service->energy_status->present_voltage_medfloat16); 180 pos += 2; 181 } 182 if ((service->energy_status->flags & BATTERY_ENERGY_STATUS_BITMASK_AVAILABLE_ENERGY_PRESENT) > 0u){ 183 little_endian_store_16(event, pos, service->energy_status->available_energy_medfloat16); 184 pos += 2; 185 } 186 if ((service->energy_status->flags & BATTERY_ENERGY_STATUS_BITMASK_AVAILABLE_BATTERY_CAPACITY_PRESENT) > 0u){ 187 little_endian_store_16(event, pos, service->energy_status->available_battery_capacity_medfloat16); 188 pos += 2; 189 } 190 if ((service->energy_status->flags & BATTERY_ENERGY_STATUS_BITMASK_CHARGE_RATE_PRESENT) > 0u){ 191 little_endian_store_16(event, pos, service->energy_status->charge_rate_medfloat16); 192 pos += 2; 193 } 194 if ((service->energy_status->flags & BATTERY_ENERGY_STATUS_BITMASK_AVAILABLE_ENERGY_AT_LAST_CHARGE_PRESENT) > 0u){ 195 little_endian_store_16(event, pos, service->energy_status->available_energy_at_last_charge_medfloat16); 196 pos += 2; 197 } 198 break; 199 200 case BAS_CHARACTERISTIC_INDEX_BATTERY_TIME_STATUS: 201 if (service->time_status == NULL){ 202 return 0; 203 } 204 event[pos++] = service->time_status->flags; 205 little_endian_store_24(event, pos, service->time_status->time_until_discharged_minutes); 206 pos += 3; 207 if ((service->time_status->flags & BATTERY_TIME_STATUS_BITMASK_TIME_UNTIL_DISCHARGED_ON_STANDBY_PRESENT) > 0u){ 208 little_endian_store_24(event, pos, service->time_status->time_until_discharged_on_standby_minutes); 209 pos += 3; 210 } 211 if ((service->time_status->flags & BATTERY_TIME_STATUS_BITMASK_TIME_UNTIL_RECHARGED_PRESENT) > 0u){ 212 little_endian_store_24(event, pos, service->time_status->time_until_recharged_minutes); 213 pos += 3; 214 } 215 break; 216 217 case BAS_CHARACTERISTIC_INDEX_BATTERY_HEALTH_STATUS: 218 if (service->health_status == NULL){ 219 return 0; 220 } 221 event[pos++] = service->health_status->flags; 222 if ((service->health_status->flags & BATTERY_HEALTH_STATUS_BITMASK_HEALTH_SUMMARY_PRESENT) > 0u){ 223 event[pos++] = service->health_status->summary; 224 } 225 if ((service->health_status->flags & BATTERY_HEALTH_STATUS_BITMASK_CYCLE_COUNT_PRESENT) > 0u){ 226 little_endian_store_16(event, pos, service->health_status->cycle_count); 227 pos += 2; 228 } 229 if ((service->health_status->flags & BATTERY_HEALTH_STATUS_BITMASK_CURRENT_TEMPERATURE_PRESENT) > 0u){ 230 event[pos++] = service->health_status->current_temperature_degree_celsius; 231 } 232 if ((service->health_status->flags & BATTERY_HEALTH_STATUS_BITMASK_DEEP_DISCHARGE_COUNT_PRESENT) > 0u){ 233 little_endian_store_16(event, pos, service->health_status->deep_discharge_count); 234 pos += 2; 235 } 236 break; 237 238 case BAS_CHARACTERISTIC_INDEX_BATTERY_HEALTH_INFORMATION: 239 if (service->health_information == NULL){ 240 return 0; 241 } 242 event[pos++] = service->health_information->flags; 243 if ((service->health_information->flags & BATTERY_HEALTH_INFORMATION_BITMASK_CYCLE_COUNT_DESIGNED_LIFETIME_PRESENT) > 0u){ 244 little_endian_store_16(event, pos, service->health_information->cycle_count_designed_lifetime); 245 pos += 2; 246 } 247 if ((service->health_information->flags & BATTERY_HEALTH_INFORMATION_BITMASK_DESIGNED_OPERATING_TEMPERATURE_PRESENT) > 0u){ 248 event[pos++] = service->health_information->min_designed_operating_temperature_degree_celsius; 249 } 250 if ((service->health_information->flags & BATTERY_HEALTH_INFORMATION_BITMASK_DESIGNED_OPERATING_TEMPERATURE_PRESENT) > 0u){ 251 event[pos++] = service->health_information->max_designed_operating_temperature_degree_celsius; 252 } 253 break; 254 255 case BAS_CHARACTERISTIC_INDEX_BATTERY_INFORMATION: 256 if (service->information == NULL){ 257 return 0; 258 } 259 little_endian_store_16(event, pos, service->information->flags); 260 pos += 2; 261 event[pos++] = service->information->features; 262 if ((service->information->flags & BATTERY_INFORMATION_BITMASK_MANUFACTURE_DATE_PRESENT) > 0u){ 263 little_endian_store_24(event, pos, service->information->manufacture_date_days); 264 pos += 3; 265 } 266 if ((service->information->flags & BATTERY_INFORMATION_BITMASK_EXPIRATION_DATE_PRESENT) > 0u){ 267 little_endian_store_24(event, pos, service->information->expiration_date_days); 268 pos += 3; 269 } 270 if ((service->information->flags & BATTERY_INFORMATION_BITMASK_DESIGNED_CAPACITY_PRESENT) > 0u){ 271 little_endian_store_16(event, pos, service->information->designed_capacity_kWh_medfloat16); 272 pos += 2; 273 } 274 if ((service->information->flags & BATTERY_INFORMATION_BITMASK_LOW_ENERGY_PRESENT) > 0u){ 275 little_endian_store_16(event, pos, service->information->low_energy_kWh_medfloat16); 276 pos += 2; 277 } 278 if ((service->information->flags & BATTERY_INFORMATION_BITMASK_CRITICAL_ENERGY_PRESENT) > 0u){ 279 little_endian_store_16(event, pos, service->information->critical_energy_kWh_medfloat16); 280 pos += 2; 281 } 282 if ((service->information->flags & BATTERY_INFORMATION_BITMASK_CHEMISTRY_PRESENT) > 0u){ 283 event[pos++] = service->information->chemistry; 284 } 285 if ((service->information->flags & BATTERY_INFORMATION_BITMASK_NOMINAL_VOLTAGE_PRESENT) > 0u){ 286 little_endian_store_16(event, pos, service->information->nominal_voltage_medfloat16); 287 pos += 2; 288 } 289 if ((service->information->flags & BATTERY_INFORMATION_BITMASK_AGGREGATION_GROUP_PRESENT) > 0u){ 290 event[pos++] = service->information->aggregation_group; 291 } 292 break; 293 default: 294 break; 295 } 296 return pos; 297 } 298 299 static uint16_t battery_service_read_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size){ 300 UNUSED(con_handle); 301 302 battery_service_v1_t * service = battery_service_service_for_attribute_handle(attribute_handle); 303 if (service == NULL){ 304 return 0; 305 } 306 307 battery_service_v1_server_connection_t * connection = battery_service_server_connection_for_con_handle(service, con_handle); 308 if (connection == NULL){ 309 connection = battery_service_server_add_connection_for_con_handle(service, con_handle); 310 if (connection == NULL){ 311 return 0; 312 } 313 } 314 315 uint8_t index; 316 uint8_t event[18]; 317 uint8_t pos = 0; 318 319 for (index = 0; index < (uint8_t) BAS_CHARACTERISTIC_INDEX_NUM; index++){ 320 if (attribute_handle != service->characteristics[index].value_handle){ 321 continue; 322 } 323 324 switch ((bas_characteristic_index_t) index){ 325 case BAS_CHARACTERISTIC_INDEX_MANUFACTURER_NAME_STRING: 326 if (service->manufacturer_name == NULL){ 327 return 0; 328 } 329 return att_read_callback_handle_blob((uint8_t *)service->manufacturer_name, strlen(service->manufacturer_name), offset, buffer, buffer_size); 330 331 case BAS_CHARACTERISTIC_INDEX_MODEL_NUMBER_STRING: 332 if (service->model_number == NULL){ 333 return 0; 334 } 335 return att_read_callback_handle_blob((uint8_t *)service->model_number, strlen(service->model_number), offset, buffer, buffer_size); 336 337 case BAS_CHARACTERISTIC_INDEX_SERIAL_NUMBER_STRING: 338 if (service->serial_number == NULL){ 339 return 0; 340 } 341 return att_read_callback_handle_blob((uint8_t *)service->serial_number, strlen(service->serial_number), offset, buffer, buffer_size); 342 343 default: 344 pos = bas_serialize_characteristic(service, index, event, sizeof(event)); 345 if (pos == 1u){ 346 return att_read_callback_handle_byte(event[0], offset, buffer, buffer_size); 347 } 348 if (pos > 1u){ 349 return att_read_callback_handle_blob(event, pos, offset, buffer, buffer_size); 350 } 351 return 0; 352 } 353 } 354 355 for (index = 0; index < (uint8_t) BAS_CHARACTERISTIC_INDEX_NUM; index++){ 356 if (attribute_handle != service->characteristics[index].client_configuration_handle){ 357 continue; 358 } 359 return att_read_callback_handle_little_endian_16(connection->configurations[index], offset, buffer, buffer_size); 360 } 361 return 0; 362 } 363 364 static int battery_service_write_callback(hci_con_handle_t con_handle, uint16_t attribute_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size){ 365 UNUSED(offset); 366 UNUSED(buffer_size); 367 368 if (transaction_mode != ATT_TRANSACTION_MODE_NONE){ 369 return 0; 370 } 371 372 battery_service_v1_t * service = battery_service_service_for_attribute_handle(attribute_handle); 373 if (service == NULL){ 374 return 0; 375 } 376 377 battery_service_v1_server_connection_t * connection = battery_service_server_connection_for_con_handle(service, con_handle); 378 if (connection == NULL){ 379 connection = battery_service_server_add_connection_for_con_handle(service, con_handle); 380 if (connection == NULL){ 381 return 0; 382 } 383 } 384 385 uint8_t index; 386 for (index = 0; index < (uint8_t) BAS_CHARACTERISTIC_INDEX_NUM; index++){ 387 if (attribute_handle != service->characteristics[index].client_configuration_handle){ 388 continue; 389 } 390 connection->configurations[index] = little_endian_read_16(buffer, 0); 391 } 392 return 0; 393 } 394 395 static void battery_service_can_send_now(void * context){ 396 battery_service_v1_server_connection_t * connection = (battery_service_v1_server_connection_t *) context; 397 if (connection == NULL){ 398 return; 399 } 400 battery_service_v1_t * service = connection->service; 401 if (service == NULL){ 402 return; 403 } 404 405 if ( (connection->scheduled_tasks & BATTERY_SERVICE_TASK_BATTERY_VALUE_CHANGED) > 0u){ 406 connection->scheduled_tasks &= ~BATTERY_SERVICE_TASK_BATTERY_VALUE_CHANGED; 407 att_server_notify(connection->con_handle, service->characteristics[BAS_CHARACTERISTIC_INDEX_BATTERY_LEVEL].value_handle, &service->battery_value, 1); 408 } 409 410 if (connection->scheduled_tasks > 0u){ 411 att_server_register_can_send_now_callback(&connection->scheduled_tasks_callback, connection->con_handle); 412 } 413 } 414 415 void battery_service_v1_server_init(void){ 416 417 } 418 419 void battery_service_v1_server_register(battery_service_v1_t *service, battery_service_v1_server_connection_t *connections, uint8_t connection_max_num){ 420 btstack_assert(service != NULL); 421 btstack_assert(connections != NULL); 422 btstack_assert(connection_max_num > 0u); 423 424 // get service handle range 425 uint16_t end_handle = 0xffff; 426 uint16_t start_handle = 0; 427 428 btstack_linked_list_iterator_t it; 429 btstack_linked_list_iterator_init(&it, &battery_services); 430 while (btstack_linked_list_iterator_has_next(&it)){ 431 battery_service_v1_t * service = (battery_service_v1_t*) btstack_linked_list_iterator_next(&it); 432 if (service->end_handle > start_handle){ 433 start_handle = service->end_handle + 1; 434 } 435 } 436 437 int service_found = gatt_server_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_BATTERY_SERVICE, &start_handle, &end_handle); 438 btstack_assert(service_found != 0); 439 UNUSED(service_found); 440 441 service->start_handle = start_handle; 442 service->end_handle = end_handle; 443 444 uint8_t i; 445 for (i = 0; i < (uint8_t) BAS_CHARACTERISTIC_INDEX_NUM; i++){ 446 // get characteristic value handle and client configuration handle 447 service->characteristics[i].value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, bas_uuid16s[i]); 448 service->characteristics[i].client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, bas_uuid16s[i]); 449 } 450 451 memset(connections, 0, sizeof(battery_service_v1_server_connection_t) * connection_max_num); 452 for (i = 0; i < connection_max_num; i++){ 453 connections[i].con_handle = HCI_CON_HANDLE_INVALID; 454 } 455 service->connections_max_num = connection_max_num; 456 service->connections = connections; 457 458 service->service_handler.read_callback = &battery_service_read_callback; 459 service->service_handler.write_callback = &battery_service_write_callback; 460 att_server_register_service_handler(&service->service_handler); 461 462 log_info("Found Battery Service 0x%02x-0x%02x", start_handle, end_handle); 463 464 btstack_linked_list_add(&battery_services, (btstack_linked_item_t *) service); 465 } 466 467 468 static void battery_service_set_callback(battery_service_v1_server_connection_t * connection, uint8_t task){ 469 if (connection->con_handle == HCI_CON_HANDLE_INVALID){ 470 connection->scheduled_tasks = 0; 471 return; 472 } 473 474 uint8_t scheduled_tasks = connection->scheduled_tasks; 475 connection->scheduled_tasks |= task; 476 if (scheduled_tasks == 0){ 477 connection->scheduled_tasks_callback.callback = &battery_service_can_send_now; 478 connection->scheduled_tasks_callback.context = (void*) connection; 479 att_server_register_can_send_now_callback(&connection->scheduled_tasks_callback, connection->con_handle); 480 } 481 } 482 483 484 void battery_service_v1_server_set_battery_value(battery_service_v1_t * service, uint8_t value){ 485 btstack_assert(service != NULL); 486 if (service->battery_value == value){ 487 return; 488 } 489 490 service->battery_value = value; 491 492 uint8_t i; 493 for (i = 0; i < service->connections_max_num; i++){ 494 battery_service_v1_server_connection_t * connection = &service->connections[i]; 495 if (connection->configurations[BAS_CHARACTERISTIC_INDEX_BATTERY_LEVEL] != 0){ 496 battery_service_set_callback(connection, BATTERY_SERVICE_TASK_BATTERY_VALUE_CHANGED); 497 } 498 } 499 } 500 501 void battery_service_v1_server_deregister(battery_service_v1_t *service){ 502 btstack_linked_list_remove(&battery_services, (btstack_linked_item_t * )service); 503 // TODO cansel can send now 504 } 505 506 void battery_service_v1_server_deinit(void){ 507 // deregister listeners 508 // empty list 509 btstack_linked_list_iterator_t it; 510 btstack_linked_list_iterator_init(&it, &battery_services); 511 while (btstack_linked_list_iterator_has_next(&it)){ 512 battery_service_v1_t * service = (battery_service_v1_t*) btstack_linked_list_iterator_next(&it); 513 battery_service_v1_server_deregister(service); 514 } 515 } 516