initial commmit

This commit is contained in:
Sam
2025-05-22 16:26:07 +10:00
commit 9ba0ff268f
1292 changed files with 225422 additions and 0 deletions

5
main/CMakeLists.txt Normal file
View File

@@ -0,0 +1,5 @@
idf_component_register(
SRCS "app_main.c" "cJSON.c"
INCLUDE_DIRS "."
PRIV_REQUIRES esp_wifi esp_event nvs_flash mqtt usb usb_host_hid
)

770
main/app_main.c Normal file
View File

@@ -0,0 +1,770 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2024 Sam (Your Name/Handle)
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <math.h> // Added for powf
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <freertos/queue.h>
#include <freertos/timers.h>
#include <esp_err.h>
#include <esp_log.h>
#include <esp_sleep.h>
#include <esp_wifi.h>
#include <esp_wifi_types.h> // For wifi_mode_t
#include <esp_event.h>
#include <nvs_flash.h>
#include <mqtt_client.h>
#include <cJSON.h> // Using local cJSON.h
#include <usb/usb_host.h>
#include <usb/hid_host.h>
#include <usb/hid_usage_keyboard.h>
// Assuming cJSON.c is compiled directly into this component
static const char *TAG = "hid_mqtt_light";
// Configuration
#define WIFI_SSID "Aussie Broadband 8729"
#define WIFI_PASSWORD "Ffdfmunfca"
#define MQTT_BROKER_URL "mqtt://192.168.20.30"
#define MQTT_USER "mqtt-user"
#define MQTT_PASSWORD "sam4jo"
#define INACTIVITY_TIMEOUT_MS (30 * 1000) // 30 seconds
static bool g_mqtt_connected = false;
static const char *light_names[] = {"sams_bed_lamp", "joanna_red_reading_light_light", "bed_sam_jo_chandelier_1"};
#define NUM_LIGHTS (sizeof(light_names) / sizeof(light_names[0]))
typedef struct {
int r, g, b;
int colorTemp; // Kelvin
} color_definition_t;
const color_definition_t color_palette[11] = {
{255, 137, 14, 2200}, {255, 255, 255, 4000}, {255, 0, 0, 2700},
{255, 165, 0, 3000}, {255, 255, 0, 3500}, {0, 255, 0, 5000},
{0, 0, 255, 6500}, {75, 0, 130, 6000}, {148, 0, 211, 5500},
{255, 0, 127, 3200}, {255, 193, 141, 2700}};
typedef struct {
bool is_on;
uint8_t brightness; // 0-10
uint8_t hue_index; // 0-10 (maps to color_palette)
} light_state_t;
typedef enum { CONTROL_MODE_BRIGHTNESS = 0, CONTROL_MODE_HUE } control_mode_t;
typedef struct {
light_state_t lights[NUM_LIGHTS];
int current_target_light_index;
control_mode_t current_control_mode;
uint32_t magic_check; // To verify if RTC data is initialized
} light_control_rtc_data_t;
#define RTC_DATA_MAGIC 0x1234ABCD
RTC_DATA_ATTR light_control_rtc_data_t rtc_data;
static esp_mqtt_client_handle_t mqtt_client = NULL;
static TimerHandle_t deep_sleep_timer = NULL;
QueueHandle_t app_event_queue = NULL;
// --- RGB to XY Conversion ---
void RGBtoXY(int r_in, int g_in, int b_in, float *x_out, float *y_out) {
float r = (float)r_in / 255.0f;
float g = (float)g_in / 255.0f;
float b = (float)b_in / 255.0f;
r = (r > 0.04045f) ? powf((r + 0.055f) / (1.0f + 0.055f), 2.4f) : (r / 12.92f);
g = (g > 0.04045f) ? powf((g + 0.055f) / (1.0f + 0.055f), 2.4f) : (g / 12.92f);
b = (b > 0.04045f) ? powf((b + 0.055f) / (1.0f + 0.055f), 2.4f) : (b / 12.92f);
float X = r * 0.4124564f + g * 0.3575761f + b * 0.1804375f;
float Y = r * 0.2126729f + g * 0.7151522f + b * 0.0721750f;
float Z = r * 0.0193339f + g * 0.1191920f + b * 0.9503041f;
float sum = X + Y + Z;
if (sum <= 0) {
*x_out = 0.3127f;
*y_out = 0.3290f;
} else {
*x_out = X / sum;
*y_out = Y / sum;
}
}
// --- Deep Sleep Timer ---
static void deep_sleep_timer_callback(TimerHandle_t xTimer) {
ESP_LOGI(TAG, "Inactivity timeout. Entering deep sleep.");
if (mqtt_client) {
esp_mqtt_client_disconnect(mqtt_client);
esp_mqtt_client_destroy(mqtt_client);
mqtt_client = NULL;
}
esp_wifi_disconnect();
esp_wifi_stop();
esp_wifi_deinit();
ESP_LOGI(TAG, "Preparing for deep sleep.");
esp_deep_sleep_start();
}
static void reset_inactivity_timer(void) {
if (deep_sleep_timer == NULL) {
deep_sleep_timer = xTimerCreate("DeepSleepTimer",
pdMS_TO_TICKS(INACTIVITY_TIMEOUT_MS),
pdFALSE,
(void *)0, deep_sleep_timer_callback);
}
if (deep_sleep_timer != NULL) {
if (xTimerIsTimerActive(deep_sleep_timer)) {
xTimerStop(deep_sleep_timer, 0);
}
xTimerStart(deep_sleep_timer, 0);
ESP_LOGD(TAG, "Inactivity timer (re)started.");
} else {
ESP_LOGE(TAG, "Failed to create or start deep sleep timer.");
}
}
// --- End of Part 1 ---
// --- Start of Part 2 ---
// --- MQTT ---
static void log_error_if_nonzero(const char *message, int error_code) {
if (error_code != 0) {
ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code);
}
}
static void mqtt_event_handler(void *handler_args, esp_event_base_t base,
int32_t event_id, void *event_data) {
ESP_LOGD(TAG, "Event dispatched from event loop base=%s, event_id=%ld",
base, event_id);
esp_mqtt_event_handle_t event = (esp_mqtt_event_handle_t)event_data;
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
g_mqtt_connected = true;
reset_inactivity_timer();
break;
case MQTT_EVENT_DISCONNECTED:
g_mqtt_connected = false;
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGI(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
ESP_LOGI(TAG, "MQTT_EVENT_DATA");
break;
case MQTT_EVENT_ERROR:
ESP_LOGI(TAG, "MQTT_EVENT_ERROR");
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
log_error_if_nonzero("reported from esp-tls",
event->error_handle->esp_tls_last_esp_err);
log_error_if_nonzero("reported from tls stack",
event->error_handle->esp_tls_stack_err);
log_error_if_nonzero("captured as transport's socket errno",
event->error_handle->esp_transport_sock_errno);
ESP_LOGI(TAG, "Last errno string (%s)",
strerror(event->error_handle->esp_transport_sock_errno));
}
break;
default:
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
break;
}
}
static void publish_light_state(int light_index) { // light_index is still useful to get the target name
bool wifi_has_ip = false;
wifi_mode_t mode;
if (esp_wifi_get_mode(&mode) == ESP_OK && (mode == WIFI_MODE_STA || mode == WIFI_MODE_APSTA)) {
esp_netif_ip_info_t ip_info;
if (esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"), &ip_info) == ESP_OK) {
if (ip_info.ip.addr != 0) {
wifi_has_ip = true;
}
}
}
ESP_LOGI(TAG, "publish_light_state: WiFi has IP: %s, MQTT Globally Connected: %s",
wifi_has_ip ? "YES" : "NO",
g_mqtt_connected ? "YES" : "NO");
if (!g_mqtt_connected || !wifi_has_ip) {
ESP_LOGW(TAG, "MQTT client not ready or WiFi disconnected. Cannot publish. (g_mqtt_connected: %d, wifi_has_ip: %d)",
g_mqtt_connected, wifi_has_ip);
return;
}
light_state_t *light_s = &rtc_data.lights[light_index];
cJSON *root = cJSON_CreateObject();
if (!root) {
ESP_LOGE(TAG, "Failed to create cJSON object");
return;
}
// --- MODIFICATION START ---
// 1. Add the target light name to the JSON payload
cJSON_AddStringToObject(root, "target_light", light_names[light_index]);
// 2. Define the static topic
const char *topic = "homeassistant/light/remote";
// --- MODIFICATION END ---
if (light_s->is_on) {
cJSON_AddStringToObject(root, "state", "ON");
cJSON_AddNumberToObject(root, "brightness",
(int)((float)light_s->brightness * 25.5f + 0.5f));
const color_definition_t *color_def =
&color_palette[light_s->hue_index];
cJSON_AddNumberToObject(root, "color_temp", color_def->colorTemp);
float x, y;
RGBtoXY(color_def->r, color_def->g, color_def->b, &x, &y);
cJSON *xy_array = cJSON_CreateArray();
if (xy_array) {
cJSON_AddItemToArray(xy_array, cJSON_CreateNumber(x));
cJSON_AddItemToArray(xy_array, cJSON_CreateNumber(y));
cJSON_AddItemToObject(root, "xy_color", xy_array);
} else {
ESP_LOGE(TAG, "Failed to create cJSON array for xy_color");
}
} else {
cJSON_AddStringToObject(root, "state", "OFF");
}
char *json_string = cJSON_PrintUnformatted(root);
if (json_string) {
ESP_LOGI(TAG, "Publishing to %s: %s", topic, json_string); // Log will show the new static topic
if (esp_mqtt_client_publish(mqtt_client, topic, json_string, 0, 1, 0) == -1) { // Use the new static topic
ESP_LOGE(TAG, "Failed to publish MQTT message");
}
cJSON_free(json_string);
} else {
ESP_LOGE(TAG, "Failed to print cJSON to string");
}
cJSON_Delete(root);
}
static void mqtt_app_start(void) {
esp_mqtt_client_config_t mqtt_cfg = {
.broker.address.uri = MQTT_BROKER_URL,
.credentials.username = MQTT_USER,
.credentials.authentication.password = MQTT_PASSWORD,
};
mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
if (mqtt_client == NULL) {
ESP_LOGE(TAG, "Failed to create MQTT client");
return;
}
esp_err_t register_err = esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID,
mqtt_event_handler, NULL);
if (register_err != ESP_OK) {
ESP_LOGE(TAG, "Failed to register MQTT event handler: %s", esp_err_to_name(register_err));
}
esp_err_t start_err = esp_mqtt_client_start(mqtt_client);
if (start_err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start MQTT client: %s", esp_err_to_name(start_err));
}
}
// --- End of Part 2 ---
// --- Start of Part 3 ---
// --- WiFi ---
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data) {
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
ESP_LOGI(TAG, "WIFI_EVENT_STA_START, connecting...");
esp_wifi_connect();
} else if (event_base == WIFI_EVENT &&
event_id == WIFI_EVENT_STA_DISCONNECTED) {
wifi_event_sta_disconnected_t *disconnected = (wifi_event_sta_disconnected_t *)event_data;
ESP_LOGW(TAG, "WiFi disconnected, reason: %d. Trying to reconnect...", disconnected->reason);
esp_wifi_connect();
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
if (mqtt_client == NULL) {
mqtt_app_start();
} else {
ESP_LOGI(TAG, "WiFi reconnected, attempting MQTT reconnect.");
esp_mqtt_client_reconnect(mqtt_client);
}
reset_inactivity_timer();
}
}
void wifi_init_sta(void) {
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_event_handler_register(
WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(
IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL));
wifi_config_t wifi_config = {
.sta =
{
.ssid = WIFI_SSID,
.password = WIFI_PASSWORD,
.sae_pwe_h2e = WPA3_SAE_PWE_BOTH,
.listen_interval = 3,
.pmf_cfg = {
.capable = true,
.required = false
},
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "wifi_init_sta finished. Connecting to AP...");
}
// --- End of Part 3 ---
// --- Start of Part 4 ---
// --- USB HID Processing ---
typedef enum {
APP_EVENT_QUEUE_USB_HOST = 0,
} app_event_queue_group_t;
typedef struct {
app_event_queue_group_t event_group;
struct {
hid_host_device_handle_t handle;
hid_host_driver_event_t event;
void *arg;
} hid_host_device;
} app_event_queue_t;
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 */
{'\r', '\r'}, /* HID_KEY_ENTER */ {0, 0}, /* HID_KEY_ESC */ {'\b', 0}, /* HID_KEY_DEL */
{'\t', 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 */ {';', ':'}, /* HID_KEY_COLON */
{'\'', '"'}, /* HID_KEY_QUOTE */ {'`', '~'}, /* HID_KEY_TILDE */ {',', '<'}, /* HID_KEY_LESS */
{'.', '>'}, /* HID_KEY_GREATER */ {'/', '?'} /* HID_KEY_SLASH */
};
static void process_key_event(unsigned char key_char) {
ESP_LOGI(TAG, "Key pressed: %c", key_char);
reset_inactivity_timer();
int light_idx = -1;
bool state_changed = false;
switch (key_char) {
case 'a': light_idx = 0; break;
case 'b': light_idx = 1; break;
case 'c': light_idx = 2; break;
case 'e':
rtc_data.current_control_mode =
(rtc_data.current_control_mode == CONTROL_MODE_BRIGHTNESS)
? CONTROL_MODE_HUE : CONTROL_MODE_BRIGHTNESS;
ESP_LOGI(TAG, "Control mode switched to: %s",
(rtc_data.current_control_mode == CONTROL_MODE_BRIGHTNESS)
? "Brightness" : "Hue");
return;
case 'd':
case 'f':
{
light_state_t *target_light =
&rtc_data.lights[rtc_data.current_target_light_index];
if (rtc_data.current_control_mode == CONTROL_MODE_BRIGHTNESS) {
if (key_char == 'd') {
if (target_light->brightness > 0) { target_light->brightness--; state_changed = true; }
else { ESP_LOGD(TAG, "Brightness already at minimum (0)"); }
} else if (key_char == 'f') {
if (target_light->brightness < 10) { target_light->brightness++; state_changed = true; }
else { ESP_LOGD(TAG, "Brightness already at maximum (10)"); }
}
} else {
if (key_char == 'd') {
if (target_light->hue_index > 0) { target_light->hue_index--; state_changed = true; }
else { ESP_LOGD(TAG, "Hue index already at minimum (0)"); }
} else if (key_char == 'f') {
if (target_light->hue_index < 10) { target_light->hue_index++; state_changed = true; }
else { ESP_LOGD(TAG, "Hue index already at maximum (10)"); }
}
}
if (state_changed && target_light->is_on) {
publish_light_state(rtc_data.current_target_light_index);
}
ESP_LOGI(TAG, "Light %d (%s): Brightness %d, Hue Index %d",
rtc_data.current_target_light_index,
light_names[rtc_data.current_target_light_index],
target_light->brightness, target_light->hue_index);
return;
}
default: ESP_LOGD(TAG, "Unhandled key: %c", key_char); return;
}
if (light_idx != -1) {
if (rtc_data.lights[light_idx].is_on) {
if (rtc_data.current_target_light_index == light_idx) {
rtc_data.lights[light_idx].is_on = false; state_changed = true;
ESP_LOGI(TAG, "Light %s turned OFF.", light_names[light_idx]);
} else {
rtc_data.current_target_light_index = light_idx;
ESP_LOGI(TAG, "Target light switched to: %s (already ON)", light_names[light_idx]);
return;
}
} else {
rtc_data.lights[light_idx].is_on = true;
rtc_data.current_target_light_index = light_idx; state_changed = true;
ESP_LOGI(TAG, "Light %s turned ON.", light_names[light_idx]);
}
if (state_changed) { publish_light_state(light_idx); }
ESP_LOGI(TAG, "Current Target: %s", light_names[rtc_data.current_target_light_index]);
}
}
static void custom_key_event_callback(uint8_t modifier, uint8_t key_code) {
unsigned char key_char = 0;
if ((key_code >= HID_KEY_A) && (key_code <= HID_KEY_Z)) {
key_char = keycode2ascii[key_code][0];
} else if ((key_code >= HID_KEY_1) && (key_code <= HID_KEY_0)) {
key_char = keycode2ascii[key_code][0];
} else if (key_code == HID_KEY_ENTER) {
key_char = keycode2ascii[HID_KEY_ENTER][0];
}
if (key_char != 0) { process_key_event(key_char); }
else { ESP_LOGD(TAG, "Ignoring unhandled key code: 0x%02X", key_code); }
}
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;
}
static uint8_t prev_keys[HID_KEYBOARD_KEY_MAX] = {0};
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)) {
ESP_LOGW(TAG, "Keyboard report too short (%d bytes)", length); return;
}
for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) {
if (kb_report->key[i] > HID_KEY_ERROR_UNDEFINED) {
if (!key_found(prev_keys, kb_report->key[i], HID_KEYBOARD_KEY_MAX)) {
custom_key_event_callback(kb_report->modifier.val, kb_report->key[i]);
}
}
}
memcpy(prev_keys, &kb_report->key, HID_KEYBOARD_KEY_MAX);
}
// --- End of Part 4 ---
// --- Start of Part 5 ---
void hid_host_interface_callback(hid_host_device_handle_t hid_device_handle,
const hid_host_interface_event_t event,
void *arg) {
esp_err_t err;
hid_host_dev_params_t dev_params;
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 interface callback: %s", esp_err_to_name(err));
if (event != HID_HOST_INTERFACE_EVENT_DISCONNECTED) {
hid_host_device_close(hid_device_handle);
}
return;
}
ESP_LOGD(TAG, "Interface Event: %d, Protocol: %d, Subclass: %d", event, dev_params.proto, dev_params.sub_class);
switch (event) {
case HID_HOST_INTERFACE_EVENT_INPUT_REPORT: {
uint8_t data[128] = {0};
size_t data_length = 0;
err = hid_host_device_get_raw_input_report_data(
hid_device_handle, data, sizeof(data), &data_length);
if (err != ESP_OK) {
if (err != ESP_ERR_TIMEOUT) {
ESP_LOGE(TAG, "Failed to get raw input report data: %s", esp_err_to_name(err));
}
break;
}
ESP_LOGV(TAG, "Input Report Received. Length: %zu, Protocol: %d, Subclass: %d", data_length, dev_params.proto, dev_params.sub_class);
if (HID_SUBCLASS_BOOT_INTERFACE == dev_params.sub_class &&
HID_PROTOCOL_KEYBOARD == dev_params.proto) {
ESP_LOGV(TAG, "Processing Boot Keyboard Report");
hid_host_keyboard_report_callback(data, data_length);
} else {
ESP_LOGV(TAG, "Ignoring Input Report from non-boot keyboard or unsupported device type.");
}
break;
}
case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "HID Device, Protocol '%d' DISCONNECTED from interface", dev_params.proto);
break;
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
ESP_LOGW(TAG, "HID Device, Protocol '%d' TRANSFER_ERROR", dev_params.proto);
err = hid_host_device_stop(hid_device_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to stop device on transfer error: %s", esp_err_to_name(err));
}
vTaskDelay(pdMS_TO_TICKS(50));
err = hid_host_device_close(hid_device_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to close device on transfer error: %s", esp_err_to_name(err));
}
break;
default:
ESP_LOGD(TAG, "Unhandled HID interface event: %d for protocol %d", event, dev_params.proto);
break;
}
}
void hid_host_device_event(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event, void *arg) {
esp_err_t err;
hid_host_dev_params_t dev_params;
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 Driver Event: %d, Protocol: %d, Subclass: %d", event, dev_params.proto, dev_params.sub_class);
switch (event) {
case HID_HOST_DRIVER_EVENT_CONNECTED:
ESP_LOGI(TAG, "HID Device CONNECTED (Driver Event): Protocol %d Subclass %d",
dev_params.proto, dev_params.sub_class);
if (HID_SUBCLASS_BOOT_INTERFACE != dev_params.sub_class ||
HID_PROTOCOL_KEYBOARD != dev_params.proto) {
ESP_LOGW(TAG, "Ignoring connected HID device - not a Boot Protocol Keyboard.");
return;
}
const hid_host_device_config_t dev_config = {
.callback = hid_host_interface_callback,
.callback_arg = NULL
};
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;
}
ESP_LOGD(TAG, "HID device opened successfully.");
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. Error: %s", esp_err_to_name(err));
hid_host_device_close(hid_device_handle);
return;
}
ESP_LOGD(TAG, "Set Boot Protocol successfully.");
if (dev_params.proto == HID_PROTOCOL_KEYBOARD) {
err = hid_class_request_set_idle(hid_device_handle, 0, 0);
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.");
}
}
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);
return;
}
ESP_LOGI(TAG, "HID device started successfully.");
reset_inactivity_timer();
break;
// HID_HOST_DRIVER_EVENT_DISCONNECTED and HID_HOST_DRIVER_EVENT_ERROR are not defined
// in the version of usb_host_hid (v1.0.1) used with ESP-IDF v5.x.
// The driver handles disconnection internally. Errors might come via interface callback.
default:
ESP_LOGD(TAG, "Unhandled HID device driver event: %d", event);
break;
}
}
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_LOGI(TAG, "Installing USB Host...");
esp_err_t err = usb_host_install(&host_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to install USB Host: %s", esp_err_to_name(err));
vTaskDelete(NULL); return;
}
ESP_LOGI(TAG, "USB Host installed.");
xTaskNotifyGive(arg);
while (true) {
uint32_t event_flags = 0;
esp_err_t usb_evt_err = usb_host_lib_handle_events(pdMS_TO_TICKS(100), &event_flags);
if (usb_evt_err != ESP_OK && usb_evt_err != ESP_ERR_TIMEOUT) {
ESP_LOGE(TAG, "USB Host Lib handle events failed: %s", esp_err_to_name(usb_evt_err));
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
ESP_LOGI(TAG, "USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS received.");
}
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
ESP_LOGI(TAG, "USB_HOST_LIB_EVENT_FLAGS_ALL_FREE: All devices freed");
}
}
ESP_LOGI(TAG, "USB Lib task shutting down (should not happen in this app design).");
vTaskDelete(NULL);
}
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_QUEUE_USB_HOST,
.hid_host_device.handle = hid_device_handle,
.hid_host_device.event = event,
.hid_host_device.arg = arg};
BaseType_t ret = xQueueSend(app_event_queue, &evt_queue, pdMS_TO_TICKS(10));
if (ret != pdTRUE) {
ESP_LOGE(TAG, "Failed to send event to queue. Event: %d", event);
}
}
// --- End of Part 5 ---
// --- Start of Part 6 ---
void app_main(void) {
esp_log_level_set(TAG, ESP_LOG_DEBUG);
esp_log_level_set("usb_host", ESP_LOG_INFO);
esp_log_level_set("hid_host", ESP_LOG_INFO);
esp_log_level_set("wifi", ESP_LOG_INFO);
esp_log_level_set("mqtt_client", ESP_LOG_INFO);
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause != ESP_SLEEP_WAKEUP_UNDEFINED && cause != ESP_SLEEP_WAKEUP_TIMER &&
rtc_data.magic_check == RTC_DATA_MAGIC) {
ESP_LOGI(TAG, "Woke from sleep. Restoring RTC data.");
} else {
ESP_LOGI(TAG, "Fresh boot or invalid RTC data. Initializing defaults.");
for (int i = 0; i < NUM_LIGHTS; i++) {
rtc_data.lights[i].is_on = false;
rtc_data.lights[i].brightness = 5;
rtc_data.lights[i].hue_index = 0;
}
rtc_data.current_target_light_index = 0;
rtc_data.current_control_mode = CONTROL_MODE_BRIGHTNESS;
rtc_data.magic_check = RTC_DATA_MAGIC;
}
ESP_LOGI(TAG, "Initial/Restored state: Target %d (%s), Mode %s",
rtc_data.current_target_light_index,
light_names[rtc_data.current_target_light_index],
rtc_data.current_control_mode == CONTROL_MODE_BRIGHTNESS ? "Brightness" : "Hue");
for (int i = 0; i < NUM_LIGHTS; i++) {
ESP_LOGI(TAG, "Light %d (%s): On=%d, Bright=%d, HueIdx=%d",
i, light_names[i], rtc_data.lights[i].is_on,
rtc_data.lights[i].brightness, rtc_data.lights[i].hue_index);
}
wifi_init_sta();
BaseType_t task_created;
task_created = xTaskCreatePinnedToCore(usb_lib_task, "usb_events", 4096,
xTaskGetCurrentTaskHandle(), 2, NULL, 0);
assert(task_created == pdTRUE);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
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_LOGI(TAG, "Installing HID Host driver...");
esp_err_t hid_install_err = hid_host_install(&hid_host_driver_config);
if (hid_install_err != ESP_OK) {
ESP_LOGE(TAG, "Failed to install HID Host driver: %s", esp_err_to_name(hid_install_err));
return;
}
ESP_LOGI(TAG, "HID Host driver installed.");
app_event_queue = xQueueCreate(10, sizeof(app_event_queue_t));
assert(app_event_queue != NULL);
ESP_LOGI(TAG, "Application started. Waiting for HID Device connection or events...");
app_event_queue_t evt_queue_item;
while (true) {
if (xQueueReceive(app_event_queue, &evt_queue_item, portMAX_DELAY)) {
if (APP_EVENT_QUEUE_USB_HOST == evt_queue_item.event_group) {
hid_host_device_event(evt_queue_item.hid_host_device.handle,
evt_queue_item.hid_host_device.event,
evt_queue_item.hid_host_device.arg);
}
}
}
// --- Cleanup (Normally not reached) ---
ESP_LOGI(TAG, "Application exiting (should not happen).");
if (deep_sleep_timer) { xTimerStop(deep_sleep_timer, 0); xTimerDelete(deep_sleep_timer, 0); deep_sleep_timer = NULL; }
if (mqtt_client) { esp_mqtt_client_disconnect(mqtt_client); esp_mqtt_client_destroy(mqtt_client); mqtt_client = NULL; }
esp_wifi_disconnect(); esp_wifi_stop(); esp_wifi_deinit();
ESP_LOGI(TAG, "Uninstalling HID Host driver...");
hid_host_uninstall();
if (app_event_queue) { vQueueDelete(app_event_queue); app_event_queue = NULL; }
}
// --- End of Part 6 ---

