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