initial commmit
This commit is contained in:
5
main/CMakeLists.txt
Normal file
5
main/CMakeLists.txt
Normal 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
770
main/app_main.c
Normal 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
3191
main/cJSON.c
Normal file
File diff suppressed because it is too large
Load Diff
306
main/cJSON.h
Normal file
306
main/cJSON.h
Normal 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
791
main/hid_host_example.c
Normal 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
5
main/idf_component.yml
Normal 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
|
||||
Reference in New Issue
Block a user