2022-12-01 07:44:20 -05:00
|
|
|
/*
|
2023-01-09 09:49:25 -05:00
|
|
|
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
2022-12-01 07:44:20 -05:00
|
|
|
*
|
2023-01-09 09:49:25 -05:00
|
|
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
2022-12-01 07:44:20 -05:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#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");
|
|
|
|
}
|