771 lines
30 KiB
C
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 ---
|