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
5868a70b34SMatthias Ringwald // hog_host_demo.gatt contains the declaration of the provided GATT Services + Characteristics
5968a70b34SMatthias Ringwald // hog_host_demo.h contains the binary representation of gatt_browser.gatt
6068a70b34SMatthias Ringwald // it is generated by the build system by calling: $BTSTACK_ROOT/tool/compile_gatt.py hog_host_demo.gatt hog_host_demo.h
6168a70b34SMatthias Ringwald // it needs to be regenerated when the GATT Database declared in gatt_browser.gatt file is modified
6268a70b34SMatthias Ringwald #include "hog_host_demo.h"
6368a70b34SMatthias 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];
hid_handle_input_report(uint8_t service_index,const uint8_t * report,uint16_t report_len)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);
227*5d4d8cc7SMatthias Ringwald fflush(stdout);
22872a8858fSMilanka Ringwald }
22972a8858fSMilanka Ringwald memcpy(last_keys, new_keys, NUM_KEYS);
23072a8858fSMilanka Ringwald }
23172a8858fSMilanka Ringwald
23272a8858fSMilanka Ringwald /**
23372a8858fSMilanka Ringwald * @section Test if advertisement contains HID UUID
23472a8858fSMilanka Ringwald * @param packet
23572a8858fSMilanka Ringwald * @param size
23672a8858fSMilanka Ringwald * @returns true if it does
23772a8858fSMilanka Ringwald */
adv_event_contains_hid_service(const uint8_t * packet)23872a8858fSMilanka Ringwald static bool adv_event_contains_hid_service(const uint8_t * packet){
23972a8858fSMilanka Ringwald const uint8_t * ad_data = gap_event_advertising_report_get_data(packet);
2400801ef92SMatthias Ringwald uint8_t ad_len = gap_event_advertising_report_get_data_length(packet);
24172a8858fSMilanka Ringwald return ad_data_contains_uuid16(ad_len, ad_data, ORG_BLUETOOTH_SERVICE_HUMAN_INTERFACE_DEVICE);
24272a8858fSMilanka Ringwald }
24372a8858fSMilanka Ringwald
24472a8858fSMilanka Ringwald /**
24572a8858fSMilanka Ringwald * Start scanning
24672a8858fSMilanka Ringwald */
hog_start_scan(void)24772a8858fSMilanka Ringwald static void hog_start_scan(void){
24872a8858fSMilanka Ringwald printf("Scanning for LE HID devices...\n");
24972a8858fSMilanka Ringwald app_state = W4_HID_DEVICE_FOUND;
25072a8858fSMilanka Ringwald // Passive scanning, 100% (scan interval = scan window)
25172a8858fSMilanka Ringwald gap_set_scan_parameters(0,48,48);
25272a8858fSMilanka Ringwald gap_start_scan();
25372a8858fSMilanka Ringwald }
25472a8858fSMilanka Ringwald
25572a8858fSMilanka Ringwald /**
25672a8858fSMilanka Ringwald * Handle timeout for outgoing connection
25772a8858fSMilanka Ringwald * @param ts
25872a8858fSMilanka Ringwald */
hog_connection_timeout(btstack_timer_source_t * ts)25972a8858fSMilanka Ringwald static void hog_connection_timeout(btstack_timer_source_t * ts){
26072a8858fSMilanka Ringwald UNUSED(ts);
26172a8858fSMilanka Ringwald printf("Timeout - abort connection\n");
26272a8858fSMilanka Ringwald gap_connect_cancel();
26372a8858fSMilanka Ringwald hog_start_scan();
26472a8858fSMilanka Ringwald }
26572a8858fSMilanka Ringwald
26672a8858fSMilanka Ringwald
26772a8858fSMilanka Ringwald /**
26872a8858fSMilanka Ringwald * Connect to remote device but set timer for timeout
26972a8858fSMilanka Ringwald */
hog_connect(void)27072a8858fSMilanka Ringwald static void hog_connect(void) {
27172a8858fSMilanka Ringwald // set timer
27272a8858fSMilanka Ringwald btstack_run_loop_set_timer(&connection_timer, 10000);
27372a8858fSMilanka Ringwald btstack_run_loop_set_timer_handler(&connection_timer, &hog_connection_timeout);
27472a8858fSMilanka Ringwald btstack_run_loop_add_timer(&connection_timer);
27572a8858fSMilanka Ringwald app_state = W4_CONNECTED;
27672a8858fSMilanka Ringwald gap_connect(remote_device.addr, remote_device.addr_type);
27772a8858fSMilanka Ringwald }
27872a8858fSMilanka Ringwald
27972a8858fSMilanka Ringwald /**
28072a8858fSMilanka Ringwald * Handle timer event to trigger reconnect
28172a8858fSMilanka Ringwald * @param ts
28272a8858fSMilanka Ringwald */
hog_reconnect_timeout(btstack_timer_source_t * ts)28372a8858fSMilanka Ringwald static void hog_reconnect_timeout(btstack_timer_source_t * ts){
28472a8858fSMilanka Ringwald UNUSED(ts);
28572a8858fSMilanka Ringwald switch (app_state){
28672a8858fSMilanka Ringwald case W4_TIMEOUT_THEN_RECONNECT:
28772a8858fSMilanka Ringwald hog_connect();
28872a8858fSMilanka Ringwald break;
28972a8858fSMilanka Ringwald case W4_TIMEOUT_THEN_SCAN:
29072a8858fSMilanka Ringwald hog_start_scan();
29172a8858fSMilanka Ringwald break;
29272a8858fSMilanka Ringwald default:
29372a8858fSMilanka Ringwald break;
29472a8858fSMilanka Ringwald }
29572a8858fSMilanka Ringwald }
29672a8858fSMilanka Ringwald
29772a8858fSMilanka Ringwald /**
29872a8858fSMilanka Ringwald * Start connecting after boot up: connect to last used device if possible, start scan otherwise
29972a8858fSMilanka Ringwald */
hog_start_connect(void)30072a8858fSMilanka Ringwald static void hog_start_connect(void){
30172a8858fSMilanka Ringwald // check if we have a bonded device
30272a8858fSMilanka Ringwald btstack_tlv_get_instance(&btstack_tlv_singleton_impl, &btstack_tlv_singleton_context);
30372a8858fSMilanka Ringwald if (btstack_tlv_singleton_impl){
30472a8858fSMilanka Ringwald int len = btstack_tlv_singleton_impl->get_tag(btstack_tlv_singleton_context, TLV_TAG_HOGD, (uint8_t *) &remote_device, sizeof(remote_device));
30572a8858fSMilanka Ringwald if (len == sizeof(remote_device)){
30672a8858fSMilanka 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));
30772a8858fSMilanka Ringwald hog_connect();
30872a8858fSMilanka Ringwald return;
30972a8858fSMilanka Ringwald }
31072a8858fSMilanka Ringwald }
31172a8858fSMilanka Ringwald // otherwise, scan for HID devices
31272a8858fSMilanka Ringwald hog_start_scan();
31372a8858fSMilanka Ringwald }
31472a8858fSMilanka Ringwald
31572a8858fSMilanka Ringwald /**
31672a8858fSMilanka Ringwald * In case of error, disconnect and start scanning again
31772a8858fSMilanka Ringwald */
handle_outgoing_connection_error(void)31872a8858fSMilanka Ringwald static void handle_outgoing_connection_error(void){
31972a8858fSMilanka Ringwald printf("Error occurred, disconnect and start over\n");
32072a8858fSMilanka Ringwald gap_disconnect(connection_handle);
32172a8858fSMilanka Ringwald hog_start_scan();
32272a8858fSMilanka Ringwald }
32372a8858fSMilanka Ringwald
32472a8858fSMilanka Ringwald /**
32572a8858fSMilanka Ringwald * Handle GATT Client Events dependent on current state
32672a8858fSMilanka Ringwald *
32772a8858fSMilanka Ringwald * @param packet_type
32872a8858fSMilanka Ringwald * @param channel
32972a8858fSMilanka Ringwald * @param packet
33072a8858fSMilanka Ringwald * @param size
33172a8858fSMilanka Ringwald */
handle_gatt_client_event(uint8_t packet_type,uint16_t channel,uint8_t * packet,uint16_t size)33272a8858fSMilanka Ringwald static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
33372a8858fSMilanka Ringwald UNUSED(packet_type);
33472a8858fSMilanka Ringwald UNUSED(channel);
33572a8858fSMilanka Ringwald UNUSED(size);
33672a8858fSMilanka Ringwald
33772a8858fSMilanka Ringwald uint8_t status;
33872a8858fSMilanka Ringwald
33972a8858fSMilanka Ringwald if (hci_event_packet_get_type(packet) != HCI_EVENT_GATTSERVICE_META){
34072a8858fSMilanka Ringwald return;
34172a8858fSMilanka Ringwald }
34272a8858fSMilanka Ringwald
34372a8858fSMilanka Ringwald switch (hci_event_gattservice_meta_get_subevent_code(packet)){
34472a8858fSMilanka Ringwald case GATTSERVICE_SUBEVENT_HID_SERVICE_CONNECTED:
34572a8858fSMilanka Ringwald status = gattservice_subevent_hid_service_connected_get_status(packet);
34672a8858fSMilanka Ringwald switch (status){
34772a8858fSMilanka Ringwald case ERROR_CODE_SUCCESS:
34872a8858fSMilanka Ringwald printf("HID service client connected, found %d services\n",
34972a8858fSMilanka Ringwald gattservice_subevent_hid_service_connected_get_num_instances(packet));
35072a8858fSMilanka Ringwald
35172a8858fSMilanka Ringwald // store device as bonded
35272a8858fSMilanka Ringwald if (btstack_tlv_singleton_impl){
35372a8858fSMilanka Ringwald btstack_tlv_singleton_impl->store_tag(btstack_tlv_singleton_context, TLV_TAG_HOGD, (const uint8_t *) &remote_device, sizeof(remote_device));
35472a8858fSMilanka Ringwald }
35572a8858fSMilanka Ringwald // done
35672a8858fSMilanka Ringwald printf("Ready - please start typing or mousing..\n");
35772a8858fSMilanka Ringwald app_state = READY;
35872a8858fSMilanka Ringwald break;
35972a8858fSMilanka Ringwald default:
360fcd55a0bSMilanka Ringwald printf("HID service client connection failed, status 0x%02x.\n", status);
36172a8858fSMilanka Ringwald handle_outgoing_connection_error();
36272a8858fSMilanka Ringwald break;
36372a8858fSMilanka Ringwald }
36472a8858fSMilanka Ringwald break;
365021192e1SMilanka Ringwald
36633d66a9bSMatthias Ringwald case GATTSERVICE_SUBEVENT_HID_SERVICE_DISCONNECTED:
36733d66a9bSMatthias Ringwald printf("HID service client disconnected\n");
36833d66a9bSMatthias Ringwald hog_start_connect();
36933d66a9bSMatthias Ringwald break;
37033d66a9bSMatthias Ringwald
37172a8858fSMilanka Ringwald case GATTSERVICE_SUBEVENT_HID_REPORT:
372021192e1SMilanka Ringwald hid_handle_input_report(
373021192e1SMilanka Ringwald gattservice_subevent_hid_report_get_service_index(packet),
37472a8858fSMilanka Ringwald gattservice_subevent_hid_report_get_report(packet),
37572a8858fSMilanka Ringwald gattservice_subevent_hid_report_get_report_len(packet));
37672a8858fSMilanka Ringwald break;
37772a8858fSMilanka Ringwald
37872a8858fSMilanka Ringwald default:
37972a8858fSMilanka Ringwald break;
38072a8858fSMilanka Ringwald }
38172a8858fSMilanka Ringwald }
38272a8858fSMilanka Ringwald
38372a8858fSMilanka Ringwald /* LISTING_START(packetHandler): Packet Handler */
packet_handler(uint8_t packet_type,uint16_t channel,uint8_t * packet,uint16_t size)38472a8858fSMilanka Ringwald static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
38572a8858fSMilanka Ringwald /* LISTING_PAUSE */
38672a8858fSMilanka Ringwald UNUSED(channel);
38772a8858fSMilanka Ringwald UNUSED(size);
38872a8858fSMilanka Ringwald uint8_t event;
38972a8858fSMilanka Ringwald /* LISTING_RESUME */
39072a8858fSMilanka Ringwald switch (packet_type) {
39172a8858fSMilanka Ringwald case HCI_EVENT_PACKET:
39272a8858fSMilanka Ringwald event = hci_event_packet_get_type(packet);
39372a8858fSMilanka Ringwald switch (event) {
39472a8858fSMilanka Ringwald case BTSTACK_EVENT_STATE:
39572a8858fSMilanka Ringwald if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING) break;
39672a8858fSMilanka Ringwald btstack_assert(app_state == W4_WORKING);
3972901a9b7SMilanka Ringwald
39872a8858fSMilanka Ringwald hog_start_connect();
39972a8858fSMilanka Ringwald break;
40072a8858fSMilanka Ringwald case GAP_EVENT_ADVERTISING_REPORT:
40172a8858fSMilanka Ringwald if (app_state != W4_HID_DEVICE_FOUND) break;
40272a8858fSMilanka Ringwald if (adv_event_contains_hid_service(packet) == false) break;
40372a8858fSMilanka Ringwald // stop scan
40472a8858fSMilanka Ringwald gap_stop_scan();
40572a8858fSMilanka Ringwald // store remote device address and type
40672a8858fSMilanka Ringwald gap_event_advertising_report_get_address(packet, remote_device.addr);
40772a8858fSMilanka Ringwald remote_device.addr_type = gap_event_advertising_report_get_address_type(packet);
40872a8858fSMilanka Ringwald // connect
40972a8858fSMilanka 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));
41072a8858fSMilanka Ringwald hog_connect();
41172a8858fSMilanka Ringwald break;
41272a8858fSMilanka Ringwald case HCI_EVENT_DISCONNECTION_COMPLETE:
4132901a9b7SMilanka Ringwald if (app_state != READY) break;
41472a8858fSMilanka Ringwald connection_handle = HCI_CON_HANDLE_INVALID;
41572a8858fSMilanka Ringwald switch (app_state){
41672a8858fSMilanka Ringwald case READY:
41772a8858fSMilanka Ringwald printf("\nDisconnected, try to reconnect...\n");
41872a8858fSMilanka Ringwald app_state = W4_TIMEOUT_THEN_RECONNECT;
41972a8858fSMilanka Ringwald break;
42072a8858fSMilanka Ringwald default:
42172a8858fSMilanka Ringwald printf("\nDisconnected, start over...\n");
42272a8858fSMilanka Ringwald app_state = W4_TIMEOUT_THEN_SCAN;
42372a8858fSMilanka Ringwald break;
42472a8858fSMilanka Ringwald }
42572a8858fSMilanka Ringwald // set timer
42672a8858fSMilanka Ringwald btstack_run_loop_set_timer(&connection_timer, 100);
42772a8858fSMilanka Ringwald btstack_run_loop_set_timer_handler(&connection_timer, &hog_reconnect_timeout);
42872a8858fSMilanka Ringwald btstack_run_loop_add_timer(&connection_timer);
42972a8858fSMilanka Ringwald break;
430bba48196SMatthias Ringwald case HCI_EVENT_META_GAP:
43172a8858fSMilanka Ringwald // wait for connection complete
432bba48196SMatthias Ringwald if (hci_event_gap_meta_get_subevent_code(packet) != GAP_SUBEVENT_LE_CONNECTION_COMPLETE) break;
43372a8858fSMilanka Ringwald if (app_state != W4_CONNECTED) return;
43472a8858fSMilanka Ringwald btstack_run_loop_remove_timer(&connection_timer);
435bba48196SMatthias Ringwald connection_handle = gap_subevent_le_connection_complete_get_connection_handle(packet);
43672a8858fSMilanka Ringwald // request security
43772a8858fSMilanka Ringwald app_state = W4_ENCRYPTED;
43872a8858fSMilanka Ringwald sm_request_pairing(connection_handle);
43972a8858fSMilanka Ringwald break;
44072a8858fSMilanka Ringwald default:
44172a8858fSMilanka Ringwald break;
44272a8858fSMilanka Ringwald }
44372a8858fSMilanka Ringwald break;
44472a8858fSMilanka Ringwald default:
44572a8858fSMilanka Ringwald break;
44672a8858fSMilanka Ringwald }
44772a8858fSMilanka Ringwald }
44872a8858fSMilanka Ringwald /* LISTING_END */
44972a8858fSMilanka Ringwald
45072a8858fSMilanka Ringwald /* @section HCI packet handler
45172a8858fSMilanka Ringwald *
45272a8858fSMilanka Ringwald * @text The SM packet handler receives Security Manager Events required for pairing.
45372a8858fSMilanka Ringwald * It also receives events generated during Identity Resolving
45472a8858fSMilanka Ringwald * see Listing SMPacketHandler.
45572a8858fSMilanka Ringwald */
45672a8858fSMilanka Ringwald
45772a8858fSMilanka Ringwald /* LISTING_START(SMPacketHandler): Scanning and receiving advertisements */
45872a8858fSMilanka Ringwald
sm_packet_handler(uint8_t packet_type,uint16_t channel,uint8_t * packet,uint16_t size)45972a8858fSMilanka Ringwald static void sm_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){
46072a8858fSMilanka Ringwald UNUSED(channel);
46172a8858fSMilanka Ringwald UNUSED(size);
46272a8858fSMilanka Ringwald
46372a8858fSMilanka Ringwald if (packet_type != HCI_EVENT_PACKET) return;
46472a8858fSMilanka Ringwald
4653f6e1c81SMatthias Ringwald bool connect_to_service = false;
4663f6e1c81SMatthias Ringwald
46772a8858fSMilanka Ringwald switch (hci_event_packet_get_type(packet)) {
46872a8858fSMilanka Ringwald case SM_EVENT_JUST_WORKS_REQUEST:
46972a8858fSMilanka Ringwald printf("Just works requested\n");
47072a8858fSMilanka Ringwald sm_just_works_confirm(sm_event_just_works_request_get_handle(packet));
47172a8858fSMilanka Ringwald break;
47272a8858fSMilanka Ringwald case SM_EVENT_NUMERIC_COMPARISON_REQUEST:
47372a8858fSMilanka Ringwald printf("Confirming numeric comparison: %"PRIu32"\n", sm_event_numeric_comparison_request_get_passkey(packet));
47472a8858fSMilanka Ringwald sm_numeric_comparison_confirm(sm_event_passkey_display_number_get_handle(packet));
47572a8858fSMilanka Ringwald break;
47672a8858fSMilanka Ringwald case SM_EVENT_PASSKEY_DISPLAY_NUMBER:
47772a8858fSMilanka Ringwald printf("Display Passkey: %"PRIu32"\n", sm_event_passkey_display_number_get_passkey(packet));
47872a8858fSMilanka Ringwald break;
47972a8858fSMilanka Ringwald case SM_EVENT_PAIRING_COMPLETE:
48072a8858fSMilanka Ringwald switch (sm_event_pairing_complete_get_status(packet)){
48172a8858fSMilanka Ringwald case ERROR_CODE_SUCCESS:
48272a8858fSMilanka Ringwald printf("Pairing complete, success\n");
4833f6e1c81SMatthias Ringwald connect_to_service = true;
48472a8858fSMilanka Ringwald break;
48572a8858fSMilanka Ringwald case ERROR_CODE_CONNECTION_TIMEOUT:
48672a8858fSMilanka Ringwald printf("Pairing failed, timeout\n");
48772a8858fSMilanka Ringwald break;
48872a8858fSMilanka Ringwald case ERROR_CODE_REMOTE_USER_TERMINATED_CONNECTION:
48978404023SMatthias Ringwald printf("Pairing failed, disconnected\n");
49072a8858fSMilanka Ringwald break;
49172a8858fSMilanka Ringwald case ERROR_CODE_AUTHENTICATION_FAILURE:
49272a8858fSMilanka Ringwald printf("Pairing failed, reason = %u\n", sm_event_pairing_complete_get_reason(packet));
49372a8858fSMilanka Ringwald break;
49472a8858fSMilanka Ringwald default:
49572a8858fSMilanka Ringwald break;
49672a8858fSMilanka Ringwald }
49772a8858fSMilanka Ringwald break;
4983f6e1c81SMatthias Ringwald case SM_EVENT_REENCRYPTION_COMPLETE:
4993f6e1c81SMatthias Ringwald printf("Re-encryption complete, success\n");
5003f6e1c81SMatthias Ringwald connect_to_service = true;
5013f6e1c81SMatthias Ringwald break;
50272a8858fSMilanka Ringwald default:
50372a8858fSMilanka Ringwald break;
50472a8858fSMilanka Ringwald }
5053f6e1c81SMatthias Ringwald
5063f6e1c81SMatthias Ringwald if (connect_to_service){
5073f6e1c81SMatthias Ringwald // continue - query primary services
5083f6e1c81SMatthias Ringwald printf("Search for HID service.\n");
5093f6e1c81SMatthias Ringwald app_state = W4_HID_CLIENT_CONNECTED;
5103f6e1c81SMatthias Ringwald hids_client_connect(connection_handle, handle_gatt_client_event, protocol_mode, &hids_cid);
5113f6e1c81SMatthias Ringwald }
51272a8858fSMilanka Ringwald }
51372a8858fSMilanka Ringwald /* LISTING_END */
51472a8858fSMilanka Ringwald
51572a8858fSMilanka Ringwald int btstack_main(int argc, const char * argv[]);
btstack_main(int argc,const char * argv[])51672a8858fSMilanka Ringwald int btstack_main(int argc, const char * argv[]){
51772a8858fSMilanka Ringwald
51872a8858fSMilanka Ringwald (void)argc;
51972a8858fSMilanka Ringwald (void)argv;
52072a8858fSMilanka Ringwald
521021192e1SMilanka Ringwald /* LISTING_START(HogBootHostSetup): HID-over-GATT Host Setup */
52272a8858fSMilanka Ringwald
5233f6e1c81SMatthias Ringwald l2cap_init();
5243f6e1c81SMatthias Ringwald
5253f6e1c81SMatthias Ringwald // setup SM: Display only
5263f6e1c81SMatthias Ringwald sm_init();
5273f6e1c81SMatthias Ringwald sm_set_io_capabilities(IO_CAPABILITY_DISPLAY_ONLY);
5283f6e1c81SMatthias Ringwald sm_set_authentication_requirements(SM_AUTHREQ_SECURE_CONNECTION | SM_AUTHREQ_BONDING);
5293f6e1c81SMatthias Ringwald
5303f6e1c81SMatthias Ringwald //
5313f6e1c81SMatthias Ringwald gatt_client_init();
5323f6e1c81SMatthias Ringwald
5333f6e1c81SMatthias Ringwald // setup ATT server - only needed if LE Peripheral does ATT queries on its own, e.g. Android and iOS
5343f6e1c81SMatthias Ringwald att_server_init(profile_data, NULL, NULL);
5353f6e1c81SMatthias Ringwald
5363f6e1c81SMatthias Ringwald hids_client_init(hid_descriptor_storage, sizeof(hid_descriptor_storage));
5373f6e1c81SMatthias Ringwald
53872a8858fSMilanka Ringwald // register for events from HCI
53972a8858fSMilanka Ringwald hci_event_callback_registration.callback = &packet_handler;
54072a8858fSMilanka Ringwald hci_add_event_handler(&hci_event_callback_registration);
54172a8858fSMilanka Ringwald
54272a8858fSMilanka Ringwald // register for events from Security Manager
54372a8858fSMilanka Ringwald sm_event_callback_registration.callback = &sm_packet_handler;
54472a8858fSMilanka Ringwald sm_add_event_handler(&sm_event_callback_registration);
5453f6e1c81SMatthias Ringwald sm_set_authentication_requirements( SM_AUTHREQ_BONDING);
54672a8858fSMilanka Ringwald
54772a8858fSMilanka Ringwald /* LISTING_END */
54872a8858fSMilanka Ringwald
54972a8858fSMilanka Ringwald // Disable stdout buffering
5500801ef92SMatthias Ringwald setvbuf(stdin, NULL, _IONBF, 0);
55172a8858fSMilanka Ringwald
55272a8858fSMilanka Ringwald app_state = W4_WORKING;
55372a8858fSMilanka Ringwald
55472a8858fSMilanka Ringwald // Turn on the device
55572a8858fSMilanka Ringwald hci_power_control(HCI_POWER_ON);
55672a8858fSMilanka Ringwald return 0;
55772a8858fSMilanka Ringwald }
55872a8858fSMilanka Ringwald
55972a8858fSMilanka Ringwald /* EXAMPLE_END */
560