diff --git a/example/hid_keyboard_demo.c b/example/hid_keyboard_demo.c index 110986679..be3cd423f 100644 --- a/example/hid_keyboard_demo.c +++ b/example/hid_keyboard_demo.c @@ -39,6 +39,8 @@ // ***************************************************************************** /* EXAMPLE_START(hid_device_demo): HID Device (Server) Demo + * + * Status: Basic implementation. HID Request from Host are not answered yet. Works with iOS. * * @text This HID Device example demonstrates how to implement * an HID keyboard. Without a HAVE_POSIX_STDIN, a fixed demo text is sent @@ -56,19 +58,17 @@ #include "btstack.h" -#undef HAVE_POSIX_STDIN - #ifdef HAVE_POSIX_STDIN #include "stdin_support.h" #endif -uint8_t hid_service_buffer[250]; -const char hid_device_name[] = "BTstack HID Keyboard"; -static btstack_packet_callback_registration_t hci_event_callback_registration; +// to enable demo text on POSIX systems +// #undef HAVE_POSIX_STDIN -// hid device state -static uint16_t hid_control_cid; -static uint16_t hid_interrupt_cid; +static uint8_t hid_service_buffer[250]; +static const char hid_device_name[] = "BTstack HID Keyboard"; +static btstack_packet_callback_registration_t hci_event_callback_registration; +static uint16_t hid_cid; // from USB HID Specification 1.1, Appendix B.1 const uint8_t hid_descriptor_keyboard_boot_mode[] = { @@ -204,34 +204,19 @@ static int send_modifier; static void send_key(int modifier, int keycode){ send_keycode = keycode; send_modifier = modifier; - l2cap_request_can_send_now_event(hid_interrupt_cid); + hid_device_request_can_send_now_event(hid_cid); } static void send_report(int modifier, int keycode){ uint8_t report[] = { 0xa1, modifier, 0, 0, keycode, 0, 0, 0, 0, 0}; - l2cap_send(hid_interrupt_cid, &report[0], sizeof(report)); + hid_device_send_interrupt_message(hid_cid, &report[0], sizeof(report)); } -static int hid_connected(void){ - return hid_control_cid && hid_interrupt_cid; -} +// Demo Application #ifdef HAVE_POSIX_STDIN -// prototypes -static void show_usage(void); - -// Testig User Interface -static void show_usage(void){ - bd_addr_t iut_address; - gap_local_bd_addr(iut_address); - - printf("\n--- Bluetooth HID Device Test Console %s ---\n", bd_addr_to_str(iut_address)); - printf("\n"); - printf("---\n"); - printf("Ctrl-c - exit\n"); - printf("---\n"); -} +// On systems with STDIN, we can directly type on the console static void stdin_process(btstack_data_source_t *ds, btstack_data_source_callback_type_t callback_type){ UNUSED(ds); @@ -246,7 +231,6 @@ static void stdin_process(btstack_data_source_t *ds, btstack_data_source_callbac send_key(modifier, keycode); return; } - show_usage(); } #else @@ -261,7 +245,7 @@ static btstack_timer_source_t typing_timer; static void typing_timer_handler(btstack_timer_source_t * ts){ // abort if not connected - if (!hid_connected()) return; + if (!hid_cid) return; // get next character uint8_t character = demo_text[demo_pos++]; @@ -282,7 +266,7 @@ static void typing_timer_handler(btstack_timer_source_t * ts){ btstack_run_loop_add_timer(ts); } -static void hid_start_typing(void){ +static void hid_embedded_start_typing(void){ demo_pos = 0; // set one-shot timer typing_timer.process = &typing_timer_handler; @@ -295,7 +279,6 @@ static void hid_start_typing(void){ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t * packet, uint16_t packet_size){ UNUSED(channel); UNUSED(packet_size); - int connected_before; switch (packet_type){ case HCI_EVENT_PACKET: switch (packet[0]){ @@ -305,63 +288,36 @@ static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t * pack log_info("SSP User Confirmation Auto accept\n"); break; - // into HID Device/Server - case L2CAP_EVENT_INCOMING_CONNECTION: - switch (l2cap_event_incoming_connection_get_psm(packet)){ - case PSM_HID_CONTROL: - case PSM_HID_INTERRUPT: - l2cap_accept_connection(channel); - break; - default: - l2cap_decline_connection(channel); - break; - } - break; - case L2CAP_EVENT_CHANNEL_OPENED: - if (packet[2]) return; - connected_before = hid_connected(); - switch (l2cap_event_channel_opened_get_psm(packet)){ - case PSM_HID_CONTROL: - hid_control_cid = l2cap_event_channel_opened_get_local_cid(packet); - log_info("HID Control opened, cid 0x%02x", hid_control_cid); - break; - case PSM_HID_INTERRUPT: - hid_interrupt_cid = l2cap_event_channel_opened_get_local_cid(packet); - log_info("HID Interrupt opened, cid 0x%02x", hid_interrupt_cid); - break; - default: - break; - } - if (!connected_before && hid_connected()){ - printf("HID Connected\n"); -#ifndef HAVE_POSIX_STDIN - hid_start_typing(); + case HCI_EVENT_HID_META: + switch (hci_event_hid_meta_get_subevent_code(packet)){ + case HID_SUBEVENT_CONNECTION_OPENED: + if (hid_subevent_connection_opened_get_status(packet)) return; + hid_cid = hid_subevent_connection_opened_get_hid_cid(packet); +#ifdef HAVE_POSIX_STDIN + printf("HID Connected, please start typing...\n"); +#else + printf("HID Connected, sending demo text...\n"); + hid_embedded_start_typing(); #endif + break; + case HID_SUBEVENT_CONNECTION_CLOSED: + printf("HID Disconnected\n"); + hid_cid = 0; + break; + case HID_SUBEVENT_CAN_SEND_NOW: + if (send_keycode){ + send_report(send_modifier, send_keycode); + send_keycode = 0; + send_modifier = 0; + hid_device_request_can_send_now_event(hid_cid); + } else { + send_report(0, 0); + } + break; + default: + break; } break; - case L2CAP_EVENT_CHANNEL_CLOSED: - connected_before = hid_connected(); - if (l2cap_event_channel_closed_get_local_cid(packet) == hid_control_cid){ - log_info("HID Control closed"); - hid_control_cid = 0; - } - if (l2cap_event_channel_closed_get_local_cid(packet) == hid_interrupt_cid){ - log_info("HID Interrupt closed"); - hid_interrupt_cid = 0; - } - if (connected_before && !hid_connected()){ - printf("HID Disconnected\n"); - } - break; - case L2CAP_EVENT_CAN_SEND_NOW: - if (send_keycode){ - send_report(send_modifier, send_keycode); - send_keycode = 0; - send_modifier = 0; - l2cap_request_can_send_now_event(hid_interrupt_cid); - } else { - send_report(0, 0); - } default: break; } @@ -396,8 +352,6 @@ int btstack_main(int argc, const char * argv[]){ // L2CAP l2cap_init(); - l2cap_register_service(packet_handler, PSM_HID_INTERRUPT, 100, LEVEL_0); - l2cap_register_service(packet_handler, PSM_HID_CONTROL, 100, LEVEL_0); // SDP Server sdp_init(); @@ -407,6 +361,10 @@ int btstack_main(int argc, const char * argv[]){ printf("SDP service record size: %u\n", de_get_len( hid_service_buffer)); sdp_register_service(hid_service_buffer); + // HID Device + hid_device_init(); + hid_device_register_packet_handler(&packet_handler); + #ifdef HAVE_POSIX_STDIN btstack_stdin_setup(stdin_process); #endif diff --git a/src/classic/hid_device.c b/src/classic/hid_device.c index 57a2f08ee..b9e6df95f 100644 --- a/src/classic/hid_device.c +++ b/src/classic/hid_device.c @@ -38,11 +38,29 @@ #define __BTSTACK_FILE__ "hid_device.c" #include - + #include "classic/hid_device.h" #include "classic/sdp_util.h" #include "bluetooth.h" #include "bluetooth_sdp.h" +#include "l2cap.h" +#include "btstack_event.h" +#include "btstack_debug.h" + +// hid device state +typedef struct hid_device { + uint16_t cid; + bd_addr_t bd_addr; + hci_con_handle_t con_handle; + uint16_t control_cid; + uint16_t interrupt_cid; + uint8_t incoming; +} hid_device_t; + +static hid_device_t _hid_device; +static hid_device_t * hid_device = &_hid_device; + +static btstack_packet_handler_t hid_callback; void hid_create_sdp_record( uint8_t *service, @@ -167,4 +185,176 @@ void hid_create_sdp_record( de_add_number(service, DE_UINT, DE_SIZE_16, BLUETOOTH_ATTRIBUTE_HID_BOOT_DEVICE); de_add_number(service, DE_BOOL, DE_SIZE_8, hid_boot_device); +} + +static inline void hid_device_emit_connected_event(hid_device_t * context, uint8_t status){ + uint8_t event[15]; + int pos = 0; + event[pos++] = HCI_EVENT_HID_META; + pos++; // skip len + event[pos++] = HID_SUBEVENT_CONNECTION_OPENED; + little_endian_store_16(event,pos,context->cid); + pos+=2; + event[pos++] = status; + memcpy(&event[pos], context->bd_addr, 6); + pos += 6; + little_endian_store_16(event,pos,context->con_handle); + pos += 2; + event[pos++] = context->incoming; + event[1] = pos - 2; + if (pos != sizeof(event)) log_error("hid_device_emit_connected_event size %u", pos); + hid_callback(HCI_EVENT_PACKET, context->cid, &event[0], pos); } + +static inline void hid_device_emit_connection_closed_event(hid_device_t * context){ + uint8_t event[5]; + int pos = 0; + event[pos++] = HCI_EVENT_HID_META; + pos++; // skip len + event[pos++] = HID_SUBEVENT_CONNECTION_CLOSED; + little_endian_store_16(event,pos,context->cid); + pos+=2; + event[1] = pos - 2; + if (pos != sizeof(event)) log_error("hid_device_emit_connection_closed_event size %u", pos); + hid_callback(HCI_EVENT_PACKET, context->cid, &event[0], pos); +} + +static inline void hid_device_emit_can_send_now_event(hid_device_t * context){ + uint8_t event[5]; + int pos = 0; + event[pos++] = HCI_EVENT_HID_META; + pos++; // skip len + event[pos++] = HID_SUBEVENT_CAN_SEND_NOW; + little_endian_store_16(event,pos,context->cid); + pos+=2; + event[1] = pos - 2; + if (pos != sizeof(event)) log_error("hid_device_emit_can_send_now_event size %u", pos); + hid_callback(HCI_EVENT_PACKET, context->cid, &event[0], pos); +} + + +static int hid_connected(void){ + return hid_device->control_cid && hid_device->interrupt_cid; +} + +static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t * packet, uint16_t packet_size){ + UNUSED(channel); + UNUSED(packet_size); + int connected_before; + switch (packet_type){ + case HCI_EVENT_PACKET: + switch (packet[0]){ + case L2CAP_EVENT_INCOMING_CONNECTION: + switch (l2cap_event_incoming_connection_get_psm(packet)){ + case PSM_HID_CONTROL: + case PSM_HID_INTERRUPT: + if (hid_device->con_handle == 0 || l2cap_event_incoming_connection_get_handle(packet) == hid_device->con_handle){ + hid_device->con_handle = l2cap_event_incoming_connection_get_handle(packet); + l2cap_accept_connection(channel); + } else { + l2cap_decline_connection(channel); + } + break; + default: + l2cap_decline_connection(channel); + break; + } + break; + case L2CAP_EVENT_CHANNEL_OPENED: + if (l2cap_event_channel_opened_get_status(packet)) return; + connected_before = hid_connected(); + switch (l2cap_event_channel_opened_get_psm(packet)){ + case PSM_HID_CONTROL: + hid_device->control_cid = l2cap_event_channel_opened_get_local_cid(packet); + log_info("HID Control opened, cid 0x%02x", hid_device->control_cid); + break; + case PSM_HID_INTERRUPT: + hid_device->interrupt_cid = l2cap_event_channel_opened_get_local_cid(packet); + log_info("HID Interrupt opened, cid 0x%02x", hid_device->interrupt_cid); + break; + default: + break; + } + if (!connected_before && hid_connected()){ + hid_device->incoming = 1; + log_info("HID Connected"); + hid_device_emit_connected_event(hid_device, 0); + } + break; + case L2CAP_EVENT_CHANNEL_CLOSED: + connected_before = hid_connected(); + if (l2cap_event_channel_closed_get_local_cid(packet) == hid_device->control_cid){ + log_info("HID Control closed"); + hid_device->control_cid = 0; + } + if (l2cap_event_channel_closed_get_local_cid(packet) == hid_device->interrupt_cid){ + log_info("HID Interrupt closed"); + hid_device->interrupt_cid = 0; + } + if (connected_before && !hid_connected()){ + hid_device->con_handle = 0; + log_info("HID Disconnected"); + hid_device_emit_connection_closed_event(hid_device); + } + break; + case L2CAP_EVENT_CAN_SEND_NOW: + log_info("HID Can send now, emit event"); + hid_device_emit_can_send_now_event(hid_device); + break; + default: + break; + } + break; + default: + break; + } +} + +/** + * @brief Set up HID Device + */ +void hid_device_init(void){ + memset(hid_device, 0, sizeof(hid_device_t)); + hid_device->cid = 1; + l2cap_register_service(packet_handler, PSM_HID_INTERRUPT, 100, LEVEL_0); + l2cap_register_service(packet_handler, PSM_HID_CONTROL, 100, LEVEL_0); +} + +/** + * @brief Register callback for the HID Device client. + * @param callback + */ +void hid_device_register_packet_handler(btstack_packet_handler_t callback){ + hid_callback = callback; +} + +/** + * @brief Request can send now event to send HID Report + * @param hid_cid + */ +void hid_device_request_can_send_now_event(uint16_t hid_cid){ + UNUSED(hid_cid); + if (!hid_device->control_cid) return; + l2cap_request_can_send_now_event(hid_device->control_cid); +} + +/** + * @brief Send HID messageon interrupt channel + * @param hid_cid + */ +void hid_device_send_interrupt_message(uint16_t hid_cid, const uint8_t * message, uint16_t message_len){ + UNUSED(hid_cid); + if (!hid_device->interrupt_cid) return; + l2cap_send(hid_device->interrupt_cid, (uint8_t*) message, message_len); +} + +/** + * @brief Send HID messageon control channel + * @param hid_cid + */ +void hid_device_send_contro_message(uint16_t hid_cid, const uint8_t * message, uint16_t message_len){ + UNUSED(hid_cid); + if (!hid_device->control_cid) return; + l2cap_send(hid_device->control_cid, (uint8_t*) message, message_len); +} + diff --git a/src/classic/hid_device.h b/src/classic/hid_device.h index ae3359275..daa6d4837 100644 --- a/src/classic/hid_device.h +++ b/src/classic/hid_device.h @@ -36,7 +36,7 @@ */ #include - +#include "btstack_defines.h" /** * @brief Create HID Device SDP service record. * @param service Empty buffer in which a new service record will be stored. @@ -64,3 +64,34 @@ void hid_create_sdp_record( uint16_t hid_descriptor_size, const char * device_name); +/** + * @brief Set up HID Device + */ +void hid_device_init(void); + +/** + * @brief Register callback for the HID Device client. + * @param callback + */ +void hid_device_register_packet_handler(btstack_packet_handler_t callback); + +/** + * @brief Request can send now event to send HID Report + * Generates an HID_SUBEVENT_CAN_SEND_NOW subevent + * @param hid_cid + */ +void hid_device_request_can_send_now_event(uint16_t hid_cid); + +/** + * @brief Send HID messageon interrupt channel + * @param hid_cid + */ +void hid_device_send_interrupt_message(uint16_t hid_cid, const uint8_t * message, uint16_t message_len); + +/** + * @brief Send HID messageon control channel + * @param hid_cid + */ +void hid_device_send_contro_message(uint16_t hid_cid, const uint8_t * message, uint16_t message_len); + +