xref: /btstack/example/gatt_heart_rate_client.c (revision a8f7f3fcbcd51f8d2e92aca076b6a9f812db358c)
1 /*
2  * Copyright (C) 2018 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__ "gatt_heart_rate_client.c"
39 
40 // *****************************************************************************
41 /* EXAMPLE_START(gatt_heart_rate_client): GATT Heart Rate Sensor Client
42  *
43  * @text Connects for Heart Rate Sensor and reports measurements.
44  */
45 // *****************************************************************************
46 
47 #include <inttypes.h>
48 #include <stdint.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 
53 #include "btstack.h"
54 
55 // prototypes
56 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
57 
58 typedef enum {
59     TC_OFF,
60     TC_IDLE,
61     TC_W4_SCAN_RESULT,
62     TC_W4_CONNECT,
63     TC_W4_SERVICE_RESULT,
64     TC_W4_HEART_RATE_MEASUREMENT_CHARACTERISTIC,
65     TC_W4_ENABLE_NOTIFICATIONS_COMPLETE,
66     TC_W4_SENSOR_LOCATION_CHARACTERISTIC,
67     TC_W4_SENSOR_LOCATION,
68     TC_CONNECTED
69 } gc_state_t;
70 
71 static const char * sensor_contact_string[] = {
72     "not supported",
73     "not supported",
74     "no/poor contact",
75     "good contact"
76 };
77 
78 static bd_addr_t cmdline_addr;
79 static int cmdline_addr_found = 0;
80 
81 // addr and type of device with correct name
82 static bd_addr_t      le_streamer_addr;
83 static bd_addr_type_t le_streamer_addr_type;
84 
85 static hci_con_handle_t connection_handle;
86 
87 static gatt_client_service_t        heart_rate_service;
88 static gatt_client_characteristic_t body_sensor_location_characteristic;
89 static gatt_client_characteristic_t heart_rate_measurement_characteristic;
90 
91 static uint8_t body_sensor_location;
92 
93 static gatt_client_notification_t notification_listener;
94 static int listener_registered;
95 
96 static gc_state_t state = TC_OFF;
97 static btstack_packet_callback_registration_t hci_event_callback_registration;
98 
99 
100 // returns 1 if name is found in advertisement
101 static int advertisement_report_contains_uuid16(uint16_t uuid16, uint8_t * advertisement_report){
102     // get advertisement from report event
103     const uint8_t * adv_data = gap_event_advertising_report_get_data(advertisement_report);
104     uint16_t        adv_len  = gap_event_advertising_report_get_data_length(advertisement_report);
105 
106     // iterate over advertisement data
107     ad_context_t context;
108     int found = 0;
109     for (ad_iterator_init(&context, adv_len, adv_data) ; ad_iterator_has_more(&context) ; ad_iterator_next(&context)){
110         uint8_t data_type    = ad_iterator_get_data_type(&context);
111         uint8_t data_size    = ad_iterator_get_data_len(&context);
112         const uint8_t * data = ad_iterator_get_data(&context);
113         int i;
114         switch (data_type){
115             case BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
116             case BLUETOOTH_DATA_TYPE_COMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS:
117                 // compare common prefix
118                 for (i=0; i<data_size;i+=2){
119                     if (little_endian_read_16(data, i) == uuid16) {
120                         found = 1;
121                         break;
122                     }
123                 }
124                 break;
125             default:
126                 break;
127         }
128     }
129     return found;
130 }
131 
132 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
133     UNUSED(packet_type);
134     UNUSED(channel);
135     UNUSED(size);
136 
137     uint16_t heart_rate;
138     uint8_t  sensor_contact;
139 
140     switch(state){
141         case TC_W4_SERVICE_RESULT:
142             switch(hci_event_packet_get_type(packet)){
143                 case GATT_EVENT_SERVICE_QUERY_RESULT:
144                     // store service (we expect only one)
145                     gatt_event_service_query_result_get_service(packet, &heart_rate_service);
146                     break;
147                 case GATT_EVENT_QUERY_COMPLETE:
148                     if (packet[4] != 0){
149                         printf("SERVICE_QUERY_RESULT - Error status %x.\n", packet[4]);
150                         gap_disconnect(connection_handle);
151                         break;
152                     }
153                     state = TC_W4_HEART_RATE_MEASUREMENT_CHARACTERISTIC;
154                     printf("Search for Heart Rate Measurement characteristic.\n");
155                     gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &heart_rate_service, ORG_BLUETOOTH_CHARACTERISTIC_HEART_RATE_MEASUREMENT);
156                     break;
157                 default:
158                     break;
159             }
160             break;
161 
162         case TC_W4_HEART_RATE_MEASUREMENT_CHARACTERISTIC:
163             switch(hci_event_packet_get_type(packet)){
164                 case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
165                     gatt_event_characteristic_query_result_get_characteristic(packet, &heart_rate_measurement_characteristic);
166                     break;
167                 case GATT_EVENT_QUERY_COMPLETE:
168                     if (packet[4] != 0){
169                         printf("CHARACTERISTIC_QUERY_RESULT - Error status %x.\n", packet[4]);
170                         gap_disconnect(connection_handle);
171                         break;
172                     }
173                     // register handler for notifications
174                     listener_registered = 1;
175                     gatt_client_listen_for_characteristic_value_updates(&notification_listener, handle_gatt_client_event, connection_handle, &heart_rate_measurement_characteristic);
176                     // enable notifications
177                     printf("Enable Notify on Heart Rate Measurements characteristic.\n");
178                     state = TC_W4_ENABLE_NOTIFICATIONS_COMPLETE;
179                     gatt_client_write_client_characteristic_configuration(handle_gatt_client_event, connection_handle,
180                         &heart_rate_measurement_characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION);
181                     break;
182                 default:
183                     break;
184             }
185             break;
186 
187         case TC_W4_ENABLE_NOTIFICATIONS_COMPLETE:
188             switch(hci_event_packet_get_type(packet)){
189                 case GATT_EVENT_QUERY_COMPLETE:
190                     printf("Notifications enabled, ATT status %02x\n", gatt_event_query_complete_get_att_status(packet));
191 
192                     state = TC_W4_SENSOR_LOCATION_CHARACTERISTIC;
193                     printf("Search for Sensor Location characteristic.\n");
194                     gatt_client_discover_characteristics_for_service_by_uuid16(handle_gatt_client_event, connection_handle, &heart_rate_service, ORG_BLUETOOTH_CHARACTERISTIC_BODY_SENSOR_LOCATION);
195                     break;
196                 default:
197                     break;
198             }
199             break;
200 
201 
202         case TC_W4_SENSOR_LOCATION_CHARACTERISTIC:
203             switch(hci_event_packet_get_type(packet)){
204                 case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT:
205                     gatt_event_characteristic_query_result_get_characteristic(packet, &body_sensor_location_characteristic);
206                     break;
207                 case GATT_EVENT_QUERY_COMPLETE:
208                     if (packet[4] != 0){
209                         printf("CHARACTERISTIC_QUERY_RESULT - Error status %x.\n", packet[4]);
210                         state = TC_CONNECTED;
211                         break;
212                     }
213                     if (body_sensor_location_characteristic.value_handle == 0){
214                         printf("Sensor Location characteristic not available.\n");
215                         state = TC_CONNECTED;
216                         break;
217                     }
218                     state = TC_W4_HEART_RATE_MEASUREMENT_CHARACTERISTIC;
219                     printf("Read Body Sensor Location.\n");
220                     state = TC_W4_SENSOR_LOCATION;
221                     gatt_client_read_value_of_characteristic(handle_gatt_client_event, connection_handle, &body_sensor_location_characteristic);
222                     break;
223                 default:
224                     break;
225             }
226             break;
227 
228 
229         case TC_W4_SENSOR_LOCATION:
230             switch(hci_event_packet_get_type(packet)){
231                 case GATT_EVENT_CHARACTERISTIC_VALUE_QUERY_RESULT:
232                     body_sensor_location = gatt_event_characteristic_value_query_result_get_value(packet)[0];
233                     printf("Sensor Location: %u\n", body_sensor_location);
234                     break;
235                 case GATT_EVENT_QUERY_COMPLETE:
236                     state = TC_CONNECTED;
237                     break;
238                 default:
239                     break;
240             }
241             break;
242 
243         case TC_CONNECTED:
244             switch(hci_event_packet_get_type(packet)){
245                 case GATT_EVENT_NOTIFICATION:
246                     if (gatt_event_notification_get_value(packet)[0] & 1){
247                         heart_rate = little_endian_read_16(gatt_event_notification_get_value(packet), 1);
248                     } else {
249                         heart_rate = gatt_event_notification_get_value(packet)[1];
250                     }
251                     sensor_contact = (gatt_event_notification_get_value(packet)[0] >> 1) & 3;
252                     printf("Heart Rate: %3u, Sensor Contact: %s\n", heart_rate, sensor_contact_string[sensor_contact]);
253                     break;
254                 case GATT_EVENT_QUERY_COMPLETE:
255                     break;
256                 case GATT_EVENT_CAN_WRITE_WITHOUT_RESPONSE:
257                     break;
258                 default:
259                     break;
260             }
261             break;
262 
263         default:
264             break;
265     }
266 }
267 
268 // Either connect to remote specified on command line or start scan for device with Hear Rate Service in advertisement
269 static void gatt_heart_rate_client_start(void){
270     if (cmdline_addr_found){
271         printf("Connect to %s\n", bd_addr_to_str(cmdline_addr));
272         state = TC_W4_CONNECT;
273         gap_connect(cmdline_addr, 0);
274     } else {
275         printf("Start scanning!\n");
276         state = TC_W4_SCAN_RESULT;
277         gap_set_scan_parameters(0,0x0030, 0x0030);
278         gap_start_scan();
279     }
280 }
281 
282 static void hci_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
283     UNUSED(channel);
284     UNUSED(size);
285 
286     if (packet_type != HCI_EVENT_PACKET) return;
287 
288     uint16_t conn_interval;
289     uint8_t event = hci_event_packet_get_type(packet);
290     switch (event) {
291         case BTSTACK_EVENT_STATE:
292             // BTstack activated, get started
293             if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING) {
294                 gatt_heart_rate_client_start();
295             } else {
296                 state = TC_OFF;
297             }
298             break;
299         case GAP_EVENT_ADVERTISING_REPORT:
300             if (state != TC_W4_SCAN_RESULT) return;
301             // check name in advertisement
302             if (!advertisement_report_contains_uuid16(ORG_BLUETOOTH_SERVICE_HEART_RATE, packet)) return;
303             // store address and type
304             gap_event_advertising_report_get_address(packet, le_streamer_addr);
305             le_streamer_addr_type = gap_event_advertising_report_get_address_type(packet);
306             // stop scanning, and connect to the device
307             state = TC_W4_CONNECT;
308             gap_stop_scan();
309             printf("Stop scan. Connect to device with addr %s.\n", bd_addr_to_str(le_streamer_addr));
310             gap_connect(le_streamer_addr,le_streamer_addr_type);
311             break;
312         case HCI_EVENT_LE_META:
313             // wait for connection complete
314             if (hci_event_le_meta_get_subevent_code(packet) !=  HCI_SUBEVENT_LE_CONNECTION_COMPLETE) break;
315             if (state != TC_W4_CONNECT) return;
316             connection_handle = hci_subevent_le_connection_complete_get_connection_handle(packet);
317             // print connection parameters (without using float operations)
318             conn_interval = hci_subevent_le_connection_complete_get_conn_interval(packet);
319             printf("Connection Interval: %u.%02u ms\n", conn_interval * 125 / 100, 25 * (conn_interval & 3));
320             printf("Connection Latency: %u\n", hci_subevent_le_connection_complete_get_conn_latency(packet));
321             // initialize gatt client context with handle, and add it to the list of active clients
322             // query primary services
323             printf("Search for Heart Rate service.\n");
324             state = TC_W4_SERVICE_RESULT;
325             gatt_client_discover_primary_services_by_uuid16(handle_gatt_client_event, connection_handle, ORG_BLUETOOTH_SERVICE_HEART_RATE);
326             break;
327         case HCI_EVENT_DISCONNECTION_COMPLETE:
328             // unregister listener
329             connection_handle = HCI_CON_HANDLE_INVALID;
330             if (listener_registered){
331                 listener_registered = 0;
332                 gatt_client_stop_listening_for_characteristic_value_updates(&notification_listener);
333             }
334             if (cmdline_addr_found){
335                 printf("Disconnected %s\n", bd_addr_to_str(cmdline_addr));
336                 return;
337             }
338             printf("Disconnected %s\n", bd_addr_to_str(le_streamer_addr));
339             if (state == TC_OFF) break;
340             gatt_heart_rate_client_start();
341             break;
342         default:
343             break;
344     }
345 }
346 
347 #ifdef HAVE_BTSTACK_STDIN
348 static void usage(const char *name){
349     fprintf(stderr, "Usage: %s [-a|--address aa:bb:cc:dd:ee:ff]\n", name);
350     fprintf(stderr, "If no argument is provided, GATT Heart Rate Client will start scanning and connect to the first device named 'LE Streamer'.\n");
351     fprintf(stderr, "To connect to a specific device use argument [-a].\n\n");
352 }
353 #endif
354 
355 int btstack_main(int argc, const char * argv[]);
356 int btstack_main(int argc, const char * argv[]){
357 
358 #ifdef HAVE_BTSTACK_STDIN
359     int arg = 1;
360     cmdline_addr_found = 0;
361 
362     while (arg < argc) {
363         if(!strcmp(argv[arg], "-a") || !strcmp(argv[arg], "--address")){
364             arg++;
365             cmdline_addr_found = sscanf_bd_addr(argv[arg], cmdline_addr);
366             arg++;
367             if (!cmdline_addr_found) exit(1);
368             continue;
369         }
370         usage(argv[0]);
371         return 0;
372     }
373 #else
374     (void)argc;
375     (void)argv;
376 #endif
377     l2cap_init();
378 
379     gatt_client_init();
380 
381     sm_init();
382     sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
383 
384     hci_event_callback_registration.callback = &hci_event_handler;
385     hci_add_event_handler(&hci_event_callback_registration);
386 
387     // turn on!
388     hci_power_control(HCI_POWER_ON);
389 
390     return 0;
391 }
392 /* EXAMPLE_END */
393