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