initial commmit
This commit is contained in:
791
main/hid_host_example.c
Normal file
791
main/hid_host_example.c
Normal file
@@ -0,0 +1,791 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "usb/usb_host.h"
|
||||
#include "errno.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#include "usb/hid_host.h"
|
||||
#include "usb/hid_usage_keyboard.h"
|
||||
#include "usb/hid_usage_mouse.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
// Add near the top with other global variables
|
||||
static uint32_t start_time = 0;
|
||||
static uint32_t report_count = 0;
|
||||
|
||||
|
||||
/* GPIO Pin number for quit from example logic */
|
||||
#define APP_QUIT_PIN GPIO_NUM_0
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
QueueHandle_t app_event_queue = NULL;
|
||||
|
||||
/**
|
||||
* @brief APP event group
|
||||
*
|
||||
* Application logic can be different. There is a one among other ways to distingiush the
|
||||
* event by application event group.
|
||||
* In this example we have two event groups:
|
||||
* APP_EVENT - General event, which is APP_QUIT_PIN press event (Generally, it is IO0).
|
||||
* APP_EVENT_HID_HOST - HID Host Driver event, such as device connection/disconnection or input report.
|
||||
*/
|
||||
typedef enum {
|
||||
APP_EVENT = 0,
|
||||
APP_EVENT_HID_HOST
|
||||
} app_event_group_t;
|
||||
|
||||
/**
|
||||
* @brief APP event queue
|
||||
*
|
||||
* This event is used for delivering the HID Host event from callback to a task.
|
||||
*/
|
||||
typedef struct {
|
||||
app_event_group_t event_group;
|
||||
/* HID Host - Device related info */
|
||||
struct {
|
||||
hid_host_device_handle_t handle;
|
||||
hid_host_driver_event_t event;
|
||||
void *arg;
|
||||
} hid_host_device;
|
||||
} app_event_queue_t;
|
||||
|
||||
/**
|
||||
* @brief HID Protocol string names
|
||||
*/
|
||||
static const char *hid_proto_name_str[] = {
|
||||
"NONE",
|
||||
"KEYBOARD",
|
||||
"MOUSE"
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Key event
|
||||
*/
|
||||
typedef struct {
|
||||
enum key_state {
|
||||
KEY_STATE_PRESSED = 0x00,
|
||||
KEY_STATE_RELEASED = 0x01
|
||||
} state;
|
||||
uint8_t modifier;
|
||||
uint8_t key_code;
|
||||
} key_event_t;
|
||||
|
||||
/* Main char symbol for ENTER key */
|
||||
#define KEYBOARD_ENTER_MAIN_CHAR '\r'
|
||||
/* When set to 1 pressing ENTER will be extending with LineFeed during serial debug output */
|
||||
#define KEYBOARD_ENTER_LF_EXTEND 1
|
||||
|
||||
/**
|
||||
* @brief Scancode to ascii table
|
||||
*/
|
||||
const uint8_t keycode2ascii [57][2] = {
|
||||
{0, 0}, /* HID_KEY_NO_PRESS */
|
||||
{0, 0}, /* HID_KEY_ROLLOVER */
|
||||
{0, 0}, /* HID_KEY_POST_FAIL */
|
||||
{0, 0}, /* HID_KEY_ERROR_UNDEFINED */
|
||||
{'a', 'A'}, /* HID_KEY_A */
|
||||
{'b', 'B'}, /* HID_KEY_B */
|
||||
{'c', 'C'}, /* HID_KEY_C */
|
||||
{'d', 'D'}, /* HID_KEY_D */
|
||||
{'e', 'E'}, /* HID_KEY_E */
|
||||
{'f', 'F'}, /* HID_KEY_F */
|
||||
{'g', 'G'}, /* HID_KEY_G */
|
||||
{'h', 'H'}, /* HID_KEY_H */
|
||||
{'i', 'I'}, /* HID_KEY_I */
|
||||
{'j', 'J'}, /* HID_KEY_J */
|
||||
{'k', 'K'}, /* HID_KEY_K */
|
||||
{'l', 'L'}, /* HID_KEY_L */
|
||||
{'m', 'M'}, /* HID_KEY_M */
|
||||
{'n', 'N'}, /* HID_KEY_N */
|
||||
{'o', 'O'}, /* HID_KEY_O */
|
||||
{'p', 'P'}, /* HID_KEY_P */
|
||||
{'q', 'Q'}, /* HID_KEY_Q */
|
||||
{'r', 'R'}, /* HID_KEY_R */
|
||||
{'s', 'S'}, /* HID_KEY_S */
|
||||
{'t', 'T'}, /* HID_KEY_T */
|
||||
{'u', 'U'}, /* HID_KEY_U */
|
||||
{'v', 'V'}, /* HID_KEY_V */
|
||||
{'w', 'W'}, /* HID_KEY_W */
|
||||
{'x', 'X'}, /* HID_KEY_X */
|
||||
{'y', 'Y'}, /* HID_KEY_Y */
|
||||
{'z', 'Z'}, /* HID_KEY_Z */
|
||||
{'1', '!'}, /* HID_KEY_1 */
|
||||
{'2', '@'}, /* HID_KEY_2 */
|
||||
{'3', '#'}, /* HID_KEY_3 */
|
||||
{'4', '$'}, /* HID_KEY_4 */
|
||||
{'5', '%'}, /* HID_KEY_5 */
|
||||
{'6', '^'}, /* HID_KEY_6 */
|
||||
{'7', '&'}, /* HID_KEY_7 */
|
||||
{'8', '*'}, /* HID_KEY_8 */
|
||||
{'9', '('}, /* HID_KEY_9 */
|
||||
{'0', ')'}, /* HID_KEY_0 */
|
||||
{KEYBOARD_ENTER_MAIN_CHAR, KEYBOARD_ENTER_MAIN_CHAR}, /* HID_KEY_ENTER */
|
||||
{0, 0}, /* HID_KEY_ESC */
|
||||
{'\b', 0}, /* HID_KEY_DEL */
|
||||
{0, 0}, /* HID_KEY_TAB */
|
||||
{' ', ' '}, /* HID_KEY_SPACE */
|
||||
{'-', '_'}, /* HID_KEY_MINUS */
|
||||
{'=', '+'}, /* HID_KEY_EQUAL */
|
||||
{'[', '{'}, /* HID_KEY_OPEN_BRACKET */
|
||||
{']', '}'}, /* HID_KEY_CLOSE_BRACKET */
|
||||
{'\\', '|'}, /* HID_KEY_BACK_SLASH */
|
||||
{'\\', '|'}, /* HID_KEY_SHARP */ // HOTFIX: for NonUS Keyboards repeat HID_KEY_BACK_SLASH
|
||||
{';', ':'}, /* HID_KEY_COLON */
|
||||
{'\'', '"'}, /* HID_KEY_QUOTE */
|
||||
{'`', '~'}, /* HID_KEY_TILDE */
|
||||
{',', '<'}, /* HID_KEY_LESS */
|
||||
{'.', '>'}, /* HID_KEY_GREATER */
|
||||
{'/', '?'} /* HID_KEY_SLASH */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Makes new line depending on report output protocol type
|
||||
*
|
||||
* @param[in] proto Current protocol to output
|
||||
*/
|
||||
static void hid_print_new_device_report_header(hid_protocol_t proto)
|
||||
{
|
||||
static hid_protocol_t prev_proto_output = -1;
|
||||
|
||||
if (prev_proto_output != proto) {
|
||||
prev_proto_output = proto;
|
||||
printf("\r\n");
|
||||
if (proto == HID_PROTOCOL_MOUSE) {
|
||||
printf("Mouse\r\n");
|
||||
} else if (proto == HID_PROTOCOL_KEYBOARD) {
|
||||
printf("Keyboard\r\n");
|
||||
} else {
|
||||
printf("Generic\r\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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_shift(uint8_t modifier)
|
||||
{
|
||||
if (((modifier & HID_LEFT_SHIFT) == HID_LEFT_SHIFT) ||
|
||||
((modifier & HID_RIGHT_SHIFT) == 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
|
||||
* @param[in] key_char Pointer to key char data
|
||||
*
|
||||
* @return true Key scancode converted successfully
|
||||
* @return false Key scancode unknown
|
||||
*/
|
||||
static inline bool hid_keyboard_get_char(uint8_t modifier,
|
||||
uint8_t key_code,
|
||||
unsigned char *key_char)
|
||||
{
|
||||
uint8_t mod = (hid_keyboard_is_modifier_shift(modifier)) ? 1 : 0;
|
||||
|
||||
if ((key_code >= HID_KEY_A) && (key_code <= HID_KEY_SLASH)) {
|
||||
*key_char = keycode2ascii[key_code][mod];
|
||||
} else {
|
||||
// All other key pressed
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HID Keyboard print char symbol
|
||||
*
|
||||
* @param[in] key_char Keyboard char to stdout
|
||||
*/
|
||||
static inline void hid_keyboard_print_char(unsigned int key_char)
|
||||
{
|
||||
if (!!key_char) {
|
||||
putchar(key_char);
|
||||
#if (KEYBOARD_ENTER_LF_EXTEND)
|
||||
if (KEYBOARD_ENTER_MAIN_CHAR == key_char) {
|
||||
putchar('\n');
|
||||
}
|
||||
#endif // KEYBOARD_ENTER_LF_EXTEND
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Key Event. Key event with the key code, state and modifier.
|
||||
*
|
||||
* @param[in] key_event Pointer to Key Event structure
|
||||
*
|
||||
*/
|
||||
static void key_event_callback(key_event_t *key_event)
|
||||
{
|
||||
unsigned char key_char;
|
||||
|
||||
hid_print_new_device_report_header(HID_PROTOCOL_KEYBOARD);
|
||||
|
||||
if (KEY_STATE_PRESSED == key_event->state) {
|
||||
if (hid_keyboard_get_char(key_event->modifier,
|
||||
key_event->key_code, &key_char)) {
|
||||
|
||||
hid_keyboard_print_char(key_char);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Key buffer scan code search.
|
||||
*
|
||||
* @param[in] src Pointer to source buffer where to search
|
||||
* @param[in] key Key scancode to search
|
||||
* @param[in] length Size of the source buffer
|
||||
*/
|
||||
static inline bool key_found(const uint8_t *const src,
|
||||
uint8_t key,
|
||||
unsigned int length)
|
||||
{
|
||||
for (unsigned int i = 0; i < length; i++) {
|
||||
if (src[i] == key) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
{
|
||||
hid_keyboard_input_report_boot_t *kb_report = (hid_keyboard_input_report_boot_t *)data;
|
||||
|
||||
if (length < sizeof(hid_keyboard_input_report_boot_t)) {
|
||||
return;
|
||||
}
|
||||
|
||||
static uint8_t prev_keys[HID_KEYBOARD_KEY_MAX] = { 0 };
|
||||
key_event_t key_event;
|
||||
|
||||
for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) {
|
||||
|
||||
// key has been released verification
|
||||
if (prev_keys[i] > HID_KEY_ERROR_UNDEFINED &&
|
||||
!key_found(kb_report->key, prev_keys[i], HID_KEYBOARD_KEY_MAX)) {
|
||||
key_event.key_code = prev_keys[i];
|
||||
key_event.modifier = 0;
|
||||
key_event.state = KEY_STATE_RELEASED;
|
||||
key_event_callback(&key_event);
|
||||
}
|
||||
|
||||
// key has been pressed verification
|
||||
if (kb_report->key[i] > HID_KEY_ERROR_UNDEFINED &&
|
||||
!key_found(prev_keys, kb_report->key[i], HID_KEYBOARD_KEY_MAX)) {
|
||||
key_event.key_code = kb_report->key[i];
|
||||
key_event.modifier = kb_report->modifier.val;
|
||||
key_event.state = KEY_STATE_PRESSED;
|
||||
key_event_callback(&key_event);
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(prev_keys, &kb_report->key, HID_KEYBOARD_KEY_MAX);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
if (length < sizeof(hid_mouse_input_report_boot_t)) {
|
||||
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_print_new_device_report_header(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 Generic Interface report callback handler
|
||||
*
|
||||
* 'generic' means anything else than mouse or keyboard
|
||||
*
|
||||
* @param[in] data Pointer to input report data buffer
|
||||
* @param[in] length Length of input report data buffer
|
||||
*/
|
||||
static void hid_host_generic_report_callback(const uint8_t *const data, const int length)
|
||||
{
|
||||
hid_print_new_device_report_header(HID_PROTOCOL_NONE);
|
||||
for (int i = 0; i < length; i++) {
|
||||
printf("%02X", data[i]);
|
||||
}
|
||||
putchar('\r');
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief USB HID Host interface callback
|
||||
*
|
||||
* @param[in] hid_device_handle HID Device handle
|
||||
* @param[in] event HID Host interface event
|
||||
* @param[in] arg Pointer to arguments, does not used
|
||||
*/
|
||||
// Ensure TAG is defined, e.g., static const char *TAG = "hid_example";
|
||||
// Ensure hid_proto_name_str is available or define it if it's from the example.
|
||||
// Example:
|
||||
// static const char *hid_proto_name_str[] = {
|
||||
// "NONE",
|
||||
// "KEYBOARD",
|
||||
// "MOUSE"
|
||||
// };
|
||||
|
||||
void print_memory_info() {
|
||||
ESP_LOGI(TAG, "Free heap: %lu", esp_get_free_heap_size());
|
||||
ESP_LOGI(TAG, "Min free heap: %lu", esp_get_minimum_free_heap_size());
|
||||
}
|
||||
|
||||
void hid_host_interface_callback(hid_host_device_handle_t hid_device_handle,
|
||||
const hid_host_interface_event_t event,
|
||||
void *arg)
|
||||
{
|
||||
|
||||
if (event == HID_HOST_INTERFACE_EVENT_INPUT_REPORT) {
|
||||
report_count++;
|
||||
if (report_count % 100 == 0) {
|
||||
print_memory_info();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint8_t data[64] = {0};
|
||||
size_t data_length = 0;
|
||||
hid_host_dev_params_t dev_params;
|
||||
// It's good practice to check the return value of this call too
|
||||
esp_err_t err = hid_host_device_get_params(hid_device_handle, &dev_params);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get HID device params: %s", esp_err_to_name(err));
|
||||
// Potentially close the device or handle error appropriately
|
||||
if (event == HID_HOST_INTERFACE_EVENT_DISCONNECTED) { // Avoid double close
|
||||
// Already handling disconnect
|
||||
} else {
|
||||
hid_host_device_close(hid_device_handle);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Interface Event: %d, Protocol: %s", event, hid_proto_name_str[dev_params.proto]);
|
||||
|
||||
switch (event) {
|
||||
case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:
|
||||
report_count++;
|
||||
if (report_count % 100 == 0) { // Log every 100 reports
|
||||
uint32_t current_time = xTaskGetTickCount() / configTICK_RATE_HZ;
|
||||
ESP_LOGI(TAG, "Time: %lu sec, Reports: %lu",
|
||||
current_time - (start_time/configTICK_RATE_HZ), report_count);
|
||||
}
|
||||
err = hid_host_device_get_raw_input_report_data(hid_device_handle,
|
||||
data,
|
||||
64,
|
||||
&data_length);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get raw input report data: %s", esp_err_to_name(err));
|
||||
// Consider how to handle this error, maybe log and continue or flag an issue
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "Input Report Received. Length: %d, Protocol: %s", data_length, hid_proto_name_str[dev_params.proto]);
|
||||
// For debugging, you could print the first few bytes of the report:
|
||||
// if (data_length > 0) {
|
||||
// ESP_LOG_BUFFER_HEX_LEVEL(TAG, data, data_length > 16 ? 16 : data_length, ESP_LOG_VERBOSE);
|
||||
// }
|
||||
|
||||
|
||||
if (HID_SUBCLASS_BOOT_INTERFACE == dev_params.sub_class) {
|
||||
if (HID_PROTOCOL_KEYBOARD == dev_params.proto) {
|
||||
ESP_LOGV(TAG, "Keyboard (Boot) Report");
|
||||
hid_host_keyboard_report_callback(data, data_length);
|
||||
} else if (HID_PROTOCOL_MOUSE == dev_params.proto) {
|
||||
ESP_LOGV(TAG, "Mouse (Boot) Report");
|
||||
hid_host_mouse_report_callback(data, data_length);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Boot Interface, Unknown Protocol: %d", dev_params.proto);
|
||||
// Potentially call generic or log it
|
||||
hid_host_generic_report_callback(data, data_length);
|
||||
}
|
||||
} else {
|
||||
// This is for HID_SUBCLASS_NONE (Report Protocol)
|
||||
ESP_LOGV(TAG, "Generic/Report Protocol Report");
|
||||
hid_host_generic_report_callback(data, data_length);
|
||||
}
|
||||
break;
|
||||
|
||||
case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
|
||||
ESP_LOGW(TAG, "HID Device, protocol '%s' DISCONNECTED", // Warning level for disconnect
|
||||
hid_proto_name_str[dev_params.proto]);
|
||||
// The example already closes, which is good.
|
||||
// Ensure hid_host_device_close is only called once if multiple interfaces disconnect.
|
||||
// The 'arg' might be useful if it points to a structure managing the device state.
|
||||
ESP_ERROR_CHECK(hid_host_device_close(hid_device_handle)); // Original line
|
||||
break;
|
||||
|
||||
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
|
||||
// This is where your error occurs
|
||||
uint32_t error_time = xTaskGetTickCount() / configTICK_RATE_HZ;
|
||||
ESP_LOGE(TAG, "Transfer Error at %lu seconds, after %lu reports",
|
||||
error_time, report_count);
|
||||
ESP_LOGE(TAG, "HID Device, protocol '%s' TRANSFER_ERROR",
|
||||
hid_proto_name_str[dev_params.proto]);
|
||||
|
||||
// Try stopping first
|
||||
err = hid_host_device_stop(hid_device_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to stop device: %s", esp_err_to_name(err));
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); // Short delay
|
||||
|
||||
// Then try to close
|
||||
err = hid_host_device_close(hid_device_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to close device: %s", esp_err_to_name(err));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGE(TAG, "HID Device, protocol '%s' Unhandled interface event: %d",
|
||||
hid_proto_name_str[dev_params.proto], event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void hid_host_device_event(hid_host_device_handle_t hid_device_handle,
|
||||
const hid_host_driver_event_t event,
|
||||
void *arg)
|
||||
{
|
||||
hid_host_dev_params_t dev_params;
|
||||
esp_err_t err = hid_host_device_get_params(hid_device_handle, &dev_params);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get HID device params in device_event: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Device Event: %d, Protocol: %s", event, hid_proto_name_str[dev_params.proto]);
|
||||
|
||||
switch (event) {
|
||||
case HID_HOST_DRIVER_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "HID Device, protocol '%s' CONNECTED",
|
||||
hid_proto_name_str[dev_params.proto]);
|
||||
|
||||
|
||||
if (HID_PROTOCOL_KEYBOARD == dev_params.proto) {
|
||||
ESP_LOGD(TAG, "Setting Keyboard Idle handling");
|
||||
// Try setting idle rate to 0 (infinite) to prevent sleep
|
||||
err = hid_class_request_set_idle(hid_device_handle, 0, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Set Idle failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
|
||||
const hid_host_device_config_t dev_config = {
|
||||
.callback = hid_host_interface_callback,
|
||||
.callback_arg = NULL // 'arg' from this function could be passed here if needed
|
||||
};
|
||||
|
||||
err = hid_host_device_open(hid_device_handle, &dev_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open HID device: %s", esp_err_to_name(err));
|
||||
return; // Can't proceed if open fails
|
||||
}
|
||||
ESP_LOGD(TAG, "HID device opened successfully.");
|
||||
|
||||
// Forcing Boot Protocol for Keyboards/Mice if they are boot compatible
|
||||
// This is standard practice in many HID host examples.
|
||||
if (HID_SUBCLASS_BOOT_INTERFACE == dev_params.sub_class) {
|
||||
ESP_LOGD(TAG, "Device supports Boot Protocol. Setting to Boot Protocol.");
|
||||
err = hid_class_request_set_protocol(hid_device_handle, HID_REPORT_PROTOCOL_BOOT);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to set Boot Protocol: %s. Device might not support it or is not a KBD/Mouse.", esp_err_to_name(err));
|
||||
// If setting boot protocol fails, it might still work with report protocol.
|
||||
// The device will then send reports in its native "Report Protocol" format.
|
||||
} else {
|
||||
//ESP_LOGD(TAG, "Successfully set Boot Protocol.");
|
||||
if (HID_PROTOCOL_KEYBOARD == dev_params.proto) {
|
||||
err = hid_class_request_set_idle(hid_device_handle, 0, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Set Idle failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (HID_PROTOCOL_KEYBOARD == dev_params.proto) {
|
||||
ESP_LOGD(TAG, "Setting Idle rate for Keyboard.");
|
||||
err = hid_class_request_set_idle(hid_device_handle, 0, 0); // Set Idle to indefinite
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to set Idle for Keyboard: %s", esp_err_to_name(err));
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Successfully set Idle for Keyboard.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Device does not claim Boot Interface subclass. Will use Report Protocol.");
|
||||
}
|
||||
|
||||
err = hid_host_device_start(hid_device_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start HID device: %s", esp_err_to_name(err));
|
||||
hid_host_device_close(hid_device_handle); // Clean up
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "HID device, protocol '%s' started successfully.", hid_proto_name_str[dev_params.proto]);
|
||||
break;
|
||||
|
||||
// It's good to handle other driver events if they are defined and relevant
|
||||
// case HID_HOST_DRIVER_EVENT_DISCONNECTED: // This event might not exist at this level, usually handled by client event
|
||||
// ESP_LOGW(TAG, "HID Device, protocol '%s' DRIVER_EVENT_DISCONNECTED", hid_proto_name_str[dev_params.proto]);
|
||||
// // Device is already gone or being closed by client event.
|
||||
// break;
|
||||
|
||||
default:
|
||||
ESP_LOGD(TAG, "Unhandled device event: %d for protocol '%s'", event, hid_proto_name_str[dev_params.proto]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start USB Host install and handle common USB host library events while app pin not low
|
||||
*
|
||||
* @param[in] arg Not used
|
||||
*/
|
||||
static void usb_lib_task(void *arg)
|
||||
{
|
||||
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));
|
||||
xTaskNotifyGive(arg);
|
||||
|
||||
while (true) {
|
||||
uint32_t event_flags;
|
||||
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
|
||||
// In this example, there is only one client registered
|
||||
// So, once we deregister the client, this call must succeed with ESP_OK
|
||||
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
|
||||
ESP_ERROR_CHECK(usb_host_device_free_all());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "USB shutdown");
|
||||
// Clean up USB Host
|
||||
vTaskDelay(10); // Short delay to allow clients clean-up
|
||||
ESP_ERROR_CHECK(usb_host_uninstall());
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief BOOT button pressed callback
|
||||
*
|
||||
* Signal application to exit the HID Host task
|
||||
*
|
||||
* @param[in] arg Unused
|
||||
*/
|
||||
static void gpio_isr_cb(void *arg)
|
||||
{
|
||||
BaseType_t xTaskWoken = pdFALSE;
|
||||
const app_event_queue_t evt_queue = {
|
||||
.event_group = APP_EVENT,
|
||||
};
|
||||
|
||||
if (app_event_queue) {
|
||||
xQueueSendFromISR(app_event_queue, &evt_queue, &xTaskWoken);
|
||||
}
|
||||
|
||||
if (xTaskWoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HID Host Device callback
|
||||
*
|
||||
* Puts new HID Device event to the queue
|
||||
*
|
||||
* @param[in] hid_device_handle HID Device handle
|
||||
* @param[in] event HID Device event
|
||||
* @param[in] arg Not used
|
||||
*/
|
||||
void hid_host_device_callback(hid_host_device_handle_t hid_device_handle,
|
||||
const hid_host_driver_event_t event,
|
||||
void *arg)
|
||||
{
|
||||
const app_event_queue_t evt_queue = {
|
||||
.event_group = APP_EVENT_HID_HOST,
|
||||
// HID Host Device related info
|
||||
.hid_host_device.handle = hid_device_handle,
|
||||
.hid_host_device.event = event,
|
||||
.hid_host_device.arg = arg
|
||||
};
|
||||
|
||||
if (app_event_queue) {
|
||||
xQueueSend(app_event_queue, &evt_queue, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static esp_err_t check_usb_configuration(void) {
|
||||
usb_host_lib_info_t lib_info;
|
||||
esp_err_t err = usb_host_lib_info(&lib_info);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get USB lib info: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "USB Configuration: Connected devices: %d", lib_info.num_devices);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
|
||||
esp_log_level_set("usb_host", ESP_LOG_DEBUG);
|
||||
esp_log_level_set("hid_host", ESP_LOG_DEBUG);
|
||||
|
||||
BaseType_t task_created;
|
||||
app_event_queue_t evt_queue;
|
||||
ESP_LOGI(TAG, "HID Host example");
|
||||
|
||||
// Init BOOT button: Pressing the button simulates app request to exit
|
||||
// It will disconnect the USB device and uninstall the HID driver and USB Host Lib
|
||||
const gpio_config_t input_pin = {
|
||||
.pin_bit_mask = BIT64(APP_QUIT_PIN),
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.intr_type = GPIO_INTR_NEGEDGE,
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&input_pin));
|
||||
ESP_ERROR_CHECK(gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1));
|
||||
ESP_ERROR_CHECK(gpio_isr_handler_add(APP_QUIT_PIN, gpio_isr_cb, NULL));
|
||||
|
||||
/*
|
||||
* Create usb_lib_task to:
|
||||
* - initialize USB Host library
|
||||
* - Handle USB Host events while APP pin in in HIGH state
|
||||
*/
|
||||
task_created = xTaskCreatePinnedToCore(usb_lib_task,
|
||||
"usb_events",
|
||||
4096,
|
||||
xTaskGetCurrentTaskHandle(),
|
||||
2, NULL, 0);
|
||||
assert(task_created == pdTRUE);
|
||||
|
||||
// Wait for notification from usb_lib_task to proceed
|
||||
ulTaskNotifyTake(false, 1000);
|
||||
|
||||
|
||||
esp_err_t check_result = check_usb_configuration();
|
||||
if (check_result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "USB configuration check failed");
|
||||
// Consider how to handle this failure - maybe retry or halt
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* HID host driver configuration
|
||||
* - create background task for handling low level event inside the HID driver
|
||||
* - provide the device callback to get new HID Device connection event
|
||||
*/
|
||||
const hid_host_driver_config_t hid_host_driver_config = {
|
||||
.create_background_task = true,
|
||||
.task_priority = 5,
|
||||
.stack_size = 4096,
|
||||
.core_id = 0,
|
||||
.callback = hid_host_device_callback,
|
||||
.callback_arg = NULL
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(hid_host_install(&hid_host_driver_config));
|
||||
|
||||
// Create queue
|
||||
app_event_queue = xQueueCreate(10, sizeof(app_event_queue_t));
|
||||
|
||||
ESP_LOGI(TAG, "Waiting for HID Device to be connected");
|
||||
|
||||
while (1) {
|
||||
// Wait queue
|
||||
if (xQueueReceive(app_event_queue, &evt_queue, portMAX_DELAY)) {
|
||||
if (APP_EVENT == evt_queue.event_group) {
|
||||
// User pressed button
|
||||
usb_host_lib_info_t lib_info;
|
||||
ESP_ERROR_CHECK(usb_host_lib_info(&lib_info));
|
||||
if (lib_info.num_devices == 0) {
|
||||
// End while cycle
|
||||
break;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "To shutdown example, remove all USB devices and press button again.");
|
||||
// Keep polling
|
||||
}
|
||||
}
|
||||
|
||||
if (APP_EVENT_HID_HOST == evt_queue.event_group) {
|
||||
hid_host_device_event(evt_queue.hid_host_device.handle,
|
||||
evt_queue.hid_host_device.event,
|
||||
evt_queue.hid_host_device.arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "HID Driver uninstall");
|
||||
ESP_ERROR_CHECK(hid_host_uninstall());
|
||||
gpio_isr_handler_remove(APP_QUIT_PIN);
|
||||
xQueueReset(app_event_queue);
|
||||
vQueueDelete(app_event_queue);
|
||||
}
|
||||
Reference in New Issue
Block a user