3191
main/cJSON.c Normal file

File diff suppressed because it is too large Load Diff

306
main/cJSON.h Normal file
View File

@@ -0,0 +1,306 @@
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
For *nix builds that support visibility attribute, you can define similar behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
*/
#define CJSON_CDECL __cdecl
#define CJSON_STDCALL __stdcall
/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type CJSON_STDCALL
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
#endif
#else /* !__WINDOWS__ */
#define CJSON_CDECL
#define CJSON_STDCALL
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 18
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_NESTING_LIMIT
#define CJSON_NESTING_LIMIT 1000
#endif
/* Limits the length of circular references can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_CIRCULAR_LIMIT
#define CJSON_CIRCULAR_LIMIT 10000
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char*) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/array that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items.
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
* writing to `item->string` */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detach items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
* The item->next and ->prev pointers are always zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
* The input pointer json cannot point to a read-only address area, such as a string constant,
* but should point to a readable and writable address area. */
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
#define cJSON_SetBoolValue(object, boolValue) ( \
(object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
(object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
cJSON_Invalid\
)
/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#ifdef __cplusplus
}
#endif
#endif

791
main/hid_host_example.c Normal file
View 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);
}

5
main/idf_component.yml Normal file
View File

@@ -0,0 +1,5 @@
dependencies:
idf: ">=4.4"
usb_host_hid: "^1.0.1"
esp_mqtt:
git: https://github.com/espressif/esp-mqtt.git