Keyboard_lights_2/main/app_main.c
2025-05-22 16:26:07 +10:00

771 lines
30 KiB
C

/*
* 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 ---