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 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_credit_based_flow_control_mode_client.c" 39 40 // ***************************************************************************** 41 /* EXAMPLE_START(le_credit_based_flow_control_mode_client): LE Credit-Based Flow-Control Mode Client - Send Data over L2CAP 42 * 43 * @text Connects to 'LE CBM Server' and streams data 44 * via L2CAP Channel in LE Credit-Based Flow-Control Mode (CBM) 45 */ 46 // ***************************************************************************** 47 48 #include <inttypes.h> 49 #include <stdint.h> 50 #include <stdio.h> 51 #include <stdlib.h> 52 #include <string.h> 53 54 #include "btstack.h" 55 56 #define TEST_STREAM_DATA 57 #define TEST_PACKET_SIZE 1000 58 59 static enum { 60 TC_OFF, 61 TC_IDLE, 62 TC_W4_SCAN_RESULT, 63 TC_W4_CONNECT, 64 TC_W4_CHANNEL, 65 TC_TEST_DATA 66 } state = TC_OFF; 67 68 const uint16_t TSPX_le_psm = 0x25; 69 70 static bd_addr_t cmdline_addr; 71 static int cmdline_addr_found = 0; 72 73 // addr and type of device with correct name 74 static bd_addr_t le_cbm_server_addr; 75 static bd_addr_type_t le_cbm_server_addr_type; 76 77 static hci_con_handle_t connection_handle; 78 static btstack_packet_callback_registration_t hci_event_callback_registration; 79 static btstack_packet_callback_registration_t sm_event_callback_registration; 80 81 static uint8_t cbm_receive_buffer[TEST_PACKET_SIZE]; 82 83 /* 84 * @section Track throughput 85 * @text We calculate the throughput by setting a start time and measuring the amount of 86 * data sent. After a configurable REPORT_INTERVAL_MS, we print the throughput in kB/s 87 * and reset the counter and start time. 88 */ 89 90 /* LISTING_START(tracking): Tracking throughput */ 91 92 #define REPORT_INTERVAL_MS 3000 93 94 // support for multiple clients 95 typedef struct { 96 char name; 97 hci_con_handle_t connection_handle; 98 uint16_t cid; 99 int counter; 100 char test_data[TEST_PACKET_SIZE]; 101 int test_data_len; 102 uint32_t test_data_sent; 103 uint32_t test_data_start; 104 } le_cbm_connection_t; 105 106 static le_cbm_connection_t le_cbm_connection; 107 108 static void test_reset(le_cbm_connection_t * context){ 109 context->test_data_start = btstack_run_loop_get_time_ms(); 110 context->test_data_sent = 0; 111 } 112 113 static void test_track_data(le_cbm_connection_t * context, int bytes_transferred){ 114 context->test_data_sent += bytes_transferred; 115 // evaluate 116 uint32_t now = btstack_run_loop_get_time_ms(); 117 uint32_t time_passed = now - context->test_data_start; 118 if (time_passed < REPORT_INTERVAL_MS) return; 119 // print speed 120 int bytes_per_second = context->test_data_sent * 1000 / time_passed; 121 printf("%c: %"PRIu32" bytes -> %u.%03u kB/s\n", context->name, context->test_data_sent, bytes_per_second / 1000, bytes_per_second % 1000); 122 123 // restart 124 context->test_data_start = now; 125 context->test_data_sent = 0; 126 } 127 /* LISTING_END(tracking): Tracking throughput */ 128 129 130 // returns 1 if name is found in advertisement 131 static int advertisement_report_contains_name(const char * name, uint8_t * advertisement_report){ 132 // get advertisement from report event 133 const uint8_t * adv_data = gap_event_advertising_report_get_data(advertisement_report); 134 uint8_t adv_len = gap_event_advertising_report_get_data_length(advertisement_report); 135 uint16_t name_len = (uint16_t) strlen(name); 136 137 // iterate over advertisement data 138 ad_context_t context; 139 for (ad_iterator_init(&context, adv_len, adv_data) ; ad_iterator_has_more(&context) ; ad_iterator_next(&context)){ 140 uint8_t data_type = ad_iterator_get_data_type(&context); 141 uint8_t data_size = ad_iterator_get_data_len(&context); 142 const uint8_t * data = ad_iterator_get_data(&context); 143 switch (data_type){ 144 case BLUETOOTH_DATA_TYPE_SHORTENED_LOCAL_NAME: 145 case BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME: 146 // compare prefix 147 if (data_size < name_len) break; 148 if (memcmp(data, name, name_len) == 0) return 1; 149 return 1; 150 default: 151 break; 152 } 153 } 154 return 0; 155 } 156 157 #ifdef TEST_STREAM_DATA 158 /* LISTING_END */ 159 /* 160 * @section Streamer 161 * 162 * @text The streamer function checks if notifications are enabled and if a notification can be sent now. 163 * It creates some test data - a single letter that gets increased every time - and tracks the data sent. 164 */ 165 166 /* LISTING_START(streamer): Streaming code */ 167 static void streamer(void){ 168 169 // create test data 170 le_cbm_connection.counter++; 171 if (le_cbm_connection.counter > 'Z') le_cbm_connection.counter = 'A'; 172 memset(le_cbm_connection.test_data, le_cbm_connection.counter, le_cbm_connection.test_data_len); 173 174 // send 175 l2cap_send(le_cbm_connection.cid, (uint8_t *) le_cbm_connection.test_data, le_cbm_connection.test_data_len); 176 177 // track 178 test_track_data(&le_cbm_connection, le_cbm_connection.test_data_len); 179 180 // request another packet 181 l2cap_request_can_send_now_event(le_cbm_connection.cid); 182 } 183 /* LISTING_END */ 184 #endif 185 186 // Either connect to remote specified on command line or start scan for device with "LE CBM Server" in advertisement 187 static void le_cbm_client_start(void){ 188 if (cmdline_addr_found){ 189 printf("Connect to %s\n", bd_addr_to_str(cmdline_addr)); 190 state = TC_W4_CONNECT; 191 gap_connect(cmdline_addr, 0); 192 } else { 193 printf("Start scanning!\n"); 194 state = TC_W4_SCAN_RESULT; 195 gap_set_scan_parameters(0,0x0030, 0x0030); 196 gap_start_scan(); 197 } 198 } 199 200 static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 201 UNUSED(channel); 202 UNUSED(size); 203 204 bd_addr_t event_address; 205 uint16_t psm; 206 uint16_t cid; 207 uint16_t conn_interval; 208 hci_con_handle_t handle; 209 uint8_t status; 210 211 switch (packet_type) { 212 case HCI_EVENT_PACKET: 213 switch (hci_event_packet_get_type(packet)) { 214 case BTSTACK_EVENT_STATE: 215 // BTstack activated, get started 216 if (btstack_event_state_get_state(packet) == HCI_STATE_WORKING) { 217 le_cbm_client_start(); 218 } else { 219 state = TC_OFF; 220 } 221 break; 222 case GAP_EVENT_ADVERTISING_REPORT: 223 if (state != TC_W4_SCAN_RESULT) return; 224 // check name in advertisement 225 if (!advertisement_report_contains_name("LE CBM Server", packet)) return; 226 // store address and type 227 gap_event_advertising_report_get_address(packet, le_cbm_server_addr); 228 le_cbm_server_addr_type = gap_event_advertising_report_get_address_type(packet); 229 // stop scanning, and connect to the device 230 state = TC_W4_CONNECT; 231 gap_stop_scan(); 232 printf("Stop scan. Connect to device with addr %s.\n", bd_addr_to_str(le_cbm_server_addr)); 233 gap_connect(le_cbm_server_addr, le_cbm_server_addr_type); 234 break; 235 case HCI_EVENT_LE_META: 236 // wait for connection complete 237 if (hci_event_le_meta_get_subevent_code(packet) != HCI_SUBEVENT_LE_CONNECTION_COMPLETE) break; 238 if (state != TC_W4_CONNECT) return; 239 connection_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); 240 // print connection parameters (without using float operations) 241 conn_interval = hci_subevent_le_connection_complete_get_conn_interval(packet); 242 printf("Connection Interval: %u.%02u ms\n", conn_interval * 125 / 100, 25 * (conn_interval & 3)); 243 printf("Connection Latency: %u\n", hci_subevent_le_connection_complete_get_conn_latency(packet)); 244 // initialize gatt client context with handle, and add it to the list of active clients 245 // query primary services 246 printf("Connect to performance test service.\n"); 247 state = TC_W4_CHANNEL; 248 l2cap_cbm_create_channel(&packet_handler, connection_handle, TSPX_le_psm, cbm_receive_buffer, 249 sizeof(cbm_receive_buffer), L2CAP_LE_AUTOMATIC_CREDITS, LEVEL_0, &le_cbm_connection.cid); 250 break; 251 case HCI_EVENT_DISCONNECTION_COMPLETE: 252 if (cmdline_addr_found){ 253 printf("Disconnected %s\n", bd_addr_to_str(cmdline_addr)); 254 return; 255 } 256 printf("Disconnected %s\n", bd_addr_to_str(le_cbm_server_addr)); 257 if (state == TC_OFF) break; 258 le_cbm_client_start(); 259 break; 260 case L2CAP_EVENT_CBM_CHANNEL_OPENED: 261 // inform about new l2cap connection 262 l2cap_event_cbm_channel_opened_get_address(packet, event_address); 263 psm = l2cap_event_cbm_channel_opened_get_psm(packet); 264 cid = l2cap_event_cbm_channel_opened_get_local_cid(packet); 265 handle = l2cap_event_cbm_channel_opened_get_handle(packet); 266 status = l2cap_event_cbm_channel_opened_get_status(packet); 267 if (status == ERROR_CODE_SUCCESS) { 268 printf("L2CAP: CBM Channel successfully opened: %s, handle 0x%02x, psm 0x%02x, local cid 0x%02x, remote cid 0x%02x\n", 269 bd_addr_to_str(event_address), handle, psm, cid, little_endian_read_16(packet, 15)); 270 le_cbm_connection.cid = cid; 271 le_cbm_connection.connection_handle = handle; 272 le_cbm_connection.test_data_len = btstack_min(l2cap_event_cbm_channel_opened_get_remote_mtu(packet), sizeof(le_cbm_connection.test_data)); 273 state = TC_TEST_DATA; 274 printf("Test packet size: %u\n", le_cbm_connection.test_data_len); 275 test_reset(&le_cbm_connection); 276 #ifdef TEST_STREAM_DATA 277 l2cap_request_can_send_now_event(le_cbm_connection.cid); 278 #endif 279 } else { 280 printf("L2CAP: Connection to device %s failed. status code 0x%02x\n", bd_addr_to_str(event_address), status); 281 } 282 break; 283 284 #ifdef TEST_STREAM_DATA 285 case L2CAP_EVENT_CAN_SEND_NOW: 286 streamer(); 287 break; 288 #endif 289 290 case L2CAP_EVENT_CHANNEL_CLOSED: 291 cid = l2cap_event_channel_closed_get_local_cid(packet); 292 printf("L2CAP: Channel closed 0x%02x\n", cid); 293 break; 294 295 default: 296 break; 297 } 298 break; 299 300 case L2CAP_DATA_PACKET: 301 test_track_data(&le_cbm_connection, size); 302 break; 303 304 default: 305 break; 306 } 307 } 308 309 /* 310 * @section SM Packet Handler 311 * 312 * @text The packet handler is used to handle pairing requests 313 */ 314 static void sm_packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 315 UNUSED(channel); 316 UNUSED(size); 317 318 if (packet_type != HCI_EVENT_PACKET) return; 319 320 switch (hci_event_packet_get_type(packet)) { 321 case SM_EVENT_JUST_WORKS_REQUEST: 322 printf("Just Works requested\n"); 323 sm_just_works_confirm(sm_event_just_works_request_get_handle(packet)); 324 break; 325 case SM_EVENT_NUMERIC_COMPARISON_REQUEST: 326 printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet)); 327 sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet)); 328 break; 329 case SM_EVENT_PASSKEY_DISPLAY_NUMBER: 330 printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet)); 331 break; 332 default: 333 break; 334 } 335 } 336 337 #ifdef HAVE_BTSTACK_STDIN 338 static void usage(const char *name){ 339 fprintf(stderr, "Usage: %s [-a|--address aa:bb:cc:dd:ee:ff]\n", name); 340 fprintf(stderr, "If no argument is provided, LE CBM Client will start scanning and connect to the first device named 'LE CBM Server'.\n"); 341 fprintf(stderr, "To connect to a specific device use argument [-a].\n\n"); 342 } 343 #endif 344 345 int btstack_main(int argc, const char * argv[]); 346 int btstack_main(int argc, const char * argv[]){ 347 348 #ifdef HAVE_BTSTACK_STDIN 349 int arg = 1; 350 cmdline_addr_found = 0; 351 352 while (arg < argc) { 353 if(!strcmp(argv[arg], "-a") || !strcmp(argv[arg], "--address")){ 354 arg++; 355 cmdline_addr_found = sscanf_bd_addr(argv[arg], cmdline_addr); 356 arg++; 357 if (!cmdline_addr_found) exit(1); 358 continue; 359 } 360 usage(argv[0]); 361 return 0; 362 } 363 #else 364 (void)argc; 365 (void)argv; 366 #endif 367 368 l2cap_init(); 369 370 sm_init(); 371 sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT); 372 373 hci_event_callback_registration.callback = &packet_handler; 374 hci_add_event_handler(&hci_event_callback_registration); 375 376 sm_event_callback_registration.callback = &sm_packet_handler; 377 sm_add_event_handler(&sm_event_callback_registration); 378 379 // turn on! 380 hci_power_control(HCI_POWER_ON); 381 382 return 0; 383 } 384 /* EXAMPLE_END */ 385