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
ias_client_get_connection_for_cid(uint16_t connection_cid)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
ias_client_add_connection(ias_client_connection_t * connection)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
ias_client_finalize_connection(ias_client_connection_t * connection)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
ias_client_replace_subevent_id_and_emit(btstack_packet_handler_t callback,uint8_t * packet,uint16_t size,uint8_t subevent_id)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
ias_client_connected(ias_client_connection_t * connection,uint8_t status,uint8_t * packet,uint16_t size)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
ias_client_value_handle_for_index(ias_client_connection_t * connection)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
ias_client_can_query_characteristic(ias_client_connection_t * connection,ias_client_characteristic_index_t characteristic_index)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
ias_client_request_send_gatt_query(ias_client_connection_t * connection,ias_client_characteristic_index_t characteristic_index)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
ias_client_request_write_without_response_characteristic(ias_client_connection_t * connection,ias_client_characteristic_index_t characteristic_index)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
ias_client_packet_handler_internal(uint8_t packet_type,uint16_t channel,uint8_t * packet,uint16_t size)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
ias_client_serialize_characteristic_value_for_write(ias_client_connection_t * connection,uint8_t ** out_value)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
ias_client_run_for_connection(void * context)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
immediate_alert_service_client_init(void)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
immediate_alert_service_client_connect(hci_con_handle_t con_handle,btstack_packet_handler_t packet_handler,ias_client_connection_t * ias_connection,uint16_t * ias_cid)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
immediate_alert_service_client_write_alert_level(uint16_t ias_cid,ias_alert_level_t alert_level)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
immediate_alert_service_client_disconnect(uint16_t ias_cid)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
immediate_alert_service_client_deinit(void)305 void immediate_alert_service_client_deinit(void){
306 gatt_service_client_unregister_client(&ias_client);
307 }
308
309