xref: /btstack/example/hog_host_demo.c (revision 2901a9b7af2a81f193187d7d58a554b5f7f1928c)
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