/* * 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 #include #include #include #include // Added for powf #include #include #include #include #include #include #include #include #include #include // For wifi_mode_t #include #include #include #include // Using local cJSON.h #include #include #include // 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 ---