diff --git a/example/Makefile.inc b/example/Makefile.inc index d441ec122..eaac84bf4 100644 --- a/example/Makefile.inc +++ b/example/Makefile.inc @@ -328,7 +328,7 @@ hog_mouse_demo: hog_mouse_demo.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_SER hog_boot_host_demo: ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_CLIENT_OBJ} hog_boot_host_demo.c ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ -hog_host_demo: ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_CLIENT_OBJ} hog_host_demo.c +hog_host_demo: ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_CLIENT_OBJ} btstack_hid_parser.o hid.o hog_host_demo.c ${CC} $^ ${CFLAGS} ${LDFLAGS} -o $@ sm_pairing_peripheral: sm_pairing_peripheral.h ${CORE_OBJ} ${COMMON_OBJ} ${ATT_OBJ} ${GATT_SERVER_OBJ} ${GATT_CLIENT_OBJ} sm_pairing_peripheral.c diff --git a/example/hog_host_demo.c b/example/hog_host_demo.c index 0d75d2c90..fa6401acc 100644 --- a/example/hog_host_demo.c +++ b/example/hog_host_demo.c @@ -35,15 +35,15 @@ * */ -#define BTSTACK_FILE__ "hog_boot_host_demo.c" +#define BTSTACK_FILE__ "hog_host_demo.c" /* - * hog_boot_host_demo.c + * hog_host_demo.c */ -/* EXAMPLE_START(hog_boot_host_demo): HID Boot Host LE +/* EXAMPLE_START(hog_host_demo): HID Host LE * - * @text This example implements a minimal HID-over-GATT Boot Host. It scans for LE HID devices, connects to it, + * @text This example implements a minimal HID-over-GATT Host. It scans for LE HID devices, connects to it, * discovers the Characteristics relevant for the HID Service and enables Notifications on them. * It then dumps all Boot Keyboard and Mouse Input Reports */ @@ -77,6 +77,10 @@ static enum { static le_device_addr_t remote_device; static hci_con_handle_t connection_handle; static uint16_t hids_cid; +static hid_protocol_mode_t protocol_mode = HID_PROTOCOL_MODE_REPORT; + +// SDP +static uint8_t hid_descriptor_storage[500]; // used to implement connection timeout and reconnect timer static btstack_timer_source_t connection_timer; @@ -138,31 +142,56 @@ static const uint8_t keytable_us_shift[] = { '6', '7', '8', '9', '0', '.', 0xb1, /* 97-100 */ }; -/** - * @section HOG Boot Keyboard Handler - * @text Boot Keyboard Input Report contains a report of format - * [ modifier, reserved, 6 x usage for key 1..6 from keyboard usage] - * Track new usages, map key usage to actual character and simulate terminal - */ -/* last_keys stores keyboard report to detect new key down events */ + #define NUM_KEYS 6 static uint8_t last_keys[NUM_KEYS]; +static void hid_handle_input_report(uint8_t service_index, const uint8_t * report, uint16_t report_len){ + // check if HID Input Report + + if (report_len < 1) return; + + btstack_hid_parser_t parser; + + switch (protocol_mode){ + case HID_PROTOCOL_MODE_BOOT: + btstack_hid_parser_init(&parser, + hid_get_boot_descriptor_data(), + hid_get_boot_descriptor_len(), + HID_REPORT_TYPE_INPUT, report, report_len); + break; -static void handle_boot_keyboard_event(const uint8_t * report, uint16_t report_len){ - UNUSED(report_len); + default: + btstack_hid_parser_init(&parser, + hids_client_descriptor_storage_get_descriptor_data(hids_cid, service_index), + hids_client_descriptor_storage_get_descriptor_len(hids_cid, service_index), + HID_REPORT_TYPE_INPUT, report, report_len); + break; + } + + int shift = 0; uint8_t new_keys[NUM_KEYS]; memset(new_keys, 0, sizeof(new_keys)); - int new_keys_count = 0; - - bool shift = (report[0] & 0x22) != 0; - - uint8_t key_index; - for (key_index = 0; key_index < NUM_KEYS; key_index++){ - - uint16_t usage = report[2 + key_index]; - if (usage == 0) continue; + int new_keys_count = 0; + while (btstack_hid_parser_has_more(&parser)){ + uint16_t usage_page; + uint16_t usage; + int32_t value; + btstack_hid_parser_get_field(&parser, &usage_page, &usage, &value); + if (usage_page != 0x07) continue; + switch (usage){ + case 0xe1: + case 0xe6: + if (value){ + shift = 1; + } + continue; + case 0x00: + continue; + default: + break; + } if (usage >= sizeof(keytable_us_none)) continue; // store new keys @@ -177,7 +206,6 @@ static void handle_boot_keyboard_event(const uint8_t * report, uint16_t report_l } if (usage == 0) continue; - // lookup character based on usage + shift modifier uint8_t key; if (shift){ key = keytable_us_shift[usage]; @@ -185,36 +213,15 @@ static void handle_boot_keyboard_event(const uint8_t * report, uint16_t report_l key = keytable_us_none[usage]; } if (key == CHAR_ILLEGAL) continue; - if (key == CHAR_BACKSPACE){ + if (key == CHAR_BACKSPACE){ printf("\b \b"); // go back one char, print space, go back one char again continue; } printf("%c", key); } - - // store current as last report memcpy(last_keys, new_keys, NUM_KEYS); } -/** - * @section HOG Boot Mouse Handler - * @text Boot Mouse Input Report contains a report of format - * [ buttons, dx, dy, dz = scroll wheel] - * Decode packet and print on stdout - * - * @param report - * @param report_len - */ -static void handle_boot_mouse_event(const uint8_t * report, uint16_t report_len){ - UNUSED(report_len); - - uint8_t buttons = report[0]; - int8_t dx = (int8_t) report[1]; - int8_t dy = (int8_t) report[2]; - int8_t dwheel = (int8_t) report[3]; - printf("Mouse: %i, %i - wheel %i - buttons 0x%02x\n", dx, dy, dwheel, buttons); -} - /** * @section Test if advertisement contains HID UUID * @param packet @@ -321,7 +328,6 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint UNUSED(size); uint8_t status; - uint8_t report_id; if (hci_event_packet_get_type(packet) != HCI_EVENT_GATTSERVICE_META){ return; @@ -349,22 +355,12 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint break; } break; + case GATTSERVICE_SUBEVENT_HID_REPORT: - report_id = gattservice_subevent_hid_report_get_report_id(packet); - switch (report_id){ - case HID_BOOT_MODE_MOUSE_ID: - handle_boot_mouse_event( - gattservice_subevent_hid_report_get_report(packet), - gattservice_subevent_hid_report_get_report_len(packet)); - break; - case HID_BOOT_MODE_KEYBOARD_ID: - handle_boot_keyboard_event( - gattservice_subevent_hid_report_get_report(packet), - gattservice_subevent_hid_report_get_report_len(packet)); - break; - default: - break; - } + hid_handle_input_report( + gattservice_subevent_hid_report_get_service_index(packet), + gattservice_subevent_hid_report_get_report(packet), + gattservice_subevent_hid_report_get_report_len(packet)); break; default: @@ -442,7 +438,7 @@ static void packet_handler (uint8_t packet_type, uint16_t channel, uint8_t *pack printf("Search for HID service.\n"); app_state = W4_HID_CLIENT_CONNECTED; - status = hids_client_connect(connection_handle, handle_gatt_client_event, HID_PROTOCOL_MODE_REPORT, &hids_cid); + status = hids_client_connect(connection_handle, handle_gatt_client_event, protocol_mode, &hids_cid); if (status != ERROR_CODE_SUCCESS){ printf("HID client connection failed, status 0x%02x\n", status); } @@ -514,7 +510,7 @@ int btstack_main(int argc, const char * argv[]){ (void)argc; (void)argv; - /* LISTING_START(HogBootHostSetup): HID-over-GATT Boot Host Setup */ + /* LISTING_START(HogBootHostSetup): HID-over-GATT Host Setup */ // register for events from HCI hci_event_callback_registration.callback = &packet_handler; @@ -531,7 +527,8 @@ int btstack_main(int argc, const char * argv[]){ l2cap_init(); sm_init(); gatt_client_init(); - hids_client_init(); + + hids_client_init(hid_descriptor_storage, sizeof(hid_descriptor_storage)); /* LISTING_END */ diff --git a/src/ble/gatt-service/hids_client.c b/src/ble/gatt-service/hids_client.c index 45fa3c412..e5eaae075 100644 --- a/src/ble/gatt-service/hids_client.c +++ b/src/ble/gatt-service/hids_client.c @@ -153,7 +153,10 @@ static void hids_client_descriptor_storage_delete(hids_client_t * client){ const uint8_t * hids_client_descriptor_storage_get_descriptor_data(uint16_t hids_cid, uint8_t service_index){ hids_client_t * client = hids_get_client_for_cid(hids_cid); - if (!client){ + if (client == NULL){ + return NULL; + } + if (service_index >= client->num_instances){ return NULL; } return &hids_client_descriptor_storage[client->services[service_index].hid_descriptor_offset]; @@ -161,7 +164,10 @@ const uint8_t * hids_client_descriptor_storage_get_descriptor_data(uint16_t hids uint16_t hids_client_descriptor_storage_get_descriptor_len(uint16_t hids_cid, uint8_t service_index){ hids_client_t * client = hids_get_client_for_cid(hids_cid); - if (!client){ + if (client == NULL){ + return 0; + } + if (service_index >= client->num_instances){ return 0; } return client->services[service_index].hid_descriptor_len; @@ -198,24 +204,8 @@ static uint8_t find_report_index_for_report_id_and_type(hids_client_t * client, return HIDS_CLIENT_INVALID_REPORT_INDEX; } -static hids_client_report_t * find_report_for_report_id_and_type(hids_client_t * client, uint8_t report_id, hid_report_type_t report_type){ - uint8_t i; - for (i = 0; i < client->num_reports; i++){ - if ( (client->reports[i].report_id == report_id) && (client->reports[i].report_type == report_type)){ - return &client->reports[i]; - } - } - return NULL; -} - -static hids_client_report_t * get_boot_mouse_input_report(hids_client_t * client){ - return find_report_for_report_id_and_type(client, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT); -} -static hids_client_report_t * get_boot_keyboard_input_report(hids_client_t * client){ - return find_report_for_report_id_and_type(client, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT); -} - -static uint8_t hids_client_add_characteristic(hids_client_t * client, gatt_client_characteristic_t * characteristic, uint8_t report_id, hid_report_type_t report_type){ +static uint8_t hids_client_add_characteristic(hids_client_t * client, gatt_client_characteristic_t * characteristic, uint8_t report_id, hid_report_type_t report_type, bool boot_report){ + uint8_t report_index = find_report_index_for_value_handle(client, characteristic->value_handle); if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){ return report_index; @@ -230,9 +220,10 @@ static uint8_t hids_client_add_characteristic(hids_client_t * client, gatt_clien client->reports[report_index].service_index = client->service_index; client->reports[report_index].report_id = report_id; client->reports[report_index].report_type = report_type; - + client->reports[report_index].boot_report = boot_report; + // log_info("add index %d, id %d, type %d, value handle 0x%02x", report_index, report_id, report_type, characteristic->value_handle); - log_info("add index %d, id %d, type %d, value handle 0x%02x", report_index, report_id, report_type, characteristic->value_handle); + log_info("add index %d, id %d, type %d, value handle 0x%02x, properties 0x%02x", report_index, report_id, report_type, characteristic->value_handle, characteristic->properties); client->num_reports++; return report_index; } else { @@ -247,13 +238,24 @@ static void hids_client_get_characteristic_for_report_index(hids_client_t * clie characteristic->properties = client->reports[report_index].properties; } -static uint8_t hids_client_get_characteristic(hids_client_t * client, uint8_t report_id, hid_report_type_t report_type, gatt_client_characteristic_t * characteristic){ - uint8_t report_index = find_report_index_for_report_id_and_type(client, report_id, report_type); - if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){ - return report_index; +static bool hid_clients_has_reports_in_report_mode(hids_client_t * client){ + uint8_t i; + for (i = 0; i < client->num_reports; i++){ + if (!client->reports[i].boot_report){ + return true; + } } - hids_client_get_characteristic_for_report_index(client, report_index, characteristic); - return report_index; + return false; +} + +static bool hid_clients_has_reports_in_boot_mode(hids_client_t * client){ + uint8_t i; + for (i = 0; i < client->num_reports; i++){ + if (client->reports[i].boot_report){ + return true; + } + } + return false; } static uint8_t hids_client_get_next_active_report_map_index(hids_client_t * client){ @@ -294,19 +296,19 @@ static uint8_t hids_client_get_next_active_report_map_uuid_index(hids_client_t * uint8_t i; uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX; - for (i = client->active_index; i < client->num_reports; i++){ + for (i = client->report_index; i < client->num_reports; i++){ hids_client_report_t report = client->reports[i]; if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_MAP_ID){ index = i; break; } } - client->active_index = index; + client->report_index = index; return index; } static bool hids_client_report_query_next_report_map_uuid(hids_client_t * client){ - client->active_index++; + client->report_index++; if (hids_client_get_next_active_report_map_uuid_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){ client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID; return true; @@ -317,7 +319,7 @@ static bool hids_client_report_query_next_report_map_uuid(hids_client_t * client static bool hids_client_report_map_uuid_query_init(hids_client_t * client){ - client->active_index = 0; + client->report_index = 0; if (hids_client_get_next_active_report_map_uuid_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){ client->state = HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID; @@ -329,19 +331,35 @@ static bool hids_client_report_map_uuid_query_init(hids_client_t * client){ static uint8_t hids_client_get_next_report_index(hids_client_t * client){ uint8_t i; uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX; - for (i = client->active_index; i < client->num_reports; i++){ - hids_client_report_t report = client->reports[i]; - if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_ID){ - index = i; + switch (client->protocol_mode){ + case HID_PROTOCOL_MODE_REPORT: + for (i = client->report_index; i < client->num_reports && (index == HIDS_CLIENT_INVALID_REPORT_INDEX); i++){ + hids_client_report_t report = client->reports[i]; + if (report.report_type == HID_REPORT_TYPE_RESERVED && report.report_id == HID_REPORT_MODE_REPORT_ID){ + index = i; + client->service_index = report.service_index; + } + } + break; + case HID_PROTOCOL_MODE_BOOT: + for (i = client->report_index; i < client->num_reports && (index == HIDS_CLIENT_INVALID_REPORT_INDEX); i++){ + hids_client_report_t report = client->reports[i]; + if (report.boot_report){ + index = i; + client->service_index = report.service_index; + } + } + break; + default: break; - } } - client->active_index = index; + + client->report_index = index; return index; } static bool hids_client_report_query_next_report(hids_client_t * client){ - client->active_index++; + client->report_index++; if (hids_client_get_next_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){ client->state = HIDS_CLIENT_STATE_W2_FIND_REPORT; return true; @@ -350,8 +368,7 @@ static bool hids_client_report_query_next_report(hids_client_t * client){ } static bool hids_client_report_query_init(hids_client_t * client){ - client->service_index = 0; - client->active_index = 0; + client->report_index = 0; if (hids_client_get_next_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){ client->state = HIDS_CLIENT_STATE_W2_FIND_REPORT; @@ -360,6 +377,62 @@ static bool hids_client_report_query_init(hids_client_t * client){ return false; } +static uint8_t hids_client_get_next_notification_report_index(hids_client_t * client){ + uint8_t i; + uint8_t index = HIDS_CLIENT_INVALID_REPORT_INDEX; + + switch (client->protocol_mode){ + case HID_PROTOCOL_MODE_REPORT: + for (i = client->report_index; i < client->num_reports && (index == HIDS_CLIENT_INVALID_REPORT_INDEX); i++){ + hids_client_report_t report = client->reports[i]; + if (report.report_type != HID_REPORT_TYPE_INPUT){ + continue; + } + if (!report.boot_report){ + index = i; + } + } + break; + + case HID_PROTOCOL_MODE_BOOT: + for (i = client->report_index; i < client->num_reports && (index == HIDS_CLIENT_INVALID_REPORT_INDEX); i++){ + hids_client_report_t report = client->reports[i]; + if (report.report_type != HID_REPORT_TYPE_INPUT){ + continue; + } + if (report.boot_report){ + index = i; + } + } + break; + + default: + break; + } + + client->report_index = index; + return index; +} + +static bool hids_client_report_next_notification_report_index(hids_client_t * client){ + client->report_index++; + if (hids_client_get_next_notification_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){ + client->state = HIDS_CLIENT_STATE_W2_ENABLE_INPUT_REPORTS; + return true; + } + return false; +} + +static bool hids_client_report_notifications_init(hids_client_t * client){ + client->report_index = 0; + + if (hids_client_get_next_notification_report_index(client) != HIDS_CLIENT_INVALID_REPORT_INDEX){ + client->state = HIDS_CLIENT_STATE_W2_ENABLE_INPUT_REPORTS; + return true; + } + return false; +} + static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t cid){ hids_client_t * client = btstack_memory_hids_client_get(); if (!client){ @@ -375,6 +448,12 @@ static hids_client_t * hids_create_client(hci_con_handle_t con_handle, uint16_t } static void hids_finalize_client(hids_client_t * client){ + // stop listening + uint8_t i; + for (i = 0; i < client->num_reports; i++){ + gatt_client_stop_listening_for_characteristic_value_updates(&client->reports[i].notification_listener); + } + hids_client_descriptor_storage_delete(client); btstack_linked_list_remove(&clients, (btstack_linked_item_t *) client); btstack_memory_hids_client_free(client); @@ -395,12 +474,46 @@ static void hids_emit_connection_established(hids_client_t * client, uint8_t sta (*client->client_handler)(HCI_EVENT_PACKET, 0, event, sizeof(event)); } +static void hids_client_setup_report_event(hids_client_t * client, uint8_t report_index, uint8_t *buffer, uint16_t report_len){ + uint16_t pos = 0; + buffer[pos++] = HCI_EVENT_GATTSERVICE_META; + pos++; // skip len + buffer[pos++] = GATTSERVICE_SUBEVENT_HID_REPORT; + little_endian_store_16(buffer, pos, client->cid); + pos += 2; + buffer[pos++] = client->reports[report_index].service_index; + buffer[pos++] = client->reports[report_index].report_id; + little_endian_store_16(buffer, pos, report_len + 1); + pos += 2; + buffer[pos++] = client->reports[report_index].report_id; + buffer[1] = pos + report_len + 1 - 2; +} + +static void handle_report_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { + UNUSED(packet_type); + UNUSED(channel); + UNUSED(size); + if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return; + + hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet)); + btstack_assert(client != NULL); + + uint8_t report_index = find_report_index_for_value_handle(client, gatt_event_notification_get_value_handle(packet)); + if (report_index == HIDS_CLIENT_INVALID_REPORT_INDEX){ + return; + } + + uint8_t * in_place_event = &packet[-1]; + hids_client_setup_report_event(client, report_index, in_place_event, gatt_event_notification_get_value_length(packet)); + + (*client->client_handler)(HCI_EVENT_GATTSERVICE_META, client->cid, in_place_event, size); +} + static void hids_run_for_client(hids_client_t * client){ uint8_t att_status; gatt_client_service_t service; gatt_client_characteristic_t characteristic; - uint8_t report_index; switch (client->state){ case HIDS_CLIENT_STATE_W2_QUERY_SERVICE: @@ -420,51 +533,24 @@ static void hids_run_for_client(hids_client_t * client){ UNUSED(att_status); break; - case HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD: - client->state = HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED; - - report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT, &characteristic); - if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){ - att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); - } - - if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){ - client->reports[report_index].value_handle = 0; - client->state = HIDS_CLIENT_STATE_CONNECTED; - } - break; - - case HIDS_CLIENT_STATE_W2_ENABLE_MOUSE: - client->state = HIDS_CLIENT_STATE_W4_MOUSE_ENABLED; - - report_index = hids_client_get_characteristic(client, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT, &characteristic); - - if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){ - att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); - } - - if ((report_index == HIDS_CLIENT_INVALID_REPORT_INDEX) || (att_status != ERROR_CODE_SUCCESS)){ - client->reports[report_index].value_handle = 0; - client->state = HIDS_CLIENT_STATE_CONNECTED; - } - - break; - - case HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE: - client->state = HIDS_CLIENT_STATE_W4_SET_PROTOCOL_MODE; + case HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE: att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle, client->protocol_mode_value_handle, 1, (uint8_t *)&client->required_protocol_mode); UNUSED(att_status); client->protocol_mode = client->required_protocol_mode; - client->state = HIDS_CLIENT_STATE_CONNECTED; - hids_emit_connection_established(client, ERROR_CODE_SUCCESS); + if (hids_client_report_query_init(client)){ + break; + } + + hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); + hids_finalize_client(client); break; case HIDS_CLIENT_W2_SEND_REPORT:{ client->state = HIDS_CLIENT_STATE_CONNECTED; att_status = gatt_client_write_value_of_characteristic_without_response(client->con_handle, - client->reports[client->active_index].value_handle, + client->reports[client->report_index].value_handle, client->report_len, (uint8_t *)client->report); UNUSED(att_status); break; @@ -472,7 +558,7 @@ static void hids_run_for_client(hids_client_t * client){ case HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_HID_DESCRIPTOR: client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR; - att_status = gatt_client_read_long_value_of_characteristic_using_value_handle(&handle_gatt_client_event, client->con_handle, client->services[client->service_index].report_map_value_handle); + att_status = gatt_client_read_value_of_characteristic_using_value_handle(&handle_gatt_client_event, client->con_handle, client->services[client->service_index].report_map_value_handle); UNUSED(att_status); break; @@ -489,7 +575,7 @@ static void hids_run_for_client(hids_client_t * client){ case HIDS_CLIENT_STATE_W2_REPORT_MAP_READ_EXTERNAL_REPORT_REFERENCE_UUID: client->state = HIDS_CLIENT_STATE_W4_REPORT_MAP_EXTERNAL_REPORT_REFERENCE_UUID; - att_status = gatt_client_read_characteristic_descriptor_using_descriptor_handle(&handle_gatt_client_event, client->con_handle, client->reports[client->active_index].value_handle); + att_status = gatt_client_read_characteristic_descriptor_using_descriptor_handle(&handle_gatt_client_event, client->con_handle, client->reports[client->report_index].value_handle); UNUSED(att_status); break; @@ -505,7 +591,7 @@ static void hids_run_for_client(hids_client_t * client){ case HIDS_CLIENT_STATE_W2_FIND_REPORT: client->state = HIDS_CLIENT_STATE_W4_REPORT_FOUND; client->descriptor_handle = 0; - hids_client_get_characteristic_for_report_index(client, client->active_index, &characteristic); + hids_client_get_characteristic_for_report_index(client, client->report_index, &characteristic); att_status = gatt_client_discover_characteristic_descriptors(&handle_gatt_client_event, client->con_handle, &characteristic); UNUSED(att_status); break; @@ -517,55 +603,39 @@ static void hids_run_for_client(hids_client_t * client){ client->descriptor_handle = 0; UNUSED(att_status); break; + + case HIDS_CLIENT_STATE_W2_ENABLE_INPUT_REPORTS: + client->state = HIDS_CLIENT_STATE_W4_INPUT_REPORTS_ENABLED; + characteristic.value_handle = client->reports[client->report_index].value_handle; + characteristic.end_handle = client->reports[client->report_index].end_handle; + characteristic.properties = client->reports[client->report_index].properties; + + att_status = gatt_client_write_client_characteristic_configuration(&handle_gatt_client_event, client->con_handle, &characteristic, GATT_CLIENT_CHARACTERISTICS_CONFIGURATION_NOTIFICATION); + + if (att_status != ERROR_CODE_SUCCESS){ + if (hids_client_report_next_notification_report_index(client)){ + hids_run_for_client(client); + break; + } + client->state = HIDS_CLIENT_STATE_CONNECTED; + hids_emit_connection_established(client, ERROR_CODE_SUCCESS); + } else { + gatt_client_listen_for_characteristic_value_updates( + &client->reports[client->report_index].notification_listener, + &handle_report_hid_event, client->con_handle, &characteristic); + + client->state = HIDS_CLIENT_STATE_CONNECTED; + hids_emit_connection_established(client, ERROR_CODE_SUCCESS); + } + UNUSED(att_status); + break; + default: break; } } -static void hids_client_setup_report_event(hids_client_t * client, uint8_t report_id, uint8_t *buffer, uint16_t report_len){ - uint16_t pos = 0; - buffer[pos++] = HCI_EVENT_GATTSERVICE_META; - pos++; // skip len - buffer[pos++] = GATTSERVICE_SUBEVENT_HID_REPORT; - little_endian_store_16(buffer, pos, client->cid); - pos += 2; - buffer[pos++] = report_id; - little_endian_store_16(buffer, pos, report_len); - pos += 2; - buffer[1] = pos + report_len - 2; -} - -static void handle_boot_keyboard_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { - UNUSED(packet_type); - UNUSED(channel); - UNUSED(size); - - if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return; - - hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet)); - btstack_assert(client != NULL); - - uint8_t * in_place_event = packet; - hids_client_setup_report_event(client, HID_BOOT_MODE_KEYBOARD_ID, in_place_event, gatt_event_notification_get_value_length(packet)); - (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size); -} - -static void handle_boot_mouse_hid_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) { - UNUSED(packet_type); - UNUSED(channel); - UNUSED(size); - - if (hci_event_packet_get_type(packet) != GATT_EVENT_NOTIFICATION) return; - - hids_client_t * client = hids_get_client_for_con_handle(gatt_event_notification_get_handle(packet)); - btstack_assert(client != NULL); - - uint8_t * in_place_event = packet; - hids_client_setup_report_event(client, HID_BOOT_MODE_MOUSE_ID, in_place_event, gatt_event_notification_get_value_length(packet)); - (*client->client_handler)(HCI_EVENT_PACKET, client->cid, in_place_event, size); -} - static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size){ UNUSED(packet_type); @@ -578,12 +648,11 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint gatt_client_characteristic_t characteristic; gatt_client_characteristic_descriptor_t characteristic_descriptor; - hids_client_report_t * boot_keyboard_report; - hids_client_report_t * boot_mouse_report; + // hids_client_report_t * boot_keyboard_report; + // hids_client_report_t * boot_mouse_report; const uint8_t * characteristic_descriptor_value; uint8_t i; uint8_t report_index; - uint8_t next_service_index; const uint8_t * descriptor; uint16_t descriptor_len; @@ -599,15 +668,17 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint break; } - next_service_index = client->num_instances; - if (next_service_index < MAX_NUM_HID_SERVICES){ + if (client->num_instances < MAX_NUM_HID_SERVICES){ + uint8_t index = client->num_instances; gatt_event_service_query_result_get_service(packet, &service); - client->services[next_service_index].start_handle = service.start_group_handle; - client->services[next_service_index].end_handle = service.end_group_handle; - - hids_client_descriptor_storage_init(client, next_service_index); + client->services[index].start_handle = service.start_group_handle; + client->services[index].end_handle = service.end_group_handle; client->num_instances++; - } + + hids_client_descriptor_storage_init(client, index); + } else { + log_info("%d hid services found, only first %d can be stored, increase MAX_NUM_HID_SERVICES", client->num_instances + 1, MAX_NUM_HID_SERVICES); + } break; case GATT_EVENT_CHARACTERISTIC_QUERY_RESULT: @@ -636,19 +707,19 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint break; case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_INPUT_REPORT: - hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT); + hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_INPUT, true); break; case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_MOUSE_INPUT_REPORT: - hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT); + hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_MOUSE_ID, HID_REPORT_TYPE_INPUT, true); break; case ORG_BLUETOOTH_CHARACTERISTIC_BOOT_KEYBOARD_OUTPUT_REPORT: - hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_OUTPUT); + hids_client_add_characteristic(client, &characteristic, HID_BOOT_MODE_KEYBOARD_ID, HID_REPORT_TYPE_OUTPUT, false); break; case ORG_BLUETOOTH_CHARACTERISTIC_REPORT: - hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_ID, HID_REPORT_TYPE_RESERVED); + hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_ID, HID_REPORT_TYPE_RESERVED, false); break; case ORG_BLUETOOTH_CHARACTERISTIC_REPORT_MAP: @@ -658,12 +729,10 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint case ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION: // printf("ORG_BLUETOOTH_CHARACTERISTIC_HID_INFORMATION\n"); - hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_HID_INFORMATION_ID, HID_REPORT_TYPE_INPUT); break; case ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT: // printf("ORG_BLUETOOTH_CHARACTERISTIC_HID_CONTROL_POINT\n"); - hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_HID_CONTROL_POINT_ID, HID_REPORT_TYPE_INPUT); break; default: break; @@ -677,10 +746,9 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint client = hids_get_client_for_con_handle(gatt_event_characteristic_value_query_result_get_handle(packet)); btstack_assert(client != NULL); - // printf("HID descriptor found\n"); descriptor_len = gatt_event_characteristic_value_query_result_get_value_length(packet); descriptor = gatt_event_characteristic_value_query_result_get_value(packet); - + for (i = 0; i < descriptor_len; i++){ bool stored = hids_client_descriptor_storage_store(client, client->service_index, descriptor[i]); if (!stored){ @@ -699,7 +767,7 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint case HIDS_CLIENT_STATE_W4_REPORT_MAP_CHARACTERISTIC_DESCRIPTORS_RESULT: // setup for descriptor value query if (characteristic_descriptor.uuid16 == ORG_BLUETOOTH_DESCRIPTOR_EXTERNAL_REPORT_REFERENCE){ - report_index = hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_MAP_ID, HID_REPORT_TYPE_RESERVED); + report_index = hids_client_add_characteristic(client, &characteristic, HID_REPORT_MODE_REPORT_MAP_ID, HID_REPORT_TYPE_RESERVED, false); if (report_index != HIDS_CLIENT_INVALID_REPORT_INDEX){ client->reports[report_index].value_handle = characteristic_descriptor.handle; @@ -738,13 +806,13 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint break; case HIDS_CLIENT_STATE_W4_REPORT_ID_AND_TYPE: - client->reports[client->active_index].report_id = characteristic_descriptor_value[0]; - client->reports[client->active_index].report_type = (hid_report_type_t)characteristic_descriptor_value[1]; + client->reports[client->report_index].report_id = characteristic_descriptor_value[0]; + client->reports[client->report_index].report_type = (hid_report_type_t)characteristic_descriptor_value[1]; log_info("update index %d, id %d, type %d, value handle 0x%02x", - client->active_index, - client->reports[client->active_index].report_id, - client->reports[client->active_index].report_type, - client->reports[client->active_index].value_handle); + client->report_index, + client->reports[client->report_index].report_id, + client->reports[client->report_index].report_type, + client->reports[client->report_index].value_handle); break; @@ -773,11 +841,6 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint break; } - if (client->num_instances > MAX_NUM_HID_SERVICES) { - log_info("%d hid services found, only first %d can be stored, increase MAX_NUM_HID_SERVICES", client->num_instances, MAX_NUM_HID_SERVICES); - client->num_instances = MAX_NUM_HID_SERVICES; - } - client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC; client->service_index = 0; break; @@ -788,43 +851,67 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint hids_finalize_client(client); break; } + + if ((client->service_index + 1) < client->num_instances){ + // discover characteristics of next service + client->service_index++; + client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC; + break; + } switch (client->required_protocol_mode){ - case HID_PROTOCOL_MODE_BOOT: - if (get_boot_keyboard_input_report(client) != NULL){ - client->state = HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD; - break; - } - if (get_boot_mouse_input_report(client) != NULL){ - client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE; + case HID_PROTOCOL_MODE_REPORT: + client->protocol_mode = HID_PROTOCOL_MODE_REPORT; + if (hid_clients_has_reports_in_report_mode(client)){ + client->protocol_mode = HID_PROTOCOL_MODE_REPORT; break; } - hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); + hids_emit_connection_established(client, att_status); hids_finalize_client(client); - break; - - case HID_PROTOCOL_MODE_REPORT: - if ((client->service_index + 1) < client->num_instances){ - // discover characteristics of next service - client->service_index++; - client->state = HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC; - } else { + return; - // 1. we need to get HID Descriptor and - // 2. get external Report characteristics if referenced from Report Map - if (hids_client_report_map_query_init(client)){ + case HID_PROTOCOL_MODE_REPORT_WITH_FALLBACK_TO_BOOT: + if (hid_clients_has_reports_in_report_mode(client)){ + client->protocol_mode = HID_PROTOCOL_MODE_REPORT; + break; + } + if (hid_clients_has_reports_in_boot_mode(client)){ + if (client->protocol_mode_value_handle != 0){ + client->state = HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE; break; } - hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); + hids_emit_connection_established(client, att_status); hids_finalize_client(client); + return; } break; default: + if (hid_clients_has_reports_in_boot_mode(client)){ + if (client->protocol_mode_value_handle != 0){ + client->state = HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE; + break; + } + hids_emit_connection_established(client, att_status); + hids_finalize_client(client); + return; + } break; } + + if (client->state == HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE){ + break; + } + + // 1. we need to get HID Descriptor and + // 2. get external Report characteristics if referenced from Report Map + if (hids_client_report_map_query_init(client)){ + break; + } + hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); + hids_finalize_client(client); break; - + // HID descriptor found case HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR: if (att_status != ATT_ERROR_SUCCESS){ @@ -868,7 +955,6 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint break; case HIDS_CLIENT_STATE_W4_EXTERNAL_REPORT_CHARACTERISTIC_RESULT: - // discover characteristic descriptor for all Report characteristics, // then read value of characteristic descriptor to get Report ID if (hids_client_report_query_init(client)){ @@ -901,66 +987,19 @@ static void handle_gatt_client_event(uint8_t packet_type, uint16_t channel, uint break; } - // TODO continue with report mode + if (hids_client_report_notifications_init(client)){ + break; + } hids_emit_connection_established(client, ERROR_CODE_SUCCESS); break; - case HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED: - boot_keyboard_report = get_boot_keyboard_input_report(client); - if (boot_keyboard_report != NULL){ - // setup listener - characteristic.value_handle = boot_keyboard_report->value_handle; - gatt_client_listen_for_characteristic_value_updates(&boot_keyboard_report->notifications, &handle_boot_keyboard_hid_event, client->con_handle, &characteristic); - } - - // check if there is mouse input report - boot_mouse_report = get_boot_mouse_input_report(client); - if (boot_mouse_report != NULL){ - client->state = HIDS_CLIENT_STATE_W2_ENABLE_MOUSE; - break; - } - - // if none of the reports is found bail out - if (!boot_mouse_report && !boot_keyboard_report){ - hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); - hids_finalize_client(client); + case HIDS_CLIENT_STATE_W4_INPUT_REPORTS_ENABLED: + if (hids_client_report_next_notification_report_index(client)){ break; } - - // set protocol - if (client->protocol_mode_value_handle != 0){ - client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE; - } else { - client->protocol_mode = HID_PROTOCOL_MODE_BOOT; - client->state = HIDS_CLIENT_STATE_CONNECTED; - hids_emit_connection_established(client, ERROR_CODE_SUCCESS); - } - hids_run_for_client(client); + hids_emit_connection_established(client, ERROR_CODE_SUCCESS); break; - - case HIDS_CLIENT_STATE_W4_MOUSE_ENABLED: - boot_mouse_report = get_boot_mouse_input_report(client); - if (boot_mouse_report != NULL){ - // setup listener - characteristic.value_handle = boot_mouse_report->value_handle; - gatt_client_listen_for_characteristic_value_updates(&boot_mouse_report->notifications, &handle_boot_mouse_hid_event, client->con_handle, &characteristic); - } - boot_keyboard_report = get_boot_keyboard_input_report(client); - if (!boot_mouse_report && !boot_keyboard_report){ - hids_emit_connection_established(client, ERROR_CODE_UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE); - hids_finalize_client(client); - break; - } - - if (client->protocol_mode_value_handle != 0){ - client->state = HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE; - } else { - client->protocol_mode = HID_PROTOCOL_MODE_BOOT; - client->state = HIDS_CLIENT_STATE_CONNECTED; - hids_emit_connection_established(client, ERROR_CODE_SUCCESS); - } - break; default: break; } @@ -1038,7 +1077,7 @@ uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint } client->state = HIDS_CLIENT_W2_SEND_REPORT; - client->active_index = report_index; + client->report_index = report_index; client->report = report; client->report_len = report_len; @@ -1046,6 +1085,9 @@ uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint return ERROR_CODE_SUCCESS; } -void hids_client_init(void){} +void hids_client_init(uint8_t * hid_descriptor_storage, uint16_t hid_descriptor_storage_len){ + hids_client_descriptor_storage = hid_descriptor_storage; + hids_client_descriptor_storage_len = hid_descriptor_storage_len; +} void hids_client_deinit(void){} diff --git a/src/ble/gatt-service/hids_client.h b/src/ble/gatt-service/hids_client.h index b4ec92b51..e7ec2a578 100644 --- a/src/ble/gatt-service/hids_client.h +++ b/src/ble/gatt-service/hids_client.h @@ -67,6 +67,8 @@ typedef enum { HIDS_CLIENT_STATE_W2_QUERY_CHARACTERISTIC, HIDS_CLIENT_STATE_W4_CHARACTERISTIC_RESULT, + HIDS_CLIENT_STATE_W2_SET_BOOT_PROTOCOL_MODE, + // for each REPORT_MAP characteristic, read HID Descriptor (Report Map Characteristic Value) HIDS_CLIENT_STATE_W2_READ_REPORT_MAP_HID_DESCRIPTOR, HIDS_CLIENT_STATE_W4_REPORT_MAP_HID_DESCRIPTOR, @@ -91,16 +93,9 @@ typedef enum { HIDS_CLIENT_STATE_W2_READ_REPORT_ID_AND_TYPE, HIDS_CLIENT_STATE_W4_REPORT_ID_AND_TYPE, - // Boot Mode - HIDS_CLIENT_STATE_W2_ENABLE_KEYBOARD, - HIDS_CLIENT_STATE_W4_KEYBOARD_ENABLED, - HIDS_CLIENT_STATE_W2_ENABLE_MOUSE, - HIDS_CLIENT_STATE_W4_MOUSE_ENABLED, - + HIDS_CLIENT_STATE_W2_ENABLE_INPUT_REPORTS, + HIDS_CLIENT_STATE_W4_INPUT_REPORTS_ENABLED, - HIDS_CLIENT_STATE_W2_SET_PROTOCOL_MODE, - HIDS_CLIENT_STATE_W4_SET_PROTOCOL_MODE, - HIDS_CLIENT_STATE_CONNECTED, HIDS_CLIENT_W2_SEND_REPORT } hid_service_client_state_t; @@ -121,7 +116,8 @@ typedef struct { uint8_t service_index; uint8_t report_id; hid_report_type_t report_type; - gatt_client_notification_t notifications; + uint8_t boot_report; + gatt_client_notification_t notification_listener; } hids_client_report_t; typedef struct { @@ -163,7 +159,7 @@ typedef struct { uint8_t num_reports; // index used for report and report map search - uint8_t active_index; + uint8_t report_index; uint16_t descriptor_handle; uint16_t report_len; const uint8_t * report; @@ -174,8 +170,10 @@ typedef struct { /** * @brief Initialize Battery Service. + * @param hid_descriptor_storage + * @param hid_descriptor_storage_len */ -void hids_client_init(void); +void hids_client_init(uint8_t * hid_descriptor_storage, uint16_t hid_descriptor_storage_len); /* @brief Connect to HID Services of remote device. * @@ -204,8 +202,18 @@ uint8_t hids_client_send_report(uint16_t hids_cid, uint8_t report_id, const uint */ uint8_t hids_client_disconnect(uint16_t hids_cid); +/* + * @brief Get descriptor data + * @param hid_cid + * @result data + */ const uint8_t * hids_client_descriptor_storage_get_descriptor_data(uint16_t hids_cid, uint8_t service_index); +/* + * @brief Get descriptor length + * @param hid_cid + * @result length + */ uint16_t hids_client_descriptor_storage_get_descriptor_len(uint16_t hids_cid, uint8_t service_index); /** diff --git a/src/hid.c b/src/hid.c new file mode 100644 index 000000000..77807b47a --- /dev/null +++ b/src/hid.c @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2021 BlueKitchen GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * 4. Any redistribution, use, or modification is done solely for + * personal benefit and not for any commercial purpose or for + * monetary gain. + * + * THIS SOFTWARE IS PROVIDED BY BLUEKITCHEN GMBH AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS + * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * Please inquire about commercial licensing options at + * contact@bluekitchen-gmbh.com + * + */ + +#include "hid.h" + + +// from USB HID Specification 1.1, Appendix B.1 +const uint8_t hid_descriptor_boot_mode[] = { + // Keyboard + + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x06, // Usage (Keyboard) + 0xa1, 0x01, // Collection (Application) + + 0x85, 0x01, // Report ID 1 + + // Modifier byte + + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x05, 0x07, // Usage Page (Key codes) + 0x19, 0xe0, // Usage Minimum (Keyboard LeftControl) + 0x29, 0xe7, // Usage Maxium (Keyboard Right GUI) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x81, 0x02, // Input (Data, Variable, Absolute) + + // Reserved byte + + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x81, 0x03, // Input (Constant, Variable, Absolute) + + // LED report + padding + + 0x95, 0x05, // Report Count (5) + 0x75, 0x01, // Report Size (1) + 0x05, 0x08, // Usage Page (LEDs) + 0x19, 0x01, // Usage Minimum (Num Lock) + 0x29, 0x05, // Usage Maxium (Kana) + 0x91, 0x02, // Output (Data, Variable, Absolute) + + 0x95, 0x01, // Report Count (1) + 0x75, 0x03, // Report Size (3) + 0x91, 0x03, // Output (Constant, Variable, Absolute) + + // Keycodes + + 0x95, 0x06, // Report Count (6) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0xff, // Logical Maximum (1) + 0x05, 0x07, // Usage Page (Key codes) + 0x19, 0x00, // Usage Minimum (Reserved (no event indicated)) + 0x29, 0xff, // Usage Maxium (Reserved) + 0x81, 0x00, // Input (Data, Array) + + 0xc0, // End collection + + // Mouse + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x02, // USAGE (Mouse) + 0xa1, 0x01, // COLLECTION (Application) + + 0x85, 0x01, // Report ID 1 + + 0x09, 0x01, // USAGE (Pointer) + + 0xa1, 0x00, // COLLECTION (Physical) + +#if 1 + 0x05, 0x09, // USAGE_PAGE (Button) + 0x19, 0x01, // USAGE_MINIMUM (Button 1) + 0x29, 0x03, // USAGE_MAXIMUM (Button 3) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x95, 0x03, // REPORT_COUNT (3) + 0x75, 0x01, // REPORT_SIZE (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + 0x95, 0x01, // REPORT_COUNT (1) + 0x75, 0x05, // REPORT_SIZE (5) + 0x81, 0x03, // INPUT (Cnst,Var,Abs) +#endif + +#if 1 + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x30, // USAGE (X) + 0x09, 0x31, // USAGE (Y) + 0x15, 0x81, // LOGICAL_MINIMUM (-127) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x02, // REPORT_COUNT (2) + 0x81, 0x06, // INPUT (Data,Var,Rel) +#endif + + 0xc0, // END_COLLECTION + 0xc0 // END_COLLECTION +}; + +const uint8_t * hid_get_boot_descriptor_data(void){ + return &hid_descriptor_boot_mode[0]; +} + +uint16_t hid_get_boot_descriptor_len(void){ + return sizeof(hid_descriptor_boot_mode); +} + diff --git a/src/hid.h b/src/hid.h index 3c03ab364..2da7d476b 100644 --- a/src/hid.h +++ b/src/hid.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 BlueKitchen GmbH + * Copyright (C) 2021 BlueKitchen GmbH * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -42,7 +42,8 @@ extern "C" { #endif -/* API_START */ + +#include #define HID_BOOT_MODE_KEYBOARD_ID 1 #define HID_BOOT_MODE_MOUSE_ID 2 @@ -105,6 +106,20 @@ typedef enum { HID_REPORT_ID_INVALID } hid_report_id_status_t; +/* API_START */ + +/* + * @brief Get boot descriptor data + * @result data + */ +const uint8_t * hid_get_boot_descriptor_data(void); + +/* + * @brief Get boot descriptor length + * @result length + */ +uint16_t hid_get_boot_descriptor_len(void); + /* API_END */ #if defined __cplusplus