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 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__ "le_streamer_client.c" 39 40 /* 41 * le_streamer_client.c 42 */ 43 44 // ***************************************************************************** 45 /* EXAMPLE_START(le_streamer_client): Performance - Stream Data over GATT (Client) 46 * 47 * @text Connects to 'LE Streamer' and subscribes to test characteristic 48 * 49 */ 50 // ***************************************************************************** 51 52 #include <inttypes.h> 53 #include <stdint.h> 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <string.h> 57 58 #include "btstack.h" 59 60 typedef struct { 61 char name; 62 int le_notification_enabled; 63 int counter; 64 char test_data[200]; 65 int test_data_len; 66 uint32_t test_data_sent; 67 uint32_t test_data_start; 68 btstack_context_callback_registration_t write_without_response_request; 69 } le_streamer_connection_t; 70 71 typedef enum { 72 TC_OFF, 73 TC_IDLE, 74 TC_W4_SCAN_RESULT, 75 TC_W4_CONNECT, 76 TC_W4_SERVICE_RESULT, 77 TC_W4_CHARACTERISTIC_RX_RESULT, 78 TC_W4_CHARACTERISTIC_TX_RESULT, 79 TC_W4_ENABLE_NOTIFICATIONS_COMPLETE, 80 TC_W4_TEST_DATA 81 } gc_state_t; 82 83 static char *const le_streamer_server_name = "LE Streamer"; 84 static bd_addr_t cmdline_addr; 85 static int cmdline_addr_found = 0; 86 87 // addr and type of device with correct name 88 static bd_addr_t le_streamer_addr; 89 static bd_addr_type_t le_streamer_addr_type; 90 91 static hci_con_handle_t connection_handle; 92 93 // On the GATT Server, RX Characteristic is used for receive data via Write, and TX Characteristic is used to send data via Notifications 94 static uint8_t le_streamer_service_uuid[16] = { 0x00, 0x00, 0xFF, 0x10, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}; 95 static uint8_t le_streamer_characteristic_rx_uuid[16] = { 0x00, 0x00, 0xFF, 0x11, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}; 96 static uint8_t le_streamer_characteristic_tx_uuid[16] = { 0x00, 0x00, 0xFF, 0x12, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}; 97 98 static gatt_client_service_t le_streamer_service; 99 static gatt_client_characteristic_t le_streamer_characteristic_rx; 100 static gatt_client_characteristic_t le_streamer_characteristic_tx; 101 102 static gatt_client_notification_t notification_listener; 103 static int listener_registered; 104 105 static gc_state_t state = TC_OFF; 106 static btstack_packet_callback_registration_t hci_event_callback_registration; 107 108 // prototypes 109 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size); 110 static void le_streamer_handle_can_write_without_response(void * context); 111 static void le_streamer_client_request_to_send(le_streamer_connection_t * connection); 112 113 /* 114 * @section Track throughput 115 * @text We calculate the throughput by setting a start time and measuring the amount of 116 * data sent. After a configurable REPORT_INTERVAL_MS, we print the throughput in kB/s 117 * and reset the counter and start time. 118 */ 119 120 /* LISTING_START(tracking): Tracking throughput */ 121 122 #define TEST_MODE_WRITE_WITHOUT_RESPONSE 1 123 #define TEST_MODE_ENABLE_NOTIFICATIONS 2 124 #define TEST_MODE_DUPLEX 3 125 126 // configure test mode: send only, receive only, full duplex 127 #define TEST_MODE TEST_MODE_DUPLEX 128 129 #define REPORT_INTERVAL_MS 3000 130 131 // support for multiple clients 132 static le_streamer_connection_t le_streamer_connection; 133 134 static void test_reset(le_streamer_connection_t * context){ 135 context->test_data_start = btstack_run_loop_get_time_ms(); 136 context->test_data_sent = 0; 137 } 138 139 static void test_track_data(le_streamer_connection_t * context, int bytes_sent){ 140 context->test_data_sent += bytes_sent; 141 // evaluate 142 uint32_t now = btstack_run_loop_get_time_ms(); 143 uint32_t time_passed = now - context->test_data_start; 144 if (time_passed < REPORT_INTERVAL_MS) return; 145 // print speed 146 int bytes_per_second = context->test_data_sent * 1000 / time_passed; 147 printf("%c: %"PRIu32" bytes -> %u.%03u kB/s\n", context->name, context->test_data_sent, bytes_per_second / 1000, bytes_per_second % 1000); 148 149 // restart 150 context->test_data_start = now; 151 context->test_data_sent = 0; 152 } 153 /* LISTING_END(tracking): Tracking throughput */ 154 155 156 // streamer 157 static void le_streamer_handle_can_write_without_response(void * context){ 158 le_streamer_connection_t * connection = (le_streamer_connection_t *) context; 159 160 // create test data 161 connection->counter++; 162 if (connection->counter > 'Z') connection->counter = 'A'; 163 memset(connection->test_data, connection->counter, connection->test_data_len); 164 165 // send 166 uint8_t status = gatt_client_write_value_of_characteristic_without_response(connection_handle, le_streamer_characteristic_rx.value_handle, connection->test_data_len, (uint8_t*) connection->test_data); 167 if (status){ 168 printf("Write without response failed, status 0x%02x.\n", status); 169 return; 170 } else { 171 test_track_data(connection, connection->test_data_len); 172 } 173 174 // request again 175 le_streamer_client_request_to_send(connection); 176 } 177 178 static void le_streamer_client_request_to_send(le_streamer_connection_t * connection){ 179 connection->write_without_response_request.callback = &le_streamer_handle_can_write_without_response; 180 connection->write_without_response_request.context = connection; 181 gatt_client_request_to_write_without_response(&connection->write_without_response_request, connection_handle); 182 } 183 184 // returns true if name is found in advertisement 185 static bool advertisement_contains_name(const char * name, uint8_t adv_len, const uint8_t * adv_data){ 186 // get advertisement from report event 187 uint16_t name_len = (uint8_t) strlen(name); 188 189 // iterate over advertisement data 190 ad_context_t context; 191 for (ad_iterator_init(&context, adv_len, adv_data) ; ad_iterator_has_more(&context) ; ad_iterator_next(&context)){ 192 uint8_t data_type = ad_iterator_get_data_type(&context); 193 uint8_t data_size = ad_iterator_get_data_len(&context); 194 const uint8_t * data = ad_iterator_get_data(&context); 195 switch (data_type){ 196 case BLUETOOTH_DATA_TYPE_SHORTENED_LOCAL_NAME: 197 case BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME: 198 // compare prefix 199 if (data_size < name_len) break; 200 if (memcmp(data, name, name_len) == 0) return true; 201 break; 202 default: 203 break; 204 } 205 } 206 return false; 207 } 208 209 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 210 UNUSED(packet_type); 211 UNUSED(channel); 212 UNUSED(size); 213 214 uint16_t mtu; 215 uint8_t att_status; 216 switch(state){ 217 case TC_W4_SERVICE_RESULT: 218 switch(hci_event_packet_get_type(packet)){ 219 case GATT_EVENT_SERVICE_QUERY_RESULT: 220 // store service (we expect only one) 221 gatt_event_service_query_result_get_service(packet, &le_streamer_service); 222 break; 223 case GATT_EVENT_QUERY_COMPLETE: 224 att_status = gatt_event_query_complete_get_att_status(packet); 225 if (att_status != ATT_ERROR_SUCCESS){ 226 printf("SERVICE_QUERY_RESULT, ATT Error 0x%02x.\n", att_status); 227 gap_disconnect(connection_handle); 228 break; 229 } 230 // service query complete, look for characteristic 231 state = TC_W4_CHARACTERISTIC_RX_RESULT; 232 printf("Search for LE Streamer RX characteristic.\n"); 233 gatt_client_discover_characteristics_for_service_by_uuid128(handle_gatt_client_event, connection_handle, &le_streamer_service, le_streamer_characteristic_rx_uuid); 234 break; 235 default: 236 break; 237 } 238 break; 239 240 case TC_W4_CHARACTERISTIC_RX_RESULT: 241 switch(hci_event_packet_get_type(packet)){ 242 case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: 243 gatt_event_characteristic_query_result_get_characteristic(packet, &le_streamer_characteristic_rx); 244 break; 245 case GATT_EVENT_QUERY_COMPLETE: 246 att_status = gatt_event_query_complete_get_att_status(packet); 247 if (att_status != ATT_ERROR_SUCCESS){ 248 printf("CHARACTERISTIC_QUERY_RESULT, ATT Error 0x%02x.\n", att_status); 249 gap_disconnect(connection_handle); 250 break; 251 } 252 // rx characteristiic found, look for tx characteristic 253 state = TC_W4_CHARACTERISTIC_TX_RESULT; 254 printf("Search for LE Streamer TX characteristic.\n"); 255 gatt_client_discover_characteristics_for_service_by_uuid128(handle_gatt_client_event, connection_handle, &le_streamer_service, le_streamer_characteristic_tx_uuid); 256 break; 257 default: 258 break; 259 } 260 break; 261 262 case TC_W4_CHARACTERISTIC_TX_RESULT: 263 switch(hci_event_packet_get_type(packet)){ 264 case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: 265 gatt_event_characteristic_query_result_get_characteristic(packet, &le_streamer_characteristic_tx); 266 break; 267 case GATT_EVENT_QUERY_COMPLETE: 268 att_status = gatt_event_query_complete_get_att_status(packet); 269 if (att_status != ATT_ERROR_SUCCESS){ 270 printf("CHARACTERISTIC_QUERY_RESULT, ATT Error 0x%02x.\n", att_status); 271 gap_disconnect(connection_handle); 272 break; 273 } 274 // register handler for notifications 275 listener_registered = 1; 276 gatt_client_listen_for_characteristic_value_updates(¬ification_listener, handle_gatt_client_event, connection_handle, &le_streamer_characteristic_tx); 277 // setup tracking 278 le_streamer_connection.name = 'A'; 279 le_streamer_connection.test_data_len = ATT_DEFAULT_MTU - 3; 280 test_reset(&le_streamer_connection); 281 gatt_client_get_mtu(connection_handle, &mtu); 282 le_streamer_connection.test_data_len = btstack_min(mtu - 3, sizeof(le_streamer_connection.test_data)); 283 printf("%c: ATT MTU = %u => use test data of len %u\n", le_streamer_connection.name, mtu, le_streamer_connection.test_data_len); 284 // enable notifications 285 #if (TEST_MODE & TEST_MODE_ENABLE_NOTIFICATIONS) 286 printf("Start streaming - enable notify on test characteristic.\n"); 287 state = TC_W4_ENABLE_NOTIFICATIONS_COMPLETE; 288 gatt_client_write_client_characteristic_configuration(handle_gatt_client_event, connection_handle, 289 &le_streamer_characteristic_tx, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); 290 break; 291 #endif 292 state = TC_W4_TEST_DATA; 293 #if (TEST_MODE & TEST_MODE_WRITE_WITHOUT_RESPONSE) 294 printf("Start streaming - request can send now.\n"); 295 le_streamer_client_request_to_send(&le_streamer_connection); 296 #endif 297 break; 298 default: 299 break; 300 } 301 break; 302 303 case TC_W4_ENABLE_NOTIFICATIONS_COMPLETE: 304 switch(hci_event_packet_get_type(packet)){ 305 case GATT_EVENT_QUERY_COMPLETE: 306 printf("Notifications enabled, ATT status 0x%02x\n", gatt_event_query_complete_get_att_status(packet)); 307 if (gatt_event_query_complete_get_att_status(packet) != ATT_ERROR_SUCCESS) break; 308 state = TC_W4_TEST_DATA; 309 #if (TEST_MODE & TEST_MODE_WRITE_WITHOUT_RESPONSE) 310 printf("Start streaming - request can send now.\n"); 311 le_streamer_client_request_to_send(&le_streamer_connection); 312 #endif 313 break; 314 default: 315 break; 316 } 317 break; 318 319 case TC_W4_TEST_DATA: 320 switch(hci_event_packet_get_type(packet)){ 321 case GATT_EVENT_NOTIFICATION: 322 test_track_data(&le_streamer_connection, gatt_event_notification_get_value_length(packet)); 323 break; 324 case GATT_EVENT_QUERY_COMPLETE: 325 break; 326 case GATT_EVENT_CAN_WRITE_WITHOUT_RESPONSE: 327 le_streamer_handle_can_write_without_response(&le_streamer_connection); 328 break; 329 default: 330 printf("Unknown packet type 0x%02x\n", hci_event_packet_get_type(packet)); 331 break; 332 } 333 break; 334 335 default: 336 printf("error\n"); 337 break; 338 } 339 340 } 341 342 // Either connect to remote specified on command line or start scan for device with "LE Streamer" in advertisement 343 static void le_streamer_client_start(void){ 344 if (cmdline_addr_found){ 345 printf("Connect to %s\n", bd_addr_to_str(cmdline_addr)); 346 state = TC_W4_CONNECT; 347 gap_connect(cmdline_addr, 0); 348 } else { 349 printf("Start scanning!\n"); 350 state = TC_W4_SCAN_RESULT; 351 gap_set_scan_parameters(0,0x0030, 0x0030); 352 gap_start_scan(); 353 } 354 } 355 356 static void le_stream_server_found(void) { 357 // stop scanning, and connect to the device 358 state = TC_W4_CONNECT; 359 gap_stop_scan(); 360 printf("Stop scan. Connect to device with addr %s.\n", bd_addr_to_str(le_streamer_addr)); 361 gap_connect(le_streamer_addr,le_streamer_addr_type); 362 } 363 364 static void hci_event_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 365 UNUSED(channel); 366 UNUSED(size); 367 368 if (packet_type != HCI_EVENT_PACKET) return; 369 370 static const char * const phy_names[] = { 371 "1 M", "2 M", "Codec" 372 }; 373 374 uint16_t conn_interval; 375 hci_con_handle_t con_handle; 376 const uint8_t * adv_data; 377 uint8_t adv_len; 378 379 switch (hci_event_packet_get_type(packet)) { 380 case BTSTACK_EVENT_STATE: 381 // BTstack activated, get started 382 if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING) { 383 le_streamer_client_start(); 384 } else { 385 state = TC_OFF; 386 } 387 break; 388 case GAP_EVENT_ADVERTISING_REPORT: 389 if (state != TC_W4_SCAN_RESULT) return; 390 // check name in advertisement 391 adv_data = gap_event_advertising_report_get_data(packet); 392 adv_len = gap_event_advertising_report_get_data_length(packet); 393 if (!advertisement_contains_name(le_streamer_server_name, adv_len, adv_data)) return; 394 // match connecting phy 395 gap_set_connection_phys(1); 396 // store address and type 397 gap_event_advertising_report_get_address(packet, le_streamer_addr); 398 le_streamer_addr_type = gap_event_advertising_report_get_address_type(packet); 399 le_stream_server_found(); 400 break; 401 #ifdef ENABLE_LE_EXTENDED_ADVERTISING 402 case GAP_EVENT_EXTENDED_ADVERTISING_REPORT: 403 if (state != TC_W4_SCAN_RESULT) return; 404 // check name in advertisement 405 adv_data = gap_event_extended_advertising_report_get_data(packet); 406 adv_len = gap_event_extended_advertising_report_get_data_length(packet); 407 if (!advertisement_contains_name(le_streamer_server_name, adv_len, adv_data)) return; 408 // match connecting phy 409 if (gap_event_extended_advertising_report_get_primary_phy(packet) == 1){ 410 // LE 1M PHY => use 1M or 2M PHY 411 gap_set_connection_phys(3); 412 } else { 413 // Coded PHY => use Coded PHY 414 gap_set_connection_phys(4); 415 } 416 // store address and type 417 gap_event_extended_advertising_report_get_address(packet, le_streamer_addr); 418 le_streamer_addr_type = gap_event_extended_advertising_report_get_address_type(packet); 419 le_stream_server_found(); 420 break; 421 #endif 422 case HCI_EVENT_META_GAP: 423 switch (hci_event_gap_meta_get_subevent_code(packet)) { 424 case GAP_SUBEVENT_LE_CONNECTION_COMPLETE: 425 switch (hci_event_gap_meta_get_subevent_code(packet)) { 426 case GAP_SUBEVENT_LE_CONNECTION_COMPLETE: 427 if (state != TC_W4_CONNECT) return; 428 connection_handle = gap_subevent_le_connection_complete_get_connection_handle(packet); 429 // print connection parameters (without using float operations) 430 conn_interval = gap_subevent_le_connection_complete_get_conn_interval(packet); 431 printf("Connection Interval: %u.%02u ms\n", conn_interval * 125 / 100, 25 * (conn_interval & 3)); 432 printf("Connection Latency: %u\n", gap_subevent_le_connection_complete_get_conn_latency(packet)); 433 // initialize gatt client context with handle, and add it to the list of active clients 434 // query primary services 435 printf("Search for LE Streamer service.\n"); 436 state = TC_W4_SERVICE_RESULT; 437 gatt_client_discover_primary_services_by_uuid128(handle_gatt_client_event, connection_handle, le_streamer_service_uuid); 438 break; 439 default: 440 break; 441 } 442 break; 443 default: 444 break; 445 } 446 break; 447 case HCI_EVENT_LE_META: 448 switch (hci_event_le_meta_get_subevent_code(packet)){ 449 case HCI_SUBEVENT_LE_DATA_LENGTH_CHANGE: 450 con_handle = hci_subevent_le_data_length_change_get_connection_handle(packet); 451 printf("- LE Connection 0x%04x: data length change - max %u bytes per packet\n", con_handle, 452 hci_subevent_le_data_length_change_get_max_tx_octets(packet)); 453 break; 454 case HCI_SUBEVENT_LE_PHY_UPDATE_COMPLETE: 455 con_handle = hci_subevent_le_phy_update_complete_get_connection_handle(packet); 456 printf("- LE Connection 0x%04x: PHY update - using LE %s PHY now\n", con_handle, 457 phy_names[hci_subevent_le_phy_update_complete_get_tx_phy(packet)]); 458 break; 459 default: 460 break; 461 } 462 break; 463 case HCI_EVENT_DISCONNECTION_COMPLETE: 464 // unregister listener 465 connection_handle = HCI_CON_HANDLE_INVALID; 466 if (listener_registered){ 467 listener_registered = 0; 468 gatt_client_stop_listening_for_characteristic_value_updates(¬ification_listener); 469 } 470 if (cmdline_addr_found){ 471 printf("Disconnected %s\n", bd_addr_to_str(cmdline_addr)); 472 return; 473 } 474 printf("Disconnected %s\n", bd_addr_to_str(le_streamer_addr)); 475 if (state == TC_OFF) break; 476 le_streamer_client_start(); 477 break; 478 default: 479 break; 480 } 481 } 482 483 #ifdef HAVE_BTSTACK_STDIN 484 static void usage(const char *name){ 485 fprintf(stderr, "Usage: %s [-a|--address aa:bb:cc:dd:ee:ff]\n", name); 486 fprintf(stderr, "If no argument is provided, LE Streamer Client will start scanning and connect to the first device named 'LE Streamer'.\n"); 487 fprintf(stderr, "To connect to a specific device use argument [-a].\n\n"); 488 } 489 #endif 490 491 int btstack_main(int argc, const char * argv[]); 492 int btstack_main(int argc, const char * argv[]){ 493 494 495 #ifdef HAVE_BTSTACK_STDIN 496 int arg; 497 cmdline_addr_found = 0; 498 499 for (arg = 1; arg < argc; arg++) { 500 if(!strcmp(argv[arg], "-a") || !strcmp(argv[arg], "--address")){ 501 if (arg + 1 < argc) { 502 arg++; 503 cmdline_addr_found = sscanf_bd_addr(argv[arg], cmdline_addr); 504 } 505 if (!cmdline_addr_found) { 506 usage(argv[0]); 507 return 1; 508 } 509 } 510 } 511 if (!cmdline_addr_found) { 512 fprintf(stderr, "No specific address specified or found; start scanning for 'LE Streamer' advertisement.\n"); 513 } 514 #else 515 (void)argc; 516 (void)argv; 517 #endif 518 l2cap_init(); 519 520 sm_init(); 521 sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); 522 523 // sm_init needed before gatt_client_init 524 gatt_client_init(); 525 526 hci_event_callback_registration.callback = &hci_event_handler; 527 hci_add_event_handler(&hci_event_callback_registration); 528 529 // use different connection parameters: conn interval min/max (* 1.25 ms), slave latency, supervision timeout, CE len min/max (* 0.6125 ms) 530 // gap_set_connection_parameters(0x06, 0x06, 4, 1000, 0x01, 0x06 * 2); 531 532 #ifdef ENABLE_LE_EXTENDED_ADVERTISING 533 // scan on Coded and 1M PHYs 534 gap_set_scan_phys(5); 535 #endif 536 537 // turn on! 538 hci_power_control(HCI_POWER_ON); 539 540 return 0; 541 } 542 /* EXAMPLE_END */ 543