xref: /btstack/src/ble/gatt-service/ancs_client.c (revision 0f7fd6c19b44e3fae3aaf3caf2c11dd07533fe4c)
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 MATTHIAS
24  * RINGWALD 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__ "ancs_client.c"
39 
40 #include "btstack_config.h"
41 
42 #include <stdint.h>
43 #include <string.h>
44 
45 #include "ble/gatt-service/ancs_client.h"
46 
47 #include "ble/core.h"
48 #include "ble/gatt_client.h"
49 #include "ble/sm.h"
50 #include "btstack_debug.h"
51 #include "btstack_event.h"
52 #include "btstack_run_loop.h"
53 #include "gap.h"
54 
55 // ancs_client.h Start
56 typedef enum ancs_chunk_parser_state {
57     W4_ATTRIBUTE_ID,
58     W4_ATTRIBUTE_LEN,
59     W4_ATTRIBUTE_COMPLETE,
60 } ancs_chunk_parser_state_t;
61 
62 typedef enum {
63     TC_IDLE,
64     TC_W4_ENCRYPTED_CONNECTION,
65     TC_W4_SERVICE_RESULT,
66     TC_W4_CHARACTERISTIC_RESULT,
67     TC_W4_DATA_SOURCE_SUBSCRIBED,
68     TC_W4_NOTIFICATION_SOURCE_SUBSCRIBED,
69     TC_SUBSCRIBED,
70     TC_W4_DISCONNECT
71 } tc_state_t;
72 
73 static uint32_t ancs_notification_uid;
74 static uint16_t gc_handle;
75 static gatt_client_notification_t ancs_notification_source_notification;
76 static gatt_client_notification_t ancs_data_source_notification;
77 static int ancs_service_found;
78 static gatt_client_service_t  ancs_service;
79 static gatt_client_characteristic_t ancs_notification_source_characteristic;
80 static gatt_client_characteristic_t ancs_control_point_characteristic;
81 static gatt_client_characteristic_t ancs_data_source_characteristic;
82 static int ancs_characteristcs;
83 static tc_state_t tc_state = TC_IDLE;
84 
85 static ancs_chunk_parser_state_t chunk_parser_state;
86 static uint8_t  ancs_notification_buffer[50];
87 static uint16_t ancs_bytes_received;
88 static uint16_t ancs_bytes_needed;
89 static uint8_t  ancs_attribute_id;
90 static uint16_t ancs_attribute_len;
91 
92 static btstack_packet_handler_t client_handler;
93 static btstack_packet_callback_registration_t hci_event_callback_registration;
94 
95 void ancs_client_register_callback(btstack_packet_handler_t handler){
96     client_handler = handler;
97 }
98 
99 static void notify_client_text(int event_type){
100     if (!client_handler) return;
101     uint8_t event[7 + sizeof(ancs_notification_buffer) + 1];
102     event[0] = HCI_EVENT_ANCS_META;
103     event[1] = 5u + ancs_attribute_len;
104     event[2] = event_type;
105     little_endian_store_16(event, 3, gc_handle);
106     little_endian_store_16(event, 5, ancs_attribute_id);
107     (void)memcpy(&event[7], ancs_notification_buffer, ancs_attribute_len);
108     // we're nice
109     event[7u+ancs_attribute_len] = 0u;
110     (*client_handler)(HCI_EVENT_PACKET, 0u, event, event[1u] + 2u);
111 }
112 
113 static void notify_client_simple(int event_type){
114     if (!client_handler) return;
115     uint8_t event[5];
116     event[0] = HCI_EVENT_ANCS_META;
117     event[1] = 3;
118     event[2] = event_type;
119     little_endian_store_16(event, 3, gc_handle);
120     (*client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event));
121 }
122 
123 static void ancs_chunk_parser_init(void){
124     chunk_parser_state = W4_ATTRIBUTE_ID;
125     ancs_bytes_received = 0;
126     ancs_bytes_needed = 6;
127 }
128 
129 const char * ancs_client_attribute_name_for_id(int id){
130     static const char * ancs_attribute_names[] = {
131             "AppIdentifier",
132             "IDTitle",
133             "IDSubtitle",
134             "IDMessage",
135             "IDMessageSize",
136             "IDDate"
137     };
138 
139     static const uint16_t ANCS_ATTRBUTE_NAMES_COUNT = sizeof(ancs_attribute_names) / sizeof(char *);
140 
141     if (id >= ANCS_ATTRBUTE_NAMES_COUNT) return NULL;
142     return ancs_attribute_names[id];
143 }
144 
145 static void ancs_chunk_parser_handle_byte(uint8_t data){
146     ancs_notification_buffer[ancs_bytes_received++] = data;
147     if (ancs_bytes_received < ancs_bytes_needed) return;
148     switch (chunk_parser_state){
149         case W4_ATTRIBUTE_ID:
150             ancs_attribute_id   = ancs_notification_buffer[ancs_bytes_received-1u];
151             ancs_bytes_received = 0;
152             ancs_bytes_needed   = 2;
153             chunk_parser_state  = W4_ATTRIBUTE_LEN;
154             break;
155         case W4_ATTRIBUTE_LEN:
156             ancs_attribute_len  = little_endian_read_16(ancs_notification_buffer, ancs_bytes_received-2u);
157             ancs_bytes_received = 0;
158             ancs_bytes_needed   = ancs_attribute_len;
159             if (ancs_attribute_len == 0u) {
160                 ancs_bytes_needed   = 1;
161                 chunk_parser_state  = W4_ATTRIBUTE_ID;
162                 break;
163             }
164             chunk_parser_state  = W4_ATTRIBUTE_COMPLETE;
165             break;
166         case W4_ATTRIBUTE_COMPLETE:
167             ancs_notification_buffer[ancs_bytes_received] = 0;
168             notify_client_text(ANCS_SUBEVENT_CLIENT_NOTIFICATION);
169             ancs_bytes_received = 0;
170             ancs_bytes_needed   = 1;
171             chunk_parser_state  = W4_ATTRIBUTE_ID;
172             break;
173         default:
174             btstack_assert(false);
175             break;
176     }
177 }
178 
179 static void handle_hci_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
180 
181     UNUSED(packet_type); // ok: only hci events
182     UNUSED(channel);     // ok: there is no channel
183     UNUSED(size);        // ok: fixed format events read from HCI buffer
184 
185     static const uint8_t ancs_service_uuid[] =             {0x79,0x05,0xF4,0x31,0xB5,0xCE,0x4E,0x99,0xA4,0x0F,0x4B,0x1E,0x12,0x2D,0x00,0xD0};
186     static const uint8_t ancs_notification_source_uuid[] = {0x9F,0xBF,0x12,0x0D,0x63,0x01,0x42,0xD9,0x8C,0x58,0x25,0xE6,0x99,0xA2,0x1D,0xBD};
187     static const uint8_t ancs_control_point_uuid[] =       {0x69,0xD1,0xD8,0xF3,0x45,0xE1,0x49,0xA8,0x98,0x21,0x9B,0xBD,0xFD,0xAA,0xD9,0xD9};
188     static const uint8_t ancs_data_source_uuid[] =         {0x22,0xEA,0xC6,0xE9,0x24,0xD6,0x4B,0xB5,0xBE,0x44,0xB3,0x6A,0xCE,0x7C,0x7B,0xFB};
189 
190 
191     int connection_encrypted;
192 
193     // handle connect / disconncet events first
194     switch (hci_event_packet_get_type(packet)) {
195         case HCI_EVENT_LE_META:
196             switch (packet[2]) {
197                 case HCI_SUBEVENT_LE_CONNECTION_COMPLETE:
198                     gc_handle = little_endian_read_16(packet, 4);
199                     log_info("Connection handle 0x%04x, request encryption", gc_handle);
200 
201                     // we need to be paired to enable notifications
202                     tc_state = TC_W4_ENCRYPTED_CONNECTION;
203                     sm_request_pairing(gc_handle);
204                     break;
205                 default:
206                     break;
207             }
208             return;
209 
210         case HCI_EVENT_ENCRYPTION_CHANGE:
211             if (gc_handle != little_endian_read_16(packet, 3)) return;
212             connection_encrypted = packet[5];
213             log_info("Encryption state change: %u", connection_encrypted);
214             if (!connection_encrypted) return;
215             if (tc_state != TC_W4_ENCRYPTED_CONNECTION) return;
216 
217             // let's start
218             log_info("\nANCS Client - CONNECTED, discover ANCS service");
219             tc_state = TC_W4_SERVICE_RESULT;
220             gatt_client_discover_primary_services_by_uuid128(handle_hci_event, gc_handle, ancs_service_uuid);
221             return;
222 
223         case HCI_EVENT_DISCONNECTION_COMPLETE:
224             if (hci_event_disconnection_complete_get_connection_handle(packet) != gc_handle) break;
225             if (tc_state == TC_SUBSCRIBED){
226                 notify_client_simple(ANCS_SUBEVENT_CLIENT_DISCONNECTED);
227             }
228             tc_state = TC_IDLE;
229             gc_handle = 0;
230             return;
231 
232         default:
233             break;
234     }
235 
236     gatt_client_characteristic_t characteristic;
237     uint8_t *           value;
238     uint16_t            value_handle;
239     uint16_t            value_length;
240 
241     switch(tc_state){
242         case TC_W4_SERVICE_RESULT:
243             switch(hci_event_packet_get_type(packet)){
244                 case GATT_EVENT_SERVICE_QUERY_RESULT:
245                     gatt_event_service_query_result_get_service(packet, &ancs_service);
246                     ancs_service_found = 1;
247                     break;
248                 case GATT_EVENT_QUERY_COMPLETE:
249                     if (!ancs_service_found){
250                         log_info("ANCS Service not found");
251                         tc_state = TC_IDLE;
252                         break;
253                     }
254                     tc_state = TC_W4_CHARACTERISTIC_RESULT;
255                     log_info("ANCS Client - Discover characteristics for ANCS SERVICE ");
256                     gatt_client_discover_characteristics_for_service(handle_hci_event, gc_handle, &ancs_service);
257                     break;
258                 default:
259                     break;
260             }
261             break;
262 
263         case TC_W4_CHARACTERISTIC_RESULT:
264             switch(hci_event_packet_get_type(packet)){
265                 case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
266                     gatt_event_characteristic_query_result_get_characteristic(packet, &characteristic);
267                     if (memcmp(characteristic.uuid128, ancs_notification_source_uuid, 16) == 0){
268                         log_info("ANCS Notification Source found, attribute handle %u", characteristic.value_handle);
269                         ancs_notification_source_characteristic = characteristic;
270                         ancs_characteristcs++;
271                         break;
272                     }
273                     if (memcmp(characteristic.uuid128, ancs_control_point_uuid, 16) == 0){
274                         log_info("ANCS Control Point found, attribute handle %u", characteristic.value_handle);
275                         ancs_control_point_characteristic = characteristic;
276                         ancs_characteristcs++;
277                         break;
278                     }
279                     if (memcmp(characteristic.uuid128, ancs_data_source_uuid, 16) == 0){
280                         log_info("ANCS Data Source found, attribute handle %u", characteristic.value_handle);
281                         ancs_data_source_characteristic = characteristic;
282                         ancs_characteristcs++;
283                         break;
284                     }
285                     break;
286                 case GATT_EVENT_QUERY_COMPLETE:
287                     log_info("ANCS Characteristcs count %u", ancs_characteristcs);
288                     tc_state = TC_W4_NOTIFICATION_SOURCE_SUBSCRIBED;
289                     gatt_client_listen_for_characteristic_value_updates(&ancs_notification_source_notification, &handle_hci_event, gc_handle, &ancs_notification_source_characteristic);
290                     gatt_client_write_client_characteristic_configuration(handle_hci_event, gc_handle, &ancs_notification_source_characteristic,
291                         GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
292                     break;
293                 default:
294                     break;
295             }
296             break;
297         case TC_W4_NOTIFICATION_SOURCE_SUBSCRIBED:
298             switch(hci_event_packet_get_type(packet)){
299                 case GATT_EVENT_QUERY_COMPLETE:
300                     log_info("ANCS Notification Source subscribed");
301                     tc_state = TC_W4_DATA_SOURCE_SUBSCRIBED;
302                     gatt_client_listen_for_characteristic_value_updates(&ancs_data_source_notification, &handle_hci_event, gc_handle, &ancs_data_source_characteristic);
303                     gatt_client_write_client_characteristic_configuration(handle_hci_event, gc_handle, &ancs_data_source_characteristic,
304                         GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
305                     break;
306                 default:
307                     break;
308             }
309             break;
310         case TC_W4_DATA_SOURCE_SUBSCRIBED:
311             switch(hci_event_packet_get_type(packet)){
312                 case GATT_EVENT_QUERY_COMPLETE:
313                     log_info("ANCS Data Source subscribed");
314                     tc_state = TC_SUBSCRIBED;
315                     notify_client_simple(ANCS_SUBEVENT_CLIENT_CONNECTED);
316                     break;
317                 default:
318                     break;
319             }
320             break;
321         case TC_SUBSCRIBED:
322             if ((hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) && (hci_event_packet_get_type(packet) != GATT_EVENT_INDICATION) ) break;
323 
324             value_handle = little_endian_read_16(packet, 4);
325             value_length = little_endian_read_16(packet, 6);
326             value = &packet[8];
327 
328             log_info("ANCS Notification, value handle %u", value_handle);
329 
330             if (value_handle == ancs_data_source_characteristic.value_handle){
331                 int i;
332                 for (i=0;i<value_length;i++) {
333                     ancs_chunk_parser_handle_byte(value[i]);
334                 }
335             } else if (value_handle == ancs_notification_source_characteristic.value_handle){
336                 ancs_notification_uid = little_endian_read_32(value, 4);
337                 log_info("Notification received: EventID %02x, EventFlags %02x, CategoryID %02x, CategoryCount %u, UID %04x",
338                     value[0], value[1], value[2], value[3], (int) ancs_notification_uid);
339                 static uint8_t get_notification_attributes[] = {0, 0,0,0,0,  0,  1,32,0,  2,32,0, 3,32,0, 4, 5};
340                 little_endian_store_32(get_notification_attributes, 1, ancs_notification_uid);
341                 ancs_notification_uid = 0;
342                 ancs_chunk_parser_init();
343                 gatt_client_write_value_of_characteristic(handle_hci_event, gc_handle, ancs_control_point_characteristic.value_handle,
344                     sizeof(get_notification_attributes), get_notification_attributes);
345             } else {
346                 log_info("Unknown Source: ");
347                 log_info_hexdump(value , value_length);
348             }
349             break;
350         default:
351             break;
352     }
353     // app_run();
354 }
355 
356 void ancs_client_init(void){
357     hci_event_callback_registration.callback = &handle_hci_event;
358     hci_add_event_handler(&hci_event_callback_registration);
359 }
360