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
test_reset(le_cbm_connection_t * context)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
test_track_data(le_cbm_connection_t * context,int bytes_transferred)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 true if name is found in advertisement
advertisement_report_contains_name(const char * name,uint8_t * advertisement_report)131 static bool 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 true;
149 break;
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 */
streamer(void)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
le_cbm_client_start(void)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
packet_handler(uint8_t packet_type,uint16_t channel,uint8_t * packet,uint16_t size)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_META_GAP:
236 // wait for connection complete
237 if (hci_event_gap_meta_get_subevent_code(packet) != GAP_SUBEVENT_LE_CONNECTION_COMPLETE) break;
238 if (state != TC_W4_CONNECT) return;
239 connection_handle = gap_subevent_le_connection_complete_get_connection_handle(packet);
240 // print connection parameters (without using float operations)
241 conn_interval = gap_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", gap_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%04x, 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 */
sm_packet_handler(uint8_t packet_type,uint16_t channel,uint8_t * packet,uint16_t size)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
usage(const char * name)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[]);
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;
350 cmdline_addr_found = 0;
351
352 for (arg = 1; arg < argc; arg++) {
353 if(!strcmp(argv[arg], "-a") || !strcmp(argv[arg], "--address")){
354 if (arg + 1 < argc) {
355 arg++;
356 cmdline_addr_found = sscanf_bd_addr(argv[arg], cmdline_addr);
357 }
358 if (!cmdline_addr_found) {
359 usage(argv[0]);
360 return 1;
361 }
362 }
363 }
364 if (!cmdline_addr_found) {
365 fprintf(stderr, "No specific address specified or found; start scanning for 'LE Streamer' advertisement.\n");
366 }
367 #else
368 (void)argc;
369 (void)argv;
370 #endif
371
372 l2cap_init();
373
374 sm_init();
375 sm_set_io_capabilities(IO_CAPABILITY_NO_INPUT_NO_OUTPUT);
376
377 hci_event_callback_registration.callback = &packet_handler;
378 hci_add_event_handler(&hci_event_callback_registration);
379
380 sm_event_callback_registration.callback = &sm_packet_handler;
381 sm_add_event_handler(&sm_event_callback_registration);
382
383 // turn on!
384 hci_power_control(HCI_POWER_ON);
385
386 return 0;
387 }
388 /* EXAMPLE_END */
389