1 /* 2 * Copyright (C) 2024 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__ "immediate_alert_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 #include "ble/gatt_service_client.h" 51 #include "ble/gatt-service/immediate_alert_service_client.h" 52 53 #include "bluetooth_gatt.h" 54 #include "btstack_debug.h" 55 #include "btstack_event.h" 56 57 // IAS Client 58 static gatt_service_client_t ias_client; 59 static btstack_linked_list_t ias_connections; 60 61 static btstack_context_callback_registration_t ias_client_handle_can_send_now; 62 63 static void ias_client_packet_handler_internal(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); 64 static void ias_client_run_for_connection(void * context); 65 66 // list of uuids 67 static const uint16_t ias_uuid16s[IMMEDIATE_ALERT_SERVICE_CLIENT_NUM_CHARACTERISTICS] = { 68 ORG_BLUETOOTH_CHARACTERISTIC_ALERT_LEVEL 69 }; 70 71 typedef enum { 72 IAS_CLIENT_CHARACTERISTIC_INDEX_ALERT_LEVEL = 0, 73 IAS_CLIENT_CHARACTERISTIC_INDEX_RFU 74 } ias_client_characteristic_index_t; 75 76 #ifdef ENABLE_TESTING_SUPPORT 77 static char * ias_client_characteristic_name[] = { 78 "ALERT_LEVEL", 79 "RFU" 80 }; 81 #endif 82 83 static ias_client_connection_t * ias_client_get_connection_for_cid(uint16_t connection_cid){ 84 btstack_linked_list_iterator_t it; 85 btstack_linked_list_iterator_init(&it, &ias_connections); 86 while (btstack_linked_list_iterator_has_next(&it)){ 87 ias_client_connection_t * connection = (ias_client_connection_t *)btstack_linked_list_iterator_next(&it); 88 if (gatt_service_client_get_connection_id(&connection->basic_connection) == connection_cid) { 89 return connection; 90 } 91 } 92 return NULL; 93 } 94 95 static void ias_client_add_connection(ias_client_connection_t * connection){ 96 btstack_linked_list_add(&ias_connections, (btstack_linked_item_t*) connection); 97 } 98 99 static void ias_client_finalize_connection(ias_client_connection_t * connection){ 100 btstack_linked_list_remove(&ias_connections, (btstack_linked_item_t*) connection); 101 } 102 103 static void ias_client_replace_subevent_id_and_emit(btstack_packet_handler_t callback, uint8_t * packet, uint16_t size, uint8_t subevent_id){ 104 UNUSED(size); 105 btstack_assert(callback != NULL); 106 // execute callback 107 packet[2] = subevent_id; 108 (*callback)(HCI_EVENT_PACKET, 0, packet, size); 109 } 110 111 static void ias_client_connected(ias_client_connection_t * connection, uint8_t status, uint8_t * packet, uint16_t size) { 112 if (status == ERROR_CODE_SUCCESS){ 113 connection->state = IMMEDIATE_ALERT_SERVICE_CLIENT_STATE_READY; 114 } else { 115 connection->state = IMMEDIATE_ALERT_SERVICE_CLIENT_STATE_IDLE; 116 } 117 ias_client_replace_subevent_id_and_emit(connection->packet_handler, packet, size, 118 GATTSERVICE_SUBEVENT_IAS_CLIENT_CONNECTED); 119 } 120 121 122 static uint16_t ias_client_value_handle_for_index(ias_client_connection_t * connection){ 123 return connection->basic_connection.characteristics[connection->characteristic_index].value_handle; 124 } 125 126 static uint8_t ias_client_can_query_characteristic(ias_client_connection_t * connection, ias_client_characteristic_index_t characteristic_index){ 127 uint8_t status = gatt_service_client_can_query_characteristic(&connection->basic_connection, 128 (uint8_t) characteristic_index); 129 if (status != ERROR_CODE_SUCCESS){ 130 return status; 131 } 132 return connection->state == IMMEDIATE_ALERT_SERVICE_CLIENT_STATE_READY ? ERROR_CODE_SUCCESS : ERROR_CODE_CONTROLLER_BUSY; 133 } 134 135 static uint8_t ias_client_request_send_gatt_query(ias_client_connection_t * connection, ias_client_characteristic_index_t characteristic_index){ 136 connection->characteristic_index = characteristic_index; 137 138 ias_client_handle_can_send_now.context = (void *)(uintptr_t)connection->basic_connection.cid; 139 uint8_t status = gatt_client_request_to_send_gatt_query(&ias_client_handle_can_send_now, connection->basic_connection.con_handle); 140 if (status != ERROR_CODE_SUCCESS){ 141 connection->state = IMMEDIATE_ALERT_SERVICE_CLIENT_STATE_READY; 142 } 143 return status; 144 } 145 146 static uint8_t ias_client_request_write_without_response_characteristic(ias_client_connection_t * connection, ias_client_characteristic_index_t characteristic_index){ 147 connection->state = IMMEDIATE_ALERT_SERVICE_CLIENT_STATE_W2_WRITE_WITHOUT_RESPONSE_CHARACTERISTIC_VALUE; 148 return ias_client_request_send_gatt_query(connection, characteristic_index); 149 } 150 151 static void ias_client_packet_handler_internal(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 152 UNUSED(channel); 153 UNUSED(size); 154 155 if (packet_type != HCI_EVENT_PACKET) return; 156 ias_client_connection_t * connection; 157 uint16_t cid; 158 uint8_t status; 159 160 switch(hci_event_packet_get_type(packet)){ 161 case HCI_EVENT_GATTSERVICE_META: 162 switch (hci_event_gattservice_meta_get_subevent_code(packet)) { 163 case GATTSERVICE_SUBEVENT_CLIENT_CONNECTED: 164 cid = gattservice_subevent_client_connected_get_cid(packet); 165 connection = ias_client_get_connection_for_cid(cid); 166 btstack_assert(connection != NULL); 167 168 #ifdef ENABLE_TESTING_SUPPORT 169 { 170 uint8_t i; 171 for (i = IAS_CLIENT_CHARACTERISTIC_INDEX_ALERT_LEVEL; 172 i < IAS_CLIENT_CHARACTERISTIC_INDEX_RFU; i++) { 173 printf("0x%04X %s\n", connection->basic_connection.characteristics[i].value_handle, 174 ias_client_characteristic_name[i]); 175 } 176 }; 177 #endif 178 status = gattservice_subevent_client_connected_get_status(packet); 179 ias_client_connected(connection, status, packet, size); 180 break; 181 182 case GATTSERVICE_SUBEVENT_CLIENT_DISCONNECTED: 183 cid = gattservice_subevent_client_disconnected_get_cid(packet); 184 connection = ias_client_get_connection_for_cid(cid); 185 btstack_assert(connection != NULL); 186 ias_client_finalize_connection(connection); 187 ias_client_replace_subevent_id_and_emit(connection->packet_handler, 188 packet, size, 189 GATTSERVICE_SUBEVENT_IAS_CLIENT_DISCONNECTED); 190 break; 191 192 default: 193 break; 194 } 195 break; 196 197 default: 198 break; 199 } 200 } 201 202 static uint16_t ias_client_serialize_characteristic_value_for_write(ias_client_connection_t * connection, uint8_t ** out_value){ 203 uint16_t characteristic_uuid16 = gatt_service_client_characteristic_uuid16_for_index(&ias_client, 204 connection->characteristic_index); 205 *out_value = (uint8_t *)connection->write_buffer; 206 207 switch (characteristic_uuid16){ 208 case ORG_BLUETOOTH_CHARACTERISTIC_ALERT_LEVEL: 209 return 1; 210 default: 211 btstack_assert(false); 212 break; 213 } 214 return 0; 215 } 216 217 static void ias_client_run_for_connection(void * context){ 218 uint16_t connection_id = (uint16_t)(uintptr_t)context; 219 ias_client_connection_t * connection = ias_client_get_connection_for_cid(connection_id); 220 221 btstack_assert(connection != NULL); 222 uint16_t value_length; 223 uint8_t * value; 224 225 switch (connection->state){ 226 case IMMEDIATE_ALERT_SERVICE_CLIENT_STATE_W2_WRITE_WITHOUT_RESPONSE_CHARACTERISTIC_VALUE: 227 connection->state = IMMEDIATE_ALERT_SERVICE_CLIENT_STATE_READY; 228 229 value_length = ias_client_serialize_characteristic_value_for_write(connection, &value); 230 gatt_client_write_value_of_characteristic_without_response( 231 gatt_service_client_get_con_handle(&connection->basic_connection), 232 ias_client_value_handle_for_index(connection), 233 value_length, value); 234 235 break; 236 237 default: 238 break; 239 } 240 } 241 242 void immediate_alert_service_client_init(void){ 243 gatt_service_client_register_client(&ias_client, &ias_client_packet_handler_internal, ias_uuid16s, sizeof(ias_uuid16s)/sizeof(uint16_t)); 244 245 ias_client_handle_can_send_now.callback = &ias_client_run_for_connection; 246 } 247 248 uint8_t immediate_alert_service_client_connect(hci_con_handle_t con_handle, 249 btstack_packet_handler_t packet_handler, 250 ias_client_connection_t * ias_connection, 251 uint16_t * ias_cid 252 ){ 253 254 btstack_assert(packet_handler != NULL); 255 btstack_assert(ias_connection != NULL); 256 257 *ias_cid = 0; 258 259 ias_connection->state = IMMEDIATE_ALERT_SERVICE_CLIENT_STATE_W4_CONNECTION; 260 ias_connection->packet_handler = packet_handler; 261 262 uint8_t status = gatt_service_client_connect_primary_service_with_uuid16(con_handle, 263 &ias_client, 264 &ias_connection->basic_connection, 265 ORG_BLUETOOTH_SERVICE_IMMEDIATE_ALERT, 0, 266 ias_connection->characteristics_storage, 267 IMMEDIATE_ALERT_SERVICE_CLIENT_NUM_CHARACTERISTICS); 268 269 if (status == ERROR_CODE_SUCCESS){ 270 ias_client_add_connection(ias_connection); 271 *ias_cid = ias_connection->basic_connection.cid; 272 } 273 274 return status; 275 } 276 277 uint8_t immediate_alert_service_client_write_alert_level(uint16_t ias_cid, ias_alert_level_t alert_level){ 278 ias_client_connection_t * connection = ias_client_get_connection_for_cid(ias_cid); 279 if (connection == NULL){ 280 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 281 } 282 if (alert_level >= IAS_ALERT_LEVEL_RFU){ 283 return ERROR_CODE_PARAMETER_OUT_OF_MANDATORY_RANGE; 284 } 285 286 ias_client_characteristic_index_t index = IAS_CLIENT_CHARACTERISTIC_INDEX_ALERT_LEVEL; 287 288 uint8_t status = ias_client_can_query_characteristic(connection, index); 289 if (status != ERROR_CODE_SUCCESS){ 290 return status; 291 } 292 293 connection->write_buffer[0] = (uint8_t)alert_level; 294 return ias_client_request_write_without_response_characteristic(connection, index); 295 } 296 297 uint8_t immediate_alert_service_client_disconnect(uint16_t ias_cid){ 298 ias_client_connection_t * connection = ias_client_get_connection_for_cid(ias_cid); 299 if (connection == NULL){ 300 return ERROR_CODE_UNKNOWN_CONNECTION_IDENTIFIER; 301 } 302 return gatt_service_client_disconnect(&connection->basic_connection); 303 } 304 305 void immediate_alert_service_client_deinit(void){ 306 gatt_service_client_unregister_client(&ias_client); 307 } 308 309