172a8858fSMilanka Ringwald /* 272a8858fSMilanka Ringwald * Copyright (C) 2020 BlueKitchen GmbH 372a8858fSMilanka Ringwald * 472a8858fSMilanka Ringwald * Redistribution and use in source and binary forms, with or without 572a8858fSMilanka Ringwald * modification, are permitted provided that the following conditions 672a8858fSMilanka Ringwald * are met: 772a8858fSMilanka Ringwald * 872a8858fSMilanka Ringwald * 1. Redistributions of source code must retain the above copyright 972a8858fSMilanka Ringwald * notice, this list of conditions and the following disclaimer. 1072a8858fSMilanka Ringwald * 2. Redistributions in binary form must reproduce the above copyright 1172a8858fSMilanka Ringwald * notice, this list of conditions and the following disclaimer in the 1272a8858fSMilanka Ringwald * documentation and/or other materials provided with the distribution. 1372a8858fSMilanka Ringwald * 3. Neither the name of the copyright holders nor the names of 1472a8858fSMilanka Ringwald * contributors may be used to endorse or promote products derived 1572a8858fSMilanka Ringwald * from this software without specific prior written permission. 1672a8858fSMilanka Ringwald * 4. Any redistribution, use, or modification is done solely for 1772a8858fSMilanka Ringwald * personal benefit and not for any commercial purpose or for 1872a8858fSMilanka Ringwald * monetary gain. 1972a8858fSMilanka Ringwald * 2072a8858fSMilanka Ringwald * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS 2172a8858fSMilanka Ringwald * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 2272a8858fSMilanka Ringwald * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 2372a8858fSMilanka Ringwald * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS 2472a8858fSMilanka Ringwald * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 2572a8858fSMilanka Ringwald * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 2672a8858fSMilanka Ringwald * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 2772a8858fSMilanka Ringwald * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 2872a8858fSMilanka Ringwald * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 2972a8858fSMilanka Ringwald * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 3072a8858fSMilanka Ringwald * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3172a8858fSMilanka Ringwald * SUCH DAMAGE. 3272a8858fSMilanka Ringwald * 3372a8858fSMilanka Ringwald * Please inquire about commercial licensing options at 3472a8858fSMilanka Ringwald * [email protected] 3572a8858fSMilanka Ringwald * 3672a8858fSMilanka Ringwald */ 3772a8858fSMilanka Ringwald 3872a8858fSMilanka Ringwald #define BTSTACK_FILE__ "hog_boot_host_demo.c" 3972a8858fSMilanka Ringwald 4072a8858fSMilanka Ringwald /* 4172a8858fSMilanka Ringwald * hog_boot_host_demo.c 4272a8858fSMilanka Ringwald */ 4372a8858fSMilanka Ringwald 4472a8858fSMilanka Ringwald /* EXAMPLE_START(hog_boot_host_demo): HID Boot Host LE 4572a8858fSMilanka Ringwald * 4672a8858fSMilanka Ringwald * @text This example implements a minimal HID-over-GATT Boot Host. It scans for LE HID devices, connects to it, 4772a8858fSMilanka Ringwald * discovers the Characteristics relevant for the HID Service and enables Notifications on them. 4872a8858fSMilanka Ringwald * It then dumps all Boot Keyboard and Mouse Input Reports 4972a8858fSMilanka Ringwald */ 5072a8858fSMilanka Ringwald 5172a8858fSMilanka Ringwald #include <inttypes.h> 5272a8858fSMilanka Ringwald #include <stdio.h> 5372a8858fSMilanka Ringwald #include <btstack_tlv.h> 5472a8858fSMilanka Ringwald 5572a8858fSMilanka Ringwald #include "btstack_config.h" 5672a8858fSMilanka Ringwald #include "btstack.h" 5772a8858fSMilanka Ringwald 5872a8858fSMilanka Ringwald // TAG to store remote device address and type in TLV 5972a8858fSMilanka Ringwald #define TLV_TAG_HOGD ((((uint32_t) 'H') << 24 ) | (((uint32_t) 'O') << 16) | (((uint32_t) 'G') << 8) | 'D') 6072a8858fSMilanka Ringwald 6172a8858fSMilanka Ringwald typedef struct { 6272a8858fSMilanka Ringwald bd_addr_t addr; 6372a8858fSMilanka Ringwald bd_addr_type_t addr_type; 6472a8858fSMilanka Ringwald } le_device_addr_t; 6572a8858fSMilanka Ringwald 6672a8858fSMilanka Ringwald static enum { 6772a8858fSMilanka Ringwald W4_WORKING, 6872a8858fSMilanka Ringwald W4_HID_DEVICE_FOUND, 6972a8858fSMilanka Ringwald W4_CONNECTED, 7072a8858fSMilanka Ringwald W4_ENCRYPTED, 71*2901a9b7SMilanka Ringwald W4_HID_CLIENT_CONNECTED, 7272a8858fSMilanka Ringwald READY, 7372a8858fSMilanka Ringwald W4_TIMEOUT_THEN_SCAN, 7472a8858fSMilanka Ringwald W4_TIMEOUT_THEN_RECONNECT, 7572a8858fSMilanka Ringwald } app_state; 7672a8858fSMilanka Ringwald 7772a8858fSMilanka Ringwald static le_device_addr_t remote_device; 7872a8858fSMilanka Ringwald static hci_con_handle_t connection_handle; 7972a8858fSMilanka Ringwald static uint16_t hids_cid; 8072a8858fSMilanka Ringwald 8172a8858fSMilanka Ringwald // used to implement connection timeout and reconnect timer 8272a8858fSMilanka Ringwald static btstack_timer_source_t connection_timer; 8372a8858fSMilanka Ringwald 8472a8858fSMilanka Ringwald // register for events from HCI/GAP and SM 8572a8858fSMilanka Ringwald static btstack_packet_callback_registration_t hci_event_callback_registration; 8672a8858fSMilanka Ringwald static btstack_packet_callback_registration_t sm_event_callback_registration; 8772a8858fSMilanka Ringwald 8872a8858fSMilanka Ringwald // used to store remote device in TLV 8972a8858fSMilanka Ringwald static const btstack_tlv_t * btstack_tlv_singleton_impl; 9072a8858fSMilanka Ringwald static void * btstack_tlv_singleton_context; 9172a8858fSMilanka Ringwald 9272a8858fSMilanka Ringwald // Simplified US Keyboard with Shift modifier 9372a8858fSMilanka Ringwald 9472a8858fSMilanka Ringwald #define CHAR_ILLEGAL 0xff 9572a8858fSMilanka Ringwald #define CHAR_RETURN '\n' 9672a8858fSMilanka Ringwald #define CHAR_ESCAPE 27 9772a8858fSMilanka Ringwald #define CHAR_TAB '\t' 9872a8858fSMilanka Ringwald #define CHAR_BACKSPACE 0x7f 9972a8858fSMilanka Ringwald 10072a8858fSMilanka Ringwald /** 10172a8858fSMilanka Ringwald * English (US) 10272a8858fSMilanka Ringwald */ 10372a8858fSMilanka Ringwald static const uint8_t keytable_us_none [] = { 10472a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 0-3 */ 10572a8858fSMilanka Ringwald 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', /* 4-13 */ 10672a8858fSMilanka Ringwald 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', /* 14-23 */ 10772a8858fSMilanka Ringwald 'u', 'v', 'w', 'x', 'y', 'z', /* 24-29 */ 10872a8858fSMilanka Ringwald '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', /* 30-39 */ 10972a8858fSMilanka Ringwald CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB, ' ', /* 40-44 */ 11072a8858fSMilanka Ringwald '-', '=', '[', ']', '\\', CHAR_ILLEGAL, ';', '\'', 0x60, ',', /* 45-54 */ 11172a8858fSMilanka Ringwald '.', '/', CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 55-60 */ 11272a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 61-64 */ 11372a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 65-68 */ 11472a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 69-72 */ 11572a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 73-76 */ 11672a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 77-80 */ 11772a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 81-84 */ 11872a8858fSMilanka Ringwald '*', '-', '+', '\n', '1', '2', '3', '4', '5', /* 85-97 */ 11972a8858fSMilanka Ringwald '6', '7', '8', '9', '0', '.', 0xa7, /* 97-100 */ 12072a8858fSMilanka Ringwald }; 12172a8858fSMilanka Ringwald 12272a8858fSMilanka Ringwald static const uint8_t keytable_us_shift[] = { 12372a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 0-3 */ 12472a8858fSMilanka Ringwald 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /* 4-13 */ 12572a8858fSMilanka Ringwald 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 14-23 */ 12672a8858fSMilanka Ringwald 'U', 'V', 'W', 'X', 'Y', 'Z', /* 24-29 */ 12772a8858fSMilanka Ringwald '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', /* 30-39 */ 12872a8858fSMilanka Ringwald CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB, ' ', /* 40-44 */ 12972a8858fSMilanka Ringwald '_', '+', '{', '}', '|', CHAR_ILLEGAL, ':', '"', 0x7E, '<', /* 45-54 */ 13072a8858fSMilanka Ringwald '>', '?', CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 55-60 */ 13172a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 61-64 */ 13272a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 65-68 */ 13372a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 69-72 */ 13472a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 73-76 */ 13572a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 77-80 */ 13672a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 81-84 */ 13772a8858fSMilanka Ringwald '*', '-', '+', '\n', '1', '2', '3', '4', '5', /* 85-97 */ 13872a8858fSMilanka Ringwald '6', '7', '8', '9', '0', '.', 0xb1, /* 97-100 */ 13972a8858fSMilanka Ringwald }; 14072a8858fSMilanka Ringwald 14172a8858fSMilanka Ringwald /** 14272a8858fSMilanka Ringwald * @section HOG Boot Keyboard Handler 14372a8858fSMilanka Ringwald * @text Boot Keyboard Input Report contains a report of format 14472a8858fSMilanka Ringwald * [ modifier, reserved, 6 x usage for key 1..6 from keyboard usage] 14572a8858fSMilanka Ringwald * Track new usages, map key usage to actual character and simulate terminal 14672a8858fSMilanka Ringwald */ 14772a8858fSMilanka Ringwald 14872a8858fSMilanka Ringwald /* last_keys stores keyboard report to detect new key down events */ 14972a8858fSMilanka Ringwald #define NUM_KEYS 6 15072a8858fSMilanka Ringwald static uint8_t last_keys[NUM_KEYS]; 15172a8858fSMilanka Ringwald 15272a8858fSMilanka Ringwald static void handle_boot_keyboard_event(const uint8_t * report, uint16_t report_len){ 15372a8858fSMilanka Ringwald UNUSED(report_len); 15472a8858fSMilanka Ringwald 15572a8858fSMilanka Ringwald uint8_t new_keys[NUM_KEYS]; 15672a8858fSMilanka Ringwald memset(new_keys, 0, sizeof(new_keys)); 15772a8858fSMilanka Ringwald int new_keys_count = 0; 15872a8858fSMilanka Ringwald 15972a8858fSMilanka Ringwald bool shift = (report[0] & 0x22) != 0; 16072a8858fSMilanka Ringwald 16172a8858fSMilanka Ringwald uint8_t key_index; 16272a8858fSMilanka Ringwald for (key_index = 0; key_index < NUM_KEYS; key_index++){ 16372a8858fSMilanka Ringwald 16472a8858fSMilanka Ringwald uint16_t usage = report[2 + key_index]; 16572a8858fSMilanka Ringwald if (usage == 0) continue; 16672a8858fSMilanka Ringwald if (usage >= sizeof(keytable_us_none)) continue; 16772a8858fSMilanka Ringwald 16872a8858fSMilanka Ringwald // store new keys 16972a8858fSMilanka Ringwald new_keys[new_keys_count++] = usage; 17072a8858fSMilanka Ringwald 17172a8858fSMilanka Ringwald // check if usage was used last time (and ignore in that case) 17272a8858fSMilanka Ringwald int i; 17372a8858fSMilanka Ringwald for (i=0;i<NUM_KEYS;i++){ 17472a8858fSMilanka Ringwald if (usage == last_keys[i]){ 17572a8858fSMilanka Ringwald usage = 0; 17672a8858fSMilanka Ringwald } 17772a8858fSMilanka Ringwald } 17872a8858fSMilanka Ringwald if (usage == 0) continue; 17972a8858fSMilanka Ringwald 18072a8858fSMilanka Ringwald // lookup character based on usage + shift modifier 18172a8858fSMilanka Ringwald uint8_t key; 18272a8858fSMilanka Ringwald if (shift){ 18372a8858fSMilanka Ringwald key = keytable_us_shift[usage]; 18472a8858fSMilanka Ringwald } else { 18572a8858fSMilanka Ringwald key = keytable_us_none[usage]; 18672a8858fSMilanka Ringwald } 18772a8858fSMilanka Ringwald if (key == CHAR_ILLEGAL) continue; 18872a8858fSMilanka Ringwald if (key == CHAR_BACKSPACE){ 18972a8858fSMilanka Ringwald printf("\b \b"); // go back one char, print space, go back one char again 19072a8858fSMilanka Ringwald continue; 19172a8858fSMilanka Ringwald } 19272a8858fSMilanka Ringwald printf("%c", key); 19372a8858fSMilanka Ringwald } 19472a8858fSMilanka Ringwald 19572a8858fSMilanka Ringwald // store current as last report 19672a8858fSMilanka Ringwald memcpy(last_keys, new_keys, NUM_KEYS); 19772a8858fSMilanka Ringwald } 19872a8858fSMilanka Ringwald 19972a8858fSMilanka Ringwald /** 20072a8858fSMilanka Ringwald * @section HOG Boot Mouse Handler 20172a8858fSMilanka Ringwald * @text Boot Mouse Input Report contains a report of format 20272a8858fSMilanka Ringwald * [ buttons, dx, dy, dz = scroll wheel] 20372a8858fSMilanka Ringwald * Decode packet and print on stdout 20472a8858fSMilanka Ringwald * 20572a8858fSMilanka Ringwald * @param report 20672a8858fSMilanka Ringwald * @param report_len 20772a8858fSMilanka Ringwald */ 20872a8858fSMilanka Ringwald static void handle_boot_mouse_event(const uint8_t * report, uint16_t report_len){ 20972a8858fSMilanka Ringwald UNUSED(report_len); 21072a8858fSMilanka Ringwald 21172a8858fSMilanka Ringwald uint8_t buttons = report[0]; 21272a8858fSMilanka Ringwald int8_t dx = (int8_t) report[1]; 21372a8858fSMilanka Ringwald int8_t dy = (int8_t) report[2]; 21472a8858fSMilanka Ringwald int8_t dwheel = (int8_t) report[3]; 21572a8858fSMilanka Ringwald printf("Mouse: %i, %i - wheel %i - buttons 0x%02x\n", dx, dy, dwheel, buttons); 21672a8858fSMilanka Ringwald } 21772a8858fSMilanka Ringwald 21872a8858fSMilanka Ringwald /** 21972a8858fSMilanka Ringwald * @section Test if advertisement contains HID UUID 22072a8858fSMilanka Ringwald * @param packet 22172a8858fSMilanka Ringwald * @param size 22272a8858fSMilanka Ringwald * @returns true if it does 22372a8858fSMilanka Ringwald */ 22472a8858fSMilanka Ringwald static bool adv_event_contains_hid_service(const uint8_t * packet){ 22572a8858fSMilanka Ringwald const uint8_t * ad_data = gap_event_advertising_report_get_data(packet); 22672a8858fSMilanka Ringwald uint16_t ad_len = gap_event_advertising_report_get_data_length(packet); 22772a8858fSMilanka Ringwald return ad_data_contains_uuid16(ad_len, ad_data, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE); 22872a8858fSMilanka Ringwald } 22972a8858fSMilanka Ringwald 23072a8858fSMilanka Ringwald /** 23172a8858fSMilanka Ringwald * Start scanning 23272a8858fSMilanka Ringwald */ 23372a8858fSMilanka Ringwald static void hog_start_scan(void){ 23472a8858fSMilanka Ringwald printf("Scanning for LE HID devices...\n"); 23572a8858fSMilanka Ringwald app_state = W4_HID_DEVICE_FOUND; 23672a8858fSMilanka Ringwald // Passive scanning, 100% (scan interval = scan window) 23772a8858fSMilanka Ringwald gap_set_scan_parameters(0,48,48); 23872a8858fSMilanka Ringwald gap_start_scan(); 23972a8858fSMilanka Ringwald } 24072a8858fSMilanka Ringwald 24172a8858fSMilanka Ringwald /** 24272a8858fSMilanka Ringwald * Handle timeout for outgoing connection 24372a8858fSMilanka Ringwald * @param ts 24472a8858fSMilanka Ringwald */ 24572a8858fSMilanka Ringwald static void hog_connection_timeout(btstack_timer_source_t * ts){ 24672a8858fSMilanka Ringwald UNUSED(ts); 24772a8858fSMilanka Ringwald printf("Timeout - abort connection\n"); 24872a8858fSMilanka Ringwald gap_connect_cancel(); 24972a8858fSMilanka Ringwald hog_start_scan(); 25072a8858fSMilanka Ringwald } 25172a8858fSMilanka Ringwald 25272a8858fSMilanka Ringwald 25372a8858fSMilanka Ringwald /** 25472a8858fSMilanka Ringwald * Connect to remote device but set timer for timeout 25572a8858fSMilanka Ringwald */ 25672a8858fSMilanka Ringwald static void hog_connect(void) { 25772a8858fSMilanka Ringwald // set timer 25872a8858fSMilanka Ringwald btstack_run_loop_set_timer(&connection_timer, 10000); 25972a8858fSMilanka Ringwald btstack_run_loop_set_timer_handler(&connection_timer, &hog_connection_timeout); 26072a8858fSMilanka Ringwald btstack_run_loop_add_timer(&connection_timer); 26172a8858fSMilanka Ringwald app_state = W4_CONNECTED; 26272a8858fSMilanka Ringwald gap_connect(remote_device.addr, remote_device.addr_type); 26372a8858fSMilanka Ringwald } 26472a8858fSMilanka Ringwald 26572a8858fSMilanka Ringwald /** 26672a8858fSMilanka Ringwald * Handle timer event to trigger reconnect 26772a8858fSMilanka Ringwald * @param ts 26872a8858fSMilanka Ringwald */ 26972a8858fSMilanka Ringwald static void hog_reconnect_timeout(btstack_timer_source_t * ts){ 27072a8858fSMilanka Ringwald UNUSED(ts); 27172a8858fSMilanka Ringwald switch (app_state){ 27272a8858fSMilanka Ringwald case W4_TIMEOUT_THEN_RECONNECT: 27372a8858fSMilanka Ringwald hog_connect(); 27472a8858fSMilanka Ringwald break; 27572a8858fSMilanka Ringwald case W4_TIMEOUT_THEN_SCAN: 27672a8858fSMilanka Ringwald hog_start_scan(); 27772a8858fSMilanka Ringwald break; 27872a8858fSMilanka Ringwald default: 27972a8858fSMilanka Ringwald break; 28072a8858fSMilanka Ringwald } 28172a8858fSMilanka Ringwald } 28272a8858fSMilanka Ringwald 28372a8858fSMilanka Ringwald /** 28472a8858fSMilanka Ringwald * Start connecting after boot up: connect to last used device if possible, start scan otherwise 28572a8858fSMilanka Ringwald */ 28672a8858fSMilanka Ringwald static void hog_start_connect(void){ 28772a8858fSMilanka Ringwald // check if we have a bonded device 28872a8858fSMilanka Ringwald btstack_tlv_get_instance(&btstack_tlv_singleton_impl, &btstack_tlv_singleton_context); 28972a8858fSMilanka Ringwald if (btstack_tlv_singleton_impl){ 29072a8858fSMilanka Ringwald int len = btstack_tlv_singleton_impl->get_tag(btstack_tlv_singleton_context, TLV_TAG_HOGD, (uint8_t *) &remote_device, sizeof(remote_device)); 29172a8858fSMilanka Ringwald if (len == sizeof(remote_device)){ 29272a8858fSMilanka Ringwald printf("Bonded, connect to device with %s address %s ...\n", remote_device.addr_type == 0 ? "public" : "random" , bd_addr_to_str(remote_device.addr)); 29372a8858fSMilanka Ringwald hog_connect(); 29472a8858fSMilanka Ringwald return; 29572a8858fSMilanka Ringwald } 29672a8858fSMilanka Ringwald } 29772a8858fSMilanka Ringwald // otherwise, scan for HID devices 29872a8858fSMilanka Ringwald hog_start_scan(); 29972a8858fSMilanka Ringwald } 30072a8858fSMilanka Ringwald 30172a8858fSMilanka Ringwald /** 30272a8858fSMilanka Ringwald * In case of error, disconnect and start scanning again 30372a8858fSMilanka Ringwald */ 30472a8858fSMilanka Ringwald static void handle_outgoing_connection_error(void){ 30572a8858fSMilanka Ringwald printf("Error occurred, disconnect and start over\n"); 30672a8858fSMilanka Ringwald gap_disconnect(connection_handle); 30772a8858fSMilanka Ringwald hog_start_scan(); 30872a8858fSMilanka Ringwald } 30972a8858fSMilanka Ringwald 31072a8858fSMilanka Ringwald /** 31172a8858fSMilanka Ringwald * Handle GATT Client Events dependent on current state 31272a8858fSMilanka Ringwald * 31372a8858fSMilanka Ringwald * @param packet_type 31472a8858fSMilanka Ringwald * @param channel 31572a8858fSMilanka Ringwald * @param packet 31672a8858fSMilanka Ringwald * @param size 31772a8858fSMilanka Ringwald */ 31872a8858fSMilanka Ringwald static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { 31972a8858fSMilanka Ringwald UNUSED(packet_type); 32072a8858fSMilanka Ringwald UNUSED(channel); 32172a8858fSMilanka Ringwald UNUSED(size); 32272a8858fSMilanka Ringwald 32372a8858fSMilanka Ringwald uint8_t status; 32472a8858fSMilanka Ringwald uint8_t report_id; 32572a8858fSMilanka Ringwald 32672a8858fSMilanka Ringwald if (hci_event_packet_get_type(packet) != HCI_EVENT_GATTSERVICE_META){ 32772a8858fSMilanka Ringwald return; 32872a8858fSMilanka Ringwald } 32972a8858fSMilanka Ringwald 33072a8858fSMilanka Ringwald switch (hci_event_gattservice_meta_get_subevent_code(packet)){ 33172a8858fSMilanka Ringwald case GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED: 33272a8858fSMilanka Ringwald status = gattservice_subevent_hid_service_connected_get_status(packet); 33372a8858fSMilanka Ringwald switch (status){ 33472a8858fSMilanka Ringwald case ERROR_CODE_SUCCESS: 33572a8858fSMilanka Ringwald printf("HID service client connected, found %d services\n", 33672a8858fSMilanka Ringwald gattservice_subevent_hid_service_connected_get_num_instances(packet)); 33772a8858fSMilanka Ringwald 33872a8858fSMilanka Ringwald // store device as bonded 33972a8858fSMilanka Ringwald if (btstack_tlv_singleton_impl){ 34072a8858fSMilanka Ringwald btstack_tlv_singleton_impl->store_tag(btstack_tlv_singleton_context, TLV_TAG_HOGD, (const uint8_t *) &remote_device, sizeof(remote_device)); 34172a8858fSMilanka Ringwald } 34272a8858fSMilanka Ringwald // done 34372a8858fSMilanka Ringwald printf("Ready - please start typing or mousing..\n"); 34472a8858fSMilanka Ringwald app_state = READY; 34572a8858fSMilanka Ringwald break; 34672a8858fSMilanka Ringwald default: 34772a8858fSMilanka Ringwald printf("HID service client connection failed, err 0x%02x.\n", status); 34872a8858fSMilanka Ringwald handle_outgoing_connection_error(); 34972a8858fSMilanka Ringwald break; 35072a8858fSMilanka Ringwald } 35172a8858fSMilanka Ringwald break; 35272a8858fSMilanka Ringwald case GATTSERVICE_SUBEVENT_HID_REPORT: 35372a8858fSMilanka Ringwald report_id = gattservice_subevent_hid_report_get_report_id(packet); 35472a8858fSMilanka Ringwald switch (report_id){ 35572a8858fSMilanka Ringwald case HID_BOOT_MODE_MOUSE_ID: 35672a8858fSMilanka Ringwald handle_boot_mouse_event( 35772a8858fSMilanka Ringwald gattservice_subevent_hid_report_get_report(packet), 35872a8858fSMilanka Ringwald gattservice_subevent_hid_report_get_report_len(packet)); 35972a8858fSMilanka Ringwald break; 36072a8858fSMilanka Ringwald case HID_BOOT_MODE_KEYBOARD_ID: 36172a8858fSMilanka Ringwald handle_boot_keyboard_event( 36272a8858fSMilanka Ringwald gattservice_subevent_hid_report_get_report(packet), 36372a8858fSMilanka Ringwald gattservice_subevent_hid_report_get_report_len(packet)); 36472a8858fSMilanka Ringwald break; 36572a8858fSMilanka Ringwald default: 36672a8858fSMilanka Ringwald break; 36772a8858fSMilanka Ringwald } 36872a8858fSMilanka Ringwald break; 36972a8858fSMilanka Ringwald 37072a8858fSMilanka Ringwald default: 37172a8858fSMilanka Ringwald break; 37272a8858fSMilanka Ringwald } 37372a8858fSMilanka Ringwald } 37472a8858fSMilanka Ringwald 37572a8858fSMilanka Ringwald /* LISTING_START(packetHandler): Packet Handler */ 37672a8858fSMilanka Ringwald static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 37772a8858fSMilanka Ringwald /* LISTING_PAUSE */ 37872a8858fSMilanka Ringwald UNUSED(channel); 37972a8858fSMilanka Ringwald UNUSED(size); 38072a8858fSMilanka Ringwald uint8_t event; 38172a8858fSMilanka Ringwald uint8_t status; 38272a8858fSMilanka Ringwald /* LISTING_RESUME */ 38372a8858fSMilanka Ringwald switch (packet_type) { 38472a8858fSMilanka Ringwald case HCI_EVENT_PACKET: 38572a8858fSMilanka Ringwald event = hci_event_packet_get_type(packet); 38672a8858fSMilanka Ringwald switch (event) { 38772a8858fSMilanka Ringwald case BTSTACK_EVENT_STATE: 38872a8858fSMilanka Ringwald if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) break; 38972a8858fSMilanka Ringwald btstack_assert(app_state == W4_WORKING); 390*2901a9b7SMilanka Ringwald 39172a8858fSMilanka Ringwald hog_start_connect(); 39272a8858fSMilanka Ringwald break; 39372a8858fSMilanka Ringwald case GAP_EVENT_ADVERTISING_REPORT: 39472a8858fSMilanka Ringwald if (app_state != W4_HID_DEVICE_FOUND) break; 39572a8858fSMilanka Ringwald if (adv_event_contains_hid_service(packet) == false) break; 39672a8858fSMilanka Ringwald // stop scan 39772a8858fSMilanka Ringwald gap_stop_scan(); 39872a8858fSMilanka Ringwald // store remote device address and type 39972a8858fSMilanka Ringwald gap_event_advertising_report_get_address(packet, remote_device.addr); 40072a8858fSMilanka Ringwald remote_device.addr_type = gap_event_advertising_report_get_address_type(packet); 40172a8858fSMilanka Ringwald // connect 40272a8858fSMilanka Ringwald printf("Found, connect to device with %s address %s ...\n", remote_device.addr_type == 0 ? "public" : "random" , bd_addr_to_str(remote_device.addr)); 40372a8858fSMilanka Ringwald hog_connect(); 40472a8858fSMilanka Ringwald break; 40572a8858fSMilanka Ringwald case HCI_EVENT_DISCONNECTION_COMPLETE: 406*2901a9b7SMilanka Ringwald if (app_state != READY) break; 40772a8858fSMilanka Ringwald connection_handle = HCI_CON_HANDLE_INVALID; 40872a8858fSMilanka Ringwald switch (app_state){ 40972a8858fSMilanka Ringwald case READY: 41072a8858fSMilanka Ringwald printf("\nDisconnected, try to reconnect...\n"); 41172a8858fSMilanka Ringwald app_state = W4_TIMEOUT_THEN_RECONNECT; 41272a8858fSMilanka Ringwald break; 41372a8858fSMilanka Ringwald default: 41472a8858fSMilanka Ringwald printf("\nDisconnected, start over...\n"); 41572a8858fSMilanka Ringwald app_state = W4_TIMEOUT_THEN_SCAN; 41672a8858fSMilanka Ringwald break; 41772a8858fSMilanka Ringwald } 41872a8858fSMilanka Ringwald // set timer 41972a8858fSMilanka Ringwald btstack_run_loop_set_timer(&connection_timer, 100); 42072a8858fSMilanka Ringwald btstack_run_loop_set_timer_handler(&connection_timer, &hog_reconnect_timeout); 42172a8858fSMilanka Ringwald btstack_run_loop_add_timer(&connection_timer); 42272a8858fSMilanka Ringwald break; 42372a8858fSMilanka Ringwald case HCI_EVENT_LE_META: 42472a8858fSMilanka Ringwald // wait for connection complete 42572a8858fSMilanka Ringwald if (hci_event_le_meta_get_subevent_code(packet) != HCI_SUBEVENT_LE_CONNECTION_COMPLETE) break; 42672a8858fSMilanka Ringwald if (app_state != W4_CONNECTED) return; 42772a8858fSMilanka Ringwald btstack_run_loop_remove_timer(&connection_timer); 42872a8858fSMilanka Ringwald connection_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); 42972a8858fSMilanka Ringwald // request security 43072a8858fSMilanka Ringwald app_state = W4_ENCRYPTED; 43172a8858fSMilanka Ringwald sm_request_pairing(connection_handle); 43272a8858fSMilanka Ringwald break; 43372a8858fSMilanka Ringwald case HCI_EVENT_ENCRYPTION_CHANGE: 43472a8858fSMilanka Ringwald if (connection_handle != hci_event_encryption_change_get_connection_handle(packet)) break; 43572a8858fSMilanka Ringwald printf("Connection encrypted: %u\n", hci_event_encryption_change_get_encryption_enabled(packet)); 43672a8858fSMilanka Ringwald if (hci_event_encryption_change_get_encryption_enabled(packet) == 0){ 43772a8858fSMilanka Ringwald printf("Encryption failed -> abort\n"); 43872a8858fSMilanka Ringwald handle_outgoing_connection_error(); 43972a8858fSMilanka Ringwald break; 44072a8858fSMilanka Ringwald } 44172a8858fSMilanka Ringwald // continue - query primary services 44272a8858fSMilanka Ringwald printf("Search for HID service.\n"); 443*2901a9b7SMilanka Ringwald app_state = W4_HID_CLIENT_CONNECTED; 44472a8858fSMilanka Ringwald 44572a8858fSMilanka Ringwald status = hids_client_connect(connection_handle, handle_gatt_client_event, HID_PROTOCOL_MODE_BOOT, &hids_cid); 44672a8858fSMilanka Ringwald if (status != ERROR_CODE_SUCCESS){ 44772a8858fSMilanka Ringwald printf("HID client connection failed, status 0x%02x\n", status); 44872a8858fSMilanka Ringwald } 44972a8858fSMilanka Ringwald break; 45072a8858fSMilanka Ringwald default: 45172a8858fSMilanka Ringwald break; 45272a8858fSMilanka Ringwald } 45372a8858fSMilanka Ringwald break; 45472a8858fSMilanka Ringwald default: 45572a8858fSMilanka Ringwald break; 45672a8858fSMilanka Ringwald } 45772a8858fSMilanka Ringwald } 45872a8858fSMilanka Ringwald /* LISTING_END */ 45972a8858fSMilanka Ringwald 46072a8858fSMilanka Ringwald /* @section HCI packet handler 46172a8858fSMilanka Ringwald * 46272a8858fSMilanka Ringwald * @text The SM packet handler receives Security Manager Events required for pairing. 46372a8858fSMilanka Ringwald * It also receives events generated during Identity Resolving 46472a8858fSMilanka Ringwald * see Listing SMPacketHandler. 46572a8858fSMilanka Ringwald */ 46672a8858fSMilanka Ringwald 46772a8858fSMilanka Ringwald /* LISTING_START(SMPacketHandler): Scanning and receiving advertisements */ 46872a8858fSMilanka Ringwald 46972a8858fSMilanka Ringwald static void sm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 47072a8858fSMilanka Ringwald UNUSED(channel); 47172a8858fSMilanka Ringwald UNUSED(size); 47272a8858fSMilanka Ringwald 47372a8858fSMilanka Ringwald if (packet_type != HCI_EVENT_PACKET) return; 47472a8858fSMilanka Ringwald 47572a8858fSMilanka Ringwald switch (hci_event_packet_get_type(packet)) { 47672a8858fSMilanka Ringwald case SM_EVENT_JUST_WORKS_REQUEST: 47772a8858fSMilanka Ringwald printf("Just works requested\n"); 47872a8858fSMilanka Ringwald sm_just_works_confirm(sm_event_just_works_request_get_handle(packet)); 47972a8858fSMilanka Ringwald break; 48072a8858fSMilanka Ringwald case SM_EVENT_NUMERIC_COMPARISON_REQUEST: 48172a8858fSMilanka Ringwald printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet)); 48272a8858fSMilanka Ringwald sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet)); 48372a8858fSMilanka Ringwald break; 48472a8858fSMilanka Ringwald case SM_EVENT_PASSKEY_DISPLAY_NUMBER: 48572a8858fSMilanka Ringwald printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet)); 48672a8858fSMilanka Ringwald break; 48772a8858fSMilanka Ringwald case SM_EVENT_PAIRING_COMPLETE: 48872a8858fSMilanka Ringwald switch (sm_event_pairing_complete_get_status(packet)){ 48972a8858fSMilanka Ringwald case ERROR_CODE_SUCCESS: 49072a8858fSMilanka Ringwald printf("Pairing complete, success\n"); 49172a8858fSMilanka Ringwald break; 49272a8858fSMilanka Ringwald case ERROR_CODE_CONNECTION_TIMEOUT: 49372a8858fSMilanka Ringwald printf("Pairing failed, timeout\n"); 49472a8858fSMilanka Ringwald break; 49572a8858fSMilanka Ringwald case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION: 49672a8858fSMilanka Ringwald printf("Pairing faileed, disconnected\n"); 49772a8858fSMilanka Ringwald break; 49872a8858fSMilanka Ringwald case ERROR_CODE_AUTHENTICATION_FAILURE: 49972a8858fSMilanka Ringwald printf("Pairing failed, reason = %u\n", sm_event_pairing_complete_get_reason(packet)); 50072a8858fSMilanka Ringwald break; 50172a8858fSMilanka Ringwald default: 50272a8858fSMilanka Ringwald break; 50372a8858fSMilanka Ringwald } 50472a8858fSMilanka Ringwald break; 50572a8858fSMilanka Ringwald default: 50672a8858fSMilanka Ringwald break; 50772a8858fSMilanka Ringwald } 50872a8858fSMilanka Ringwald } 50972a8858fSMilanka Ringwald /* LISTING_END */ 51072a8858fSMilanka Ringwald 51172a8858fSMilanka Ringwald int btstack_main(int argc, const char * argv[]); 51272a8858fSMilanka Ringwald int btstack_main(int argc, const char * argv[]){ 51372a8858fSMilanka Ringwald 51472a8858fSMilanka Ringwald (void)argc; 51572a8858fSMilanka Ringwald (void)argv; 51672a8858fSMilanka Ringwald 51772a8858fSMilanka Ringwald /* LISTING_START(HogBootHostSetup): HID-over-GATT Boot Host Setup */ 51872a8858fSMilanka Ringwald 51972a8858fSMilanka Ringwald // register for events from HCI 52072a8858fSMilanka Ringwald hci_event_callback_registration.callback = &packet_handler; 52172a8858fSMilanka Ringwald hci_add_event_handler(&hci_event_callback_registration); 52272a8858fSMilanka Ringwald 52372a8858fSMilanka Ringwald // register for events from Security Manager 52472a8858fSMilanka Ringwald sm_event_callback_registration.callback = &sm_packet_handler; 52572a8858fSMilanka Ringwald sm_add_event_handler(&sm_event_callback_registration); 52672a8858fSMilanka Ringwald 52772a8858fSMilanka Ringwald // setup le device db 52872a8858fSMilanka Ringwald le_device_db_init(); 52972a8858fSMilanka Ringwald 53072a8858fSMilanka Ringwald // 53172a8858fSMilanka Ringwald l2cap_init(); 53272a8858fSMilanka Ringwald sm_init(); 53372a8858fSMilanka Ringwald gatt_client_init(); 53472a8858fSMilanka Ringwald hids_client_init(); 53572a8858fSMilanka Ringwald 53672a8858fSMilanka Ringwald /* LISTING_END */ 53772a8858fSMilanka Ringwald 53872a8858fSMilanka Ringwald // Disable stdout buffering 53972a8858fSMilanka Ringwald setbuf(stdout, NULL); 54072a8858fSMilanka Ringwald 54172a8858fSMilanka Ringwald app_state = W4_WORKING; 54272a8858fSMilanka Ringwald 54372a8858fSMilanka Ringwald // Turn on the device 54472a8858fSMilanka Ringwald hci_power_control(HCI_POWER_ON); 54572a8858fSMilanka Ringwald return 0; 54672a8858fSMilanka Ringwald } 54772a8858fSMilanka Ringwald 54872a8858fSMilanka Ringwald /* EXAMPLE_END */ 549