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 232fca4dadSMilanka Ringwald * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BLUEKITCHEN 242fca4dadSMilanka Ringwald * GMBH 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 38021192e1SMilanka Ringwald #define BTSTACK_FILE__ "hog_host_demo.c" 3972a8858fSMilanka Ringwald 4072a8858fSMilanka Ringwald /* 41021192e1SMilanka Ringwald * hog_host_demo.c 4272a8858fSMilanka Ringwald */ 4372a8858fSMilanka Ringwald 44021192e1SMilanka Ringwald /* EXAMPLE_START(hog_host_demo): HID Host LE 4572a8858fSMilanka Ringwald * 46021192e1SMilanka Ringwald * @text This example implements a minimal HID-over-GATT 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 58*68a70b34SMatthias Ringwald // hog_host_demo.gatt contains the declaration of the provided GATT Services + Characteristics 59*68a70b34SMatthias Ringwald // hog_host_demo.h contains the binary representation of gatt_browser.gatt 60*68a70b34SMatthias Ringwald // it is generated by the build system by calling: $BTSTACK_ROOT/tool/compile_gatt.py hog_host_demo.gatt hog_host_demo.h 61*68a70b34SMatthias Ringwald // it needs to be regenerated when the GATT Database declared in gatt_browser.gatt file is modified 62*68a70b34SMatthias Ringwald #include "hog_host_demo.h" 63*68a70b34SMatthias Ringwald 6472a8858fSMilanka Ringwald // TAG to store remote device address and type in TLV 6572a8858fSMilanka Ringwald #define TLV_TAG_HOGD ((((uint32_t) 'H') << 24 ) | (((uint32_t) 'O') << 16) | (((uint32_t) 'G') << 8) | 'D') 6672a8858fSMilanka Ringwald 6772a8858fSMilanka Ringwald typedef struct { 6872a8858fSMilanka Ringwald bd_addr_t addr; 6972a8858fSMilanka Ringwald bd_addr_type_t addr_type; 7072a8858fSMilanka Ringwald } le_device_addr_t; 7172a8858fSMilanka Ringwald 7272a8858fSMilanka Ringwald static enum { 7372a8858fSMilanka Ringwald W4_WORKING, 7472a8858fSMilanka Ringwald W4_HID_DEVICE_FOUND, 7572a8858fSMilanka Ringwald W4_CONNECTED, 7672a8858fSMilanka Ringwald W4_ENCRYPTED, 772901a9b7SMilanka Ringwald W4_HID_CLIENT_CONNECTED, 7872a8858fSMilanka Ringwald READY, 7972a8858fSMilanka Ringwald W4_TIMEOUT_THEN_SCAN, 8072a8858fSMilanka Ringwald W4_TIMEOUT_THEN_RECONNECT, 8172a8858fSMilanka Ringwald } app_state; 8272a8858fSMilanka Ringwald 8372a8858fSMilanka Ringwald static le_device_addr_t remote_device; 8472a8858fSMilanka Ringwald static hci_con_handle_t connection_handle; 8572a8858fSMilanka Ringwald static uint16_t hids_cid; 86021192e1SMilanka Ringwald static hid_protocol_mode_t protocol_mode = HID_PROTOCOL_MODE_REPORT; 87021192e1SMilanka Ringwald 88021192e1SMilanka Ringwald // SDP 89021192e1SMilanka Ringwald static uint8_t hid_descriptor_storage[500]; 9072a8858fSMilanka Ringwald 9172a8858fSMilanka Ringwald // used to implement connection timeout and reconnect timer 9272a8858fSMilanka Ringwald static btstack_timer_source_t connection_timer; 9372a8858fSMilanka Ringwald 9472a8858fSMilanka Ringwald // register for events from HCI/GAP and SM 9572a8858fSMilanka Ringwald static btstack_packet_callback_registration_t hci_event_callback_registration; 9672a8858fSMilanka Ringwald static btstack_packet_callback_registration_t sm_event_callback_registration; 9772a8858fSMilanka Ringwald 9872a8858fSMilanka Ringwald // used to store remote device in TLV 9972a8858fSMilanka Ringwald static const btstack_tlv_t * btstack_tlv_singleton_impl; 10072a8858fSMilanka Ringwald static void * btstack_tlv_singleton_context; 10172a8858fSMilanka Ringwald 10272a8858fSMilanka Ringwald // Simplified US Keyboard with Shift modifier 10372a8858fSMilanka Ringwald 10472a8858fSMilanka Ringwald #define CHAR_ILLEGAL 0xff 10572a8858fSMilanka Ringwald #define CHAR_RETURN '\n' 10672a8858fSMilanka Ringwald #define CHAR_ESCAPE 27 10772a8858fSMilanka Ringwald #define CHAR_TAB '\t' 10872a8858fSMilanka Ringwald #define CHAR_BACKSPACE 0x7f 10972a8858fSMilanka Ringwald 11072a8858fSMilanka Ringwald /** 11172a8858fSMilanka Ringwald * English (US) 11272a8858fSMilanka Ringwald */ 11372a8858fSMilanka Ringwald static const uint8_t keytable_us_none [] = { 11472a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 0-3 */ 11572a8858fSMilanka Ringwald 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', /* 4-13 */ 11672a8858fSMilanka Ringwald 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', /* 14-23 */ 11772a8858fSMilanka Ringwald 'u', 'v', 'w', 'x', 'y', 'z', /* 24-29 */ 11872a8858fSMilanka Ringwald '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', /* 30-39 */ 11972a8858fSMilanka Ringwald CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB, ' ', /* 40-44 */ 12072a8858fSMilanka Ringwald '-', '=', '[', ']', '\\', CHAR_ILLEGAL, ';', '\'', 0x60, ',', /* 45-54 */ 12172a8858fSMilanka Ringwald '.', '/', CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 55-60 */ 12272a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 61-64 */ 12372a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 65-68 */ 12472a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 69-72 */ 12572a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 73-76 */ 12672a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 77-80 */ 12772a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 81-84 */ 12872a8858fSMilanka Ringwald '*', '-', '+', '\n', '1', '2', '3', '4', '5', /* 85-97 */ 12972a8858fSMilanka Ringwald '6', '7', '8', '9', '0', '.', 0xa7, /* 97-100 */ 13072a8858fSMilanka Ringwald }; 13172a8858fSMilanka Ringwald 13272a8858fSMilanka Ringwald static const uint8_t keytable_us_shift[] = { 13372a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 0-3 */ 13472a8858fSMilanka Ringwald 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', /* 4-13 */ 13572a8858fSMilanka Ringwald 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', /* 14-23 */ 13672a8858fSMilanka Ringwald 'U', 'V', 'W', 'X', 'Y', 'Z', /* 24-29 */ 13772a8858fSMilanka Ringwald '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', /* 30-39 */ 13872a8858fSMilanka Ringwald CHAR_RETURN, CHAR_ESCAPE, CHAR_BACKSPACE, CHAR_TAB, ' ', /* 40-44 */ 13972a8858fSMilanka Ringwald '_', '+', '{', '}', '|', CHAR_ILLEGAL, ':', '"', 0x7E, '<', /* 45-54 */ 14072a8858fSMilanka Ringwald '>', '?', CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 55-60 */ 14172a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 61-64 */ 14272a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 65-68 */ 14372a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 69-72 */ 14472a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 73-76 */ 14572a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 77-80 */ 14672a8858fSMilanka Ringwald CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, CHAR_ILLEGAL, /* 81-84 */ 14772a8858fSMilanka Ringwald '*', '-', '+', '\n', '1', '2', '3', '4', '5', /* 85-97 */ 14872a8858fSMilanka Ringwald '6', '7', '8', '9', '0', '.', 0xb1, /* 97-100 */ 14972a8858fSMilanka Ringwald }; 15072a8858fSMilanka Ringwald 15172a8858fSMilanka Ringwald 152021192e1SMilanka Ringwald 15372a8858fSMilanka Ringwald #define NUM_KEYS 6 15472a8858fSMilanka Ringwald static uint8_t last_keys[NUM_KEYS]; 155021192e1SMilanka Ringwald static void hid_handle_input_report(uint8_t service_index, const uint8_t * report, uint16_t report_len){ 156021192e1SMilanka Ringwald // check if HID Input Report 15772a8858fSMilanka Ringwald 158021192e1SMilanka Ringwald if (report_len < 1) return; 15972a8858fSMilanka Ringwald 160021192e1SMilanka Ringwald btstack_hid_parser_t parser; 161021192e1SMilanka Ringwald 162021192e1SMilanka Ringwald switch (protocol_mode){ 163021192e1SMilanka Ringwald case HID_PROTOCOL_MODE_BOOT: 164021192e1SMilanka Ringwald btstack_hid_parser_init(&parser, 165e9933dfeSMatthias Ringwald btstack_hid_get_boot_descriptor_data(), 166e9933dfeSMatthias Ringwald btstack_hid_get_boot_descriptor_len(), 167021192e1SMilanka Ringwald HID_REPORT_TYPE_INPUT, report, report_len); 168021192e1SMilanka Ringwald break; 169021192e1SMilanka Ringwald 170021192e1SMilanka Ringwald default: 171021192e1SMilanka Ringwald btstack_hid_parser_init(&parser, 172021192e1SMilanka Ringwald hids_client_descriptor_storage_get_descriptor_data(hids_cid, service_index), 173021192e1SMilanka Ringwald hids_client_descriptor_storage_get_descriptor_len(hids_cid, service_index), 174021192e1SMilanka Ringwald HID_REPORT_TYPE_INPUT, report, report_len); 175021192e1SMilanka Ringwald break; 176021192e1SMilanka Ringwald 177021192e1SMilanka Ringwald } 178021192e1SMilanka Ringwald 179021192e1SMilanka Ringwald int shift = 0; 18072a8858fSMilanka Ringwald uint8_t new_keys[NUM_KEYS]; 18172a8858fSMilanka Ringwald memset(new_keys, 0, sizeof(new_keys)); 18272a8858fSMilanka Ringwald int new_keys_count = 0; 183021192e1SMilanka Ringwald while (btstack_hid_parser_has_more(&parser)){ 184021192e1SMilanka Ringwald uint16_t usage_page; 185021192e1SMilanka Ringwald uint16_t usage; 186021192e1SMilanka Ringwald int32_t value; 187021192e1SMilanka Ringwald btstack_hid_parser_get_field(&parser, &usage_page, &usage, &value); 188021192e1SMilanka Ringwald if (usage_page != 0x07) continue; 189021192e1SMilanka Ringwald switch (usage){ 190021192e1SMilanka Ringwald case 0xe1: 191021192e1SMilanka Ringwald case 0xe6: 192021192e1SMilanka Ringwald if (value){ 193021192e1SMilanka Ringwald shift = 1; 194021192e1SMilanka Ringwald } 195021192e1SMilanka Ringwald continue; 196021192e1SMilanka Ringwald case 0x00: 197021192e1SMilanka Ringwald continue; 198021192e1SMilanka Ringwald default: 199021192e1SMilanka Ringwald break; 200021192e1SMilanka Ringwald } 20172a8858fSMilanka Ringwald if (usage >= sizeof(keytable_us_none)) continue; 20272a8858fSMilanka Ringwald 20372a8858fSMilanka Ringwald // store new keys 2040801ef92SMatthias Ringwald new_keys[new_keys_count++] = (uint8_t) usage; 20572a8858fSMilanka Ringwald 20672a8858fSMilanka Ringwald // check if usage was used last time (and ignore in that case) 20772a8858fSMilanka Ringwald int i; 20872a8858fSMilanka Ringwald for (i=0;i<NUM_KEYS;i++){ 20972a8858fSMilanka Ringwald if (usage == last_keys[i]){ 21072a8858fSMilanka Ringwald usage = 0; 21172a8858fSMilanka Ringwald } 21272a8858fSMilanka Ringwald } 21372a8858fSMilanka Ringwald if (usage == 0) continue; 21472a8858fSMilanka Ringwald 21572a8858fSMilanka Ringwald uint8_t key; 21672a8858fSMilanka Ringwald if (shift){ 21772a8858fSMilanka Ringwald key = keytable_us_shift[usage]; 21872a8858fSMilanka Ringwald } else { 21972a8858fSMilanka Ringwald key = keytable_us_none[usage]; 22072a8858fSMilanka Ringwald } 22172a8858fSMilanka Ringwald if (key == CHAR_ILLEGAL) continue; 22272a8858fSMilanka Ringwald if (key == CHAR_BACKSPACE){ 22372a8858fSMilanka Ringwald printf("\b \b"); // go back one char, print space, go back one char again 22472a8858fSMilanka Ringwald continue; 22572a8858fSMilanka Ringwald } 22672a8858fSMilanka Ringwald printf("%c", key); 22772a8858fSMilanka Ringwald } 22872a8858fSMilanka Ringwald memcpy(last_keys, new_keys, NUM_KEYS); 22972a8858fSMilanka Ringwald } 23072a8858fSMilanka Ringwald 23172a8858fSMilanka Ringwald /** 23272a8858fSMilanka Ringwald * @section Test if advertisement contains HID UUID 23372a8858fSMilanka Ringwald * @param packet 23472a8858fSMilanka Ringwald * @param size 23572a8858fSMilanka Ringwald * @returns true if it does 23672a8858fSMilanka Ringwald */ 23772a8858fSMilanka Ringwald static bool adv_event_contains_hid_service(const uint8_t * packet){ 23872a8858fSMilanka Ringwald const uint8_t * ad_data = gap_event_advertising_report_get_data(packet); 2390801ef92SMatthias Ringwald uint8_t ad_len = gap_event_advertising_report_get_data_length(packet); 24072a8858fSMilanka Ringwald return ad_data_contains_uuid16(ad_len, ad_data, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE); 24172a8858fSMilanka Ringwald } 24272a8858fSMilanka Ringwald 24372a8858fSMilanka Ringwald /** 24472a8858fSMilanka Ringwald * Start scanning 24572a8858fSMilanka Ringwald */ 24672a8858fSMilanka Ringwald static void hog_start_scan(void){ 24772a8858fSMilanka Ringwald printf("Scanning for LE HID devices...\n"); 24872a8858fSMilanka Ringwald app_state = W4_HID_DEVICE_FOUND; 24972a8858fSMilanka Ringwald // Passive scanning, 100% (scan interval = scan window) 25072a8858fSMilanka Ringwald gap_set_scan_parameters(0,48,48); 25172a8858fSMilanka Ringwald gap_start_scan(); 25272a8858fSMilanka Ringwald } 25372a8858fSMilanka Ringwald 25472a8858fSMilanka Ringwald /** 25572a8858fSMilanka Ringwald * Handle timeout for outgoing connection 25672a8858fSMilanka Ringwald * @param ts 25772a8858fSMilanka Ringwald */ 25872a8858fSMilanka Ringwald static void hog_connection_timeout(btstack_timer_source_t * ts){ 25972a8858fSMilanka Ringwald UNUSED(ts); 26072a8858fSMilanka Ringwald printf("Timeout - abort connection\n"); 26172a8858fSMilanka Ringwald gap_connect_cancel(); 26272a8858fSMilanka Ringwald hog_start_scan(); 26372a8858fSMilanka Ringwald } 26472a8858fSMilanka Ringwald 26572a8858fSMilanka Ringwald 26672a8858fSMilanka Ringwald /** 26772a8858fSMilanka Ringwald * Connect to remote device but set timer for timeout 26872a8858fSMilanka Ringwald */ 26972a8858fSMilanka Ringwald static void hog_connect(void) { 27072a8858fSMilanka Ringwald // set timer 27172a8858fSMilanka Ringwald btstack_run_loop_set_timer(&connection_timer, 10000); 27272a8858fSMilanka Ringwald btstack_run_loop_set_timer_handler(&connection_timer, &hog_connection_timeout); 27372a8858fSMilanka Ringwald btstack_run_loop_add_timer(&connection_timer); 27472a8858fSMilanka Ringwald app_state = W4_CONNECTED; 27572a8858fSMilanka Ringwald gap_connect(remote_device.addr, remote_device.addr_type); 27672a8858fSMilanka Ringwald } 27772a8858fSMilanka Ringwald 27872a8858fSMilanka Ringwald /** 27972a8858fSMilanka Ringwald * Handle timer event to trigger reconnect 28072a8858fSMilanka Ringwald * @param ts 28172a8858fSMilanka Ringwald */ 28272a8858fSMilanka Ringwald static void hog_reconnect_timeout(btstack_timer_source_t * ts){ 28372a8858fSMilanka Ringwald UNUSED(ts); 28472a8858fSMilanka Ringwald switch (app_state){ 28572a8858fSMilanka Ringwald case W4_TIMEOUT_THEN_RECONNECT: 28672a8858fSMilanka Ringwald hog_connect(); 28772a8858fSMilanka Ringwald break; 28872a8858fSMilanka Ringwald case W4_TIMEOUT_THEN_SCAN: 28972a8858fSMilanka Ringwald hog_start_scan(); 29072a8858fSMilanka Ringwald break; 29172a8858fSMilanka Ringwald default: 29272a8858fSMilanka Ringwald break; 29372a8858fSMilanka Ringwald } 29472a8858fSMilanka Ringwald } 29572a8858fSMilanka Ringwald 29672a8858fSMilanka Ringwald /** 29772a8858fSMilanka Ringwald * Start connecting after boot up: connect to last used device if possible, start scan otherwise 29872a8858fSMilanka Ringwald */ 29972a8858fSMilanka Ringwald static void hog_start_connect(void){ 30072a8858fSMilanka Ringwald // check if we have a bonded device 30172a8858fSMilanka Ringwald btstack_tlv_get_instance(&btstack_tlv_singleton_impl, &btstack_tlv_singleton_context); 30272a8858fSMilanka Ringwald if (btstack_tlv_singleton_impl){ 30372a8858fSMilanka Ringwald int len = btstack_tlv_singleton_impl->get_tag(btstack_tlv_singleton_context, TLV_TAG_HOGD, (uint8_t *) &remote_device, sizeof(remote_device)); 30472a8858fSMilanka Ringwald if (len == sizeof(remote_device)){ 30572a8858fSMilanka 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)); 30672a8858fSMilanka Ringwald hog_connect(); 30772a8858fSMilanka Ringwald return; 30872a8858fSMilanka Ringwald } 30972a8858fSMilanka Ringwald } 31072a8858fSMilanka Ringwald // otherwise, scan for HID devices 31172a8858fSMilanka Ringwald hog_start_scan(); 31272a8858fSMilanka Ringwald } 31372a8858fSMilanka Ringwald 31472a8858fSMilanka Ringwald /** 31572a8858fSMilanka Ringwald * In case of error, disconnect and start scanning again 31672a8858fSMilanka Ringwald */ 31772a8858fSMilanka Ringwald static void handle_outgoing_connection_error(void){ 31872a8858fSMilanka Ringwald printf("Error occurred, disconnect and start over\n"); 31972a8858fSMilanka Ringwald gap_disconnect(connection_handle); 32072a8858fSMilanka Ringwald hog_start_scan(); 32172a8858fSMilanka Ringwald } 32272a8858fSMilanka Ringwald 32372a8858fSMilanka Ringwald /** 32472a8858fSMilanka Ringwald * Handle GATT Client Events dependent on current state 32572a8858fSMilanka Ringwald * 32672a8858fSMilanka Ringwald * @param packet_type 32772a8858fSMilanka Ringwald * @param channel 32872a8858fSMilanka Ringwald * @param packet 32972a8858fSMilanka Ringwald * @param size 33072a8858fSMilanka Ringwald */ 33172a8858fSMilanka Ringwald static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { 33272a8858fSMilanka Ringwald UNUSED(packet_type); 33372a8858fSMilanka Ringwald UNUSED(channel); 33472a8858fSMilanka Ringwald UNUSED(size); 33572a8858fSMilanka Ringwald 33672a8858fSMilanka Ringwald uint8_t status; 33772a8858fSMilanka Ringwald 33872a8858fSMilanka Ringwald if (hci_event_packet_get_type(packet) != HCI_EVENT_GATTSERVICE_META){ 33972a8858fSMilanka Ringwald return; 34072a8858fSMilanka Ringwald } 34172a8858fSMilanka Ringwald 34272a8858fSMilanka Ringwald switch (hci_event_gattservice_meta_get_subevent_code(packet)){ 34372a8858fSMilanka Ringwald case GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED: 34472a8858fSMilanka Ringwald status = gattservice_subevent_hid_service_connected_get_status(packet); 34572a8858fSMilanka Ringwald switch (status){ 34672a8858fSMilanka Ringwald case ERROR_CODE_SUCCESS: 34772a8858fSMilanka Ringwald printf("HID service client connected, found %d services\n", 34872a8858fSMilanka Ringwald gattservice_subevent_hid_service_connected_get_num_instances(packet)); 34972a8858fSMilanka Ringwald 35072a8858fSMilanka Ringwald // store device as bonded 35172a8858fSMilanka Ringwald if (btstack_tlv_singleton_impl){ 35272a8858fSMilanka Ringwald btstack_tlv_singleton_impl->store_tag(btstack_tlv_singleton_context, TLV_TAG_HOGD, (const uint8_t *) &remote_device, sizeof(remote_device)); 35372a8858fSMilanka Ringwald } 35472a8858fSMilanka Ringwald // done 35572a8858fSMilanka Ringwald printf("Ready - please start typing or mousing..\n"); 35672a8858fSMilanka Ringwald app_state = READY; 35772a8858fSMilanka Ringwald break; 35872a8858fSMilanka Ringwald default: 359fcd55a0bSMilanka Ringwald printf("HID service client connection failed, status 0x%02x.\n", status); 36072a8858fSMilanka Ringwald handle_outgoing_connection_error(); 36172a8858fSMilanka Ringwald break; 36272a8858fSMilanka Ringwald } 36372a8858fSMilanka Ringwald break; 364021192e1SMilanka Ringwald 36572a8858fSMilanka Ringwald case GATTSERVICE_SUBEVENT_HID_REPORT: 366021192e1SMilanka Ringwald hid_handle_input_report( 367021192e1SMilanka Ringwald gattservice_subevent_hid_report_get_service_index(packet), 36872a8858fSMilanka Ringwald gattservice_subevent_hid_report_get_report(packet), 36972a8858fSMilanka Ringwald gattservice_subevent_hid_report_get_report_len(packet)); 37072a8858fSMilanka Ringwald break; 37172a8858fSMilanka Ringwald 37272a8858fSMilanka Ringwald default: 37372a8858fSMilanka Ringwald break; 37472a8858fSMilanka Ringwald } 37572a8858fSMilanka Ringwald } 37672a8858fSMilanka Ringwald 37772a8858fSMilanka Ringwald /* LISTING_START(packetHandler): Packet Handler */ 37872a8858fSMilanka Ringwald static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 37972a8858fSMilanka Ringwald /* LISTING_PAUSE */ 38072a8858fSMilanka Ringwald UNUSED(channel); 38172a8858fSMilanka Ringwald UNUSED(size); 38272a8858fSMilanka Ringwald uint8_t event; 38372a8858fSMilanka Ringwald /* LISTING_RESUME */ 38472a8858fSMilanka Ringwald switch (packet_type) { 38572a8858fSMilanka Ringwald case HCI_EVENT_PACKET: 38672a8858fSMilanka Ringwald event = hci_event_packet_get_type(packet); 38772a8858fSMilanka Ringwald switch (event) { 38872a8858fSMilanka Ringwald case BTSTACK_EVENT_STATE: 38972a8858fSMilanka Ringwald if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) break; 39072a8858fSMilanka Ringwald btstack_assert(app_state == W4_WORKING); 3912901a9b7SMilanka Ringwald 39272a8858fSMilanka Ringwald hog_start_connect(); 39372a8858fSMilanka Ringwald break; 39472a8858fSMilanka Ringwald case GAP_EVENT_ADVERTISING_REPORT: 39572a8858fSMilanka Ringwald if (app_state != W4_HID_DEVICE_FOUND) break; 39672a8858fSMilanka Ringwald if (adv_event_contains_hid_service(packet) == false) break; 39772a8858fSMilanka Ringwald // stop scan 39872a8858fSMilanka Ringwald gap_stop_scan(); 39972a8858fSMilanka Ringwald // store remote device address and type 40072a8858fSMilanka Ringwald gap_event_advertising_report_get_address(packet, remote_device.addr); 40172a8858fSMilanka Ringwald remote_device.addr_type = gap_event_advertising_report_get_address_type(packet); 40272a8858fSMilanka Ringwald // connect 40372a8858fSMilanka 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)); 40472a8858fSMilanka Ringwald hog_connect(); 40572a8858fSMilanka Ringwald break; 40672a8858fSMilanka Ringwald case HCI_EVENT_DISCONNECTION_COMPLETE: 4072901a9b7SMilanka Ringwald if (app_state != READY) break; 40872a8858fSMilanka Ringwald connection_handle = HCI_CON_HANDLE_INVALID; 40972a8858fSMilanka Ringwald switch (app_state){ 41072a8858fSMilanka Ringwald case READY: 41172a8858fSMilanka Ringwald printf("\nDisconnected, try to reconnect...\n"); 41272a8858fSMilanka Ringwald app_state = W4_TIMEOUT_THEN_RECONNECT; 41372a8858fSMilanka Ringwald break; 41472a8858fSMilanka Ringwald default: 41572a8858fSMilanka Ringwald printf("\nDisconnected, start over...\n"); 41672a8858fSMilanka Ringwald app_state = W4_TIMEOUT_THEN_SCAN; 41772a8858fSMilanka Ringwald break; 41872a8858fSMilanka Ringwald } 41972a8858fSMilanka Ringwald // set timer 42072a8858fSMilanka Ringwald btstack_run_loop_set_timer(&connection_timer, 100); 42172a8858fSMilanka Ringwald btstack_run_loop_set_timer_handler(&connection_timer, &hog_reconnect_timeout); 42272a8858fSMilanka Ringwald btstack_run_loop_add_timer(&connection_timer); 42372a8858fSMilanka Ringwald break; 42472a8858fSMilanka Ringwald case HCI_EVENT_LE_META: 42572a8858fSMilanka Ringwald // wait for connection complete 42672a8858fSMilanka Ringwald if (hci_event_le_meta_get_subevent_code(packet) != HCI_SUBEVENT_LE_CONNECTION_COMPLETE) break; 42772a8858fSMilanka Ringwald if (app_state != W4_CONNECTED) return; 42872a8858fSMilanka Ringwald btstack_run_loop_remove_timer(&connection_timer); 42972a8858fSMilanka Ringwald connection_handle = hci_subevent_le_connection_complete_get_connection_handle(packet); 43072a8858fSMilanka Ringwald // request security 43172a8858fSMilanka Ringwald app_state = W4_ENCRYPTED; 43272a8858fSMilanka Ringwald sm_request_pairing(connection_handle); 43372a8858fSMilanka Ringwald break; 43472a8858fSMilanka Ringwald default: 43572a8858fSMilanka Ringwald break; 43672a8858fSMilanka Ringwald } 43772a8858fSMilanka Ringwald break; 43872a8858fSMilanka Ringwald default: 43972a8858fSMilanka Ringwald break; 44072a8858fSMilanka Ringwald } 44172a8858fSMilanka Ringwald } 44272a8858fSMilanka Ringwald /* LISTING_END */ 44372a8858fSMilanka Ringwald 44472a8858fSMilanka Ringwald /* @section HCI packet handler 44572a8858fSMilanka Ringwald * 44672a8858fSMilanka Ringwald * @text The SM packet handler receives Security Manager Events required for pairing. 44772a8858fSMilanka Ringwald * It also receives events generated during Identity Resolving 44872a8858fSMilanka Ringwald * see Listing SMPacketHandler. 44972a8858fSMilanka Ringwald */ 45072a8858fSMilanka Ringwald 45172a8858fSMilanka Ringwald /* LISTING_START(SMPacketHandler): Scanning and receiving advertisements */ 45272a8858fSMilanka Ringwald 45372a8858fSMilanka Ringwald static void sm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ 45472a8858fSMilanka Ringwald UNUSED(channel); 45572a8858fSMilanka Ringwald UNUSED(size); 45672a8858fSMilanka Ringwald 45772a8858fSMilanka Ringwald if (packet_type != HCI_EVENT_PACKET) return; 45872a8858fSMilanka Ringwald 45972a8858fSMilanka Ringwald switch (hci_event_packet_get_type(packet)) { 46072a8858fSMilanka Ringwald case SM_EVENT_JUST_WORKS_REQUEST: 46172a8858fSMilanka Ringwald printf("Just works requested\n"); 46272a8858fSMilanka Ringwald sm_just_works_confirm(sm_event_just_works_request_get_handle(packet)); 46372a8858fSMilanka Ringwald break; 46472a8858fSMilanka Ringwald case SM_EVENT_NUMERIC_COMPARISON_REQUEST: 46572a8858fSMilanka Ringwald printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet)); 46672a8858fSMilanka Ringwald sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet)); 46772a8858fSMilanka Ringwald break; 46872a8858fSMilanka Ringwald case SM_EVENT_PASSKEY_DISPLAY_NUMBER: 46972a8858fSMilanka Ringwald printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet)); 47072a8858fSMilanka Ringwald break; 47172a8858fSMilanka Ringwald case SM_EVENT_PAIRING_COMPLETE: 47272a8858fSMilanka Ringwald switch (sm_event_pairing_complete_get_status(packet)){ 47372a8858fSMilanka Ringwald case ERROR_CODE_SUCCESS: 47472a8858fSMilanka Ringwald printf("Pairing complete, success\n"); 47578404023SMatthias Ringwald 47678404023SMatthias Ringwald // continue - query primary services 47778404023SMatthias Ringwald printf("Search for HID service.\n"); 47878404023SMatthias Ringwald app_state = W4_HID_CLIENT_CONNECTED; 47978404023SMatthias Ringwald hids_client_connect(connection_handle, handle_gatt_client_event, protocol_mode, &hids_cid); 48072a8858fSMilanka Ringwald break; 48172a8858fSMilanka Ringwald case ERROR_CODE_CONNECTION_TIMEOUT: 48272a8858fSMilanka Ringwald printf("Pairing failed, timeout\n"); 48372a8858fSMilanka Ringwald break; 48472a8858fSMilanka Ringwald case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION: 48578404023SMatthias Ringwald printf("Pairing failed, disconnected\n"); 48672a8858fSMilanka Ringwald break; 48772a8858fSMilanka Ringwald case ERROR_CODE_AUTHENTICATION_FAILURE: 48872a8858fSMilanka Ringwald printf("Pairing failed, reason = %u\n", sm_event_pairing_complete_get_reason(packet)); 48972a8858fSMilanka Ringwald break; 49072a8858fSMilanka Ringwald default: 49172a8858fSMilanka Ringwald break; 49272a8858fSMilanka Ringwald } 49372a8858fSMilanka Ringwald break; 49472a8858fSMilanka Ringwald default: 49572a8858fSMilanka Ringwald break; 49672a8858fSMilanka Ringwald } 49772a8858fSMilanka Ringwald } 49872a8858fSMilanka Ringwald /* LISTING_END */ 49972a8858fSMilanka Ringwald 50072a8858fSMilanka Ringwald int btstack_main(int argc, const char * argv[]); 50172a8858fSMilanka Ringwald int btstack_main(int argc, const char * argv[]){ 50272a8858fSMilanka Ringwald 50372a8858fSMilanka Ringwald (void)argc; 50472a8858fSMilanka Ringwald (void)argv; 50572a8858fSMilanka Ringwald 506021192e1SMilanka Ringwald /* LISTING_START(HogBootHostSetup): HID-over-GATT Host Setup */ 50772a8858fSMilanka Ringwald 50872a8858fSMilanka Ringwald // register for events from HCI 50972a8858fSMilanka Ringwald hci_event_callback_registration.callback = &packet_handler; 51072a8858fSMilanka Ringwald hci_add_event_handler(&hci_event_callback_registration); 51172a8858fSMilanka Ringwald 51272a8858fSMilanka Ringwald // register for events from Security Manager 51372a8858fSMilanka Ringwald sm_event_callback_registration.callback = &sm_packet_handler; 51472a8858fSMilanka Ringwald sm_add_event_handler(&sm_event_callback_registration); 51572a8858fSMilanka Ringwald 51672a8858fSMilanka Ringwald // 51772a8858fSMilanka Ringwald l2cap_init(); 51872a8858fSMilanka Ringwald sm_init(); 51972a8858fSMilanka Ringwald gatt_client_init(); 520021192e1SMilanka Ringwald 521*68a70b34SMatthias Ringwald // setup ATT server - only needed if LE Peripheral does ATT queries on its own, e.g. Android and iOS 522*68a70b34SMatthias Ringwald att_server_init(profile_data, NULL, NULL); 523*68a70b34SMatthias Ringwald 524021192e1SMilanka Ringwald hids_client_init(hid_descriptor_storage, sizeof(hid_descriptor_storage)); 52572a8858fSMilanka Ringwald 52672a8858fSMilanka Ringwald /* LISTING_END */ 52772a8858fSMilanka Ringwald 52872a8858fSMilanka Ringwald // Disable stdout buffering 5290801ef92SMatthias Ringwald setvbuf(stdin, NULL, _IONBF, 0); 53072a8858fSMilanka Ringwald 53172a8858fSMilanka Ringwald app_state = W4_WORKING; 53272a8858fSMilanka Ringwald 53372a8858fSMilanka Ringwald // Turn on the device 53472a8858fSMilanka Ringwald hci_power_control(HCI_POWER_ON); 53572a8858fSMilanka Ringwald return 0; 53672a8858fSMilanka Ringwald } 53772a8858fSMilanka Ringwald 53872a8858fSMilanka Ringwald /* EXAMPLE_END */ 539