1 /* 2 * Copyright (C) 2020 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__ "hog_boot_host_demo.c" 39 40 /* 41 * hog_boot_host_demo.c 42 */ 43 44 /* EXAMPLE_START(hog_boot_host_demo): HID Boot Host LE 45 * 46 * @text This example implements a minimal HID-over-GATT Boot Host. It scans for LE HID devices, connects to it, 47 * discovers the Characteristics relevant for the HID Service and enables Notifications on them. 48 * It then dumps all Boot Keyboard and Mouse Input Reports 49 */ 50 51 #include <inttypes.h> 52 #include <stdio.h> 53 #include <btstack_tlv.h> 54 55 #include "btstack_config.h" 56 #include "btstack.h" 57 58 // TAG to store remote device address and type in TLV 59 #define TLV_TAG_HOGD ((((uint32_t) 'H') << 24 ) | (((uint32_t) 'O') << 16) | (((uint32_t) 'G') << 8) | 'D') 60 61 typedef struct { 62 bd_addr_t addr; 63 bd_addr_type_t addr_type; 64 } le_device_addr_t; 65 66 static enum { 67 W4_WORKING, 68 W4_HID_DEVICE_FOUND, 69 W4_CONNECTED, 70 W4_ENCRYPTED, 71 W4_HID_CLIENT_CONNECTED, 72 READY, 73 W4_TIMEOUT_THEN_SCAN, 74 W4_TIMEOUT_THEN_RECONNECT, 75 } app_state; 76 77 static le_device_addr_t remote_device; 78 static hci_con_handle_t connection_handle; 79 static uint16_t hids_cid; 80 81 // used to implement connection timeout and reconnect timer 82 static btstack_timer_source_t connection_timer; 83 84 // register for events from HCI/GAP and SM 85 static btstack_packet_callback_registration_t hci_event_callback_registration; 86 static btstack_packet_callback_registration_t sm_event_callback_registration; 87 88 // used to store remote device in TLV 89 static const btstack_tlv_t * btstack_tlv_singleton_impl; 90 static void * btstack_tlv_singleton_context; 91 92 // Simplified US Keyboard with Shift modifier 93 94 #define CHAR_ILLEGAL 0xff 95 #define CHAR_RETURN '\n' 96 #define CHAR_ESCAPE 27 97 #define CHAR_TAB '\t' 98 #define CHAR_BACKSPACE 0x7f 99 100 /** 101 * English (US) 102 */ 103 static const uint8_t keytable_us_none [] = { 104 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 0-3 */ 105 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', /* 4-13 */ 106 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', /* 14-23 */ 107 'u', 'v', 'w', 'x', 'y', 'z', /* 24-29 */ 108 '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', /* 30-39 */ 109 CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB, ' ', /* 40-44 */ 110 '-', '=', '[', ']', '\\', CHAR_ILLEGAL, ';', '\'', 0x60, ',', /* 45-54 */ 111 '.', '/', CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 55-60 */ 112 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 61-64 */ 113 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 65-68 */ 114 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 69-72 */ 115 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 73-76 */ 116 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 77-80 */ 117 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 81-84 */ 118 '*', '-', '+', '\n', '1', '2', '3', '4', '5', /* 85-97 */ 119 '6', '7', '8', '9', '0', '.', 0xa7, /* 97-100 */ 120 }; 121 122 static const uint8_t keytable_us_shift[] = { 123 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 0-3 */ 124 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /* 4-13 */ 125 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 14-23 */ 126 'U', 'V', 'W', 'X', 'Y', 'Z', /* 24-29 */ 127 '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', /* 30-39 */ 128 CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB, ' ', /* 40-44 */ 129 '_', '+', '{', '}', '|', CHAR_ILLEGAL, ':', '"', 0x7E, '<', /* 45-54 */ 130 '>', '?', CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 55-60 */ 131 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 61-64 */ 132 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 65-68 */ 133 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 69-72 */ 134 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 73-76 */ 135 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 77-80 */ 136 CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 81-84 */ 137 '*', '-', '+', '\n', '1', '2', '3', '4', '5', /* 85-97 */ 138 '6', '7', '8', '9', '0', '.', 0xb1, /* 97-100 */ 139 }; 140 141 /** 142 * @section HOG Boot Keyboard Handler 143 * @text Boot Keyboard Input Report contains a report of format 144 * [ modifier, reserved, 6 x usage for key 1..6 from keyboard usage] 145 * Track new usages, map key usage to actual character and simulate terminal 146 */ 147 148 /* last_keys stores keyboard report to detect new key down events */ 149 #define NUM_KEYS 6 150 static uint8_t last_keys[NUM_KEYS]; 151 152 static void handle_boot_keyboard_event(const uint8_t * report, uint16_t report_len){ 153 UNUSED(report_len); 154 155 uint8_t new_keys[NUM_KEYS]; 156 memset(new_keys, 0, sizeof(new_keys)); 157 int new_keys_count = 0; 158 159 bool shift = (report[0] & 0x22) != 0; 160 161 uint8_t key_index; 162 for (key_index = 0; key_index < NUM_KEYS; key_index++){ 163 164 uint16_t usage = report[2 + key_index]; 165 if (usage == 0) continue; 166 if (usage >= sizeof(keytable_us_none)) continue; 167 168 // store new keys 169 new_keys[new_keys_count++] = usage; 170 171 // check if usage was used last time (and ignore in that case) 172 int i; 173 for (i=0;i<NUM_KEYS;i++){ 174 if (usage == last_keys[i]){ 175 usage = 0; 176 } 177 } 178 if (usage == 0) continue; 179 180 // lookup character based on usage + shift modifier 181 uint8_t key; 182 if (shift){ 183 key = keytable_us_shift[usage]; 184 } else { 185 key = keytable_us_none[usage]; 186 } 187 if (key == CHAR_ILLEGAL) continue; 188 if (key == CHAR_BACKSPACE){ 189 printf("\b \b"); // go back one char, print space, go back one char again 190 continue; 191 } 192 printf("%c", key); 193 } 194 195 // store current as last report 196 memcpy(last_keys, new_keys, NUM_KEYS); 197 } 198 199 /** 200 * @section HOG Boot Mouse Handler 201 * @text Boot Mouse Input Report contains a report of format 202 * [ buttons, dx, dy, dz = scroll wheel] 203 * Decode packet and print on stdout 204 * 205 * @param report 206 * @param report_len 207 */ 208 static void handle_boot_mouse_event(const uint8_t * report, uint16_t report_len){ 209 UNUSED(report_len); 210 211 uint8_t buttons = report[0]; 212 int8_t dx = (int8_t) report[1]; 213 int8_t dy = (int8_t) report[2]; 214 int8_t dwheel = (int8_t) report[3]; 215 printf("Mouse: %i, %i - wheel %i - buttons 0x%02x\n", dx, dy, dwheel, buttons); 216 } 217 218 /** 219 * @section Test if advertisement contains HID UUID 220 * @param packet 221 * @param size 222 * @returns true if it does 223 */ 224 static bool adv_event_contains_hid_service(const uint8_t * packet){ 225 const uint8_t * ad_data = gap_event_advertising_report_get_data(packet); 226 uint16_t ad_len = gap_event_advertising_report_get_data_length(packet); 227 return ad_data_contains_uuid16(ad_len, ad_data, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE); 228 } 229 230 /** 231 * Start scanning 232 */ 233 static void hog_start_scan(void){ 234 printf("Scanning for LE HID devices...\n"); 235 app_state = W4_HID_DEVICE_FOUND; 236 // Passive scanning, 100% (scan interval = scan window) 237 gap_set_scan_parameters(0,48,48); 238 gap_start_scan(); 239 } 240 241 /** 242 * Handle timeout for outgoing connection 243 * @param ts 244 */ 245 static void hog_connection_timeout(btstack_timer_source_t * ts){ 246 UNUSED(ts); 247 printf("Timeout - abort connection\n"); 248 gap_connect_cancel(); 249 hog_start_scan(); 250 } 251 252 253 /** 254 * Connect to remote device but set timer for timeout 255 */ 256 static void hog_connect(void) { 257 // set timer 258 btstack_run_loop_set_timer(&connection_timer, 10000); 259 btstack_run_loop_set_timer_handler(&connection_timer, &hog_connection_timeout); 260 btstack_run_loop_add_timer(&connection_timer); 261 app_state = W4_CONNECTED; 262 gap_connect(remote_device.addr, remote_device.addr_type); 263 } 264 265 /** 266 * Handle timer event to trigger reconnect 267 * @param ts 268 */ 269 static void hog_reconnect_timeout(btstack_timer_source_t * ts){ 270 UNUSED(ts); 271 switch (app_state){ 272 case W4_TIMEOUT_THEN_RECONNECT: 273 hog_connect(); 274 break; 275 case W4_TIMEOUT_THEN_SCAN: 276 hog_start_scan(); 277 break; 278 default: 279 break; 280 } 281 } 282 283 /** 284 * Start connecting after boot up: connect to last used device if possible, start scan otherwise 285 */ 286 static void hog_start_connect(void){ 287 // check if we have a bonded device 288 btstack_tlv_get_instance(&btstack_tlv_singleton_impl, &btstack_tlv_singleton_context); 289 if (btstack_tlv_singleton_impl){ 290 int len = btstack_tlv_singleton_impl->get_tag(btstack_tlv_singleton_context, TLV_TAG_HOGD, (uint8_t *) &remote_device, sizeof(remote_device)); 291 if (len == sizeof(remote_device)){ 292 printf("Bonded, connect to device with %s address %s ...\n", remote_device.addr_type == 0 ? "public" : "random" , bd_addr_to_str(remote_device.addr)); 293 hog_connect(); 294 return; 295 } 296 } 297 // otherwise, scan for HID devices 298 hog_start_scan(); 299 } 300 301 /** 302 * In case of error, disconnect and start scanning again 303 */ 304 static void handle_outgoing_connection_error(void){ 305 printf("Error occurred, disconnect and start over\n"); 306 gap_disconnect(connection_handle); 307 hog_start_scan(); 308 } 309 310 /** 311 * Handle GATT Client Events dependent on current state 312 * 313 * @param packet_type 314 * @param channel 315 * @param packet 316 * @param size 317 */ 318 static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { 319 UNUSED(packet_type); 320 UNUSED(channel); 321 UNUSED(size); 322 323 uint8_t status; 324 uint8_t report_id; 325 326 if (hci_event_packet_get_type(packet) != HCI_EVENT_GATTSERVICE_META){ 327 return; 328 } 329 330 switch (hci_event_gattservice_meta_get_subevent_code(packet)){ 331 case GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED: 332 status = gattservice_subevent_hid_service_connected_get_status(packet); 333 switch (status){ 334 case ERROR_CODE_SUCCESS: 335 printf("HID service client connected, found %d services\n", 336 gattservice_subevent_hid_service_connected_get_num_instances(packet)); 337 338 // store device as bonded 339 if (btstack_tlv_singleton_impl){ 340 btstack_tlv_singleton_impl->store_tag(btstack_tlv_singleton_context, TLV_TAG_HOGD, (const uint8_t *) &remote_device, sizeof(remote_device)); 341 } 342 // done 343 printf("Ready - please start typing or mousing..\n"); 344 app_state = READY; 345 break; 346 default: 347 printf("HID service client connection failed, err 0x%02x.\n", status); 348 handle_outgoing_connection_error(); 349 break; 350 } 351 break; 352 case GATTSERVICE_SUBEVENT_HID_REPORT: 353 report_id = gattservice_subevent_hid_report_get_report_id(packet); 354 switch (report_id){ 355 case HID_BOOT_MODE_MOUSE_ID: 356 handle_boot_mouse_event( 357 gattservice_subevent_hid_report_get_report(packet), 358 gattservice_subevent_hid_report_get_report_len(packet)); 359 break; 360 case HID_BOOT_MODE_KEYBOARD_ID: 361 handle_boot_keyboard_event( 362 gattservice_subevent_hid_report_get_report(packet), 363 gattservice_subevent_hid_report_get_report_len(packet)); 364 break; 365 default: 366 break; 367 } 368 break; 369 370 default: 371 break; 372 } 373 } 374 375 /* LISTING_START(packetHandler): Packet Handler */ 376 static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 377 /* LISTING_PAUSE */ 378 UNUSED(channel); 379 UNUSED(size); 380 uint8_t event; 381 uint8_t status; 382 /* LISTING_RESUME */ 383 switch (packet_type) { 384 case HCI_EVENT_PACKET: 385 event = hci_event_packet_get_type(packet); 386 switch (event) { 387 case BTSTACK_EVENT_STATE: 388 if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) break; 389 btstack_assert(app_state == W4_WORKING); 390 391 hog_start_connect(); 392 break; 393 case GAP_EVENT_ADVERTISING_REPORT: 394 if (app_state != W4_HID_DEVICE_FOUND) break; 395 if (adv_event_contains_hid_service(packet) == false) break; 396 // stop scan 397 gap_stop_scan(); 398 // store remote device address and type 399 gap_event_advertising_report_get_address(packet, remote_device.addr); 400 remote_device.addr_type = gap_event_advertising_report_get_address_type(packet); 401 // connect 402 printf("Found, connect to device with %s address %s ...\n", remote_device.addr_type == 0 ? "public" : "random" , bd_addr_to_str(remote_device.addr)); 403 hog_connect(); 404 break; 405 case HCI_EVENT_DISCONNECTION_COMPLETE: 406 if (app_state != READY) break; 407 connection_handle = HCI_CON_HANDLE_INVALID; 408 switch (app_state){ 409 case READY: 410 printf("\nDisconnected, try to reconnect...\n"); 411 app_state = W4_TIMEOUT_THEN_RECONNECT; 412 break; 413 default: 414 printf("\nDisconnected, start over...\n"); 415 app_state = W4_TIMEOUT_THEN_SCAN; 416 break; 417 } 418 // set timer 419 btstack_run_loop_set_timer(&connection_timer, 100); 420 btstack_run_loop_set_timer_handler(&connection_timer, &hog_reconnect_timeout); 421 btstack_run_loop_add_timer(&connection_timer); 422 break; 423 case HCI_EVENT_LE_META: 424 // wait for connection complete 425 if (hci_event_le_meta_get_subevent_code(packet) != HCI_SUBEVENT_LE_CONNECTION_COMPLETE) break; 426 if (app_state != W4_CONNECTED) return; 427 btstack_run_loop_remove_timer(&connection_timer); 428 connection_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); 429 // request security 430 app_state = W4_ENCRYPTED; 431 sm_request_pairing(connection_handle); 432 break; 433 case HCI_EVENT_ENCRYPTION_CHANGE: 434 if (connection_handle != hci_event_encryption_change_get_connection_handle(packet)) break; 435 printf("Connection encrypted: %u\n", hci_event_encryption_change_get_encryption_enabled(packet)); 436 if (hci_event_encryption_change_get_encryption_enabled(packet) == 0){ 437 printf("Encryption failed -> abort\n"); 438 handle_outgoing_connection_error(); 439 break; 440 } 441 // continue - query primary services 442 printf("Search for HID service.\n"); 443 app_state = W4_HID_CLIENT_CONNECTED; 444 445 status = hids_client_connect(connection_handle, handle_gatt_client_event, HID_PROTOCOL_MODE_BOOT, &hids_cid); 446 if (status != ERROR_CODE_SUCCESS){ 447 printf("HID client connection failed, status 0x%02x\n", status); 448 } 449 break; 450 default: 451 break; 452 } 453 break; 454 default: 455 break; 456 } 457 } 458 /* LISTING_END */ 459 460 /* @section HCI packet handler 461 * 462 * @text The SM packet handler receives Security Manager Events required for pairing. 463 * It also receives events generated during Identity Resolving 464 * see Listing SMPacketHandler. 465 */ 466 467 /* LISTING_START(SMPacketHandler): Scanning and receiving advertisements */ 468 469 static void sm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 470 UNUSED(channel); 471 UNUSED(size); 472 473 if (packet_type != HCI_EVENT_PACKET) return; 474 475 switch (hci_event_packet_get_type(packet)) { 476 case SM_EVENT_JUST_WORKS_REQUEST: 477 printf("Just works requested\n"); 478 sm_just_works_confirm(sm_event_just_works_request_get_handle(packet)); 479 break; 480 case SM_EVENT_NUMERIC_COMPARISON_REQUEST: 481 printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet)); 482 sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet)); 483 break; 484 case SM_EVENT_PASSKEY_DISPLAY_NUMBER: 485 printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet)); 486 break; 487 case SM_EVENT_PAIRING_COMPLETE: 488 switch (sm_event_pairing_complete_get_status(packet)){ 489 case ERROR_CODE_SUCCESS: 490 printf("Pairing complete, success\n"); 491 break; 492 case ERROR_CODE_CONNECTION_TIMEOUT: 493 printf("Pairing failed, timeout\n"); 494 break; 495 case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION: 496 printf("Pairing faileed, disconnected\n"); 497 break; 498 case ERROR_CODE_AUTHENTICATION_FAILURE: 499 printf("Pairing failed, reason = %u\n", sm_event_pairing_complete_get_reason(packet)); 500 break; 501 default: 502 break; 503 } 504 break; 505 default: 506 break; 507 } 508 } 509 /* LISTING_END */ 510 511 int btstack_main(int argc, const char * argv[]); 512 int btstack_main(int argc, const char * argv[]){ 513 514 (void)argc; 515 (void)argv; 516 517 /* LISTING_START(HogBootHostSetup): HID-over-GATT Boot Host Setup */ 518 519 // register for events from HCI 520 hci_event_callback_registration.callback = &packet_handler; 521 hci_add_event_handler(&hci_event_callback_registration); 522 523 // register for events from Security Manager 524 sm_event_callback_registration.callback = &sm_packet_handler; 525 sm_add_event_handler(&sm_event_callback_registration); 526 527 // setup le device db 528 le_device_db_init(); 529 530 // 531 l2cap_init(); 532 sm_init(); 533 gatt_client_init(); 534 hids_client_init(); 535 536 /* LISTING_END */ 537 538 // Disable stdout buffering 539 setbuf(stdout, NULL); 540 541 app_state = W4_WORKING; 542 543 // Turn on the device 544 hci_power_control(HCI_POWER_ON); 545 return 0; 546 } 547 548 /* EXAMPLE_END */ 549