xref: /btstack/src/ble/gatt-service/immediate_alert_service_client.c (revision 64b4329f5bf32b92c612ea7ea6267ac254f10927)
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