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