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 135 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){ 136 UNUSED(con_handle); 137 138 battery_service_v1_t * service = battery_service_service_for_attribute_handle(attribute_handle); 139 if (service == NULL){ 140 return 0; 141 } 142 143 battery_service_v1_server_connection_t * connection = battery_service_server_connection_for_con_handle(service, con_handle); 144 if (connection == NULL){ 145 connection = battery_service_server_add_connection_for_con_handle(service, con_handle); 146 if (connection == NULL){ 147 return 0; 148 } 149 } 150 151 uint8_t index; 152 uint8_t event[18]; 153 uint8_t pos = 0; 154 155 for (index = 0; index < (uint8_t) BAS_CHARACTERISTIC_INDEX_NUM; index++){ 156 if (attribute_handle != service->characteristics[index].value_handle){ 157 continue; 158 } 159 160 161 switch ((bas_characteristic_index_t) index){ 162 case BAS_CHARACTERISTIC_INDEX_BATTERY_LEVEL: 163 return att_read_callback_handle_byte(service->battery_value, offset, buffer, buffer_size); 164 165 case BAS_CHARACTERISTIC_INDEX_BATTERY_LEVEL_STATUS: 166 event[pos++] = service->battery_level.flags; 167 little_endian_store_16(event, pos, service->battery_level.power_state_flags); 168 pos += 2; 169 if ((service->battery_level.flags & BATTERY_LEVEL_STATUS_BITMASK_IDENTIFIER_PRESENT) > 0u){ 170 little_endian_store_16(event, pos, service->battery_level.identifier); 171 pos += 2; 172 } 173 if ((service->battery_level.flags & BATTERY_LEVEL_STATUS_BITMASK_BATTERY_LEVEL_PRESENT) > 0u){ 174 event[pos++] = service->battery_level.battery_level; 175 } 176 if ((service->battery_level.flags & BATTERY_LEVEL_STATUS_BITMASK_ADDITIONAL_STATUS_PRESENT) > 0u){ 177 event[pos++] = service->battery_level.additional_status_flags; 178 } 179 return att_read_callback_handle_blob(event, pos, offset, buffer, buffer_size); 180 181 case BAS_CHARACTERISTIC_INDEX_ESTIMATED_SERVICE_DATE: 182 little_endian_store_24(event, pos, service->estimated_service_date_days); 183 pos += 3; 184 return att_read_callback_handle_blob(event, pos, offset, buffer, buffer_size); 185 186 case BAS_CHARACTERISTIC_INDEX_BATTERY_CRITCAL_STATUS: 187 return att_read_callback_handle_byte(service->battery_critcal_status_flags, offset, buffer, buffer_size); 188 189 case BAS_CHARACTERISTIC_INDEX_BATTERY_ENERGY_STATUS: 190 event[pos++] = service->energy_status.flags; 191 if ((service->energy_status.flags & BATTERY_ENERGY_STATUS_BITMASK_EXTERNAL_SOURCE_POWER_PRESENT) > 0u){ 192 little_endian_store_16(event, pos, service->energy_status.external_source_power_medfloat16); 193 pos += 2; 194 } 195 if ((service->energy_status.flags & BATTERY_ENERGY_STATUS_BITMASK_PRESENT_VOLTAGE_PRESENT) > 0u){ 196 little_endian_store_16(event, pos, service->energy_status.present_voltage_medfloat16); 197 pos += 2; 198 } 199 if ((service->energy_status.flags & BATTERY_ENERGY_STATUS_BITMASK_AVAILABLE_ENERGY_PRESENT) > 0u){ 200 little_endian_store_16(event, pos, service->energy_status.available_energy_medfloat16); 201 pos += 2; 202 } 203 if ((service->energy_status.flags & BATTERY_ENERGY_STATUS_BITMASK_AVAILABLE_BATTERY_CAPACITY_PRESENT) > 0u){ 204 little_endian_store_16(event, pos, service->energy_status.available_battery_capacity_medfloat16); 205 pos += 2; 206 } 207 if ((service->energy_status.flags & BATTERY_ENERGY_STATUS_BITMASK_CHARGE_RATE_PRESENT) > 0u){ 208 little_endian_store_16(event, pos, service->energy_status.charge_rate_medfloat16); 209 pos += 2; 210 } 211 if ((service->energy_status.flags & BATTERY_ENERGY_STATUS_BITMASK_AVAILABLE_ENERGY_AT_LAST_CHARGE_PRESENT) > 0u){ 212 little_endian_store_16(event, pos, service->energy_status.available_energy_at_last_charge_medfloat16); 213 pos += 2; 214 } 215 return att_read_callback_handle_blob(event, pos, offset, buffer, buffer_size); 216 217 case BAS_CHARACTERISTIC_INDEX_BATTERY_TIME_STATUS: 218 event[pos++] = service->time_status.flags; 219 little_endian_store_24(event, pos, service->time_status.time_until_discharged_minutes); 220 pos += 3; 221 if ((service->time_status.flags & BATTERY_TIME_STATUS_BITMASK_TIME_UNTIL_DISCHARGED_ON_STANDBY_PRESENT) > 0u){ 222 little_endian_store_24(event, pos, service->time_status.time_until_discharged_on_standby_minutes); 223 pos += 3; 224 } 225 if ((service->time_status.flags & BATTERY_TIME_STATUS_BITMASK_TIME_UNTIL_RECHARGED_PRESENT) > 0u){ 226 little_endian_store_24(event, pos, service->time_status.time_until_recharged_minutes); 227 pos += 3; 228 } 229 return att_read_callback_handle_blob(event, pos, offset, buffer, buffer_size); 230 231 case BAS_CHARACTERISTIC_INDEX_BATTERY_HEALTH_STATUS: 232 event[pos++] = service->health_status.flags; 233 if ((service->health_status.flags & BATTERY_HEALTH_STATUS_BITMASK_HEALTH_SUMMARY_PRESENT) > 0u){ 234 event[pos++] = service->health_status.summary; 235 } 236 if ((service->health_status.flags & BATTERY_HEALTH_STATUS_BITMASK_CYCLE_COUNT_PRESENT) > 0u){ 237 little_endian_store_16(event, pos, service->health_status.cycle_count); 238 pos += 2; 239 } 240 if ((service->health_status.flags & BATTERY_HEALTH_STATUS_BITMASK_CURRENT_TEMPERATURE_PRESENT) > 0u){ 241 event[pos++] = service->health_status.current_temperature_degree_celsius; 242 } 243 if ((service->health_status.flags & BATTERY_HEALTH_STATUS_BITMASK_DEEP_DISCHARGE_COUNT_PRESENT) > 0u){ 244 little_endian_store_16(event, pos, service->health_status.deep_discharge_count); 245 pos += 2; 246 } 247 return att_read_callback_handle_blob(event, pos, offset, buffer, buffer_size); 248 249 case BAS_CHARACTERISTIC_INDEX_BATTERY_HEALTH_INFORMATION: 250 event[pos++] = service->health_information.flags; 251 if ((service->health_information.flags & BATTERY_HEALTH_INFORMATION_BITMASK_CYCLE_COUNT_DESIGNED_LIFETIME_PRESENT) > 0u){ 252 little_endian_store_16(event, pos, service->health_information.cycle_count_designed_lifetime); 253 pos += 2; 254 } 255 if ((service->health_information.flags & BATTERY_HEALTH_INFORMATION_BITMASK_DESIGNED_OPERATING_TEMPERATURE_PRESENT) > 0u){ 256 event[pos++] = service->health_information.min_designed_operating_temperature_degree_celsius; 257 } 258 if ((service->health_information.flags & BATTERY_HEALTH_INFORMATION_BITMASK_DESIGNED_OPERATING_TEMPERATURE_PRESENT) > 0u){ 259 event[pos++] = service->health_information.max_designed_operating_temperature_degree_celsius; 260 } 261 return att_read_callback_handle_blob(event, pos, offset, buffer, buffer_size); 262 263 case BAS_CHARACTERISTIC_INDEX_BATTERY_INFORMATION: 264 little_endian_store_16(event, pos, service->information.flags); 265 pos += 2; 266 event[pos++] = service->information.features; 267 if ((service->information.flags & BATTERY_INFORMATION_BITMASK_MANUFACTURE_DATE_PRESENT) > 0u){ 268 little_endian_store_24(event, pos, service->information.manufacture_date_days); 269 pos += 3; 270 } 271 if ((service->information.flags & BATTERY_INFORMATION_BITMASK_EXPIRATION_DATE_PRESENT) > 0u){ 272 little_endian_store_24(event, pos, service->information.expiration_date_days); 273 pos += 3; 274 } 275 if ((service->information.flags & BATTERY_INFORMATION_BITMASK_DESIGNED_CAPACITY_PRESENT) > 0u){ 276 little_endian_store_16(event, pos, service->information.designed_capacity_kWh_medfloat16); 277 pos += 2; 278 } 279 if ((service->information.flags & BATTERY_INFORMATION_BITMASK_LOW_ENERGY_PRESENT) > 0u){ 280 little_endian_store_16(event, pos, service->information.low_energy_kWh_medfloat16); 281 pos += 2; 282 } 283 if ((service->information.flags & BATTERY_INFORMATION_BITMASK_CRITICAL_ENERGY_PRESENT) > 0u){ 284 little_endian_store_16(event, pos, service->information.critical_energy_kWh_medfloat16); 285 pos += 2; 286 } 287 if ((service->information.flags & BATTERY_INFORMATION_BITMASK_CHEMISTRY_PRESENT) > 0u){ 288 event[pos++] = service->information.chemistry; 289 } 290 if ((service->information.flags & BATTERY_INFORMATION_BITMASK_NOMINAL_VOLTAGE_PRESENT) > 0u){ 291 little_endian_store_16(event, pos, service->information.nominal_voltage_medfloat16); 292 pos += 2; 293 } 294 if ((service->information.flags & BATTERY_INFORMATION_BITMASK_AGGREGATION_GROUP_PRESENT) > 0u){ 295 event[pos++] = service->information.aggregation_group; 296 } 297 return att_read_callback_handle_blob(event, pos, offset, buffer, buffer_size); 298 299 case BAS_CHARACTERISTIC_INDEX_MANUFACTURER_NAME_STRING: 300 return att_read_callback_handle_blob(service->manufacturer_name, service->manufacturer_name_len, offset, buffer, buffer_size); 301 302 case BAS_CHARACTERISTIC_INDEX_MODEL_NUMBER_STRING: 303 return att_read_callback_handle_blob(service->model_number, service->model_number_len, offset, buffer, buffer_size); 304 305 case BAS_CHARACTERISTIC_INDEX_SERIAL_NUMBER_STRING: 306 return att_read_callback_handle_blob(service->serial_number, service->serial_number_len, offset, buffer, buffer_size); 307 308 default: 309 return 0; 310 } 311 } 312 313 for (index = 0; index < (uint8_t) BAS_CHARACTERISTIC_INDEX_NUM; index++){ 314 if (attribute_handle != service->characteristics[index].client_configuration_handle){ 315 continue; 316 } 317 return att_read_callback_handle_little_endian_16(connection->configurations[index], offset, buffer, buffer_size); 318 } 319 return 0; 320 } 321 322 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){ 323 UNUSED(offset); 324 UNUSED(buffer_size); 325 326 if (transaction_mode != ATT_TRANSACTION_MODE_NONE){ 327 return 0; 328 } 329 330 battery_service_v1_t * service = battery_service_service_for_attribute_handle(attribute_handle); 331 if (service == NULL){ 332 return 0; 333 } 334 335 battery_service_v1_server_connection_t * connection = battery_service_server_connection_for_con_handle(service, con_handle); 336 if (connection == NULL){ 337 connection = battery_service_server_add_connection_for_con_handle(service, con_handle); 338 if (connection == NULL){ 339 return 0; 340 } 341 } 342 343 uint8_t index; 344 for (index = 0; index < (uint8_t) BAS_CHARACTERISTIC_INDEX_NUM; index++){ 345 if (attribute_handle != service->characteristics[index].client_configuration_handle){ 346 continue; 347 } 348 connection->configurations[index] = little_endian_read_16(buffer, 0); 349 } 350 return 0; 351 } 352 353 static void battery_service_can_send_now(void * context){ 354 battery_service_v1_server_connection_t * connection = (battery_service_v1_server_connection_t *) context; 355 if (connection == NULL){ 356 return; 357 } 358 battery_service_v1_t * service = connection->service; 359 if (service == NULL){ 360 return; 361 } 362 363 if ( (connection->scheduled_tasks & BATTERY_SERVICE_TASK_BATTERY_VALUE_CHANGED) > 0u){ 364 connection->scheduled_tasks &= ~BATTERY_SERVICE_TASK_BATTERY_VALUE_CHANGED; 365 att_server_notify(connection->con_handle, service->characteristics[BAS_CHARACTERISTIC_INDEX_BATTERY_LEVEL].value_handle, &service->battery_value, 1); 366 } 367 368 if (connection->scheduled_tasks > 0u){ 369 att_server_register_can_send_now_callback(&connection->scheduled_tasks_callback, connection->con_handle); 370 } 371 } 372 373 void battery_service_v1_server_init(void){ 374 375 } 376 377 void battery_service_v1_server_register(battery_service_v1_t *service, battery_service_v1_server_connection_t *connections, uint8_t connection_max_num){ 378 btstack_assert(service != NULL); 379 btstack_assert(connections != NULL); 380 btstack_assert(connection_max_num > 0u); 381 382 // get service handle range 383 uint16_t end_handle = 0xffff; 384 uint16_t start_handle = 0; 385 386 btstack_linked_list_iterator_t it; 387 btstack_linked_list_iterator_init(&it, &battery_services); 388 while (btstack_linked_list_iterator_has_next(&it)){ 389 battery_service_v1_t * service = (battery_service_v1_t*) btstack_linked_list_iterator_next(&it); 390 if (service->end_handle > start_handle){ 391 start_handle = service->end_handle + 1; 392 } 393 } 394 395 int service_found = gatt_server_get_handle_range_for_service_with_uuid16(ORG_BLUETOOTH_SERVICE_BATTERY_SERVICE, &start_handle, &end_handle); 396 btstack_assert(service_found != 0); 397 UNUSED(service_found); 398 399 service->start_handle = start_handle; 400 service->end_handle = end_handle; 401 402 uint8_t i; 403 for (i = 0; i < (uint8_t) BAS_CHARACTERISTIC_INDEX_NUM; i++){ 404 // get characteristic value handle and client configuration handle 405 service->characteristics[i].value_handle = gatt_server_get_value_handle_for_characteristic_with_uuid16(start_handle, end_handle, bas_uuid16s[i]); 406 service->characteristics[i].client_configuration_handle = gatt_server_get_client_configuration_handle_for_characteristic_with_uuid16(start_handle, end_handle, bas_uuid16s[i]); 407 } 408 409 memset(connections, 0, sizeof(battery_service_v1_server_connection_t) * connection_max_num); 410 for (i = 0; i < connection_max_num; i++){ 411 connections[i].con_handle = HCI_CON_HANDLE_INVALID; 412 } 413 service->connections_max_num = connection_max_num; 414 service->connections = connections; 415 416 service->service_handler.read_callback = &battery_service_read_callback; 417 service->service_handler.write_callback = &battery_service_write_callback; 418 att_server_register_service_handler(&service->service_handler); 419 420 log_info("Found Battery Service 0x%02x-0x%02x", start_handle, end_handle); 421 422 btstack_linked_list_add(&battery_services, (btstack_linked_item_t *) service); 423 } 424 425 426 static void battery_service_set_callback(battery_service_v1_server_connection_t * connection, uint8_t task){ 427 if (connection->con_handle == HCI_CON_HANDLE_INVALID){ 428 connection->scheduled_tasks = 0; 429 return; 430 } 431 432 uint8_t scheduled_tasks = connection->scheduled_tasks; 433 connection->scheduled_tasks |= task; 434 if (scheduled_tasks == 0){ 435 connection->scheduled_tasks_callback.callback = &battery_service_can_send_now; 436 connection->scheduled_tasks_callback.context = (void*) connection; 437 att_server_register_can_send_now_callback(&connection->scheduled_tasks_callback, connection->con_handle); 438 } 439 } 440 441 442 void battery_service_v1_server_set_battery_value(battery_service_v1_t * service, uint8_t value){ 443 btstack_assert(service != NULL); 444 if (service->battery_value == value){ 445 return; 446 } 447 448 service->battery_value = value; 449 450 uint8_t i; 451 for (i = 0; i < service->connections_max_num; i++){ 452 battery_service_v1_server_connection_t * connection = &service->connections[i]; 453 if (connection->configurations[BAS_CHARACTERISTIC_INDEX_BATTERY_LEVEL] != 0){ 454 battery_service_set_callback(connection, BATTERY_SERVICE_TASK_BATTERY_VALUE_CHANGED); 455 } 456 } 457 } 458 459 void battery_service_v1_server_deregister(battery_service_v1_t *service){ 460 btstack_linked_list_remove(&battery_services, (btstack_linked_item_t * )service); 461 // TODO cansel can send now 462 } 463 464 void battery_service_v1_server_deinit(void){ 465 // deregister listeners 466 // empty list 467 btstack_linked_list_iterator_t it; 468 btstack_linked_list_iterator_init(&it, &battery_services); 469 while (btstack_linked_list_iterator_has_next(&it)){ 470 battery_service_v1_t * service = (battery_service_v1_t*) btstack_linked_list_iterator_next(&it); 471 battery_service_v1_server_deregister(service); 472 } 473 } 474