/* * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_err.h" #include "esp_log.h" #include "usb/usb_host.h" #include "errno.h" #include "driver/gpio.h" #include "hid_host.h" #include "hid_usage_keyboard.h" #include "hid_usage_mouse.h" #define APP_QUIT_PIN GPIO_NUM_0 #define APP_QUIT_PIN_POLL_MS 500 #define READY_TO_UNINSTALL (HOST_NO_CLIENT | HOST_ALL_FREE) typedef enum { HOST_NO_CLIENT = 0x1, HOST_ALL_FREE = 0x2, DEVICE_CONNECTED = 0x4, DEVICE_DISCONNECTED = 0x8, DEVICE_ADDRESS_MASK = 0xFF0, } app_event_t; #define USB_EVENTS_TO_WAIT (DEVICE_CONNECTED | DEVICE_ADDRESS_MASK | DEVICE_DISCONNECTED) static const char *TAG = "example"; static EventGroupHandle_t usb_flags; static bool hid_device_connected = false; hid_host_interface_handle_t keyboard_handle = NULL; hid_host_interface_handle_t mouse_handle = NULL; const char *modifier_char_name[8] = { "LEFT_CONTROL", "LEFT_SHIFT", "LEFT_ALT", "LEFT_GUI", "RIGHT_CONTROL", "RIGHT_SHIFT", "RIGHT_ALT", "RIGHT_GUI" }; /** * @brief Makes new line depending on report output protocol type * * @param[in] proto Current protocol to output */ static void hid_trigger_new_line_output(hid_protocol_t proto) { static hid_protocol_t prev_proto_output = HID_PROTOCOL_NONE; if (prev_proto_output != proto) { prev_proto_output = proto; printf("\r\n"); fflush(stdout); } } /** * @brief HID Keyboard modifier verification function. Verify and print debug information about modifier has been pressed * * @param[in] modifier */ static inline void hid_keyboard_modifier_pressed(uint8_t modifier) { // verify bit mask for (uint8_t i = 0; i < (sizeof(uint8_t) << 3); i++) { if ((modifier >> i) & 0x01) { ESP_LOGD(TAG, "Modifier Pressed: %s", modifier_char_name[i]); } } } /** * @brief HID Keyboard modifier verification for capitalization application (right or left shift) * * @param[in] modifier * @return true Modifier was pressed (left or right shift) * @return false Modifier was not pressed (left or right shift) * */ static inline bool hid_keyboard_is_modifier_capital(uint8_t modifier) { if ((modifier && HID_LEFT_SHIFT) || (modifier && HID_RIGHT_SHIFT)) { return true; } return false; } /** * @brief HID Keyboard get char symbol from key code * * @param[in] modifier Keyboard modifier data * @param[in] key_code Keyboard key code */ static inline char hid_keyboard_get_char(uint8_t modifier, uint8_t key_code) { uint8_t key_char = (hid_keyboard_is_modifier_capital(modifier)) ? 'A' : 'a'; // Handle only char key pressed if ((key_code >= HID_KEY_A) && (key_code <= HID_KEY_Z)) { key_char += (key_code - HID_KEY_A); } else if ((key_code >= HID_KEY_1) && (key_code <= HID_KEY_9)) { key_char = '1' + (key_code - HID_KEY_1); } else if (key_code == HID_KEY_0) { key_char = '0'; } else { // All other key pressed key_char = 0x00; } return key_char; } /** * @brief USB HID Host Keyboard Interface report callback handler * * @param[in] data Pointer to input report data buffer * @param[in] length Length of input report data buffer */ static void hid_host_keyboard_report_callback(const uint8_t *const data, const int length) { bool keys_state_changed = false; hid_keyboard_input_report_boot_t *kb_report = (hid_keyboard_input_report_boot_t *)data; if (kb_report->modifier.val != 0) { hid_keyboard_modifier_pressed(kb_report->modifier.val); } static uint8_t keys[HID_KEYBOARD_KEY_MAX] = { 0 }; for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) { if (kb_report->key[i] != keys[i]) { keys_state_changed = true; if (kb_report->key[i] != 0) { keys[i] = hid_keyboard_get_char(kb_report->modifier.val, kb_report->key[i]); } else { keys[i] = 0x00; } } } if (keys_state_changed) { hid_trigger_new_line_output(HID_PROTOCOL_KEYBOARD); printf("|"); for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) { printf("%c|", keys[i] ? keys[i] : ' '); } printf("\r"); fflush(stdout); } } /** * @brief USB HID Host Mouse Interface report callback handler * * @param[in] data Pointer to input report data buffer * @param[in] length Length of input report data buffer */ static void hid_host_mouse_report_callback(const uint8_t *const data, const int length) { hid_mouse_input_report_boot_t *mouse_report = (hid_mouse_input_report_boot_t *)data; // First 3 bytes are mandated by HID specification if (length < sizeof(hid_mouse_input_report_boot_t)) { ESP_LOGE(TAG, "Mouse Boot report length (%d) error", length); return; } static int x_pos = 0; static int y_pos = 0; // Calculate absolute position from displacement x_pos += mouse_report->x_displacement; y_pos += mouse_report->y_displacement; hid_trigger_new_line_output(HID_PROTOCOL_MOUSE); printf("X: %06d\tY: %06d\t|%c|%c|\r", x_pos, y_pos, (mouse_report->buttons.button1 ? 'o' : ' '), (mouse_report->buttons.button2 ? 'o' : ' ')); fflush(stdout); } /** * @brief USB HID Host event callback. Handle such event as device connection and removing * * @param[in] event HID device event * @param[in] arg Pointer to arguments, does not used */ static void hid_host_event_callback(const hid_host_event_t *event, void *arg) { if (event->event == HID_DEVICE_CONNECTED) { // Obtained USB device address is placed after application events xEventGroupSetBits(usb_flags, DEVICE_CONNECTED | (event->device.address << 4)); } else if (event->event == HID_DEVICE_DISCONNECTED) { xEventGroupSetBits(usb_flags, DEVICE_DISCONNECTED); } } /** * @brief USB HID Host interface callback * * @param[in] event HID interface event * @param[in] arg Pointer to arguments, does not used */ static void hid_host_interface_event_callback(const hid_host_interface_event_t *event, void *arg) { switch (event->event) { case HID_DEVICE_INTERFACE_INIT: ESP_LOGI(TAG, "Interface number %d, protocol %s", event->interface.num, (event->interface.proto == HID_PROTOCOL_KEYBOARD) ? "Keyboard" : "Mouse"); if (event->interface.proto == HID_PROTOCOL_KEYBOARD) { const hid_host_interface_config_t hid_keyboard_config = { .proto = HID_PROTOCOL_KEYBOARD, .callback = hid_host_keyboard_report_callback, }; hid_host_claim_interface(&hid_keyboard_config, &keyboard_handle); } if (event->interface.proto == HID_PROTOCOL_MOUSE) { const hid_host_interface_config_t hid_mouse_config = { .proto = HID_PROTOCOL_MOUSE, .callback = hid_host_mouse_report_callback, }; hid_host_claim_interface(&hid_mouse_config, &mouse_handle); } break; case HID_DEVICE_INTERFACE_TRANSFER_ERROR: ESP_LOGI(TAG, "Interface number %d, transfer error", event->interface.num); break; case HID_DEVICE_INTERFACE_CLAIM: case HID_DEVICE_INTERFACE_RELEASE: // ... do nothing here for now break; default: ESP_LOGI(TAG, "%s Unhandled event %X, Interface number %d", __FUNCTION__, event->event, event->interface.num); break; } } /** * @brief Handle common USB host library events * * @param[in] args Pointer to arguments, does not used */ static void handle_usb_events(void *args) { while (1) { uint32_t event_flags; usb_host_lib_handle_events(portMAX_DELAY, &event_flags); // Release devices once all clients has deregistered if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { usb_host_device_free_all(); xEventGroupSetBits(usb_flags, HOST_NO_CLIENT); } // Give ready_to_uninstall_usb semaphore to indicate that USB Host library // can be deinitialized, and terminate this task. if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { xEventGroupSetBits(usb_flags, HOST_ALL_FREE); } } vTaskDelete(NULL); } static bool wait_for_event(EventBits_t event, TickType_t timeout) { return xEventGroupWaitBits(usb_flags, event, pdTRUE, pdTRUE, timeout) & event; } void app_main(void) { TaskHandle_t usb_events_task_handle; hid_host_device_handle_t hid_device; BaseType_t task_created; const gpio_config_t input_pin = { .pin_bit_mask = BIT64(APP_QUIT_PIN), .mode = GPIO_MODE_INPUT, .pull_up_en = GPIO_PULLUP_ENABLE, }; ESP_ERROR_CHECK( gpio_config(&input_pin) ); ESP_LOGI(TAG, "HID HOST example"); usb_flags = xEventGroupCreate(); assert(usb_flags); const usb_host_config_t host_config = { .skip_phy_setup = false, .intr_flags = ESP_INTR_FLAG_LEVEL1 }; ESP_ERROR_CHECK( usb_host_install(&host_config) ); task_created = xTaskCreate(handle_usb_events, "usb_events", 4096, NULL, 2, &usb_events_task_handle); assert(task_created); // hid host driver config const hid_host_driver_config_t hid_host_config = { .create_background_task = true, .task_priority = 5, .stack_size = 4096, .core_id = 0, .callback = hid_host_event_callback, .callback_arg = NULL }; ESP_ERROR_CHECK( hid_host_install(&hid_host_config) ); do { EventBits_t event = xEventGroupWaitBits(usb_flags, USB_EVENTS_TO_WAIT, pdTRUE, pdFALSE, pdMS_TO_TICKS(APP_QUIT_PIN_POLL_MS)); if (event & DEVICE_CONNECTED) { xEventGroupClearBits(usb_flags, DEVICE_CONNECTED); hid_device_connected = true; } if (event & DEVICE_ADDRESS_MASK) { xEventGroupClearBits(usb_flags, DEVICE_ADDRESS_MASK); const hid_host_device_config_t hid_host_device_config = { .dev_addr = (event & DEVICE_ADDRESS_MASK) >> 4, .iface_event_cb = hid_host_interface_event_callback, .iface_event_arg = NULL, }; ESP_ERROR_CHECK( hid_host_install_device(&hid_host_device_config, &hid_device) ); } if (event & DEVICE_DISCONNECTED) { xEventGroupClearBits(usb_flags, DEVICE_DISCONNECTED); hid_host_release_interface(keyboard_handle); hid_host_release_interface(mouse_handle); ESP_ERROR_CHECK( hid_host_uninstall_device(hid_device) ); hid_device_connected = false; } } while (gpio_get_level(APP_QUIT_PIN) != 0); if (hid_device_connected) { ESP_LOGI(TAG, "Uninitializing HID Device"); hid_host_release_interface(keyboard_handle); hid_host_release_interface(mouse_handle); ESP_ERROR_CHECK( hid_host_uninstall_device(hid_device) ); hid_device_connected = false; } ESP_LOGI(TAG, "Uninitializing USB"); ESP_ERROR_CHECK( hid_host_uninstall() ); wait_for_event(READY_TO_UNINSTALL, portMAX_DELAY); ESP_ERROR_CHECK( usb_host_uninstall() ); vTaskDelete(usb_events_task_handle); vEventGroupDelete(usb_flags); ESP_LOGI(TAG, "Done"); }