commit 8d0055079e64e48107f56f8ed02bb2cb5b8004f7 Author: Sam Date: Thu May 22 16:26:07 2025 +1000 initial commmit diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..dafb8ad --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +ARG DOCKER_TAG=latest +FROM espressif/idf:${DOCKER_TAG} + +ENV LC_ALL=C.UTF-8 +ENV LANG=C.UTF-8 + +RUN apt-get update -y && apt-get install udev -y + +RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc + +ENTRYPOINT [ "/opt/esp/entrypoint.sh" ] + +CMD ["/bin/bash", "-c"] \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..b801786 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,21 @@ +{ + "name": "ESP-IDF QEMU", + "build": { + "dockerfile": "Dockerfile" + }, + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash", + "idf.espIdfPath": "/opt/esp/idf", + "idf.toolsPath": "/opt/esp", + "idf.gitPath": "/usr/bin/git" + }, + "extensions": [ + "espressif.esp-idf-extension", + "espressif.esp-idf-web" + ] + } + }, + "runArgs": ["--privileged"] +} \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..b0646d4 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,23 @@ +{ + "configurations": [ + { + "name": "ESP-IDF", + "compilerPath": "${config:idf.toolsPathWin}\\tools\\xtensa-esp-elf\\esp-14.2.0_20241119\\xtensa-esp-elf\\bin\\xtensa-esp32s3-elf-gcc.exe", + "compileCommands": "${config:idf.buildPath}/compile_commands.json", + "includePath": [ + "${config:idf.espIdfPath}/components/**", + "${config:idf.espIdfPathWin}/components/**", + "${workspaceFolder}/**" + ], + "browse": { + "path": [ + "${config:idf.espIdfPath}/components", + "${config:idf.espIdfPathWin}/components", + "${workspaceFolder}" + ], + "limitSymbolsToIncludedHeaders": true + } + } + ], + "version": 4 +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2511a38 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + }, + { + "type": "espidf", + "name": "Launch", + "request": "launch" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6ec32bb --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "C_Cpp.intelliSenseEngine": "default", + "idf.espIdfPathWin": "c:\\Espressif\\frameworks\\v5.4.1\\esp-idf", + "idf.toolsPathWin": "C:\\Espressif\\tools", + "idf.pythonInstallPath": "C:\\Espressif\\tools\\tools\\idf-python\\3.11.2\\python.exe", + "idf.portWin": "COM20", + "idf.openOcdConfigs": [ + "board/esp32s3-builtin.cfg" + ], + "idf.customExtraVars": { + "OPENOCD_SCRIPTS": "C:\\Users\\Dell\\.espressif\\tools\\openocd-esp32\\v0.12.0-esp32-20240318/openocd-esp32/share/openocd/scripts", + "IDF_CCACHE_ENABLE": "1", + "ESP_ROM_ELF_DIR": "C:\\Users\\Dell\\.espressif\\tools\\esp-rom-elfs\\20240305/", + "IDF_TARGET": "esp32s3" + }, + "files.associations": { + "hid_usage_keyboard.h": "c", + "esp_task_wdt.h": "c", + "freertos.h": "c", + "task.h": "c" + } +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8539d7f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +get_filename_component(ProjectId ${CMAKE_CURRENT_LIST_DIR} NAME) +string(REPLACE " " "_" ProjectId ${ProjectId}) +project(${ProjectId}) diff --git a/README.md b/README.md new file mode 100644 index 0000000..281c180 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +| Supported Targets | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | + +# USB HID Class example +This example implements a basic USB Host HID Class Driver, and demonstrates how to use the driver to communicate with USB HID devices (such as Keyboard and Mouse or both) on the ESP32-S2/S3. Currently, the example only supports the HID boot protocol which should be present on most USB Mouse and Keyboards. The example will continuously scan for the connection of any HID Mouse or Keyboard, and attempt to fetch HID reports from those devices once connected. To quit the example (and shut down the HID driver), users can GPIO0 to low (i.e., pressing the "Boot" button on most ESP dev kits). + + +### Hardware Required +* Development board with USB capable ESP SoC (ESP32-S2/ESP32-S3) +* A USB cable for Power supply and programming +* USB OTG Cable + +### Common Pin Assignments + +If your board doesn't have a USB A connector connected to the dedicated GPIOs, +you may have to DIY a cable and connect **D+** and **D-** to the pins listed below. + +``` +ESP BOARD USB CONNECTOR (type A) + -- + | || VCC +[GPIO19] ------> | || D- +[GPIO20] ------> | || D+ + | || GND + -- +``` + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +The example serial output will be the following: + +``` +I (198) example: HID HOST example +I (598) example: Interface number 0, protocol Mouse +I (598) example: Interface number 1, protocol Keyboard + +Mouse +X: 000883 Y: 000058 |o| | +Keyboard +qwertyuiop[]\asdfghjkl;'zxcvbnm,./ +Mouse +X: 000883 Y: 000058 | |o| +``` + +Where every keyboard key printed as char symbol if it is possible and a Hex value for any other key. + +#### Keyboard input data +Keyboard input data starts with the word "Keyboard" and every pressed key is printed to the serial debug. +Left or right Shift modifier is also supported. + +``` +Keyboard +Hello, ESP32 USB HID Keyboard is here! +``` + +#### Mouse input data +Mouse input data starts with the word "Mouse" and has the following structure. +``` +Mouse +X: -00343 Y: 000183 | |o| + | | | | + | | | +- Right mouse button pressed status ("o" - pressed, " " - not pressed) + | | +--- Left mouse button pressed status ("o" - pressed, " " - not pressed) + | +---------- Y relative coordinate of the cursor + +----------------------- X relative coordinate of the cursor +``` diff --git a/dependencies.lock b/dependencies.lock new file mode 100644 index 0000000..6225d57 --- /dev/null +++ b/dependencies.lock @@ -0,0 +1,36 @@ +dependencies: + esp_mqtt: + component_hash: 2fb8e302a94e05fa40af4788fa45414369db53b2a8ccfaa7d07797192894c708 + dependencies: + - name: idf + version: '>=5.0' + source: + git: https://github.com/espressif/esp-mqtt.git + path: . + type: git + version: 6af4446a48ea7fa54948edc4e62277ed70abb6e1 + espressif/usb_host_hid: + component_hash: eabf03fe3cb1bdb5325d2725f420c6324c3e150033d893ce3737222929455a55 + dependencies: + - name: idf + require: private + version: '>=4.4' + source: + registry_url: https://components.espressif.com/ + type: service + targets: + - esp32s2 + - esp32s3 + - esp32p4 + version: 1.0.3 + idf: + source: + type: idf + version: 5.4.1 +direct_dependencies: +- esp_mqtt +- espressif/usb_host_hid +- idf +manifest_hash: d2366cab3b8a55e6f5c5bd0d8b839bbc994a2e9b1ec0c77bb3372b8d561145e0 +target: esp32s3 +version: 2.0.0 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt new file mode 100644 index 0000000..6dc089b --- /dev/null +++ b/main/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/main/app_main.c b/main/app_main.c new file mode 100644 index 0000000..41dbd77 --- /dev/null +++ b/main/app_main.c @@ -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 +#include +#include +#include +#include // Added for powf + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include // For wifi_mode_t +#include +#include +#include +#include // Using local cJSON.h + +#include +#include +#include + +// Assuming cJSON.c is compiled directly into this component + +static const char *TAG = "hid_mqtt_light"; + +// Configuration +#define WIFI_SSID "Aussie Broadband 8729" +#define WIFI_PASSWORD "WIFI_PASS_REDACTED" +#define MQTT_BROKER_URL "mqtt://192.168.20.30" +#define MQTT_USER "MQTT_USER_REDACTED" +#define MQTT_PASSWORD "MQTT_PASS_REDACTED" + +#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 --- diff --git a/main/cJSON.c b/main/cJSON.c new file mode 100644 index 0000000..3873ab1 --- /dev/null +++ b/main/cJSON.c @@ -0,0 +1,3191 @@ +/* + 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. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 18) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + item->valuestring = NULL; + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + item->string = NULL; + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char *number_c_string; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + size_t number_string_length = 0; + cJSON_bool has_decimal_point = false; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_string_length++; + break; + + case '.': + number_string_length++; + has_decimal_point = true; + break; + + default: + goto loop_end; + } + } +loop_end: + /* malloc for temporary buffer, add 1 for '\0' */ + number_c_string = (unsigned char *) input_buffer->hooks.allocate(number_string_length + 1); + if (number_c_string == NULL) + { + return false; /* allocation failure */ + } + + memcpy(number_c_string, buffer_at_offset(input_buffer), number_string_length); + number_c_string[number_string_length] = '\0'; + + if (has_decimal_point) + { + for (i = 0; i < number_string_length; i++) + { + if (number_c_string[i] == '.') + { + /* replace '.' with the decimal point of the current locale (for strtod) */ + number_c_string[i] = decimal_point; + } + } + } + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + /* free the temporary buffer */ + input_buffer->hooks.deallocate(number_c_string); + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + /* free the temporary buffer */ + input_buffer->hooks.deallocate(number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +/* Note: when passing a NULL valuestring, cJSON_SetValuestring treats this as an error and return NULL */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + size_t v1_len; + size_t v2_len; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if ((object == NULL) || !(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return NULL; + } + /* return NULL if the object is corrupted or valuestring is NULL */ + if (object->valuestring == NULL || valuestring == NULL) + { + return NULL; + } + + v1_len = strlen(valuestring); + v2_len = strlen(object->valuestring); + + if (v1_len <= v2_len) + { + /* strcpy does not handle overlapping string: [X1, X2] [Y1, Y2] => X2 < Y1 or Y2 < X1 */ + if (!( valuestring + v1_len < object->valuestring || object->valuestring + v2_len < valuestring )) + { + return NULL; + } + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "null"); + } + else if(d == (double)item->valueint) + { + length = sprintf((char*)number_buffer, "%d", item->valueint); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + output = NULL; + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + buffer->buffer = NULL; + } + + if (printed != NULL) + { + hooks->deallocate(printed); + printed = NULL; + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + p.buffer = NULL; + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + if (cannot_access_at_index(input_buffer, 1)) + { + goto fail; /* nothing comes after the comma */ + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL) || (item != parent->child && item->prev == NULL)) + { + return NULL; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0 || newitem == NULL) + { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + return add_item_to_array(array, newitem); + } + + if (after_inserted != array->child && after_inserted->prev == NULL) { + /* return false if after_inserted is a corrupted array item */ + return false; + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (parent->child == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if (replacement->string == NULL) + { + return false; + } + + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse); + +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + return cJSON_Duplicate_rec(item, 0, recurse ); +} + +cJSON * cJSON_Duplicate_rec(const cJSON *item, size_t depth, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + if(depth >= CJSON_CIRCULAR_LIMIT) { + goto fail; + } + newchild = cJSON_Duplicate_rec(child, depth + 1, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); + object = NULL; +} \ No newline at end of file diff --git a/main/cJSON.h b/main/cJSON.h new file mode 100644 index 0000000..db1576c --- /dev/null +++ b/main/cJSON.h @@ -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 + +/* 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 \ No newline at end of file diff --git a/main/hid_host_example.c b/main/hid_host_example.c new file mode 100644 index 0000000..320f0cf --- /dev/null +++ b/main/hid_host_example.c @@ -0,0 +1,791 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#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); +} diff --git a/main/idf_component.yml b/main/idf_component.yml new file mode 100644 index 0000000..1782043 --- /dev/null +++ b/main/idf_component.yml @@ -0,0 +1,5 @@ +dependencies: + idf: ">=4.4" + usb_host_hid: "^1.0.1" + esp_mqtt: + git: https://github.com/espressif/esp-mqtt.git \ No newline at end of file diff --git a/managed_components/esp_mqtt/.component_hash b/managed_components/esp_mqtt/.component_hash new file mode 100644 index 0000000..ce6f4d4 --- /dev/null +++ b/managed_components/esp_mqtt/.component_hash @@ -0,0 +1 @@ +2fb8e302a94e05fa40af4788fa45414369db53b2a8ccfaa7d07797192894c708 \ No newline at end of file diff --git a/managed_components/esp_mqtt/.editorconfig b/managed_components/esp_mqtt/.editorconfig new file mode 100644 index 0000000..15977c6 --- /dev/null +++ b/managed_components/esp_mqtt/.editorconfig @@ -0,0 +1,34 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# http://editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[{*.md,*.rst}] +trim_trailing_whitespace = false + +[{Makefile,*.mk,*.bat}] +indent_style = tab +indent_size = 2 + +[*/freertos/**] +indent_style = tab +indent_size = 4 + +[{*/freertos/**.S,**/FreeRTOSConfig.h}] +indent_style = space +indent_size = 4 + +[*.pem] +insert_final_newline = false + +[*.py] +max_line_length = 119 diff --git a/managed_components/esp_mqtt/.gitignore b/managed_components/esp_mqtt/.gitignore new file mode 100644 index 0000000..2c4b72c --- /dev/null +++ b/managed_components/esp_mqtt/.gitignore @@ -0,0 +1,36 @@ +# Object files +*.o +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +build +examples/**/build +examples/**/sdkconfig* diff --git a/managed_components/esp_mqtt/.travis.yml b/managed_components/esp_mqtt/.travis.yml new file mode 100644 index 0000000..4affec1 --- /dev/null +++ b/managed_components/esp_mqtt/.travis.yml @@ -0,0 +1,65 @@ +sudo: false +language: bash +os: + - linux + +addons: + apt: + packages: + - gperf + - python + - python-serial + +before_install: + # Save path to the git respository + - PROJECT_PATH=$(pwd) + # Have to checkout a temp branch for later in tree reference + - git checkout -b temporary_ref_branch + - CI_COMMIT_SHA=$(git rev-parse HEAD) + # Test building with latest (stable == v3.3 for now) IDF + - LTS_IDF=release/v3.3 + +install: + # Install ESP32 toochain following steps as desribed + # in http://esp-idf.readthedocs.io/en/latest/linux-setup.html + # + # Get required packages - already done above, see addons: apt: packages: + # - sudo apt-get install git wget make libncurses-dev flex bison gperf python python-serial + # Prepare directory for the toolchain + - mkdir -p ~/esp + - cd ~/esp + # Download binary toolchain for the ESP32 + - wget https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz + - tar -xzf xtensa-esp32-elf-linux64-1.22.0-80-g6c4433a-5.2.0.tar.gz + # Get ESP-IDF from github (non-recursive to save time, later we update submodules for different versions) + - git clone https://github.com/espressif/esp-idf.git + # Set the path to ESP-IDF directory + - export IDF_PATH=~/esp/esp-idf + - python -m pip install --user -r $IDF_PATH/requirements.txt + # Setup build tool: xtensa-esp32-elf and idf.py + - export PATH=$PATH:$HOME/esp/xtensa-esp32-elf/bin:$IDF_PATH/tools + +script: + # Legacy build with IDF < 3.2 + - cd $IDF_PATH + - git checkout v3.1 && git submodule update --init --recursive + - cd $PROJECT_PATH + - ./ci/modify_for_legacy_idf.sh ${LTS_IDF} || true + - cd $PROJECT_PATH/examples/tcp + - make defconfig + - make -j4 + # Build with v3.3 (LTS) IDF + - cd $IDF_PATH + - git checkout ${LTS_IDF} && git submodule update --init --recursive + - cd $IDF_PATH/components/mqtt/esp-mqtt + - git remote add local $PROJECT_PATH/.git + - git fetch local + - git reset --hard $CI_COMMIT_SHA + - cd $IDF_PATH/examples/protocols/mqtt/tcp + - idf.py build + - cd $IDF_PATH/examples/protocols/mqtt/ssl + - idf.py build + - cd $IDF_PATH/examples/protocols/mqtt/ws + - idf.py build + - cd $IDF_PATH/examples/protocols/mqtt/wss + - idf.py build \ No newline at end of file diff --git a/managed_components/esp_mqtt/CMakeLists.txt b/managed_components/esp_mqtt/CMakeLists.txt new file mode 100644 index 0000000..8f86e9d --- /dev/null +++ b/managed_components/esp_mqtt/CMakeLists.txt @@ -0,0 +1,14 @@ +set(srcs mqtt_client.c lib/mqtt_msg.c lib/mqtt_outbox.c lib/platform_esp32_idf.c) + +if(CONFIG_MQTT_PROTOCOL_5) + list(APPEND srcs lib/mqtt5_msg.c mqtt5_client.c) +endif() + +list(TRANSFORM srcs PREPEND ${CMAKE_CURRENT_LIST_DIR}/) +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR}/include + PRIV_INCLUDE_DIRS ${CMAKE_CURRENT_LIST_DIR}/lib/include + REQUIRES esp_event tcp_transport + PRIV_REQUIRES esp_timer http_parser esp_hw_support heap + KCONFIG ${CMAKE_CURRENT_LIST_DIR}/Kconfig + ) diff --git a/managed_components/esp_mqtt/Kconfig b/managed_components/esp_mqtt/Kconfig new file mode 100644 index 0000000..d438858 --- /dev/null +++ b/managed_components/esp_mqtt/Kconfig @@ -0,0 +1,190 @@ +menu "ESP-MQTT Configurations" + + config MQTT_PROTOCOL_311 + bool "Enable MQTT protocol 3.1.1" + default y + help + If not, this library will use MQTT protocol 3.1 + + config MQTT_PROTOCOL_5 + bool "Enable MQTT protocol 5.0" + default n + help + If not, this library will not support MQTT 5.0 + + config MQTT_TRANSPORT_SSL + bool "Enable MQTT over SSL" + default y + help + Enable MQTT transport over SSL with mbedtls + + config MQTT_TRANSPORT_WEBSOCKET + bool "Enable MQTT over Websocket" + default y + depends on WS_TRANSPORT + help + Enable MQTT transport over Websocket. + + config MQTT_TRANSPORT_WEBSOCKET_SECURE + bool "Enable MQTT over Websocket Secure" + default y + depends on MQTT_TRANSPORT_WEBSOCKET + depends on MQTT_TRANSPORT_SSL + help + Enable MQTT transport over Websocket Secure. + + config MQTT_MSG_ID_INCREMENTAL + bool "Use Incremental Message Id" + default n + help + Set this to true for the message id (2.3.1 Packet Identifier) to be generated + as an incremental number rather then a random value (used by default) + + config MQTT_SKIP_PUBLISH_IF_DISCONNECTED + bool "Skip publish if disconnected" + default n + help + Set this to true to avoid publishing (enqueueing messages) if the client is disconnected. + The MQTT client tries to publish all messages by default, even in the disconnected state + (where the qos1 and qos2 packets are stored in the internal outbox to be published later) + The MQTT_SKIP_PUBLISH_IF_DISCONNECTED option allows applications to override this behaviour + and not enqueue publish packets in the disconnected state. + + config MQTT_REPORT_DELETED_MESSAGES + bool "Report deleted messages" + default n + help + Set this to true to post events for all messages which were deleted from the outbox + before being correctly sent and confirmed. + + config MQTT_USE_CUSTOM_CONFIG + bool "MQTT Using custom configurations" + default n + help + Custom MQTT configurations. + + config MQTT_TCP_DEFAULT_PORT + int "Default MQTT over TCP port" + default 1883 + depends on MQTT_USE_CUSTOM_CONFIG + help + Default MQTT over TCP port + + config MQTT_SSL_DEFAULT_PORT + int "Default MQTT over SSL port" + default 8883 + depends on MQTT_USE_CUSTOM_CONFIG + depends on MQTT_TRANSPORT_SSL + help + Default MQTT over SSL port + + config MQTT_WS_DEFAULT_PORT + int "Default MQTT over Websocket port" + default 80 + depends on MQTT_USE_CUSTOM_CONFIG + depends on MQTT_TRANSPORT_WEBSOCKET + help + Default MQTT over Websocket port + + config MQTT_WSS_DEFAULT_PORT + int "Default MQTT over Websocket Secure port" + default 443 + depends on MQTT_USE_CUSTOM_CONFIG + depends on MQTT_TRANSPORT_WEBSOCKET + depends on MQTT_TRANSPORT_WEBSOCKET_SECURE + help + Default MQTT over Websocket Secure port + + config MQTT_BUFFER_SIZE + int "Default MQTT Buffer Size" + default 1024 + depends on MQTT_USE_CUSTOM_CONFIG + help + This buffer size using for both transmit and receive + + config MQTT_TASK_STACK_SIZE + int "MQTT task stack size" + default 6144 + depends on MQTT_USE_CUSTOM_CONFIG + help + MQTT task stack size + + config MQTT_DISABLE_API_LOCKS + bool "Disable API locks" + default n + depends on MQTT_USE_CUSTOM_CONFIG + help + Default config employs API locks to protect internal structures. It is possible to disable + these locks if the user code doesn't access MQTT API from multiple concurrent tasks + + config MQTT_TASK_PRIORITY + int "MQTT task priority" + default 5 + depends on MQTT_USE_CUSTOM_CONFIG + help + MQTT task priority. Higher number denotes higher priority. + + config MQTT_POLL_READ_TIMEOUT_MS + int "MQTT transport poll read timeut" + default 1000 + depends on MQTT_USE_CUSTOM_CONFIG + help + Timeout when polling underlying transport for read. + + config MQTT_EVENT_QUEUE_SIZE + int "Number of queued events." + default 1 + depends on MQTT_USE_CUSTOM_CONFIG + help + A value higher than 1 enables multiple queued events. + + config MQTT_TASK_CORE_SELECTION_ENABLED + bool "Enable MQTT task core selection" + help + This will enable core selection + + choice MQTT_TASK_CORE_SELECTION + depends on MQTT_TASK_CORE_SELECTION_ENABLED + prompt "Core to use ?" + config MQTT_USE_CORE_0 + bool "Core 0" + config MQTT_USE_CORE_1 + bool "Core 1" + endchoice + + config MQTT_OUTBOX_DATA_ON_EXTERNAL_MEMORY + bool "Use external memory for outbox data" + default n + depends on MQTT_USE_CUSTOM_CONFIG + help + Set to true to use external memory for outbox data. + + config MQTT_CUSTOM_OUTBOX + bool "Enable custom outbox implementation" + default n + help + Set to true if a specific implementation of message outbox is needed (e.g. persistent outbox in NVM or + similar). + Note: Implementation of the custom outbox must be added to the mqtt component. These CMake commands + could be used to append the custom implementation to lib-mqtt sources: + idf_component_get_property(mqtt mqtt COMPONENT_LIB) + set_property(TARGET ${mqtt} PROPERTY SOURCES ${PROJECT_DIR}/custom_outbox.c APPEND) + + config MQTT_OUTBOX_EXPIRED_TIMEOUT_MS + int "Outbox message expired timeout[ms]" + default 30000 + depends on MQTT_USE_CUSTOM_CONFIG + help + Messages which stays in the outbox longer than this value before being published will be discarded. + + config MQTT_TOPIC_PRESENT_ALL_DATA_EVENTS + bool "Enable publish topic in all data events" + default n + depends on MQTT_USE_CUSTOM_CONFIG + help + Set to true to have publish topic in all data events. This changes the behaviour + when the message is bigger than the receive buffer size. The first event of the sequence + always have the topic. + Note: This will allocate memory to store the topic only in case of messge bigger than the buffer size. + +endmenu diff --git a/managed_components/esp_mqtt/LICENSE b/managed_components/esp_mqtt/LICENSE new file mode 100644 index 0000000..a623b53 --- /dev/null +++ b/managed_components/esp_mqtt/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 Tuan PM + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/managed_components/esp_mqtt/README.md b/managed_components/esp_mqtt/README.md new file mode 100644 index 0000000..1be44b6 --- /dev/null +++ b/managed_components/esp_mqtt/README.md @@ -0,0 +1,41 @@ +# ESP32 MQTT Library + +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/espressif/esp-mqtt/test-examples.yml?branch=master) +![License](https://img.shields.io/github/license/espressif/esp-mqtt) +![GitHub contributors](https://img.shields.io/github/contributors/espressif/esp-mqtt) + +## Features + +- Based on: +- Support MQTT over TCP, SSL with mbedtls, MQTT over Websocket, MQTT over Websocket Secure +- Easy to setup with URI +- Multiple instances (Multiple clients in one application) +- Support subscribing, publishing, authentication, will messages, keep alive pings and all 3 QoS levels (it should be a fully functional client). +- Support for MQTT 3.1.1 and 5.0 + +## How to use + +[ESP-MQTT](https://github.com/espressif/esp-mqtt) is a standard [ESP-IDF](https://github.com/espressif/esp-idf) component. +Please refer to instructions in [ESP-IDF](https://github.com/espressif/esp-idf) + +## Documentation + +- Please refer to the standard [ESP-IDF](https://github.com/espressif/esp-idf), documentation for the latest version: + +- Documentation of ESP-MQTT API: + +## License + +- MQTT Package - [Stephen Robinson - contiki-mqtt](https://github.com/esar/contiki-mqtt) +- Others [@tuanpmt](https://twitter.com/tuanpmt) +Apache License + +## Older IDF verisons + +For [ESP-IDF](https://github.com/espressif/esp-idf) versions prior to IDFv3.2, please clone as a component of [ESP-IDF](https://github.com/espressif/esp-idf): + +``` +git submodule add https://github.com/espressif/esp-mqtt.git components/espmqtt +``` + +and checkout the [ESP-MQTT_FOR_IDF_3.1](https://github.com/espressif/esp-mqtt/tree/ESP-MQTT_FOR_IDF_3.1) tag diff --git a/managed_components/esp_mqtt/ci/build-test-rules.yml b/managed_components/esp_mqtt/ci/build-test-rules.yml new file mode 100644 index 0000000..ebc2ec3 --- /dev/null +++ b/managed_components/esp_mqtt/ci/build-test-rules.yml @@ -0,0 +1,11 @@ +# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps + +examples/protocols: + enable: + - if: IDF_TARGET in ["esp32"] + +examples/protocols/mqtt/ssl_ds: + disable: + - if: SOC_DIG_SIGN_SUPPORTED != 1 + temporary: false + reason: DS not present diff --git a/managed_components/esp_mqtt/ci/build_examples.sh b/managed_components/esp_mqtt/ci/build_examples.sh new file mode 100644 index 0000000..9643ea2 --- /dev/null +++ b/managed_components/esp_mqtt/ci/build_examples.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +# build mqtt examples with make if $1=="make", with cmake otherwise +set -o errexit # Exit if command failed. + +if [ -z $IDF_PATH ] ; then + echo "Mandatory variables undefined" + exit 1; +fi; + +examples="tcp ssl ssl_mutual_auth ws wss" +for i in $examples; do + echo "Building MQTT example $i" + cd $IDF_PATH/examples/protocols/mqtt/$i + if [[ "$1" = "make" ]]; then + make defconfig + make -j 4 + else + rm -rf build sdkconfig + idf.py build + fi; +done diff --git a/managed_components/esp_mqtt/ci/idf_build_apps.toml b/managed_components/esp_mqtt/ci/idf_build_apps.toml new file mode 100644 index 0000000..c93a728 --- /dev/null +++ b/managed_components/esp_mqtt/ci/idf_build_apps.toml @@ -0,0 +1,2 @@ +build_dir = "$GITHUB_WORKSPACE/build_@t_@n" +config="sdkconfig.ci" diff --git a/managed_components/esp_mqtt/ci/modify_for_legacy_idf.sh b/managed_components/esp_mqtt/ci/modify_for_legacy_idf.sh new file mode 100644 index 0000000..8ea7e2b --- /dev/null +++ b/managed_components/esp_mqtt/ci/modify_for_legacy_idf.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +if [[ -z $1 ]]; then + LATEST_IDF=master +else + LATEST_IDF=$1 +fi + +# This snipped prepares environment for using esp-mqtt repository separately from idf -- legacy use before IDFv3.2 +# +esp_mqtt_path=`pwd` +mkdir -p ${esp_mqtt_path}/examples +pushd +cd $IDF_PATH +former_commit_id=`git rev-parse HEAD` +git checkout ${LATEST_IDF} + +for example in tcp; do + cp -r $IDF_PATH/examples/protocols/mqtt/${example} ${esp_mqtt_path}/examples + echo 'EXTRA_COMPONENT_DIRS += $(PROJECT_PATH)/../../../' > ${esp_mqtt_path}/examples/${example}/Makefile + cat $IDF_PATH/examples/protocols/mqtt/${example}/Makefile >> ${esp_mqtt_path}/examples/${example}/Makefile + echo "CONFIG_MQTT_TRANSPORT_SSL=" >> ${esp_mqtt_path}/examples/${example}/sdkconfig.defaults + echo "CONFIG_MQTT_TRANSPORT_WEBSOCKET=" >> ${esp_mqtt_path}/examples/${example}/sdkconfig.defaults +done + +cp -r $IDF_PATH/components/tcp_transport ${esp_mqtt_path}/.. +rm ${esp_mqtt_path}/../tcp_transport/transport_ssl.c +echo -e "#include \"esp_transport.h\"\nvoid esp_transport_ws_set_path(esp_transport_handle_t t, const char *path) {}" > ${esp_mqtt_path}/../tcp_transport/transport_ws.c + +cp $IDF_PATH/components/mqtt/Kconfig ${esp_mqtt_path} +sed 's/esp-mqtt/\./g' $IDF_PATH/components/mqtt/component.mk > ${esp_mqtt_path}/component.mk +git checkout $former_commit_id +popd \ No newline at end of file diff --git a/managed_components/esp_mqtt/ci/publish_connect_mqtt_qemu.yml b/managed_components/esp_mqtt/ci/publish_connect_mqtt_qemu.yml new file mode 100644 index 0000000..1a8cd50 --- /dev/null +++ b/managed_components/esp_mqtt/ci/publish_connect_mqtt_qemu.yml @@ -0,0 +1,7 @@ +CaseConfig: +- name: test_app_protocol_mqtt_publish_connect + overwrite: + dut: + class: ESP32QEMUDUT + package: ttfw_idf + diff --git a/managed_components/esp_mqtt/ci/set_idf.sh b/managed_components/esp_mqtt/ci/set_idf.sh new file mode 100644 index 0000000..1bd7ab8 --- /dev/null +++ b/managed_components/esp_mqtt/ci/set_idf.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# sets up the IDF repo incl submodules with specified version as $1 +set -o errexit # Exit if command failed. + +if [ -z $IDF_PATH ] || [ -z $MQTT_PATH ] || [ -z $1 ] ; then + echo "Mandatory variables undefined" + exit 1; +fi; + +echo "Checking out IDF version $1" +cd $IDF_PATH +# Cleans out the untracked files in the repo, so the next "git checkout" doesn't fail +git clean -f +git checkout $1 +# Removes the mqtt submodule, so the next submodule update doesn't fail +rm -rf $IDF_PATH/components/mqtt/esp-mqtt +git submodule update --init --recursive + diff --git a/managed_components/esp_mqtt/ci/set_mqtt.sh b/managed_components/esp_mqtt/ci/set_mqtt.sh new file mode 100644 index 0000000..8c70b66 --- /dev/null +++ b/managed_components/esp_mqtt/ci/set_mqtt.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# sets the mqtt in IDF tree as a submodule with the version specified as $1 +set -o errexit # Exit if command failed. + +if [ -z $IDF_PATH ] || [ -z $MQTT_PATH ] || [ -z $1 ] ; then + echo "Mandatory variables undefined" + exit 1; +fi; + +echo "Checking out MQTT version to $1" +# exchange remotes of mqtt submodules with plain copy +cd $IDF_PATH/components/mqtt/esp-mqtt +rm -rf .git # removes the actual IDF referenced version +cp -r $MQTT_PATH/.git . # replaces with the MQTT_PATH (CI checked tree) +git reset --hard $1 # sets the requested version diff --git a/managed_components/esp_mqtt/host_test/CMakeLists.txt b/managed_components/esp_mqtt/host_test/CMakeLists.txt new file mode 100644 index 0000000..b2b4aba --- /dev/null +++ b/managed_components/esp_mqtt/host_test/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS mqtt main) +list(APPEND EXTRA_COMPONENT_DIRS + "$ENV{IDF_PATH}/tools/mocks/esp_hw_support/" + "$ENV{IDF_PATH}/tools/mocks/freertos/" + "$ENV{IDF_PATH}/tools/mocks/esp_timer/" + "$ENV{IDF_PATH}/tools/mocks/esp_event/" + "$ENV{IDF_PATH}/tools/mocks/lwip/" + "$ENV{IDF_PATH}/tools/mocks/esp-tls/" + "$ENV{IDF_PATH}/tools/mocks/http_parser/" + "$ENV{IDF_PATH}/tools/mocks/tcp_transport/") + +project(host_mqtt_client_test) diff --git a/managed_components/esp_mqtt/host_test/README.md b/managed_components/esp_mqtt/host_test/README.md new file mode 100644 index 0000000..a62087a --- /dev/null +++ b/managed_components/esp_mqtt/host_test/README.md @@ -0,0 +1,30 @@ +| Supported Targets | Linux | +| ----------------- | ----- | + +# Description + +This directory contains test code for the mqtt client that runs on host. + +Tests are written using [Catch2](https://github.com/catchorg/Catch2) test framework + +# Build + +Tests build regularly like an idf project. + +``` +idf.py build +``` + +# Run + +The build produces an executable in the build folder. + +Just run: + +``` +./build/host_mqtt_client_test.elf +``` + +The test executable have some options provided by the test framework. + + diff --git a/managed_components/esp_mqtt/host_test/main/CMakeLists.txt b/managed_components/esp_mqtt/host_test/main/CMakeLists.txt new file mode 100644 index 0000000..9e5347b --- /dev/null +++ b/managed_components/esp_mqtt/host_test/main/CMakeLists.txt @@ -0,0 +1,21 @@ +idf_component_register(SRCS "test_mqtt_client.cpp" + REQUIRES cmock mqtt esp_timer esp_hw_support http_parser log + WHOLE_ARCHIVE) + +target_compile_options(${COMPONENT_LIB} PUBLIC -fsanitize=address -fconcepts) +target_link_options(${COMPONENT_LIB} PUBLIC -fsanitize=address) +target_link_libraries(${COMPONENT_LIB} PUBLIC Catch2::Catch2WithMain) + +idf_component_get_property(mqtt mqtt COMPONENT_LIB) +target_compile_definitions(${mqtt} PRIVATE SOC_WIFI_SUPPORTED=1) +target_compile_options(${mqtt} PUBLIC -fsanitize=address -fconcepts) +target_link_options(${mqtt} PUBLIC -fsanitize=address) + +if(CONFIG_GCOV_ENABLED) + target_compile_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage) + target_link_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage) + + idf_component_get_property(mqtt mqtt COMPONENT_LIB) + target_compile_options(${mqtt} PUBLIC --coverage -fprofile-arcs -ftest-coverage) + target_link_options(${mqtt} PUBLIC --coverage -fprofile-arcs -ftest-coverage) +endif() diff --git a/managed_components/esp_mqtt/host_test/main/Kconfig b/managed_components/esp_mqtt/host_test/main/Kconfig new file mode 100644 index 0000000..6ef29cd --- /dev/null +++ b/managed_components/esp_mqtt/host_test/main/Kconfig @@ -0,0 +1,9 @@ +menu "Host-test config" + + config GCOV_ENABLED + bool "Coverage analyzer" + default n + help + Enables coverage analyzing for host tests. + +endmenu diff --git a/managed_components/esp_mqtt/host_test/main/idf_component.yml b/managed_components/esp_mqtt/host_test/main/idf_component.yml new file mode 100644 index 0000000..6efd693 --- /dev/null +++ b/managed_components/esp_mqtt/host_test/main/idf_component.yml @@ -0,0 +1,6 @@ +## IDF Component Manager Manifest File +dependencies: + espressif/catch2: "^3.5.2" + ## Required IDF version + idf: + version: ">=5.0.0" diff --git a/managed_components/esp_mqtt/host_test/main/test_mqtt_client.cpp b/managed_components/esp_mqtt/host_test/main/test_mqtt_client.cpp new file mode 100644 index 0000000..8963df0 --- /dev/null +++ b/managed_components/esp_mqtt/host_test/main/test_mqtt_client.cpp @@ -0,0 +1,168 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include "esp_transport.h" +#include + +#include "mqtt_client.h" +extern "C" { +#include "Mockesp_event.h" +#include "Mockesp_mac.h" +#include "Mockesp_transport.h" +#include "Mockesp_transport_ssl.h" +#include "Mockesp_transport_tcp.h" +#include "Mockesp_transport_ws.h" +#include "Mockevent_groups.h" +#include "Mockhttp_parser.h" +#include "Mockqueue.h" +#include "Mocktask.h" +#if __has_include ("Mockidf_additions.h") +/* Some functions were moved from "task.h" to "idf_additions.h" */ +#include "Mockidf_additions.h" +#endif +#include "Mockesp_timer.h" + + /* + * The following functions are not directly called but the generation of them + * from cmock is broken, so we need to define them here. + */ + esp_err_t esp_tls_get_and_clear_last_error(esp_tls_error_handle_t h, int *esp_tls_code, int *esp_tls_flags) + { + return ESP_OK; + } +} + +auto random_string(std::size_t n) +{ + static constexpr std::string_view char_set = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456790"; + std::string str; + std::sample(char_set.begin(), char_set.end(), std::back_inserter(str), n, + std::mt19937 {std::random_device{}()}); + return str; +} + +using unique_mqtt_client = std::unique_ptr < std::remove_pointer_t, decltype([](esp_mqtt_client_handle_t client) +{ + esp_mqtt_client_destroy(client); +}) >; + +SCENARIO("MQTT Client Operation") +{ + // Set expectations for the mocked calls. + int mtx = 0; + int transport_list = 0; + int transport = 0; + int event_group = 0; + uint8_t mac[] = {0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55}; + esp_timer_get_time_IgnoreAndReturn(0); + xQueueTakeMutexRecursive_IgnoreAndReturn(true); + xQueueGiveMutexRecursive_IgnoreAndReturn(true); + xQueueCreateMutex_ExpectAnyArgsAndReturn( + reinterpret_cast(&mtx)); + xEventGroupCreate_IgnoreAndReturn(reinterpret_cast(&event_group)); + esp_transport_list_init_IgnoreAndReturn(reinterpret_cast(&transport_list)); + esp_transport_tcp_init_IgnoreAndReturn(reinterpret_cast(&transport)); + esp_transport_ssl_init_IgnoreAndReturn(reinterpret_cast(&transport)); + esp_transport_ws_init_IgnoreAndReturn(reinterpret_cast(&transport)); + esp_transport_ws_set_subprotocol_IgnoreAndReturn(ESP_OK); + esp_transport_list_add_IgnoreAndReturn(ESP_OK); + esp_transport_set_default_port_IgnoreAndReturn(ESP_OK); + http_parser_url_init_Ignore(); + esp_event_loop_create_IgnoreAndReturn(ESP_OK); + esp_read_mac_IgnoreAndReturn(ESP_OK); + esp_read_mac_ReturnThruPtr_mac(mac); + esp_transport_list_destroy_IgnoreAndReturn(ESP_OK); + esp_transport_destroy_IgnoreAndReturn(ESP_OK); + vEventGroupDelete_Ignore(); + vQueueDelete_Ignore(); + GIVEN("An a minimal config") { + esp_mqtt_client_config_t config{}; + config.broker.address.uri = "mqtt://1.1.1.1"; + struct http_parser_url ret_uri = { + .field_set = 1 | (1 << 1), + .port = 0, + .field_data = { { 0, 4 } /*mqtt*/, { 7, 1 } } // at least *scheme* and *host* + }; + http_parser_parse_url_ExpectAnyArgsAndReturn(0); + http_parser_parse_url_ReturnThruPtr_u(&ret_uri); + xTaskCreatePinnedToCore_ExpectAnyArgsAndReturn(pdTRUE); + SECTION("Client with minimal config") { + auto client = unique_mqtt_client{esp_mqtt_client_init(&config)}; + REQUIRE(client != nullptr); + SECTION("User will set a new uri") { + struct http_parser_url ret_uri = { + .field_set = 1, + .port = 0, + .field_data = { { 0, 1} } + }; + SECTION("User set a correct URI") { + http_parser_parse_url_StopIgnore(); + http_parser_parse_url_ExpectAnyArgsAndReturn(0); + http_parser_parse_url_ReturnThruPtr_u(&ret_uri); + auto res = esp_mqtt_client_set_uri(client.get(), " "); + REQUIRE(res == ESP_OK); + } + SECTION("Incorrect URI from user") { + http_parser_parse_url_StopIgnore(); + http_parser_parse_url_ExpectAnyArgsAndReturn(1); + http_parser_parse_url_ReturnThruPtr_u(&ret_uri); + auto res = esp_mqtt_client_set_uri(client.get(), " "); + REQUIRE(res == ESP_FAIL); + } + } + SECTION("User set interface to use"){ + http_parser_parse_url_ExpectAnyArgsAndReturn(0); + http_parser_parse_url_ReturnThruPtr_u(&ret_uri); + struct ifreq if_name = {.ifr_ifrn = {"custom"}}; + config.network.if_name = &if_name; + SECTION("Client is not started"){ + REQUIRE(esp_mqtt_set_config(client.get(), &config)== ESP_OK); + } + } + SECTION("After Start Client Is Cleanly destroyed") { + REQUIRE(esp_mqtt_client_start(client.get()) == ESP_OK); + // Only need to start the client, destroy is called automatically at the end of + // scope + } + } + SECTION("Client with all allocating configuration set") { + auto host = random_string(20); + auto path = random_string(10); + auto username = random_string(10); + auto client_id = random_string(10); + auto password = random_string(10); + auto lw_topic = random_string(10); + auto lw_msg = random_string(10); + + config.broker = {.address = { + .hostname = host.data(), + .path = path.data() + } + }; + config.credentials = { + .username = username.data(), + .client_id = client_id.data(), + .authentication = { + .password = password.data() + } + }; + config.session = { + .last_will { + .topic = lw_topic.data(), + .msg = lw_msg.data() + } + }; + auto client = unique_mqtt_client{esp_mqtt_client_init(&config)}; + REQUIRE(client != nullptr); + + } + } +} + diff --git a/managed_components/esp_mqtt/host_test/mocks/include/sys/queue.h b/managed_components/esp_mqtt/host_test/mocks/include/sys/queue.h new file mode 100644 index 0000000..1afffb9 --- /dev/null +++ b/managed_components/esp_mqtt/host_test/mocks/include/sys/queue.h @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 1991-1993 The Regents of the University of California + * + * SPDX-License-Identifier: BSD-3-Clause + */ +#pragma once + +/* Implementation from BSD headers*/ +#define QMD_SAVELINK(name, link) void **name = (void *)&(link) +#define TRASHIT(x) do {(x) = (void *)-1;} while (0) +#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) + +#define STAILQ_FIRST(head) ((head)->stqh_first) + +#define STAILQ_HEAD(name, type) \ +struct name { \ + struct type *stqh_first;/* first element */ \ + struct type **stqh_last;/* addr of last next element */ \ +} + +#define STAILQ_ENTRY(type) \ +struct { \ + struct type *stqe_next; /* next element */ \ +} + +#define STAILQ_INSERT_TAIL(head, elm, field) do { \ + STAILQ_NEXT((elm), field) = NULL; \ + *(head)->stqh_last = (elm); \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ +} while (0) + +#define STAILQ_INIT(head) do { \ + STAILQ_FIRST((head)) = NULL; \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +#define STAILQ_FOREACH(var, head, field) \ + for((var) = STAILQ_FIRST((head)); \ + (var); \ + (var) = STAILQ_NEXT((var), field)) + +#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ + for ((var) = STAILQ_FIRST((head)); \ + (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ + (var) = (tvar)) + +#define STAILQ_REMOVE_AFTER(head, elm, field) do { \ + if ((STAILQ_NEXT(elm, field) = \ + STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \ + (head)->stqh_last = &STAILQ_NEXT((elm), field); \ +} while (0) + +#define STAILQ_REMOVE_HEAD(head, field) do { \ + if ((STAILQ_FIRST((head)) = \ + STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ + (head)->stqh_last = &STAILQ_FIRST((head)); \ +} while (0) + +#define STAILQ_REMOVE(head, elm, type, field) do { \ + QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \ + if (STAILQ_FIRST((head)) == (elm)) { \ + STAILQ_REMOVE_HEAD((head), field); \ + } \ + else { \ + struct type *curelm = STAILQ_FIRST((head)); \ + while (STAILQ_NEXT(curelm, field) != (elm)) \ + curelm = STAILQ_NEXT(curelm, field); \ + STAILQ_REMOVE_AFTER(head, curelm, field); \ + } \ + TRASHIT(*oldnext); \ +} while (0) diff --git a/managed_components/esp_mqtt/host_test/sdkconfig.ci.coverage b/managed_components/esp_mqtt/host_test/sdkconfig.ci.coverage new file mode 100644 index 0000000..3c490c0 --- /dev/null +++ b/managed_components/esp_mqtt/host_test/sdkconfig.ci.coverage @@ -0,0 +1 @@ +CONFIG_GCOV_ENABLED=y diff --git a/managed_components/esp_mqtt/host_test/sdkconfig.defaults b/managed_components/esp_mqtt/host_test/sdkconfig.defaults new file mode 100644 index 0000000..c126429 --- /dev/null +++ b/managed_components/esp_mqtt/host_test/sdkconfig.defaults @@ -0,0 +1,6 @@ +CONFIG_IDF_TARGET="linux" +CONFIG_COMPILER_CXX_EXCEPTIONS=y +CONFIG_COMPILER_CXX_RTTI=y +CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0 +CONFIG_COMPILER_STACK_CHECK_NONE=y +CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n diff --git a/managed_components/esp_mqtt/idf_component.yml b/managed_components/esp_mqtt/idf_component.yml new file mode 100644 index 0000000..23cec31 --- /dev/null +++ b/managed_components/esp_mqtt/idf_component.yml @@ -0,0 +1,5 @@ +version: "1.0.0" +description: esp-mqtt +dependencies: + idf: + version: ">=5.0" diff --git a/managed_components/esp_mqtt/include/mqtt5_client.h b/managed_components/esp_mqtt/include/mqtt5_client.h new file mode 100644 index 0000000..25254c4 --- /dev/null +++ b/managed_components/esp_mqtt/include/mqtt5_client.h @@ -0,0 +1,286 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _MQTT5_CLIENT_H_ +#define _MQTT5_CLIENT_H_ + +#include "mqtt_client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct esp_mqtt_client *esp_mqtt5_client_handle_t; + +/** + * MQTT5 protocol error reason code, more details refer to MQTT5 protocol document section 2.4 + */ +typedef enum mqtt5_error_reason_code_t { + MQTT5_UNSPECIFIED_ERROR = 0x80, + MQTT5_MALFORMED_PACKET = 0x81, + MQTT5_PROTOCOL_ERROR = 0x82, + MQTT5_IMPLEMENT_SPECIFIC_ERROR = 0x83, + MQTT5_UNSUPPORTED_PROTOCOL_VER = 0x84, + MQTT5_INVAILD_CLIENT_ID __attribute__((deprecated)) = 0x85, + MQTT5_INVALID_CLIENT_ID = 0x85, + MQTT5_BAD_USERNAME_OR_PWD = 0x86, + MQTT5_NOT_AUTHORIZED = 0x87, + MQTT5_SERVER_UNAVAILABLE = 0x88, + MQTT5_SERVER_BUSY = 0x89, + MQTT5_BANNED = 0x8A, + MQTT5_SERVER_SHUTTING_DOWN = 0x8B, + MQTT5_BAD_AUTH_METHOD = 0x8C, + MQTT5_KEEP_ALIVE_TIMEOUT = 0x8D, + MQTT5_SESSION_TAKEN_OVER = 0x8E, + MQTT5_TOPIC_FILTER_INVAILD __attribute__((deprecated)) = 0x8F, + MQTT5_TOPIC_FILTER_INVALID = 0x8F, + MQTT5_TOPIC_NAME_INVAILD __attribute__((deprecated)) = 0x90, + MQTT5_TOPIC_NAME_INVALID = 0x90, + MQTT5_PACKET_IDENTIFIER_IN_USE = 0x91, + MQTT5_PACKET_IDENTIFIER_NOT_FOUND = 0x92, + MQTT5_RECEIVE_MAXIMUM_EXCEEDED = 0x93, + MQTT5_TOPIC_ALIAS_INVAILD __attribute__((deprecated)) = 0x94, + MQTT5_TOPIC_ALIAS_INVALID = 0x94, + MQTT5_PACKET_TOO_LARGE = 0x95, + MQTT5_MESSAGE_RATE_TOO_HIGH = 0x96, + MQTT5_QUOTA_EXCEEDED = 0x97, + MQTT5_ADMINISTRATIVE_ACTION = 0x98, + MQTT5_PAYLOAD_FORMAT_INVAILD __attribute__((deprecated)) = 0x99, + MQTT5_PAYLOAD_FORMAT_INVALID = 0x99, + MQTT5_RETAIN_NOT_SUPPORT = 0x9A, + MQTT5_QOS_NOT_SUPPORT = 0x9B, + MQTT5_USE_ANOTHER_SERVER = 0x9C, + MQTT5_SERVER_MOVED = 0x9D, + MQTT5_SHARED_SUBSCR_NOT_SUPPORTED = 0x9E, + MQTT5_CONNECTION_RATE_EXCEEDED = 0x9F, + MQTT5_MAXIMUM_CONNECT_TIME = 0xA0, + MQTT5_SUBSCRIBE_IDENTIFIER_NOT_SUPPORT = 0xA1, + MQTT5_WILDCARD_SUBSCRIBE_NOT_SUPPORT = 0xA2, +} esp_mqtt5_error_reason_code_t; + +/** + * MQTT5 user property handle + */ +typedef struct mqtt5_user_property_list_t *mqtt5_user_property_handle_t; + +/** + * MQTT5 protocol connect properties and will properties configuration, more details refer to MQTT5 protocol document section 3.1.2.11 and 3.3.2.3 + */ +typedef struct { + uint32_t session_expiry_interval; /*!< The interval time of session expiry */ + uint32_t maximum_packet_size; /*!< The maximum packet size that we can receive */ + uint16_t receive_maximum; /*!< The maximum pakcket count that we process concurrently */ + uint16_t topic_alias_maximum; /*!< The maximum topic alias that we support */ + bool request_resp_info; /*!< This value to request Server to return Response information */ + bool request_problem_info; /*!< This value to indicate whether the reason string or user properties are sent in case of failures */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ + uint32_t will_delay_interval; /*!< The time interval that server delays publishing will message */ + uint32_t message_expiry_interval; /*!< The time interval that message expiry */ + bool payload_format_indicator; /*!< This value is to indicator will message payload format */ + const char *content_type; /*!< This value is to indicator will message content type, use a MIME content type string */ + const char *response_topic; /*!< Topic name for a response message */ + const char *correlation_data; /*!< Binary data for receiver to match the response message */ + uint16_t correlation_data_len; /*!< The length of correlation data */ + mqtt5_user_property_handle_t will_user_property; /*!< The handle for will message user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_connection_property_config_t; + +/** + * MQTT5 protocol publish properties configuration, more details refer to MQTT5 protocol document section 3.3.2.3 + */ +typedef struct { + bool payload_format_indicator; /*!< This value is to indicator publish message payload format */ + uint32_t message_expiry_interval; /*!< The time interval that message expiry */ + uint16_t topic_alias; /*!< An interger value to identify the topic instead of using topic name string */ + const char *response_topic; /*!< Topic name for a response message */ + const char *correlation_data; /*!< Binary data for receiver to match the response message */ + uint16_t correlation_data_len; /*!< The length of correlation data */ + const char *content_type; /*!< This value is to indicator publish message content type, use a MIME content type string */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_publish_property_config_t; + +/** + * MQTT5 protocol subscribe properties configuration, more details refer to MQTT5 protocol document section 3.8.2.1 + */ +typedef struct { + uint16_t subscribe_id; /*!< A variable byte represents the identifier of the subscription */ + bool no_local_flag; /*!< Subscription Option to allow that server publish message that client sent */ + bool retain_as_published_flag; /*!< Subscription Option to keep the retain flag as published option */ + uint8_t retain_handle; /*!< Subscription Option to handle retain option */ + bool is_share_subscribe; /*!< Whether subscribe is a shared subscription */ + const char *share_name; /*!< The name of shared subscription which is a part of $share/{share_name}/{topic} */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_subscribe_property_config_t; + +/** + * MQTT5 protocol unsubscribe properties configuration, more details refer to MQTT5 protocol document section 3.10.2.1 + */ +typedef struct { + bool is_share_subscribe; /*!< Whether subscribe is a shared subscription */ + const char *share_name; /*!< The name of shared subscription which is a part of $share/{share_name}/{topic} */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_unsubscribe_property_config_t; + +/** + * MQTT5 protocol disconnect properties configuration, more details refer to MQTT5 protocol document section 3.14.2.2 + */ +typedef struct { + uint32_t session_expiry_interval; /*!< The interval time of session expiry */ + uint8_t disconnect_reason; /*!< The reason that connection disconnet, refer to mqtt5_error_reason_code */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_set_user_property to set it */ +} esp_mqtt5_disconnect_property_config_t; + +/** + * MQTT5 protocol for event properties + */ +typedef struct { + bool payload_format_indicator; /*!< Payload format of the message */ + char *response_topic; /*!< Response topic of the message */ + int response_topic_len; /*!< Response topic length of the message */ + char *correlation_data; /*!< Correlation data of the message */ + uint16_t correlation_data_len; /*!< Correlation data length of the message */ + char *content_type; /*!< Content type of the message */ + int content_type_len; /*!< Content type length of the message */ + uint16_t subscribe_id; /*!< Subscription identifier of the message */ + mqtt5_user_property_handle_t user_property; /*!< The handle for user property, call function esp_mqtt5_client_delete_user_property to free the memory */ +} esp_mqtt5_event_property_t; + +/** + * MQTT5 protocol for user property + */ +typedef struct { + const char *key; /*!< Item key name */ + const char *value; /*!< Item value string */ +} esp_mqtt5_user_property_item_t; + +/** + * @brief Set MQTT5 client connect property configuration + * + * @param client mqtt client handle + * @param connect_property connect property + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_connect_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_connection_property_config_t *connect_property); + +/** + * @brief Set MQTT5 client publish property configuration + * + * This API will not store the publish property, it is one-time configuration. + * Before call `esp_mqtt_client_publish` to publish data, call this API to set publish property if have + * + * @param client mqtt client handle + * @param property publish property + * + * @return ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_publish_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_publish_property_config_t *property); + +/** + * @brief Set MQTT5 client subscribe property configuration + * + * This API will not store the subscribe property, it is one-time configuration. + * Before call `esp_mqtt_client_subscribe` to subscribe topic, call this API to set subscribe property if have + * + * @param client mqtt client handle + * @param property subscribe property + * + * @return ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_subscribe_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_subscribe_property_config_t *property); + +/** + * @brief Set MQTT5 client unsubscribe property configuration + * + * This API will not store the unsubscribe property, it is one-time configuration. + * Before call `esp_mqtt_client_unsubscribe` to unsubscribe topic, call this API to set unsubscribe property if have + * + * @param client mqtt client handle + * @param property unsubscribe property + * + * @return ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_unsubscribe_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_unsubscribe_property_config_t *property); + +/** + * @brief Set MQTT5 client disconnect property configuration + * + * This API will not store the disconnect property, it is one-time configuration. + * Before call `esp_mqtt_client_disconnect` to disconnect connection, call this API to set disconnect property if have + * + * @param client mqtt client handle + * @param property disconnect property + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_disconnect_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_disconnect_property_config_t *property); + +/** + * @brief Set MQTT5 client user property configuration + * + * This API will allocate memory for user_property, please DO NOT forget `call esp_mqtt5_client_delete_user_property` + * after you use it. + * Before publish data, subscribe topic, unsubscribe, etc, call this API to set user property if have + * + * @param user_property user_property handle + * @param item array of user property data (eg. {{"var","val"},{"other","2"}}) + * @param item_num number of items in user property data + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_set_user_property(mqtt5_user_property_handle_t *user_property, esp_mqtt5_user_property_item_t item[], uint8_t item_num); + +/** + * @brief Get MQTT5 client user property + * + * @param user_property user_property handle + * @param item point that store user property data + * @param item_num number of items in user property data + * + * This API can use with `esp_mqtt5_client_get_user_property_count` to get list count of user property. + * And malloc number of count item array memory to store the user property data. + * Please DO NOT forget the item memory, key and value point in item memory when get user property data successfully. + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_FAIL on fail + * ESP_OK on success + */ +esp_err_t esp_mqtt5_client_get_user_property(mqtt5_user_property_handle_t user_property, esp_mqtt5_user_property_item_t *item, uint8_t *item_num); + +/** + * @brief Get MQTT5 client user property list count + * + * @param user_property user_property handle + * @return user property list count + */ +uint8_t esp_mqtt5_client_get_user_property_count(mqtt5_user_property_handle_t user_property); + +/** + * @brief Free the user property list + * + * @param user_property user_property handle + * + * This API will free the memory in user property list and free user_property itself + */ +void esp_mqtt5_client_delete_user_property(mqtt5_user_property_handle_t user_property); +#ifdef __cplusplus +} +#endif //__cplusplus + +#endif diff --git a/managed_components/esp_mqtt/include/mqtt_client.h b/managed_components/esp_mqtt/include/mqtt_client.h new file mode 100644 index 0000000..0b4df87 --- /dev/null +++ b/managed_components/esp_mqtt/include/mqtt_client.h @@ -0,0 +1,702 @@ +/* + * This file is subject to the terms and conditions defined in + * file 'LICENSE', which is part of this source code package. + * Tuan PM + */ + +#ifndef _MQTT_CLIENT_H_ +#define _MQTT_CLIENT_H_ + +#include +#include +#include +#include "esp_err.h" +#include "esp_event.h" +#include "esp_transport.h" +#ifdef CONFIG_MQTT_PROTOCOL_5 +#include "mqtt5_client.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ESP_EVENT_DECLARE_BASE +// Define event loop types if macros not available +typedef void *esp_event_loop_handle_t; +typedef void *esp_event_handler_t; +#endif + +typedef struct esp_mqtt_client *esp_mqtt_client_handle_t; + +#define MQTT_OVER_TCP_SCHEME "mqtt" +#define MQTT_OVER_SSL_SCHEME "mqtts" +#define MQTT_OVER_WS_SCHEME "ws" +#define MQTT_OVER_WSS_SCHEME "wss" + +/** + * @brief *MQTT* event types. + * + * User event handler receives context data in `esp_mqtt_event_t` structure with + * - client - *MQTT* client handle + * - various other data depending on event type + * + */ +typedef enum esp_mqtt_event_id_t { + MQTT_EVENT_ANY = -1, + MQTT_EVENT_ERROR = + 0, /*!< on error event, additional context: connection return code, error + handle from esp_tls (if supported) */ + MQTT_EVENT_CONNECTED, /*!< connected event, additional context: + session_present flag */ + MQTT_EVENT_DISCONNECTED, /*!< disconnected event */ + MQTT_EVENT_SUBSCRIBED, /*!< subscribed event, additional context: + - msg_id message id + - error_handle `error_type` in case subscribing failed + - data pointer to broker response, check for errors. + - data_len length of the data for this + event + */ + MQTT_EVENT_UNSUBSCRIBED, /*!< unsubscribed event, additional context: msg_id */ + MQTT_EVENT_PUBLISHED, /*!< published event, additional context: msg_id */ + MQTT_EVENT_DATA, /*!< data event, additional context: + - msg_id message id + - topic pointer to the received topic + - topic_len length of the topic + - data pointer to the received data + - data_len length of the data for this event + - current_data_offset offset of the current data for + this event + - total_data_len total length of the data received + - retain retain flag of the message + - qos QoS level of the message + - dup dup flag of the message + Note: Multiple MQTT_EVENT_DATA could be fired for one + message, if it is longer than internal buffer. In that + case only first event contains topic pointer and length, + other contain data only with current data length and + current data offset updating. + */ + MQTT_EVENT_BEFORE_CONNECT, /*!< The event occurs before connecting */ + MQTT_EVENT_DELETED, /*!< Notification on delete of one message from the + internal outbox, if the message couldn't have been sent + or acknowledged before expiring defined in + OUTBOX_EXPIRED_TIMEOUT_MS. (events are not posted upon + deletion of successfully acknowledged messages) + - This event id is posted only if + MQTT_REPORT_DELETED_MESSAGES==1 + - Additional context: msg_id (id of the deleted + message, always 0 for QoS = 0 messages). + */ + MQTT_USER_EVENT, /*!< Custom event used to queue tasks into mqtt event handler + All fields from the esp_mqtt_event_t type could be used to pass + an additional context data to the handler. + */ +} esp_mqtt_event_id_t; + +/** + * *MQTT* connection error codes propagated via ERROR event + */ +typedef enum esp_mqtt_connect_return_code_t { + MQTT_CONNECTION_ACCEPTED = 0, /*!< Connection accepted */ + MQTT_CONNECTION_REFUSE_PROTOCOL, /*!< *MQTT* connection refused reason: Wrong + protocol */ + MQTT_CONNECTION_REFUSE_ID_REJECTED, /*!< *MQTT* connection refused reason: ID + rejected */ + MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE, /*!< *MQTT* connection refused + reason: Server unavailable */ + MQTT_CONNECTION_REFUSE_BAD_USERNAME, /*!< *MQTT* connection refused reason: + Wrong user */ + MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED /*!< *MQTT* connection refused reason: + Wrong username or password */ +} esp_mqtt_connect_return_code_t; + +/** + * *MQTT* connection error codes propagated via ERROR event + */ +typedef enum esp_mqtt_error_type_t { + MQTT_ERROR_TYPE_NONE = 0, + MQTT_ERROR_TYPE_TCP_TRANSPORT, + MQTT_ERROR_TYPE_CONNECTION_REFUSED, + MQTT_ERROR_TYPE_SUBSCRIBE_FAILED +} esp_mqtt_error_type_t; + +/** + * MQTT_ERROR_TYPE_TCP_TRANSPORT error type hold all sorts of transport layer + * errors, including ESP-TLS error, but in the past only the errors from + * MQTT_ERROR_TYPE_ESP_TLS layer were reported, so the ESP-TLS error type is + * re-defined here for backward compatibility + */ +#define MQTT_ERROR_TYPE_ESP_TLS MQTT_ERROR_TYPE_TCP_TRANSPORT + +typedef enum esp_mqtt_transport_t { + MQTT_TRANSPORT_UNKNOWN = 0x0, + MQTT_TRANSPORT_OVER_TCP, /*!< *MQTT* over TCP, using scheme: ``MQTT`` */ + MQTT_TRANSPORT_OVER_SSL, /*!< *MQTT* over SSL, using scheme: ``MQTTS`` */ + MQTT_TRANSPORT_OVER_WS, /*!< *MQTT* over Websocket, using scheme:: ``ws`` */ + MQTT_TRANSPORT_OVER_WSS /*!< *MQTT* over Websocket Secure, using scheme: + ``wss`` */ +} esp_mqtt_transport_t; + +/** + * *MQTT* protocol version used for connection + */ +typedef enum esp_mqtt_protocol_ver_t { + MQTT_PROTOCOL_UNDEFINED = 0, + MQTT_PROTOCOL_V_3_1, + MQTT_PROTOCOL_V_3_1_1, + MQTT_PROTOCOL_V_5, +} esp_mqtt_protocol_ver_t; + +/** + * @brief *MQTT* error code structure to be passed as a contextual information + * into ERROR event + * + * Important: This structure extends `esp_tls_last_error` error structure and is + * backward compatible with it (so might be down-casted and treated as + * `esp_tls_last_error` error, but recommended to update applications if used + * this way previously) + * + * Use this structure directly checking error_type first and then appropriate + * error code depending on the source of the error: + * + * | error_type | related member variables | note | + * | MQTT_ERROR_TYPE_TCP_TRANSPORT | esp_tls_last_esp_err, esp_tls_stack_err, + * esp_tls_cert_verify_flags, sock_errno | Error reported from + * tcp_transport/esp-tls | | MQTT_ERROR_TYPE_CONNECTION_REFUSED | + * connect_return_code | Internal error reported from *MQTT* broker on + * connection | + */ +typedef struct esp_mqtt_error_codes { + /* compatible portion of the struct corresponding to struct esp_tls_last_error + */ + esp_err_t esp_tls_last_esp_err; /*!< last esp_err code reported from esp-tls + component */ + int esp_tls_stack_err; /*!< tls specific error code reported from underlying + tls stack */ + int esp_tls_cert_verify_flags; /*!< tls flags reported from underlying tls + stack during certificate verification */ + /* esp-mqtt specific structure extension */ + esp_mqtt_error_type_t + error_type; /*!< error type referring to the source of the error */ + esp_mqtt_connect_return_code_t + connect_return_code; /*!< connection refused error code reported from + *MQTT* broker on connection */ +#ifdef CONFIG_MQTT_PROTOCOL_5 + esp_mqtt5_error_reason_code_t + disconnect_return_code; /*!< disconnection reason code reported from + *MQTT* broker on disconnection */ +#endif + /* tcp_transport extension */ + int esp_transport_sock_errno; /*!< errno from the underlying socket */ + +} esp_mqtt_error_codes_t; + +/** + * *MQTT* event configuration structure + */ +typedef struct esp_mqtt_event_t { + esp_mqtt_event_id_t event_id; /*!< *MQTT* event type */ + esp_mqtt_client_handle_t client; /*!< *MQTT* client handle for this event */ + char *data; /*!< Data associated with this event */ + int data_len; /*!< Length of the data for this event */ + int total_data_len; /*!< Total length of the data (longer data are supplied + with multiple events) */ + int current_data_offset; /*!< Actual offset for the data associated with this + event */ + char *topic; /*!< Topic associated with this event */ + int topic_len; /*!< Length of the topic for this event associated with this + event */ + int msg_id; /*!< *MQTT* messaged id of message */ + int session_present; /*!< *MQTT* session_present flag for connection event */ + esp_mqtt_error_codes_t + *error_handle; /*!< esp-mqtt error handle including esp-tls errors as well + as internal *MQTT* errors */ + bool retain; /*!< Retained flag of the message associated with this event */ + int qos; /*!< QoS of the messages associated with this event */ + bool dup; /*!< dup flag of the message associated with this event */ + esp_mqtt_protocol_ver_t protocol_ver; /*!< MQTT protocol version used for connection, defaults to value from menuconfig*/ +#ifdef CONFIG_MQTT_PROTOCOL_5 + esp_mqtt5_event_property_t *property; /*!< MQTT 5 property associated with this event */ +#endif + +} esp_mqtt_event_t; + +typedef esp_mqtt_event_t *esp_mqtt_event_handle_t; + +/** + * *MQTT* client configuration structure + * + * - Default values can be set via menuconfig + * - All certificates and key data could be passed in PEM or DER format. PEM format must have a terminating NULL + * character and the related len field set to 0. DER format requires a related len field set to the correct length. + */ +typedef struct esp_mqtt_client_config_t { + /** + * Broker related configuration + */ + struct broker_t { + /** + * Broker address + * + * - uri have precedence over other fields + * - If uri isn't set at least hostname, transport and port should. + */ + struct address_t { + const char *uri; /*!< Complete *MQTT* broker URI */ + const char *hostname; /*!< Hostname, to set ipv4 pass it as string) */ + esp_mqtt_transport_t transport; /*!< Selects transport*/ + const char *path; /*!< Path in the URI*/ + uint32_t port; /*!< *MQTT* server port */ + } address; /*!< Broker address configuration */ + /** + * Broker identity verification + * + * If fields are not set broker's identity isn't verified. it's recommended + * to set the options in this struct for security reasons. + */ + struct verification_t { + bool use_global_ca_store; /*!< Use a global ca_store, look esp-tls + documentation for details. */ + esp_err_t (*crt_bundle_attach)(void *conf); /*!< Pointer to ESP x509 Certificate Bundle attach function for + the usage of certificate bundles. Client only attach the bundle, the clean up must be done by the user. */ + const char *certificate; /*!< Certificate data, default is NULL. It's not copied nor freed by the client, user needs to clean up.*/ + size_t certificate_len; /*!< Length of the buffer pointed to by certificate. */ + const struct psk_key_hint *psk_hint_key; /*!< Pointer to PSK struct defined in esp_tls.h to enable PSK + authentication (as alternative to certificate verification). + PSK is enabled only if there are no other ways to + verify broker. It's not copied nor freed by the client, user needs to clean up.*/ + bool skip_cert_common_name_check; /*!< Skip any validation of server certificate CN field, this reduces the + security of TLS and makes the *MQTT* client susceptible to MITM attacks */ + const char **alpn_protos; /*!< NULL-terminated list of supported application protocols to be used for ALPN.*/ + const char *common_name; /*!< Pointer to the string containing server certificate common name. + If non-NULL, server certificate CN must match this name, + If NULL, server certificate CN must match hostname. + This is ignored if skip_cert_common_name_check=true. + It's not copied nor freed by the client, user needs to clean up.*/ + } verification; /*!< Security verification of the broker */ + } broker; /*!< Broker address and security verification */ + /** + * Client related credentials for authentication. + */ + struct credentials_t { + const char *username; /*!< *MQTT* username */ + const char *client_id; /*!< Set *MQTT* client identifier. Ignored if set_null_client_id == true If NULL set + the default client id. Default client id is ``ESP32_%CHIPID%`` where `%CHIPID%` are + last 3 bytes of MAC address in hex format */ + bool set_null_client_id; /*!< Selects a NULL client id */ + /** + * Client authentication + * + * Fields related to client authentication by broker + * + * For mutual authentication using TLS, user could select certificate and key, + * secure element or digital signature peripheral if available. + * + */ + struct authentication_t { + const char *password; /*!< *MQTT* password */ + const char *certificate; /*!< Certificate for ssl mutual authentication, not required if mutual + authentication is not needed. Must be provided with `key`. It's not copied nor freed by the client, user needs to clean up.*/ + size_t certificate_len; /*!< Length of the buffer pointed to by certificate.*/ + const char *key; /*!< Private key for SSL mutual authentication, not required if mutual authentication + is not needed. If it is not NULL, also `certificate` has to be provided. It's not copied nor freed by the client, user needs to clean up.*/ + size_t key_len; /*!< Length of the buffer pointed to by key.*/ + const char *key_password; /*!< Client key decryption password, not PEM nor DER, if provided + `key_password_len` must be correctly set.*/ + int key_password_len; /*!< Length of the password pointed to by `key_password` */ + bool use_secure_element; /*!< Enable secure element, available in ESP32-ROOM-32SE, for SSL connection */ + void *ds_data; /*!< Carrier of handle for digital signature parameters, digital signature peripheral is + available in some Espressif devices. It's not copied nor freed by the client, user needs to clean up.*/ + bool use_ecdsa_peripheral; /*!< Enable ECDSA peripheral, available in some Espressif devices. */ + uint8_t ecdsa_key_efuse_blk; /*!< ECDSA key block number from efuse, available in some Espressif devices. */ + } authentication; /*!< Client authentication */ + } credentials; /*!< User credentials for broker */ + /** + * *MQTT* Session related configuration + */ + struct session_t { + /** + * Last Will and Testament message configuration. + */ + struct last_will_t { + const char *topic; /*!< LWT (Last Will and Testament) message topic */ + const char *msg; /*!< LWT message, may be NULL terminated*/ + int msg_len; /*!< LWT message length, if msg isn't NULL terminated must have the correct length */ + int qos; /*!< LWT message QoS */ + int retain; /*!< LWT retained message flag */ + } last_will; /*!< Last will configuration */ + bool disable_clean_session; /*!< *MQTT* clean session, default clean_session is true */ + int keepalive; /*!< *MQTT* keepalive, default is 120 seconds + When configuring this value, keep in mind that the client attempts + to communicate with the broker at half the interval that is actually set. + This conservative approach allows for more attempts before the broker's timeout occurs */ + bool disable_keepalive; /*!< Set `disable_keepalive=true` to turn off keep-alive mechanism, keepalive is active + by default. Note: setting the config value `keepalive` to `0` doesn't disable + keepalive feature, but uses a default keepalive period */ + esp_mqtt_protocol_ver_t protocol_ver; /*!< *MQTT* protocol version used for connection.*/ + int message_retransmit_timeout; /*!< timeout for retransmitting of failed packet */ + } session; /*!< *MQTT* session configuration. */ + /** + * Network related configuration + */ + struct network_t { + int reconnect_timeout_ms; /*!< Reconnect to the broker after this value in miliseconds if auto reconnect is not + disabled (defaults to 10s) */ + int timeout_ms; /*!< Abort network operation if it is not completed after this value, in milliseconds + (defaults to 10s). */ + int refresh_connection_after_ms; /*!< Refresh connection after this value (in milliseconds) */ + bool disable_auto_reconnect; /*!< Client will reconnect to server (when errors/disconnect). Set + `disable_auto_reconnect=true` to disable */ + esp_transport_keep_alive_t tcp_keep_alive_cfg; /*!< Transport keep-alive config*/ + esp_transport_handle_t transport; /*!< Custom transport handle to use, leave it NULL to allow MQTT client create or recreate its own. Warning: The transport should be valid during the client lifetime and is destroyed when esp_mqtt_client_destroy is called. */ + struct ifreq * if_name; /*!< The name of interface for data to go through. Use the default interface without setting */ + } network; /*!< Network configuration */ + /** + * Client task configuration + */ + struct task_t { + int priority; /*!< *MQTT* task priority*/ + int stack_size; /*!< *MQTT* task stack size*/ + } task; /*!< FreeRTOS task configuration.*/ + /** + * Client buffer size configuration + * + * Client have two buffers for input and output respectivelly. + */ + struct buffer_t { + int size; /*!< size of *MQTT* send/receive buffer*/ + int out_size; /*!< size of *MQTT* output buffer. If not defined, defaults to the size defined by + ``buffer_size`` */ + } buffer; /*!< Buffer size configuration.*/ + + /** + * Client outbox configuration options. + */ + struct outbox_config_t { + uint64_t limit; /*!< Size limit for the outbox in bytes.*/ + } outbox; /*!< Outbox configuration. */ +} esp_mqtt_client_config_t; + +/** + * Topic definition struct + */ +typedef struct topic_t { + const char *filter; /*!< Topic filter to subscribe */ + int qos; /*!< Max QoS level of the subscription */ +} esp_mqtt_topic_t; + +/** + * @brief Creates *MQTT* client handle based on the configuration + * + * @param config *MQTT* configuration structure + * + * @return mqtt_client_handle if successfully created, NULL on error + */ +esp_mqtt_client_handle_t +esp_mqtt_client_init(const esp_mqtt_client_config_t *config); + +/** + * @brief Sets *MQTT* connection URI. This API is usually used to overrides the + * URI configured in esp_mqtt_client_init + * + * @param client *MQTT* client handle + * @param uri + * + * @return ESP_FAIL if URI parse error, ESP_OK on success + */ +esp_err_t esp_mqtt_client_set_uri(esp_mqtt_client_handle_t client, + const char *uri); + +/** + * @brief Starts *MQTT* client with already created client handle + * + * @param client *MQTT* client handle + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL on other error + */ +esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client); + +/** + * @brief This api is typically used to force reconnection upon a specific event + * + * @param client *MQTT* client handle + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL if client is in invalid state + */ +esp_err_t esp_mqtt_client_reconnect(esp_mqtt_client_handle_t client); + +/** + * @brief This api is typically used to force disconnection from the broker + * + * @param client *MQTT* client handle + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG on wrong initialization + */ +esp_err_t esp_mqtt_client_disconnect(esp_mqtt_client_handle_t client); + +/** + * @brief Stops *MQTT* client tasks + * + * * Notes: + * - Cannot be called from the *MQTT* event handler + * + * @param client *MQTT* client handle + * + * @return ESP_OK on success + * ESP_ERR_INVALID_ARG on wrong initialization + * ESP_FAIL if client is in invalid state + */ +esp_err_t esp_mqtt_client_stop(esp_mqtt_client_handle_t client); + +#ifdef __cplusplus + +#define esp_mqtt_client_subscribe esp_mqtt_client_subscribe_single + +#else +/** + * @brief Convenience macro to select subscribe function to use. + * + * Notes: + * - Usage of `esp_mqtt_client_subscribe_single` is the same as previous + * esp_mqtt_client_subscribe, refer to it for details. + * + * @param client_handle *MQTT* client handle + * @param topic_type Needs to be char* for single subscription or `esp_mqtt_topic_t` for multiple topics + * @param qos_or_size It's either a qos when subscribing to a single topic or the size of the subscription array when subscribing to multiple topics. + * + * @return message_id of the subscribe message on success + * -1 on failure + * -2 in case of full outbox. + */ +#define esp_mqtt_client_subscribe(client_handle, topic_type, qos_or_size) _Generic((topic_type), \ + char *: esp_mqtt_client_subscribe_single, \ + const char *: esp_mqtt_client_subscribe_single, \ + esp_mqtt_topic_t*: esp_mqtt_client_subscribe_multiple \ + )(client_handle, topic_type, qos_or_size) + +#endif /* __cplusplus*/ +/** + * @brief Subscribe the client to defined topic with defined qos + * + * Notes: + * - Client must be connected to send subscribe message + * - This API is could be executed from a user task or + * from a *MQTT* event callback i.e. internal *MQTT* task + * (API is protected by internal mutex, so it might block + * if a longer data receive operation is in progress. + * - `esp_mqtt_client_subscribe` could be used to call this function. + * + * @param client *MQTT* client handle + * @param topic topic filter to subscribe + * @param qos Max qos level of the subscription + * + * @return message_id of the subscribe message on success + * -1 on failure + * -2 in case of full outbox. + */ +int esp_mqtt_client_subscribe_single(esp_mqtt_client_handle_t client, + const char *topic, int qos); +/** + * @brief Subscribe the client to a list of defined topics with defined qos + * + * Notes: + * - Client must be connected to send subscribe message + * - This API is could be executed from a user task or + * from a *MQTT* event callback i.e. internal *MQTT* task + * (API is protected by internal mutex, so it might block + * if a longer data receive operation is in progress. + * - `esp_mqtt_client_subscribe` could be used to call this function. + * + * @param client *MQTT* client handle + * @param topic_list List of topics to subscribe + * @param size size of topic_list + * + * @return message_id of the subscribe message on success + * -1 on failure + * -2 in case of full outbox. + */ +int esp_mqtt_client_subscribe_multiple(esp_mqtt_client_handle_t client, + const esp_mqtt_topic_t *topic_list, int size); + +/** + * @brief Unsubscribe the client from defined topic + * + * Notes: + * - Client must be connected to send unsubscribe message + * - It is thread safe, please refer to `esp_mqtt_client_subscribe_single` for details + * + * @param client *MQTT* client handle + * @param topic + * + * @return message_id of the subscribe message on success + * -1 on failure + */ +int esp_mqtt_client_unsubscribe(esp_mqtt_client_handle_t client, + const char *topic); + +/** + * @brief Client to send a publish message to the broker + * + * Notes: + * - This API might block for several seconds, either due to network timeout + * (10s) or if publishing payloads longer than internal buffer (due to message + * fragmentation) + * - Client doesn't have to be connected for this API to work, enqueueing the + * messages with qos>1 (returning -1 for all the qos=0 messages if + * disconnected). If MQTT_SKIP_PUBLISH_IF_DISCONNECTED is enabled, this API will + * not attempt to publish when the client is not connected and will always + * return -1. + * - It is thread safe, please refer to `esp_mqtt_client_subscribe` for details + * + * @param client *MQTT* client handle + * @param topic topic string + * @param data payload string (set to NULL, sending empty payload message) + * @param len data length, if set to 0, length is calculated from payload + * string + * @param qos QoS of publish message + * @param retain retain flag + * + * @return message_id of the publish message (for QoS 0 message_id will always + * be zero) on success. -1 on failure, -2 in case of full outbox. + */ +int esp_mqtt_client_publish(esp_mqtt_client_handle_t client, const char *topic, + const char *data, int len, int qos, int retain); + +/** + * @brief Enqueue a message to the outbox, to be sent later. Typically used for + * messages with qos>0, but could be also used for qos=0 messages if store=true. + * + * This API generates and stores the publish message into the internal outbox + * and the actual sending to the network is performed in the mqtt-task context + * (in contrast to the esp_mqtt_client_publish() which sends the publish message + * immediately in the user task's context). Thus, it could be used as a non + * blocking version of esp_mqtt_client_publish(). + * + * @param client *MQTT* client handle + * @param topic topic string + * @param data payload string (set to NULL, sending empty payload message) + * @param len data length, if set to 0, length is calculated from payload + * string + * @param qos QoS of publish message + * @param retain retain flag + * @param store if true, all messages are enqueued; otherwise only QoS 1 and + * QoS 2 are enqueued + * + * @return message_id if queued successfully, -1 on failure, -2 in case of full outbox. + */ +int esp_mqtt_client_enqueue(esp_mqtt_client_handle_t client, const char *topic, + const char *data, int len, int qos, int retain, + bool store); + +/** + * @brief Destroys the client handle + * + * Notes: + * - Cannot be called from the *MQTT* event handler + * + * @param client *MQTT* client handle + * + * @return ESP_OK + * ESP_ERR_INVALID_ARG on wrong initialization + */ +esp_err_t esp_mqtt_client_destroy(esp_mqtt_client_handle_t client); + +/** + * @brief Set configuration structure, typically used when updating the config + * (i.e. on "before_connect" event + * + * Notes: + * - When calling this function make sure to have all the intendend configurations + * set, otherwise default values are set. + * @param client *MQTT* client handle + * + * @param config *MQTT* configuration structure + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_ERR_INVALID_ARG if conflicts on transport configuration. + * ESP_OK on success + */ +esp_err_t esp_mqtt_set_config(esp_mqtt_client_handle_t client, + const esp_mqtt_client_config_t *config); + +/** + * @brief Registers *MQTT* event + * + * @param client *MQTT* client handle + * @param event event type + * @param event_handler handler callback + * @param event_handler_arg handlers context + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_ERR_INVALID_ARG on wrong initialization + * ESP_OK on success + */ +esp_err_t esp_mqtt_client_register_event(esp_mqtt_client_handle_t client, + esp_mqtt_event_id_t event, + esp_event_handler_t event_handler, + void *event_handler_arg); + +/** + * @brief Unregisters mqtt event + * + * @param client mqtt client handle + * @param event event ID + * @param event_handler handler to unregister + * + * @return ESP_ERR_NO_MEM if failed to allocate + * ESP_ERR_INVALID_ARG on invalid event ID + * ESP_OK on success + */ +esp_err_t esp_mqtt_client_unregister_event(esp_mqtt_client_handle_t client, esp_mqtt_event_id_t event, esp_event_handler_t event_handler); + +/** + * @brief Get outbox size + * + * @param client *MQTT* client handle + * @return outbox size + * 0 on wrong initialization + */ +int esp_mqtt_client_get_outbox_size(esp_mqtt_client_handle_t client); + +/** + * @brief Dispatch user event to the mqtt internal event loop + * + * @param client *MQTT* client handle + * @param event *MQTT* event handle structure + * @return ESP_OK on success + * ESP_ERR_TIMEOUT if the event couldn't be queued (ref also CONFIG_MQTT_EVENT_QUEUE_SIZE) + */ +esp_err_t esp_mqtt_dispatch_custom_event(esp_mqtt_client_handle_t client, esp_mqtt_event_t *event); + +/** + * @brief Get a transport from the scheme + * + * Allows extra settings to be made on the selected transport, + * for convenience the scheme used by the mqtt client are defined as + * MQTT_OVER_TCP_SCHEME, MQTT_OVER_SSL_SCHEME, MQTT_OVER_WS_SCHEME and MQTT_OVER_WSS_SCHEME + * If the transport_scheme is NULL and the client was set with a custom transport the custom transport will be returned. + * + * Notes: + * - This function should be called only on MQTT_EVENT_BEFORE_CONNECT. + * - The intetion is to provide a way to set different configurations than the ones available from client config. + * - If esp_mqtt_client_destroy is called the returned pointer will be invalidated. + * - All the required settings should be made in the MQTT_EVENT_BEFORE_CONNECT event handler + * + * @param client *MQTT* client handle + * @param transport_scheme Transport handle to search for. + * @return the transport handle + * NULL in case of error + * +*/ +esp_transport_handle_t esp_mqtt_client_get_transport(esp_mqtt_client_handle_t client, char *transport_scheme); +#ifdef __cplusplus +} +#endif //__cplusplus + +#endif diff --git a/managed_components/esp_mqtt/include/mqtt_supported_features.h b/managed_components/esp_mqtt/include/mqtt_supported_features.h new file mode 100644 index 0000000..b9e7c2d --- /dev/null +++ b/managed_components/esp_mqtt/include/mqtt_supported_features.h @@ -0,0 +1,79 @@ +// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _MQTT_SUPPORTED_FEATURES_H_ +#define _MQTT_SUPPORTED_FEATURES_H_ + +#if __has_include("esp_idf_version.h") +#include "esp_idf_version.h" +#endif + +/** + * @brief This header defines supported features of IDF which mqtt module + * could use depending on specific version of ESP-IDF. + * In case "esp_idf_version.h" were not found, all additional + * features would be disabled + */ + +#ifdef ESP_IDF_VERSION + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0) +// Features supported from 3.3 +#define MQTT_SUPPORTED_FEATURE_EVENT_LOOP +#define MQTT_SUPPORTED_FEATURE_SKIP_CRT_CMN_NAME_CHECK +#endif + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) +// Features supported in 4.0 +#define MQTT_SUPPORTED_FEATURE_WS_SUBPROTOCOL +#define MQTT_SUPPORTED_FEATURE_TRANSPORT_ERR_REPORTING +#endif + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0) +// Features supported in 4.1 +#define MQTT_SUPPORTED_FEATURE_PSK_AUTHENTICATION +#define MQTT_SUPPORTED_FEATURE_DER_CERTIFICATES +#define MQTT_SUPPORTED_FEATURE_ALPN +#define MQTT_SUPPORTED_FEATURE_CLIENT_KEY_PASSWORD +#endif + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 2, 0) +// Features supported in 4.2 +#define MQTT_SUPPORTED_FEATURE_SECURE_ELEMENT +#endif + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 3, 0) +// Features supported in 4.3 +#define MQTT_SUPPORTED_FEATURE_DIGITAL_SIGNATURE +#define MQTT_SUPPORTED_FEATURE_TRANSPORT_SOCK_ERRNO_REPORTING +#endif + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) +// Features supported in 4.4 +#define MQTT_SUPPORTED_FEATURE_CERTIFICATE_BUNDLE +#endif + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0) +// Features supported in 5.1.0 +#define MQTT_SUPPORTED_FEATURE_CRT_CMN_NAME +#endif + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0) +// Features supported in 5.2.0 +#define MQTT_SUPPORTED_FEATURE_ECDSA_PERIPHERAL +#endif + + +#endif /* ESP_IDF_VERSION */ +#endif // _MQTT_SUPPORTED_FEATURES_H_ diff --git a/managed_components/esp_mqtt/lib/include/mqtt5_client_priv.h b/managed_components/esp_mqtt/lib/include/mqtt5_client_priv.h new file mode 100644 index 0000000..1b5f04b --- /dev/null +++ b/managed_components/esp_mqtt/lib/include/mqtt5_client_priv.h @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _MQTT5_CLIENT_PRIV_H_ +#define _MQTT5_CLIENT_PRIV_H_ + +#include "mqtt5_client.h" +#include "mqtt_client_priv.h" +#include "mqtt5_msg.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct mqtt5_topic_alias { + char *topic; + uint16_t topic_len; + uint16_t topic_alias; + STAILQ_ENTRY(mqtt5_topic_alias) next; +} mqtt5_topic_alias_t; +STAILQ_HEAD(mqtt5_topic_alias_list_t, mqtt5_topic_alias); +typedef struct mqtt5_topic_alias_list_t *mqtt5_topic_alias_handle_t; +typedef struct mqtt5_topic_alias *mqtt5_topic_alias_item_t; + +typedef struct { + esp_mqtt5_connection_property_storage_t connect_property_info; + esp_mqtt5_connection_will_property_storage_t will_property_info; + esp_mqtt5_connection_server_resp_property_t server_resp_property_info; + esp_mqtt5_disconnect_property_config_t disconnect_property_info; + const esp_mqtt5_publish_property_config_t *publish_property_info; + const esp_mqtt5_subscribe_property_config_t *subscribe_property_info; + const esp_mqtt5_unsubscribe_property_config_t *unsubscribe_property_info; + mqtt5_topic_alias_handle_t peer_topic_alias; +} mqtt5_config_storage_t; + +void esp_mqtt5_increment_packet_counter(esp_mqtt5_client_handle_t client); +void esp_mqtt5_decrement_packet_counter(esp_mqtt5_client_handle_t client); +void esp_mqtt5_parse_pubcomp(esp_mqtt5_client_handle_t client); +void esp_mqtt5_parse_puback(esp_mqtt5_client_handle_t client); +void esp_mqtt5_parse_unsuback(esp_mqtt5_client_handle_t client); +void esp_mqtt5_parse_suback(esp_mqtt5_client_handle_t client); +void esp_mqtt5_parse_disconnect(esp_mqtt5_client_handle_t client, int *disconnect_rsp_code); +esp_err_t esp_mqtt5_parse_connack(esp_mqtt5_client_handle_t client, int *connect_rsp_code); +void esp_mqtt5_client_destory(esp_mqtt5_client_handle_t client); +esp_err_t esp_mqtt5_client_publish_check(esp_mqtt5_client_handle_t client, int qos, int retain); +esp_err_t esp_mqtt5_client_subscribe_check(esp_mqtt5_client_handle_t client, int qos); +esp_err_t esp_mqtt5_create_default_config(esp_mqtt5_client_handle_t client); +esp_err_t esp_mqtt5_get_publish_data(esp_mqtt5_client_handle_t client, uint8_t *msg_buf, size_t msg_read_len, char **msg_topic, size_t *msg_topic_len, char **msg_data, size_t *msg_data_len); +#ifdef __cplusplus +} +#endif //__cplusplus + +#endif \ No newline at end of file diff --git a/managed_components/esp_mqtt/lib/include/mqtt5_msg.h b/managed_components/esp_mqtt/lib/include/mqtt5_msg.h new file mode 100644 index 0000000..6c2a036 --- /dev/null +++ b/managed_components/esp_mqtt/lib/include/mqtt5_msg.h @@ -0,0 +1,142 @@ +#ifndef MQTT5_MSG_H +#define MQTT5_MSG_H +#include +#include +#include "sys/queue.h" +#include "mqtt_config.h" +#include "mqtt_msg.h" +#include "mqtt_client.h" +#ifdef __cplusplus +extern "C" { +#endif + +enum mqtt_properties_type { + MQTT5_PROPERTY_PAYLOAD_FORMAT_INDICATOR = 0x01, + MQTT5_PROPERTY_MESSAGE_EXPIRY_INTERVAL = 0x02, + MQTT5_PROPERTY_CONTENT_TYPE = 0x03, + MQTT5_PROPERTY_RESPONSE_TOPIC = 0x08, + MQTT5_PROPERTY_CORRELATION_DATA = 0x09, + MQTT5_PROPERTY_SUBSCRIBE_IDENTIFIER = 0x0B, + MQTT5_PROPERTY_SESSION_EXPIRY_INTERVAL = 0x11, + MQTT5_PROPERTY_ASSIGNED_CLIENT_IDENTIFIER = 0x12, + MQTT5_PROPERTY_SERVER_KEEP_ALIVE = 0x13, + MQTT5_PROPERTY_AUTHENTICATION_METHOD = 0x15, + MQTT5_PROPERTY_AUTHENTICATION_DATA = 0x16, + MQTT5_PROPERTY_REQUEST_PROBLEM_INFO = 0x17, + MQTT5_PROPERTY_WILL_DELAY_INTERVAL = 0x18, + MQTT5_PROPERTY_REQUEST_RESP_INFO = 0x19, + MQTT5_PROPERTY_RESP_INFO = 0x1A, + MQTT5_PROPERTY_SERVER_REFERENCE = 0x1C, + MQTT5_PROPERTY_REASON_STRING = 0x1F, + MQTT5_PROPERTY_RECEIVE_MAXIMUM = 0x21, + MQTT5_PROPERTY_TOPIC_ALIAS_MAXIMIM = 0x22, + MQTT5_PROPERTY_TOPIC_ALIAS = 0x23, + MQTT5_PROPERTY_MAXIMUM_QOS = 0x24, + MQTT5_PROPERTY_RETAIN_AVAILABLE = 0x25, + MQTT5_PROPERTY_USER_PROPERTY = 0x26, + MQTT5_PROPERTY_MAXIMUM_PACKET_SIZE = 0x27, + MQTT5_PROPERTY_WILDCARD_SUBSCR_AVAILABLE = 0x28, + MQTT5_PROPERTY_SUBSCR_IDENTIFIER_AVAILABLE = 0x29, + MQTT5_PROPERTY_SHARED_SUBSCR_AVAILABLE = 0x2A, +}; + +typedef struct mqtt5_user_property { + char *key; + char *value; + STAILQ_ENTRY(mqtt5_user_property) next; +} mqtt5_user_property_t; +STAILQ_HEAD(mqtt5_user_property_list_t, mqtt5_user_property); +typedef struct mqtt5_user_property *mqtt5_user_property_item_t; + +typedef struct { + uint32_t maximum_packet_size; + uint16_t receive_maximum; + uint16_t topic_alias_maximum; + uint8_t max_qos; + bool retain_available; + bool wildcard_subscribe_available; + bool subscribe_identifiers_available; + bool shared_subscribe_available; + char *response_info; +} esp_mqtt5_connection_server_resp_property_t; + +typedef struct { + bool payload_format_indicator; + uint32_t message_expiry_interval; + uint16_t topic_alias; + char *response_topic; + int response_topic_len; + char *correlation_data; + uint16_t correlation_data_len; + char *content_type; + int content_type_len; + uint16_t subscribe_id; +} esp_mqtt5_publish_resp_property_t; + +typedef struct { + uint32_t session_expiry_interval; + uint32_t maximum_packet_size; + uint16_t receive_maximum; + uint16_t topic_alias_maximum; + bool request_resp_info; + bool request_problem_info; + mqtt5_user_property_handle_t user_property; +} esp_mqtt5_connection_property_storage_t; + +typedef struct { + uint32_t will_delay_interval; + uint32_t message_expiry_interval; + bool payload_format_indicator; + char *content_type; + char *response_topic; + char *correlation_data; + uint16_t correlation_data_len; + mqtt5_user_property_handle_t user_property; +} esp_mqtt5_connection_will_property_storage_t; + +#define mqtt5_get_type mqtt_get_type + +#define mqtt5_get_dup mqtt_get_dup + +#define mqtt5_set_dup mqtt_set_dup + +#define mqtt5_get_qos mqtt_get_qos + +#define mqtt5_get_retain mqtt_get_retain + +#define mqtt5_msg_init mqtt_msg_init + +#define mqtt5_get_total_length mqtt_get_total_length + +#define mqtt5_has_valid_msg_hdr mqtt_has_valid_msg_hdr + +#define mqtt5_msg_pingreq mqtt_msg_pingreq + +#define mqtt5_msg_pingresp mqtt_msg_pingresp + +#define mqtt5_get_unsuback_data mqtt5_get_suback_data + +#define mqtt5_get_pubcomp_data mqtt5_get_puback_data + +uint16_t mqtt5_get_id(uint8_t *buffer, size_t length); +char *mqtt5_get_publish_property_payload(uint8_t *buffer, size_t buffer_length, char **msg_topic, size_t *msg_topic_len, esp_mqtt5_publish_resp_property_t *resp_property, uint16_t *property_len, size_t *payload_len, mqtt5_user_property_handle_t *user_property); +char *mqtt5_get_suback_data(uint8_t *buffer, size_t *length, mqtt5_user_property_handle_t *user_property); +char *mqtt5_get_puback_data(uint8_t *buffer, size_t *length, mqtt5_user_property_handle_t *user_property); +mqtt_message_t *mqtt5_msg_connect(mqtt_connection_t *connection, mqtt_connect_info_t *info, esp_mqtt5_connection_property_storage_t *property, esp_mqtt5_connection_will_property_storage_t *will_property); +mqtt_message_t *mqtt5_msg_publish(mqtt_connection_t *connection, const char *topic, const char *data, int data_length, int qos, int retain, uint16_t *message_id, const esp_mqtt5_publish_property_config_t *property, const char *resp_info); +esp_err_t mqtt5_msg_parse_connack_property(uint8_t *buffer, size_t buffer_len, mqtt_connect_info_t *connection_info, esp_mqtt5_connection_property_storage_t *connection_property, esp_mqtt5_connection_server_resp_property_t *resp_property, int *reason_code, uint8_t *ack_flag, mqtt5_user_property_handle_t *user_property); +int mqtt5_msg_get_reason_code(uint8_t *buffer, size_t length); +mqtt_message_t *mqtt5_msg_subscribe(mqtt_connection_t *connection, const esp_mqtt_topic_t *topic, int size, uint16_t *message_id, const esp_mqtt5_subscribe_property_config_t *property); +mqtt_message_t *mqtt5_msg_unsubscribe(mqtt_connection_t *connection, const char *topic, uint16_t *message_id, const esp_mqtt5_unsubscribe_property_config_t *property); +mqtt_message_t *mqtt5_msg_disconnect(mqtt_connection_t *connection, esp_mqtt5_disconnect_property_config_t *disconnect_property_info); +mqtt_message_t *mqtt5_msg_pubcomp(mqtt_connection_t *connection, uint16_t message_id); +mqtt_message_t *mqtt5_msg_pubrel(mqtt_connection_t *connection, uint16_t message_id); +mqtt_message_t *mqtt5_msg_pubrec(mqtt_connection_t *connection, uint16_t message_id); +mqtt_message_t *mqtt5_msg_puback(mqtt_connection_t *connection, uint16_t message_id); + +#ifdef __cplusplus +} +#endif + +#endif /* MQTT5_MSG_H */ + diff --git a/managed_components/esp_mqtt/lib/include/mqtt_client_priv.h b/managed_components/esp_mqtt/lib/include/mqtt_client_priv.h new file mode 100644 index 0000000..4aa0a9f --- /dev/null +++ b/managed_components/esp_mqtt/lib/include/mqtt_client_priv.h @@ -0,0 +1,149 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _MQTT_CLIENT_PRIV_H_ +#define _MQTT_CLIENT_PRIV_H_ + +#include +#include +#include +#include +#include "esp_err.h" +#include "platform.h" + +#include "esp_event.h" +#include "mqtt_client.h" +#include "mqtt_msg.h" +#ifdef MQTT_PROTOCOL_5 +#include "mqtt5_client_priv.h" +#endif +#include "esp_transport.h" +#include "esp_transport_tcp.h" +#include "esp_transport_ssl.h" +#include "esp_transport_ws.h" +#include "esp_log.h" +#include "mqtt_outbox.h" +#include "freertos/event_groups.h" +#include +#include + +#include "mqtt_supported_features.h" + +/* using uri parser */ +#include "http_parser.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_NEWLIB_NANO_FORMAT +#define NEWLIB_NANO_COMPAT_FORMAT PRIu32 +#define NEWLIB_NANO_COMPAT_CAST(size_t_var) (uint32_t)size_t_var +#else +#define NEWLIB_NANO_COMPAT_FORMAT "zu" +#define NEWLIB_NANO_COMPAT_CAST(size_t_var) size_t_var +#endif + +#ifdef MQTT_DISABLE_API_LOCKS +# define MQTT_API_LOCK(c) +# define MQTT_API_UNLOCK(c) +#else +# define MQTT_API_LOCK(c) xSemaphoreTakeRecursive(c->api_lock, portMAX_DELAY) +# define MQTT_API_UNLOCK(c) xSemaphoreGiveRecursive(c->api_lock) +#endif /* MQTT_USE_API_LOCKS */ + +typedef struct mqtt_state { + uint8_t *in_buffer; + int in_buffer_length; + size_t message_length; + size_t in_buffer_read_len; + mqtt_connection_t connection; + uint16_t pending_msg_id; + int pending_msg_type; + int pending_publish_qos; +} mqtt_state_t; + +typedef struct { + esp_event_loop_handle_t event_loop_handle; + int task_stack; + int task_prio; + char *uri; + char *host; + char *path; + char *scheme; + int port; + bool auto_reconnect; + int network_timeout_ms; + int refresh_connection_after_ms; + int reconnect_timeout_ms; + char **alpn_protos; + int num_alpn_protos; + char *clientkey_password; + int clientkey_password_len; + bool use_global_ca_store; + esp_err_t ((*crt_bundle_attach)(void *conf)); + const char *cacert_buf; + size_t cacert_bytes; + const char *clientcert_buf; + size_t clientcert_bytes; + const char *clientkey_buf; + size_t clientkey_bytes; + const struct psk_key_hint *psk_hint_key; + bool skip_cert_common_name_check; + const char *common_name; + bool use_secure_element; + void *ds_data; + bool use_ecdsa_peripheral; + uint8_t ecdsa_key_efuse_blk; + int message_retransmit_timeout; + uint64_t outbox_limit; + esp_transport_handle_t transport; + struct ifreq * if_name; + esp_transport_keep_alive_t tcp_keep_alive_cfg; +} mqtt_config_storage_t; + +typedef enum { + MQTT_STATE_INIT = 0, + MQTT_STATE_DISCONNECTED, + MQTT_STATE_CONNECTED, + MQTT_STATE_WAIT_RECONNECT, +} mqtt_client_state_t; + +struct esp_mqtt_client { + esp_transport_list_handle_t transport_list; + esp_transport_handle_t transport; + mqtt_config_storage_t *config; + mqtt_state_t mqtt_state; + _Atomic mqtt_client_state_t state; + uint64_t refresh_connection_tick; + int64_t keepalive_tick; + uint64_t reconnect_tick; +#ifdef MQTT_PROTOCOL_5 + mqtt5_config_storage_t *mqtt5_config; + uint16_t send_publish_packet_count; // This is for MQTT v5.0 flow control +#endif + int wait_timeout_ms; + int auto_reconnect; + esp_mqtt_event_t event; + bool run; + bool wait_for_ping_resp; + outbox_handle_t outbox; + EventGroupHandle_t status_bits; + SemaphoreHandle_t api_lock; + TaskHandle_t task_handle; +#if MQTT_EVENT_QUEUE_SIZE > 1 + atomic_int queued_events; +#endif +}; + +bool esp_mqtt_set_if_config(char const *const new_config, char **old_config); +void esp_mqtt_destroy_config(esp_mqtt_client_handle_t client); + +#ifdef __cplusplus +} +#endif //__cplusplus + +#endif diff --git a/managed_components/esp_mqtt/lib/include/mqtt_config.h b/managed_components/esp_mqtt/lib/include/mqtt_config.h new file mode 100644 index 0000000..d96e2ad --- /dev/null +++ b/managed_components/esp_mqtt/lib/include/mqtt_config.h @@ -0,0 +1,118 @@ +/* + * This file is subject to the terms and conditions defined in + * file 'LICENSE', which is part of this source code package. + * Tuan PM + */ +#ifndef _MQTT_CONFIG_H_ +#define _MQTT_CONFIG_H_ + +#include "sdkconfig.h" + +#ifdef CONFIG_MQTT_PROTOCOL_311 +#define MQTT_PROTOCOL_311 +#endif + +#ifdef CONFIG_MQTT_PROTOCOL_5 +#define MQTT_PROTOCOL_5 +#endif + +#define MQTT_RECON_DEFAULT_MS (10*1000) + +#ifdef CONFIG_MQTT_POLL_READ_TIMEOUT_MS +#define MQTT_POLL_READ_TIMEOUT_MS CONFIG_MQTT_POLL_READ_TIMEOUT_MS +#else +#define MQTT_POLL_READ_TIMEOUT_MS (1000) +#endif + +#define MQTT_MSG_ID_INCREMENTAL CONFIG_MQTT_MSG_ID_INCREMENTAL + +#define MQTT_SKIP_PUBLISH_IF_DISCONNECTED CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED + +#define MQTT_REPORT_DELETED_MESSAGES CONFIG_MQTT_REPORT_DELETED_MESSAGES + +#if CONFIG_MQTT_BUFFER_SIZE +#define MQTT_BUFFER_SIZE_BYTE CONFIG_MQTT_BUFFER_SIZE +#else +#define MQTT_BUFFER_SIZE_BYTE 1024 +#endif + +#if CONFIG_MQTT_TASK_PRIORITY +#define MQTT_TASK_PRIORITY CONFIG_MQTT_TASK_PRIORITY +#else +#define MQTT_TASK_PRIORITY 5 +#endif + +#if CONFIG_MQTT_TASK_STACK_SIZE +#define MQTT_TASK_STACK CONFIG_MQTT_TASK_STACK_SIZE +#else +#define MQTT_TASK_STACK (6*1024) +#endif + +#define MQTT_KEEPALIVE_TICK (120) +#define MQTT_NETWORK_TIMEOUT_MS (10000) + +#ifdef CONFIG_MQTT_TCP_DEFAULT_PORT +#define MQTT_TCP_DEFAULT_PORT CONFIG_MQTT_TCP_DEFAULT_PORT +#else +#define MQTT_TCP_DEFAULT_PORT 1883 +#endif + +#ifdef CONFIG_MQTT_SSL_DEFAULT_PORT +#define MQTT_SSL_DEFAULT_PORT CONFIG_MQTT_SSL_DEFAULT_PORT +#else +#define MQTT_SSL_DEFAULT_PORT 8883 +#endif + +#ifdef CONFIG_MQTT_WS_DEFAULT_PORT +#define MQTT_WS_DEFAULT_PORT CONFIG_MQTT_WS_DEFAULT_PORT +#else +#define MQTT_WS_DEFAULT_PORT 80 +#endif + +#ifdef CONFIG_MQTT_WSS_DEFAULT_PORT +#define MQTT_WSS_DEFAULT_PORT CONFIG_MQTT_WSS_DEFAULT_PORT +#else +#define MQTT_WSS_DEFAULT_PORT 443 +#endif + +#define MQTT_CORE_SELECTION_ENABLED CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED + +#ifdef CONFIG_MQTT_DISABLE_API_LOCKS +#define MQTT_DISABLE_API_LOCKS CONFIG_MQTT_DISABLE_API_LOCKS +#endif + +#ifdef CONFIG_MQTT_USE_CORE_0 +#define MQTT_TASK_CORE 0 +#else +#ifdef CONFIG_MQTT_USE_CORE_1 +#define MQTT_TASK_CORE 1 +#else +#define MQTT_TASK_CORE 0 +#endif +#endif + +#ifdef CONFIG_MQTT_OUTBOX_EXPIRED_TIMEOUT_MS +#define OUTBOX_EXPIRED_TIMEOUT_MS CONFIG_MQTT_OUTBOX_EXPIRED_TIMEOUT_MS +#else +#define OUTBOX_EXPIRED_TIMEOUT_MS (30*1000) +#endif + +#define MQTT_ENABLE_SSL CONFIG_MQTT_TRANSPORT_SSL +#define MQTT_ENABLE_WS CONFIG_MQTT_TRANSPORT_WEBSOCKET +#define MQTT_ENABLE_WSS CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE +#define MQTT_DEFAULT_RETRANSMIT_TIMEOUT_MS 1000 + +#ifdef CONFIG_MQTT_EVENT_QUEUE_SIZE +#define MQTT_EVENT_QUEUE_SIZE CONFIG_MQTT_EVENT_QUEUE_SIZE +#else +#define MQTT_EVENT_QUEUE_SIZE 1 +#endif + +#ifdef CONFIG_MQTT_OUTBOX_DATA_ON_EXTERNAL_MEMORY +#define MQTT_OUTBOX_MEMORY MALLOC_CAP_SPIRAM +#else +#define MQTT_OUTBOX_MEMORY MALLOC_CAP_DEFAULT +#endif + +#define OUTBOX_MAX_SIZE (4*1024) +#endif diff --git a/managed_components/esp_mqtt/lib/include/mqtt_msg.h b/managed_components/esp_mqtt/lib/include/mqtt_msg.h new file mode 100644 index 0000000..f09590d --- /dev/null +++ b/managed_components/esp_mqtt/lib/include/mqtt_msg.h @@ -0,0 +1,152 @@ +#ifndef MQTT_MSG_H +#define MQTT_MSG_H +#include +#include + +#include "mqtt_config.h" +#include "mqtt_client.h" +#ifdef __cplusplus +extern "C" { +#endif + +/* +* Copyright (c) 2014, Stephen Robinson +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. Neither the name of the copyright holder nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +*/ +/* 7 6 5 4 3 2 1 0 */ +/*| --- Message Type---- | DUP Flag | QoS Level | Retain | */ +/* Remaining Length */ + + +enum mqtt_message_type { + MQTT_MSG_TYPE_CONNECT = 1, + MQTT_MSG_TYPE_CONNACK = 2, + MQTT_MSG_TYPE_PUBLISH = 3, + MQTT_MSG_TYPE_PUBACK = 4, + MQTT_MSG_TYPE_PUBREC = 5, + MQTT_MSG_TYPE_PUBREL = 6, + MQTT_MSG_TYPE_PUBCOMP = 7, + MQTT_MSG_TYPE_SUBSCRIBE = 8, + MQTT_MSG_TYPE_SUBACK = 9, + MQTT_MSG_TYPE_UNSUBSCRIBE = 10, + MQTT_MSG_TYPE_UNSUBACK = 11, + MQTT_MSG_TYPE_PINGREQ = 12, + MQTT_MSG_TYPE_PINGRESP = 13, + MQTT_MSG_TYPE_DISCONNECT = 14 +}; + +typedef struct mqtt_message { + uint8_t *data; + size_t length; + size_t fragmented_msg_total_length; /*!< total len of fragmented messages (zero for all other messages) */ + size_t fragmented_msg_data_offset; /*!< data offset of fragmented messages (zero for all other messages) */ +} mqtt_message_t; + +typedef struct mqtt_connect_info { + char *client_id; + char *username; + char *password; + char *will_topic; + char *will_message; + int64_t keepalive; /*!< keepalive=0 -> keepalive is disabled */ + int will_length; + int will_qos; + int will_retain; + int clean_session; + esp_mqtt_protocol_ver_t protocol_ver; +} mqtt_connect_info_t; + +typedef struct mqtt_connection { + mqtt_message_t outbound_message; +#if MQTT_MSG_ID_INCREMENTAL + uint16_t last_message_id; /*!< last used id if incremental message id configured */ +#endif + uint8_t *buffer; + size_t buffer_length; + mqtt_connect_info_t information; + +} mqtt_connection_t; + +static inline int mqtt_get_type(const uint8_t *buffer) +{ + return (buffer[0] & 0xf0) >> 4; +} +static inline int mqtt_get_connect_session_present(const uint8_t *buffer) +{ + return buffer[2] & 0x01; +} +static inline int mqtt_get_connect_return_code(const uint8_t *buffer) +{ + return buffer[3]; +} +static inline int mqtt_get_dup(const uint8_t *buffer) +{ + return (buffer[0] & 0x08) >> 3; +} +static inline void mqtt_set_dup(uint8_t *buffer) +{ + buffer[0] |= 0x08; +} +static inline int mqtt_get_qos(const uint8_t *buffer) +{ + return (buffer[0] & 0x06) >> 1; +} +static inline int mqtt_get_retain(const uint8_t *buffer) +{ + return (buffer[0] & 0x01); +} + +bool mqtt_header_complete(uint8_t *buffer, size_t buffer_length); +size_t mqtt_get_total_length(const uint8_t *buffer, size_t length, int *fixed_size_len); +char *mqtt_get_publish_topic(uint8_t *buffer, size_t *length); +char *mqtt_get_publish_data(uint8_t *buffer, size_t *length); +char *mqtt_get_suback_data(uint8_t *buffer, size_t *length); +uint16_t mqtt_get_id(uint8_t *buffer, size_t length); +int mqtt_has_valid_msg_hdr(uint8_t *buffer, size_t length); + +esp_err_t mqtt_msg_buffer_init(mqtt_connection_t *connection, int buffer_size); +void mqtt_msg_buffer_destroy(mqtt_connection_t *connection); + +mqtt_message_t *mqtt_msg_connect(mqtt_connection_t *connection, mqtt_connect_info_t *info); +mqtt_message_t *mqtt_msg_publish(mqtt_connection_t *connection, const char *topic, const char *data, int data_length, int qos, int retain, uint16_t *message_id); +mqtt_message_t *mqtt_msg_puback(mqtt_connection_t *connection, uint16_t message_id); +mqtt_message_t *mqtt_msg_pubrec(mqtt_connection_t *connection, uint16_t message_id); +mqtt_message_t *mqtt_msg_pubrel(mqtt_connection_t *connection, uint16_t message_id); +mqtt_message_t *mqtt_msg_pubcomp(mqtt_connection_t *connection, uint16_t message_id); +mqtt_message_t *mqtt_msg_subscribe(mqtt_connection_t *connection, const esp_mqtt_topic_t topic_list[], int size, uint16_t *message_id) __attribute__((nonnull)); +mqtt_message_t *mqtt_msg_unsubscribe(mqtt_connection_t *connection, const char *topic, uint16_t *message_id); +mqtt_message_t *mqtt_msg_pingreq(mqtt_connection_t *connection); +mqtt_message_t *mqtt_msg_pingresp(mqtt_connection_t *connection); +mqtt_message_t *mqtt_msg_disconnect(mqtt_connection_t *connection); +#ifdef __cplusplus +} +#endif + +#endif /* MQTT_MSG_H */ + diff --git a/managed_components/esp_mqtt/lib/include/mqtt_outbox.h b/managed_components/esp_mqtt/lib/include/mqtt_outbox.h new file mode 100644 index 0000000..241b335 --- /dev/null +++ b/managed_components/esp_mqtt/lib/include/mqtt_outbox.h @@ -0,0 +1,64 @@ +/* + * This file is subject to the terms and conditions defined in + * file 'LICENSE', which is part of this source code package. + * Tuan PM + */ +#ifndef _MQTT_OUTOBX_H_ +#define _MQTT_OUTOBX_H_ +#include "platform.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct outbox_item; + +typedef struct outbox_t *outbox_handle_t; +typedef struct outbox_item *outbox_item_handle_t; +typedef struct outbox_message *outbox_message_handle_t; +typedef long long outbox_tick_t; + +typedef struct outbox_message { + uint8_t *data; + int len; + int msg_id; + int msg_qos; + int msg_type; + uint8_t *remaining_data; + int remaining_len; +} outbox_message_t; + +typedef enum pending_state { + QUEUED, + TRANSMITTED, + ACKNOWLEDGED, + CONFIRMED +} pending_state_t; + +outbox_handle_t outbox_init(void); +outbox_item_handle_t outbox_enqueue(outbox_handle_t outbox, outbox_message_handle_t message, outbox_tick_t tick); +outbox_item_handle_t outbox_dequeue(outbox_handle_t outbox, pending_state_t pending, outbox_tick_t *tick); +outbox_item_handle_t outbox_get(outbox_handle_t outbox, int msg_id); +uint8_t *outbox_item_get_data(outbox_item_handle_t item, size_t *len, uint16_t *msg_id, int *msg_type, int *qos); +esp_err_t outbox_delete(outbox_handle_t outbox, int msg_id, int msg_type); +esp_err_t outbox_delete_item(outbox_handle_t outbox, outbox_item_handle_t item); +int outbox_delete_expired(outbox_handle_t outbox, outbox_tick_t current_tick, outbox_tick_t timeout); +/** + * @brief Deletes single expired message returning it's message id + * + * @return msg id of the deleted message, -1 if no expired message in the outbox + */ +int outbox_delete_single_expired(outbox_handle_t outbox, outbox_tick_t current_tick, outbox_tick_t timeout); + +esp_err_t outbox_set_pending(outbox_handle_t outbox, int msg_id, pending_state_t pending); +pending_state_t outbox_item_get_pending(outbox_item_handle_t item); +esp_err_t outbox_set_tick(outbox_handle_t outbox, int msg_id, outbox_tick_t tick); +uint64_t outbox_get_size(outbox_handle_t outbox); +void outbox_destroy(outbox_handle_t outbox); +void outbox_delete_all_items(outbox_handle_t outbox); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/managed_components/esp_mqtt/lib/include/platform.h b/managed_components/esp_mqtt/lib/include/platform.h new file mode 100644 index 0000000..b3358b9 --- /dev/null +++ b/managed_components/esp_mqtt/lib/include/platform.h @@ -0,0 +1,14 @@ +/* + * This file is subject to the terms and conditions defined in + * file 'LICENSE', which is part of this source code package. + * Tuan PM + */ +#ifndef _PLATFORM_H__ +#define _PLATFORM_H__ + +//Support ESP32 +#ifdef ESP_PLATFORM +#include "platform_esp32_idf.h" +#endif + +#endif diff --git a/managed_components/esp_mqtt/lib/include/platform_esp32_idf.h b/managed_components/esp_mqtt/lib/include/platform_esp32_idf.h new file mode 100644 index 0000000..98f5fe1 --- /dev/null +++ b/managed_components/esp_mqtt/lib/include/platform_esp32_idf.h @@ -0,0 +1,29 @@ +/* + * This file is subject to the terms and conditions defined in + * file 'LICENSE', which is part of this source code package. + * Tuan PM + */ +#ifndef _ESP_PLATFORM_H__ +#define _ESP_PLATFORM_H__ + +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" + +#include +#include + +char *platform_create_id_string(void); +int platform_random(int max); +uint64_t platform_tick_get_ms(void); + +#define ESP_MEM_CHECK(TAG, a, action) if (!(a)) { \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Memory exhausted"); \ + action; \ + } + +#define ESP_OK_CHECK(TAG, a, action) if ((a) != ESP_OK) { \ + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Failed"); \ + action; \ + } + +#endif diff --git a/managed_components/esp_mqtt/lib/mqtt5_msg.c b/managed_components/esp_mqtt/lib/mqtt5_msg.c new file mode 100644 index 0000000..32ff929 --- /dev/null +++ b/managed_components/esp_mqtt/lib/mqtt5_msg.c @@ -0,0 +1,1047 @@ +#include +#include "mqtt5_msg.h" +#include "mqtt_client.h" +#include "mqtt_config.h" +#include "platform.h" +#include "esp_log.h" + +#define MQTT5_MAX_FIXED_HEADER_SIZE 5 + +static const char *TAG = "mqtt5_msg"; + +#define APPEND_CHECK(a, ret) if(a == -1) { \ + ESP_LOGE(TAG,"%s(%d) fail",__FUNCTION__, __LINE__); \ + return (ret); \ + } +#define MQTT5_SHARED_SUB "$share/%s/%s" +#define MQTT5_CONVERT_ONE_BYTE_TO_FOUR(i, a, b, c, d) i = (a << 24); \ + i |= (b << 16); \ + i |= (c << 8); \ + i |= d; + +#define MQTT5_CONVERT_ONE_BYTE_TO_TWO(i, a, b) i = (a << 8); \ + i |= b; + +#define MQTT5_CONVERT_TWO_BYTE(i, a) i = (a >> 8) & 0xff; \ + i = a & 0xff; + +enum mqtt5_connect_flag { + MQTT5_CONNECT_FLAG_USERNAME = 1 << 7, + MQTT5_CONNECT_FLAG_PASSWORD = 1 << 6, + MQTT5_CONNECT_FLAG_WILL_RETAIN = 1 << 5, + MQTT5_CONNECT_FLAG_WILL = 1 << 2, + MQTT5_CONNECT_FLAG_CLEAN_SESSION = 1 << 1 +}; + +static void generate_variable_len(size_t len, uint8_t *len_bytes, uint8_t *encoded_lens) +{ + uint8_t bytes = 0; + do { + uint8_t i = len % 128; + len /= 128; + if (len > 0) { + i |= 0x80; + } + encoded_lens[bytes ++] = i; + } while (len > 0); + *len_bytes = bytes; +} + +static size_t get_variable_len(uint8_t *buffer, size_t offset, size_t buffer_length, uint8_t *len_bytes) +{ + *len_bytes = 0; + size_t len = 0, i = 0; + for (i = offset; i < buffer_length; i ++) { + len += (buffer[i] & 0x7f) << (7 * (i - offset)); + if ((buffer[i] & 0x80) == 0) { + i ++; + break; + } + } + *len_bytes = i - offset; + return len; +} + +static int update_property_len_value(mqtt_connection_t *connection, size_t property_len, int property_offset) +{ + uint8_t encoded_lens[4] = {0}, len_bytes = 0; + size_t len = property_len, message_offset = property_offset + property_len; + generate_variable_len(len, &len_bytes, encoded_lens); + int offset = len_bytes - 1; + + connection->outbound_message.length += offset; + if (connection->outbound_message.length > connection->buffer_length) { + return -1; + } + + if (offset > 0) { + for (int i = 0; i < property_len; i ++) { + connection->buffer[message_offset + offset] = connection->buffer[message_offset]; + message_offset --; + } + } + + for (int i = 0; i < len_bytes; i ++) { + connection->buffer[property_offset ++] = encoded_lens[i]; + } + return offset; +} + +static int append_property(mqtt_connection_t *connection, uint8_t property_type, uint8_t len_occupy, const char *data, size_t data_len) +{ + if ((connection->outbound_message.length + len_occupy + (data ? data_len : 0) + (property_type ? 1 : 0)) > connection->buffer_length) { + return -1; + } + + size_t origin_message_len = connection->outbound_message.length; + if (property_type) { + connection->buffer[connection->outbound_message.length ++] = property_type; + } + + if (len_occupy == 0) { + uint8_t encoded_lens[4] = {0}, len_bytes = 0; + generate_variable_len(data_len, &len_bytes, encoded_lens); + for (int j = 0; j < len_bytes; j ++) { + connection->buffer[connection->outbound_message.length ++] = encoded_lens[j]; + } + } else { + for (int i = 1; i <= len_occupy; i ++) { + connection->buffer[connection->outbound_message.length ++] = (data_len >> (8 * (len_occupy - i))) & 0xff; + } + } + + if (data) { + memcpy(connection->buffer + connection->outbound_message.length, data, data_len); + connection->outbound_message.length += data_len; + } + + return connection->outbound_message.length - origin_message_len; +} + +static uint16_t append_message_id(mqtt_connection_t *connection, uint16_t message_id) +{ + // If message_id is zero then we should assign one, otherwise + // we'll use the one supplied by the caller + while (message_id == 0) { +#if MQTT_MSG_ID_INCREMENTAL + message_id = ++ connection->last_message_id; +#else + message_id = platform_random(65535); +#endif + } + + if (connection->outbound_message.length + 2 > connection->buffer_length) { + return 0; + } + + MQTT5_CONVERT_TWO_BYTE(connection->buffer[connection->outbound_message.length ++], message_id) + + return message_id; +} + +static int init_message(mqtt_connection_t *connection) +{ + connection->outbound_message.length = MQTT5_MAX_FIXED_HEADER_SIZE; + return MQTT5_MAX_FIXED_HEADER_SIZE; +} + +static mqtt_message_t *fail_message(mqtt_connection_t *connection) +{ + connection->outbound_message.data = connection->buffer; + connection->outbound_message.length = 0; + return &connection->outbound_message; +} + +static mqtt_message_t *fini_message(mqtt_connection_t *connection, int type, int dup, int qos, int retain) +{ + int message_length = connection->outbound_message.length - MQTT5_MAX_FIXED_HEADER_SIZE; + int total_length = message_length; + uint8_t encoded_lens[4] = {0}, len_bytes = 0; + // Check if we have fragmented message and update total_len + if (connection->outbound_message.fragmented_msg_total_length) { + total_length = connection->outbound_message.fragmented_msg_total_length - MQTT5_MAX_FIXED_HEADER_SIZE; + } + + // Encode MQTT message length + generate_variable_len(total_length, &len_bytes, encoded_lens); + + // Sanity check for MQTT header + if (len_bytes + 1 > MQTT5_MAX_FIXED_HEADER_SIZE) { + return fail_message(connection); + } + + // Save the header bytes + connection->outbound_message.length = message_length + len_bytes + 1; // msg len + encoded_size len + type (1 byte) + int offs = MQTT5_MAX_FIXED_HEADER_SIZE - 1 - len_bytes; + connection->outbound_message.data = connection->buffer + offs; + connection->outbound_message.fragmented_msg_data_offset -= offs; + // type byte + connection->buffer[offs ++] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); + // length bytes + for (int j = 0; j < len_bytes; j ++) { + connection->buffer[offs ++] = encoded_lens[j]; + } + + return &connection->outbound_message; +} + +static esp_err_t mqtt5_msg_set_user_property(mqtt5_user_property_handle_t *user_property, char *key, size_t key_len, char *value, size_t value_len) +{ + if (!*user_property) { + *user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, *user_property, return ESP_FAIL); + STAILQ_INIT(*user_property); + } + + mqtt5_user_property_item_t user_property_item = calloc(1, sizeof(mqtt5_user_property_t)); + ESP_MEM_CHECK(TAG, user_property_item, return ESP_FAIL;); + user_property_item->key = calloc(1, key_len + 1); + ESP_MEM_CHECK(TAG, user_property_item->key, { + free(user_property_item); + return ESP_FAIL; + }); + memcpy(user_property_item->key, key, key_len); + user_property_item->key[key_len] = '\0'; + + user_property_item->value = calloc(1, value_len + 1); + ESP_MEM_CHECK(TAG, user_property_item->value, { + free(user_property_item->key); + free(user_property_item); + return ESP_FAIL; + }); + memcpy(user_property_item->value, value, value_len); + user_property_item->value[value_len] = '\0'; + + STAILQ_INSERT_TAIL(*user_property, user_property_item, next); + return ESP_OK; +} + +static mqtt5_user_property_handle_t mqtt5_msg_get_user_property(uint8_t *buffer, size_t buffer_length) +{ + mqtt5_user_property_handle_t user_porperty = NULL; + uint8_t *property = buffer; + uint16_t property_offset = 0, len = 0; + while (property_offset < buffer_length) { + uint8_t property_id = property[property_offset ++]; + switch (property_id) { + case MQTT5_PROPERTY_REASON_STRING: //only print now + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_REASON_STRING %.*s", len, &property[property_offset]); + property_offset += len; + continue; + case MQTT5_PROPERTY_USER_PROPERTY: { + uint8_t *key = NULL, *value = NULL; + size_t key_len = 0, value_len = 0; + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + key = &property[property_offset]; + key_len = len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_USER_PROPERTY key: %.*s", key_len, (char *)key); + property_offset += len; + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + value = &property[property_offset]; + value_len = len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_USER_PROPERTY value: %.*s", value_len, (char *)value); + property_offset += len; + if (mqtt5_msg_set_user_property(&user_porperty, (char *)key, key_len, (char *)value, value_len) != ESP_OK) { + ESP_LOGE(TAG, "mqtt5_msg_set_user_property fail"); + goto err; + } + continue; + } + default: + ESP_LOGW(TAG, "Unknow property id 0x%02x", property_id); + goto err; + } + } + return user_porperty; +err: + esp_mqtt5_client_delete_user_property(user_porperty); + return NULL; +} + +uint16_t mqtt5_get_id(uint8_t *buffer, size_t length) +{ + int topiclen = 0; + uint8_t len_bytes = 0; + size_t offset = 1; + size_t totlen = get_variable_len(buffer, offset, length, &len_bytes); + offset += len_bytes; + totlen += offset; + + if (offset + 2 > length) { + return 0; + } + + switch (mqtt5_get_type(buffer)) { + case MQTT_MSG_TYPE_PUBLISH: { + MQTT5_CONVERT_ONE_BYTE_TO_TWO(topiclen, buffer[offset++], buffer[offset++]) + offset += topiclen; + if (offset + 2 > length) { + return 0; + } + if (mqtt_get_qos(buffer) == 0) { + return 0; + } + return (buffer[offset] << 8) | buffer[offset + 1]; + } + case MQTT_MSG_TYPE_PUBACK: + case MQTT_MSG_TYPE_PUBREC: + case MQTT_MSG_TYPE_PUBREL: + case MQTT_MSG_TYPE_PUBCOMP: + case MQTT_MSG_TYPE_SUBACK: + case MQTT_MSG_TYPE_UNSUBACK: + case MQTT_MSG_TYPE_SUBSCRIBE: + case MQTT_MSG_TYPE_UNSUBSCRIBE: { + return (buffer[offset] << 8) | buffer[offset + 1]; + } + default: + return 0; + } +} + +char *mqtt5_get_publish_property_payload(uint8_t *buffer, size_t buffer_length, char **msg_topic, size_t *msg_topic_len, esp_mqtt5_publish_resp_property_t *resp_property, uint16_t *property_len, size_t *payload_len, mqtt5_user_property_handle_t *user_property) +{ + *user_property = NULL; + uint8_t len_bytes = 0; + size_t offset = 1; + size_t totlen = get_variable_len(buffer, offset, buffer_length, &len_bytes); + offset += len_bytes; + totlen += offset; + + size_t topic_len = buffer[offset ++] << 8; + topic_len |= buffer[offset ++] & 0xff; + *msg_topic = (char *)(buffer + offset); + *msg_topic_len = topic_len; + offset += topic_len; + + if (offset >= buffer_length) { + return NULL; + } + + if (mqtt5_get_qos(buffer) > 0) { + if (offset + 2 >= buffer_length) { + return NULL; + } + offset += 2; // skip the message id + } + + *property_len = get_variable_len(buffer, offset, buffer_length, &len_bytes); + offset += len_bytes; + + uint16_t len = 0, property_offset = 0; + uint8_t *property = (buffer + offset); + while (property_offset < *property_len) { + uint8_t property_id = property[property_offset ++]; + switch (property_id) { + case MQTT5_PROPERTY_PAYLOAD_FORMAT_INDICATOR: + resp_property->payload_format_indicator = property[property_offset ++]; + ESP_LOGD(TAG, "MQTT5_PROPERTY_PAYLOAD_FORMAT_INDICATOR %d", resp_property->payload_format_indicator); + continue; + case MQTT5_PROPERTY_MESSAGE_EXPIRY_INTERVAL: + MQTT5_CONVERT_ONE_BYTE_TO_FOUR(resp_property->message_expiry_interval, property[property_offset ++], property[property_offset ++], property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_MESSAGE_EXPIRY_INTERVAL %"PRIu32, resp_property->message_expiry_interval); + continue; + case MQTT5_PROPERTY_TOPIC_ALIAS: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(resp_property->topic_alias, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_TOPIC_ALIAS %d", resp_property->topic_alias); + continue; + case MQTT5_PROPERTY_RESPONSE_TOPIC: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(resp_property->response_topic_len, property[property_offset ++], property[property_offset ++]) + resp_property->response_topic = (char *)(property + property_offset); + property_offset += resp_property->response_topic_len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_RESPONSE_TOPIC %.*s", resp_property->response_topic_len, resp_property->response_topic); + continue; + case MQTT5_PROPERTY_CORRELATION_DATA: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(resp_property->correlation_data_len, property[property_offset ++], property[property_offset ++]) + resp_property->correlation_data = (char *)(property + property_offset); + property_offset += resp_property->correlation_data_len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_CORRELATION_DATA length %d", resp_property->correlation_data_len); + continue; + case MQTT5_PROPERTY_SUBSCRIBE_IDENTIFIER: + resp_property->subscribe_id = get_variable_len(property, property_offset, buffer_length, &len_bytes); + property_offset += len_bytes; + ESP_LOGD(TAG, "MQTT5_PROPERTY_SUBSCRIBE_IDENTIFIER %d", resp_property->subscribe_id); + continue; + case MQTT5_PROPERTY_CONTENT_TYPE: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(resp_property->content_type_len, property[property_offset ++], property[property_offset ++]) + resp_property->content_type = (char *)(property + property_offset); + property_offset += resp_property->content_type_len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_CONTENT_TYPE %.*s", resp_property->content_type_len, resp_property->content_type); + continue; + case MQTT5_PROPERTY_USER_PROPERTY: { + uint8_t *key = NULL, *value = NULL; + size_t key_len = 0, value_len = 0; + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + key = &property[property_offset]; + key_len = len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_USER_PROPERTY key: %.*s", key_len, (char *)key); + property_offset += len; + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + value = &property[property_offset]; + value_len = len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_USER_PROPERTY value: %.*s", value_len, (char *)value); + property_offset += len; + if (mqtt5_msg_set_user_property(user_property, (char *)key, key_len, (char *)value, value_len) != ESP_OK) { + esp_mqtt5_client_delete_user_property(*user_property); + *user_property = NULL; + ESP_LOGE(TAG, "mqtt5_msg_set_user_property fail"); + return NULL; + } + continue; + } + case MQTT5_PROPERTY_REASON_STRING: //only print now + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_REASON_STRING %.*s", len, &property[property_offset]); + property_offset += len; + continue; + default: + ESP_LOGW(TAG, "Unknow publish property id 0x%02x", property_id); + return NULL; + } + } + + offset += property_offset; + if (totlen <= buffer_length) { + *payload_len = totlen - offset; + } else { + *payload_len = buffer_length - offset; + } + return (char *)(buffer + offset); +} + +char *mqtt5_get_suback_data(uint8_t *buffer, size_t *length, mqtt5_user_property_handle_t *user_property) +{ + uint8_t len_bytes = 0; + size_t offset = 1; + size_t totlen = get_variable_len(buffer, offset, *length, &len_bytes); + offset += len_bytes; + totlen += offset; + + if (totlen > *length) { + goto err; + } + offset += 2; // skip the message id + if (offset < totlen) { + size_t property_len = get_variable_len(buffer, offset, totlen, &len_bytes); + offset += len_bytes; + *user_property = mqtt5_msg_get_user_property(buffer + offset, property_len); + offset += property_len; + if (offset < totlen) { + *length = totlen - offset; + return (char *)(buffer + offset); + } + } +err: + *user_property = NULL; + *length = 0; + return NULL; +} + +char *mqtt5_get_puback_data(uint8_t *buffer, size_t *length, mqtt5_user_property_handle_t *user_property) +{ + uint8_t len_bytes = 0; + size_t offset = 1; + size_t totlen = get_variable_len(buffer, offset, *length, &len_bytes); + offset += len_bytes; + totlen += offset; + + offset += 2; // skip the message id + if (offset < totlen) { + *length = 1; + char *data = (char *)(buffer + offset); + offset ++; + if (offset < totlen) { + size_t property_len = get_variable_len(buffer, offset, totlen, &len_bytes); + offset += len_bytes; + *user_property = mqtt5_msg_get_user_property(buffer + offset, property_len); + } + return data; + } else { + *length = 0; + return NULL; + } +} + +mqtt_message_t *mqtt5_msg_connect(mqtt_connection_t *connection, mqtt_connect_info_t *info, esp_mqtt5_connection_property_storage_t *property, esp_mqtt5_connection_will_property_storage_t *will_property) +{ + init_message(connection); + connection->buffer[connection->outbound_message.length ++] = 0; // Variable header length MSB + /* Defaults to protocol version 5 values */ + connection->buffer[connection->outbound_message.length ++] = 4; // Variable header length LSB + memcpy(&connection->buffer[connection->outbound_message.length], "MQTT", 4); // Protocol name + connection->outbound_message.length += 4; + connection->buffer[connection->outbound_message.length ++] = 5; // Protocol version + + int flags_offset = connection->outbound_message.length; + connection->buffer[connection->outbound_message.length ++] = 0; // Flags + MQTT5_CONVERT_TWO_BYTE(connection->buffer[connection->outbound_message.length ++], info->keepalive) // Keep-alive + + if (info->clean_session) { + connection->buffer[flags_offset] |= MQTT5_CONNECT_FLAG_CLEAN_SESSION; + } + + //Add properties + int properties_offset = connection->outbound_message.length; + connection->outbound_message.length ++; + if (property->session_expiry_interval) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_SESSION_EXPIRY_INTERVAL, 4, NULL, property->session_expiry_interval), fail_message(connection)); + } + if (property->maximum_packet_size) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_MAXIMUM_PACKET_SIZE, 4, NULL, property->maximum_packet_size), fail_message(connection)); + } + if (property->receive_maximum) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_RECEIVE_MAXIMUM, 2, NULL, property->receive_maximum), fail_message(connection)); + } + if (property->topic_alias_maximum) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_TOPIC_ALIAS_MAXIMIM, 2, NULL, property->topic_alias_maximum), fail_message(connection)); + } + if (property->request_resp_info) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_REQUEST_RESP_INFO, 1, NULL, 1), fail_message(connection)); + } + if (property->request_problem_info) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_REQUEST_PROBLEM_INFO, 1, NULL, 1), fail_message(connection)); + } + if (property->user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, property->user_property, next) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_USER_PROPERTY, 2, item->key, strlen(item->key)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, item->value, strlen(item->value)), fail_message(connection)); + } + } + APPEND_CHECK(update_property_len_value(connection, connection->outbound_message.length - properties_offset - 1, properties_offset), fail_message(connection)); + + if (info->client_id != NULL && info->client_id[0] != '\0') { + APPEND_CHECK(append_property(connection, 0, 2, info->client_id, strlen(info->client_id)), fail_message(connection)); + } else { + APPEND_CHECK(append_property(connection, 0, 2, NULL, 0), fail_message(connection)); + } + + //Add will properties + if (info->will_topic != NULL && info->will_topic[0] != '\0') { + properties_offset = connection->outbound_message.length; + connection->outbound_message.length ++; + if (will_property->will_delay_interval) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_WILL_DELAY_INTERVAL, 4, NULL, will_property->will_delay_interval), fail_message(connection)); + } + if (will_property->payload_format_indicator) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_PAYLOAD_FORMAT_INDICATOR, 1, NULL, 1), fail_message(connection)); + } + if (will_property->message_expiry_interval) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_MESSAGE_EXPIRY_INTERVAL, 4, NULL, will_property->message_expiry_interval), fail_message(connection)); + } + if (will_property->content_type) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_CONTENT_TYPE, 2, will_property->content_type, strlen(will_property->content_type)), fail_message(connection)); + } + if (will_property->response_topic) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_RESPONSE_TOPIC, 2, will_property->response_topic, strlen(will_property->response_topic)), fail_message(connection)); + } + if (will_property->correlation_data && will_property->correlation_data_len) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_CORRELATION_DATA, 2, will_property->correlation_data, will_property->correlation_data_len), fail_message(connection)); + } + if (will_property->user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, will_property->user_property, next) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_USER_PROPERTY, 2, item->key, strlen(item->key)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, item->value, strlen(item->value)), fail_message(connection)); + } + } + APPEND_CHECK(update_property_len_value(connection, connection->outbound_message.length - properties_offset - 1, properties_offset), fail_message(connection)); + + APPEND_CHECK(append_property(connection, 0, 2, info->will_topic, strlen(info->will_topic)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, info->will_message, info->will_length), fail_message(connection)); + + connection->buffer[flags_offset] |= MQTT5_CONNECT_FLAG_WILL; + if (info->will_retain) { + connection->buffer[flags_offset] |= MQTT5_CONNECT_FLAG_WILL_RETAIN; + } + connection->buffer[flags_offset] |= (info->will_qos & 3) << 3; + } + + if (info->username != NULL && info->username[0] != '\0') { + APPEND_CHECK(append_property(connection, 0, 2, info->username, strlen(info->username)), fail_message(connection)); + connection->buffer[flags_offset] |= MQTT5_CONNECT_FLAG_USERNAME; + } + + if (info->password != NULL && info->password[0] != '\0') { + if (info->username == NULL || info->username[0] == '\0') { + /* In case if password is set without username, we need to set a zero length username. + * (otherwise we violate: MQTT-3.1.2-22: If the User Name Flag is set to 0 then the Password Flag MUST be set to 0.) + */ + APPEND_CHECK(append_property(connection, 0, 2, NULL, 0), fail_message(connection)); + connection->buffer[flags_offset] |= MQTT5_CONNECT_FLAG_USERNAME; + } + APPEND_CHECK(append_property(connection, 0, 2, info->password, strlen(info->password)), fail_message(connection)); + connection->buffer[flags_offset] |= MQTT5_CONNECT_FLAG_PASSWORD; + } + + return fini_message(connection, MQTT_MSG_TYPE_CONNECT, 0, 0, 0); +} + +esp_err_t mqtt5_msg_parse_connack_property(uint8_t *buffer, size_t buffer_len, mqtt_connect_info_t *connection_info, esp_mqtt5_connection_property_storage_t *connection_property, esp_mqtt5_connection_server_resp_property_t *resp_property, int *reason_code, uint8_t *ack_flag, mqtt5_user_property_handle_t *user_property) +{ + *reason_code = 0; + *user_property = NULL; + uint8_t len_bytes = 0; + size_t offset = 1; + size_t totlen = get_variable_len(buffer, offset, buffer_len, &len_bytes); + offset += len_bytes; + totlen += offset; + + if (totlen > buffer_len) { + ESP_LOGE(TAG, "Total length %d is over read len %d", totlen, buffer_len); + return ESP_FAIL; + } + + *ack_flag = buffer[offset ++]; //acknowledge flags + *reason_code = buffer[offset ++]; //reason code + size_t property_len = get_variable_len(buffer, offset, buffer_len, &len_bytes); + offset += len_bytes; + uint16_t property_offset = 0, len = 0; + uint8_t *property = (buffer + offset); + while (property_offset < property_len) { + uint8_t property_id = property[property_offset ++]; + switch (property_id) { + case MQTT5_PROPERTY_SESSION_EXPIRY_INTERVAL: + MQTT5_CONVERT_ONE_BYTE_TO_FOUR(connection_property->session_expiry_interval, property[property_offset ++], property[property_offset ++], property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_SESSION_EXPIRY_INTERVAL %"PRIu32, connection_property->session_expiry_interval); + continue; + case MQTT5_PROPERTY_RECEIVE_MAXIMUM: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(resp_property->receive_maximum, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_RECEIVE_MAXIMUM %d", resp_property->receive_maximum); + continue; + case MQTT5_PROPERTY_MAXIMUM_QOS: + resp_property->max_qos = property[property_offset ++]; + ESP_LOGD(TAG, "MQTT5_PROPERTY_MAXIMUM_QOS %d", resp_property->max_qos); + continue; + case MQTT5_PROPERTY_RETAIN_AVAILABLE: + resp_property->retain_available = property[property_offset ++]; + ESP_LOGD(TAG, "MQTT5_PROPERTY_RETAIN_AVAILABLE %d", resp_property->retain_available); + continue; + case MQTT5_PROPERTY_MAXIMUM_PACKET_SIZE: + MQTT5_CONVERT_ONE_BYTE_TO_FOUR(resp_property->maximum_packet_size, property[property_offset ++], property[property_offset ++], property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_MAXIMUM_PACKET_SIZE %"PRIu32, resp_property->maximum_packet_size); + continue; + case MQTT5_PROPERTY_ASSIGNED_CLIENT_IDENTIFIER: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + if (connection_info->client_id) { + free(connection_info->client_id); + } + connection_info->client_id = calloc(1, len + 1); + if (!connection_info->client_id) { + ESP_LOGE(TAG, "Failed to calloc %d data", len); + return ESP_FAIL; + } + memcpy(connection_info->client_id, &property[property_offset], len); + connection_info->client_id[len] = '\0'; + property_offset += len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_ASSIGNED_CLIENT_IDENTIFIER %s", connection_info->client_id); + continue; + case MQTT5_PROPERTY_TOPIC_ALIAS_MAXIMIM: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(resp_property->topic_alias_maximum, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_TOPIC_ALIAS_MAXIMIM %d", resp_property->topic_alias_maximum); + continue; + case MQTT5_PROPERTY_REASON_STRING: //only print now + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_REASON_STRING %.*s", len, &property[property_offset]); + property_offset += len; + continue; + case MQTT5_PROPERTY_USER_PROPERTY: { + uint8_t *key = NULL, *value = NULL; + size_t key_len = 0, value_len = 0; + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + key = &property[property_offset]; + key_len = len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_USER_PROPERTY key: %.*s", key_len, (char *)key); + property_offset += len; + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + value = &property[property_offset]; + value_len = len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_USER_PROPERTY value: %.*s", value_len, (char *)value); + property_offset += len; + if (mqtt5_msg_set_user_property(user_property, (char *)key, key_len, (char *)value, value_len) != ESP_OK) { + esp_mqtt5_client_delete_user_property(*user_property); + *user_property = NULL; + ESP_LOGE(TAG, "mqtt5_msg_set_user_property fail"); + return ESP_FAIL; + } + continue; + } + case MQTT5_PROPERTY_WILDCARD_SUBSCR_AVAILABLE: + resp_property->wildcard_subscribe_available = property[property_offset++]; + ESP_LOGD(TAG, "MQTT5_PROPERTY_WILDCARD_SUBSCR_AVAILABLE %d", resp_property->wildcard_subscribe_available); + continue; + case MQTT5_PROPERTY_SUBSCR_IDENTIFIER_AVAILABLE: + resp_property->subscribe_identifiers_available = property[property_offset++]; + ESP_LOGD(TAG, "MQTT5_PROPERTY_SUBSCR_IDENTIFIER_AVAILABLE %d", resp_property->subscribe_identifiers_available); + continue; + case MQTT5_PROPERTY_SHARED_SUBSCR_AVAILABLE: + resp_property->shared_subscribe_available = property[property_offset++]; + ESP_LOGD(TAG, "MQTT5_PROPERTY_SHARED_SUBSCR_AVAILABLE %d", resp_property->shared_subscribe_available); + continue; + case MQTT5_PROPERTY_SERVER_KEEP_ALIVE: + MQTT5_CONVERT_ONE_BYTE_TO_TWO(connection_info->keepalive, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_SERVER_KEEP_ALIVE %lld", connection_info->keepalive); + continue; + case MQTT5_PROPERTY_RESP_INFO: + if (resp_property->response_info) { + free(resp_property->response_info); + } + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + resp_property->response_info = calloc(1, len + 1); + if (!resp_property->response_info) { + ESP_LOGE(TAG, "Failed to calloc %d data", len); + return ESP_FAIL; + } + memcpy(resp_property->response_info, &property[property_offset], len); + resp_property->response_info[len] = '\0'; + property_offset += len; + ESP_LOGD(TAG, "MQTT5_PROPERTY_RESP_INFO %s", resp_property->response_info); + continue; + case MQTT5_PROPERTY_SERVER_REFERENCE: //only print now + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_SERVER_REFERENCE %.*s", len, &property[property_offset]); + property_offset += len; + continue; + case MQTT5_PROPERTY_AUTHENTICATION_METHOD: //only print now + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_AUTHENTICATION_METHOD %.*s", len, &property[property_offset]); + property_offset += len; + continue; + case MQTT5_PROPERTY_AUTHENTICATION_DATA: //only print now + MQTT5_CONVERT_ONE_BYTE_TO_TWO(len, property[property_offset ++], property[property_offset ++]) + ESP_LOGD(TAG, "MQTT5_PROPERTY_AUTHENTICATION_DATA length %d", len); + property_offset += len; + continue; + default: + ESP_LOGW(TAG, "Unknow connack property id 0x%02x", property_id); + return ESP_FAIL; + } + } + return ESP_OK; +} + +mqtt_message_t *mqtt5_msg_publish(mqtt_connection_t *connection, const char *topic, const char *data, int data_length, int qos, int retain, uint16_t *message_id, const esp_mqtt5_publish_property_config_t *property, const char *resp_info) +{ + init_message(connection); + + if ((topic == NULL || topic[0] == '\0') && (!property || !property->topic_alias)){ + ESP_LOGE(TAG, "Message must have a topic filter or a topic alias set"); + return fail_message(connection); + } + int topic_len = (topic == NULL || topic[0] == '\0') ? 0 : strlen(topic); + APPEND_CHECK(append_property(connection, 0, 2, topic, topic_len), fail_message(connection)); + + if (data == NULL && data_length > 0) { + return fail_message(connection); + } + + if (qos > 0) { + if ((*message_id = append_message_id(connection, 0)) == 0) { + return fail_message(connection); + } + } else { + *message_id = 0; + } + + int properties_offset = connection->outbound_message.length; + connection->outbound_message.length ++; + + if (property) { + if (property->payload_format_indicator) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_PAYLOAD_FORMAT_INDICATOR, 1, NULL, 1), fail_message(connection)); + } + if (property->message_expiry_interval) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_MESSAGE_EXPIRY_INTERVAL, 4, NULL, property->message_expiry_interval), fail_message(connection)); + } + if (property->topic_alias) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_TOPIC_ALIAS, 2, NULL, property->topic_alias), fail_message(connection)); + } + if (property->response_topic) { + if (resp_info && strlen(resp_info)) { + uint16_t response_topic_size = strlen(property->response_topic) + strlen(resp_info) + 1; + char *response_topic = calloc(1, response_topic_size); + if (!response_topic) { + ESP_LOGE(TAG, "Failed to calloc %d memory", response_topic_size); + return fail_message(connection); + } + snprintf(response_topic, response_topic_size, "%s/%s", property->response_topic, resp_info); + if (append_property(connection, MQTT5_PROPERTY_RESPONSE_TOPIC, 2, response_topic, response_topic_size) == -1) { + ESP_LOGE(TAG, "%s(%d) fail", __FUNCTION__, __LINE__); + free(response_topic); + return fail_message(connection); + } + free(response_topic); + } else { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_RESPONSE_TOPIC, 2, property->response_topic, strlen(property->response_topic)), fail_message(connection)); + } + } + if (property->correlation_data && property->correlation_data_len) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_CORRELATION_DATA, 2, property->correlation_data, property->correlation_data_len), fail_message(connection)); + } + if (property->user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, property->user_property, next) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_USER_PROPERTY, 2, item->key, strlen(item->key)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, item->value, strlen(item->value)), fail_message(connection)); + } + } + if (property->content_type) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_CONTENT_TYPE, 2, property->content_type, strlen(property->content_type)), fail_message(connection)); + } + } + APPEND_CHECK(update_property_len_value(connection, connection->outbound_message.length - properties_offset - 1, properties_offset), fail_message(connection)); + + if (connection->outbound_message.length + data_length > connection->buffer_length) { + // Not enough size in buffer -> fragment this message + connection->outbound_message.fragmented_msg_data_offset = connection->outbound_message.length; + memcpy(connection->buffer + connection->outbound_message.length, data, connection->buffer_length - connection->outbound_message.length); + connection->outbound_message.length = connection->buffer_length; + connection->outbound_message.fragmented_msg_total_length = data_length + connection->outbound_message.fragmented_msg_data_offset; + } else { + if (data != NULL) { + memcpy(connection->buffer + connection->outbound_message.length, data, data_length); + connection->outbound_message.length += data_length; + } + connection->outbound_message.fragmented_msg_total_length = 0; + } + return fini_message(connection, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain); +} + +int mqtt5_msg_get_reason_code(uint8_t *buffer, size_t length) +{ + uint8_t len_bytes = 0; + size_t offset = 1; // Message type + size_t variable_len = get_variable_len(buffer, offset, length, &len_bytes); + offset += len_bytes; + + switch (mqtt5_get_type(buffer)) { + case MQTT_MSG_TYPE_PUBACK: + case MQTT_MSG_TYPE_PUBREC: + case MQTT_MSG_TYPE_PUBREL: + case MQTT_MSG_TYPE_PUBCOMP: + if(variable_len == 2) { + return 0; + } + offset += 2; //skip the message id + if (offset >= length) { + ESP_LOGE(TAG, "Invalid control packet, reason code is absent"); + return -1; + } + return buffer[offset]; + case MQTT_MSG_TYPE_SUBACK: + case MQTT_MSG_TYPE_UNSUBACK: { + offset += 2; //skip the message id + if (offset >= length) { + return -1; + } + size_t property_len = get_variable_len(buffer, offset, length, &len_bytes); + offset = offset + len_bytes + property_len; + if (offset >= length) { + ESP_LOGE(TAG, "Invalid control packet, reason code is absent"); + return -1; + } + return buffer[offset]; + } + case MQTT_MSG_TYPE_DISCONNECT: + if (offset >= length) { + return -1; + } else { + return buffer[offset]; + } + default: + break; + } + return -1; +} + +mqtt_message_t *mqtt5_msg_subscribe(mqtt_connection_t *connection, const esp_mqtt_topic_t *topic_list, int size, uint16_t *message_id, const esp_mqtt5_subscribe_property_config_t *property) +{ + init_message(connection); + + if ((*message_id = append_message_id(connection, 0)) == 0) { + return fail_message(connection); + } + + int properties_offset = connection->outbound_message.length; + connection->outbound_message.length ++; + + if (property) { + if (property->subscribe_id) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_SUBSCRIBE_IDENTIFIER, 0, NULL, property->subscribe_id), fail_message(connection)); + } + if (property->user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, property->user_property, next) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_USER_PROPERTY, 2, item->key, strlen(item->key)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, item->value, strlen(item->value)), fail_message(connection)); + } + } + } + APPEND_CHECK(update_property_len_value(connection, connection->outbound_message.length - properties_offset - 1, properties_offset), fail_message(connection)); + + for (int topic_number = 0; topic_number < size; ++topic_number) { + if (topic_list[topic_number].filter[0] == '\0') { + return fail_message(connection); + } + if (property && property->is_share_subscribe) { + uint16_t shared_topic_size = strlen(topic_list[topic_number].filter) + strlen(MQTT5_SHARED_SUB) + strlen(property->share_name); + char *shared_topic = calloc(1, shared_topic_size); + if (!shared_topic) { + ESP_LOGE(TAG, "Failed to calloc %d memory", shared_topic_size); + fail_message(connection); + } + snprintf(shared_topic, shared_topic_size, MQTT5_SHARED_SUB, property->share_name, topic_list[topic_number].filter); + if (append_property(connection, 0, 2, shared_topic, strlen(shared_topic)) == -1) { + ESP_LOGE(TAG, "%s(%d) fail", __FUNCTION__, __LINE__); + free(shared_topic); + return fail_message(connection); + } + free(shared_topic); + } else { + APPEND_CHECK(append_property(connection, 0, 2, topic_list[topic_number].filter, strlen(topic_list[topic_number].filter)), fail_message(connection)); + } + + if (connection->outbound_message.length + 1 > connection->buffer_length) { + return fail_message(connection); + } + connection->buffer[connection->outbound_message.length] = 0; + if (property) { + if (property->retain_handle > 0 && property->retain_handle < 3) { + connection->buffer[connection->outbound_message.length] |= (property->retain_handle & 3) << 4; + } + if (property->no_local_flag) { + connection->buffer[connection->outbound_message.length] |= (property->no_local_flag << 2); + } + if (property->retain_as_published_flag) { + connection->buffer[connection->outbound_message.length] |= (property->retain_as_published_flag << 3); + } + } + connection->buffer[connection->outbound_message.length] |= (topic_list[topic_number].qos & 3); + connection->outbound_message.length ++; + } + return fini_message(connection, MQTT_MSG_TYPE_SUBSCRIBE, 0, 1, 0); +} + +mqtt_message_t *mqtt5_msg_disconnect(mqtt_connection_t *connection, esp_mqtt5_disconnect_property_config_t *disconnect_property_info) +{ + init_message(connection); + int reason_offset = connection->outbound_message.length; + connection->buffer[connection->outbound_message.length ++] = 0; + int properties_offset = connection->outbound_message.length; + connection->outbound_message.length ++; + if (disconnect_property_info) { + if (disconnect_property_info->session_expiry_interval) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_SESSION_EXPIRY_INTERVAL, 4, NULL, disconnect_property_info->session_expiry_interval), fail_message(connection)); + } + if (disconnect_property_info->user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, disconnect_property_info->user_property, next) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_USER_PROPERTY, 2, item->key, strlen(item->key)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, item->value, strlen(item->value)), fail_message(connection)); + } + } + if (disconnect_property_info->disconnect_reason) { + connection->buffer[reason_offset] = disconnect_property_info->disconnect_reason; + } + } + APPEND_CHECK(update_property_len_value(connection, connection->outbound_message.length - properties_offset - 1, properties_offset), fail_message(connection)); + return fini_message(connection, MQTT_MSG_TYPE_DISCONNECT, 0, 0, 0); +} + +mqtt_message_t *mqtt5_msg_unsubscribe(mqtt_connection_t *connection, const char *topic, uint16_t *message_id, const esp_mqtt5_unsubscribe_property_config_t *property) +{ + init_message(connection); + + if (topic == NULL || topic[0] == '\0') { + return fail_message(connection); + } + + if ((*message_id = append_message_id(connection, 0)) == 0) { + return fail_message(connection); + } + + int properties_offset = connection->outbound_message.length; + connection->outbound_message.length ++; + if (property) { + if (property->user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, property->user_property, next) { + APPEND_CHECK(append_property(connection, MQTT5_PROPERTY_USER_PROPERTY, 2, item->key, strlen(item->key)), fail_message(connection)); + APPEND_CHECK(append_property(connection, 0, 2, item->value, strlen(item->value)), fail_message(connection)); + } + } + } + + APPEND_CHECK(update_property_len_value(connection, connection->outbound_message.length - properties_offset - 1, properties_offset), fail_message(connection)); + if (property && property->is_share_subscribe) { + uint16_t shared_topic_size = strlen(topic) + strlen(MQTT5_SHARED_SUB) + strlen(property->share_name); + char *shared_topic = calloc(1, shared_topic_size); + if (!shared_topic) { + ESP_LOGE(TAG, "Failed to calloc %d memory", shared_topic_size); + fail_message(connection); + } + snprintf(shared_topic, shared_topic_size, MQTT5_SHARED_SUB, property->share_name, topic); + if (append_property(connection, 0, 2, shared_topic, strlen(shared_topic)) == -1) { + ESP_LOGE(TAG, "%s(%d) fail", __FUNCTION__, __LINE__); + free(shared_topic); + return fail_message(connection); + } + free(shared_topic); + } else { + APPEND_CHECK(append_property(connection, 0, 2, topic, strlen(topic)), fail_message(connection)); + } + + return fini_message(connection, MQTT_MSG_TYPE_UNSUBSCRIBE, 0, 1, 0); +} + +mqtt_message_t *mqtt5_msg_puback(mqtt_connection_t *connection, uint16_t message_id) +{ + init_message(connection); + if (append_message_id(connection, message_id) == 0) { + return fail_message(connection); + } + connection->buffer[connection->outbound_message.length ++] = 0; // Regard it is success + int properties_offset = connection->outbound_message.length; + connection->outbound_message.length ++; + APPEND_CHECK(update_property_len_value(connection, connection->outbound_message.length - properties_offset - 1, properties_offset), fail_message(connection)); + return fini_message(connection, MQTT_MSG_TYPE_PUBACK, 0, 0, 0); +} + +mqtt_message_t *mqtt5_msg_pubrec(mqtt_connection_t *connection, uint16_t message_id) +{ + init_message(connection); + if (append_message_id(connection, message_id) == 0) { + return fail_message(connection); + } + connection->buffer[connection->outbound_message.length ++] = 0; // Regard it is success + int properties_offset = connection->outbound_message.length; + connection->outbound_message.length ++; + APPEND_CHECK(update_property_len_value(connection, connection->outbound_message.length - properties_offset - 1, properties_offset), fail_message(connection)); + return fini_message(connection, MQTT_MSG_TYPE_PUBREC, 0, 0, 0); +} + +mqtt_message_t *mqtt5_msg_pubrel(mqtt_connection_t *connection, uint16_t message_id) +{ + init_message(connection); + if (append_message_id(connection, message_id) == 0) { + return fail_message(connection); + } + connection->buffer[connection->outbound_message.length ++] = 0; // Regard it is success + int properties_offset = connection->outbound_message.length; + connection->outbound_message.length ++; + APPEND_CHECK(update_property_len_value(connection, connection->outbound_message.length - properties_offset - 1, properties_offset), fail_message(connection)); + return fini_message(connection, MQTT_MSG_TYPE_PUBREL, 0, 1, 0); +} + +mqtt_message_t *mqtt5_msg_pubcomp(mqtt_connection_t *connection, uint16_t message_id) +{ + init_message(connection); + if (append_message_id(connection, message_id) == 0) { + return fail_message(connection); + } + connection->buffer[connection->outbound_message.length ++] = 0; // Regard it is success + int properties_offset = connection->outbound_message.length; + connection->outbound_message.length ++; + APPEND_CHECK(update_property_len_value(connection, connection->outbound_message.length - properties_offset - 1, properties_offset), fail_message(connection)); + return fini_message(connection, MQTT_MSG_TYPE_PUBCOMP, 0, 0, 0); +} diff --git a/managed_components/esp_mqtt/lib/mqtt_msg.c b/managed_components/esp_mqtt/lib/mqtt_msg.c new file mode 100644 index 0000000..582fc92 --- /dev/null +++ b/managed_components/esp_mqtt/lib/mqtt_msg.c @@ -0,0 +1,637 @@ +/* +* Copyright (c) 2014, Stephen Robinson +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. Neither the name of the copyright holder nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* +*/ +#include +#include "mqtt_client.h" +#include "mqtt_msg.h" +#include "mqtt_config.h" +#include "platform.h" + +#define MQTT_MAX_FIXED_HEADER_SIZE 5 +#define MQTT_3_1_VARIABLE_HEADER_SIZE 12 +#define MQTT_3_1_1_VARIABLE_HEADER_SIZE 10 + +enum mqtt_connect_flag { + MQTT_CONNECT_FLAG_USERNAME = 1 << 7, + MQTT_CONNECT_FLAG_PASSWORD = 1 << 6, + MQTT_CONNECT_FLAG_WILL_RETAIN = 1 << 5, + MQTT_CONNECT_FLAG_WILL = 1 << 2, + MQTT_CONNECT_FLAG_CLEAN_SESSION = 1 << 1 +}; + +static int append_string(mqtt_connection_t *connection, const char *string, int len) +{ + if (connection->outbound_message.length + len + 2 > connection->buffer_length) { + return -1; + } + + connection->buffer[connection->outbound_message.length++] = len >> 8; + connection->buffer[connection->outbound_message.length++] = len & 0xff; + memcpy(connection->buffer + connection->outbound_message.length, string, len); + connection->outbound_message.length += len; + + return len + 2; +} + +static uint16_t append_message_id(mqtt_connection_t *connection, uint16_t message_id) +{ + // If message_id is zero then we should assign one, otherwise + // we'll use the one supplied by the caller + while (message_id == 0) { +#if MQTT_MSG_ID_INCREMENTAL + message_id = ++connection->last_message_id; +#else + message_id = platform_random(65535); +#endif + } + + if (connection->outbound_message.length + 2 > connection->buffer_length) { + return 0; + } + + connection->buffer[connection->outbound_message.length++] = message_id >> 8; + connection->buffer[connection->outbound_message.length++] = message_id & 0xff; + + return message_id; +} + +static int set_message_header_size(mqtt_connection_t *connection) +{ + connection->outbound_message.length = MQTT_MAX_FIXED_HEADER_SIZE; + return MQTT_MAX_FIXED_HEADER_SIZE; +} + +static mqtt_message_t *fail_message(mqtt_connection_t *connection) +{ + connection->outbound_message.data = connection->buffer; + connection->outbound_message.length = 0; + return &connection->outbound_message; +} + +static mqtt_message_t *fini_message(mqtt_connection_t *connection, int type, int dup, int qos, int retain) +{ + int message_length = connection->outbound_message.length - MQTT_MAX_FIXED_HEADER_SIZE; + int total_length = message_length; + int encoded_length = 0; + uint8_t encoded_lens[4] = {0}; + // Check if we have fragmented message and update total_len + if (connection->outbound_message.fragmented_msg_total_length) { + total_length = connection->outbound_message.fragmented_msg_total_length - MQTT_MAX_FIXED_HEADER_SIZE; + } + + // Encode MQTT message length + int len_bytes = 0; // size of encoded message length + do { + encoded_length = total_length % 128; + total_length /= 128; + if (total_length > 0) { + encoded_length |= 0x80; + } + encoded_lens[len_bytes] = encoded_length; + len_bytes++; + } while (total_length > 0); + + // Sanity check for MQTT header + if (len_bytes + 1 > MQTT_MAX_FIXED_HEADER_SIZE) { + return fail_message(connection); + } + + // Save the header bytes + connection->outbound_message.length = message_length + len_bytes + 1; // msg len + encoded_size len + type (1 byte) + int offs = MQTT_MAX_FIXED_HEADER_SIZE - 1 - len_bytes; + connection->outbound_message.data = connection->buffer + offs; + connection->outbound_message.fragmented_msg_data_offset -= offs; + // type byte + connection->buffer[offs++] = ((type & 0x0f) << 4) | ((dup & 1) << 3) | ((qos & 3) << 1) | (retain & 1); + // length bytes + for (int j = 0; j < len_bytes; j++) { + connection->buffer[offs++] = encoded_lens[j]; + } + + return &connection->outbound_message; +} + +size_t mqtt_get_total_length(const uint8_t *buffer, size_t length, int *fixed_size_len) +{ + int i; + size_t totlen = 0; + + for (i = 1; i < length; ++i) { + totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } + } + totlen += i; + if (fixed_size_len) { + *fixed_size_len = i; + } + + return totlen; +} + +bool mqtt_header_complete(uint8_t *buffer, size_t buffer_length) +{ + uint16_t i; + uint16_t topiclen; + + for (i = 1; i < MQTT_MAX_FIXED_HEADER_SIZE; ++i) { + if (i >= buffer_length) { + return false; + } + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } + } + // i is now the length of the fixed header + + if (i + 2 >= buffer_length) { + return false; + } + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; + + i += topiclen; + + if (mqtt_get_qos(buffer) > 0) { + i += 2; + } + // i is now the length of the fixed + variable header + return buffer_length >= i; +} + +char *mqtt_get_publish_topic(uint8_t *buffer, size_t *length) +{ + int i; + int topiclen; + + for (i = 1; i < *length; ++i) { + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } + } + + if (i + 2 >= *length) { + return NULL; + } + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; + + if (i + topiclen > *length) { + return NULL; + } + + *length = topiclen; + return (char *)(buffer + i); +} + +char *mqtt_get_publish_data(uint8_t *buffer, size_t *length) +{ + int i; + int totlen = 0; + int topiclen; + int blength = *length; + *length = 0; + + for (i = 1; i < blength; ++i) { + totlen += (buffer[i] & 0x7f) << (7 * (i - 1)); + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } + } + totlen += i; + + if (i + 2 >= blength) { + return NULL; + } + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; + + if (i + topiclen >= blength) { + return NULL; + } + + i += topiclen; + + if (mqtt_get_qos(buffer) > 0) { + if (i + 2 >= blength) { + return NULL; + } + i += 2; + } + + if (totlen < i) { + return NULL; + } + + if (totlen <= blength) { + *length = totlen - i; + } else { + *length = blength - i; + } + return (char *)(buffer + i); +} + +char *mqtt_get_suback_data(uint8_t *buffer, size_t *length) +{ + // SUBACK payload length = total length - (fixed header (2 bytes) + variable header (2 bytes)) + // This requires the remaining length to be encoded in 1 byte. + if (*length > 4) { + *length -= 4; + return (char *)(buffer + 4); + } + *length = 0; + return NULL; +} + +uint16_t mqtt_get_id(uint8_t *buffer, size_t length) +{ + if (length < 1) { + return 0; + } + + switch (mqtt_get_type(buffer)) { + case MQTT_MSG_TYPE_PUBLISH: { + int i; + int topiclen; + + for (i = 1; i < length; ++i) { + if ((buffer[i] & 0x80) == 0) { + ++i; + break; + } + } + + if (i + 2 >= length) { + return 0; + } + topiclen = buffer[i++] << 8; + topiclen |= buffer[i++]; + + if (i + topiclen > length) { + return 0; + } + i += topiclen; + + if (mqtt_get_qos(buffer) > 0) { + if (i + 2 > length) { + return 0; + } + //i += 2; + } else { + return 0; + } + + return (buffer[i] << 8) | buffer[i + 1]; + } + case MQTT_MSG_TYPE_PUBACK: + case MQTT_MSG_TYPE_PUBREC: + case MQTT_MSG_TYPE_PUBREL: + case MQTT_MSG_TYPE_PUBCOMP: + case MQTT_MSG_TYPE_SUBACK: + case MQTT_MSG_TYPE_UNSUBACK: + case MQTT_MSG_TYPE_SUBSCRIBE: + case MQTT_MSG_TYPE_UNSUBSCRIBE: { + // This requires the remaining length to be encoded in 1 byte, + // which it should be. + if (length >= 4 && (buffer[1] & 0x80) == 0) { + return (buffer[2] << 8) | buffer[3]; + } else { + return 0; + } + } + + default: + return 0; + } +} + +mqtt_message_t *mqtt_msg_connect(mqtt_connection_t *connection, mqtt_connect_info_t *info) +{ + + set_message_header_size(connection); + + int header_len; + if (info->protocol_ver == MQTT_PROTOCOL_V_3_1) { + header_len = MQTT_3_1_VARIABLE_HEADER_SIZE; + } else { + header_len = MQTT_3_1_1_VARIABLE_HEADER_SIZE; + } + + if (connection->outbound_message.length + header_len > connection->buffer_length) { + return fail_message(connection); + } + char *variable_header = (char *)(connection->buffer + connection->outbound_message.length); + connection->outbound_message.length += header_len; + + int header_idx = 0; + variable_header[header_idx++] = 0; // Variable header length MSB + + if (info->protocol_ver == MQTT_PROTOCOL_V_3_1) { + variable_header[header_idx++] = 6; // Variable header length LSB + memcpy(&variable_header[header_idx], "MQIsdp", 6); // Protocol name + header_idx = header_idx + 6; + variable_header[header_idx++] = 3; // Protocol version + } else { + /* Defaults to protocol version 3.1.1 values */ + variable_header[header_idx++] = 4; // Variable header length LSB + memcpy(&variable_header[header_idx], "MQTT", 4); // Protocol name + header_idx = header_idx + 4; + variable_header[header_idx++] = 4; // Protocol version + } + + int flags_offset = header_idx; + variable_header[header_idx++] = 0; // Flags + variable_header[header_idx++] = info->keepalive >> 8; // Keep-alive MSB + variable_header[header_idx] = info->keepalive & 0xff; // Keep-alive LSB + + if (info->clean_session) { + variable_header[flags_offset] |= MQTT_CONNECT_FLAG_CLEAN_SESSION; + } + + if (info->client_id != NULL && info->client_id[0] != '\0') { + if (append_string(connection, info->client_id, strlen(info->client_id)) < 0) { + return fail_message(connection); + } + } else { + if (append_string(connection, "", 0) < 0) { + return fail_message(connection); + } + } + + if (info->will_topic != NULL && info->will_topic[0] != '\0') { + if (append_string(connection, info->will_topic, strlen(info->will_topic)) < 0) { + return fail_message(connection); + } + + if (append_string(connection, info->will_message, info->will_length) < 0) { + return fail_message(connection); + } + + variable_header[flags_offset] |= MQTT_CONNECT_FLAG_WILL; + if (info->will_retain) { + variable_header[flags_offset] |= MQTT_CONNECT_FLAG_WILL_RETAIN; + } + variable_header[flags_offset] |= (info->will_qos & 3) << 3; + } + + if (info->username != NULL && info->username[0] != '\0') { + if (append_string(connection, info->username, strlen(info->username)) < 0) { + return fail_message(connection); + } + + variable_header[flags_offset] |= MQTT_CONNECT_FLAG_USERNAME; + } + + if (info->password != NULL && info->password[0] != '\0') { + if (info->username == NULL || info->username[0] == '\0') { + /* In case if password is set without username, we need to set a zero length username. + * (otherwise we violate: MQTT-3.1.2-22: If the User Name Flag is set to 0 then the Password Flag MUST be set to 0.) + */ + if (append_string(connection, "", 0) < 0) { + return fail_message(connection); + } + + variable_header[flags_offset] |= MQTT_CONNECT_FLAG_USERNAME; + } + + if (append_string(connection, info->password, strlen(info->password)) < 0) { + return fail_message(connection); + } + + variable_header[flags_offset] |= MQTT_CONNECT_FLAG_PASSWORD; + } + + return fini_message(connection, MQTT_MSG_TYPE_CONNECT, 0, 0, 0); +} + +mqtt_message_t *mqtt_msg_publish(mqtt_connection_t *connection, const char *topic, const char *data, int data_length, int qos, int retain, uint16_t *message_id) +{ + set_message_header_size(connection); + + if (topic == NULL || topic[0] == '\0') { + return fail_message(connection); + } + + if (append_string(connection, topic, strlen(topic)) < 0) { + return fail_message(connection); + } + + if (data == NULL && data_length > 0) { + return fail_message(connection); + } + + if (qos > 0) { + if ((*message_id = append_message_id(connection, 0)) == 0) { + return fail_message(connection); + } + } else { + *message_id = 0; + } + + if (data != NULL) { + if (connection->outbound_message.length + data_length > connection->buffer_length) { + // Not enough size in buffer -> fragment this message + connection->outbound_message.fragmented_msg_data_offset = connection->outbound_message.length; + memcpy(connection->buffer + connection->outbound_message.length, data, connection->buffer_length - connection->outbound_message.length); + connection->outbound_message.length = connection->buffer_length; + connection->outbound_message.fragmented_msg_total_length = data_length + connection->outbound_message.fragmented_msg_data_offset; + } else { + memcpy(connection->buffer + connection->outbound_message.length, data, data_length); + connection->outbound_message.length += data_length; + connection->outbound_message.fragmented_msg_total_length = 0; + } + } + return fini_message(connection, MQTT_MSG_TYPE_PUBLISH, 0, qos, retain); +} + +mqtt_message_t *mqtt_msg_puback(mqtt_connection_t *connection, uint16_t message_id) +{ + set_message_header_size(connection); + if (append_message_id(connection, message_id) == 0) { + return fail_message(connection); + } + return fini_message(connection, MQTT_MSG_TYPE_PUBACK, 0, 0, 0); +} + +mqtt_message_t *mqtt_msg_pubrec(mqtt_connection_t *connection, uint16_t message_id) +{ + set_message_header_size(connection); + if (append_message_id(connection, message_id) == 0) { + return fail_message(connection); + } + return fini_message(connection, MQTT_MSG_TYPE_PUBREC, 0, 0, 0); +} + +mqtt_message_t *mqtt_msg_pubrel(mqtt_connection_t *connection, uint16_t message_id) +{ + set_message_header_size(connection); + if (append_message_id(connection, message_id) == 0) { + return fail_message(connection); + } + return fini_message(connection, MQTT_MSG_TYPE_PUBREL, 0, 1, 0); +} + +mqtt_message_t *mqtt_msg_pubcomp(mqtt_connection_t *connection, uint16_t message_id) +{ + set_message_header_size(connection); + if (append_message_id(connection, message_id) == 0) { + return fail_message(connection); + } + return fini_message(connection, MQTT_MSG_TYPE_PUBCOMP, 0, 0, 0); +} + +mqtt_message_t *mqtt_msg_subscribe(mqtt_connection_t *connection, const esp_mqtt_topic_t topic_list[], int size, uint16_t *message_id) +{ + set_message_header_size(connection); + + if ((*message_id = append_message_id(connection, 0)) == 0) { + return fail_message(connection); + } + + for (int topic_number = 0; topic_number < size; ++topic_number) { + if (topic_list[topic_number].filter[0] == '\0') { + return fail_message(connection); + } + + if (append_string(connection, topic_list[topic_number].filter, strlen(topic_list[topic_number].filter)) < 0) { + return fail_message(connection); + } + + if (connection->outbound_message.length + 1 > connection->buffer_length) { + return fail_message(connection); + } + connection->buffer[connection->outbound_message.length] = topic_list[topic_number].qos; + connection->outbound_message.length ++; + } + + return fini_message(connection, MQTT_MSG_TYPE_SUBSCRIBE, 0, 1, 0); +} + +mqtt_message_t *mqtt_msg_unsubscribe(mqtt_connection_t *connection, const char *topic, uint16_t *message_id) +{ + set_message_header_size(connection); + + if (topic == NULL || topic[0] == '\0') { + return fail_message(connection); + } + + if ((*message_id = append_message_id(connection, 0)) == 0) { + return fail_message(connection); + } + + if (append_string(connection, topic, strlen(topic)) < 0) { + return fail_message(connection); + } + + return fini_message(connection, MQTT_MSG_TYPE_UNSUBSCRIBE, 0, 1, 0); +} + +mqtt_message_t *mqtt_msg_pingreq(mqtt_connection_t *connection) +{ + set_message_header_size(connection); + return fini_message(connection, MQTT_MSG_TYPE_PINGREQ, 0, 0, 0); +} + +mqtt_message_t *mqtt_msg_pingresp(mqtt_connection_t *connection) +{ + set_message_header_size(connection); + return fini_message(connection, MQTT_MSG_TYPE_PINGRESP, 0, 0, 0); +} + +mqtt_message_t *mqtt_msg_disconnect(mqtt_connection_t *connection) +{ + set_message_header_size(connection); + return fini_message(connection, MQTT_MSG_TYPE_DISCONNECT, 0, 0, 0); +} + +/* + * check flags: [MQTT-2.2.2-1], [MQTT-2.2.2-2] + * returns 0 if flags are invalid, otherwise returns 1 + */ +int mqtt_has_valid_msg_hdr(uint8_t *buffer, size_t length) +{ + int qos, dup; + + if (length < 1) { + return 0; + } + switch (mqtt_get_type(buffer)) { + case MQTT_MSG_TYPE_CONNECT: + case MQTT_MSG_TYPE_CONNACK: + case MQTT_MSG_TYPE_PUBACK: + case MQTT_MSG_TYPE_PUBREC: + case MQTT_MSG_TYPE_PUBCOMP: + case MQTT_MSG_TYPE_SUBACK: + case MQTT_MSG_TYPE_UNSUBACK: + case MQTT_MSG_TYPE_PINGREQ: + case MQTT_MSG_TYPE_PINGRESP: + case MQTT_MSG_TYPE_DISCONNECT: + return (buffer[0] & 0x0f) == 0; /* all flag bits are 0 */ + case MQTT_MSG_TYPE_PUBREL: + case MQTT_MSG_TYPE_SUBSCRIBE: + case MQTT_MSG_TYPE_UNSUBSCRIBE: + return (buffer[0] & 0x0f) == 0x02; /* only bit 1 is set */ + case MQTT_MSG_TYPE_PUBLISH: + qos = mqtt_get_qos(buffer); + dup = mqtt_get_dup(buffer); + /* + * there is no qos=3 [MQTT-3.3.1-4] + * dup flag must be set to 0 for all qos=0 messages [MQTT-3.3.1-2] + */ + return (qos < 3) && ((qos > 0) || (dup == 0)); + default: + return 0; + } +} + +esp_err_t mqtt_msg_buffer_init(mqtt_connection_t *connection, int buffer_size) +{ + memset(&connection->outbound_message, 0, sizeof(mqtt_message_t)); + connection->buffer = (uint8_t *)calloc(buffer_size, sizeof(uint8_t)); + if (!connection->buffer) { + return ESP_ERR_NO_MEM; + } + connection->buffer_length = buffer_size; + return ESP_OK; +} + +void mqtt_msg_buffer_destroy(mqtt_connection_t *connection) +{ + if (connection) { + free(connection->buffer); + } +} + + diff --git a/managed_components/esp_mqtt/lib/mqtt_outbox.c b/managed_components/esp_mqtt/lib/mqtt_outbox.c new file mode 100644 index 0000000..ab44e82 --- /dev/null +++ b/managed_components/esp_mqtt/lib/mqtt_outbox.c @@ -0,0 +1,221 @@ +#include "mqtt_outbox.h" +#include +#include +#include +#include "mqtt_config.h" +#include "sys/queue.h" +#include "esp_heap_caps.h" +#include "esp_log.h" + +#ifndef CONFIG_MQTT_CUSTOM_OUTBOX +static const char *TAG = "outbox"; + +typedef struct outbox_item { + char *buffer; + int len; + int msg_id; + int msg_type; + int msg_qos; + outbox_tick_t tick; + pending_state_t pending; + STAILQ_ENTRY(outbox_item) next; +} outbox_item_t; + +STAILQ_HEAD(outbox_list_t, outbox_item); + +struct outbox_t { + _Atomic uint64_t size; + struct outbox_list_t *list; +}; + +outbox_handle_t outbox_init(void) +{ + outbox_handle_t outbox = calloc(1, sizeof(struct outbox_t)); + ESP_MEM_CHECK(TAG, outbox, return NULL); + outbox->list = calloc(1, sizeof(struct outbox_list_t)); + ESP_MEM_CHECK(TAG, outbox->list, {free(outbox); return NULL;}); + outbox->size = 0; + STAILQ_INIT(outbox->list); + return outbox; +} + +outbox_item_handle_t outbox_enqueue(outbox_handle_t outbox, outbox_message_handle_t message, outbox_tick_t tick) +{ + outbox_item_handle_t item = calloc(1, sizeof(outbox_item_t)); + ESP_MEM_CHECK(TAG, item, return NULL); + item->msg_id = message->msg_id; + item->msg_type = message->msg_type; + item->msg_qos = message->msg_qos; + item->tick = tick; + item->len = message->len + message->remaining_len; + item->pending = QUEUED; + item->buffer = heap_caps_malloc(message->len + message->remaining_len, MQTT_OUTBOX_MEMORY); + ESP_MEM_CHECK(TAG, item->buffer, { + free(item); + return NULL; + }); + memcpy(item->buffer, message->data, message->len); + if (message->remaining_data) { + memcpy(item->buffer + message->len, message->remaining_data, message->remaining_len); + } + STAILQ_INSERT_TAIL(outbox->list, item, next); + outbox->size += item->len; + ESP_LOGD(TAG, "ENQUEUE msgid=%d, msg_type=%d, len=%d, size=%"PRIu64, message->msg_id, message->msg_type, message->len + message->remaining_len, outbox_get_size(outbox)); + return item; +} + +outbox_item_handle_t outbox_get(outbox_handle_t outbox, int msg_id) +{ + outbox_item_handle_t item; + STAILQ_FOREACH(item, outbox->list, next) { + if (item->msg_id == msg_id) { + return item; + } + } + return NULL; +} + +outbox_item_handle_t outbox_dequeue(outbox_handle_t outbox, pending_state_t pending, outbox_tick_t *tick) +{ + outbox_item_handle_t item; + STAILQ_FOREACH(item, outbox->list, next) { + if (item->pending == pending) { + if (tick) { + *tick = item->tick; + } + return item; + } + } + return NULL; +} + +esp_err_t outbox_delete_item(outbox_handle_t outbox, outbox_item_handle_t item_to_delete) +{ + outbox_item_handle_t item; + STAILQ_FOREACH(item, outbox->list, next) { + if (item == item_to_delete) { + STAILQ_REMOVE(outbox->list, item, outbox_item, next); + outbox->size -= item->len; + free(item->buffer); + free(item); + return ESP_OK; + } + } + return ESP_FAIL; +} + +uint8_t *outbox_item_get_data(outbox_item_handle_t item, size_t *len, uint16_t *msg_id, int *msg_type, int *qos) +{ + if (item) { + *len = item->len; + *msg_id = item->msg_id; + *msg_type = item->msg_type; + *qos = item->msg_qos; + return (uint8_t *)item->buffer; + } + return NULL; +} + +esp_err_t outbox_delete(outbox_handle_t outbox, int msg_id, int msg_type) +{ + outbox_item_handle_t item, tmp; + STAILQ_FOREACH_SAFE(item, outbox->list, next, tmp) { + if (item->msg_id == msg_id && (0xFF & (item->msg_type)) == msg_type) { + STAILQ_REMOVE(outbox->list, item, outbox_item, next); + outbox->size -= item->len; + free(item->buffer); + free(item); + ESP_LOGD(TAG, "DELETED msgid=%d, msg_type=%d, remain size=%"PRIu64, msg_id, msg_type, outbox_get_size(outbox)); + return ESP_OK; + } + + } + return ESP_FAIL; +} + +esp_err_t outbox_set_pending(outbox_handle_t outbox, int msg_id, pending_state_t pending) +{ + outbox_item_handle_t item = outbox_get(outbox, msg_id); + if (item) { + item->pending = pending; + return ESP_OK; + } + return ESP_FAIL; +} + +pending_state_t outbox_item_get_pending(outbox_item_handle_t item) +{ + if (item) { + return item->pending; + } + return QUEUED; +} + +esp_err_t outbox_set_tick(outbox_handle_t outbox, int msg_id, outbox_tick_t tick) +{ + outbox_item_handle_t item = outbox_get(outbox, msg_id); + if (item) { + item->tick = tick; + return ESP_OK; + } + return ESP_FAIL; +} + +int outbox_delete_single_expired(outbox_handle_t outbox, outbox_tick_t current_tick, outbox_tick_t timeout) +{ + int msg_id = -1; + outbox_item_handle_t item; + STAILQ_FOREACH(item, outbox->list, next) { + if (current_tick - item->tick > timeout) { + STAILQ_REMOVE(outbox->list, item, outbox_item, next); + free(item->buffer); + outbox->size -= item->len; + msg_id = item->msg_id; + free(item); + return msg_id; + } + + } + return msg_id; +} + +int outbox_delete_expired(outbox_handle_t outbox, outbox_tick_t current_tick, outbox_tick_t timeout) +{ + int deleted_items = 0; + outbox_item_handle_t item, tmp; + STAILQ_FOREACH_SAFE(item, outbox->list, next, tmp) { + if (current_tick - item->tick > timeout) { + STAILQ_REMOVE(outbox->list, item, outbox_item, next); + free(item->buffer); + outbox->size -= item->len; + free(item); + deleted_items ++; + } + + } + return deleted_items; +} + +uint64_t outbox_get_size(outbox_handle_t outbox) +{ + return outbox->size; +} + +void outbox_delete_all_items(outbox_handle_t outbox) +{ + outbox_item_handle_t item, tmp; + STAILQ_FOREACH_SAFE(item, outbox->list, next, tmp) { + STAILQ_REMOVE(outbox->list, item, outbox_item, next); + outbox->size -= item->len; + free(item->buffer); + free(item); + } +} +void outbox_destroy(outbox_handle_t outbox) +{ + outbox_delete_all_items(outbox); + free(outbox->list); + free(outbox); +} + +#endif /* CONFIG_MQTT_CUSTOM_OUTBOX */ diff --git a/managed_components/esp_mqtt/lib/platform_esp32_idf.c b/managed_components/esp_mqtt/lib/platform_esp32_idf.c new file mode 100644 index 0000000..a84e6fb --- /dev/null +++ b/managed_components/esp_mqtt/lib/platform_esp32_idf.c @@ -0,0 +1,48 @@ +#include "platform.h" + +#ifdef ESP_PLATFORM +#include "esp_log.h" +#include "esp_mac.h" +#include "soc/soc_caps.h" +#include "esp_timer.h" +#include "esp_random.h" +#include +#include + +static const char *TAG = "platform"; + +#define MAX_ID_STRING (32) + +#if defined SOC_WIFI_SUPPORTED +#define MAC_TYPE ESP_MAC_WIFI_STA +#elif defined SOC_EMAC_SUPPORTED +#define MAC_TYPE ESP_MAC_ETH +#elif defined SOC_IEEE802154_SUPPORTED +#define MAC_TYPE ESP_MAC_IEEE802154 +#endif +char *platform_create_id_string(void) +{ + char *id_string = calloc(1, MAX_ID_STRING); + ESP_MEM_CHECK(TAG, id_string, return NULL); + #ifndef MAC_TYPE + ESP_LOGW(TAG, "Soc doesn't provide MAC, client could be disconnected in case of device with same name in the broker."); + sprintf(id_string, "esp_mqtt_client_id"); + #else + uint8_t mac[6]; + esp_read_mac(mac, MAC_TYPE); + sprintf(id_string, "ESP32_%02x%02X%02X", mac[3], mac[4], mac[5]); + #endif + return id_string; +} + +int platform_random(int max) +{ + return esp_random() % max; +} + +uint64_t platform_tick_get_ms(void) +{ + return esp_timer_get_time()/(int64_t)1000; +} + +#endif diff --git a/managed_components/esp_mqtt/mqtt5_client.c b/managed_components/esp_mqtt/mqtt5_client.c new file mode 100644 index 0000000..060dfed --- /dev/null +++ b/managed_components/esp_mqtt/mqtt5_client.c @@ -0,0 +1,768 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "mqtt_client_priv.h" +#include "esp_log.h" +#include + +static const char *TAG = "mqtt5_client"; + +static void esp_mqtt5_print_error_code(esp_mqtt5_client_handle_t client, int code); +static esp_err_t esp_mqtt5_client_update_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, char *topic, size_t topic_len); +static char *esp_mqtt5_client_get_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, size_t *topic_length); +static void esp_mqtt5_client_delete_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle); +static esp_err_t esp_mqtt5_user_property_copy(mqtt5_user_property_handle_t user_property_new, const mqtt5_user_property_handle_t user_property_old); + +void esp_mqtt5_increment_packet_counter(esp_mqtt5_client_handle_t client) +{ + bool msg_dup = mqtt5_get_dup(client->mqtt_state.connection.outbound_message.data); + if (msg_dup == false) { + client->send_publish_packet_count ++; + ESP_LOGD(TAG, "Sent (%d) qos > 0 publish packet without ack", client->send_publish_packet_count); + } +} + +void esp_mqtt5_decrement_packet_counter(esp_mqtt5_client_handle_t client) +{ + if (client->send_publish_packet_count > 0) { + client->send_publish_packet_count --; + ESP_LOGD(TAG, "Receive (%d) qos > 0 publish packet with ack", client->send_publish_packet_count); + } +} + +void esp_mqtt5_parse_pubcomp(esp_mqtt5_client_handle_t client) +{ + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + ESP_LOGD(TAG, "MQTT_MSG_TYPE_PUBCOMP return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + size_t msg_data_len = client->mqtt_state.in_buffer_read_len; + client->event.data = mqtt5_get_pubcomp_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property); + client->event.data_len = msg_data_len; + client->event.total_data_len = msg_data_len; + client->event.current_data_offset = 0; + } +} + +void esp_mqtt5_parse_puback(esp_mqtt5_client_handle_t client) +{ + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + ESP_LOGD(TAG, "MQTT_MSG_TYPE_PUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + size_t msg_data_len = client->mqtt_state.in_buffer_read_len; + client->event.data = mqtt5_get_puback_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property); + client->event.data_len = msg_data_len; + client->event.total_data_len = msg_data_len; + client->event.current_data_offset = 0; + } +} + +void esp_mqtt5_parse_unsuback(esp_mqtt5_client_handle_t client) +{ + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + ESP_LOGD(TAG, "MQTT_MSG_TYPE_UNSUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + size_t msg_data_len = client->mqtt_state.in_buffer_read_len; + client->event.data = mqtt5_get_unsuback_data(client->mqtt_state.in_buffer, &msg_data_len, &client->event.property->user_property); + client->event.data_len = msg_data_len; + client->event.total_data_len = msg_data_len; + client->event.current_data_offset = 0; + } +} + +void esp_mqtt5_parse_suback(esp_mqtt5_client_handle_t client) +{ + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + ESP_LOGD(TAG, "MQTT_MSG_TYPE_SUBACK return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + } +} + +void esp_mqtt5_parse_disconnect(esp_mqtt5_client_handle_t client, int *disconnect_rsp_code) +{ + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + *disconnect_rsp_code = mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len); + ESP_LOGD(TAG, "MQTT_MSG_TYPE_DISCONNECT return code is %d", *disconnect_rsp_code); + } +} + +esp_err_t esp_mqtt5_parse_connack(esp_mqtt5_client_handle_t client, int *connect_rsp_code) +{ + size_t len = client->mqtt_state.in_buffer_read_len; + client->mqtt_state.in_buffer_read_len = 0; + uint8_t ack_flag = 0; + if (mqtt5_msg_parse_connack_property(client->mqtt_state.in_buffer, len, &client->mqtt_state. + connection.information, &client->mqtt5_config->connect_property_info, &client->mqtt5_config->server_resp_property_info, connect_rsp_code, &ack_flag, &client->event.property->user_property) != ESP_OK) { + ESP_LOGE(TAG, "Failed to parse CONNACK packet"); + return ESP_FAIL; + } + if (*connect_rsp_code == MQTT_CONNECTION_ACCEPTED) { + ESP_LOGD(TAG, "Connected"); + client->event.session_present = ack_flag & 0x01; + return ESP_OK; + } + esp_mqtt5_print_error_code(client, *connect_rsp_code); + return ESP_FAIL; +} + +esp_err_t esp_mqtt5_get_publish_data(esp_mqtt5_client_handle_t client, uint8_t *msg_buf, size_t msg_read_len, char **msg_topic, size_t *msg_topic_len, char **msg_data, size_t *msg_data_len) +{ + // get property + uint16_t property_len = 0; + esp_mqtt5_publish_resp_property_t property = {0}; + *msg_data = mqtt5_get_publish_property_payload(msg_buf, msg_read_len, msg_topic, msg_topic_len, &property, &property_len, msg_data_len, &client->event.property->user_property); + if (*msg_data == NULL) { + ESP_LOGE(TAG, "%s: mqtt5_get_publish_property_payload() failed", __func__); + return ESP_FAIL; + } + + if (property.topic_alias > client->mqtt5_config->connect_property_info.topic_alias_maximum) { + ESP_LOGE(TAG, "%s: Broker response topic alias %d is over the max topic alias %d", __func__, property.topic_alias, client->mqtt5_config->connect_property_info.topic_alias_maximum); + return ESP_FAIL; + } + + if (property.topic_alias) { + if (*msg_topic_len == 0) { + *msg_topic = esp_mqtt5_client_get_topic_alias(client->mqtt5_config->peer_topic_alias, property.topic_alias, msg_topic_len); + if (!*msg_topic) { + ESP_LOGE(TAG, "%s: esp_mqtt5_client_get_topic_alias() failed", __func__); + return ESP_FAIL; + } + } else { + if (esp_mqtt5_client_update_topic_alias(client->mqtt5_config->peer_topic_alias, property.topic_alias, *msg_topic, *msg_topic_len) != ESP_OK) { + ESP_LOGE(TAG, "%s: esp_mqtt5_client_update_topic_alias() failed", __func__); + return ESP_FAIL; + } + } + } + + client->event.property->payload_format_indicator = property.payload_format_indicator; + client->event.property->response_topic = property.response_topic; + client->event.property->response_topic_len = property.response_topic_len; + client->event.property->correlation_data = property.correlation_data; + client->event.property->correlation_data_len = property.correlation_data_len; + client->event.property->content_type = property.content_type; + client->event.property->content_type_len = property.content_type_len; + client->event.property->subscribe_id = property.subscribe_id; + return ESP_OK; +} + +esp_err_t esp_mqtt5_create_default_config(esp_mqtt5_client_handle_t client) +{ + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + client->event.property = calloc(1, sizeof(esp_mqtt5_event_property_t)); + ESP_MEM_CHECK(TAG, client->event.property, return ESP_FAIL) + client->mqtt5_config = calloc(1, sizeof(mqtt5_config_storage_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config, return ESP_FAIL) + client->mqtt5_config->server_resp_property_info.max_qos = 2; + client->mqtt5_config->server_resp_property_info.retain_available = true; + client->mqtt5_config->server_resp_property_info.wildcard_subscribe_available = true; + client->mqtt5_config->server_resp_property_info.subscribe_identifiers_available = true; + client->mqtt5_config->server_resp_property_info.shared_subscribe_available = true; + client->mqtt5_config->server_resp_property_info.receive_maximum = 65535; + } + return ESP_OK; +} + +static void esp_mqtt5_print_error_code(esp_mqtt5_client_handle_t client, int code) +{ + switch (code) { + case MQTT5_UNSPECIFIED_ERROR: + ESP_LOGW(TAG, "Unspecified error"); + break; + case MQTT5_MALFORMED_PACKET: + ESP_LOGW(TAG, "Malformed Packet"); + break; + case MQTT5_PROTOCOL_ERROR: + ESP_LOGW(TAG, "Protocol Error"); + break; + case MQTT5_IMPLEMENT_SPECIFIC_ERROR: + ESP_LOGW(TAG, "Implementation specific error"); + break; + case MQTT5_UNSUPPORTED_PROTOCOL_VER: + ESP_LOGW(TAG, "Unsupported Protocol Version"); + break; + case MQTT5_INVALID_CLIENT_ID: + ESP_LOGW(TAG, "Client Identifier not valid"); + break; + case MQTT5_BAD_USERNAME_OR_PWD: + ESP_LOGW(TAG, "Bad User Name or Password"); + break; + case MQTT5_NOT_AUTHORIZED: + ESP_LOGW(TAG, "Not authorized"); + break; + case MQTT5_SERVER_UNAVAILABLE: + ESP_LOGW(TAG, "Server unavailable"); + break; + case MQTT5_SERVER_BUSY: + ESP_LOGW(TAG, "Server busy"); + break; + case MQTT5_BANNED: + ESP_LOGW(TAG, "Banned"); + break; + case MQTT5_SERVER_SHUTTING_DOWN: + ESP_LOGW(TAG, "Server shutting down"); + break; + case MQTT5_BAD_AUTH_METHOD: + ESP_LOGW(TAG, "Bad authentication method"); + break; + case MQTT5_KEEP_ALIVE_TIMEOUT: + ESP_LOGW(TAG, "Keep Alive timeout"); + break; + case MQTT5_SESSION_TAKEN_OVER: + ESP_LOGW(TAG, "Session taken over"); + break; + case MQTT5_TOPIC_FILTER_INVALID: + ESP_LOGW(TAG, "Topic Filter invalid"); + break; + case MQTT5_TOPIC_NAME_INVALID: + ESP_LOGW(TAG, "Topic Name invalid"); + break; + case MQTT5_PACKET_IDENTIFIER_IN_USE: + ESP_LOGW(TAG, "Packet Identifier in use"); + break; + case MQTT5_PACKET_IDENTIFIER_NOT_FOUND: + ESP_LOGW(TAG, "Packet Identifier not found"); + break; + case MQTT5_RECEIVE_MAXIMUM_EXCEEDED: + ESP_LOGW(TAG, "Receive Maximum exceeded"); + break; + case MQTT5_TOPIC_ALIAS_INVALID: + ESP_LOGW(TAG, "Topic Alias invalid"); + break; + case MQTT5_PACKET_TOO_LARGE: + ESP_LOGW(TAG, "Packet too large"); + break; + case MQTT5_MESSAGE_RATE_TOO_HIGH: + ESP_LOGW(TAG, "Message rate too high"); + break; + case MQTT5_QUOTA_EXCEEDED: + ESP_LOGW(TAG, "Quota exceeded"); + break; + case MQTT5_ADMINISTRATIVE_ACTION: + ESP_LOGW(TAG, "Administrative action"); + break; + case MQTT5_PAYLOAD_FORMAT_INVALID: + ESP_LOGW(TAG, "Payload format invalid"); + break; + case MQTT5_RETAIN_NOT_SUPPORT: + ESP_LOGW(TAG, "Retain not supported"); + break; + case MQTT5_QOS_NOT_SUPPORT: + ESP_LOGW(TAG, "QoS not supported"); + break; + case MQTT5_USE_ANOTHER_SERVER: + ESP_LOGW(TAG, "Use another server"); + break; + case MQTT5_SERVER_MOVED: + ESP_LOGW(TAG, "Server moved"); + break; + case MQTT5_SHARED_SUBSCR_NOT_SUPPORTED: + ESP_LOGW(TAG, "Shared Subscriptions not supported"); + break; + case MQTT5_CONNECTION_RATE_EXCEEDED: + ESP_LOGW(TAG, "Connection rate exceeded"); + break; + case MQTT5_MAXIMUM_CONNECT_TIME: + ESP_LOGW(TAG, "Maximum connect time"); + break; + case MQTT5_SUBSCRIBE_IDENTIFIER_NOT_SUPPORT: + ESP_LOGW(TAG, "Subscription Identifiers not supported"); + break; + case MQTT5_WILDCARD_SUBSCRIBE_NOT_SUPPORT: + ESP_LOGW(TAG, "Wildcard Subscriptions not supported"); + break; + default: + ESP_LOGW(TAG, "Connection refused, Unknow reason"); + break; + } +} + +esp_err_t esp_mqtt5_client_subscribe_check(esp_mqtt5_client_handle_t client, int qos) +{ + /* Check Server support QoS level */ + if (client->mqtt5_config->server_resp_property_info.max_qos < qos) { + ESP_LOGE(TAG, "Server only support max QoS level %d", client->mqtt5_config->server_resp_property_info.max_qos); + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_publish_check(esp_mqtt5_client_handle_t client, int qos, int retain) +{ + /* Check Server support QoS level */ + if (client->mqtt5_config->server_resp_property_info.max_qos < qos) { + ESP_LOGE(TAG, "Server only support max QoS level %d", client->mqtt5_config->server_resp_property_info.max_qos); + return ESP_FAIL; + } + + /* Check Server support RETAIN */ + if (!client->mqtt5_config->server_resp_property_info.retain_available && retain) { + ESP_LOGE(TAG, "Server not support retain"); + return ESP_FAIL; + } + + /* Flow control to check PUBLISH(No PUBACK or PUBCOMP received) packet sent count(Only record QoS1 and QoS2)*/ + if (client->send_publish_packet_count > client->mqtt5_config->server_resp_property_info.receive_maximum) { + ESP_LOGE(TAG, "Client send more than %d QoS1 and QoS2 PUBLISH packet without no ack", client->mqtt5_config->server_resp_property_info.receive_maximum); + return ESP_FAIL; + } + + return ESP_OK; +} + +void esp_mqtt5_client_destory(esp_mqtt5_client_handle_t client) +{ + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + if (client->mqtt5_config) { + free(client->mqtt5_config->will_property_info.content_type); + free(client->mqtt5_config->will_property_info.response_topic); + free(client->mqtt5_config->will_property_info.correlation_data); + free(client->mqtt5_config->server_resp_property_info.response_info); + esp_mqtt5_client_delete_topic_alias(client->mqtt5_config->peer_topic_alias); + esp_mqtt5_client_delete_user_property(client->mqtt5_config->connect_property_info.user_property); + esp_mqtt5_client_delete_user_property(client->mqtt5_config->will_property_info.user_property); + esp_mqtt5_client_delete_user_property(client->mqtt5_config->disconnect_property_info.user_property); + free(client->mqtt5_config); + } + free(client->event.property); + } +} + +static void esp_mqtt5_client_delete_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle) +{ + if (topic_alias_handle) { + mqtt5_topic_alias_item_t item, tmp; + STAILQ_FOREACH_SAFE(item, topic_alias_handle, next, tmp) { + STAILQ_REMOVE(topic_alias_handle, item, mqtt5_topic_alias, next); + free(item->topic); + free(item); + } + free(topic_alias_handle); + } +} + +static esp_err_t esp_mqtt5_client_update_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, char *topic, size_t topic_len) +{ + mqtt5_topic_alias_item_t item; + bool found = false; + STAILQ_FOREACH(item, topic_alias_handle, next) { + if (item->topic_alias == topic_alias) { + found = true; + break; + } + } + if (found) { + if ((item->topic_len != topic_len) || strncmp(topic, item->topic, topic_len)) { + free(item->topic); + item->topic = calloc(1, topic_len); + ESP_MEM_CHECK(TAG, item->topic, return ESP_FAIL); + memcpy(item->topic, topic, topic_len); + item->topic_len = topic_len; + } + } else { + item = calloc(1, sizeof(mqtt5_topic_alias_t)); + ESP_MEM_CHECK(TAG, item, return ESP_FAIL); + item->topic_alias = topic_alias; + item->topic_len = topic_len; + item->topic = calloc(1, topic_len); + ESP_MEM_CHECK(TAG, item->topic, { + free(item); + return ESP_FAIL; + }); + memcpy(item->topic, topic, topic_len); + STAILQ_INSERT_TAIL(topic_alias_handle, item, next); + } + return ESP_OK; +} + +static char *esp_mqtt5_client_get_topic_alias(mqtt5_topic_alias_handle_t topic_alias_handle, uint16_t topic_alias, size_t *topic_length) +{ + mqtt5_topic_alias_item_t item; + STAILQ_FOREACH(item, topic_alias_handle, next) { + if (item->topic_alias == topic_alias) { + *topic_length = item->topic_len; + return item->topic; + } + } + *topic_length = 0; + return NULL; +} + +static esp_err_t esp_mqtt5_user_property_copy(mqtt5_user_property_handle_t user_property_new, const mqtt5_user_property_handle_t user_property_old) +{ + if (!user_property_new || !user_property_old) { + ESP_LOGE(TAG, "Input is NULL"); + return ESP_FAIL; + } + + mqtt5_user_property_item_t old_item, new_item; + STAILQ_FOREACH(old_item, user_property_old, next) { + new_item = calloc(1, sizeof(mqtt5_user_property_t)); + ESP_MEM_CHECK(TAG, new_item, return ESP_FAIL); + new_item->key = strdup(old_item->key); + ESP_MEM_CHECK(TAG, new_item->key, { + free(new_item); + return ESP_FAIL; + }); + new_item->value = strdup(old_item->value); + ESP_MEM_CHECK(TAG, new_item->value, { + free(new_item->key); + free(new_item); + return ESP_FAIL; + }); + STAILQ_INSERT_TAIL(user_property_new, new_item, next); + } + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_publish_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_publish_property_config_t *property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->mqtt_state.connection.information.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + /* Check topic alias less than server maximum topic alias */ + if (property->topic_alias > client->mqtt5_config->server_resp_property_info.topic_alias_maximum) { + ESP_LOGE(TAG, "Topic alias %d is bigger than server support %d", property->topic_alias, client->mqtt5_config->server_resp_property_info.topic_alias_maximum); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + client->mqtt5_config->publish_property_info = property; + MQTT_API_UNLOCK(client); + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_subscribe_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_subscribe_property_config_t *property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + if (property->retain_handle > 2) { + ESP_LOGE(TAG, "retain_handle only support 0, 1, 2"); + return -1; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->mqtt_state.connection.information.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (property->is_share_subscribe) { + if (property->no_local_flag) { + // MQTT-3.8.3-4 not allow that No Local bit to 1 on a Shared Subscription + ESP_LOGE(TAG, "Protocol error that no local flag set on shared subscription"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (!client->mqtt5_config->server_resp_property_info.shared_subscribe_available) { + ESP_LOGE(TAG, "MQTT broker not support shared subscribe"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (!property->share_name || !strlen(property->share_name)) { + ESP_LOGE(TAG, "Share name can't be empty for shared subscribe"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + } + client->mqtt5_config->subscribe_property_info = property; + MQTT_API_UNLOCK(client); + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_unsubscribe_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_unsubscribe_property_config_t *property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->mqtt_state.connection.information.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (property->is_share_subscribe) { + if (!client->mqtt5_config->server_resp_property_info.shared_subscribe_available) { + ESP_LOGE(TAG, "MQTT broker not support shared subscribe"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (!property->share_name || !strlen(property->share_name)) { + ESP_LOGE(TAG, "Share name can't be empty for shared subscribe"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + } + client->mqtt5_config->unsubscribe_property_info = property; + MQTT_API_UNLOCK(client); + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_disconnect_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_disconnect_property_config_t *property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->mqtt_state.connection.information.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (property) { + if (property->session_expiry_interval) { + client->mqtt5_config->disconnect_property_info.session_expiry_interval = property->session_expiry_interval; + } + if (property->disconnect_reason) { + client->mqtt5_config->disconnect_property_info.disconnect_reason = property->disconnect_reason; + } + if (property->user_property) { + esp_mqtt5_client_delete_user_property(client->mqtt5_config->disconnect_property_info.user_property); + client->mqtt5_config->disconnect_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config->disconnect_property_info.user_property, { + MQTT_API_UNLOCK(client); + return ESP_ERR_NO_MEM; + }); + STAILQ_INIT(client->mqtt5_config->disconnect_property_info.user_property); + if (esp_mqtt5_user_property_copy(client->mqtt5_config->disconnect_property_info.user_property, property->user_property) != ESP_OK) { + ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail"); + free(client->mqtt5_config->disconnect_property_info.user_property); + client->mqtt5_config->disconnect_property_info.user_property = NULL; + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + } + } + + MQTT_API_UNLOCK(client); + return ESP_OK; +} + +esp_err_t esp_mqtt5_client_set_connect_property(esp_mqtt5_client_handle_t client, const esp_mqtt5_connection_property_config_t *connect_property) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + + /* Check protocol version */ + if (client->mqtt_state.connection.information.protocol_ver != MQTT_PROTOCOL_V_5) { + ESP_LOGE(TAG, "MQTT protocol version is not v5"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + if (connect_property) { + if (connect_property->session_expiry_interval) { + client->mqtt5_config->connect_property_info.session_expiry_interval = connect_property->session_expiry_interval; + } + if (connect_property->maximum_packet_size) { + if (connect_property->maximum_packet_size > client->mqtt_state.in_buffer_length) { + ESP_LOGW(TAG, "Connect maximum_packet_size property is over buffer_size(%d), Please first change it", client->mqtt_state.in_buffer_length); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } else { + client->mqtt5_config->connect_property_info.maximum_packet_size = connect_property->maximum_packet_size; + } + } else { + client->mqtt5_config->connect_property_info.maximum_packet_size = client->mqtt_state.in_buffer_length; + } + if (connect_property->receive_maximum) { + client->mqtt5_config->connect_property_info.receive_maximum = connect_property->receive_maximum; + } + if (connect_property->topic_alias_maximum) { + client->mqtt5_config->connect_property_info.topic_alias_maximum = connect_property->topic_alias_maximum; + if (!client->mqtt5_config->peer_topic_alias) { + client->mqtt5_config->peer_topic_alias = calloc(1, sizeof(struct mqtt5_topic_alias_list_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config->peer_topic_alias, goto _mqtt_set_config_failed); + STAILQ_INIT(client->mqtt5_config->peer_topic_alias); + } + } + if (connect_property->request_resp_info) { + client->mqtt5_config->connect_property_info.request_resp_info = connect_property->request_resp_info; + } + if (connect_property->request_problem_info) { + client->mqtt5_config->connect_property_info.request_problem_info = connect_property->request_problem_info; + } + if (connect_property->user_property) { + esp_mqtt5_client_delete_user_property(client->mqtt5_config->connect_property_info.user_property); + client->mqtt5_config->connect_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config->connect_property_info.user_property, goto _mqtt_set_config_failed); + STAILQ_INIT(client->mqtt5_config->connect_property_info.user_property); + if (esp_mqtt5_user_property_copy(client->mqtt5_config->connect_property_info.user_property, connect_property->user_property) != ESP_OK) { + ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail"); + goto _mqtt_set_config_failed; + } + } + if (connect_property->payload_format_indicator) { + client->mqtt5_config->will_property_info.payload_format_indicator = connect_property->payload_format_indicator; + } + if (connect_property->will_delay_interval) { + client->mqtt5_config->will_property_info.will_delay_interval = connect_property->will_delay_interval; + } + if (connect_property->message_expiry_interval) { + client->mqtt5_config->will_property_info.message_expiry_interval = connect_property->message_expiry_interval; + } + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(connect_property->content_type, &client->mqtt5_config->will_property_info.content_type), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(connect_property->response_topic, &client->mqtt5_config->will_property_info.response_topic), goto _mqtt_set_config_failed); + if (connect_property->correlation_data && connect_property->correlation_data_len) { + free(client->mqtt5_config->will_property_info.correlation_data); + client->mqtt5_config->will_property_info.correlation_data = malloc(connect_property->correlation_data_len); + ESP_MEM_CHECK(TAG, client->mqtt5_config->will_property_info.correlation_data, goto _mqtt_set_config_failed); + memcpy(client->mqtt5_config->will_property_info.correlation_data, connect_property->correlation_data, connect_property->correlation_data_len); + client->mqtt5_config->will_property_info.correlation_data_len = connect_property->correlation_data_len; + } + if (connect_property->will_user_property) { + esp_mqtt5_client_delete_user_property(client->mqtt5_config->will_property_info.user_property); + client->mqtt5_config->will_property_info.user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, client->mqtt5_config->will_property_info.user_property, goto _mqtt_set_config_failed); + STAILQ_INIT(client->mqtt5_config->will_property_info.user_property); + if (esp_mqtt5_user_property_copy(client->mqtt5_config->will_property_info.user_property, connect_property->will_user_property) != ESP_OK) { + ESP_LOGE(TAG, "esp_mqtt5_user_property_copy fail"); + goto _mqtt_set_config_failed; + } + } + } + MQTT_API_UNLOCK(client); + return ESP_OK; +_mqtt_set_config_failed: + esp_mqtt_destroy_config(client); + MQTT_API_UNLOCK(client); + return ESP_ERR_NO_MEM; +} + +esp_err_t esp_mqtt5_client_set_user_property(mqtt5_user_property_handle_t *user_property, esp_mqtt5_user_property_item_t item[], uint8_t item_num) +{ + if (!item_num || !item) { + ESP_LOGE(TAG, "Input value is NULL"); + return ESP_FAIL; + } + + if (!*user_property) { + *user_property = calloc(1, sizeof(struct mqtt5_user_property_list_t)); + ESP_MEM_CHECK(TAG, *user_property, return ESP_ERR_NO_MEM); + STAILQ_INIT(*user_property); + } + + for (int i = 0; i < item_num; i ++) { + if (item[i].key && item[i].value) { + mqtt5_user_property_item_t user_property_item = calloc(1, sizeof(mqtt5_user_property_t)); + ESP_MEM_CHECK(TAG, user_property_item, goto err); + size_t key_len = strlen(item[i].key); + size_t value_len = strlen(item[i].value); + + user_property_item->key = calloc(1, key_len + 1); + ESP_MEM_CHECK(TAG, user_property_item->key, { + free(user_property_item); + goto err; + }); + memcpy(user_property_item->key, item[i].key, key_len); + user_property_item->key[key_len] = '\0'; + + user_property_item->value = calloc(1, value_len + 1); + ESP_MEM_CHECK(TAG, user_property_item->value, { + free(user_property_item->key); + free(user_property_item); + goto err; + }); + memcpy(user_property_item->value, item[i].value, value_len); + user_property_item->value[value_len] = '\0'; + + STAILQ_INSERT_TAIL(*user_property, user_property_item, next); + } + } + return ESP_OK; +err: + esp_mqtt5_client_delete_user_property(*user_property); + *user_property = NULL; + return ESP_ERR_NO_MEM; +} + +esp_err_t esp_mqtt5_client_get_user_property(mqtt5_user_property_handle_t user_property, esp_mqtt5_user_property_item_t *item, uint8_t *item_num) +{ + int i = 0, j = 0; + if (user_property && item && *item_num) { + mqtt5_user_property_item_t user_property_item; + uint8_t num = *item_num; + STAILQ_FOREACH(user_property_item, user_property, next) { + if (i < num) { + size_t item_key_len = strlen(user_property_item->key); + size_t item_value_len = strlen(user_property_item->value); + char *key = calloc(1, item_key_len + 1); + ESP_MEM_CHECK(TAG, key, goto err); + memcpy(key, user_property_item->key, item_key_len); + key[item_key_len] = '\0'; + char *value = calloc(1, item_value_len + 1); + ESP_MEM_CHECK(TAG, value, { + free(key); + goto err; + }); + memcpy(value, user_property_item->value, item_value_len); + value[item_value_len] = '\0'; + item[i].key = key; + item[i].value = value; + i ++; + } else { + break; + } + } + *item_num = i; + return ESP_OK; + } else { + ESP_LOGE(TAG, "Input value is NULL or item_num is 0"); + return ESP_FAIL; + } +err: + for (j = 0; j < i; j ++) { + if (item[j].key) { + free((char *)item[j].key); + } + if (item[j].value) { + free((char *)item[j].value); + } + } + return ESP_ERR_NO_MEM; +} + +uint8_t esp_mqtt5_client_get_user_property_count(mqtt5_user_property_handle_t user_property) +{ + uint8_t count = 0; + if (user_property) { + mqtt5_user_property_item_t item; + STAILQ_FOREACH(item, user_property, next) { + count ++; + } + } + return count; +} + +void esp_mqtt5_client_delete_user_property(mqtt5_user_property_handle_t user_property) +{ + if (user_property) { + mqtt5_user_property_item_t item, tmp; + STAILQ_FOREACH_SAFE(item, user_property, next, tmp) { + STAILQ_REMOVE(user_property, item, mqtt5_user_property, next); + free(item->key); + free(item->value); + free(item); + } + } + free(user_property); +} diff --git a/managed_components/esp_mqtt/mqtt_client.c b/managed_components/esp_mqtt/mqtt_client.c new file mode 100644 index 0000000..b70f518 --- /dev/null +++ b/managed_components/esp_mqtt/mqtt_client.c @@ -0,0 +1,2380 @@ +#include +#include +#include +#include "esp_err.h" +#include "esp_log.h" +#include "esp_heap_caps.h" +#include "esp_transport.h" +#include "mqtt_client.h" +#include "mqtt_client_priv.h" +#include "mqtt_msg.h" +#include "mqtt_outbox.h" + +_Static_assert(sizeof(uint64_t) == sizeof(outbox_tick_t), "mqtt-client tick type size different from outbox tick type"); +#ifdef ESP_EVENT_ANY_ID +_Static_assert(MQTT_EVENT_ANY == ESP_EVENT_ANY_ID, "mqtt-client event enum does not match the global EVENT_ANY_ID"); +#endif + +static const char *TAG = "mqtt_client"; + +#ifdef MQTT_SUPPORTED_FEATURE_EVENT_LOOP +/** + * @brief Define of MQTT Event base + * + */ +ESP_EVENT_DEFINE_BASE(MQTT_EVENTS); +#endif + +const static int STOPPED_BIT = (1 << 0); +const static int RECONNECT_BIT = (1 << 1); +const static int DISCONNECT_BIT = (1 << 2); + +static esp_err_t esp_mqtt_dispatch_event(esp_mqtt_client_handle_t client); +static esp_err_t esp_mqtt_dispatch_event_with_msgid(esp_mqtt_client_handle_t client); +static esp_err_t esp_mqtt_connect(esp_mqtt_client_handle_t client, int timeout_ms); +static void esp_mqtt_abort_connection(esp_mqtt_client_handle_t client); +static esp_err_t esp_mqtt_client_ping(esp_mqtt_client_handle_t client); +static char *create_string(const char *ptr, int len); +static int mqtt_message_receive(esp_mqtt_client_handle_t client, int read_poll_timeout_ms); +static void esp_mqtt_client_dispatch_transport_error(esp_mqtt_client_handle_t client); +static esp_err_t send_disconnect_msg(esp_mqtt_client_handle_t client); + +/** + * @brief Processes error reported from transport layer (considering the message read status) + * + * @param err: Error reported from TCP transport + * @param client: MQTT client handle + * @param mid_message: True if the error occured when reading incomplete message + * + * @return - 0 on Timeout + * - -1 on Timeout with incomplete message + * - -2 on Error or EOF + */ +static int esp_mqtt_handle_transport_read_error(int err, esp_mqtt_client_handle_t client, bool mid_message) +{ + if (err == ERR_TCP_TRANSPORT_CONNECTION_TIMEOUT) { + if (mid_message) { + // No error message, because we could've read with timeout 0 (caller will decide) + return -1; + } + // Not an error, continue + ESP_LOGV(TAG, "%s: transport_read(): call timed out before data was ready!", __func__); + return 0; + } + + if (err == ERR_TCP_TRANSPORT_CONNECTION_CLOSED_BY_FIN) { + ESP_LOGE(TAG, "%s: transport_read(): EOF", __func__); + } + + ESP_LOGE(TAG, "%s: transport_read() error: errno=%d", __func__, errno); + esp_mqtt_client_dispatch_transport_error(client); + return -2; +} + +#if MQTT_ENABLE_SSL +enum esp_mqtt_ssl_cert_key_api { + MQTT_SSL_DATA_API_CA_CERT, + MQTT_SSL_DATA_API_CLIENT_CERT, + MQTT_SSL_DATA_API_CLIENT_KEY, + MQTT_SSL_DATA_API_MAX, +}; + +static esp_err_t esp_mqtt_set_cert_key_data(esp_transport_handle_t ssl, enum esp_mqtt_ssl_cert_key_api what, const char *cert_key_data, int cert_key_len) +{ + char *data = (char *)cert_key_data; + int ssl_transport_api_id = what; + int len = cert_key_len; + + if (!data) { + return ESP_OK; + } + + if (len == 0) { + // if length not specified, expect 0-terminated PEM string + // and the original transport_api_id (by convention after the last api_id in the enum) + ssl_transport_api_id += MQTT_SSL_DATA_API_MAX; + len = strlen(data); + } +#ifndef MQTT_SUPPORTED_FEATURE_DER_CERTIFICATES + else { + ESP_LOGE(TAG, "Explicit cert-/key-len is not available in IDF version %s", IDF_VER); + return ESP_ERR_NOT_SUPPORTED; + } +#endif + + // option to force the cert/key config to null (i.e. skip validation) when existing config updates + if (0 == strcmp(data, "NULL")) { + data = NULL; + len = 0; + } + + switch (ssl_transport_api_id) { +#ifdef MQTT_SUPPORTED_FEATURE_DER_CERTIFICATES + case MQTT_SSL_DATA_API_CA_CERT: + esp_transport_ssl_set_cert_data_der(ssl, data, len); + break; + case MQTT_SSL_DATA_API_CLIENT_CERT: + esp_transport_ssl_set_client_cert_data_der(ssl, data, len); + break; + case MQTT_SSL_DATA_API_CLIENT_KEY: + esp_transport_ssl_set_client_key_data_der(ssl, data, len); + break; +#endif + case MQTT_SSL_DATA_API_CA_CERT + MQTT_SSL_DATA_API_MAX: + esp_transport_ssl_set_cert_data(ssl, data, len); + break; + case MQTT_SSL_DATA_API_CLIENT_CERT + MQTT_SSL_DATA_API_MAX: + esp_transport_ssl_set_client_cert_data(ssl, data, len); + break; + case MQTT_SSL_DATA_API_CLIENT_KEY + MQTT_SSL_DATA_API_MAX: + esp_transport_ssl_set_client_key_data(ssl, data, len); + break; + default: + return ESP_ERR_INVALID_ARG; + } + return ESP_OK; +} + +static esp_err_t esp_mqtt_set_ssl_transport_properties(esp_transport_list_handle_t transport_list, mqtt_config_storage_t *cfg) +{ + esp_transport_handle_t ssl = esp_transport_list_get_transport(transport_list, MQTT_OVER_SSL_SCHEME); + + if (cfg->use_global_ca_store == true) { + esp_transport_ssl_enable_global_ca_store(ssl); + } else if (cfg->crt_bundle_attach != NULL) { +#ifdef MQTT_SUPPORTED_FEATURE_CERTIFICATE_BUNDLE +#ifdef CONFIG_MBEDTLS_CERTIFICATE_BUNDLE + esp_transport_ssl_crt_bundle_attach(ssl, cfg->crt_bundle_attach); +#else + ESP_LOGE(TAG, "Certificate bundle is not enabled for mbedTLS in menuconfig"); + goto esp_mqtt_set_transport_failed; +#endif /* CONFIG_MBEDTLS_CERTIFICATE_BUNDLE */ +#else + ESP_LOGE(TAG, "Certificate bundle feature is not available in IDF version %s", IDF_VER); + goto esp_mqtt_set_transport_failed; +#endif /* MQTT_SUPPORTED_FEATURE_CERTIFICATE_BUNDLE */ + } else { + ESP_OK_CHECK(TAG, esp_mqtt_set_cert_key_data(ssl, MQTT_SSL_DATA_API_CA_CERT, cfg->cacert_buf, cfg->cacert_bytes), + goto esp_mqtt_set_transport_failed); + + } + if (cfg->psk_hint_key) { +#if defined(MQTT_SUPPORTED_FEATURE_PSK_AUTHENTICATION) && MQTT_ENABLE_SSL +#ifdef CONFIG_ESP_TLS_PSK_VERIFICATION + esp_transport_ssl_set_psk_key_hint(ssl, cfg->psk_hint_key); +#else + ESP_LOGE(TAG, "PSK authentication configured but not enabled in menuconfig: Please enable ESP_TLS_PSK_VERIFICATION option"); + goto esp_mqtt_set_transport_failed; +#endif +#else + ESP_LOGE(TAG, "PSK authentication is not available in IDF version %s", IDF_VER); + goto esp_mqtt_set_transport_failed; +#endif + } + + if (cfg->alpn_protos) { +#if defined(MQTT_SUPPORTED_FEATURE_ALPN) && MQTT_ENABLE_SSL +#if defined(CONFIG_MBEDTLS_SSL_ALPN) || defined(CONFIG_WOLFSSL_HAVE_ALPN) + esp_transport_ssl_set_alpn_protocol(ssl, (const char **)cfg->alpn_protos); +#else + ESP_LOGE(TAG, "APLN configured but not enabled in menuconfig: Please enable MBEDTLS_SSL_ALPN or WOLFSSL_HAVE_ALPN option"); + goto esp_mqtt_set_transport_failed; +#endif +#else + ESP_LOGE(TAG, "APLN is not available in IDF version %s", IDF_VER); + goto esp_mqtt_set_transport_failed; +#endif + } + + + if (cfg->skip_cert_common_name_check) { +#if defined(MQTT_SUPPORTED_FEATURE_SKIP_CRT_CMN_NAME_CHECK) && MQTT_ENABLE_SSL + esp_transport_ssl_skip_common_name_check(ssl); +#else + ESP_LOGE(TAG, "Skip certificate common name check is not available in IDF version %s", IDF_VER); + goto esp_mqtt_set_transport_failed; +#endif + } + + if (cfg->common_name) { +#if defined(MQTT_SUPPORTED_FEATURE_CRT_CMN_NAME) && MQTT_ENABLE_SSL + esp_transport_ssl_set_common_name(ssl, cfg->common_name); +#else + ESP_LOGE(TAG, "Setting expected certificate common name is not available in IDF version %s", IDF_VER); + goto esp_mqtt_set_transport_failed; +#endif + } + + if (cfg->use_secure_element) { +#ifdef MQTT_SUPPORTED_FEATURE_SECURE_ELEMENT +#ifdef CONFIG_ESP_TLS_USE_SECURE_ELEMENT + esp_transport_ssl_use_secure_element(ssl); +#else + ESP_LOGE(TAG, "Secure element not enabled for esp-tls in menuconfig"); + goto esp_mqtt_set_transport_failed; +#endif /* CONFIG_ESP_TLS_USE_SECURE_ELEMENT */ +#else + ESP_LOGE(TAG, "Secure element feature is not available in IDF version %s", IDF_VER); + goto esp_mqtt_set_transport_failed; +#endif /* MQTT_SUPPORTED_FEATURE_SECURE_ELEMENT */ + } + + if (cfg->ds_data != NULL) { +#ifdef MQTT_SUPPORTED_FEATURE_DIGITAL_SIGNATURE +#ifdef CONFIG_ESP_TLS_USE_DS_PERIPHERAL + esp_transport_ssl_set_ds_data(ssl, cfg->ds_data); +#else + ESP_LOGE(TAG, "Digital Signature not enabled for esp-tls in menuconfig"); + goto esp_mqtt_set_transport_failed; +#endif /* CONFIG_ESP_TLS_USE_DS_PERIPHERAL */ +#else + ESP_LOGE(TAG, "Digital Signature feature is not available in IDF version %s", IDF_VER); + goto esp_mqtt_set_transport_failed; +#endif + } + + if (cfg->use_ecdsa_peripheral) { +#ifdef MQTT_SUPPORTED_FEATURE_ECDSA_PERIPHERAL +#ifdef CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN + esp_transport_ssl_set_client_key_ecdsa_peripheral(ssl, cfg->ecdsa_key_efuse_blk); +#else + ESP_LOGE(TAG, "ECDSA peripheral not enabled for esp-tls in menuconfig"); + goto esp_mqtt_set_transport_failed; +#endif /* CONFIG_MBEDTLS_HARDWARE_ECDSA_SIGN */ +#else + ESP_LOGE(TAG, "ECDSA peripheral feature is not available in IDF version %s", IDF_VER); + goto esp_mqtt_set_transport_failed; +#endif /* MQTT_SUPPORTED_FEATURE_ECDSA_PERIPHERAL */ + } + + ESP_OK_CHECK(TAG, esp_mqtt_set_cert_key_data(ssl, MQTT_SSL_DATA_API_CLIENT_CERT, cfg->clientcert_buf, cfg->clientcert_bytes), + goto esp_mqtt_set_transport_failed); + ESP_OK_CHECK(TAG, esp_mqtt_set_cert_key_data(ssl, MQTT_SSL_DATA_API_CLIENT_KEY, cfg->clientkey_buf, cfg->clientkey_bytes), + goto esp_mqtt_set_transport_failed); + + if (cfg->clientkey_password && cfg->clientkey_password_len) { +#if defined(MQTT_SUPPORTED_FEATURE_CLIENT_KEY_PASSWORD) && MQTT_ENABLE_SSL + esp_transport_ssl_set_client_key_password(ssl, + cfg->clientkey_password, + cfg->clientkey_password_len); +#else + ESP_LOGE(TAG, "Password protected keys are not available in IDF version %s", IDF_VER); + goto esp_mqtt_set_transport_failed; +#endif + } + + + return ESP_OK; + +esp_mqtt_set_transport_failed: + return ESP_FAIL; +} +#endif // MQTT_ENABLE_SSL + +/* Checks if the user supplied config values are internally consistent */ +static esp_err_t esp_mqtt_check_cfg_conflict(const mqtt_config_storage_t *cfg, const esp_mqtt_client_config_t *user_cfg) +{ + if (cfg == NULL || user_cfg == NULL) { + ESP_LOGE(TAG, "Invalid configuration"); + return ESP_ERR_INVALID_ARG; + } + esp_err_t ret = ESP_OK; + + bool ssl_cfg_enabled = cfg->use_global_ca_store || cfg->cacert_buf || cfg->clientcert_buf || cfg->psk_hint_key || cfg->alpn_protos; + bool is_ssl_scheme = false; + if (cfg->scheme) { + is_ssl_scheme = (strncasecmp(cfg->scheme, MQTT_OVER_SSL_SCHEME, sizeof(MQTT_OVER_SSL_SCHEME)) == 0) || (strncasecmp(cfg->scheme, MQTT_OVER_WSS_SCHEME, sizeof(MQTT_OVER_WSS_SCHEME)) == 0); + } + + if (!is_ssl_scheme && ssl_cfg_enabled) { + if (cfg->uri) { + ESP_LOGW(TAG, "SSL related configs set, but the URI scheme specifies a non-SSL scheme, scheme = %s", cfg->scheme); + } else { + ESP_LOGW(TAG, "SSL related configs set, but the transport protocol is a non-SSL scheme, transport = %d", user_cfg->broker.address.transport); + } + ret = ESP_ERR_INVALID_ARG; + } + + if (cfg->uri && user_cfg->broker.address.transport) { + ESP_LOGW(TAG, "Transport config set, but overridden by scheme from URI: transport = %d, uri scheme = %s", user_cfg->broker.address.transport, cfg->scheme); + ret = ESP_ERR_INVALID_ARG; + } + + return ret; +} + +bool esp_mqtt_set_if_config(char const *const new_config, char **old_config) +{ + if (new_config) { + free(*old_config); + *old_config = strdup(new_config); + if (*old_config == NULL) { + return false; + } + } + return true; +} + +static esp_err_t esp_mqtt_client_create_transport(esp_mqtt_client_handle_t client) +{ + esp_err_t ret = ESP_OK; + if (client->transport_list) { + esp_transport_list_destroy(client->transport_list); + client->transport_list = NULL; + } + if (client->config->scheme) { + client->transport_list = esp_transport_list_init(); + ESP_MEM_CHECK(TAG, client->transport_list, return ESP_ERR_NO_MEM); + + if ((strncasecmp(client->config->scheme, MQTT_OVER_TCP_SCHEME, sizeof(MQTT_OVER_TCP_SCHEME)) == 0) || (strncasecmp(client->config->scheme, MQTT_OVER_WS_SCHEME, sizeof(MQTT_OVER_WS_SCHEME)) == 0)) { + esp_transport_handle_t tcp = esp_transport_tcp_init(); + ESP_MEM_CHECK(TAG, tcp, return ESP_ERR_NO_MEM); + esp_transport_set_default_port(tcp, MQTT_TCP_DEFAULT_PORT); + if (client->config->if_name) { + esp_transport_tcp_set_interface_name(tcp, client->config->if_name); + } + esp_transport_list_add(client->transport_list, tcp, MQTT_OVER_TCP_SCHEME); + if (strncasecmp(client->config->scheme, MQTT_OVER_WS_SCHEME, sizeof(MQTT_OVER_WS_SCHEME)) == 0) { +#if MQTT_ENABLE_WS + esp_transport_handle_t ws = esp_transport_ws_init(tcp); + ESP_MEM_CHECK(TAG, ws, return ESP_ERR_NO_MEM); + esp_transport_set_default_port(ws, MQTT_WS_DEFAULT_PORT); + if (client->config->path) { + esp_transport_ws_set_path(ws, client->config->path); + } +#ifdef MQTT_SUPPORTED_FEATURE_WS_SUBPROTOCOL + esp_transport_ws_set_subprotocol(ws, MQTT_OVER_TCP_SCHEME); +#endif + esp_transport_list_add(client->transport_list, ws, MQTT_OVER_WS_SCHEME); +#else + ESP_LOGE(TAG, "Please enable MQTT_ENABLE_WS to use %s", client->config->scheme); + ret = ESP_FAIL; +#endif + } + } else if ((strncasecmp(client->config->scheme, MQTT_OVER_SSL_SCHEME, sizeof(MQTT_OVER_SSL_SCHEME)) == 0) || (strncasecmp(client->config->scheme, MQTT_OVER_WSS_SCHEME, sizeof(MQTT_OVER_WSS_SCHEME)) == 0)) { +#if MQTT_ENABLE_SSL + esp_transport_handle_t ssl = esp_transport_ssl_init(); + ESP_MEM_CHECK(TAG, ssl, return ESP_ERR_NO_MEM); + esp_transport_set_default_port(ssl, MQTT_SSL_DEFAULT_PORT); + if (client->config->if_name) { + esp_transport_ssl_set_interface_name(ssl, client->config->if_name); + } + esp_transport_list_add(client->transport_list, ssl, MQTT_OVER_SSL_SCHEME); + if (strncasecmp(client->config->scheme, MQTT_OVER_WSS_SCHEME, sizeof(MQTT_OVER_WSS_SCHEME)) == 0) { +#if MQTT_ENABLE_WS + esp_transport_handle_t wss = esp_transport_ws_init(ssl); + ESP_MEM_CHECK(TAG, wss, return ESP_ERR_NO_MEM); + esp_transport_set_default_port(wss, MQTT_WSS_DEFAULT_PORT); + if (client->config->path) { + esp_transport_ws_set_path(wss, client->config->path); + } +#ifdef MQTT_SUPPORTED_FEATURE_WS_SUBPROTOCOL + esp_transport_ws_set_subprotocol(wss, MQTT_OVER_TCP_SCHEME); +#endif + esp_transport_list_add(client->transport_list, wss, MQTT_OVER_WSS_SCHEME); +#else + ESP_LOGE(TAG, "Please enable MQTT_ENABLE_WS to use %s", client->config->scheme); + ret = ESP_FAIL; +#endif + } +#else + ESP_LOGE(TAG, "Please enable MQTT_ENABLE_SSL to use %s", client->config->scheme); + ret = ESP_FAIL; +#endif + } else { + ESP_LOGE(TAG, "Not support this mqtt scheme %s", client->config->scheme); + ret = ESP_FAIL; + } + } else { + ESP_LOGE(TAG, "No scheme found"); + ret = ESP_FAIL; + } + return ret; +} + +esp_err_t esp_mqtt_set_config(esp_mqtt_client_handle_t client, const esp_mqtt_client_config_t *config) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + //Copy user configurations to client context + esp_err_t err = ESP_OK; + if (!client->config) { + client->config = calloc(1, sizeof(mqtt_config_storage_t)); + ESP_MEM_CHECK(TAG, client->config, { + MQTT_API_UNLOCK(client); + return ESP_ERR_NO_MEM; + }); + } + + mqtt_msg_buffer_destroy(&client->mqtt_state.connection); + int buffer_size = config->buffer.size; + if (buffer_size <= 0) { + buffer_size = MQTT_BUFFER_SIZE_BYTE; + } + + // use separate value for output buffer size if configured + int out_buffer_size = config->buffer.out_size > 0 ? config->buffer.out_size : buffer_size; + if (mqtt_msg_buffer_init(&client->mqtt_state.connection, out_buffer_size) != ESP_OK) { + goto _mqtt_set_config_failed; + } + + free(client->mqtt_state.in_buffer); + client->mqtt_state.in_buffer = (uint8_t *)malloc(buffer_size); + ESP_MEM_CHECK(TAG, client->mqtt_state.in_buffer, goto _mqtt_set_config_failed); + client->mqtt_state.in_buffer_length = buffer_size; + + client->config->message_retransmit_timeout = config->session.message_retransmit_timeout; + if (config->session.message_retransmit_timeout <= 0) { + client->config->message_retransmit_timeout = MQTT_DEFAULT_RETRANSMIT_TIMEOUT_MS; + } + + client->config->task_prio = config->task.priority; + if (client->config->task_prio <= 0) { + client->config->task_prio = MQTT_TASK_PRIORITY; + } + + client->config->task_stack = config->task.stack_size; + if (client->config->task_stack <= 0) { + client->config->task_stack = MQTT_TASK_STACK; + } + + if (config->broker.address.port) { + client->config->port = config->broker.address.port; + } + + if (config->network.tcp_keep_alive_cfg.keep_alive_enable) { + client->config->tcp_keep_alive_cfg = config->network.tcp_keep_alive_cfg; + } + + err = ESP_ERR_NO_MEM; + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->broker.address.hostname, &client->config->host), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->broker.address.path, &client->config->path), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->credentials.username, &client->mqtt_state.connection.information.username), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->credentials.authentication.password, &client->mqtt_state.connection.information.password), goto _mqtt_set_config_failed); + + if (!config->credentials.set_null_client_id) { + if (config->credentials.client_id) { + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->credentials.client_id, &client->mqtt_state.connection.information.client_id), goto _mqtt_set_config_failed); + } else if (client->mqtt_state.connection.information.client_id == NULL) { + client->mqtt_state.connection.information.client_id = platform_create_id_string(); + } + ESP_MEM_CHECK(TAG, client->mqtt_state.connection.information.client_id, goto _mqtt_set_config_failed); + ESP_LOGD(TAG, "MQTT client_id=%s", client->mqtt_state.connection.information.client_id); + } + + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->broker.address.uri, &client->config->uri), goto _mqtt_set_config_failed); + ESP_MEM_CHECK(TAG, esp_mqtt_set_if_config(config->session.last_will.topic, &client->mqtt_state.connection.information.will_topic), goto _mqtt_set_config_failed); + + if (config->session.last_will.msg_len && config->session.last_will.msg) { + free(client->mqtt_state.connection.information.will_message); + client->mqtt_state.connection.information.will_message = malloc(config->session.last_will.msg_len); + ESP_MEM_CHECK(TAG, client->mqtt_state.connection.information.will_message, goto _mqtt_set_config_failed); + memcpy(client->mqtt_state.connection.information.will_message, config->session.last_will.msg, config->session.last_will.msg_len); + client->mqtt_state.connection.information.will_length = config->session.last_will.msg_len; + } else if (config->session.last_will.msg) { + free(client->mqtt_state.connection.information.will_message); + client->mqtt_state.connection.information.will_message = strdup(config->session.last_will.msg); + ESP_MEM_CHECK(TAG, client->mqtt_state.connection.information.will_message, goto _mqtt_set_config_failed); + client->mqtt_state.connection.information.will_length = strlen(config->session.last_will.msg); + } + if (config->session.last_will.qos) { + client->mqtt_state.connection.information.will_qos = config->session.last_will.qos; + } + if (config->session.last_will.retain) { + client->mqtt_state.connection.information.will_retain = config->session.last_will.retain; + } + + if (config->session.disable_clean_session == client->mqtt_state.connection.information.clean_session) { + client->mqtt_state.connection.information.clean_session = !config->session.disable_clean_session; + if (!client->mqtt_state.connection.information.clean_session && config->credentials.set_null_client_id) { + ESP_LOGE(TAG, "Clean Session flag must be true if client has a null id"); + } + } + if (config->session.keepalive) { + client->mqtt_state.connection.information.keepalive = config->session.keepalive; + } + if (client->mqtt_state.connection.information.keepalive == 0) { + client->mqtt_state.connection.information.keepalive = MQTT_KEEPALIVE_TICK; + } + if (config->session.disable_keepalive) { + // internal `keepalive` value (in connect_info) is in line with 3.1.2.10 Keep Alive from mqtt spec: + // * keepalive=0: Keep alive mechanism disabled (server not to disconnect the client on its inactivity) + // * period in seconds to send a Control packet if inactive + client->mqtt_state.connection.information.keepalive = 0; + } + + if (config->session.protocol_ver) { + client->mqtt_state.connection.information.protocol_ver = config->session.protocol_ver; + } + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_UNDEFINED) { +#ifdef MQTT_PROTOCOL_311 + client->mqtt_state.connection.information.protocol_ver = MQTT_PROTOCOL_V_3_1_1; +#else + client->mqtt_state.connection.information.protocol_ver = MQTT_PROTOCOL_V_3_1; +#endif + } else if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifndef MQTT_PROTOCOL_5 + ESP_LOGE(TAG, "Please first enable MQTT_PROTOCOL_5 feature in menuconfig"); + goto _mqtt_set_config_failed; +#endif + } + + client->config->network_timeout_ms = config->network.timeout_ms; + if (client->config->network_timeout_ms <= 0) { + client->config->network_timeout_ms = MQTT_NETWORK_TIMEOUT_MS; + } + + if (config->network.refresh_connection_after_ms) { + client->config->refresh_connection_after_ms = config->network.refresh_connection_after_ms; + } + + client->config->auto_reconnect = true; + if (config->network.disable_auto_reconnect == client->config->auto_reconnect) { + client->config->auto_reconnect = !config->network.disable_auto_reconnect; + } + + if (config->network.reconnect_timeout_ms) { + client->config->reconnect_timeout_ms = config->network.reconnect_timeout_ms; + } else { + client->config->reconnect_timeout_ms = MQTT_RECON_DEFAULT_MS; + } + + client->config->transport = config->network.transport; + + if (config->network.if_name) { + client->config->if_name = calloc(1, sizeof(struct ifreq) + 1); + ESP_MEM_CHECK(TAG, client->config->if_name, goto _mqtt_set_config_failed); + memcpy(client->config->if_name, config->network.if_name, sizeof(struct ifreq)); + } + + if (config->broker.verification.alpn_protos) { + for (int i = 0; i < client->config->num_alpn_protos; i++) { + free(client->config->alpn_protos[i]); + } + free(client->config->alpn_protos); + client->config->num_alpn_protos = 0; + + const char **p; + + for (p = config->broker.verification.alpn_protos; *p != NULL; p++ ) { + client->config->num_alpn_protos++; + } + // mbedTLS expects the list to be null-terminated + client->config->alpn_protos = calloc(client->config->num_alpn_protos + 1, sizeof(*config->broker.verification.alpn_protos)); + ESP_MEM_CHECK(TAG, client->config->alpn_protos, goto _mqtt_set_config_failed); + + for (int i = 0; i < client->config->num_alpn_protos; i++) { + client->config->alpn_protos[i] = strdup(config->broker.verification.alpn_protos[i]); + ESP_MEM_CHECK(TAG, client->config->alpn_protos[i], goto _mqtt_set_config_failed); + } + } + + // configure ssl related parameters + client->config->use_global_ca_store = config->broker.verification.use_global_ca_store; + client->config->cacert_buf = config->broker.verification.certificate; + client->config->cacert_bytes = config->broker.verification.certificate_len; + client->config->psk_hint_key = config->broker.verification.psk_hint_key; + client->config->crt_bundle_attach = config->broker.verification.crt_bundle_attach; + client->config->clientcert_buf = config->credentials.authentication.certificate; + client->config->clientcert_bytes = config->credentials.authentication.certificate_len; + client->config->clientkey_buf = config->credentials.authentication.key; + client->config->clientkey_bytes = config->credentials.authentication.key_len; + client->config->skip_cert_common_name_check = config->broker.verification.skip_cert_common_name_check; + client->config->common_name = config->broker.verification.common_name; + client->config->use_secure_element = config->credentials.authentication.use_secure_element; + client->config->ds_data = config->credentials.authentication.ds_data; + client->config->use_ecdsa_peripheral = config->credentials.authentication.use_ecdsa_peripheral; + client->config->ecdsa_key_efuse_blk = config->credentials.authentication.ecdsa_key_efuse_blk; + + if (config->credentials.authentication.key_password && config->credentials.authentication.key_password_len) { + client->config->clientkey_password_len = config->credentials.authentication.key_password_len; + client->config->clientkey_password = malloc(client->config->clientkey_password_len); + ESP_MEM_CHECK(TAG, client->config->clientkey_password, goto _mqtt_set_config_failed); + memcpy(client->config->clientkey_password, config->credentials.authentication.key_password, client->config->clientkey_password_len); + } + + if (config->broker.address.transport) { + free(client->config->scheme); + client->config->scheme = NULL; + if (config->broker.address.transport == MQTT_TRANSPORT_OVER_TCP) { + client->config->scheme = create_string(MQTT_OVER_TCP_SCHEME, strlen(MQTT_OVER_TCP_SCHEME)); + ESP_MEM_CHECK(TAG, client->config->scheme, goto _mqtt_set_config_failed); + } +#if MQTT_ENABLE_WS + else if (config->broker.address.transport == MQTT_TRANSPORT_OVER_WS) { + client->config->scheme = create_string(MQTT_OVER_WS_SCHEME, strlen(MQTT_OVER_WS_SCHEME)); + ESP_MEM_CHECK(TAG, client->config->scheme, goto _mqtt_set_config_failed); + } +#endif +#if MQTT_ENABLE_SSL + else if (config->broker.address.transport == MQTT_TRANSPORT_OVER_SSL) { + client->config->scheme = create_string(MQTT_OVER_SSL_SCHEME, strlen(MQTT_OVER_SSL_SCHEME)); + ESP_MEM_CHECK(TAG, client->config->scheme, goto _mqtt_set_config_failed); + } +#endif +#if MQTT_ENABLE_WSS + else if (config->broker.address.transport == MQTT_TRANSPORT_OVER_WSS) { + client->config->scheme = create_string(MQTT_OVER_WSS_SCHEME, strlen(MQTT_OVER_WSS_SCHEME)); + ESP_MEM_CHECK(TAG, client->config->scheme, goto _mqtt_set_config_failed); + } +#endif + } + + // Set uri at the end of config to override separately configured uri elements + if (config->broker.address.uri) { + if (esp_mqtt_client_set_uri(client, client->config->uri) != ESP_OK) { + err = ESP_FAIL; + goto _mqtt_set_config_failed; + } + } + client->config->outbox_limit = config->outbox.limit; + esp_err_t config_has_conflict = esp_mqtt_check_cfg_conflict(client->config, config); + + MQTT_API_UNLOCK(client); + + return config_has_conflict; +_mqtt_set_config_failed: + esp_mqtt_destroy_config(client); + MQTT_API_UNLOCK(client); + return err; +} + +void esp_mqtt_destroy_config(esp_mqtt_client_handle_t client) +{ + if (client->config == NULL) { + return; + } + free(client->mqtt_state.in_buffer); + mqtt_msg_buffer_destroy(&client->mqtt_state.connection); + free(client->config->host); + free(client->config->uri); + free(client->config->path); + free(client->config->scheme); + for (int i = 0; i < client->config->num_alpn_protos; i++) { + free(client->config->alpn_protos[i]); + } + free(client->config->alpn_protos); + free(client->config->clientkey_password); + free(client->config->if_name); + free(client->mqtt_state.connection.information.will_topic); + free(client->mqtt_state.connection.information.will_message); + free(client->mqtt_state.connection.information.client_id); + free(client->mqtt_state.connection.information.username); + free(client->mqtt_state.connection.information.password); +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_client_destory(client); +#endif + memset(&client->mqtt_state.connection.information, 0, sizeof(mqtt_connect_info_t)); +#ifdef MQTT_SUPPORTED_FEATURE_EVENT_LOOP + if (client->config->event_loop_handle) { + esp_event_loop_delete(client->config->event_loop_handle); + } +#endif + esp_transport_destroy(client->config->transport); + memset(client->config, 0, sizeof(mqtt_config_storage_t)); + free(client->config); + client->config = NULL; +} + +static inline bool has_timed_out(uint64_t last_tick, uint64_t timeout) +{ + uint64_t next = last_tick + timeout; + return (int64_t)(next - platform_tick_get_ms()) <= 0; +} + +static esp_err_t process_keepalive(esp_mqtt_client_handle_t client) +{ + if (client->mqtt_state.connection.information.keepalive > 0) { + const uint64_t keepalive_ms = client->mqtt_state.connection.information.keepalive * 1000; + + if (client->wait_for_ping_resp == true ) { + if (has_timed_out(client->keepalive_tick, keepalive_ms)) { + ESP_LOGE(TAG, "No PING_RESP, disconnected"); + esp_mqtt_abort_connection(client); + client->wait_for_ping_resp = false; + return ESP_FAIL; + } + return ESP_OK; + } + + if (has_timed_out(client->keepalive_tick, keepalive_ms / 2)) { + if (esp_mqtt_client_ping(client) == ESP_FAIL) { + ESP_LOGE(TAG, "Can't send ping, disconnected"); + esp_mqtt_abort_connection(client); + return ESP_FAIL; + } + client->wait_for_ping_resp = true; + return ESP_OK; + } + } + return ESP_OK; +} + +static inline esp_err_t esp_mqtt_write(esp_mqtt_client_handle_t client) +{ + int wlen = 0, widx = 0, len = client->mqtt_state.connection.outbound_message.length; + while (len > 0) { + wlen = esp_transport_write(client->transport, + (char *)client->mqtt_state.connection.outbound_message.data + widx, + len, + client->config->network_timeout_ms); + if (wlen < 0) { + ESP_LOGE(TAG, "Writing failed: errno=%d", errno); + esp_mqtt_client_dispatch_transport_error(client); + return ESP_FAIL; + } + + if (wlen == 0) { + ESP_LOGE(TAG, "Writing didn't complete in specified timeout: errno=%d", errno); + return ESP_ERR_TIMEOUT; + } + widx += wlen; + len -= wlen; + } + return ESP_OK; +} + +static esp_err_t esp_mqtt_connect(esp_mqtt_client_handle_t client, int timeout_ms) +{ + int read_len, connect_rsp_code = 0; + client->wait_for_ping_resp = false; + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + mqtt5_msg_connect(&client->mqtt_state.connection, + &client->mqtt_state.connection.information, &client->mqtt5_config->connect_property_info, &client->mqtt5_config->will_property_info); +#endif + } else { + mqtt_msg_connect(&client->mqtt_state.connection, + &client->mqtt_state.connection.information); + } + if (client->mqtt_state.connection.outbound_message.length == 0) { + ESP_LOGE(TAG, "Connect message cannot be created"); + return ESP_FAIL; + } + + client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.connection.outbound_message.data); + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + client->mqtt_state.pending_msg_id = mqtt5_get_id(client->mqtt_state.connection.outbound_message.data, + client->mqtt_state.connection.outbound_message.length); +#endif + } else { + client->mqtt_state.pending_msg_id = mqtt_get_id(client->mqtt_state.connection.outbound_message.data, + client->mqtt_state.connection.outbound_message.length); + } + ESP_LOGD(TAG, "Sending MQTT CONNECT message, type: %d, id: %04X", + client->mqtt_state.pending_msg_type, + client->mqtt_state.pending_msg_id); + + if (esp_mqtt_write(client) != ESP_OK) { + return ESP_FAIL; + } + + client->mqtt_state.in_buffer_read_len = 0; + client->mqtt_state.message_length = 0; + + /* wait configured network timeout for broker connection response */ + uint64_t connack_recv_started = platform_tick_get_ms(); + do { + read_len = mqtt_message_receive(client, client->config->network_timeout_ms); + } while (read_len == 0 && platform_tick_get_ms() - connack_recv_started < client->config->network_timeout_ms); + + if (read_len <= 0) { + ESP_LOGE(TAG, "%s: mqtt_message_receive() returned %d", __func__, read_len); + return ESP_FAIL; + } + + if (mqtt_get_type(client->mqtt_state.in_buffer) != MQTT_MSG_TYPE_CONNACK) { + ESP_LOGE(TAG, "Invalid MSG_TYPE response: %d, read_len: %d", mqtt_get_type(client->mqtt_state.in_buffer), read_len); + return ESP_FAIL; + } + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + if (esp_mqtt5_parse_connack(client, &connect_rsp_code) == ESP_OK) { + client->send_publish_packet_count = 0; + return ESP_OK; + } +#endif + } else { + client->mqtt_state.in_buffer_read_len = 0; + connect_rsp_code = mqtt_get_connect_return_code(client->mqtt_state.in_buffer); + if (connect_rsp_code == MQTT_CONNECTION_ACCEPTED) { + ESP_LOGD(TAG, "Connected"); + return ESP_OK; + } + switch (connect_rsp_code) { + case MQTT_CONNECTION_REFUSE_PROTOCOL: + ESP_LOGW(TAG, "Connection refused, bad protocol"); + break; + case MQTT_CONNECTION_REFUSE_SERVER_UNAVAILABLE: + ESP_LOGW(TAG, "Connection refused, server unavailable"); + break; + case MQTT_CONNECTION_REFUSE_BAD_USERNAME: + ESP_LOGW(TAG, "Connection refused, bad username or password"); + break; + case MQTT_CONNECTION_REFUSE_NOT_AUTHORIZED: + ESP_LOGW(TAG, "Connection refused, not authorized"); + break; + default: + ESP_LOGW(TAG, "Connection refused, Unknow reason"); + break; + } + } + /* propagate event with connection refused error */ + client->event.event_id = MQTT_EVENT_ERROR; + client->event.error_handle->error_type = MQTT_ERROR_TYPE_CONNECTION_REFUSED; + client->event.error_handle->connect_return_code = connect_rsp_code; + client->event.error_handle->esp_tls_stack_err = 0; + client->event.error_handle->esp_tls_last_esp_err = 0; + client->event.error_handle->esp_tls_cert_verify_flags = 0; + esp_mqtt_dispatch_event_with_msgid(client); + + return ESP_FAIL; +} + +static void esp_mqtt_abort_connection(esp_mqtt_client_handle_t client) +{ + MQTT_API_LOCK(client); + esp_transport_close(client->transport); + client->wait_timeout_ms = client->config->reconnect_timeout_ms; + client->reconnect_tick = platform_tick_get_ms(); + client->state = MQTT_STATE_WAIT_RECONNECT; + ESP_LOGD(TAG, "Reconnect after %d ms", client->wait_timeout_ms); + client->event.event_id = MQTT_EVENT_DISCONNECTED; + client->wait_for_ping_resp = false; + esp_mqtt_dispatch_event_with_msgid(client); + MQTT_API_UNLOCK(client); +} + +static bool create_client_data(esp_mqtt_client_handle_t client) +{ + client->event.error_handle = calloc(1, sizeof(esp_mqtt_error_codes_t)); + ESP_MEM_CHECK(TAG, client->event.error_handle, return false) + + client->api_lock = xSemaphoreCreateRecursiveMutex(); + ESP_MEM_CHECK(TAG, client->api_lock, return false); + + client->outbox = outbox_init(); + ESP_MEM_CHECK(TAG, client->outbox, return false); + client->status_bits = xEventGroupCreate(); + ESP_MEM_CHECK(TAG, client->status_bits, return false); + + return true; +} + +esp_mqtt_client_handle_t esp_mqtt_client_init(const esp_mqtt_client_config_t *config) +{ + esp_mqtt_client_handle_t client = heap_caps_calloc(1, sizeof(struct esp_mqtt_client), +#if MQTT_EVENT_QUEUE_SIZE > 1 + // if supporting multiple queued events, we keep track of them + // using atomic variable, so need to make sure it won't get allocated in PSRAM + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); +#else + MALLOC_CAP_DEFAULT); +#endif + ESP_MEM_CHECK(TAG, client, return NULL); + if (!create_client_data(client)) { + goto _mqtt_init_failed; + } + + if (esp_mqtt_set_config(client, config) != ESP_OK) { + goto _mqtt_init_failed; + } +#ifdef MQTT_SUPPORTED_FEATURE_EVENT_LOOP + esp_event_loop_args_t no_task_loop = { + .queue_size = MQTT_EVENT_QUEUE_SIZE, + .task_name = NULL, + }; + esp_event_loop_create(&no_task_loop, &client->config->event_loop_handle); +#if MQTT_EVENT_QUEUE_SIZE > 1 + atomic_init(&client->queued_events, 0); +#endif +#endif + + client->keepalive_tick = platform_tick_get_ms(); + client->reconnect_tick = platform_tick_get_ms(); + client->refresh_connection_tick = platform_tick_get_ms(); + client->wait_for_ping_resp = false; +#ifdef MQTT_PROTOCOL_5 + if (esp_mqtt5_create_default_config(client) != ESP_OK) { + goto _mqtt_init_failed; + } +#endif + return client; +_mqtt_init_failed: + esp_mqtt_client_destroy(client); + return NULL; +} + +esp_err_t esp_mqtt_client_destroy(esp_mqtt_client_handle_t client) +{ + if (client == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (client->run) { + esp_mqtt_client_stop(client); + } + esp_mqtt_destroy_config(client); + if (client->transport_list) { + esp_transport_list_destroy(client->transport_list); + } + if (client->outbox) { + outbox_destroy(client->outbox); + } + if (client->status_bits) { + vEventGroupDelete(client->status_bits); + } + if (client->api_lock) { + vSemaphoreDelete(client->api_lock); + } + free(client->event.error_handle); + free(client); + return ESP_OK; +} + +static char *create_string(const char *ptr, int len) +{ + char *ret; + if (len <= 0) { + return NULL; + } + ret = calloc(1, len + 1); + ESP_MEM_CHECK(TAG, ret, return NULL); + memcpy(ret, ptr, len); + return ret; +} + +esp_err_t esp_mqtt_client_set_uri(esp_mqtt_client_handle_t client, const char *uri) +{ + struct http_parser_url puri; + http_parser_url_init(&puri); + int parser_status = http_parser_parse_url(uri, strlen(uri), 0, &puri); + if (parser_status != 0) { + ESP_LOGE(TAG, "Error parse uri = %s", uri); + return ESP_FAIL; + } + + // This API could be also executed when client is active (need to protect config fields) + MQTT_API_LOCK(client); + // set uri overrides actual scheme, host, path if configured previously +// False-positive leak detection. TODO: GCC-366 +#pragma GCC diagnostic push // TODO: IDF-10105 +#if __clang__ +#pragma clang diagnostic ignored "-Wunknown-warning-option" +#else +#pragma GCC diagnostic ignored "-Wpragmas" +#endif +#pragma GCC diagnostic ignored "-Wanalyzer-malloc-leak" + free(client->config->scheme); + free(client->config->host); + free(client->config->path); + + client->config->scheme = create_string(uri + puri.field_data[UF_SCHEMA].off, puri.field_data[UF_SCHEMA].len); + client->config->host = create_string(uri + puri.field_data[UF_HOST].off, puri.field_data[UF_HOST].len); + client->config->path = NULL; +#pragma GCC diagnostic pop + + if (puri.field_data[UF_PATH].len || puri.field_data[UF_QUERY].len) { + int asprintf_ret_value; + if (puri.field_data[UF_QUERY].len == 0) { + asprintf_ret_value = asprintf(&client->config->path, + "%.*s", + puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off); + } else if (puri.field_data[UF_PATH].len == 0) { + asprintf_ret_value = asprintf(&client->config->path, + "/?%.*s", + puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off); + } else { + asprintf_ret_value = asprintf(&client->config->path, + "%.*s?%.*s", + puri.field_data[UF_PATH].len, uri + puri.field_data[UF_PATH].off, + puri.field_data[UF_QUERY].len, uri + puri.field_data[UF_QUERY].off); + } + + if (asprintf_ret_value == -1) { + ESP_LOGE(TAG,"%s(%d): %s", __FUNCTION__, __LINE__, "Memory exhausted"); + MQTT_API_UNLOCK(client); + return ESP_ERR_NO_MEM; + } + } + + if (puri.field_data[UF_PORT].len) { + client->config->port = strtol((const char *)(uri + puri.field_data[UF_PORT].off), NULL, 10); + } + + char *user_info = create_string(uri + puri.field_data[UF_USERINFO].off, puri.field_data[UF_USERINFO].len); + if (user_info) { + char *pass = strchr(user_info, ':'); + if (pass) { + pass[0] = 0; //terminal username + pass ++; + client->mqtt_state.connection.information.password = strdup(pass); + } + client->mqtt_state.connection.information.username = strdup(user_info); + + free(user_info); + } + + MQTT_API_UNLOCK(client); + return ESP_OK; +} + + +static esp_err_t esp_mqtt_dispatch_event_with_msgid(esp_mqtt_client_handle_t client) +{ + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + client->event.msg_id = mqtt5_get_id(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_length); +#endif + } else { + client->event.msg_id = mqtt_get_id(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_length); + } + return esp_mqtt_dispatch_event(client); +} + +esp_err_t esp_mqtt_dispatch_custom_event(esp_mqtt_client_handle_t client, esp_mqtt_event_t *event) +{ + esp_err_t ret = esp_event_post_to(client->config->event_loop_handle, MQTT_EVENTS, MQTT_USER_EVENT, event, sizeof(*event), 0); +#if MQTT_EVENT_QUEUE_SIZE > 1 + if (ret == ESP_OK) { + atomic_fetch_add(&client->queued_events, 1); + } +#endif + return ret; +} + +static esp_err_t esp_mqtt_dispatch_event(esp_mqtt_client_handle_t client) +{ + client->event.client = client; + client->event.protocol_ver = client->mqtt_state.connection.information.protocol_ver; + esp_err_t ret = ESP_FAIL; + +#ifdef MQTT_SUPPORTED_FEATURE_EVENT_LOOP + esp_event_post_to(client->config->event_loop_handle, MQTT_EVENTS, client->event.event_id, &client->event, sizeof(client->event), portMAX_DELAY); + ret = esp_event_loop_run(client->config->event_loop_handle, 0); +#else + return ESP_FAIL; +#endif + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_client_delete_user_property(client->event.property->user_property); + client->event.property->user_property = NULL; +#endif + } + return ret; +} + +static esp_err_t deliver_publish(esp_mqtt_client_handle_t client) +{ + uint8_t *msg_buf = client->mqtt_state.in_buffer; + size_t msg_read_len = client->mqtt_state.in_buffer_read_len; + size_t msg_total_len = client->mqtt_state.message_length; + size_t msg_topic_len = msg_read_len; + size_t msg_data_len = msg_read_len; + size_t msg_data_offset = 0; + size_t saved_msg_topic_len = 0; + char *saved_msg_topic = NULL; + char *msg_topic = NULL; + char *msg_data = NULL; + + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + if (esp_mqtt5_get_publish_data(client, msg_buf, msg_read_len, &msg_topic, &msg_topic_len, &msg_data, &msg_data_len) != ESP_OK) { + ESP_LOGE(TAG, "%s: esp_mqtt5_get_publish_data() failed", __func__); + return ESP_FAIL; + } +#endif + } else { + msg_topic = mqtt_get_publish_topic(msg_buf, &msg_topic_len); + if (msg_topic == NULL) { + ESP_LOGE(TAG, "%s: mqtt_get_publish_topic() failed", __func__); + return ESP_FAIL; + } + ESP_LOGD(TAG, "%s: msg_topic_len=%"NEWLIB_NANO_COMPAT_FORMAT, __func__, NEWLIB_NANO_COMPAT_CAST(msg_topic_len)); + + // get payload + msg_data = mqtt_get_publish_data(msg_buf, &msg_data_len); + if (msg_data_len > 0 && msg_data == NULL) { + ESP_LOGE(TAG, "%s: mqtt_get_publish_data() failed", __func__); + return ESP_FAIL; + } + } + + client->event.retain = mqtt_get_retain(msg_buf); + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + client->event.msg_id = mqtt5_get_id(msg_buf, msg_read_len); +#endif + } else { + client->event.msg_id = mqtt_get_id(msg_buf, msg_read_len); + } + client->event.qos = mqtt_get_qos(msg_buf); + client->event.dup = mqtt_get_dup(msg_buf); + client->event.total_data_len = msg_data_len + msg_total_len - msg_read_len; + + bool send_event = true; + while (send_event) { + ESP_LOGD(TAG, "Get data len= %"NEWLIB_NANO_COMPAT_FORMAT", topic len=%"NEWLIB_NANO_COMPAT_FORMAT", total_data: %d offset: %"NEWLIB_NANO_COMPAT_FORMAT, + NEWLIB_NANO_COMPAT_CAST(msg_data_len), NEWLIB_NANO_COMPAT_CAST(msg_topic_len), + client->event.total_data_len, NEWLIB_NANO_COMPAT_CAST(msg_data_offset)); + client->event.event_id = MQTT_EVENT_DATA; + client->event.data = msg_data_len > 0 ? msg_data : NULL; + client->event.data_len = msg_data_len; + client->event.current_data_offset = msg_data_offset; + client->event.topic = msg_topic; + client->event.topic_len = msg_topic_len; + esp_mqtt_dispatch_event(client); + send_event = false; + + if (msg_read_len < msg_total_len) { + send_event = true; + #ifdef CONFIG_MQTT_TOPIC_PRESENT_ALL_DATA_EVENTS + if (!saved_msg_topic) { + saved_msg_topic = strndup(msg_topic, msg_topic_len); + ESP_MEM_CHECK(TAG, saved_msg_topic, return ESP_ERR_NO_MEM); + saved_msg_topic_len = msg_topic_len; + } + #endif + size_t buf_len = client->mqtt_state.in_buffer_length; + + msg_data = (char *)client->mqtt_state.in_buffer; + msg_topic = saved_msg_topic; + msg_topic_len = saved_msg_topic_len; + msg_data_offset += msg_data_len; + int ret = esp_transport_read(client->transport, (char *)client->mqtt_state.in_buffer, + msg_total_len - msg_read_len > buf_len ? buf_len : msg_total_len - msg_read_len, + client->config->network_timeout_ms); + if (ret <= 0) { + return esp_mqtt_handle_transport_read_error(ret, client, false) == 0 ? ESP_OK : ESP_FAIL; + } + + msg_data_len = ret; + msg_read_len += msg_data_len; + } + } + free(saved_msg_topic); + return ESP_OK; +} + +static esp_err_t deliver_suback(esp_mqtt_client_handle_t client) +{ + uint8_t *msg_buf = client->mqtt_state.in_buffer; + size_t msg_data_len = client->mqtt_state.in_buffer_read_len; + char *msg_data = NULL; + + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + msg_data = mqtt5_get_suback_data(msg_buf, &msg_data_len, &client->event.property->user_property); +#else + // SUBACK Using MQTT5 received but MQTT5 is disabled, This is unlikely to happen. + return ESP_FAIL; +#endif + } else { + msg_data = mqtt_get_suback_data(msg_buf, &msg_data_len); + } + if (msg_data_len <= 0) { + ESP_LOGE(TAG, "Failed to acquire suback data"); + return ESP_FAIL; + } + client->event.error_handle->esp_tls_stack_err = 0; + client->event.error_handle->esp_tls_last_esp_err = 0; + client->event.error_handle->esp_tls_cert_verify_flags = 0; + client->event.error_handle->error_type = MQTT_ERROR_TYPE_NONE; + client->event.error_handle->connect_return_code = MQTT_CONNECTION_ACCEPTED; + // post data event + for (int topic = 0; topic < msg_data_len; ++topic) { + if ((uint8_t)msg_data[topic] >= 0x80) { + client->event.error_handle->error_type = MQTT_ERROR_TYPE_SUBSCRIBE_FAILED; + break; + } + } + client->event.data_len = msg_data_len; + client->event.total_data_len = msg_data_len; + client->event.event_id = MQTT_EVENT_SUBSCRIBED; + client->event.data = msg_data; + client->event.current_data_offset = 0; + esp_mqtt_dispatch_event_with_msgid(client); + + return ESP_OK; +} + +// Deletes the initial message in MQTT communication protocol +// Return false when message is not found, making the received counterpart invalid. +static bool remove_initiator_message(esp_mqtt_client_handle_t client, int msg_type, int msg_id) +{ + if (outbox_delete(client->outbox, msg_id, msg_type) == ESP_OK) { + ESP_LOGD(TAG, "Removed pending_id=%d", client->mqtt_state.pending_msg_id); + return true; + } + + ESP_LOGD(TAG, "Failed to remove pending_id=%d", client->mqtt_state.pending_msg_id); + return false; +} + +static outbox_item_handle_t mqtt_enqueue(esp_mqtt_client_handle_t client, uint8_t *remaining_data, int remaining_len) +{ + ESP_LOGD(TAG, "mqtt_enqueue id: %d, type=%d successful", + client->mqtt_state.pending_msg_id, client->mqtt_state.pending_msg_type); + outbox_message_t msg = { 0 }; + msg.data = client->mqtt_state.connection.outbound_message.data; + msg.len = client->mqtt_state.connection.outbound_message.length; + msg.msg_id = client->mqtt_state.pending_msg_id; + msg.msg_type = client->mqtt_state.pending_msg_type; + msg.msg_qos = client->mqtt_state.pending_publish_qos; + msg.remaining_data = remaining_data; + msg.remaining_len = remaining_len; + //Copy to queue buffer + return outbox_enqueue(client->outbox, &msg, platform_tick_get_ms()); +} + + +/* + * Returns: + * -2 in case of failure or EOF (clean connection closure) + * -1 timeout while in-the-middle of the messge + * 0 if no message has been received + * 1 if a message has been received and placed to client->mqtt_state: + * message length: client->mqtt_state.message_length + * message content: client->mqtt_state.in_buffer + * + */ +static int mqtt_message_receive(esp_mqtt_client_handle_t client, int read_poll_timeout_ms) +{ + int read_len, total_len, fixed_header_len; + uint8_t *buf = client->mqtt_state.in_buffer + client->mqtt_state.in_buffer_read_len; + esp_transport_handle_t t = client->transport; + + client->mqtt_state.message_length = 0; + if (client->mqtt_state.in_buffer_read_len == 0) { + /* + * Read first byte of the mqtt packet fixed header, it contains packet + * type and flags. + */ + read_len = esp_transport_read(t, (char *)buf, 1, read_poll_timeout_ms); + if (read_len <= 0) { + return esp_mqtt_handle_transport_read_error(read_len, client, false); + } + ESP_LOGD(TAG, "%s: first byte: 0x%x", __func__, *buf); + /* + * Verify the flags and act according to MQTT protocol: close connection + * if the flags are set incorrectly. + */ + if (!mqtt_has_valid_msg_hdr(buf, read_len)) { + ESP_LOGE(TAG, "%s: received a message with an invalid header=0x%x", __func__, *buf); + goto err; + } + buf++; + client->mqtt_state.in_buffer_read_len++; + } + if ((client->mqtt_state.in_buffer_read_len == 1) || + ((client->mqtt_state.in_buffer_read_len < 6) && (*(buf - 1) & 0x80))) { + do { + /* + * Read the "remaining length" part of mqtt packet fixed header. It + * starts at second byte and spans up to 4 bytes, but we accept here + * only up to 2 bytes of remaining length, i.e. messages with + * maximal remaining length value = 16383 (maximal total message + * size of 16386 bytes). + */ + read_len = esp_transport_read(t, (char *)buf, 1, read_poll_timeout_ms); + if (read_len <= 0) { + return esp_mqtt_handle_transport_read_error(read_len, client, true); + } + ESP_LOGD(TAG, "%s: read \"remaining length\" byte: 0x%x", __func__, *buf); + buf++; + client->mqtt_state.in_buffer_read_len++; + } while ((client->mqtt_state.in_buffer_read_len < 6) && (*(buf - 1) & 0x80)); + } + total_len = mqtt_get_total_length(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len, &fixed_header_len); + ESP_LOGD(TAG, "%s: total message length: %d (already read: %"NEWLIB_NANO_COMPAT_FORMAT")", __func__, total_len, NEWLIB_NANO_COMPAT_CAST(client->mqtt_state.in_buffer_read_len)); + client->mqtt_state.message_length = total_len; + if (client->mqtt_state.in_buffer_length < total_len) { + if (mqtt_get_type(client->mqtt_state.in_buffer) == MQTT_MSG_TYPE_PUBLISH) { + /* + * In case larger publish messages, we only need to read full topic, data can be split to multiple data event. + * Evaluate and correct total_len to read only publish message header, so data can be read separately + */ + if (client->mqtt_state.in_buffer_read_len < fixed_header_len + 2) { + /* read next 2 bytes - topic length to get minimum portion of publish packet */ + read_len = esp_transport_read(t, (char *)buf, client->mqtt_state.in_buffer_read_len - fixed_header_len + 2, read_poll_timeout_ms); + ESP_LOGD(TAG, "%s: read_len=%d", __func__, read_len); + if (read_len <= 0) { + return esp_mqtt_handle_transport_read_error(read_len, client, true); + } + client->mqtt_state.in_buffer_read_len += read_len; + buf += read_len; + if (client->mqtt_state.in_buffer_read_len < fixed_header_len + 2) { + ESP_LOGD(TAG, "%s: transport_read(): message reading left in progress :: total message length: %d (already read: %"NEWLIB_NANO_COMPAT_FORMAT")", + __func__, total_len, NEWLIB_NANO_COMPAT_CAST(client->mqtt_state.in_buffer_read_len)); + return 0; + } + } + int topic_len = client->mqtt_state.in_buffer[fixed_header_len] << 8; + topic_len |= client->mqtt_state.in_buffer[fixed_header_len + 1]; + total_len = fixed_header_len + topic_len + (mqtt_get_qos(client->mqtt_state.in_buffer) > 0 ? 2 : 0); + ESP_LOGD(TAG, "%s: total len modified to %d as message longer than input buffer", __func__, total_len); + if (client->mqtt_state.in_buffer_length < total_len) { + ESP_LOGE(TAG, "%s: message is too big, insufficient buffer size", __func__); + goto err; + } else { + total_len = client->mqtt_state.in_buffer_length; + } + /* free to continue with reading */ + } else { + ESP_LOGE(TAG, "%s: message is too big, insufficient buffer size", __func__); + goto err; + } + } + if (client->mqtt_state.in_buffer_read_len < total_len) { + /* read the rest of the mqtt message */ + read_len = esp_transport_read(t, (char *)buf, total_len - client->mqtt_state.in_buffer_read_len, read_poll_timeout_ms); + ESP_LOGD(TAG, "%s: read_len=%d", __func__, read_len); + if (read_len <= 0) { + return esp_mqtt_handle_transport_read_error(read_len, client, true); + } + client->mqtt_state.in_buffer_read_len += read_len; + if (client->mqtt_state.in_buffer_read_len < total_len) { + ESP_LOGD(TAG, "%s: transport_read(): message reading left in progress :: total message length: %d (already read: %"NEWLIB_NANO_COMPAT_FORMAT")", + __func__, total_len, NEWLIB_NANO_COMPAT_CAST(client->mqtt_state.in_buffer_read_len)); + return 0; + } + } + ESP_LOGV(TAG, "%s: transport_read():%"NEWLIB_NANO_COMPAT_FORMAT" %"NEWLIB_NANO_COMPAT_FORMAT, __func__, + NEWLIB_NANO_COMPAT_CAST(client->mqtt_state.in_buffer_read_len), NEWLIB_NANO_COMPAT_CAST(client->mqtt_state.message_length)); + return 1; +err: + esp_mqtt_client_dispatch_transport_error(client); + return -2; +} + +static esp_err_t mqtt_process_receive(esp_mqtt_client_handle_t client) +{ + uint8_t msg_type = 0, msg_qos = 0; + uint16_t msg_id = 0; + size_t previous_in_buffer_read_len = client->mqtt_state.in_buffer_read_len; + + /* non-blocking receive in order not to block other tasks */ + int recv = mqtt_message_receive(client, 0); + if (recv == 0) { // Timeout + return ESP_OK; + } + if (recv == -1) { // Mid-message timeout + if (previous_in_buffer_read_len == client->mqtt_state.in_buffer_read_len) { + // Report error only if didn't receive anything since previous iteration + ESP_LOGE(TAG, "%s: Network timeout while reading MQTT message", __func__); + return ESP_FAIL; + } + return ESP_OK; // Treat as standard timeout (keep reading the message) + } + if (recv < 0) { // Other error + ESP_LOGE(TAG, "%s: mqtt_message_receive() returned %d", __func__, recv); + return ESP_FAIL; + } + int read_len = client->mqtt_state.message_length; + + // If the message was valid, get the type, quality of service and id of the message + msg_type = mqtt_get_type(client->mqtt_state.in_buffer); + msg_qos = mqtt_get_qos(client->mqtt_state.in_buffer); + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + msg_id = mqtt5_get_id(client->mqtt_state.in_buffer, read_len); +#endif + } else { + msg_id = mqtt_get_id(client->mqtt_state.in_buffer, read_len); + } + + ESP_LOGD(TAG, "msg_type=%d, msg_id=%d", msg_type, msg_id); + + switch (msg_type) { + case MQTT_MSG_TYPE_SUBACK: + if (remove_initiator_message(client, MQTT_MSG_TYPE_SUBSCRIBE, msg_id)) { +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_parse_suback(client); +#endif + ESP_LOGD(TAG, "deliver_suback, message_length_read=%"NEWLIB_NANO_COMPAT_FORMAT", message_length=%"NEWLIB_NANO_COMPAT_FORMAT, + NEWLIB_NANO_COMPAT_CAST(client->mqtt_state.in_buffer_read_len), NEWLIB_NANO_COMPAT_CAST(client->mqtt_state.message_length)); + if (deliver_suback(client) != ESP_OK) { + ESP_LOGE(TAG, "Failed to deliver suback message id=%d", msg_id); + return ESP_FAIL; + } + } + break; + case MQTT_MSG_TYPE_UNSUBACK: + if (remove_initiator_message(client, MQTT_MSG_TYPE_UNSUBSCRIBE, msg_id)) { +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_parse_unsuback(client); +#endif + ESP_LOGD(TAG, "UnSubscribe successful"); + client->event.event_id = MQTT_EVENT_UNSUBSCRIBED; + esp_mqtt_dispatch_event_with_msgid(client); + } + break; + case MQTT_MSG_TYPE_PUBLISH: + ESP_LOGD(TAG, "deliver_publish, message_length_read=%"NEWLIB_NANO_COMPAT_FORMAT", message_length=%"NEWLIB_NANO_COMPAT_FORMAT, NEWLIB_NANO_COMPAT_CAST(client->mqtt_state.in_buffer_read_len), NEWLIB_NANO_COMPAT_CAST(client->mqtt_state.message_length)); + if (deliver_publish(client) != ESP_OK) { + ESP_LOGE(TAG, "Failed to deliver publish message id=%d", msg_id); + return ESP_FAIL; + } + if (msg_qos == 1 || msg_qos == 2) { + if (msg_qos == 1) { + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + mqtt5_msg_puback(&client->mqtt_state.connection, msg_id); +#endif + } else { + mqtt_msg_puback(&client->mqtt_state.connection, msg_id); + } + } else if (msg_qos == 2) { + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + mqtt5_msg_pubrec(&client->mqtt_state.connection, msg_id); +#endif + } else { + mqtt_msg_pubrec(&client->mqtt_state.connection, msg_id); + } + } + if (client->mqtt_state.connection.outbound_message.length == 0) { + ESP_LOGE(TAG, "Publish response message PUBACK or PUBREC cannot be created"); + return ESP_FAIL; + } + + ESP_LOGD(TAG, "Queue response QoS: %d", msg_qos); + + if (esp_mqtt_write(client) != ESP_OK) { + ESP_LOGE(TAG, "Error write qos msg repsonse, qos = %d", msg_qos); + return ESP_FAIL; + } + } + break; + case MQTT_MSG_TYPE_PUBACK: +#ifdef MQTT_PROTOCOL_5 + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + esp_mqtt5_decrement_packet_counter(client); + } +#endif + if (remove_initiator_message(client, MQTT_MSG_TYPE_PUBLISH, msg_id)) { + ESP_LOGD(TAG, "received MQTT_MSG_TYPE_PUBACK, finish QoS1 publish"); +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_parse_puback(client); +#endif + client->event.event_id = MQTT_EVENT_PUBLISHED; + esp_mqtt_dispatch_event_with_msgid(client); + } + break; + case MQTT_MSG_TYPE_PUBREC: + ESP_LOGD(TAG, "received MQTT_MSG_TYPE_PUBREC"); + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + ESP_LOGI(TAG, "MQTT_MSG_TYPE_PUBREC return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + mqtt5_msg_pubrel(&client->mqtt_state.connection, msg_id); +#endif + } else { + mqtt_msg_pubrel(&client->mqtt_state.connection, msg_id); + } + if (client->mqtt_state.connection.outbound_message.length == 0) { + ESP_LOGE(TAG, "Publish response message PUBREL cannot be created"); + return ESP_FAIL; + } + + outbox_set_pending(client->outbox, msg_id, ACKNOWLEDGED); + esp_mqtt_write(client); + break; + case MQTT_MSG_TYPE_PUBREL: + ESP_LOGD(TAG, "received MQTT_MSG_TYPE_PUBREL"); + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + ESP_LOGI(TAG, "MQTT_MSG_TYPE_PUBREL return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + mqtt5_msg_pubcomp(&client->mqtt_state.connection, msg_id); +#endif + } else { + mqtt_msg_pubcomp(&client->mqtt_state.connection, msg_id); + } + if (client->mqtt_state.connection.outbound_message.length == 0) { + ESP_LOGE(TAG, "Publish response message PUBCOMP cannot be created"); + return ESP_FAIL; + } + + esp_mqtt_write(client); + break; + case MQTT_MSG_TYPE_PUBCOMP: + ESP_LOGD(TAG, "received MQTT_MSG_TYPE_PUBCOMP"); +#ifdef MQTT_PROTOCOL_5 + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + esp_mqtt5_decrement_packet_counter(client); + } +#endif + if (remove_initiator_message(client, MQTT_MSG_TYPE_PUBLISH, msg_id)) { + ESP_LOGD(TAG, "Receive MQTT_MSG_TYPE_PUBCOMP, finish QoS2 publish"); +#ifdef MQTT_PROTOCOL_5 + esp_mqtt5_parse_pubcomp(client); +#endif + client->event.event_id = MQTT_EVENT_PUBLISHED; + esp_mqtt_dispatch_event_with_msgid(client); + } + break; + case MQTT_MSG_TYPE_PINGRESP: + ESP_LOGD(TAG, "MQTT_MSG_TYPE_PINGRESP"); + client->wait_for_ping_resp = false; + /* It is the responsibility of the Client to ensure that the interval between Control Packets + * being sent does not exceed the Keep Alive value. In the absence of sending any other Control + * Packets, the Client MUST send a PINGREQ Packet [MQTT-3.1.2-23]. + * [MQTT-3.1.2-23] + */ + client->keepalive_tick = platform_tick_get_ms(); + break; + case MQTT_MSG_TYPE_DISCONNECT: + ESP_LOGD(TAG, "MQTT_MSG_TYPE_DISCONNECT"); +#ifdef MQTT_PROTOCOL_5 + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + int disconnect_rsp_code; + esp_mqtt5_parse_disconnect(client, &disconnect_rsp_code); + client->event.event_id = MQTT_EVENT_DISCONNECTED; + client->event.error_handle->disconnect_return_code = disconnect_rsp_code; + esp_mqtt_dispatch_event_with_msgid(client); + } +#endif + break; + } + + client->mqtt_state.in_buffer_read_len = 0; + return ESP_OK; +} + +static esp_err_t mqtt_resend_queued(esp_mqtt_client_handle_t client, outbox_item_handle_t item) +{ + // decode queued data + client->mqtt_state.connection.outbound_message.data = outbox_item_get_data(item, &client->mqtt_state.connection.outbound_message.length, &client->mqtt_state.pending_msg_id, + &client->mqtt_state.pending_msg_type, &client->mqtt_state.pending_publish_qos); + // set duplicate flag for QoS-1 and QoS-2 messages + if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH && client->mqtt_state.pending_publish_qos > 0 && (outbox_item_get_pending(item) == TRANSMITTED)) { + mqtt_set_dup(client->mqtt_state.connection.outbound_message.data); + ESP_LOGD(TAG, "Sending Duplicated QoS%d message with id=%d", client->mqtt_state.pending_publish_qos, client->mqtt_state.pending_msg_id); + } + + // try to resend the data + if (esp_mqtt_write(client) != ESP_OK) { + ESP_LOGE(TAG, "Error to resend data "); + esp_mqtt_abort_connection(client); + return ESP_FAIL; + } + + return ESP_OK; +} + +static esp_err_t mqtt_resend_pubrel(esp_mqtt_client_handle_t client, outbox_item_handle_t item) +{ + client->mqtt_state.connection.outbound_message.data = outbox_item_get_data(item, &client->mqtt_state.connection.outbound_message.length, &client->mqtt_state.pending_msg_id, + &client->mqtt_state.pending_msg_type, &client->mqtt_state.pending_publish_qos); + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + ESP_LOGI(TAG, "MQTT_MSG_TYPE_PUBREC return code is %d", mqtt5_msg_get_reason_code(client->mqtt_state.in_buffer, client->mqtt_state.in_buffer_read_len)); + mqtt5_msg_pubrel(&client->mqtt_state.connection, client->mqtt_state.pending_msg_id); +#endif + } else { + mqtt_msg_pubrel(&client->mqtt_state.connection, client->mqtt_state.pending_msg_id); + } + if (client->mqtt_state.connection.outbound_message.length == 0) { + ESP_LOGE(TAG, "Publish response message PUBREL cannot be created"); + return ESP_FAIL; + } + + if (esp_mqtt_write(client) != ESP_OK) { + ESP_LOGE(TAG, "Error to resend data "); + esp_mqtt_abort_connection(client); + return ESP_FAIL; + } + + return ESP_OK; +} + +static void mqtt_delete_expired_messages(esp_mqtt_client_handle_t client) +{ + // Delete message after OUTBOX_EXPIRED_TIMEOUT_MS milliseconds +#if MQTT_REPORT_DELETED_MESSAGES + // also report the deleted items as MQTT_EVENT_DELETED events if enabled + int msg_id = 0; + while ((msg_id = outbox_delete_single_expired(client->outbox, platform_tick_get_ms(), OUTBOX_EXPIRED_TIMEOUT_MS)) >= 0) { + client->event.event_id = MQTT_EVENT_DELETED; + client->event.msg_id = msg_id; + if (esp_mqtt_dispatch_event(client) != ESP_OK) { + ESP_LOGE(TAG, "Failed to post event on deleting message id=%d", msg_id); + } + } +#else + outbox_delete_expired(client->outbox, platform_tick_get_ms(), OUTBOX_EXPIRED_TIMEOUT_MS); +#endif +} + +/** + * @brief When using multiple queued item, we'd like to reduce the poll timeout to proceed with event loop exacution + */ +static inline int max_poll_timeout(esp_mqtt_client_handle_t client, int max_timeout) +{ + return +#if MQTT_EVENT_QUEUE_SIZE > 1 + atomic_load(&client->queued_events) > 0 ? 10 : max_timeout; +#else + max_timeout; +#endif +} + +static inline void run_event_loop(esp_mqtt_client_handle_t client) +{ +#if MQTT_EVENT_QUEUE_SIZE > 1 + if (atomic_load(&client->queued_events) > 0) { + atomic_fetch_sub(&client->queued_events, 1); +#else + { +#endif + esp_err_t ret = esp_event_loop_run(client->config->event_loop_handle, 0); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error in running event_loop %d", ret); + } + } +} + +static void esp_mqtt_task(void *pv) +{ + esp_mqtt_client_handle_t client = (esp_mqtt_client_handle_t) pv; + uint64_t last_retransmit = 0; + outbox_tick_t msg_tick = 0; + client->run = true; + + client->state = MQTT_STATE_INIT; + xEventGroupClearBits(client->status_bits, STOPPED_BIT); + while (client->run) { + MQTT_API_LOCK(client); + run_event_loop(client); + // delete long pending messages + mqtt_delete_expired_messages(client); + mqtt_client_state_t state = client->state; + switch (state) { + case MQTT_STATE_DISCONNECTED: + break; + case MQTT_STATE_INIT: + xEventGroupClearBits(client->status_bits, RECONNECT_BIT | DISCONNECT_BIT); + + client->transport = client->config->transport; + if (!client->transport) { + + if (esp_mqtt_client_create_transport(client) != ESP_OK) { + ESP_LOGE(TAG, "Failed to create transport list"); + client->run = false; + break; + } + //get transport by scheme + client->transport = esp_transport_list_get_transport(client->transport_list, client->config->scheme); + + if (client->transport == NULL) { + ESP_LOGE(TAG, "There are no transports valid, stop mqtt client, config scheme = %s", client->config->scheme); + client->run = false; + break; + } + } + //default port + if (client->config->port == 0) { + client->config->port = esp_transport_get_default_port(client->transport); + } + +#if MQTT_ENABLE_SSL + esp_mqtt_set_ssl_transport_properties(client->transport_list, client->config); +#endif + + if(client->config->tcp_keep_alive_cfg.keep_alive_enable) { + esp_transport_tcp_set_keep_alive(client->transport, &client->config->tcp_keep_alive_cfg); + } + + client->event.event_id = MQTT_EVENT_BEFORE_CONNECT; + esp_mqtt_dispatch_event_with_msgid(client); + + if (esp_transport_connect(client->transport, + client->config->host, + client->config->port, + client->config->network_timeout_ms) < 0) { + ESP_LOGE(TAG, "Error transport connect"); + esp_mqtt_client_dispatch_transport_error(client); + esp_mqtt_abort_connection(client); + break; + } + ESP_LOGD(TAG, "Transport connected to %s://%s:%d", client->config->scheme, client->config->host, client->config->port); + if (esp_mqtt_connect(client, client->config->network_timeout_ms) != ESP_OK) { + ESP_LOGE(TAG, "MQTT connect failed"); + esp_mqtt_abort_connection(client); + break; + } + client->event.event_id = MQTT_EVENT_CONNECTED; + if (client->mqtt_state.connection.information.protocol_ver != MQTT_PROTOCOL_V_5) { + client->event.session_present = mqtt_get_connect_session_present(client->mqtt_state.in_buffer); + } + client->state = MQTT_STATE_CONNECTED; + esp_mqtt_dispatch_event_with_msgid(client); + client->refresh_connection_tick = platform_tick_get_ms(); + client->keepalive_tick = platform_tick_get_ms(); + + break; + case MQTT_STATE_CONNECTED: + // check for disconnection request + if (xEventGroupWaitBits(client->status_bits, DISCONNECT_BIT, true, true, 0) & DISCONNECT_BIT) { + send_disconnect_msg(client); // ignore error, if clean disconnect fails, just abort the connection + esp_mqtt_abort_connection(client); + break; + } + // receive and process data + if (mqtt_process_receive(client) == ESP_FAIL) { + esp_mqtt_abort_connection(client); + break; + } + + if (last_retransmit == 0) { + // connected for first time, set last_retransmit to now, avoid retransmit + last_retransmit = platform_tick_get_ms(); + } + + + // resend all non-transmitted messages first + outbox_item_handle_t item = outbox_dequeue(client->outbox, QUEUED, NULL); + if (item) { + if (mqtt_resend_queued(client, item) == ESP_OK) { + if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_PUBLISH && client->mqtt_state.pending_publish_qos == 0) { + // delete all qos0 publish messages once we process them + if (outbox_delete_item(client->outbox, item) != ESP_OK) { + ESP_LOGE(TAG, "Failed to remove queued qos0 message from the outbox"); + } + } + if (client->mqtt_state.pending_publish_qos > 0) { + outbox_set_pending(client->outbox, client->mqtt_state.pending_msg_id, TRANSMITTED); +#ifdef MQTT_PROTOCOL_5 + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + esp_mqtt5_increment_packet_counter(client); + } +#endif + } + } + // resend other "transmitted" messages after 1s + } else if (has_timed_out(last_retransmit, client->config->message_retransmit_timeout)) { + last_retransmit = platform_tick_get_ms(); + item = outbox_dequeue(client->outbox, TRANSMITTED, &msg_tick); + if (item && (last_retransmit - msg_tick > client->config->message_retransmit_timeout)) { + if (mqtt_resend_queued(client, item) == ESP_OK) { +#ifdef MQTT_PROTOCOL_5 + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + esp_mqtt5_increment_packet_counter(client); + } +#endif + } + } + item = outbox_dequeue(client->outbox, ACKNOWLEDGED, &msg_tick); + if (item && (last_retransmit - msg_tick > client->config->message_retransmit_timeout)) { + if (mqtt_resend_pubrel(client, item) == ESP_OK) { +#ifdef MQTT_PROTOCOL_5 + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + esp_mqtt5_increment_packet_counter(client); + } +#endif + } + } + } + + if (process_keepalive(client) != ESP_OK) { + break; + } + + if (client->config->refresh_connection_after_ms && + has_timed_out(client->refresh_connection_tick, client->config->refresh_connection_after_ms)) { + ESP_LOGD(TAG, "Refreshing the connection..."); + esp_mqtt_abort_connection(client); + client->state = MQTT_STATE_INIT; + } + + break; + case MQTT_STATE_WAIT_RECONNECT: + + if (!client->config->auto_reconnect && xEventGroupGetBits(client->status_bits)&RECONNECT_BIT) { + xEventGroupClearBits(client->status_bits, RECONNECT_BIT); + client->state = MQTT_STATE_INIT; + client->wait_timeout_ms = MQTT_RECON_DEFAULT_MS; + ESP_LOGD(TAG, "Reconnecting per user request..."); + break; + } else if (client->config->auto_reconnect && + platform_tick_get_ms() - client->reconnect_tick > client->wait_timeout_ms) { + client->state = MQTT_STATE_INIT; + client->reconnect_tick = platform_tick_get_ms(); + ESP_LOGD(TAG, "Reconnecting..."); + break; + } + MQTT_API_UNLOCK(client); + xEventGroupWaitBits(client->status_bits, RECONNECT_BIT, false, true, + max_poll_timeout(client, client->wait_timeout_ms / 2 / portTICK_PERIOD_MS)); + // continue the while loop instead of break, as the mutex is unlocked + continue; + default: + ESP_LOGE(TAG, "MQTT client error, client is in an unrecoverable state."); + break; + } + MQTT_API_UNLOCK(client); + if (MQTT_STATE_CONNECTED == client->state) { + if (esp_transport_poll_read(client->transport, max_poll_timeout(client, MQTT_POLL_READ_TIMEOUT_MS)) < 0) { + ESP_LOGE(TAG, "Poll read error: %d, aborting connection", errno); + esp_mqtt_abort_connection(client); + } + } + + } + esp_transport_close(client->transport); + outbox_delete_all_items(client->outbox); + client->state = MQTT_STATE_DISCONNECTED; + xEventGroupSetBits(client->status_bits, STOPPED_BIT); + + vTaskDelete(NULL); +} + +esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + if (client->state != MQTT_STATE_INIT && client->state != MQTT_STATE_DISCONNECTED) { + ESP_LOGE(TAG, "Client has started"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + esp_err_t err = ESP_OK; +#if MQTT_CORE_SELECTION_ENABLED + ESP_LOGD(TAG, "Core selection enabled on %u", MQTT_TASK_CORE); + if (xTaskCreatePinnedToCore(esp_mqtt_task, "mqtt_task", client->config->task_stack, client, client->config->task_prio, &client->task_handle, MQTT_TASK_CORE) != pdTRUE) { + ESP_LOGE(TAG, "Error create mqtt task"); + err = ESP_FAIL; + } +#else + ESP_LOGD(TAG, "Core selection disabled"); + if (xTaskCreate(esp_mqtt_task, "mqtt_task", client->config->task_stack, client, client->config->task_prio, &client->task_handle) != pdTRUE) { + ESP_LOGE(TAG, "Error create mqtt task"); + err = ESP_FAIL; + } +#endif + MQTT_API_UNLOCK(client); + return err; +} + +esp_err_t esp_mqtt_client_disconnect(esp_mqtt_client_handle_t client) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + ESP_LOGI(TAG, "Client asked to disconnect"); + xEventGroupSetBits(client->status_bits, DISCONNECT_BIT); + return ESP_OK; +} + +esp_err_t esp_mqtt_client_reconnect(esp_mqtt_client_handle_t client) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + ESP_LOGI(TAG, "Client force reconnect requested"); + if (client->state != MQTT_STATE_WAIT_RECONNECT) { + ESP_LOGD(TAG, "The client is not waiting for reconnection. Ignore the request"); + return ESP_FAIL; + } + client->wait_timeout_ms = 0; + xEventGroupSetBits(client->status_bits, RECONNECT_BIT); + return ESP_OK; +} + +static esp_err_t send_disconnect_msg(esp_mqtt_client_handle_t client) +{ + // Notify the broker we are disconnecting + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + mqtt5_msg_disconnect(&client->mqtt_state.connection, &client->mqtt5_config->disconnect_property_info); + if (client->mqtt_state.connection.outbound_message.length) { + esp_mqtt5_client_delete_user_property(client->mqtt5_config->disconnect_property_info.user_property); + client->mqtt5_config->disconnect_property_info.user_property = NULL; + memset(&client->mqtt5_config->disconnect_property_info, 0, sizeof(esp_mqtt5_disconnect_property_config_t)); + } +#endif + } else { + mqtt_msg_disconnect(&client->mqtt_state.connection); + } + if (client->mqtt_state.connection.outbound_message.length == 0) { + ESP_LOGE(TAG, "Disconnect message cannot be created"); + return ESP_FAIL; + } + if (esp_mqtt_write(client) != ESP_OK) { + ESP_LOGE(TAG, "Error sending disconnect message"); + } + return ESP_OK; +} + +esp_err_t esp_mqtt_client_stop(esp_mqtt_client_handle_t client) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return ESP_ERR_INVALID_ARG; + } + MQTT_API_LOCK(client); + if (client->run) { + /* A running client cannot be stopped from the MQTT task/event handler */ + TaskHandle_t running_task = xTaskGetCurrentTaskHandle(); + if (running_task == client->task_handle) { + MQTT_API_UNLOCK(client); + ESP_LOGE(TAG, "Client cannot be stopped from MQTT task"); + return ESP_FAIL; + } + + // Only send the disconnect message if the client is connected + if (client->state == MQTT_STATE_CONNECTED) { + if (send_disconnect_msg(client) != ESP_OK) { + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } + } + + client->run = false; + client->state = MQTT_STATE_DISCONNECTED; + MQTT_API_UNLOCK(client); + xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY); + return ESP_OK; + } else { + ESP_LOGW(TAG, "Client asked to stop, but was not started"); + MQTT_API_UNLOCK(client); + return ESP_FAIL; + } +} + +static esp_err_t esp_mqtt_client_ping(esp_mqtt_client_handle_t client) +{ + mqtt_msg_pingreq(&client->mqtt_state.connection); + if (client->mqtt_state.connection.outbound_message.length == 0) { + ESP_LOGE(TAG, "Ping message cannot be created"); + return ESP_FAIL; + } + + if (esp_mqtt_write(client) != ESP_OK) { + ESP_LOGE(TAG, "Error sending ping"); + return ESP_FAIL; + } + ESP_LOGD(TAG, "Sent PING successful"); + return ESP_OK; +} + +int esp_mqtt_client_subscribe_multiple(esp_mqtt_client_handle_t client, + const esp_mqtt_topic_t *topic_list, int size) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return -1; + } + + if (client->config->outbox_limit > 0 && outbox_get_size(client->outbox) > client->config->outbox_limit) { + return -2; + } + + if (client->state != MQTT_STATE_CONNECTED) { + ESP_LOGE(TAG, "Client has not connected"); + return -1; + } + + MQTT_API_LOCK(client); + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + int max_qos = topic_list[0].qos; + for (int topic_number = 0; topic_number < size; ++topic_number) { + if (topic_list[topic_number].qos > max_qos) { + max_qos = topic_list[topic_number].qos; + } + } + if (esp_mqtt5_client_subscribe_check(client, max_qos) != ESP_OK) { + ESP_LOGI(TAG, "MQTT5 subscribe check fail: QoS %d not accepted by broker ", max_qos); + MQTT_API_UNLOCK(client); + return -1; + } + mqtt5_msg_subscribe(&client->mqtt_state.connection, + topic_list, size, + &client->mqtt_state.pending_msg_id, client->mqtt5_config->subscribe_property_info); + if (client->mqtt_state.connection.outbound_message.length) { + client->mqtt5_config->subscribe_property_info = NULL; + } +#endif + } else { + mqtt_msg_subscribe(&client->mqtt_state.connection, + topic_list, size, + &client->mqtt_state.pending_msg_id); + } + if (client->mqtt_state.connection.outbound_message.length == 0) { + ESP_LOGE(TAG, "Subscribe message cannot be created"); + MQTT_API_UNLOCK(client); + return -1; + } + + client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.connection.outbound_message.data); + //move pending msg to outbox (if have) + if (!mqtt_enqueue(client, NULL, 0)) { + MQTT_API_UNLOCK(client); + return -1; + } + outbox_set_pending(client->outbox, client->mqtt_state.pending_msg_id, TRANSMITTED);// handle error + + if (esp_mqtt_write(client) != ESP_OK) { + ESP_LOGE(TAG, "Error to send subscribe message, first topic: %s, qos: %d", topic_list[0].filter, topic_list[0].qos); + MQTT_API_UNLOCK(client); + return -1; + } + + ESP_LOGD(TAG, "Sent subscribe, first topic=%s, id: %d", topic_list[0].filter, client->mqtt_state.pending_msg_id); + MQTT_API_UNLOCK(client); + return client->mqtt_state.pending_msg_id; + +} +int esp_mqtt_client_subscribe_single(esp_mqtt_client_handle_t client, const char *topic, int qos) +{ + esp_mqtt_topic_t user_topic = {.filter = topic, .qos = qos}; + return esp_mqtt_client_subscribe_multiple(client, &user_topic, 1); +} + +int esp_mqtt_client_unsubscribe(esp_mqtt_client_handle_t client, const char *topic) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return -1; + } + if (client->state != MQTT_STATE_CONNECTED) { + ESP_LOGE(TAG, "Client has not connected"); + return -1; + } + MQTT_API_LOCK(client); + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + mqtt5_msg_unsubscribe(&client->mqtt_state.connection, + topic, + &client->mqtt_state.pending_msg_id, client->mqtt5_config->unsubscribe_property_info); + if (client->mqtt_state.connection.outbound_message.length) { + client->mqtt5_config->unsubscribe_property_info = NULL; + } +#endif + } else { + mqtt_msg_unsubscribe(&client->mqtt_state.connection, + topic, + &client->mqtt_state.pending_msg_id); + } + if (client->mqtt_state.connection.outbound_message.length == 0) { + MQTT_API_UNLOCK(client); + ESP_LOGE(TAG, "Unubscribe message cannot be created"); + return -1; + } + ESP_LOGD(TAG, "unsubscribe, topic\"%s\", id: %d", topic, client->mqtt_state.pending_msg_id); + + client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.connection.outbound_message.data); + if (!mqtt_enqueue(client, NULL, 0)) { + MQTT_API_UNLOCK(client); + return -1; + } + outbox_set_pending(client->outbox, client->mqtt_state.pending_msg_id, TRANSMITTED); //handle error + + if (esp_mqtt_write(client) != ESP_OK) { + ESP_LOGE(TAG, "Error to unsubscribe topic=%s", topic); + MQTT_API_UNLOCK(client); + return -1; + } + + ESP_LOGD(TAG, "Sent Unsubscribe topic=%s, id: %d, successful", topic, client->mqtt_state.pending_msg_id); + MQTT_API_UNLOCK(client); + return client->mqtt_state.pending_msg_id; +} + +static int make_publish(esp_mqtt_client_handle_t client, const char *topic, const char *data, + int len, int qos, int retain) +{ + uint16_t pending_msg_id = 0; + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { +#ifdef MQTT_PROTOCOL_5 + mqtt5_msg_publish(&client->mqtt_state.connection, + topic, data, len, + qos, retain, + &pending_msg_id, client->mqtt5_config->publish_property_info, client->mqtt5_config->server_resp_property_info.response_info); + if (client->mqtt_state.connection.outbound_message.length) { + client->mqtt5_config->publish_property_info = NULL; + } +#endif + } else { + mqtt_msg_publish(&client->mqtt_state.connection, + topic, data, len, + qos, retain, + &pending_msg_id); + } + + if (client->mqtt_state.connection.outbound_message.length == 0) { + ESP_LOGE(TAG, "Publish message cannot be created"); + return -1; + } + return pending_msg_id; +} +static inline int mqtt_client_enqueue_publish(esp_mqtt_client_handle_t client, const char *topic, const char *data, + int len, int qos, int retain, bool store) +{ + int pending_msg_id = make_publish(client, topic, data, len, qos, retain); + if (pending_msg_id < 0) { + return -1; + } + /* We have to set as pending all the qos>0 messages */ + //TODO: client->mqtt_state.outbound_message = publish_msg; + if (qos > 0 || store) { + client->mqtt_state.pending_msg_type = mqtt_get_type(client->mqtt_state.connection.outbound_message.data); + client->mqtt_state.pending_msg_id = pending_msg_id; + client->mqtt_state.pending_publish_qos = qos; + // by default store as QUEUED (not transmitted yet) only for messages which would fit outbound buffer + if (client->mqtt_state.connection.outbound_message.fragmented_msg_total_length == 0) { + if (!mqtt_enqueue(client, NULL, 0)) { + return -1; + } + } else { + int first_fragment = client->mqtt_state.connection.outbound_message.length - client->mqtt_state.connection.outbound_message.fragmented_msg_data_offset; + if (!mqtt_enqueue(client, ((uint8_t *)data) + first_fragment, len - first_fragment)) { + return -1; + } + client->mqtt_state.connection.outbound_message.fragmented_msg_total_length = 0; + } + } + return pending_msg_id; +} + +int esp_mqtt_client_publish(esp_mqtt_client_handle_t client, const char *topic, const char *data, int len, int qos, int retain) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return -1; + } +#if MQTT_SKIP_PUBLISH_IF_DISCONNECTED + if (client->state != MQTT_STATE_CONNECTED) { + ESP_LOGI(TAG, "Publishing skipped: client is not connected"); + return -1; + } +#endif + +MQTT_API_LOCK(client); +#ifdef MQTT_PROTOCOL_5 + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + if (esp_mqtt5_client_publish_check(client, qos, retain) != ESP_OK) { + ESP_LOGI(TAG, "MQTT5 publish check fail"); + MQTT_API_UNLOCK(client); + return -1; + } + } +#endif + + /* Acceptable publish messages: + data == NULL, len == 0: publish null message + data valid, len == 0: publish all data, payload len is determined from string length + data valid, len > 0: publish data with defined length + */ + if (len <= 0 && data != NULL) { + len = strlen(data); + } + + if (client->config->outbox_limit > 0 && qos > 0) { + if (len + outbox_get_size(client->outbox) > client->config->outbox_limit) { + MQTT_API_UNLOCK(client); + return -2; + } + } + + int pending_msg_id = mqtt_client_enqueue_publish(client, topic, data, len, qos, retain, false); + if (pending_msg_id < 0) { + MQTT_API_UNLOCK(client); + return -1; + } + int ret = 0; + + /* Skip sending if not connected (rely on resending) */ + if (client->state != MQTT_STATE_CONNECTED) { + ESP_LOGD(TAG, "Publish: client is not connected"); + if (qos > 0) { + ret = pending_msg_id; + } else { + ret = -1; + } + + goto cannot_publish; + } + + /* Provide support for sending fragmented message if it doesn't fit buffer */ + int remaining_len = len; + const char *current_data = data; + bool sending = true; + + while (sending) { + + if (esp_mqtt_write(client) != ESP_OK) { + esp_mqtt_abort_connection(client); + ret = -1; + goto cannot_publish; + } + + int data_sent = client->mqtt_state.connection.outbound_message.length - client->mqtt_state.connection.outbound_message.fragmented_msg_data_offset; + client->mqtt_state.connection.outbound_message.fragmented_msg_data_offset = 0; + client->mqtt_state.connection.outbound_message.fragmented_msg_total_length = 0; + remaining_len -= data_sent; + current_data += data_sent; + + if (remaining_len > 0) { + mqtt_connection_t *connection = &client->mqtt_state.connection; + ESP_LOGD(TAG, "Sending fragmented message, remains to send %d bytes of %d", remaining_len, len); + int write_len = remaining_len > connection->buffer_length ? connection->buffer_length : remaining_len; + memcpy(connection->buffer, current_data, write_len); + connection->outbound_message.data = connection->buffer; + connection->outbound_message.length = write_len; + sending = true; + } else { + // Message was sent correctly + sending = false; + } + } + + if (qos > 0) { +#ifdef MQTT_PROTOCOL_5 + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + esp_mqtt5_increment_packet_counter(client); + } +#endif + //Tick is set after transmit to avoid retransmitting too early due slow network speed / big messages + outbox_set_tick(client->outbox, pending_msg_id, platform_tick_get_ms()); + outbox_set_pending(client->outbox, pending_msg_id, TRANSMITTED); + } + MQTT_API_UNLOCK(client); + return pending_msg_id; + +cannot_publish: + // clear out possible fragmented publish if failed or skipped + client->mqtt_state.connection.outbound_message.fragmented_msg_total_length = 0; + if (qos == 0) { + ESP_LOGW(TAG, "Publish: Losing qos0 data when client not connected"); + } + MQTT_API_UNLOCK(client); + + return ret; +} + +int esp_mqtt_client_enqueue(esp_mqtt_client_handle_t client, const char *topic, const char *data, int len, int qos, int retain, bool store) +{ + if (!client) { + ESP_LOGE(TAG, "Client was not initialized"); + return -1; + } + + /* Acceptable publish messages: + data == NULL, len == 0: publish null message + data valid, len == 0: publish all data, payload len is determined from string length + data valid, len > 0: publish data with defined length + */ + if (len <= 0 && data != NULL) { + len = strlen(data); + } + + if (client->config->outbox_limit > 0) { + if (len + outbox_get_size(client->outbox) > client->config->outbox_limit) { + return -2; + } + + } + + MQTT_API_LOCK(client); +#ifdef MQTT_PROTOCOL_5 + if (client->mqtt_state.connection.information.protocol_ver == MQTT_PROTOCOL_V_5) { + if (esp_mqtt5_client_publish_check(client, qos, retain) != ESP_OK) { + ESP_LOGI(TAG, "esp_mqtt_client_enqueue check fail"); + MQTT_API_UNLOCK(client); + return -1; + } + } +#endif + int ret = mqtt_client_enqueue_publish(client, topic, data, len, qos, retain, store); + MQTT_API_UNLOCK(client); + if (ret == 0 && store == false) { + // messages with qos=0 are not enqueued if not overridden by store_in_outobx -> indicate as error + return -1; + } + return ret; +} + +esp_err_t esp_mqtt_client_register_event(esp_mqtt_client_handle_t client, esp_mqtt_event_id_t event, esp_event_handler_t event_handler, void *event_handler_arg) +{ + if (client == NULL) { + return ESP_ERR_INVALID_ARG; + } +#ifdef MQTT_SUPPORTED_FEATURE_EVENT_LOOP + + return esp_event_handler_register_with(client->config->event_loop_handle, MQTT_EVENTS, event, event_handler, event_handler_arg); +#else + ESP_LOGE(TAG, "Registering event handler while event loop not available in IDF version %s", IDF_VER); + return ESP_FAIL; +#endif +} + +esp_err_t esp_mqtt_client_unregister_event(esp_mqtt_client_handle_t client, esp_mqtt_event_id_t event, esp_event_handler_t event_handler) +{ + if (client == NULL) { + return ESP_ERR_INVALID_ARG; + } +#ifdef MQTT_SUPPORTED_FEATURE_EVENT_LOOP + + return esp_event_handler_unregister_with(client->config->event_loop_handle, MQTT_EVENTS, event, event_handler); +#else + ESP_LOGE(TAG, "Unregistering event handler while event loop not available in IDF version %s", IDF_VER); + return ESP_FAIL; +#endif +} + +static void esp_mqtt_client_dispatch_transport_error(esp_mqtt_client_handle_t client) +{ + client->event.event_id = MQTT_EVENT_ERROR; + client->event.error_handle->error_type = MQTT_ERROR_TYPE_TCP_TRANSPORT; + client->event.error_handle->connect_return_code = 0; +#ifdef MQTT_PROTOCOL_5 + client->event.error_handle->disconnect_return_code = 0; +#endif +#ifdef MQTT_SUPPORTED_FEATURE_TRANSPORT_ERR_REPORTING + client->event.error_handle->esp_tls_last_esp_err = esp_tls_get_and_clear_last_error(esp_transport_get_error_handle(client->transport), + &client->event.error_handle->esp_tls_stack_err, + &client->event.error_handle->esp_tls_cert_verify_flags); +#ifdef MQTT_SUPPORTED_FEATURE_TRANSPORT_SOCK_ERRNO_REPORTING + client->event.error_handle->esp_transport_sock_errno = esp_transport_get_errno(client->transport); +#endif +#endif + esp_mqtt_dispatch_event_with_msgid(client); +} + +int esp_mqtt_client_get_outbox_size(esp_mqtt_client_handle_t client) +{ + int outbox_size = 0; + + if (client == NULL) { + return 0; + } + + MQTT_API_LOCK(client); + + if (client->outbox) { + outbox_size = outbox_get_size(client->outbox); + } + + MQTT_API_UNLOCK(client); + + return outbox_size; +} + +esp_transport_handle_t esp_mqtt_client_get_transport(esp_mqtt_client_handle_t client, char *transport_scheme){ + if (client == NULL || (transport_scheme == NULL && client->config->transport == NULL)) { + return NULL; + } + if (transport_scheme == NULL && client->config->transport != NULL) { + return client->config->transport; + } + return esp_transport_list_get_transport(client->transport_list, transport_scheme); +} diff --git a/managed_components/esp_mqtt/static-analysis-rules.yml b/managed_components/esp_mqtt/static-analysis-rules.yml new file mode 100644 index 0000000..bd36de8 --- /dev/null +++ b/managed_components/esp_mqtt/static-analysis-rules.yml @@ -0,0 +1,9 @@ +limits: + "clang-analyzer-core.NullDereference" : 0 + "clang-analyzer-unix.Malloc" : 0 + +ignore: + - "llvm-header-guard" + - "llvm-include-order" + +skip: diff --git a/managed_components/espressif__usb_host_hid/.component_hash b/managed_components/espressif__usb_host_hid/.component_hash new file mode 100644 index 0000000..8316edc --- /dev/null +++ b/managed_components/espressif__usb_host_hid/.component_hash @@ -0,0 +1 @@ +eabf03fe3cb1bdb5325d2725f420c6324c3e150033d893ce3737222929455a55 \ No newline at end of file diff --git a/managed_components/espressif__usb_host_hid/CHANGELOG.md b/managed_components/espressif__usb_host_hid/CHANGELOG.md new file mode 100644 index 0000000..95e23c0 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/CHANGELOG.md @@ -0,0 +1,21 @@ +## 1.0.3 +- Fixed a bug with interface mismatch on EP IN transfer complete while several HID devices are present. +- Fixed a bug during device freeing, while detaching one of several attached HID devices. + +## 1.0.2 + +- Added support for ESP32-P4 +- Fixed device open procedure for HID devices with multiple non-sequential interfaces. + +## 1.0.1 + +- Fixed a bug where configuring the driver with `create_background_task = false` did not properly initialize the driver. This lead to `the hid_host_uninstall()` hang-up. +- Fixed a bug where `hid_host_uninstall()` would cause a crash during the call while USB device has not been removed. +- Added `hid_host_get_device_info()` to get the basic information of a connected USB HID device. + +## 1.0.0 + +- Initial version + + + diff --git a/managed_components/espressif__usb_host_hid/CMakeLists.txt b/managed_components/espressif__usb_host_hid/CMakeLists.txt new file mode 100644 index 0000000..3158946 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register( SRCS "hid_host.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES usb ) diff --git a/managed_components/espressif__usb_host_hid/LICENSE b/managed_components/espressif__usb_host_hid/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/managed_components/espressif__usb_host_hid/README.md b/managed_components/espressif__usb_host_hid/README.md new file mode 100644 index 0000000..7372eb4 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/README.md @@ -0,0 +1,52 @@ +# USB Host HID (Human Interface Device) Driver + +[![Component Registry](https://components.espressif.com/components/espressif/usb_host_hid/badge.svg)](https://components.espressif.com/components/espressif/usb_host_hid) + +This directory contains an implementation of a USB HID Driver implemented on top of the [USB Host Library](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html). + +HID driver allows access to HID devices. + +## Usage + +The following steps outline the typical API call pattern of the HID Class Driver: + +1. Install the USB Host Library via 'usb_host_install()' +2. Install the HID driver via 'hid_host_install()' +3. The HID Host driver device callback provide the following events (via two callbacks): + - HID_HOST_DRIVER_EVENT_CONNECTED + - HID_HOST_INTERFACE_EVENT_INPUT_REPORT + - HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR + - HID_HOST_INTERFACE_EVENT_DISCONNECTED + +4. Specific HID device can be opened or closed with: + - 'hid_host_device_open()' + - 'hid_host_device_close()' +5. To enable / disable data receiving in case of event (keyboard key was pressed or mouse device was moved e.t.c) use: + - 'hid_host_device_start()' + - 'hid_host_device_stop()' +6. HID Class specific device requests: + - 'hid_host_interface_get_report_descriptor()' + - 'hid_class_request_get_report()' + - 'hid_class_request_get_idle()' + - 'hid_class_request_get_protocol()' + - 'hid_class_request_set_report()' + - 'hid_class_request_set_idle()' + - 'hid_class_request_set_protocol()' +7. When HID device event occurs the driver call an interface callback with events: + - HID_HOST_INTERFACE_EVENT_INPUT_REPORT + - HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR + - HID_HOST_INTERFACE_EVENT_DISCONNECTED +8. The HID driver can be uninstalled via 'hid_host_uninstall()' + +## Known issues + +- Empty + +## Examples + +- For an example, refer to [hid_host_example](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/host/hid) + +## Supported Devices + +- HID Driver support any HID compatible device with a USB bIterfaceClass 0x03 (Human Interface Device). +- There are two options to handle HID device input data: either in RAW format or via special event handlers (which are available only for HID Devices which support Boot Protocol). diff --git a/managed_components/espressif__usb_host_hid/hid_host.c b/managed_components/espressif__usb_host_hid/hid_host.c new file mode 100644 index 0000000..f1c6210 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/hid_host.c @@ -0,0 +1,1527 @@ +/* + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include "esp_log.h" +#include "esp_check.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "usb/usb_host.h" + +#include "usb/hid_host.h" + +// HID spinlock +static portMUX_TYPE hid_lock = portMUX_INITIALIZER_UNLOCKED; +#define HID_ENTER_CRITICAL() portENTER_CRITICAL(&hid_lock) +#define HID_EXIT_CRITICAL() portEXIT_CRITICAL(&hid_lock) + +// HID verification macros +#define HID_GOTO_ON_FALSE_CRITICAL(exp, err) \ + do { \ + if(!(exp)) { \ + HID_EXIT_CRITICAL(); \ + ret = err; \ + goto fail; \ + } \ + } while(0) + +#define HID_RETURN_ON_FALSE_CRITICAL(exp, err) \ + do { \ + if(!(exp)) { \ + HID_EXIT_CRITICAL(); \ + return err; \ + } \ + } while(0) + +#define HID_GOTO_ON_ERROR(exp, msg) ESP_GOTO_ON_ERROR(exp, fail, TAG, msg) + +#define HID_GOTO_ON_FALSE(exp, err, msg) ESP_GOTO_ON_FALSE( (exp), err, fail, TAG, msg ) + +#define HID_RETURN_ON_ERROR(exp, msg) ESP_RETURN_ON_ERROR((exp), TAG, msg) + +#define HID_RETURN_ON_FALSE(exp, err, msg) ESP_RETURN_ON_FALSE( (exp), (err), TAG, msg) + +#define HID_RETURN_ON_INVALID_ARG(exp) ESP_RETURN_ON_FALSE((exp) != NULL, ESP_ERR_INVALID_ARG, TAG, "Argument error") + +// USB Descriptor parsing helping macros +#define GET_NEXT_INTERFACE_DESC(p, max_len, offs) \ + ((const usb_intf_desc_t *)usb_parse_next_descriptor_of_type((const usb_standard_desc_t *)p, \ + max_len, \ + USB_B_DESCRIPTOR_TYPE_INTERFACE, \ + &(offs))) + +#define GET_NEXT_HID_DESC(p, max_len, offs) \ + ((const hid_descriptor_t *)usb_parse_next_descriptor_of_type((const usb_standard_desc_t *)p, \ + max_len, \ + HID_CLASS_DESCRIPTOR_TYPE_HID, \ + &(offs))) + +static const char *TAG = "hid-host"; + +#define DEFAULT_TIMEOUT_MS (5000) + +/** + * @brief HID Device structure. + * + */ +typedef struct hid_host_device { + STAILQ_ENTRY(hid_host_device) tailq_entry; /**< HID device queue */ + SemaphoreHandle_t device_busy; /**< HID device main mutex */ + SemaphoreHandle_t ctrl_xfer_done; /**< Control transfer semaphore */ + usb_transfer_t *ctrl_xfer; /**< Pointer to control transfer buffer */ + usb_device_handle_t dev_hdl; /**< USB device handle */ + uint8_t dev_addr; /**< USB device address */ +} hid_device_t; + +/** + * @brief HID Interface state +*/ +typedef enum { + HID_INTERFACE_STATE_NOT_INITIALIZED = 0x00, /**< HID Interface not initialized */ + HID_INTERFACE_STATE_IDLE, /**< HID Interface has been found in connected USB device */ + HID_INTERFACE_STATE_READY, /**< HID Interface opened and ready to start transfer */ + HID_INTERFACE_STATE_ACTIVE, /**< HID Interface is in use */ + HID_INTERFACE_STATE_WAIT_USER_DELETION, /**< HID Interface wait user to be removed */ + HID_INTERFACE_STATE_MAX +} hid_iface_state_t; + +/** + * @brief HID Interface structure in device to interact with. After HID device opening keeps the interface configuration + * + */ +typedef struct hid_interface { + STAILQ_ENTRY(hid_interface) tailq_entry; + hid_device_t *parent; /**< Parent USB HID device */ + hid_host_dev_params_t dev_params; /**< USB device parameters */ + uint8_t ep_in; /**< Interrupt IN EP number */ + uint16_t ep_in_mps; /**< Interrupt IN max size */ + uint8_t country_code; /**< Country code */ + uint16_t report_desc_size; /**< Size of Report */ + uint8_t *report_desc; /**< Pointer to HID Report */ + usb_transfer_t *in_xfer; /**< Pointer to IN transfer buffer */ + hid_host_interface_event_cb_t user_cb; /**< Interface application callback */ + void *user_cb_arg; /**< Interface application callback arg */ + hid_iface_state_t state; /**< Interface state */ +} hid_iface_t; + +/** + * @brief HID driver default context + * + * This context is created during HID Host install. + */ +typedef struct { + STAILQ_HEAD(devices, hid_host_device) hid_devices_tailq; /**< STAILQ of HID interfaces */ + STAILQ_HEAD(interfaces, hid_interface) hid_ifaces_tailq; /**< STAILQ of HID interfaces */ + usb_host_client_handle_t client_handle; /**< Client task handle */ + hid_host_driver_event_cb_t user_cb; /**< User application callback */ + void *user_arg; /**< User application callback args */ + bool event_handling_started; /**< Events handler started flag */ + SemaphoreHandle_t all_events_handled; /**< Events handler semaphore */ + volatile bool end_client_event_handling; /**< Client event handling flag */ +} hid_driver_t; + +static hid_driver_t *s_hid_driver; /**< Internal pointer to HID driver */ + + +// ----------------------- Private Prototypes ---------------------------------- + +static esp_err_t hid_host_install_device(uint8_t dev_addr, + usb_device_handle_t dev_hdl, + hid_device_t **hid_device); + + +static esp_err_t hid_host_uninstall_device(hid_device_t *hid_device); + +// --------------------------- Internal Logic ---------------------------------- +/** + * @brief HID class specific request +*/ +typedef struct hid_class_request { + uint8_t bRequest; /**< bRequest */ + uint16_t wValue; /**< wValue: Report Type and Report ID */ + uint16_t wIndex; /**< wIndex: Interface */ + uint16_t wLength; /**< wLength: Report Length */ + uint8_t *data; /**< Pointer to data */ +} hid_class_request_t; + + +// ----------------- USB Event Handler - Internal Task ------------------------- + +/** + * @brief USB Event handler + * + * Handle all USB related events such as USB host (usbh) events or hub events from USB hardware + * + * @param[in] arg Argument, does not used + */ +static void event_handler_task(void *arg) +{ + ESP_LOGD(TAG, "USB HID handling start"); + while (hid_host_handle_events(portMAX_DELAY) == ESP_OK) { + } + ESP_LOGD(TAG, "USB HID handling stop"); + vTaskDelete(NULL); +} + +/** + * @brief Return HID device in devices list by USB device handle + * + * @param[in] usb_device_handle_t USB device handle + * @return hid_device_t Pointer to device, NULL if device not present + */ +static hid_device_t *get_hid_device_by_handle(usb_device_handle_t usb_handle) +{ + hid_device_t *device = NULL; + + HID_ENTER_CRITICAL(); + STAILQ_FOREACH(device, &s_hid_driver->hid_devices_tailq, tailq_entry) { + if (usb_handle == device->dev_hdl) { + HID_EXIT_CRITICAL(); + return device; + } + } + HID_EXIT_CRITICAL(); + return NULL; +} + +/** + * @brief Return HID Device from the transfer context + * + * @param[in] xfer USB transfer struct + * @return hid_device_t Pointer to HID Device + */ +static inline hid_device_t *get_hid_device_from_context(usb_transfer_t *xfer) +{ + return (hid_device_t *)xfer->context; +} + +/** + * @brief Verify presence of Interface in the RAM list + * + * @param[in] iface Pointer to an Interface structure + * @return true Interface is in the list + * @return false Interface is not in the list + */ +static inline bool is_interface_in_list(hid_iface_t *iface) +{ + hid_iface_t *interface = NULL; + + HID_ENTER_CRITICAL(); + STAILQ_FOREACH(interface, &s_hid_driver->hid_ifaces_tailq, tailq_entry) { + if (iface == interface) { + HID_EXIT_CRITICAL(); + return true; + } + } + + HID_EXIT_CRITICAL(); + return false; +} + +/** + * @brief Get HID Interface pointer by external HID Device handle with verification in RAM list + * + * @param[in] hid_dev_handle HID Device handle + * @return hid_iface_t Pointer to an Interface structure + */ +static hid_iface_t *get_iface_by_handle(hid_host_device_handle_t hid_dev_handle) +{ + hid_iface_t *hid_iface = (hid_iface_t *) hid_dev_handle; + + if (!is_interface_in_list(hid_iface)) { + ESP_LOGE(TAG, "HID interface handle not found"); + return NULL; + } + + return hid_iface; +} + +/** + * @brief Returns pointer to first IN Endpoint descriptor + * + * @param[in] iface_desc Pointer to Interface Descriptor + * @param[in] total_length Total length of configuration descriptor + * @return usb_ep_desc_t Pointer to EP IN Descriptor + */ +static inline const usb_ep_desc_t *get_iface_ep_in(const usb_intf_desc_t *iface_desc, + const size_t total_length) +{ + assert(iface_desc); + const usb_ep_desc_t *ep_desc = NULL; + for (int i = 0; i < iface_desc->bNumEndpoints; i++) { + int ep_offset = 0; + ep_desc = usb_parse_endpoint_descriptor_by_index(iface_desc, i, total_length, &ep_offset); + if (ep_desc) { + if (USB_EP_DESC_GET_EP_DIR(ep_desc)) { + return ep_desc; + } + } + } + return NULL; +} + +/** + * @brief Check HID interface descriptor present + * + * @param[in] config_desc Pointer to Configuration Descriptor + * @return esp_err_t + */ +static bool hid_interface_present(const usb_config_desc_t *config_desc) +{ + assert(config_desc); + int offset = 0; + int total_len = config_desc->wTotalLength; + const usb_intf_desc_t *iface_desc = GET_NEXT_INTERFACE_DESC(config_desc, total_len, offset); + while (iface_desc != NULL) { + if (USB_CLASS_HID == iface_desc->bInterfaceClass) { + return true; + } + iface_desc = GET_NEXT_INTERFACE_DESC(iface_desc, total_len, offset); + } + return false; +} + +/** + * @brief HID Interface user callback function. + * + * @param[in] hid_iface Pointer to an Interface structure + * @param[in] event_id HID Interface event + */ +static inline void hid_host_user_interface_callback(hid_iface_t *hid_iface, + const hid_host_interface_event_t event) +{ + assert(hid_iface); + + hid_host_dev_params_t *dev_params = &hid_iface->dev_params; + + assert(dev_params); + + if (hid_iface->user_cb) { + hid_iface->user_cb(hid_iface, event, hid_iface->user_cb_arg); + } +} + +/** + * @brief HID Device user callback function. + * + * @param[in] event_id HID Device event + * @param[in] dev_params HID Device parameters + */ +static inline void hid_host_user_device_callback(hid_iface_t *hid_iface, + const hid_host_driver_event_t event) +{ + assert(hid_iface); + + hid_host_dev_params_t *dev_params = &hid_iface->dev_params; + + assert(dev_params); + + if (s_hid_driver && s_hid_driver->user_cb) { + s_hid_driver->user_cb(hid_iface, event, s_hid_driver->user_arg); + } +} + +/** + * @brief Add interface in a list + * + * @param[in] hid_device HID device handle + * @param[in] iface_desc Pointer to an Interface descriptor + * @param[in] hid_desc Pointer to an HID device descriptor + * @param[in] ep_desc Pointer to an EP descriptor + * @return esp_err_t + */ +static esp_err_t hid_host_add_interface(hid_device_t *hid_device, + const usb_intf_desc_t *iface_desc, + const hid_descriptor_t *hid_desc, + const usb_ep_desc_t *ep_in_desc) +{ + hid_iface_t *hid_iface = calloc(1, sizeof(hid_iface_t)); + + HID_RETURN_ON_FALSE(hid_iface, + ESP_ERR_NO_MEM, + "Unable to allocate memory"); + + HID_ENTER_CRITICAL(); + hid_iface->parent = hid_device; + hid_iface->state = HID_INTERFACE_STATE_NOT_INITIALIZED; + hid_iface->dev_params.addr = hid_device->dev_addr; + + if (iface_desc) { + hid_iface->dev_params.iface_num = iface_desc->bInterfaceNumber; + hid_iface->dev_params.sub_class = iface_desc->bInterfaceSubClass; + hid_iface->dev_params.proto = iface_desc->bInterfaceProtocol; + } + + if (hid_desc) { + hid_iface->country_code = hid_desc->bCountryCode; + hid_iface->report_desc_size = hid_desc->wReportDescriptorLength; + } + + // EP IN && INT Type + if (ep_in_desc) { + if ( (ep_in_desc->bEndpointAddress & USB_B_ENDPOINT_ADDRESS_EP_DIR_MASK) && + (ep_in_desc->bmAttributes & USB_B_ENDPOINT_ADDRESS_EP_NUM_MASK) ) { + hid_iface->ep_in = ep_in_desc->bEndpointAddress; + hid_iface->ep_in_mps = USB_EP_DESC_GET_MPS(ep_in_desc); + } else { + ESP_EARLY_LOGE(TAG, "HID device EP IN %#X configuration error", + ep_in_desc->bEndpointAddress); + } + } + + if (iface_desc && hid_desc && ep_in_desc) { + hid_iface->state = HID_INTERFACE_STATE_IDLE; + } + + STAILQ_INSERT_TAIL(&s_hid_driver->hid_ifaces_tailq, hid_iface, tailq_entry); + HID_EXIT_CRITICAL(); + + return ESP_OK; +} + +/** + * @brief Remove interface from a list + * + * Use only inside critical section + * + * @param[in] hid_iface HID interface handle + * @return esp_err_t + */ +static esp_err_t _hid_host_remove_interface(hid_iface_t *hid_iface) +{ + hid_iface->state = HID_INTERFACE_STATE_NOT_INITIALIZED; + STAILQ_REMOVE(&s_hid_driver->hid_ifaces_tailq, hid_iface, hid_interface, tailq_entry); + free(hid_iface); + return ESP_OK; +} + +/** + * @brief Notify user about the connected Interfaces + * + * @param[in] hid_device Pointer to HID device structure + */ +static void hid_host_notify_interface_connected(hid_device_t *hid_device) +{ + HID_ENTER_CRITICAL(); + hid_iface_t *iface = STAILQ_FIRST(&s_hid_driver->hid_ifaces_tailq); + hid_iface_t *tmp = NULL; + + while (iface != NULL) { + tmp = STAILQ_NEXT(iface, tailq_entry); + HID_EXIT_CRITICAL(); + + if (iface->parent && (iface->parent->dev_addr == hid_device->dev_addr)) { + hid_host_user_device_callback(iface, HID_HOST_DRIVER_EVENT_CONNECTED); + } + iface = tmp; + + HID_ENTER_CRITICAL(); + } + HID_EXIT_CRITICAL(); +} + +/** + * @brief Create a list of available interfaces in RAM + * + * @param[in] hid_device Pointer to HID device structure + * @param[in] dev_addr USB device physical address + * @param[in] sub_class USB HID SubClass value + * @return esp_err_t + */ +static esp_err_t hid_host_interface_list_create(hid_device_t *hid_device, + const usb_config_desc_t *config_desc) +{ + assert(hid_device); + assert(config_desc); + size_t total_length = config_desc->wTotalLength; + const usb_intf_desc_t *iface_desc = NULL; + const hid_descriptor_t *hid_desc = NULL; + const usb_ep_desc_t *ep_in_desc = NULL; + int iface_offset = 0; + int hid_desc_offset = 0; + + // Get first Interface descriptor + iface_desc = GET_NEXT_INTERFACE_DESC(config_desc, total_length, iface_offset); + // For every Interface + while (iface_desc != NULL) { + + hid_desc = NULL; + hid_desc_offset = iface_offset; + ep_in_desc = NULL; + + if (USB_CLASS_HID == iface_desc->bInterfaceClass) { + ESP_LOGD(TAG, "Found HID, bInterfaceNumber=%d", iface_desc->bInterfaceNumber); + hid_desc = GET_NEXT_HID_DESC(iface_desc, total_length, hid_desc_offset); + if (hid_desc) { + ep_in_desc = get_iface_ep_in(iface_desc, total_length); + if (ep_in_desc) { + HID_RETURN_ON_ERROR( hid_host_add_interface(hid_device, + iface_desc, + hid_desc, + ep_in_desc), + "Unable to add HID Interface to the RAM list"); + } + } + } // HID Interface + iface_desc = GET_NEXT_INTERFACE_DESC(iface_desc, total_length, iface_offset); + } + + hid_host_notify_interface_connected(hid_device); + + return ESP_OK; +} + +/** + * @brief HID Host initialize device attempt + * + * @param[in] dev_addr USB device physical address + * @return true USB device contain HID Interface and device was initialized + * @return false USB does not contain HID Interface + */ +static bool hid_host_device_init_attempt(uint8_t dev_addr) +{ + bool is_hid_device = false; + usb_device_handle_t dev_hdl; + const usb_config_desc_t *config_desc = NULL; + hid_device_t *hid_device = NULL; + + if (usb_host_device_open(s_hid_driver->client_handle, dev_addr, &dev_hdl) == ESP_OK) { + if (usb_host_get_active_config_descriptor(dev_hdl, &config_desc) == ESP_OK) { + is_hid_device = hid_interface_present(config_desc); + } + } + + // Create HID interfaces list in RAM, connected to the particular USB dev + if (is_hid_device) { + // Proceed, add HID device to the list, get handle if necessary + ESP_ERROR_CHECK( hid_host_install_device(dev_addr, dev_hdl, &hid_device) ); + // Create Interfaces list for a possibility to claim Interface + ESP_ERROR_CHECK( hid_host_interface_list_create(hid_device, config_desc) ); + } else { + usb_host_device_close(s_hid_driver->client_handle, dev_hdl); + ESP_LOGW(TAG, "No HID device at USB port %d", dev_addr); + } + + return is_hid_device; +} + +/** + * @brief Deinit USB device by handle + * + * @param[in] dev_hdl USB device handle + * @return esp_err_t + */ +static esp_err_t hid_host_device_disconnected(usb_device_handle_t dev_hdl) +{ + hid_device_t *hid_device = get_hid_device_by_handle(dev_hdl); + HID_RETURN_ON_INVALID_ARG(hid_device); + + HID_ENTER_CRITICAL(); + hid_iface_t *hid_iface_curr; + hid_iface_t *hid_iface_next; + // Go through list + hid_iface_curr = STAILQ_FIRST(&s_hid_driver->hid_ifaces_tailq); + while (hid_iface_curr != NULL) { + hid_iface_next = STAILQ_NEXT(hid_iface_curr, tailq_entry); + HID_EXIT_CRITICAL(); + + if (hid_iface_curr->parent && (hid_iface_curr->parent->dev_addr == hid_device->dev_addr)) { + HID_RETURN_ON_ERROR( hid_host_device_close(hid_iface_curr), + "Unable to close device"); + } + HID_ENTER_CRITICAL(); + hid_iface_curr = hid_iface_next; + } + HID_EXIT_CRITICAL(); + + // Delete HID compliant device + HID_RETURN_ON_ERROR( hid_host_uninstall_device(hid_device), + "Unable to uninstall device"); + + return ESP_OK; +} + +/** + * @brief USB Host Client's event callback + * + * @param[in] event Client event message + * @param[in] arg Argument, does not used + */ +static void client_event_cb(const usb_host_client_event_msg_t *event, void *arg) +{ + if (event->event == USB_HOST_CLIENT_EVENT_NEW_DEV) { + hid_host_device_init_attempt(event->new_dev.address); + } else if (event->event == USB_HOST_CLIENT_EVENT_DEV_GONE) { + hid_host_device_disconnected(event->dev_gone.dev_hdl); + } +} + +/** + * @brief HID Host claim Interface and prepare transfer, change state to READY + * + * @param[in] iface Pointer to Interface structure, + * @return esp_err_t + */ +static esp_err_t hid_host_interface_claim_and_prepare_transfer(hid_iface_t *iface) +{ + HID_RETURN_ON_ERROR( usb_host_interface_claim( s_hid_driver->client_handle, + iface->parent->dev_hdl, + iface->dev_params.iface_num, 0), + "Unable to claim Interface"); + + HID_RETURN_ON_ERROR( usb_host_transfer_alloc(iface->ep_in_mps, 0, &iface->in_xfer), + "Unable to allocate transfer buffer for EP IN"); + + // Change state + iface->state = HID_INTERFACE_STATE_READY; + return ESP_OK; +} + +/** + * @brief HID Host release Interface and free transfer, change state to IDLE + * + * @param[in] iface Pointer to Interface structure, + * @return esp_err_t + */ +static esp_err_t hid_host_interface_release_and_free_transfer(hid_iface_t *iface) +{ + HID_RETURN_ON_INVALID_ARG(iface); + HID_RETURN_ON_INVALID_ARG(iface->parent); + + HID_RETURN_ON_FALSE(is_interface_in_list(iface), + ESP_ERR_NOT_FOUND, + "Interface handle not found"); + + HID_RETURN_ON_ERROR( usb_host_interface_release(s_hid_driver->client_handle, + iface->parent->dev_hdl, + iface->dev_params.iface_num), + "Unable to release HID Interface"); + + ESP_ERROR_CHECK( usb_host_transfer_free(iface->in_xfer) ); + + // Change state + iface->state = HID_INTERFACE_STATE_IDLE; + return ESP_OK; +} + +/** + * @brief Disable active interface + * + * @param[in] iface Pointer to Interface structure + * @return esp_err_t + */ +static esp_err_t hid_host_disable_interface(hid_iface_t *iface) +{ + HID_RETURN_ON_INVALID_ARG(iface); + HID_RETURN_ON_INVALID_ARG(iface->parent); + + HID_RETURN_ON_FALSE(is_interface_in_list(iface), + ESP_ERR_NOT_FOUND, + "Interface handle not found"); + + HID_RETURN_ON_FALSE((HID_INTERFACE_STATE_ACTIVE == iface->state), + ESP_ERR_INVALID_STATE, + "Interface wrong state"); + + HID_RETURN_ON_ERROR( usb_host_endpoint_halt(iface->parent->dev_hdl, iface->ep_in), + "Unable to HALT EP"); + HID_RETURN_ON_ERROR( usb_host_endpoint_flush(iface->parent->dev_hdl, iface->ep_in), + "Unable to FLUSH EP"); + usb_host_endpoint_clear(iface->parent->dev_hdl, iface->ep_in); + + iface->state = HID_INTERFACE_STATE_READY; + + return ESP_OK; +} + +/** + * @brief HID IN Transfer complete callback + * + * @param[in] transfer Pointer to transfer data structure + */ +static void in_xfer_done(usb_transfer_t *in_xfer) +{ + assert(in_xfer); + assert(in_xfer->context); + + hid_iface_t *iface = (hid_iface_t *) in_xfer->context; + + switch (in_xfer->status) { + case USB_TRANSFER_STATUS_COMPLETED: + // Notify user + hid_host_user_interface_callback(iface, HID_HOST_INTERFACE_EVENT_INPUT_REPORT); + // Relaunch transfer + usb_host_transfer_submit(in_xfer); + return; + case USB_TRANSFER_STATUS_NO_DEVICE: + case USB_TRANSFER_STATUS_CANCELED: + // User is notified about device disconnection from usb_event_cb + // No need to do anything + return; + default: + // Any other error + break; + } + + ESP_LOGE(TAG, "Transfer failed, status %d", in_xfer->status); + // Notify user about transfer or any other error + hid_host_user_interface_callback(iface, HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR); +} + +/** Lock HID device from other task + * + * @param[in] hid_device Pointer to HID device structure + * @param[in] timeout_ms Timeout of trying to take the mutex + * @return esp_err_t + */ +static inline esp_err_t hid_device_try_lock(hid_device_t *hid_device, uint32_t timeout_ms) +{ + return ( xSemaphoreTake(hid_device->device_busy, pdMS_TO_TICKS(timeout_ms)) + ? ESP_OK + : ESP_ERR_TIMEOUT ); +} + +/** Unlock HID device from other task + * + * @param[in] hid_device Pointer to HID device structure + * @param[in] timeout_ms Timeout of trying to take the mutex + * @return esp_err_t + */ +static inline void hid_device_unlock(hid_device_t *hid_device) +{ + xSemaphoreGive(hid_device->device_busy); +} + +/** + * @brief HID Control transfer complete callback + * + * @param[in] ctrl_xfer Pointer to transfer data structure + */ +static void ctrl_xfer_done(usb_transfer_t *ctrl_xfer) +{ + assert(ctrl_xfer); + hid_device_t *hid_device = (hid_device_t *)ctrl_xfer->context; + xSemaphoreGive(hid_device->ctrl_xfer_done); +} + +/** + * @brief HID control transfer synchronous. + * + * @note Passes interface and endpoint descriptors to obtain: + + * - interface number, IN endpoint, OUT endpoint, max. packet size + * + * @param[in] hid_device Pointer to HID device structure + * @param[in] ctrl_xfer Pointer to the Transfer structure + * @param[in] len Number of bytes to transfer + * @param[in] timeout_ms Timeout in ms + * @return esp_err_t + */ +static esp_err_t hid_control_transfer(hid_device_t *hid_device, + size_t len, + uint32_t timeout_ms) +{ + + usb_transfer_t *ctrl_xfer = hid_device->ctrl_xfer; + + ctrl_xfer->device_handle = hid_device->dev_hdl; + ctrl_xfer->callback = ctrl_xfer_done; + ctrl_xfer->context = hid_device; + ctrl_xfer->bEndpointAddress = 0; + ctrl_xfer->timeout_ms = timeout_ms; + ctrl_xfer->num_bytes = len; + + HID_RETURN_ON_ERROR( usb_host_transfer_submit_control(s_hid_driver->client_handle, ctrl_xfer), + "Unable to submit control transfer"); + + BaseType_t received = xSemaphoreTake(hid_device->ctrl_xfer_done, pdMS_TO_TICKS(ctrl_xfer->timeout_ms)); + + if (received != pdTRUE) { + // Transfer was not finished, error in USB LIB. Reset the endpoint + ESP_LOGE(TAG, "Control Transfer Timeout"); + + HID_RETURN_ON_ERROR( usb_host_endpoint_halt(hid_device->dev_hdl, ctrl_xfer->bEndpointAddress), + "Unable to HALT EP"); + HID_RETURN_ON_ERROR( usb_host_endpoint_flush(hid_device->dev_hdl, ctrl_xfer->bEndpointAddress), + "Unable to FLUSH EP"); + usb_host_endpoint_clear(hid_device->dev_hdl, ctrl_xfer->bEndpointAddress); + return ESP_ERR_TIMEOUT; + } + + ESP_LOG_BUFFER_HEXDUMP(TAG, ctrl_xfer->data_buffer, ctrl_xfer->actual_num_bytes, ESP_LOG_DEBUG); + + return ESP_OK; +} + +/** + * @brief USB class standard request get descriptor + * + * @param[in] hidh_device Pointer to HID device structure + * @param[in] req Pointer to a class specific request structure + * @return esp_err_t + */ +static esp_err_t usb_class_request_get_descriptor(hid_device_t *hid_device, const hid_class_request_t *req) +{ + esp_err_t ret; + usb_transfer_t *ctrl_xfer = hid_device->ctrl_xfer; + const size_t ctrl_size = hid_device->ctrl_xfer->data_buffer_size; + + HID_RETURN_ON_INVALID_ARG(hid_device); + HID_RETURN_ON_INVALID_ARG(hid_device->ctrl_xfer); + HID_RETURN_ON_INVALID_ARG(req); + HID_RETURN_ON_INVALID_ARG(req->data); + + HID_RETURN_ON_ERROR( hid_device_try_lock(hid_device, DEFAULT_TIMEOUT_MS), + "HID Device is busy by other task"); + + if (ctrl_size < (USB_SETUP_PACKET_SIZE + req->wLength)) { + usb_device_info_t dev_info; + ESP_ERROR_CHECK(usb_host_device_info(hid_device->dev_hdl, &dev_info)); + // reallocate the ctrl xfer buffer for new length + ESP_LOGD(TAG, "Change HID ctrl xfer size from %d to %d", + ctrl_size, + (int) (USB_SETUP_PACKET_SIZE + req->wLength)); + + usb_host_transfer_free(hid_device->ctrl_xfer); + HID_RETURN_ON_ERROR( usb_host_transfer_alloc(USB_SETUP_PACKET_SIZE + req->wLength, + 0, + &hid_device->ctrl_xfer), + "Unable to allocate transfer buffer for EP0"); + } + + usb_setup_packet_t *setup = (usb_setup_packet_t *)ctrl_xfer->data_buffer; + + setup->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | + USB_BM_REQUEST_TYPE_TYPE_STANDARD | + USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + setup->bRequest = req->bRequest; + setup->wValue = req->wValue; + setup->wIndex = req->wIndex; + setup->wLength = req->wLength; + + ret = hid_control_transfer(hid_device, + USB_SETUP_PACKET_SIZE + req->wLength, + DEFAULT_TIMEOUT_MS); + + if (ESP_OK == ret) { + ctrl_xfer->actual_num_bytes -= USB_SETUP_PACKET_SIZE; + if (ctrl_xfer->actual_num_bytes <= req->wLength) { + memcpy(req->data, ctrl_xfer->data_buffer + USB_SETUP_PACKET_SIZE, ctrl_xfer->actual_num_bytes); + } else { + ret = ESP_ERR_INVALID_SIZE; + } + } + + hid_device_unlock(hid_device); + + return ret; +} + +/** + * @brief HID Host Request Report Descriptor + * + * @param[in] hidh_iface Pointer to HID Interface configuration structure + * @return esp_err_t + */ +static esp_err_t hid_class_request_report_descriptor(hid_iface_t *iface) +{ + HID_RETURN_ON_INVALID_ARG(iface); + + // Get Report Descriptor is possible only in Ready or Active state + HID_RETURN_ON_FALSE((HID_INTERFACE_STATE_READY == iface->state) || + (HID_INTERFACE_STATE_ACTIVE == iface->state), + ESP_ERR_INVALID_STATE, + "Unable to request report descriptor. Interface is not ready"); + + iface->report_desc = malloc(iface->report_desc_size); + HID_RETURN_ON_FALSE(iface->report_desc, + ESP_ERR_NO_MEM, + "Unable to allocate memory"); + + const hid_class_request_t get_desc = { + .bRequest = USB_B_REQUEST_GET_DESCRIPTOR, + .wValue = (HID_CLASS_DESCRIPTOR_TYPE_REPORT << 8), + .wIndex = iface->dev_params.iface_num, + .wLength = iface->report_desc_size, + .data = iface->report_desc + }; + + return usb_class_request_get_descriptor(iface->parent, &get_desc); +} + +/** + * @brief HID class specific request Set + * + * @param[in] hid_device Pointer to HID device structure + * @param[in] req Pointer to a class specific request structure + * @return esp_err_t + */ +static esp_err_t hid_class_request_set(hid_device_t *hid_device, + const hid_class_request_t *req) +{ + esp_err_t ret; + usb_transfer_t *ctrl_xfer = hid_device->ctrl_xfer; + HID_RETURN_ON_INVALID_ARG(hid_device); + HID_RETURN_ON_INVALID_ARG(hid_device->ctrl_xfer); + + HID_RETURN_ON_ERROR( hid_device_try_lock(hid_device, DEFAULT_TIMEOUT_MS), + "HID Device is busy by other task"); + + usb_setup_packet_t *setup = (usb_setup_packet_t *)ctrl_xfer->data_buffer; + setup->bmRequestType = USB_BM_REQUEST_TYPE_DIR_OUT | + USB_BM_REQUEST_TYPE_TYPE_CLASS | + USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + setup->bRequest = req->bRequest; + setup->wValue = req->wValue; + setup->wIndex = req->wIndex; + setup->wLength = req->wLength; + + if (req->wLength && req->data) { + memcpy(ctrl_xfer->data_buffer + USB_SETUP_PACKET_SIZE, req->data, req->wLength); + } + + ret = hid_control_transfer(hid_device, + USB_SETUP_PACKET_SIZE + setup->wLength, + DEFAULT_TIMEOUT_MS); + + hid_device_unlock(hid_device); + + return ret; +} + +/** + * @brief HID class specific request Get + * + * @param[in] hid_device Pointer to HID device structure + * @param[in] req Pointer to a class specific request structure + * @param[out] out_length Length of the response in data buffer of req struct + * @return esp_err_t + */ +static esp_err_t hid_class_request_get(hid_device_t *hid_device, + const hid_class_request_t *req, + size_t *out_length) +{ + esp_err_t ret; + HID_RETURN_ON_INVALID_ARG(hid_device); + HID_RETURN_ON_INVALID_ARG(hid_device->ctrl_xfer); + + usb_transfer_t *ctrl_xfer = hid_device->ctrl_xfer; + + HID_RETURN_ON_ERROR( hid_device_try_lock(hid_device, DEFAULT_TIMEOUT_MS), + "HID Device is busy by other task"); + + usb_setup_packet_t *setup = (usb_setup_packet_t *)ctrl_xfer->data_buffer; + + setup->bmRequestType = USB_BM_REQUEST_TYPE_DIR_IN | + USB_BM_REQUEST_TYPE_TYPE_CLASS | + USB_BM_REQUEST_TYPE_RECIP_INTERFACE; + setup->bRequest = req->bRequest; + setup->wValue = req->wValue; + setup->wIndex = req->wIndex; + setup->wLength = req->wLength; + + ret = hid_control_transfer(hid_device, + USB_SETUP_PACKET_SIZE + setup->wLength, + DEFAULT_TIMEOUT_MS); + + if (ESP_OK == ret) { + // We do not need the setup data, which is still in the transfer data buffer + ctrl_xfer->actual_num_bytes -= USB_SETUP_PACKET_SIZE; + // Copy data if the size is ok + if (ctrl_xfer->actual_num_bytes <= req->wLength) { + memcpy(req->data, ctrl_xfer->data_buffer + USB_SETUP_PACKET_SIZE, ctrl_xfer->actual_num_bytes); + // return actual num bytes of response + if (out_length) { + *out_length = ctrl_xfer->actual_num_bytes; + } + } else { + ret = ESP_ERR_INVALID_SIZE; + } + } + + hid_device_unlock(hid_device); + + return ret; +} + +// ---------------------------- Private --------------------------------------- +static esp_err_t hid_host_string_descriptor_copy(wchar_t *dest, + const usb_str_desc_t *src) +{ + if (dest == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (src != NULL) { + size_t len = MIN((src->bLength - USB_STANDARD_DESC_SIZE) / 2, HID_STR_DESC_MAX_LENGTH - 1); + for (int i = 0; i < len; i++) { + dest[i] = (wchar_t) src->wData[i]; + } + // This should be always true, we just check to avoid LoadProhibited exception + if (dest != NULL) { + dest[len] = 0; + } + } else { + dest[0] = 0; + } + return ESP_OK; +} + +esp_err_t hid_host_install_device(uint8_t dev_addr, + usb_device_handle_t dev_hdl, + hid_device_t **hid_device_handle) +{ + esp_err_t ret; + hid_device_t *hid_device; + + HID_GOTO_ON_FALSE( hid_device = calloc(1, sizeof(hid_device_t)), + ESP_ERR_NO_MEM, + "Unable to allocate memory for HID Device"); + + hid_device->dev_addr = dev_addr; + hid_device->dev_hdl = dev_hdl; + + HID_GOTO_ON_FALSE( hid_device->ctrl_xfer_done = xSemaphoreCreateBinary(), + ESP_ERR_NO_MEM, + "Unable to create semaphore"); + HID_GOTO_ON_FALSE( hid_device->device_busy = xSemaphoreCreateMutex(), + ESP_ERR_NO_MEM, + "Unable to create semaphore"); + + /* + * TIP: Usually, we need to allocate 'EP bMaxPacketSize0 + 1' here. + * To take the size of a report descriptor into a consideration, + * we need to allocate more here, e.g. 512 bytes. + */ + HID_GOTO_ON_ERROR(usb_host_transfer_alloc(512, 0, &hid_device->ctrl_xfer), + "Unable to allocate transfer buffer"); + + HID_ENTER_CRITICAL(); + HID_GOTO_ON_FALSE_CRITICAL( s_hid_driver, ESP_ERR_INVALID_STATE ); + HID_GOTO_ON_FALSE_CRITICAL( s_hid_driver->client_handle, ESP_ERR_INVALID_STATE ); + STAILQ_INSERT_TAIL(&s_hid_driver->hid_devices_tailq, hid_device, tailq_entry); + HID_EXIT_CRITICAL(); + + if (hid_device_handle) { + *hid_device_handle = hid_device; + } + + return ESP_OK; + +fail: + hid_host_uninstall_device(hid_device); + return ret; +} + +esp_err_t hid_host_uninstall_device(hid_device_t *hid_device) +{ + HID_RETURN_ON_INVALID_ARG(hid_device); + + HID_RETURN_ON_ERROR( usb_host_transfer_free(hid_device->ctrl_xfer), + "Unable to free transfer buffer for EP0"); + HID_RETURN_ON_ERROR( usb_host_device_close(s_hid_driver->client_handle, + hid_device->dev_hdl), + "Unable to close USB host"); + + if (hid_device->ctrl_xfer_done) { + vSemaphoreDelete(hid_device->ctrl_xfer_done); + } + + if (hid_device->device_busy) { + vSemaphoreDelete(hid_device->device_busy); + } + + ESP_LOGD(TAG, "Remove addr %d device from list", + hid_device->dev_addr); + + HID_ENTER_CRITICAL(); + STAILQ_REMOVE(&s_hid_driver->hid_devices_tailq, hid_device, hid_host_device, tailq_entry); + HID_EXIT_CRITICAL(); + + free(hid_device); + return ESP_OK; +} + +// ----------------------------- Public ---------------------------------------- + +esp_err_t hid_host_install(const hid_host_driver_config_t *config) +{ + esp_err_t ret; + + HID_RETURN_ON_INVALID_ARG(config); + HID_RETURN_ON_INVALID_ARG(config->callback); + + if ( config->create_background_task ) { + HID_RETURN_ON_FALSE(config->stack_size != 0, + ESP_ERR_INVALID_ARG, + "Wrong stack size value"); + HID_RETURN_ON_FALSE(config->task_priority != 0, + ESP_ERR_INVALID_ARG, + "Wrong task priority value"); + } + + HID_RETURN_ON_FALSE(!s_hid_driver, + ESP_ERR_INVALID_STATE, + "HID Host driver is already installed"); + + // Create HID driver structure + hid_driver_t *driver = heap_caps_calloc(1, sizeof(hid_driver_t), MALLOC_CAP_DEFAULT); + HID_RETURN_ON_FALSE(driver, + ESP_ERR_NO_MEM, + "Unable to allocate memory"); + + driver->user_cb = config->callback; + driver->user_arg = config->callback_arg; + + usb_host_client_config_t client_config = { + .is_synchronous = false, + .async.client_event_callback = client_event_cb, + .async.callback_arg = NULL, + .max_num_event_msg = 10, + }; + + driver->end_client_event_handling = false; + driver->all_events_handled = xSemaphoreCreateBinary(); + HID_GOTO_ON_FALSE(driver->all_events_handled, + ESP_ERR_NO_MEM, + "Unable to create semaphore"); + + HID_GOTO_ON_ERROR( usb_host_client_register(&client_config, + &driver->client_handle), + "Unable to register USB Host client"); + + HID_ENTER_CRITICAL(); + HID_GOTO_ON_FALSE_CRITICAL(!s_hid_driver, ESP_ERR_INVALID_STATE); + s_hid_driver = driver; + STAILQ_INIT(&s_hid_driver->hid_devices_tailq); + STAILQ_INIT(&s_hid_driver->hid_ifaces_tailq); + HID_EXIT_CRITICAL(); + + if (config->create_background_task) { + BaseType_t task_created = xTaskCreatePinnedToCore( + event_handler_task, + "USB HID Host", + config->stack_size, + NULL, + config->task_priority, + NULL, + config->core_id); + HID_GOTO_ON_FALSE(task_created, + ESP_ERR_NO_MEM, + "Unable to create USB HID Host task"); + } + + return ESP_OK; + +fail: + s_hid_driver = NULL; + if (driver->client_handle) { + usb_host_client_deregister(driver->client_handle); + } + if (driver->all_events_handled) { + vSemaphoreDelete(driver->all_events_handled); + } + free(driver); + return ret; +} + +esp_err_t hid_host_uninstall(void) +{ + // Make sure hid driver is installed, + HID_RETURN_ON_FALSE(s_hid_driver, + ESP_OK, + "HID Host driver was not installed"); + + // Make sure that hid driver + // not being uninstalled from other task + // and no hid device is registered + HID_ENTER_CRITICAL(); + HID_RETURN_ON_FALSE_CRITICAL( !s_hid_driver->end_client_event_handling, ESP_ERR_INVALID_STATE ); + HID_RETURN_ON_FALSE_CRITICAL( STAILQ_EMPTY(&s_hid_driver->hid_devices_tailq), ESP_ERR_INVALID_STATE ); + HID_RETURN_ON_FALSE_CRITICAL( STAILQ_EMPTY(&s_hid_driver->hid_ifaces_tailq), ESP_ERR_INVALID_STATE ); + s_hid_driver->end_client_event_handling = true; + HID_EXIT_CRITICAL(); + + if (s_hid_driver->event_handling_started) { + ESP_ERROR_CHECK( usb_host_client_unblock(s_hid_driver->client_handle) ); + // In case the event handling started, we must wait until it finishes + xSemaphoreTake(s_hid_driver->all_events_handled, portMAX_DELAY); + } + vSemaphoreDelete(s_hid_driver->all_events_handled); + ESP_ERROR_CHECK( usb_host_client_deregister(s_hid_driver->client_handle) ); + free(s_hid_driver); + s_hid_driver = NULL; + return ESP_OK; +} + +esp_err_t hid_host_device_open(hid_host_device_handle_t hid_dev_handle, + const hid_host_device_config_t *config) +{ + HID_RETURN_ON_FALSE(s_hid_driver, + ESP_ERR_INVALID_STATE, + "HID Driver is not installed"); + + hid_iface_t *hid_iface = get_iface_by_handle(hid_dev_handle); + + HID_RETURN_ON_INVALID_ARG(hid_iface); + + HID_RETURN_ON_FALSE((hid_iface->dev_params.proto >= HID_PROTOCOL_NONE) + && (hid_iface->dev_params.proto < HID_PROTOCOL_MAX), + ESP_ERR_INVALID_ARG, + "HID device protocol not supported"); + + HID_RETURN_ON_FALSE((HID_INTERFACE_STATE_IDLE == hid_iface->state), + ESP_ERR_INVALID_STATE, + "Interface wrong state"); + + // Claim interface, allocate xfer and save report callback + HID_RETURN_ON_ERROR( hid_host_interface_claim_and_prepare_transfer(hid_iface), + "Unable to claim interface"); + + // Save HID Interface callback + hid_iface->user_cb = config->callback; + hid_iface->user_cb_arg = config->callback_arg; + + return ESP_OK; +} + +esp_err_t hid_host_device_close(hid_host_device_handle_t hid_dev_handle) +{ + hid_iface_t *hid_iface = get_iface_by_handle(hid_dev_handle); + + HID_RETURN_ON_INVALID_ARG(hid_iface); + + ESP_LOGD(TAG, "Close addr %d, iface %d, state %d", + hid_iface->dev_params.addr, + hid_iface->dev_params.iface_num, + hid_iface->state); + + if (HID_INTERFACE_STATE_ACTIVE == hid_iface->state) { + HID_RETURN_ON_ERROR( hid_host_disable_interface(hid_iface), + "Unable to disable HID Interface"); + } + + if (HID_INTERFACE_STATE_READY == hid_iface->state) { + HID_RETURN_ON_ERROR( hid_host_interface_release_and_free_transfer(hid_iface), + "Unable to release HID Interface"); + + // If the device is closing by user before device detached we need to flush user callback here + free(hid_iface->report_desc); + hid_iface->report_desc = NULL; + } + + if (hid_iface->user_cb && hid_iface->state != HID_INTERFACE_STATE_WAIT_USER_DELETION) { + // Let user handle the remove process and wait for next hid_host_device_close() call + hid_iface->state = HID_INTERFACE_STATE_WAIT_USER_DELETION; + hid_host_user_interface_callback(hid_iface, HID_HOST_INTERFACE_EVENT_DISCONNECTED); + } else { + // Second call + hid_iface->user_cb = NULL; + hid_iface->user_cb_arg = NULL; + + /* Remove Interface from the list */ + ESP_LOGD(TAG, "Remove addr %d, iface %d from list", + hid_iface->dev_params.addr, + hid_iface->dev_params.iface_num); + HID_ENTER_CRITICAL(); + _hid_host_remove_interface(hid_iface); + HID_EXIT_CRITICAL(); + } + + return ESP_OK; +} + +esp_err_t hid_host_handle_events(uint32_t timeout) +{ + HID_RETURN_ON_FALSE(s_hid_driver != NULL, + ESP_ERR_INVALID_STATE, + "HID Driver is not installed"); + + ESP_LOGD(TAG, "USB HID handling"); + s_hid_driver->event_handling_started = true; + esp_err_t ret = usb_host_client_handle_events(s_hid_driver->client_handle, timeout); + if (s_hid_driver->end_client_event_handling) { + xSemaphoreGive(s_hid_driver->all_events_handled); + return ESP_FAIL; + } + return ret; +} + +esp_err_t hid_host_device_get_params(hid_host_device_handle_t hid_dev_handle, + hid_host_dev_params_t *dev_params) +{ + hid_iface_t *iface = get_iface_by_handle(hid_dev_handle); + + HID_RETURN_ON_FALSE(iface, + ESP_ERR_INVALID_STATE, + "HID Interface not found"); + + HID_RETURN_ON_FALSE(dev_params, + ESP_ERR_INVALID_ARG, + "Wrong argument"); + + memcpy(dev_params, &iface->dev_params, sizeof(hid_host_dev_params_t)); + return ESP_OK; +} + +esp_err_t hid_host_device_get_raw_input_report_data(hid_host_device_handle_t hid_dev_handle, + uint8_t *data, + size_t data_length_max, + size_t *data_length) +{ + hid_iface_t *iface = get_iface_by_handle(hid_dev_handle); + + HID_RETURN_ON_FALSE(iface, + ESP_ERR_INVALID_STATE, + "HID Interface not found"); + + HID_RETURN_ON_FALSE(data, + ESP_ERR_INVALID_ARG, + "Wrong argument"); + + HID_RETURN_ON_FALSE(data_length, + ESP_ERR_INVALID_ARG, + "Wrong argument"); + + size_t copied = (data_length_max >= iface->in_xfer->actual_num_bytes) + ? iface->in_xfer->actual_num_bytes + : data_length_max; + memcpy(data, iface->in_xfer->data_buffer, copied); + *data_length = copied; + return ESP_OK; +} + +// ------------------------ USB HID Host driver API ---------------------------- + +esp_err_t hid_host_device_start(hid_host_device_handle_t hid_dev_handle) +{ + hid_iface_t *iface = get_iface_by_handle(hid_dev_handle); + + HID_RETURN_ON_INVALID_ARG(iface); + HID_RETURN_ON_INVALID_ARG(iface->in_xfer); + HID_RETURN_ON_INVALID_ARG(iface->parent); + + HID_RETURN_ON_FALSE(is_interface_in_list(iface), + ESP_ERR_NOT_FOUND, + "Interface handle not found"); + + HID_RETURN_ON_FALSE ((HID_INTERFACE_STATE_READY == iface->state), + ESP_ERR_INVALID_STATE, + "Interface wrong state"); + + // prepare transfer + iface->in_xfer->device_handle = iface->parent->dev_hdl; + iface->in_xfer->callback = in_xfer_done; + iface->in_xfer->context = iface; + iface->in_xfer->timeout_ms = DEFAULT_TIMEOUT_MS; + iface->in_xfer->bEndpointAddress = iface->ep_in; + iface->in_xfer->num_bytes = iface->ep_in_mps; + + iface->state = HID_INTERFACE_STATE_ACTIVE; + + // start data transfer + return usb_host_transfer_submit(iface->in_xfer); +} + +esp_err_t hid_host_device_stop(hid_host_device_handle_t hid_dev_handle) +{ + hid_iface_t *iface = get_iface_by_handle(hid_dev_handle); + + HID_RETURN_ON_INVALID_ARG(iface); + + return hid_host_disable_interface(iface); +} + +uint8_t *hid_host_get_report_descriptor(hid_host_device_handle_t hid_dev_handle, + size_t *report_desc_len) +{ + hid_iface_t *iface = get_iface_by_handle(hid_dev_handle); + + if (NULL == iface) { + return NULL; + } + + // Report Descriptor was already requested, return pointer + if (iface->report_desc) { + *report_desc_len = iface->report_desc_size; + return iface->report_desc; + } + + // Request Report Descriptor + if (ESP_OK == hid_class_request_report_descriptor(iface)) { + *report_desc_len = iface->report_desc_size; + return iface->report_desc; + } + + return NULL; +} + +esp_err_t hid_host_get_device_info(hid_host_device_handle_t hid_dev_handle, + hid_host_dev_info_t *hid_dev_info) +{ + HID_RETURN_ON_INVALID_ARG(hid_dev_info); + + hid_iface_t *iface = get_iface_by_handle(hid_dev_handle); + HID_RETURN_ON_INVALID_ARG(iface); + + hid_device_t *hid_dev = iface->parent; + + // Fill descriptor device information + const usb_device_desc_t *desc; + usb_device_info_t dev_info; + HID_RETURN_ON_ERROR( usb_host_get_device_descriptor(hid_dev->dev_hdl, &desc), + "Unable to get device descriptor"); + HID_RETURN_ON_ERROR( usb_host_device_info(hid_dev->dev_hdl, &dev_info), + "Unable to get USB device info"); + // VID, PID + hid_dev_info->VID = desc->idVendor; + hid_dev_info->PID = desc->idProduct; + // Strings + hid_host_string_descriptor_copy(hid_dev_info->iManufacturer, + dev_info.str_desc_manufacturer); + hid_host_string_descriptor_copy(hid_dev_info->iProduct, + dev_info.str_desc_product); + hid_host_string_descriptor_copy(hid_dev_info->iSerialNumber, + dev_info.str_desc_serial_num); + return ESP_OK; +} + +esp_err_t hid_class_request_get_report(hid_host_device_handle_t hid_dev_handle, + uint8_t report_type, + uint8_t report_id, + uint8_t *report, + size_t *report_length) +{ + hid_iface_t *iface = get_iface_by_handle(hid_dev_handle); + + HID_RETURN_ON_INVALID_ARG(iface); + HID_RETURN_ON_INVALID_ARG(report); + + const hid_class_request_t get_report = { + .bRequest = HID_CLASS_SPECIFIC_REQ_GET_REPORT, + .wValue = (report_type << 8) | report_id, + .wIndex = iface->dev_params.iface_num, + .wLength = *report_length, + .data = report + }; + + return hid_class_request_get(iface->parent, &get_report, report_length); +} + +esp_err_t hid_class_request_get_idle(hid_host_device_handle_t hid_dev_handle, + uint8_t report_id, + uint8_t *idle_rate) +{ + hid_iface_t *iface = get_iface_by_handle(hid_dev_handle); + + HID_RETURN_ON_INVALID_ARG(iface); + HID_RETURN_ON_INVALID_ARG(idle_rate); + + uint8_t tmp[1] = { 0xff }; + + const hid_class_request_t get_idle = { + .bRequest = HID_CLASS_SPECIFIC_REQ_GET_IDLE, + .wValue = report_id, + .wIndex = iface->dev_params.iface_num, + .wLength = 1, + .data = tmp + }; + + HID_RETURN_ON_ERROR( hid_class_request_get(iface->parent, &get_idle, NULL), + "HID class request transfer failure"); + + *idle_rate = tmp[0]; + + return ESP_OK; +} + +esp_err_t hid_class_request_get_protocol(hid_host_device_handle_t hid_dev_handle, + hid_report_protocol_t *protocol) +{ + hid_iface_t *iface = get_iface_by_handle(hid_dev_handle); + + HID_RETURN_ON_INVALID_ARG(iface); + HID_RETURN_ON_INVALID_ARG(protocol); + + uint8_t tmp[1] = { 0xff }; + + const hid_class_request_t get_proto = { + .bRequest = HID_CLASS_SPECIFIC_REQ_GET_PROTOCOL, + .wValue = 0, + .wIndex = iface->dev_params.iface_num, + .wLength = 1, + .data = tmp + }; + + HID_RETURN_ON_ERROR( hid_class_request_get(iface->parent, &get_proto, NULL), + "HID class request failure"); + + *protocol = (hid_report_protocol_t) tmp[0]; + return ESP_OK; +} + +esp_err_t hid_class_request_set_report(hid_host_device_handle_t hid_dev_handle, + uint8_t report_type, + uint8_t report_id, + uint8_t *report, + size_t report_length) +{ + hid_iface_t *iface = get_iface_by_handle(hid_dev_handle); + + HID_RETURN_ON_INVALID_ARG(iface); + + const hid_class_request_t set_report = { + .bRequest = HID_CLASS_SPECIFIC_REQ_SET_REPORT, + .wValue = (report_type << 8) | report_id, + .wIndex = iface->dev_params.iface_num, + .wLength = report_length, + .data = report + }; + + return hid_class_request_set(iface->parent, &set_report); +} + +esp_err_t hid_class_request_set_idle(hid_host_device_handle_t hid_dev_handle, + uint8_t duration, + uint8_t report_id) +{ + hid_iface_t *iface = get_iface_by_handle(hid_dev_handle); + + HID_RETURN_ON_INVALID_ARG(iface); + + const hid_class_request_t set_idle = { + .bRequest = HID_CLASS_SPECIFIC_REQ_SET_IDLE, + .wValue = (duration << 8) | report_id, + .wIndex = iface->dev_params.iface_num, + .wLength = 0, + .data = NULL + }; + + return hid_class_request_set(iface->parent, &set_idle); +} + +esp_err_t hid_class_request_set_protocol(hid_host_device_handle_t hid_dev_handle, + hid_report_protocol_t protocol) +{ + hid_iface_t *iface = get_iface_by_handle(hid_dev_handle); + + HID_RETURN_ON_INVALID_ARG(iface); + + const hid_class_request_t set_proto = { + .bRequest = HID_CLASS_SPECIFIC_REQ_SET_PROTOCOL, + .wValue = protocol, + .wIndex = iface->dev_params.iface_num, + .wLength = 0, + .data = NULL + }; + + return hid_class_request_set(iface->parent, &set_proto); +} diff --git a/managed_components/espressif__usb_host_hid/idf_component.yml b/managed_components/espressif__usb_host_hid/idf_component.yml new file mode 100644 index 0000000..b428d6b --- /dev/null +++ b/managed_components/espressif__usb_host_hid/idf_component.yml @@ -0,0 +1,13 @@ +dependencies: + idf: '>=4.4' +description: USB Host HID driver +repository: git://github.com/espressif/esp-usb.git +repository_info: + commit_sha: 39b0de39a90c9e391db26f06d54e4b10f58be231 + path: host/class/hid/usb_host_hid +targets: +- esp32s2 +- esp32s3 +- esp32p4 +url: https://github.com/espressif/esp-usb/tree/master/host/class/hid/usb_host_hid +version: 1.0.3 diff --git a/managed_components/espressif__usb_host_hid/include/usb/hid.h b/managed_components/espressif__usb_host_hid/include/usb/hid.h new file mode 100644 index 0000000..c10604c --- /dev/null +++ b/managed_components/espressif__usb_host_hid/include/usb/hid.h @@ -0,0 +1,147 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief HID Subclass + * + * @see 4.2 Subclass, p.8 of Device Class Definition for Human Interface Devices (HID) Version 1.11 + */ +typedef enum { + HID_SUBCLASS_NO_SUBCLASS = 0x00, + HID_SUBCLASS_BOOT_INTERFACE = 0x01 +} __attribute__((packed)) hid_subclass_t; + +/** + * @brief HID Protocols + * + * @see 4.3 Protocols, p.9 of Device Class Definition for Human Interface Devices (HID) Version 1.11 + */ +typedef enum { + HID_PROTOCOL_NONE = 0x00, + HID_PROTOCOL_KEYBOARD = 0x01, + HID_PROTOCOL_MOUSE = 0x02, + HID_PROTOCOL_MAX +} __attribute__((packed)) hid_protocol_t; + +/** + * @brief HID Descriptor + * + * @see 6.2.1 HID Descriptor, p.22 of Device Class Definition for Human Interface Devices (HID) Version 1.11 + */ +typedef struct { + uint8_t bLength; // Numeric expression that is the total size of the HID descriptor + uint8_t bDescriptorType; // Constant name specifying type of HID descriptor + uint16_t bcdHID; // Numeric expression identifying the HIDClass Specification release + uint8_t bCountryCode; // Numeric expression identifying country code of the localized hardware + uint8_t bNumDescriptors; // Numeric expression specifying the number of class descriptors (always at least one i.e. Report descriptor.) + uint8_t bReportDescriptorType; // Constant name identifying type of class descriptor. See Section 7.1.2: Set_Descriptor Request for a table of class descriptor constants + uint16_t wReportDescriptorLength; // Numeric expression that is the total size of the Report descriptor + // Optional descriptors may follow further +} __attribute__((packed)) hid_descriptor_t; + +/** + * @brief HID Country Codes + * + * @see 6.2.1 HID Descriptor, p.23 of Device Class Definition for Human Interface Devices (HID) Version 1.11 + */ + +typedef enum { + HID_COUNTRY_CODE_NOT_SUPPORTED = 0x00, + HID_COUNTRY_CODE_ARABIC = 0x01, + HID_COUNTRY_CODE_BELGIAN = 0x02, + HID_COUNTRY_CODE_CANADIAN_BILINGUAL = 0x03, + HID_COUNTRY_CODE_CANADIAN_FRENCH = 0x04, + HID_COUNTRY_CODE_CZECH = 0x05, + HID_COUNTRY_CODE_DANISH = 0x06, + HID_COUNTRY_CODE_FINNISH = 0x07, + HID_COUNTRY_CODE_FRENCH = 0x08, + HID_COUNTRY_CODE_GERMAN = 0x09, + HID_COUNTRY_CODE_GREEK = 0x0A, + HID_COUNTRY_CODE_HEBREW = 0x0B, + HID_COUNTRY_CODE_HUNGARY = 0x0C, + HID_COUNTRY_CODE_ISO = 0x0D, + HID_COUNTRY_CODE_ITALIAN = 0x0E, + HID_COUNTRY_CODE_JAPAN = 0x0F, + HID_COUNTRY_CODE_KOREAN = 0x10, + HID_COUNTRY_CODE_LATIN_AMERICAN = 0x11, + HID_COUNTRY_CODE_NETHERLANDS = 0x12, + HID_COUNTRY_CODE_NORWEGIAN = 0x13, + HID_COUNTRY_CODE_PERSIAN = 0x14, + HID_COUNTRY_CODE_POLAND = 0x15, + HID_COUNTRY_CODE_PORTUGUESE = 0x16, + HID_COUNTRY_CODE_RUSSIA = 0x17, + HID_COUNTRY_CODE_SLOVAKIA = 0x18, + HID_COUNTRY_CODE_SPANISH = 0x19, + HID_COUNTRY_CODE_SWEDISH = 0x1A, + HID_COUNTRY_CODE_SWISS_F = 0x1B, + HID_COUNTRY_CODE_SWISS_G = 0x1C, + HID_COUNTRY_CODE_SWITZERLAND = 0x1D, + HID_COUNTRY_CODE_TAIWAN = 0x1E, + HID_COUNTRY_CODE_TURKISH_Q = 0x1F, + HID_COUNTRY_CODE_UK = 0x20, + HID_COUNTRY_CODE_US = 0x21, + HID_COUNTRY_CODE_YUGOSLAVIA = 0x22, + HID_COUNTRY_CODE_TURKISH_F = 0x23 +} __attribute__((packed)) hid_country_code_t; + +/** + * @brief HID Class Descriptor Types + * + * @see 7.1, p.49 of Device Class Definition for Human Interface Devices (HID) Version 1.11 + */ +typedef enum { + HID_CLASS_DESCRIPTOR_TYPE_HID = 0x21, + HID_CLASS_DESCRIPTOR_TYPE_REPORT = 0x22, + HID_CLASS_DESCRIPTOR_TYPE_PHYSICAL = 0x23 +} __attribute__((packed)) hid_class_descritpor_type_t; + +/** + * @brief HID Class-Specific Requests + * + * @see 7.2, p.50 of Device Class Definition for Human Interface Devices (HID) Version 1.11 + */ +typedef enum { + HID_CLASS_SPECIFIC_REQ_GET_REPORT = 0x01, + HID_CLASS_SPECIFIC_REQ_GET_IDLE = 0x02, + HID_CLASS_SPECIFIC_REQ_GET_PROTOCOL = 0x03, + HID_CLASS_SPECIFIC_REQ_SET_REPORT = 0x09, + HID_CLASS_SPECIFIC_REQ_SET_IDLE = 0x0A, + HID_CLASS_SPECIFIC_REQ_SET_PROTOCOL = 0x0B +} __attribute__((packed)) hid_class_specific_req_t; + +/** + * @brief HID Report Types + * + * @see 7.2.1, p.51 of Device Class Definition for Human Interface Devices (HID) Version 1.11 + */ +typedef enum { + HID_REPORT_TYPE_INPUT = 0x01, + HID_REPORT_TYPE_OUTPUT = 0x02, + HID_REPORT_TYPE_FEATURE = 0x03, +} __attribute__((packed)) hid_report_type_t; + +/** + * @brief HID Report protocol + * + * @see 7.2.5/7.2.6, p.54 of Device Class Definition for Human Interface Devices (HID) Version 1.11 + */ +typedef enum { + HID_REPORT_PROTOCOL_BOOT = 0x00, + HID_REPORT_PROTOCOL_REPORT = 0x01, + HID_REPORT_PROTOCOL_MAX +} __attribute__((packed)) hid_report_protocol_t; + +#ifdef __cplusplus +} +#endif //__cplusplus diff --git a/managed_components/espressif__usb_host_hid/include/usb/hid_host.h b/managed_components/espressif__usb_host_hid/include/usb/hid_host.h new file mode 100644 index 0000000..ada0932 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/include/usb/hid_host.h @@ -0,0 +1,313 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include + +#include "hid.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief USB HID HOST string descriptor maximal length + * + * The maximum possible number of characters in an embedded string is device specific. + * For USB devices, the maximum string length is 126 wide characters (not including the terminating NULL character). + * This is a length, which is available to upper level application during getting information + * of HID Device with 'hid_host_get_device_info' call. + * + * To decrease memory usage 32 wide characters (64 bytes per every string) is used. +*/ +#define HID_STR_DESC_MAX_LENGTH 32 + +typedef struct hid_interface *hid_host_device_handle_t; /**< Device Handle. Handle to a particular HID interface */ + +// ------------------------ USB HID Host events -------------------------------- +/** + * @brief USB HID HOST Device event id +*/ +typedef enum { + HID_HOST_DRIVER_EVENT_CONNECTED = 0x00, /**< HID Device has been found in connected USB device (at least one) */ +} hid_host_driver_event_t; + +/** + * @brief USB HID HOST Interface event id +*/ +typedef enum { + HID_HOST_INTERFACE_EVENT_INPUT_REPORT = 0x00, /**< HID Device input report */ + HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR, /**< HID Device transfer error */ + HID_HOST_INTERFACE_EVENT_DISCONNECTED, /**< HID Device has been disconnected */ +} hid_host_interface_event_t; + +/** + * @brief HID device descriptor common data. +*/ +typedef struct { + uint16_t VID; + uint16_t PID; + wchar_t iManufacturer[HID_STR_DESC_MAX_LENGTH]; + wchar_t iProduct[HID_STR_DESC_MAX_LENGTH]; + wchar_t iSerialNumber[HID_STR_DESC_MAX_LENGTH]; +} hid_host_dev_info_t; + +/** + * @brief USB HID Host device parameters +*/ +typedef struct { + uint8_t addr; /**< USB Address of connected HID device */ + uint8_t iface_num; /**< HID Interface Number */ + uint8_t sub_class; /**< HID Interface SubClass */ + uint8_t proto; /**< HID Interface Protocol */ +} hid_host_dev_params_t; + +// ------------------------ USB HID Host callbacks ----------------------------- + +/** + * @brief USB HID driver event callback. + * + * @param[in] hid_handle HID device handle (HID Interface) + * @param[in] event HID driver event + * @param[in] arg User argument from HID driver configuration structure +*/ +typedef void (*hid_host_driver_event_cb_t)(hid_host_device_handle_t hid_device_handle, + const hid_host_driver_event_t event, + void *arg); + +/** + * @brief USB HID Interface event callback. + * + * @param[in] hid_device_handle HID device handle (HID Interface) + * @param[in] event HID Interface event + * @param[in] arg User argument +*/ +typedef void (*hid_host_interface_event_cb_t)(hid_host_device_handle_t hid_device_handle, + const hid_host_interface_event_t event, + void *arg); + +// ----------------------------- Public --------------------------------------- +/** + * @brief HID configuration structure. +*/ +typedef struct { + bool create_background_task; /**< When set to true, background task handling USB events is created. + Otherwise user has to periodically call hid_host_handle_events function */ + size_t task_priority; /**< Task priority of created background task */ + size_t stack_size; /**< Stack size of created background task */ + BaseType_t core_id; /**< Select core on which background task will run or tskNO_AFFINITY */ + hid_host_driver_event_cb_t callback; /**< Callback invoked when HID driver event occurs. Must not be NULL. */ + void *callback_arg; /**< User provided argument passed to callback */ +} hid_host_driver_config_t; + +/** + * @brief HID device configuration structure (HID Interface) +*/ +typedef struct { + hid_host_interface_event_cb_t callback; /**< Callback invoked when HID Interface event occurs */ + void *callback_arg; /**< User provided argument passed to callback */ +} hid_host_device_config_t; + +/** + * @brief USB HID Host install USB Host HID Class driver + * + * @param[in] config configuration structure HID to create + * @return esp_err_r + */ +esp_err_t hid_host_install(const hid_host_driver_config_t *config); + +/** + * @brief USB HID Host uninstall HID Class driver + * @return esp_err_t + */ +esp_err_t hid_host_uninstall(void); + +/** + * @brief USB HID Host open a device with specific device parameters + * + * @param[in] iface_handle Handle of the HID device to open + * @param[in] config Configuration structure HID device to open + * @return esp_err_t + */ +esp_err_t hid_host_device_open(hid_host_device_handle_t hid_dev_handle, + const hid_host_device_config_t *config); + +/** + * @brief USB HID Host close device + * + * @param[in] hid_dev_handle Handle of the HID device to close + * @return esp_err_t + */ +esp_err_t hid_host_device_close(hid_host_device_handle_t hid_dev_handle); + +/** + * @brief HID Host USB event handler + * + * If HID Host install was made with create_background_task=false configuration, + * application needs to handle USB Host events itself. + * Do not used if HID host install was made with create_background_task=true configuration + * + * @param[in] timeout Timeout in ticks. For milliseconds, please use 'pdMS_TO_TICKS()' macros + * @return esp_err_t + */ +esp_err_t hid_host_handle_events(uint32_t timeout); + +/** + * @brief HID Device get parameters by handle. + * + * @param[in] hid_dev_handle HID Device handle + * @param[out] dev_params Pointer to a dev_params struct to fill + * + * @return esp_err_t + */ +esp_err_t hid_host_device_get_params(hid_host_device_handle_t hid_dev_handle, + hid_host_dev_params_t *dev_params); +/** + * @brief HID Host get device raw input report data pointer by handle + * + * This functions should be called after HID Interface device event HID_HOST_INTERFACE_EVENT_INPUT_REPORT + * to get the actual raw data of input report. + * + * @param[in] hid_dev_handle HID Device handle + * @param[in] data Pointer to buffer where the input data will be copied + * @param[in] data_length_max Max length of data can be copied to data buffer + * @param[out] data_length Length of input report + * + * @return esp_err_t + */ +esp_err_t hid_host_device_get_raw_input_report_data(hid_host_device_handle_t hid_dev_handle, + uint8_t *data, + size_t data_length_max, + size_t *data_length); + +// ------------------------ USB HID Host driver API ---------------------------- + +/** + * @brief HID Host start awaiting event from a device by handle + * + * Calls a callback when the HID Interface event has occurred. + * + * @param[in] hid_dev_handle HID Device handle + * @return esp_err_t + */ +esp_err_t hid_host_device_start(hid_host_device_handle_t hid_dev_handle); + +/** + * @brief HID Host stop device + * + * @param[in] hid_dev_handle HID Device handle + * + * @return esp_err_t + */ +esp_err_t hid_host_device_stop(hid_host_device_handle_t hid_dev_handle); + +/** + * @brief HID Host Get Report Descriptor + * + * @param[in] hid_dev_handle HID Device handle + * @param[out] report_desc_len Length of report descriptor + * + * @return a uint8_t pointer to report descriptor data + */ +uint8_t *hid_host_get_report_descriptor(hid_host_device_handle_t hid_dev_handle, + size_t *report_desc_len); + + +/** + * @brief HID Host Get device information + * + * @param[in] hid_dev_handle HID Device handle +*/ +esp_err_t hid_host_get_device_info(hid_host_device_handle_t hid_dev_handle, + hid_host_dev_info_t *hid_dev_info); + +/** + * @brief HID class specific request GET REPORT + * + * @param[in] hid_dev_handle HID Device handle + * @param[in] report_id Report ID + * @param[out] report Pointer to buffer for a report data + * @param[in/out] report_length Report data length, before the get report contain the maximum value of a report buffer. + * After get report there is a value of actual data in report buffer. + * + * @return esp_err_t + */ +esp_err_t hid_class_request_get_report(hid_host_device_handle_t hid_dev_handle, + uint8_t report_type, + uint8_t report_id, + uint8_t *report, + size_t *report_length); + +/** + * @brief HID class specific request GET IDLE + * + * @param[in] hid_dev_handle HID Device handle + * @param[in] report_id ReportID + * @param[out] idle_rate Idle rate [ms] + * + * @return esp_err_t + */ +esp_err_t hid_class_request_get_idle(hid_host_device_handle_t hid_dev_handle, + uint8_t report_id, + uint8_t *idle_rate); + +/** + * @brief HID class specific request GET PROTOCOL + * + * @param[in] hid_dev_handle HID Device handle + * @param[out] protocol Pointer to HID report protocol (boot or report) of device + * + * @return esp_err_t + */ +esp_err_t hid_class_request_get_protocol(hid_host_device_handle_t hid_dev_handle, + hid_report_protocol_t *protocol); + +/** +* @brief HID class specific request SET REPORT +* +* @param[in] hid_dev_handle HID Device handle +* @param[in] report_type Report type +* @param[in] report_id Report ID +* @param[in] report Pointer to a buffer with report data +* @param[in] report_length Report data length +* +* @return esp_err_t +*/ +esp_err_t hid_class_request_set_report(hid_host_device_handle_t hid_dev_handle, + uint8_t report_type, + uint8_t report_id, + uint8_t *report, + size_t report_length); + +/** + * @brief HID class specific request SET IDLE + * + * @param[in] hid_dev_handle HID Device handle + * @param[in] duration 0 (zero) for the indefinite duration, non-zero, then a fixed duration used. + * @param[in] report_id If 0 (zero) the idle rate applies to all input reports generated by the device, otherwise ReportID + * @return esp_err_t + */ +esp_err_t hid_class_request_set_idle(hid_host_device_handle_t hid_dev_handle, + uint8_t duration, + uint8_t report_id); + +/** + * @brief HID class specific request SET PROTOCOL + * + * @param[in] hid_dev_handle HID Device handle + * @param[in] protocol HID report protocol (boot or report) + * @return esp_err_t + */ +esp_err_t hid_class_request_set_protocol(hid_host_device_handle_t hid_dev_handle, + hid_report_protocol_t protocol); + +#ifdef __cplusplus +} +#endif //__cplusplus diff --git a/managed_components/espressif__usb_host_hid/include/usb/hid_usage_keyboard.h b/managed_components/espressif__usb_host_hid/include/usb/hid_usage_keyboard.h new file mode 100644 index 0000000..a740b90 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/include/usb/hid_usage_keyboard.h @@ -0,0 +1,292 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//------------------------------------------ HID usage keys --------------------------------------------------------------- +/** + * @brief HID Keys + * + */ +typedef enum { + HID_KEY_NO_PRESS = 0x00, + HID_KEY_ROLLOVER = 0x01, + HID_KEY_POST_FAIL = 0x02, + HID_KEY_ERROR_UNDEFINED = 0x03, + HID_KEY_A = 0x04, + HID_KEY_B = 0x05, + HID_KEY_C = 0x06, + HID_KEY_D = 0x07, + HID_KEY_E = 0x08, + HID_KEY_F = 0x09, + HID_KEY_G = 0x0A, + HID_KEY_H = 0x0B, + HID_KEY_I = 0x0C, + HID_KEY_J = 0x0D, + HID_KEY_K = 0x0E, + HID_KEY_L = 0x0F, + HID_KEY_M = 0x10, + HID_KEY_N = 0x11, + HID_KEY_O = 0x12, + HID_KEY_P = 0x13, + HID_KEY_Q = 0x14, + HID_KEY_R = 0x15, + HID_KEY_S = 0x16, + HID_KEY_T = 0x17, + HID_KEY_U = 0x18, + HID_KEY_V = 0x19, + HID_KEY_W = 0x1A, + HID_KEY_X = 0x1B, + HID_KEY_Y = 0x1C, + HID_KEY_Z = 0x1D, + HID_KEY_1 = 0x1E, + HID_KEY_2 = 0x1F, + HID_KEY_3 = 0x20, + HID_KEY_4 = 0x21, + HID_KEY_5 = 0x22, + HID_KEY_6 = 0x23, + HID_KEY_7 = 0x24, + HID_KEY_8 = 0x25, + HID_KEY_9 = 0x26, + HID_KEY_0 = 0x27, + HID_KEY_ENTER = 0x28, + HID_KEY_ESC = 0x29, + HID_KEY_DEL = 0x2A, + HID_KEY_TAB = 0x2B, + HID_KEY_SPACE = 0x2C, + HID_KEY_MINUS = 0x2D, + HID_KEY_EQUAL = 0x2E, + HID_KEY_OPEN_BRACKET = 0x2F, + HID_KEY_CLOSE_BRACKET = 0x30, + HID_KEY_BACK_SLASH = 0x31, + HID_KEY_SHARP = 0x32, + HID_KEY_COLON = 0x33, + HID_KEY_QUOTE = 0x34, + HID_KEY_TILDE = 0x35, + HID_KEY_LESS = 0x36, + HID_KEY_GREATER = 0x37, + HID_KEY_SLASH = 0x38, + HID_KEY_CAPS_LOCK = 0x39, + HID_KEY_F1 = 0x3A, + HID_KEY_F2 = 0x3B, + HID_KEY_F3 = 0x3C, + HID_KEY_F4 = 0x3D, + HID_KEY_F5 = 0x3E, + HID_KEY_F6 = 0x3F, + HID_KEY_F7 = 0x40, + HID_KEY_F8 = 0x41, + HID_KEY_F9 = 0x42, + HID_KEY_F10 = 0x43, + HID_KEY_F11 = 0x44, + HID_KEY_F12 = 0x45, + HID_KEY_PRINT_SCREEN = 0x46, + HID_KEY_SCROLL_LOCK = 0x47, + HID_KEY_PAUSE = 0x48, + HID_KEY_INSERT = 0x49, + HID_KEY_HOME = 0x4A, + HID_KEY_PAGEUP = 0x4B, + HID_KEY_DELETE = 0x4C, + HID_KEY_END = 0x4D, + HID_KEY_PAGEDOWN = 0x4E, + HID_KEY_RIGHT = 0x4F, + HID_KEY_LEFT = 0x50, + HID_KEY_DOWN = 0x51, + HID_KEY_UP = 0x52, + HID_KEY_NUM_LOCK = 0x53, + HID_KEY_KEYPAD_DIV = 0x54, + HID_KEY_KEYPAD_MUL = 0x55, + HID_KEY_KEYPAD_SUB = 0x56, + HID_KEY_KEYPAD_ADD = 0x57, + HID_KEY_KEYPAD_ENTER = 0x58, + HID_KEY_KEYPAD_1 = 0x59, + HID_KEY_KEYPAD_2 = 0x5A, + HID_KEY_KEYPAD_3 = 0x5B, + HID_KEY_KEYPAD_4 = 0x5C, + HID_KEY_KEYPAD_5 = 0x5D, + HID_KEY_KEYPAD_6 = 0x5E, + HID_KEY_KEYPAD_7 = 0x5F, + HID_KEY_KEYPAD_8 = 0x60, + HID_KEY_KEYPAD_9 = 0x61, + HID_KEY_KEYPAD_0 = 0x62, + HID_KEY_KEYPAD_DELETE = 0x63, + HID_KEY_KEYPAD_SLASH = 0x64, + HID_KEY_APPLICATION = 0x65, + HID_KEY_POWER = 0x66, + HID_KEY_KEYPAD_EQUAL = 0x67, + HID_KEY_F13 = 0x68, + HID_KEY_F14 = 0x69, + HID_KEY_F15 = 0x6A, + HID_KEY_F16 = 0x6B, + HID_KEY_F17 = 0x6C, + HID_KEY_F18 = 0x6D, + HID_KEY_F19 = 0x6E, + HID_KEY_F20 = 0x6F, + HID_KEY_F21 = 0x70, + HID_KEY_F22 = 0x71, + HID_KEY_F23 = 0x72, + HID_KEY_F24 = 0x73, + HID_KEY_EXECUTE = 0x74, + HID_KEY_HELP = 0x75, + HID_KEY_MENU = 0x76, + HID_KEY_SELECT = 0x77, + HID_KEY_STOP = 0x78, + HID_KEY_AGAIN = 0x79, + HID_KEY_UNDO = 0x7A, + HID_KEY_CUT = 0x7B, + HID_KEY_COPY = 0x7C, + HID_KEY_PASTE = 0x7D, + HID_KEY_FIND = 0x7E, + HID_KEY_MUTE = 0x7F, + HID_KEY_VOLUME_UP = 0x80, + HID_KEY_VOLUME_DOWN = 0x81, + HID_KEY_LOCKING_CAPS_LOCK = 0x82, + HID_KEY_LOCKING_NUM_LOCK = 0x83, + HID_KEY_LOCKING_SCROLL_LOCK = 0x84, + HID_KEY_KEYPAD_COMMA = 0x85, + HID_KEY_KEYPAD_EQUAL_SIGN = 0x86, + HID_KEY_INTERNATIONAL_1 = 0x87, + HID_KEY_INTERNATIONAL_2 = 0x88, + HID_KEY_INTERNATIONAL_3 = 0x89, + HID_KEY_INTERNATIONAL_4 = 0x8A, + HID_KEY_INTERNATIONAL_5 = 0x8B, + HID_KEY_INTERNATIONAL_6 = 0x8C, + HID_KEY_INTERNATIONAL_7 = 0x8D, + HID_KEY_INTERNATIONAL_8 = 0x8E, + HID_KEY_INTERNATIONAL_9 = 0x8F, + HID_KEY_LANG_1 = 0x90, + HID_KEY_LANG_2 = 0x91, + HID_KEY_LANG_3 = 0x92, + HID_KEY_LANG_4 = 0x93, + HID_KEY_LANG_5 = 0x94, + HID_KEY_LANG_6 = 0x95, + HID_KEY_LANG_7 = 0x96, + HID_KEY_LANG_8 = 0x97, + HID_KEY_LANG_9 = 0x98, + HID_KEY_ALTERNATE_ERASE = 0x99, + HID_KEY_SYSREQ = 0x9A, + HID_KEY_CANCEL = 0x9B, + HID_KEY_CLEAR = 0x9C, + HID_KEY_PRIOR = 0x9D, + HID_KEY_RETURN = 0x9E, + HID_KEY_SEPARATOR = 0x9F, + HID_KEY_OUT = 0xA0, + HID_KEY_OPER = 0xA1, + HID_KEY_CLEAR_AGAIN = 0xA2, + HID_KEY_CRSEL = 0xA3, + HID_KEY_EXSEL = 0xA4, + HID_KEY_KEYPAD_00 = 0xB0, + HID_KEY_KEYPAD_000 = 0xB1, + HID_KEY_THOUSANDS_SEPARATOR = 0xB2, + HID_KEY_DECIMAL_SEPARATOR = 0xB3, + HID_KEY_CURRENCY_UNIT = 0xB4, + HID_KEY_CURRENCY_SUB_UNIT = 0xB5, + HID_KEY_KEYPAD_OPEN_PARENTHESIS = 0xB6, + HID_KEY_KEYPAD_CLOSE_PARENTHESIS = 0xB7, + HID_KEY_KEYPAD_OPEN_BRACE = 0xB8, + HID_KEY_KEYPAD_CLOSE_BRACE = 0xB9, + HID_KEY_KEYPAD_TAB = 0xBA, + HID_KEY_KEYPAD_BACKSPACE = 0xBB, + HID_KEY_KEYPAD_A = 0xBC, + HID_KEY_KEYPAD_B = 0xBD, + HID_KEY_KEYPAD_C = 0xBE, + HID_KEY_KEYPAD_D = 0xBF, + HID_KEY_KEYPAD_E = 0xC0, + HID_KEY_KEYPAD_F = 0xC1, + HID_KEY_KEYPAD_XOR = 0xC2, + HID_KEY_KEYPAD_CARET = 0xC3, + HID_KEY_KEYPAD_PERCENT = 0xC4, + HID_KEY_KEYPAD_LESSER = 0xC5, + HID_KEY_KEYPAD_GREATER = 0xC6, + HID_KEY_KEYPAD_AND = 0xC7, + HID_KEY_KEYPAD_LOGICAL_AND = 0xC8, + HID_KEY_KEYPAD_OR = 0xC9, + HID_KEY_KEYPAD_LOGICAL_OR = 0xCA, + HID_KEY_KEYPAD_COLON = 0xCB, + HID_KEY_KEYPAD_SHARP = 0xCC, + HID_KEY_KEYPAD_SPACE = 0xCD, + HID_KEY_KEYPAD_AT = 0xCE, + HID_KEY_KEYPAD_BANG = 0xCF, + HID_KEY_KEYPAD_MEMORY_STORE = 0xD0, + HID_KEY_KEYPAD_MEMORY_RECALL = 0xD1, + HID_KEY_KEYPAD_MEMORY_CLEAD = 0xD2, + HID_KEY_KEYPAD_MEMORY_ADD = 0xD3, + HID_KEY_KEYPAD_MEMORY_SUBSTRACT = 0xD4, + HID_KEY_KEYPAD_MEMORY_MULTIPLY = 0xD5, + HID_KEY_KEYPAD_MEMORY_DIVIDE = 0xD6, + HID_KEY_KEYPAD_SIGN = 0xD7, + HID_KEY_KEYPAD_CLEAR = 0xD8, + HID_KEY_KEYPAD_CLEAR_ENTRY = 0xD9, + HID_KEY_KEYPAD_BINARY = 0xDA, + HID_KEY_KEYPAD_OCTAL = 0xDB, + HID_KEY_KEYPAD_DECIMAL = 0xDC, + HID_KEY_KEYPAD_HEXADECIMAL = 0xDD, + HID_KEY_LEFT_CONTROL = 0xE0, + HID_KEY_LEFT_SHIFT = 0xE1, + HID_KEY_LEFT_ALT = 0xE2, + HID_KEY_LEFT_GUI = 0xE3, + HID_KEY_RIGHT_CONTROL = 0xE0, + HID_KEY_RIGHT_SHIFT = 0xE1, + HID_KEY_RIGHT_ALT = 0xE2, + HID_KEY_RIGHT_GUI = 0xE3 +} __attribute__((packed)) hid_key_t; + +// Modifier bit mask +#define HID_LEFT_CONTROL (1 << 0) +#define HID_LEFT_SHIFT (1 << 1) +#define HID_LEFT_ALT (1 << 2) +#define HID_LEFT_GUI (1 << 3) +#define HID_RIGHT_CONTROL (1 << 4) +#define HID_RIGHT_SHIFT (1 << 5) +#define HID_RIGHT_ALT (1 << 6) +#define HID_RIGHT_GUI (1 << 7) + +/** + * @brief HID Keyboard Key number for Boot Interface + * + * @see B.1, p.60 of Device Class Definition for Human Interface Devices (HID) Version 1.11 + */ +typedef enum { + HID_KEYBOARD_KEY_NUMBER0 = 0, + HID_KEYBOARD_KEY_NUMBER1, + HID_KEYBOARD_KEY_NUMBER2, + HID_KEYBOARD_KEY_NUMBER3, + HID_KEYBOARD_KEY_NUMBER4, + HID_KEYBOARD_KEY_NUMBER5, + HID_KEYBOARD_KEY_MAX, +} hid_keyboard_key_number_t; + +/** + * @brief HID Keyboard Input Report for Boot Interfaces + * + * @see B.1, p.60 of Device Class Definition for Human Interface Devices (HID) Version 1.11 + */ +typedef struct { + union { + struct { + uint8_t left_ctr: 1; + uint8_t left_shift: 1; + uint8_t left_alt: 1; + uint8_t left_gui: 1; + uint8_t rigth_ctr: 1; + uint8_t right_shift: 1; + uint8_t right_alt: 1; + uint8_t right_gui: 1; + }; + uint8_t val; + } modifier; + uint8_t reserved; + uint8_t key[HID_KEYBOARD_KEY_MAX]; +} __attribute__((packed)) hid_keyboard_input_report_boot_t; + +#ifdef __cplusplus +} +#endif //__cplusplus diff --git a/managed_components/espressif__usb_host_hid/include/usb/hid_usage_mouse.h b/managed_components/espressif__usb_host_hid/include/usb/hid_usage_mouse.h new file mode 100644 index 0000000..5ef0183 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/include/usb/hid_usage_mouse.h @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief HID Mouse Input Report for Boot Interfaces + * + * @see B.1, p.60 of Device Class Definition for Human Interface Devices (HID) Version 1.11 + */ +typedef struct { + union { + struct { + uint8_t button1: 1; + uint8_t button2: 1; + uint8_t button3: 1; + uint8_t reserved: 5; + }; + uint8_t val; + } buttons; + int8_t x_displacement; + int8_t y_displacement; +} __attribute__((packed)) hid_mouse_input_report_boot_t; + +#ifdef __cplusplus +} +#endif //__cplusplus diff --git a/managed_components/espressif__usb_host_hid/test_app/CMakeLists.txt b/managed_components/espressif__usb_host_hid/test_app/CMakeLists.txt new file mode 100644 index 0000000..edac891 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/test_app/CMakeLists.txt @@ -0,0 +1,15 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +set(EXTRA_COMPONENT_DIRS + ../../usb_host_hid + ) + +# Set the components to include the tests for. +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0") + list(APPEND EXTRA_COMPONENT_DIRS ../../../../../device/esp_tinyusb) +endif() + +project(test_app_usb_host_hid) \ No newline at end of file diff --git a/managed_components/espressif__usb_host_hid/test_app/README.md b/managed_components/espressif__usb_host_hid/test_app/README.md new file mode 100644 index 0000000..a87b4dc --- /dev/null +++ b/managed_components/espressif__usb_host_hid/test_app/README.md @@ -0,0 +1,4 @@ +| Supported Targets | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | + +# USB: HID Class test application \ No newline at end of file diff --git a/managed_components/espressif__usb_host_hid/test_app/main/CMakeLists.txt b/managed_components/espressif__usb_host_hid/test_app/main/CMakeLists.txt new file mode 100644 index 0000000..57b7387 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/test_app/main/CMakeLists.txt @@ -0,0 +1,25 @@ +include($ENV{IDF_PATH}/tools/cmake/version.cmake) +set (TINYUSB_LIB) +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0") + set(TINYUSB_LIB "esp_tinyusb") +else() + set(TINYUSB_LIB "tinyusb") +endif() + +# TODO: once IDF_v4.4 is at the EOL support, use WHOLE_ARCHIVE +idf_component_register(SRC_DIRS . + INCLUDE_DIRS . + REQUIRES unity usb usb_host_hid ${TINYUSB_LIB}) + +# In order for the cases defined by `TEST_CASE` to be linked into the final elf, +# the component can be registered as WHOLE_ARCHIVE + +# Due to the backward compatibility to IDFv4.4 (in which WHOLE_ARCHIVE is not implemented) we use following approach: +# Any non-static function test_app/main/*.c (apart from test_app_main.c) file is added as an undefined symbol +# because otherwise the linker will ignore test_app/main/*.c as it has no other files depending on any +# symbols in it. + +# force-link test_hid_basic.c +set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u test_hid_setup") +# force-link test_hid_err_handling.c +set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u test_interface_callback_handler") diff --git a/managed_components/espressif__usb_host_hid/test_app/main/hid_mock_device.c b/managed_components/espressif__usb_host_hid/test_app/main/hid_mock_device.c new file mode 100644 index 0000000..16e6ed6 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/test_app/main/hid_mock_device.c @@ -0,0 +1,204 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "sdkconfig.h" +#include "tinyusb.h" +#include "class/hid/hid_device.h" +#include "esp_idf_version.h" +#include "hid_mock_device.h" + +static tusb_iface_count_t tusb_iface_count = 0; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +/************* TinyUSB descriptors ****************/ +#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_HID * TUD_HID_DESC_LEN) + +/** + * @brief HID report descriptor + * + * In this example we implement Keyboard + Mouse HID device, + * so we must define both report descriptors + */ +const uint8_t hid_report_descriptor[] = { + TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD) ), + TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(HID_ITF_PROTOCOL_MOUSE) ) +}; + +const uint8_t hid_keyboard_report_descriptor[] = { + TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD) ) +}; + +const uint8_t hid_mouse_report_descriptor[] = { + TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(HID_ITF_PROTOCOL_MOUSE) ) +}; + +/** + * @brief String descriptor + */ +const char *hid_string_descriptor[5] = { + // array of pointer to string descriptors + (char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) + "TinyUSB", // 1: Manufacturer + "TinyUSB Device", // 2: Product + "123456", // 3: Serials, should use chip ID + "Example HID interface", // 4: HID +}; + +/** + * @brief Configuration descriptor + * + * This is a simple configuration descriptor that defines 1 configuration and 1 HID interface + */ +static const uint8_t hid_configuration_descriptor_one_iface[] = { + // Configuration number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, CFG_TUD_HID, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + + // Interface number, string index, boot protocol, report descriptor len, EP In address, size & polling interval + TUD_HID_DESCRIPTOR(0, 4, false, sizeof(hid_report_descriptor), 0x81, 16, 10), + TUD_HID_DESCRIPTOR(1, 4, false, sizeof(hid_report_descriptor), 0x82, 16, 10), +}; + +static const uint8_t hid_configuration_descriptor_two_ifaces[] = { + // Configuration number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, CFG_TUD_HID, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + + // Interface number, string index, boot protocol, report descriptor len, EP In address, size & polling interval + TUD_HID_DESCRIPTOR(0, 4, HID_ITF_PROTOCOL_KEYBOARD, sizeof(hid_keyboard_report_descriptor), 0x81, 16, 10), + TUD_HID_DESCRIPTOR(2, 4, HID_ITF_PROTOCOL_MOUSE, sizeof(hid_mouse_report_descriptor), 0x82, 16, 10), +}; + +static const uint8_t *hid_configuration_descriptor_list[TUSB_IFACE_COUNT_MAX] = { + hid_configuration_descriptor_one_iface, + hid_configuration_descriptor_two_ifaces +}; +#endif // // esp idf >= v5.0.0 +/********* TinyUSB HID callbacks ***************/ + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +// Invoked when received GET HID REPORT DESCRIPTOR request +// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete +uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) +{ + switch (tusb_iface_count) { + case TUSB_IFACE_COUNT_ONE: + return hid_report_descriptor; + + case TUSB_IFACE_COUNT_TWO: + return (!!instance) ? hid_mouse_report_descriptor : hid_keyboard_report_descriptor; + + default: + break; + } + return NULL; +} +#endif // esp idf >= v5.0.0 + +#if ((ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0))) +#define HID_ITF_PROTOCOL_KEYBOARD HID_PROTOCOL_KEYBOARD +#define HID_ITF_PROTOCOL_MOUSE HID_PROTOCOL_MOUSE +#endif // 4.4.0 <= esp idf < v5.0.0 + +/** + * @brief Get Keyboard report + * + * Fill buffer with test Keyboard report data + * + * @param[in] buffer Pointer to a buffer for filling + * @return uint16_t Length of copied data to buffer + */ +static inline uint16_t get_keyboard_report(uint8_t *buffer) +{ + hid_keyboard_report_t kb_report = { + 0, // Keyboard modifier + 0, // Reserved + { HID_KEY_M, HID_KEY_N, HID_KEY_O, HID_KEY_P, HID_KEY_Q, HID_KEY_R } + }; + memcpy(buffer, &kb_report, sizeof(kb_report)); + return sizeof(kb_report); +} + +/** + * @brief Get Mouse report + * + * Fill buffer with test Mouse report data + * + * @param[in] buffer Pointer to a buffer for filling + * @return uint16_t Length of copied data to buffer + */ +static inline uint16_t get_mouse_report(uint8_t *buffer) +{ + hid_mouse_report_t mouse_report = { + MOUSE_BUTTON_LEFT | MOUSE_BUTTON_RIGHT, // buttons + -1, // x + 127, // y + 0, // wheel + 0 // pan + }; + memcpy(buffer, &mouse_report, sizeof(mouse_report)); + return sizeof(mouse_report); +} + +// Invoked when received GET_REPORT control request +// Application must fill buffer report's content and return its length. +// Return zero will cause the stack to STALL request +uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) +{ + switch (report_id) { + case HID_ITF_PROTOCOL_KEYBOARD: + return get_keyboard_report(buffer); + + case HID_ITF_PROTOCOL_MOUSE: + return get_mouse_report(buffer); + + default: + printf("HID mock device, Unhandled ReportID %d\n", report_id); + break; + } + return 0; +} + +// Invoked when received SET_REPORT control request or +// received data on OUT endpoint ( Report ID = 0, Type = 0 ) +void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) +{ + +} + +/** + * @brief HID Mock device start + * + * @param[in] iface_count Interface count, when TUSB_IFACE_COUNT_ONE then there is two Interfaces, but equal (Protocol=None). + * when TUSB_IFACE_COUNT_TWO then HID device mocked with two independent Interfaces (Protocol=BootKeyboard, Protocol=BootMouse). + */ +void hid_mock_device(tusb_iface_count_t iface_count) +{ + if (iface_count > TUSB_IFACE_COUNT_MAX) { + printf("UHID mock device, wrong iface_count paramteter (%d)\n", + iface_count); + return; + } + + // Global Interfaces count value + tusb_iface_count = iface_count; + + const tinyusb_config_t tusb_cfg = { + .external_phy = false, +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + .device_descriptor = NULL, + .string_descriptor = hid_string_descriptor, + .string_descriptor_count = sizeof(hid_string_descriptor) / sizeof(hid_string_descriptor[0]), + .configuration_descriptor = hid_configuration_descriptor_list[tusb_iface_count], +#endif // esp idf >= v5.0.0 + }; + + ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); + + printf("HID mock device with %s has been started\n", + (TUSB_IFACE_COUNT_ONE == tusb_iface_count) + ? "1xInterface (Protocol=None)" + : "2xInterfaces (Protocol=BootKeyboard, Protocol=BootMouse)"); +} diff --git a/managed_components/espressif__usb_host_hid/test_app/main/hid_mock_device.h b/managed_components/espressif__usb_host_hid/test_app/main/hid_mock_device.h new file mode 100644 index 0000000..95a7087 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/test_app/main/hid_mock_device.h @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +typedef enum { + TUSB_IFACE_COUNT_ONE = 0x00, + TUSB_IFACE_COUNT_TWO = 0x01, + TUSB_IFACE_COUNT_MAX +} tusb_iface_count_t; + +void hid_mock_device(tusb_iface_count_t iface_count); diff --git a/managed_components/espressif__usb_host_hid/test_app/main/test_app_main.c b/managed_components/espressif__usb_host_hid/test_app/main/test_app_main.c new file mode 100644 index 0000000..b25c8df --- /dev/null +++ b/managed_components/espressif__usb_host_hid/test_app/main/test_app_main.c @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "unity.h" +#include "esp_heap_caps.h" + +static size_t before_free_8bit; +static size_t before_free_32bit; + +#define TEST_MEMORY_LEAK_THRESHOLD (-530) +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void app_main(void) +{ + // ____ ___ ___________________ __ __ + // | | \/ _____/\______ \ _/ |_ ____ _______/ |_ + // | | /\_____ \ | | _/ \ __\/ __ \ / ___/\ __\. + // | | / / \ | | \ | | \ ___/ \___ \ | | + // |______/ /_______ / |______ / |__| \___ >____ > |__| + // \/ \/ \/ \/ + printf(" ____ ___ ___________________ __ __ \r\n"); + printf("| | \\/ _____/\\______ \\ _/ |_ ____ _______/ |_ \r\n"); + printf("| | /\\_____ \\ | | _/ \\ __\\/ __ \\ / ___/\\ __\\\r\n"); + printf("| | / / \\ | | \\ | | \\ ___/ \\___ \\ | | \r\n"); + printf("|______/ /_______ / |______ / |__| \\___ >____ > |__| \r\n"); + printf(" \\/ \\/ \\/ \\/ \r\n"); + + UNITY_BEGIN(); + unity_run_menu(); + UNITY_END(); +} + +/* setUp runs before every test */ +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +/* tearDown runs after every test */ +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} diff --git a/managed_components/espressif__usb_host_hid/test_app/main/test_hid_basic.c b/managed_components/espressif__usb_host_hid/test_app/main/test_hid_basic.c new file mode 100644 index 0000000..f25a525 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/test_app/main/test_hid_basic.c @@ -0,0 +1,684 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "esp_private/usb_phy.h" +#include "usb/usb_host.h" + +#include "usb/hid_host.h" +#include "usb/hid_usage_keyboard.h" +#include "usb/hid_usage_mouse.h" + +#include "test_hid_basic.h" +#include "hid_mock_device.h" + +// USB PHY for device discinnection emulation +static usb_phy_handle_t phy_hdl = NULL; + +// Global variable to verify user arg passing through callbacks +static uint32_t user_arg_value = 0x8A53E0A4; // Just a constant renadom number + +// Queue and task for possibility to interact with USB device +// IMPORTANT: Interaction is not possible within device/interface callback +static bool time_to_shutdown = false; +static bool time_to_stop_polling = false; +QueueHandle_t hid_host_test_event_queue; +TaskHandle_t hid_test_task_handle; + +// Multiple tasks testing +static hid_host_device_handle_t global_hdl; +static int test_num_passed; + +static const char *test_hid_sub_class_names[] = { + "NO_SUBCLASS", + "BOOT_INTERFACE", +}; + +static const char *test_hid_proto_names[] = { + "NONE", + "KEYBOARD", + "MOUSE" +}; + +typedef struct { + hid_host_device_handle_t hid_device_handle; + hid_host_driver_event_t event; + void *arg; +} hid_host_test_event_queue_t; + +typedef enum { + HID_HOST_TEST_TOUCH_WAY_ASSERT = 0x00, + HID_HOST_TEST_TOUCH_WAY_SUDDEN_DISCONNECT = 0x01, +} hid_host_test_touch_way_t; + +static void force_conn_state(bool connected, TickType_t delay_ticks) +{ + TEST_ASSERT_NOT_NULL(phy_hdl); + if (delay_ticks > 0) { + //Delay of 0 ticks causes a yield. So skip if delay_ticks is 0. + vTaskDelay(delay_ticks); + } + ESP_ERROR_CHECK(usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN)); +} + +void hid_host_test_interface_callback(hid_host_device_handle_t hid_device_handle, + const hid_host_interface_event_t event, + void *arg) +{ + uint8_t data[64] = { 0 }; + size_t data_length = 0; + hid_host_dev_params_t dev_params; + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(hid_device_handle, &dev_params)); + TEST_ASSERT_EQUAL_PTR_MESSAGE(&user_arg_value, arg, "User argument has lost"); + + switch (event) { + case HID_HOST_INTERFACE_EVENT_INPUT_REPORT: + printf("USB port %d, Interface num %d: ", + dev_params.addr, + dev_params.iface_num); + + hid_host_device_get_raw_input_report_data(hid_device_handle, + data, + 64, + &data_length); + + for (int i = 0; i < data_length; i++) { + printf("%02x ", data[i]); + } + printf("\n"); + break; + case HID_HOST_INTERFACE_EVENT_DISCONNECTED: + printf("USB port %d, iface num %d removed\n", + dev_params.addr, + dev_params.iface_num); + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_close(hid_device_handle) ); + break; + case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR: + printf("USB Host transfer error\n"); + break; + default: + TEST_FAIL_MESSAGE("HID Interface unhandled event"); + break; + } +} + +void hid_host_test_callback(hid_host_device_handle_t hid_device_handle, + const hid_host_driver_event_t event, + void *arg) +{ + hid_host_dev_params_t dev_params; + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(hid_device_handle, &dev_params)); + TEST_ASSERT_EQUAL_PTR_MESSAGE(&user_arg_value, arg, "User argument has lost"); + + switch (event) { + case HID_HOST_DRIVER_EVENT_CONNECTED: + printf("USB port %d, interface %d, '%s', '%s'\n", + dev_params.addr, + dev_params.iface_num, + test_hid_sub_class_names[dev_params.sub_class], + test_hid_proto_names[dev_params.proto]); + + const hid_host_device_config_t dev_config = { + .callback = hid_host_test_interface_callback, + .callback_arg = (void *) &user_arg_value + }; + + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_open(hid_device_handle, &dev_config) ); + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_start(hid_device_handle) ); + + break; + default: + TEST_FAIL_MESSAGE("HID Driver unhandled event"); + break; + } +} + +void hid_host_test_concurrent(hid_host_device_handle_t hid_device_handle, + const hid_host_driver_event_t event, + void *arg) +{ + hid_host_dev_params_t dev_params; + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(hid_device_handle, &dev_params)); + TEST_ASSERT_EQUAL_PTR_MESSAGE(&user_arg_value, arg, "User argument has lost"); + + switch (event) { + case HID_HOST_DRIVER_EVENT_CONNECTED: + printf("USB port %d, interface %d, '%s', '%s'\n", + dev_params.addr, + dev_params.iface_num, + test_hid_sub_class_names[dev_params.sub_class], + test_hid_proto_names[dev_params.proto]); + + const hid_host_device_config_t dev_config = { + .callback = hid_host_test_interface_callback, + .callback_arg = &user_arg_value + }; + + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_open(hid_device_handle, &dev_config) ); + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_start(hid_device_handle) ); + + global_hdl = hid_device_handle; + break; + default: + TEST_FAIL_MESSAGE("HID Driver unhandled event"); + break; + } +} + +void hid_host_test_device_callback_to_queue(hid_host_device_handle_t hid_device_handle, + const hid_host_driver_event_t event, + void *arg) +{ + const hid_host_test_event_queue_t evt_queue = { + .hid_device_handle = hid_device_handle, + .event = event, + .arg = arg + }; + xQueueSend(hid_host_test_event_queue, &evt_queue, 0); +} + +void hid_host_test_requests_callback(hid_host_device_handle_t hid_device_handle, + const hid_host_driver_event_t event, + void *arg) +{ + hid_host_dev_params_t dev_params; + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(hid_device_handle, &dev_params)); + TEST_ASSERT_EQUAL_PTR_MESSAGE(&user_arg_value, arg, "User argument has lost"); + + uint8_t *test_buffer = NULL; // for report descriptor + unsigned int test_length = 0; + uint8_t tmp[10] = { 0 }; // for input report + size_t rep_len = 0; + + switch (event) { + case HID_HOST_DRIVER_EVENT_CONNECTED: + printf("USB port %d, interface %d, '%s', '%s'\n", + dev_params.addr, + dev_params.iface_num, + test_hid_sub_class_names[dev_params.sub_class], + test_hid_proto_names[dev_params.proto]); + + const hid_host_device_config_t dev_config = { + .callback = hid_host_test_interface_callback, + .callback_arg = &user_arg_value + }; + + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_open(hid_device_handle, &dev_config) ); + + // Class device requests + // hid_host_get_report_descriptor + test_buffer = hid_host_get_report_descriptor(hid_device_handle, &test_length); + + TEST_ASSERT_NOT_NULL(test_buffer); + printf("HID Report descriptor length: %d\n", test_length); + + // // HID Device info + hid_host_dev_info_t hid_dev_info; + TEST_ASSERT_EQUAL(ESP_OK, hid_host_get_device_info(hid_device_handle, + &hid_dev_info) ); + + printf("\t VID: 0x%04X\n", hid_dev_info.VID); + printf("\t PID: 0x%04X\n", hid_dev_info.PID); + wprintf(L"\t iProduct: %S \n", hid_dev_info.iProduct); + wprintf(L"\t iManufacturer: %S \n", hid_dev_info.iManufacturer); + wprintf(L"\t iSerialNumber: %S \n", hid_dev_info.iSerialNumber); + + if (dev_params.proto == HID_PROTOCOL_NONE) { + // If Protocol NONE, based on hid1_11.pdf, p.78, all other devices should support + rep_len = sizeof(tmp); + // For testing with ESP32 we used ReportID = 0x01 (Keyboard ReportID) + if (ESP_OK == hid_class_request_get_report(hid_device_handle, + HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len)) { + printf("HID Get Report, type %d, id %d, length: %d:\n", + HID_REPORT_TYPE_INPUT, 0, rep_len); + for (int i = 0; i < rep_len; i++) { + printf("%02X ", tmp[i]); + } + printf("\n"); + } + + rep_len = sizeof(tmp); + // For testing with ESP32 we used ReportID = 0x02 (Mouse ReportID) + if (ESP_OK == hid_class_request_get_report(hid_device_handle, + HID_REPORT_TYPE_INPUT, 0x02, tmp, &rep_len)) { + printf("HID Get Report, type %d, id %d, length: %d:\n", + HID_REPORT_TYPE_INPUT, 0, rep_len); + for (int i = 0; i < rep_len; i++) { + printf("%02X ", tmp[i]); + } + printf("\n"); + } + } else { + // hid_class_request_get_protocol + hid_report_protocol_t proto; + if (ESP_OK == hid_class_request_get_protocol(hid_device_handle, &proto)) { + printf("HID protocol: %d\n", proto); + } + + if (dev_params.proto == HID_PROTOCOL_KEYBOARD) { + uint8_t idle_rate; + // hid_class_request_get_idle + if (ESP_OK == hid_class_request_get_idle(hid_device_handle, + 0, &idle_rate)) { + printf("HID idle rate: %d\n", idle_rate); + } + // hid_class_request_set_idle + if (ESP_OK == hid_class_request_set_idle(hid_device_handle, + 0, 0)) { + printf("HID idle rate set to 0\n"); + } + + // hid_class_request_get_report + rep_len = sizeof(tmp); + if (ESP_OK == hid_class_request_get_report(hid_device_handle, + HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len)) { + printf("HID get report type %d, id %d, length: %d\n", + HID_REPORT_TYPE_INPUT, 0x00, rep_len); + } + + // hid_class_request_set_report + uint8_t rep[1] = { 0x00 }; + if (ESP_OK == hid_class_request_set_report(hid_device_handle, + HID_REPORT_TYPE_OUTPUT, 0x01, rep, 1)) { + printf("HID set report type %d, id %d\n", HID_REPORT_TYPE_OUTPUT, 0x00); + } + } + + if (dev_params.proto == HID_PROTOCOL_MOUSE) { + // hid_class_request_get_report + rep_len = sizeof(tmp); + if (ESP_OK == hid_class_request_get_report(hid_device_handle, + HID_REPORT_TYPE_INPUT, 0x02, tmp, &rep_len)) { + printf("HID get report type %d, id %d, length: %d\n", + HID_REPORT_TYPE_INPUT, 0x00, rep_len); + } + } + + // hid_class_request_set_protocol + if (ESP_OK == hid_class_request_set_protocol(hid_device_handle, + HID_REPORT_PROTOCOL_BOOT)) { + printf("HID protocol change to BOOT: %d\n", proto); + } + } + + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_start(hid_device_handle) ); + break; + default: + TEST_FAIL_MESSAGE("HID Driver unhandled event"); + break; + } +} + +void hid_host_test_task(void *pvParameters) +{ + hid_host_test_event_queue_t evt_queue; + // Create queue + hid_host_test_event_queue = xQueueCreate(10, sizeof(hid_host_test_event_queue_t)); + + // Wait queue + while (!time_to_shutdown) { + if (xQueueReceive(hid_host_test_event_queue, &evt_queue, pdMS_TO_TICKS(50))) { + hid_host_test_requests_callback(evt_queue.hid_device_handle, + evt_queue.event, + evt_queue.arg); + } + } + + xQueueReset(hid_host_test_event_queue); + vQueueDelete(hid_host_test_event_queue); + vTaskDelete(NULL); +} + +void hid_host_test_polling_task(void *pvParameters) +{ + // Wait queue + while (!time_to_stop_polling) { + hid_host_handle_events(portMAX_DELAY); + } + + vTaskDelete(NULL); +} + +static void test_hid_host_device_touch(hid_host_dev_params_t *dev_params, + hid_host_test_touch_way_t touch_way) +{ + uint8_t tmp[10] = { 0 }; // for input report + size_t rep_len = 0; + hid_report_protocol_t proto; + + if (dev_params->proto == HID_PROTOCOL_NONE) { + rep_len = sizeof(tmp); + // For testing with ESP32 we used ReportID = 0x01 (Keyboard ReportID) + if (HID_HOST_TEST_TOUCH_WAY_ASSERT == touch_way) { + TEST_ASSERT_EQUAL(ESP_OK, hid_class_request_get_report(global_hdl, + HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len)); + } else { + hid_class_request_get_report(global_hdl, + HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len); + } + + } else { + // Get Protocol + TEST_ASSERT_EQUAL(ESP_OK, hid_class_request_get_protocol(global_hdl, &proto)); + // Get Report for Keyboard protocol, ReportID = 0x00 (Boot Keyboard ReportID) + if (dev_params->proto == HID_PROTOCOL_KEYBOARD) { + rep_len = sizeof(tmp); + if (HID_HOST_TEST_TOUCH_WAY_ASSERT == touch_way) { + TEST_ASSERT_EQUAL(ESP_OK, hid_class_request_get_report(global_hdl, + HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len)); + } else { + hid_class_request_get_report(global_hdl, + HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len); + } + } + if (dev_params->proto == HID_PROTOCOL_MOUSE) { + rep_len = sizeof(tmp); + if (HID_HOST_TEST_TOUCH_WAY_ASSERT == touch_way) { + TEST_ASSERT_EQUAL(ESP_OK, hid_class_request_get_report(global_hdl, + HID_REPORT_TYPE_INPUT, 0x02, tmp, &rep_len)); + } else { + hid_class_request_get_report(global_hdl, + HID_REPORT_TYPE_INPUT, 0x02, tmp, &rep_len); + } + } + } +} + +#define MULTIPLE_TASKS_TASKS_NUM 10 + +void concurrent_task(void *arg) +{ + uint8_t *test_buffer = NULL; + unsigned int test_length = 0; + hid_host_dev_params_t dev_params; + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(global_hdl, &dev_params)); + + // Get Report descriptor + test_buffer = hid_host_get_report_descriptor(global_hdl, &test_length); + TEST_ASSERT_NOT_NULL(test_buffer); + + test_hid_host_device_touch(&dev_params, HID_HOST_TEST_TOUCH_WAY_ASSERT); + test_num_passed++; + + vTaskDelete(NULL); +} + +void access_task(void *arg) +{ + uint8_t *test_buffer = NULL; + unsigned int test_length = 0; + hid_host_dev_params_t dev_params; + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(global_hdl, &dev_params)); + + // Get Report descriptor + test_buffer = hid_host_get_report_descriptor(global_hdl, &test_length); + TEST_ASSERT_NOT_NULL(test_buffer); + + while (!time_to_shutdown) { + test_hid_host_device_touch(&dev_params, HID_HOST_TEST_TOUCH_WAY_ASSERT/* HID_HOST_TEST_TOUCH_WAY_SUDDEN_DISCONNECT */); + } + + vTaskDelete(NULL); +} + +/** + * @brief Creates MULTIPLE_TASKS_TASKS_NUM to get report descriptor and get protocol from HID device. + * After test_num_passed - is a global static variable that increases during every successful task test. + */ +void test_multiple_tasks_access(void) +{ + // Create tasks that will try to access HID dev with global hdl + for (int i = 0; i < MULTIPLE_TASKS_TASKS_NUM; i++) { + TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(concurrent_task, "HID multi touch", 4096, NULL, i + 3, NULL)); + } + // Wait until all tasks finish + vTaskDelay(pdMS_TO_TICKS(500)); +} + +void test_task_access(void) +{ + // Create task which will be touching the device with control requests, while device is present + TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(access_task, "HID touch", 4096, NULL, 3, NULL)); +} + +/** + * @brief Start USB Host and handle common USB host library events while devices/clients are present + * + * @param[in] arg Main task handle + */ +static void usb_lib_task(void *arg) +{ + // Initialize the internal USB PHY to connect to the USB OTG peripheral. + // We manually install the USB PHY for testing + usb_phy_config_t phy_config = { + .controller = USB_PHY_CTRL_OTG, + .target = USB_PHY_TARGET_INT, + .otg_mode = USB_OTG_MODE_HOST, + .otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_hdl)); + + const usb_host_config_t host_config = { + .skip_phy_setup = true, + .intr_flags = ESP_INTR_FLAG_LEVEL1, + }; + TEST_ASSERT_EQUAL(ESP_OK, usb_host_install(&host_config) ); + printf("USB Host installed\n"); + xTaskNotifyGive(arg); + + bool all_clients_gone = false; + bool all_dev_free = false; + while (!all_clients_gone || !all_dev_free) { + uint32_t event_flags; + usb_host_lib_handle_events(portMAX_DELAY, &event_flags); + + // Release devices once all clients has deregistered + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) { + usb_host_device_free_all(); + printf("USB Event flags: NO_CLIENTS\n"); + all_clients_gone = true; + } + // All devices were removed + if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) { + printf("USB Event flags: ALL_FREE\n"); + all_dev_free = true; + time_to_stop_polling = true; + // Notify that device was being disconnected + xTaskNotifyGive(arg); + } + } + + // Change global flag for all tasks still running + time_to_shutdown = true; + + // Clean up USB Host + vTaskDelay(10); // Short delay to allow clients clean-up + TEST_ASSERT_EQUAL(ESP_OK, usb_host_uninstall()); + TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_hdl)); //Tear down USB PHY + phy_hdl = NULL; + vTaskDelete(NULL); +} + +// ----------------------- Public ------------------------- + +/** + * @brief Setups HID testing + * + * - Create USB lib task + * - Install HID Host driver + */ +void test_hid_setup(hid_host_driver_event_cb_t device_callback, + hid_test_event_handle_t hid_test_event_handle) +{ + TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(usb_lib_task, + "usb_events", + 4096, + xTaskGetCurrentTaskHandle(), + 2, NULL, 0)); + // Wait for notification from usb_lib_task + ulTaskNotifyTake(false, 1000); + + // HID host driver config + const hid_host_driver_config_t hid_host_driver_config = { + .create_background_task = (hid_test_event_handle == HID_TEST_EVENT_HANDLE_IN_DRIVER) + ? true + : false, + .task_priority = 5, + .stack_size = 4096, + .core_id = 0, + .callback = device_callback, + .callback_arg = (void *) &user_arg_value + }; + + TEST_ASSERT_EQUAL(ESP_OK, hid_host_install(&hid_host_driver_config) ); +} + +/** + * @brief Teardowns HID testing + * - Disconnect connected USB device manually by PHY triggering + * - Wait for USB lib task was closed + * - Uninstall HID Host driver + * - Clear the notification value to 0 + * - Short delay to allow task to be cleaned up + */ +void test_hid_teardown(void) +{ + force_conn_state(false, pdMS_TO_TICKS(1000)); + vTaskDelay(50); + TEST_ASSERT_EQUAL(ESP_OK, hid_host_uninstall() ); + ulTaskNotifyValueClear(NULL, 1); + vTaskDelay(20); +} + +// ------------------------- HID Test ------------------------------------------ +static void test_setup_hid_task(void) +{ + // Task is working until the devices are gone + time_to_shutdown = false; + // Create process + TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(&hid_host_test_task, + "hid_task", + 4 * 1024, + NULL, + 3, + &hid_test_task_handle)); +} + +static void test_setup_hid_polling_task(void) +{ + time_to_stop_polling = false; + + TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(&hid_host_test_polling_task, + "hid_task_polling", + 4 * 1024, + NULL, 2, NULL)); +} + +TEST_CASE("memory_leakage", "[hid_host]") +{ + // Install USB and HID driver with the regular 'hid_host_test_callback' + test_hid_setup(hid_host_test_callback, HID_TEST_EVENT_HANDLE_IN_DRIVER); + // Tear down test + test_hid_teardown(); + // Verify the memory leackage during test environment tearDown() +} + +TEST_CASE("multiple_task_access", "[hid_host]") +{ + // Install USB and HID driver with 'hid_host_test_concurrent' + test_hid_setup(hid_host_test_concurrent, HID_TEST_EVENT_HANDLE_IN_DRIVER); + // Wait for USB device appearing for 250 msec + vTaskDelay(250); + // Refresh the num passed test value + test_num_passed = 0; + // Start multiple task access to USB device with control requests + test_multiple_tasks_access(); + // Tear down test + test_hid_teardown(); + // Verify how much tests was done + TEST_ASSERT_EQUAL(MULTIPLE_TASKS_TASKS_NUM, test_num_passed); + // Verify the memory leackage during test environment tearDown() +} + +TEST_CASE("class_specific_requests", "[hid_host]") +{ + // Create external HID events task + test_setup_hid_task(); + // Install USB and HID driver with 'hid_host_test_device_callback_to_queue' + test_hid_setup(hid_host_test_device_callback_to_queue, HID_TEST_EVENT_HANDLE_IN_DRIVER); + // All specific control requests will be verified during device connection callback 'hid_host_test_requests_callback' + // Wait for test completed for 250 ms + vTaskDelay(250); + // Tear down test + test_hid_teardown(); + // Verify the memory leackage during test environment tearDown() +} + +TEST_CASE("class_specific_requests_with_external_polling", "[hid_host]") +{ + // Create external HID events task + test_setup_hid_task(); + // Install USB and HID driver with 'hid_host_test_device_callback_to_queue' + test_hid_setup(hid_host_test_device_callback_to_queue, HID_TEST_EVENT_HANDLE_EXTERNAL); + // Create HID Driver events polling task + test_setup_hid_polling_task(); + // All specific control requests will be verified during device connection callback 'hid_host_test_requests_callback' + // Wait for test completed for 250 ms + vTaskDelay(250); + // Tear down test + test_hid_teardown(); + // Verify the memory leackage during test environment tearDown() +} + +TEST_CASE("class_specific_requests_with_external_polling_without_polling", "[hid_host]") +{ + // Create external HID events task + test_setup_hid_task(); + // Install USB and HID driver with 'hid_host_test_device_callback_to_queue' + test_hid_setup(hid_host_test_device_callback_to_queue, HID_TEST_EVENT_HANDLE_EXTERNAL); + // Do not create HID Driver events polling task to eliminate events polling + // ... + // Wait for 250 ms + vTaskDelay(250); + // Tear down test + test_hid_teardown(); + // Verify the memory leackage during test environment tearDown() +} + +TEST_CASE("sudden_disconnect", "[hid_host]") +{ + // Install USB and HID driver with 'hid_host_test_concurrent' + test_hid_setup(hid_host_test_concurrent, HID_TEST_EVENT_HANDLE_IN_DRIVER); + // Wait for USB device appearing for 250 msec + vTaskDelay(250); + // Start task to access USB device with control requests + test_task_access(); + // Tear down test during thr task_access stress the HID device + test_hid_teardown(); +} + +TEST_CASE("mock_hid_device", "[hid_device][ignore]") +{ + hid_mock_device(TUSB_IFACE_COUNT_ONE); + while (1) { + vTaskDelay(10); + } +} + +TEST_CASE("mock_hid_device_with_two_ifaces", "[hid_device2][ignore]") +{ + hid_mock_device(TUSB_IFACE_COUNT_TWO); + while (1) { + vTaskDelay(10); + } +} diff --git a/managed_components/espressif__usb_host_hid/test_app/main/test_hid_basic.h b/managed_components/espressif__usb_host_hid/test_app/main/test_hid_basic.h new file mode 100644 index 0000000..7b4454a --- /dev/null +++ b/managed_components/espressif__usb_host_hid/test_app/main/test_hid_basic.h @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "usb/hid_host.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + HID_TEST_EVENT_HANDLE_IN_DRIVER = 0, + HID_TEST_EVENT_HANDLE_EXTERNAL +} hid_test_event_handle_t; + +// ------------------------ HID Test ------------------------------------------- + +void test_hid_setup(hid_host_driver_event_cb_t device_callback, + hid_test_event_handle_t hid_test_event_handle); + +void test_hid_teardown(void); + +#ifdef __cplusplus +} +#endif //__cplusplus diff --git a/managed_components/espressif__usb_host_hid/test_app/main/test_hid_err_handling.c b/managed_components/espressif__usb_host_hid/test_app/main/test_hid_err_handling.c new file mode 100644 index 0000000..d24d869 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/test_app/main/test_hid_err_handling.c @@ -0,0 +1,213 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "usb/hid_host.h" + +#include "test_hid_basic.h" + +// ----------------------- Private ------------------------- +/** + * @brief USB HID Host interface callback. + * + * Handle close event only. + * + * @param[in] event HID Host device event + * @param[in] arg Pointer to arguments, does not used + * + */ +static void test_hid_host_interface_event_close(hid_host_device_handle_t hid_device_handle, + const hid_host_interface_event_t event, + void *arg) +{ + switch (event) { + case HID_HOST_INTERFACE_EVENT_INPUT_REPORT: + case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR: + break; + case HID_HOST_INTERFACE_EVENT_DISCONNECTED: + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_close(hid_device_handle) ); + break; + } +} + +/** + * @brief USB HID Host event callback stub. + * + * Does not handle anything. + * + * @param[in] event HID Host device event + * @param[in] arg Pointer to arguments, does not used + * + */ +static void test_hid_host_event_callback_stub(hid_host_device_handle_t hid_device_handle, + const hid_host_driver_event_t event, + void *arg) +{ + if (event == HID_HOST_DRIVER_EVENT_CONNECTED) { + // Device connected + } +} + +/** + * @brief USB HID Host event callback. + * + * Handle connected event and open a device. + * + * @param[in] event HID Host device event + * @param[in] arg Pointer to arguments, does not used + * + */ +static void test_hid_host_event_callback_open(hid_host_device_handle_t hid_device_handle, + const hid_host_driver_event_t event, + void *arg) +{ + if (event == HID_HOST_DRIVER_EVENT_CONNECTED) { + const hid_host_device_config_t dev_config = { + .callback = test_hid_host_interface_event_close, + .callback_arg = NULL + }; + + TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_open(hid_device_handle, &dev_config) ); + } +} + +// Install HID driver without USB Host and without configuration +static void test_install_hid_driver_without_config(void) +{ + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(NULL)); +} + +// Install HID driver without USB Host and with configuration +static void test_install_hid_driver_with_wrong_config(void) +{ + const hid_host_driver_config_t hid_host_config_callback_null = { + .create_background_task = true, + .task_priority = 5, + .stack_size = 4096, + .core_id = 0, + .callback = NULL, /* error expected */ + .callback_arg = NULL + }; + + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(&hid_host_config_callback_null)); + + const hid_host_driver_config_t hid_host_config_stack_size_null = { + .create_background_task = true, + .task_priority = 5, + .stack_size = 0, /* error expected */ + .core_id = 0, + .callback = test_hid_host_event_callback_stub, + .callback_arg = NULL + }; + + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(&hid_host_config_stack_size_null)); + + const hid_host_driver_config_t hid_host_config_task_priority_null = { + .create_background_task = true, + .task_priority = 0,/* error expected */ + .stack_size = 4096, + .core_id = 0, + .callback = test_hid_host_event_callback_stub, + .callback_arg = NULL + }; + + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(&hid_host_config_task_priority_null)); + + const hid_host_driver_config_t hid_host_config_correct = { + .create_background_task = true, + .task_priority = 5, + .stack_size = 4096, + .core_id = 0, + .callback = test_hid_host_event_callback_stub, + .callback_arg = NULL + }; + // Invalid state without USB Host installed + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hid_host_install(&hid_host_config_correct)); +} + +void test_interface_callback_handler(hid_host_device_handle_t hid_device_handle, + const hid_host_interface_event_t event, + void *arg) +{ + // ... +} + +// Open device without installed driver +static void test_claim_interface_without_driver(void) +{ + hid_host_device_handle_t hid_dev_handle = NULL; + + const hid_host_device_config_t dev_config = { + .callback = test_interface_callback_handler, + .callback_arg = NULL + }; + + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, + hid_host_device_open(hid_dev_handle, &dev_config) ); +} + +static void test_install_hid_driver_when_already_installed(void) +{ + // Install USB and HID driver with the stub test_hid_host_event_callback_stub + test_hid_setup(test_hid_host_event_callback_stub, HID_TEST_EVENT_HANDLE_IN_DRIVER); + // Try to install HID driver again + const hid_host_driver_config_t hid_host_config = { + .create_background_task = true, + .task_priority = 5, + .stack_size = 4096, + .core_id = 0, + .callback = test_hid_host_event_callback_stub, + .callback_arg = NULL + }; + // Verify error code + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hid_host_install(&hid_host_config)); + // Tear down test + test_hid_teardown(); +} + +static void test_uninstall_hid_driver_while_device_was_not_opened(void) +{ + // Install USB and HID driver with the stub test_hid_host_event_callback_stub + test_hid_setup(test_hid_host_event_callback_stub, HID_TEST_EVENT_HANDLE_IN_DRIVER); + // Tear down test + test_hid_teardown(); +} + +static void test_uninstall_hid_driver_while_device_is_present(void) +{ + // Install USB and HID driver with the stub test_hid_host_event_callback_stub + test_hid_setup(test_hid_host_event_callback_open, HID_TEST_EVENT_HANDLE_IN_DRIVER); + // Wait for USB device appearing for 250 msec + vTaskDelay(250); + // Uninstall HID Driver while device is still connected and verify a result + printf("HID Driver uninstall attempt while HID Device is still present ...\n"); + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hid_host_uninstall()); + // Tear down test + test_hid_teardown(); +} + +// ----------------------- Public -------------------------- + +/** + * @brief HID Error handling test + * + * There are multiple erroneous scenarios checked in this test. + * + */ +TEST_CASE("error_handling", "[hid_host]") +{ + test_install_hid_driver_without_config(); + test_install_hid_driver_with_wrong_config(); + test_claim_interface_without_driver(); + test_install_hid_driver_when_already_installed(); + test_uninstall_hid_driver_while_device_was_not_opened(); + test_uninstall_hid_driver_while_device_is_present(); +} diff --git a/managed_components/espressif__usb_host_hid/test_app/pytest_usb_host_hid.py b/managed_components/espressif__usb_host_hid/test_app/pytest_usb_host_hid.py new file mode 100644 index 0000000..c4b26d8 --- /dev/null +++ b/managed_components/espressif__usb_host_hid/test_app/pytest_usb_host_hid.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +from typing import Tuple + +import pytest +from pytest_embedded_idf.dut import IdfDut + + +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.usb_host +@pytest.mark.parametrize('count', [ + 2, +], indirect=True) +def test_usb_host_hid(dut: Tuple[IdfDut, IdfDut]) -> None: + device = dut[0] + host = dut[1] + + # 3.1 Prepare USB device with one Interface for HID tests + device.expect_exact('Press ENTER to see the list of tests.') + device.write('[hid_device]') + device.expect_exact('HID mock device with 1xInterface (Protocol=None) has been started') + + # 3.2 Run HID tests + host.run_all_single_board_cases(group='hid_host') + + # 3.3 Prepare USB device with two Interfaces for HID tests + device.serial.hard_reset() + device.expect_exact('Press ENTER to see the list of tests.') + device.write('[hid_device2]') + device.expect_exact('HID mock device with 2xInterfaces (Protocol=BootKeyboard, Protocol=BootMouse) has been started') + + # 3.4 Run HID tests + host.run_all_single_board_cases(group='hid_host') diff --git a/managed_components/espressif__usb_host_hid/test_app/sdkconfig.defaults b/managed_components/espressif__usb_host_hid/test_app/sdkconfig.defaults new file mode 100644 index 0000000..7d7d1ce --- /dev/null +++ b/managed_components/espressif__usb_host_hid/test_app/sdkconfig.defaults @@ -0,0 +1,19 @@ +# Configure TinyUSB, it will be used to mock USB devices +CONFIG_TINYUSB=y +CONFIG_TINYUSB_MSC_ENABLED=n +CONFIG_TINYUSB_CDC_ENABLED=n +CONFIG_TINYUSB_CDC_COUNT=0 +CONFIG_TINYUSB_HID_COUNT=2 + +# Disable watchdogs, they'd get triggered during unity interactive menu +CONFIG_ESP_INT_WDT=n +CONFIG_ESP_TASK_WDT=n + +# Run-time checks of Heap and Stack +CONFIG_HEAP_POISONING_COMPREHENSIVE=y +CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y +CONFIG_COMPILER_STACK_CHECK=y + +CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y + +CONFIG_COMPILER_CXX_EXCEPTIONS=y diff --git a/sdkconfig b/sdkconfig new file mode 100644 index 0000000..a821b99 --- /dev/null +++ b/sdkconfig @@ -0,0 +1,2015 @@ +# +# Automatically generated file. DO NOT EDIT. +# Espressif IoT Development Framework (ESP-IDF) 5.4.1 Project Configuration +# +CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 +CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 +CONFIG_SOC_ADC_SUPPORTED=y +CONFIG_SOC_UART_SUPPORTED=y +CONFIG_SOC_PCNT_SUPPORTED=y +CONFIG_SOC_PHY_SUPPORTED=y +CONFIG_SOC_WIFI_SUPPORTED=y +CONFIG_SOC_TWAI_SUPPORTED=y +CONFIG_SOC_GDMA_SUPPORTED=y +CONFIG_SOC_AHB_GDMA_SUPPORTED=y +CONFIG_SOC_GPTIMER_SUPPORTED=y +CONFIG_SOC_LCDCAM_SUPPORTED=y +CONFIG_SOC_LCDCAM_I80_LCD_SUPPORTED=y +CONFIG_SOC_LCDCAM_RGB_LCD_SUPPORTED=y +CONFIG_SOC_MCPWM_SUPPORTED=y +CONFIG_SOC_DEDICATED_GPIO_SUPPORTED=y +CONFIG_SOC_CACHE_SUPPORT_WRAP=y +CONFIG_SOC_ULP_SUPPORTED=y +CONFIG_SOC_ULP_FSM_SUPPORTED=y +CONFIG_SOC_RISCV_COPROC_SUPPORTED=y +CONFIG_SOC_BT_SUPPORTED=y +CONFIG_SOC_USB_OTG_SUPPORTED=y +CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED=y +CONFIG_SOC_CCOMP_TIMER_SUPPORTED=y +CONFIG_SOC_ASYNC_MEMCPY_SUPPORTED=y +CONFIG_SOC_SUPPORTS_SECURE_DL_MODE=y +CONFIG_SOC_EFUSE_KEY_PURPOSE_FIELD=y +CONFIG_SOC_EFUSE_SUPPORTED=y +CONFIG_SOC_SDMMC_HOST_SUPPORTED=y +CONFIG_SOC_RTC_FAST_MEM_SUPPORTED=y +CONFIG_SOC_RTC_SLOW_MEM_SUPPORTED=y +CONFIG_SOC_RTC_MEM_SUPPORTED=y +CONFIG_SOC_PSRAM_DMA_CAPABLE=y +CONFIG_SOC_XT_WDT_SUPPORTED=y +CONFIG_SOC_I2S_SUPPORTED=y +CONFIG_SOC_RMT_SUPPORTED=y +CONFIG_SOC_SDM_SUPPORTED=y +CONFIG_SOC_GPSPI_SUPPORTED=y +CONFIG_SOC_LEDC_SUPPORTED=y +CONFIG_SOC_I2C_SUPPORTED=y +CONFIG_SOC_SYSTIMER_SUPPORTED=y +CONFIG_SOC_SUPPORT_COEXISTENCE=y +CONFIG_SOC_TEMP_SENSOR_SUPPORTED=y +CONFIG_SOC_AES_SUPPORTED=y +CONFIG_SOC_MPI_SUPPORTED=y +CONFIG_SOC_SHA_SUPPORTED=y +CONFIG_SOC_HMAC_SUPPORTED=y +CONFIG_SOC_DIG_SIGN_SUPPORTED=y +CONFIG_SOC_FLASH_ENC_SUPPORTED=y +CONFIG_SOC_SECURE_BOOT_SUPPORTED=y +CONFIG_SOC_MEMPROT_SUPPORTED=y +CONFIG_SOC_TOUCH_SENSOR_SUPPORTED=y +CONFIG_SOC_BOD_SUPPORTED=y +CONFIG_SOC_CLK_TREE_SUPPORTED=y +CONFIG_SOC_MPU_SUPPORTED=y +CONFIG_SOC_WDT_SUPPORTED=y +CONFIG_SOC_SPI_FLASH_SUPPORTED=y +CONFIG_SOC_RNG_SUPPORTED=y +CONFIG_SOC_LIGHT_SLEEP_SUPPORTED=y +CONFIG_SOC_DEEP_SLEEP_SUPPORTED=y +CONFIG_SOC_LP_PERIPH_SHARE_INTERRUPT=y +CONFIG_SOC_PM_SUPPORTED=y +CONFIG_SOC_SIMD_INSTRUCTION_SUPPORTED=y +CONFIG_SOC_XTAL_SUPPORT_40M=y +CONFIG_SOC_APPCPU_HAS_CLOCK_GATING_BUG=y +CONFIG_SOC_ADC_RTC_CTRL_SUPPORTED=y +CONFIG_SOC_ADC_DIG_CTRL_SUPPORTED=y +CONFIG_SOC_ADC_ARBITER_SUPPORTED=y +CONFIG_SOC_ADC_DIG_IIR_FILTER_SUPPORTED=y +CONFIG_SOC_ADC_MONITOR_SUPPORTED=y +CONFIG_SOC_ADC_DMA_SUPPORTED=y +CONFIG_SOC_ADC_PERIPH_NUM=2 +CONFIG_SOC_ADC_MAX_CHANNEL_NUM=10 +CONFIG_SOC_ADC_ATTEN_NUM=4 +CONFIG_SOC_ADC_DIGI_CONTROLLER_NUM=2 +CONFIG_SOC_ADC_PATT_LEN_MAX=24 +CONFIG_SOC_ADC_DIGI_MIN_BITWIDTH=12 +CONFIG_SOC_ADC_DIGI_MAX_BITWIDTH=12 +CONFIG_SOC_ADC_DIGI_RESULT_BYTES=4 +CONFIG_SOC_ADC_DIGI_DATA_BYTES_PER_CONV=4 +CONFIG_SOC_ADC_DIGI_IIR_FILTER_NUM=2 +CONFIG_SOC_ADC_DIGI_MONITOR_NUM=2 +CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH=83333 +CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW=611 +CONFIG_SOC_ADC_RTC_MIN_BITWIDTH=12 +CONFIG_SOC_ADC_RTC_MAX_BITWIDTH=12 +CONFIG_SOC_ADC_CALIBRATION_V1_SUPPORTED=y +CONFIG_SOC_ADC_SELF_HW_CALI_SUPPORTED=y +CONFIG_SOC_ADC_SHARED_POWER=y +CONFIG_SOC_APB_BACKUP_DMA=y +CONFIG_SOC_BROWNOUT_RESET_SUPPORTED=y +CONFIG_SOC_CACHE_WRITEBACK_SUPPORTED=y +CONFIG_SOC_CACHE_FREEZE_SUPPORTED=y +CONFIG_SOC_CPU_CORES_NUM=2 +CONFIG_SOC_CPU_INTR_NUM=32 +CONFIG_SOC_CPU_HAS_FPU=y +CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y +CONFIG_SOC_CPU_BREAKPOINTS_NUM=2 +CONFIG_SOC_CPU_WATCHPOINTS_NUM=2 +CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=64 +CONFIG_SOC_SIMD_PREFERRED_DATA_ALIGNMENT=16 +CONFIG_SOC_DS_SIGNATURE_MAX_BIT_LEN=4096 +CONFIG_SOC_DS_KEY_PARAM_MD_IV_LENGTH=16 +CONFIG_SOC_DS_KEY_CHECK_MAX_WAIT_US=1100 +CONFIG_SOC_AHB_GDMA_VERSION=1 +CONFIG_SOC_GDMA_NUM_GROUPS_MAX=1 +CONFIG_SOC_GDMA_PAIRS_PER_GROUP=5 +CONFIG_SOC_GDMA_PAIRS_PER_GROUP_MAX=5 +CONFIG_SOC_AHB_GDMA_SUPPORT_PSRAM=y +CONFIG_SOC_GPIO_PORT=1 +CONFIG_SOC_GPIO_PIN_COUNT=49 +CONFIG_SOC_GPIO_SUPPORT_PIN_GLITCH_FILTER=y +CONFIG_SOC_GPIO_FILTER_CLK_SUPPORT_APB=y +CONFIG_SOC_GPIO_SUPPORT_RTC_INDEPENDENT=y +CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y +CONFIG_SOC_GPIO_VALID_GPIO_MASK=0x1FFFFFFFFFFFF +CONFIG_SOC_GPIO_IN_RANGE_MAX=48 +CONFIG_SOC_GPIO_OUT_RANGE_MAX=48 +CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0x0001FFFFFC000000 +CONFIG_SOC_GPIO_CLOCKOUT_BY_IO_MUX=y +CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=3 +CONFIG_SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP=y +CONFIG_SOC_DEDIC_GPIO_OUT_CHANNELS_NUM=8 +CONFIG_SOC_DEDIC_GPIO_IN_CHANNELS_NUM=8 +CONFIG_SOC_DEDIC_GPIO_OUT_AUTO_ENABLE=y +CONFIG_SOC_I2C_NUM=2 +CONFIG_SOC_HP_I2C_NUM=2 +CONFIG_SOC_I2C_FIFO_LEN=32 +CONFIG_SOC_I2C_CMD_REG_NUM=8 +CONFIG_SOC_I2C_SUPPORT_SLAVE=y +CONFIG_SOC_I2C_SUPPORT_HW_CLR_BUS=y +CONFIG_SOC_I2C_SUPPORT_XTAL=y +CONFIG_SOC_I2C_SUPPORT_RTC=y +CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR=y +CONFIG_SOC_I2C_SLAVE_SUPPORT_BROADCAST=y +CONFIG_SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS=y +CONFIG_SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE=y +CONFIG_SOC_I2S_NUM=2 +CONFIG_SOC_I2S_HW_VERSION_2=y +CONFIG_SOC_I2S_SUPPORTS_XTAL=y +CONFIG_SOC_I2S_SUPPORTS_PLL_F160M=y +CONFIG_SOC_I2S_SUPPORTS_PCM=y +CONFIG_SOC_I2S_SUPPORTS_PDM=y +CONFIG_SOC_I2S_SUPPORTS_PDM_TX=y +CONFIG_SOC_I2S_PDM_MAX_TX_LINES=2 +CONFIG_SOC_I2S_SUPPORTS_PDM_RX=y +CONFIG_SOC_I2S_PDM_MAX_RX_LINES=4 +CONFIG_SOC_I2S_SUPPORTS_TDM=y +CONFIG_SOC_LEDC_SUPPORT_APB_CLOCK=y +CONFIG_SOC_LEDC_SUPPORT_XTAL_CLOCK=y +CONFIG_SOC_LEDC_TIMER_NUM=4 +CONFIG_SOC_LEDC_CHANNEL_NUM=8 +CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=14 +CONFIG_SOC_LEDC_SUPPORT_FADE_STOP=y +CONFIG_SOC_MCPWM_GROUPS=2 +CONFIG_SOC_MCPWM_TIMERS_PER_GROUP=3 +CONFIG_SOC_MCPWM_OPERATORS_PER_GROUP=3 +CONFIG_SOC_MCPWM_COMPARATORS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_GENERATORS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_TRIGGERS_PER_OPERATOR=2 +CONFIG_SOC_MCPWM_GPIO_FAULTS_PER_GROUP=3 +CONFIG_SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP=y +CONFIG_SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER=3 +CONFIG_SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP=3 +CONFIG_SOC_MCPWM_SWSYNC_CAN_PROPAGATE=y +CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=1 +CONFIG_SOC_MMU_PERIPH_NUM=1 +CONFIG_SOC_PCNT_GROUPS=1 +CONFIG_SOC_PCNT_UNITS_PER_GROUP=4 +CONFIG_SOC_PCNT_CHANNELS_PER_UNIT=2 +CONFIG_SOC_PCNT_THRES_POINT_PER_UNIT=2 +CONFIG_SOC_RMT_GROUPS=1 +CONFIG_SOC_RMT_TX_CANDIDATES_PER_GROUP=4 +CONFIG_SOC_RMT_RX_CANDIDATES_PER_GROUP=4 +CONFIG_SOC_RMT_CHANNELS_PER_GROUP=8 +CONFIG_SOC_RMT_MEM_WORDS_PER_CHANNEL=48 +CONFIG_SOC_RMT_SUPPORT_RX_PINGPONG=y +CONFIG_SOC_RMT_SUPPORT_RX_DEMODULATION=y +CONFIG_SOC_RMT_SUPPORT_TX_ASYNC_STOP=y +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_COUNT=y +CONFIG_SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP=y +CONFIG_SOC_RMT_SUPPORT_TX_SYNCHRO=y +CONFIG_SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY=y +CONFIG_SOC_RMT_SUPPORT_XTAL=y +CONFIG_SOC_RMT_SUPPORT_RC_FAST=y +CONFIG_SOC_RMT_SUPPORT_APB=y +CONFIG_SOC_RMT_SUPPORT_DMA=y +CONFIG_SOC_LCD_I80_SUPPORTED=y +CONFIG_SOC_LCD_RGB_SUPPORTED=y +CONFIG_SOC_LCD_I80_BUSES=1 +CONFIG_SOC_LCD_RGB_PANELS=1 +CONFIG_SOC_LCD_I80_BUS_WIDTH=16 +CONFIG_SOC_LCD_RGB_DATA_WIDTH=16 +CONFIG_SOC_LCD_SUPPORT_RGB_YUV_CONV=y +CONFIG_SOC_LCDCAM_I80_NUM_BUSES=1 +CONFIG_SOC_LCDCAM_I80_BUS_WIDTH=16 +CONFIG_SOC_LCDCAM_RGB_NUM_PANELS=1 +CONFIG_SOC_LCDCAM_RGB_DATA_WIDTH=16 +CONFIG_SOC_RTC_CNTL_CPU_PD_DMA_BUS_WIDTH=128 +CONFIG_SOC_RTC_CNTL_CPU_PD_REG_FILE_NUM=549 +CONFIG_SOC_RTC_CNTL_TAGMEM_PD_DMA_BUS_WIDTH=128 +CONFIG_SOC_RTCIO_PIN_COUNT=22 +CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y +CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y +CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y +CONFIG_SOC_LP_IO_CLOCK_IS_INDEPENDENT=y +CONFIG_SOC_SDM_GROUPS=y +CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8 +CONFIG_SOC_SDM_CLK_SUPPORT_APB=y +CONFIG_SOC_SPI_PERIPH_NUM=3 +CONFIG_SOC_SPI_MAX_CS_NUM=6 +CONFIG_SOC_SPI_MAXIMUM_BUFFER_SIZE=64 +CONFIG_SOC_SPI_SUPPORT_DDRCLK=y +CONFIG_SOC_SPI_SLAVE_SUPPORT_SEG_TRANS=y +CONFIG_SOC_SPI_SUPPORT_CD_SIG=y +CONFIG_SOC_SPI_SUPPORT_CONTINUOUS_TRANS=y +CONFIG_SOC_SPI_SUPPORT_SLAVE_HD_VER2=y +CONFIG_SOC_SPI_SUPPORT_CLK_APB=y +CONFIG_SOC_SPI_SUPPORT_CLK_XTAL=y +CONFIG_SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUT=y +CONFIG_SOC_MEMSPI_IS_INDEPENDENT=y +CONFIG_SOC_SPI_MAX_PRE_DIVIDER=16 +CONFIG_SOC_SPI_SUPPORT_OCT=y +CONFIG_SOC_SPI_SCT_SUPPORTED=y +CONFIG_SOC_SPI_SCT_REG_NUM=14 +CONFIG_SOC_SPI_SCT_BUFFER_NUM_MAX=y +CONFIG_SOC_SPI_SCT_CONF_BITLEN_MAX=0x3FFFA +CONFIG_SOC_MEMSPI_SRC_FREQ_120M=y +CONFIG_SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED=y +CONFIG_SOC_MEMSPI_SRC_FREQ_40M_SUPPORTED=y +CONFIG_SOC_MEMSPI_SRC_FREQ_20M_SUPPORTED=y +CONFIG_SOC_SPIRAM_SUPPORTED=y +CONFIG_SOC_SPIRAM_XIP_SUPPORTED=y +CONFIG_SOC_SYSTIMER_COUNTER_NUM=2 +CONFIG_SOC_SYSTIMER_ALARM_NUM=3 +CONFIG_SOC_SYSTIMER_BIT_WIDTH_LO=32 +CONFIG_SOC_SYSTIMER_BIT_WIDTH_HI=20 +CONFIG_SOC_SYSTIMER_FIXED_DIVIDER=y +CONFIG_SOC_SYSTIMER_INT_LEVEL=y +CONFIG_SOC_SYSTIMER_ALARM_MISS_COMPENSATE=y +CONFIG_SOC_TIMER_GROUPS=2 +CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2 +CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=54 +CONFIG_SOC_TIMER_GROUP_SUPPORT_XTAL=y +CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y +CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 +CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32 +CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16 +CONFIG_SOC_TOUCH_SENSOR_VERSION=2 +CONFIG_SOC_TOUCH_SENSOR_NUM=15 +CONFIG_SOC_TOUCH_SUPPORT_SLEEP_WAKEUP=y +CONFIG_SOC_TOUCH_SUPPORT_WATERPROOF=y +CONFIG_SOC_TOUCH_SUPPORT_PROX_SENSING=y +CONFIG_SOC_TOUCH_PROXIMITY_CHANNEL_NUM=3 +CONFIG_SOC_TOUCH_PROXIMITY_MEAS_DONE_SUPPORTED=y +CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=1 +CONFIG_SOC_TWAI_CONTROLLER_NUM=1 +CONFIG_SOC_TWAI_CLK_SUPPORT_APB=y +CONFIG_SOC_TWAI_BRP_MIN=2 +CONFIG_SOC_TWAI_BRP_MAX=16384 +CONFIG_SOC_TWAI_SUPPORTS_RX_STATUS=y +CONFIG_SOC_UART_NUM=3 +CONFIG_SOC_UART_HP_NUM=3 +CONFIG_SOC_UART_FIFO_LEN=128 +CONFIG_SOC_UART_BITRATE_MAX=5000000 +CONFIG_SOC_UART_SUPPORT_FSM_TX_WAIT_SEND=y +CONFIG_SOC_UART_SUPPORT_WAKEUP_INT=y +CONFIG_SOC_UART_SUPPORT_APB_CLK=y +CONFIG_SOC_UART_SUPPORT_RTC_CLK=y +CONFIG_SOC_UART_SUPPORT_XTAL_CLK=y +CONFIG_SOC_USB_OTG_PERIPH_NUM=1 +CONFIG_SOC_SHA_DMA_MAX_BUFFER_SIZE=3968 +CONFIG_SOC_SHA_SUPPORT_DMA=y +CONFIG_SOC_SHA_SUPPORT_RESUME=y +CONFIG_SOC_SHA_GDMA=y +CONFIG_SOC_SHA_SUPPORT_SHA1=y +CONFIG_SOC_SHA_SUPPORT_SHA224=y +CONFIG_SOC_SHA_SUPPORT_SHA256=y +CONFIG_SOC_SHA_SUPPORT_SHA384=y +CONFIG_SOC_SHA_SUPPORT_SHA512=y +CONFIG_SOC_SHA_SUPPORT_SHA512_224=y +CONFIG_SOC_SHA_SUPPORT_SHA512_256=y +CONFIG_SOC_SHA_SUPPORT_SHA512_T=y +CONFIG_SOC_MPI_MEM_BLOCKS_NUM=4 +CONFIG_SOC_MPI_OPERATIONS_NUM=3 +CONFIG_SOC_RSA_MAX_BIT_LEN=4096 +CONFIG_SOC_AES_SUPPORT_DMA=y +CONFIG_SOC_AES_GDMA=y +CONFIG_SOC_AES_SUPPORT_AES_128=y +CONFIG_SOC_AES_SUPPORT_AES_256=y +CONFIG_SOC_PM_SUPPORT_EXT0_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_EXT_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_WIFI_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_BT_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_CPU_PD=y +CONFIG_SOC_PM_SUPPORT_TAGMEM_PD=y +CONFIG_SOC_PM_SUPPORT_RTC_PERIPH_PD=y +CONFIG_SOC_PM_SUPPORT_RC_FAST_PD=y +CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y +CONFIG_SOC_PM_SUPPORT_MAC_BB_PD=y +CONFIG_SOC_PM_SUPPORT_MODEM_PD=y +CONFIG_SOC_CONFIGURABLE_VDDSDIO_SUPPORTED=y +CONFIG_SOC_PM_SUPPORT_DEEPSLEEP_CHECK_STUB_ONLY=y +CONFIG_SOC_PM_CPU_RETENTION_BY_RTCCNTL=y +CONFIG_SOC_PM_MODEM_RETENTION_BY_BACKUPDMA=y +CONFIG_SOC_PM_MODEM_PD_BY_SW=y +CONFIG_SOC_CLK_RC_FAST_D256_SUPPORTED=y +CONFIG_SOC_RTC_SLOW_CLK_SUPPORT_RC_FAST_D256=y +CONFIG_SOC_CLK_RC_FAST_SUPPORT_CALIBRATION=y +CONFIG_SOC_CLK_XTAL32K_SUPPORTED=y +CONFIG_SOC_EFUSE_DIS_DOWNLOAD_ICACHE=y +CONFIG_SOC_EFUSE_DIS_DOWNLOAD_DCACHE=y +CONFIG_SOC_EFUSE_HARD_DIS_JTAG=y +CONFIG_SOC_EFUSE_DIS_USB_JTAG=y +CONFIG_SOC_EFUSE_SOFT_DIS_JTAG=y +CONFIG_SOC_EFUSE_DIS_DIRECT_BOOT=y +CONFIG_SOC_EFUSE_DIS_ICACHE=y +CONFIG_SOC_EFUSE_BLOCK9_KEY_PURPOSE_QUIRK=y +CONFIG_SOC_SECURE_BOOT_V2_RSA=y +CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS=3 +CONFIG_SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS=y +CONFIG_SOC_SUPPORT_SECURE_BOOT_REVOKE_KEY=y +CONFIG_SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX=64 +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_OPTIONS=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_128=y +CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_256=y +CONFIG_SOC_MEMPROT_CPU_PREFETCH_PAD_SIZE=16 +CONFIG_SOC_MEMPROT_MEM_ALIGN_SIZE=256 +CONFIG_SOC_PHY_DIG_REGS_MEM_SIZE=21 +CONFIG_SOC_MAC_BB_PD_MEM_SIZE=192 +CONFIG_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12 +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_WAIT_IDLE=y +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND=y +CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_RESUME=y +CONFIG_SOC_SPI_MEM_SUPPORT_SW_SUSPEND=y +CONFIG_SOC_SPI_MEM_SUPPORT_OPI_MODE=y +CONFIG_SOC_SPI_MEM_SUPPORT_TIMING_TUNING=y +CONFIG_SOC_SPI_MEM_SUPPORT_CONFIG_GPIO_BY_EFUSE=y +CONFIG_SOC_SPI_MEM_SUPPORT_WRAP=y +CONFIG_SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY=y +CONFIG_SOC_MEMSPI_CORE_CLK_SHARED_WITH_PSRAM=y +CONFIG_SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP=y +CONFIG_SOC_COEX_HW_PTI=y +CONFIG_SOC_EXTERNAL_COEX_LEADER_TX_LINE=y +CONFIG_SOC_SDMMC_USE_GPIO_MATRIX=y +CONFIG_SOC_SDMMC_NUM_SLOTS=2 +CONFIG_SOC_SDMMC_SUPPORT_XTAL_CLOCK=y +CONFIG_SOC_SDMMC_DELAY_PHASE_NUM=4 +CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_FAST_RC=y +CONFIG_SOC_WIFI_HW_TSF=y +CONFIG_SOC_WIFI_FTM_SUPPORT=y +CONFIG_SOC_WIFI_GCMP_SUPPORT=y +CONFIG_SOC_WIFI_WAPI_SUPPORT=y +CONFIG_SOC_WIFI_CSI_SUPPORT=y +CONFIG_SOC_WIFI_MESH_SUPPORT=y +CONFIG_SOC_WIFI_SUPPORT_VARIABLE_BEACON_WINDOW=y +CONFIG_SOC_WIFI_PHY_NEEDS_USB_WORKAROUND=y +CONFIG_SOC_BLE_SUPPORTED=y +CONFIG_SOC_BLE_MESH_SUPPORTED=y +CONFIG_SOC_BLE_50_SUPPORTED=y +CONFIG_SOC_BLE_DEVICE_PRIVACY_SUPPORTED=y +CONFIG_SOC_BLUFI_SUPPORTED=y +CONFIG_SOC_ULP_HAS_ADC=y +CONFIG_SOC_PHY_COMBO_MODULE=y +CONFIG_IDF_CMAKE=y +CONFIG_IDF_TOOLCHAIN="gcc" +CONFIG_IDF_TOOLCHAIN_GCC=y +CONFIG_IDF_TARGET_ARCH_XTENSA=y +CONFIG_IDF_TARGET_ARCH="xtensa" +CONFIG_IDF_TARGET="esp32s3" +CONFIG_IDF_INIT_VERSION="5.4.1" +CONFIG_IDF_TARGET_ESP32S3=y +CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009 + +# +# Build type +# +CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y +# CONFIG_APP_BUILD_TYPE_RAM is not set +CONFIG_APP_BUILD_GENERATE_BINARIES=y +CONFIG_APP_BUILD_BOOTLOADER=y +CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y +# CONFIG_APP_REPRODUCIBLE_BUILD is not set +# CONFIG_APP_NO_BLOBS is not set +# end of Build type + +# +# Bootloader config +# + +# +# Bootloader manager +# +CONFIG_BOOTLOADER_COMPILE_TIME_DATE=y +CONFIG_BOOTLOADER_PROJECT_VER=1 +# end of Bootloader manager + +CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x0 +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set +# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set + +# +# Log +# +# CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set +CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y +# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set +# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set +CONFIG_BOOTLOADER_LOG_LEVEL=3 + +# +# Format +# +# CONFIG_BOOTLOADER_LOG_COLORS is not set +CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y +# end of Format +# end of Log + +# +# Serial Flash Configurations +# +# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set +CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y +# end of Serial Flash Configurations + +CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y +# CONFIG_BOOTLOADER_FACTORY_RESET is not set +# CONFIG_BOOTLOADER_APP_TEST is not set +CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=y +CONFIG_BOOTLOADER_WDT_ENABLE=y +# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set +CONFIG_BOOTLOADER_WDT_TIME_MS=9000 +# CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON is not set +# CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS is not set +CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0 +# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set +# end of Bootloader config + +# +# Security features +# +CONFIG_SECURE_BOOT_V2_RSA_SUPPORTED=y +CONFIG_SECURE_BOOT_V2_PREFERRED=y +# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set +# CONFIG_SECURE_BOOT is not set +# CONFIG_SECURE_FLASH_ENC_ENABLED is not set +CONFIG_SECURE_ROM_DL_MODE_ENABLED=y +# end of Security features + +# +# Application manager +# +CONFIG_APP_COMPILE_TIME_DATE=y +# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set +# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set +# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set +CONFIG_APP_RETRIEVE_LEN_ELF_SHA=9 +# end of Application manager + +CONFIG_ESP_ROM_HAS_CRC_LE=y +CONFIG_ESP_ROM_HAS_CRC_BE=y +CONFIG_ESP_ROM_HAS_MZ_CRC32=y +CONFIG_ESP_ROM_HAS_JPEG_DECODE=y +CONFIG_ESP_ROM_UART_CLK_IS_XTAL=y +CONFIG_ESP_ROM_HAS_RETARGETABLE_LOCKING=y +CONFIG_ESP_ROM_USB_OTG_NUM=3 +CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=4 +CONFIG_ESP_ROM_HAS_ERASE_0_REGION_BUG=y +CONFIG_ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV=y +CONFIG_ESP_ROM_GET_CLK_FREQ=y +CONFIG_ESP_ROM_HAS_HAL_WDT=y +CONFIG_ESP_ROM_NEEDS_SWSETUP_WORKAROUND=y +CONFIG_ESP_ROM_HAS_LAYOUT_TABLE=y +CONFIG_ESP_ROM_HAS_SPI_FLASH=y +CONFIG_ESP_ROM_HAS_ETS_PRINTF_BUG=y +CONFIG_ESP_ROM_HAS_NEWLIB=y +CONFIG_ESP_ROM_HAS_NEWLIB_NANO_FORMAT=y +CONFIG_ESP_ROM_HAS_NEWLIB_32BIT_TIME=y +CONFIG_ESP_ROM_NEEDS_SET_CACHE_MMU_SIZE=y +CONFIG_ESP_ROM_RAM_APP_NEEDS_MMU_INIT=y +CONFIG_ESP_ROM_HAS_FLASH_COUNT_PAGES_BUG=y +CONFIG_ESP_ROM_HAS_CACHE_SUSPEND_WAITI_BUG=y +CONFIG_ESP_ROM_HAS_CACHE_WRITEBACK_BUG=y +CONFIG_ESP_ROM_HAS_SW_FLOAT=y +CONFIG_ESP_ROM_HAS_VERSION=y +CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB=y +CONFIG_ESP_ROM_HAS_OUTPUT_PUTC_FUNC=y + +# +# Boot ROM Behavior +# +CONFIG_BOOT_ROM_LOG_ALWAYS_ON=y +# CONFIG_BOOT_ROM_LOG_ALWAYS_OFF is not set +# CONFIG_BOOT_ROM_LOG_ON_GPIO_HIGH is not set +# CONFIG_BOOT_ROM_LOG_ON_GPIO_LOW is not set +# end of Boot ROM Behavior + +# +# Serial flasher config +# +# CONFIG_ESPTOOLPY_NO_STUB is not set +# CONFIG_ESPTOOLPY_OCT_FLASH is not set +CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=y +# CONFIG_ESPTOOLPY_FLASHMODE_QIO is not set +# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set +CONFIG_ESPTOOLPY_FLASHMODE_DIO=y +# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set +CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y +CONFIG_ESPTOOLPY_FLASHMODE="dio" +# CONFIG_ESPTOOLPY_FLASHFREQ_120M is not set +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y +# CONFIG_ESPTOOLPY_FLASHFREQ_40M is not set +# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set +CONFIG_ESPTOOLPY_FLASHFREQ="80m" +# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set +# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set +CONFIG_ESPTOOLPY_FLASHSIZE="2MB" +# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set +CONFIG_ESPTOOLPY_BEFORE_RESET=y +# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set +CONFIG_ESPTOOLPY_BEFORE="default_reset" +CONFIG_ESPTOOLPY_AFTER_RESET=y +# CONFIG_ESPTOOLPY_AFTER_NORESET is not set +CONFIG_ESPTOOLPY_AFTER="hard_reset" +CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 +# end of Serial flasher config + +# +# Partition Table +# +CONFIG_PARTITION_TABLE_SINGLE_APP=y +# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set +# CONFIG_PARTITION_TABLE_TWO_OTA is not set +# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set +# CONFIG_PARTITION_TABLE_CUSTOM is not set +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# Compiler options +# +CONFIG_COMPILER_OPTIMIZATION_DEBUG=y +# CONFIG_COMPILER_OPTIMIZATION_SIZE is not set +# CONFIG_COMPILER_OPTIMIZATION_PERF is not set +# CONFIG_COMPILER_OPTIMIZATION_NONE is not set +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set +CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE=y +CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 +# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set +CONFIG_COMPILER_HIDE_PATHS_MACROS=y +# CONFIG_COMPILER_CXX_EXCEPTIONS is not set +# CONFIG_COMPILER_CXX_RTTI is not set +CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y +# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set +# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set +# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set +# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set +CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y +# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set +# CONFIG_COMPILER_DUMP_RTL_FILES is not set +CONFIG_COMPILER_RT_LIB_GCCLIB=y +CONFIG_COMPILER_RT_LIB_NAME="gcc" +CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y +# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set +# CONFIG_COMPILER_STATIC_ANALYZER is not set +# end of Compiler options + +# +# Component config +# + +# +# Driver Configurations +# + +# +# TWAI Configuration +# +# CONFIG_TWAI_ISR_IN_IRAM is not set +CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y +# end of TWAI Configuration + +# +# Legacy ADC Driver Configuration +# +# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set + +# +# Legacy ADC Calibration Configuration +# +# CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN is not set +# end of Legacy ADC Calibration Configuration +# end of Legacy ADC Driver Configuration + +# +# Legacy MCPWM Driver Configurations +# +# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy MCPWM Driver Configurations + +# +# Legacy Timer Group Driver Configurations +# +# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy Timer Group Driver Configurations + +# +# Legacy RMT Driver Configurations +# +# CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy RMT Driver Configurations + +# +# Legacy I2S Driver Configurations +# +# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy I2S Driver Configurations + +# +# Legacy PCNT Driver Configurations +# +# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy PCNT Driver Configurations + +# +# Legacy SDM Driver Configurations +# +# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy SDM Driver Configurations + +# +# Legacy Temperature Sensor Driver Configurations +# +# CONFIG_TEMP_SENSOR_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_TEMP_SENSOR_SKIP_LEGACY_CONFLICT_CHECK is not set +# end of Legacy Temperature Sensor Driver Configurations +# end of Driver Configurations + +# +# eFuse Bit Manager +# +# CONFIG_EFUSE_CUSTOM_TABLE is not set +# CONFIG_EFUSE_VIRTUAL is not set +CONFIG_EFUSE_MAX_BLK_LEN=256 +# end of eFuse Bit Manager + +# +# ESP-TLS +# +CONFIG_ESP_TLS_USING_MBEDTLS=y +CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y +# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK is not set +# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set +# CONFIG_ESP_TLS_PSK_VERIFICATION is not set +# CONFIG_ESP_TLS_INSECURE is not set +# end of ESP-TLS + +# +# Wireless Coexistence +# +CONFIG_ESP_COEX_ENABLED=y +# CONFIG_ESP_COEX_EXTERNAL_COEXIST_ENABLE is not set +# CONFIG_ESP_COEX_GPIO_DEBUG is not set +# end of Wireless Coexistence + +# +# Common ESP-related +# +CONFIG_ESP_ERR_TO_NAME_LOOKUP=y +# end of Common ESP-related + +# +# ESP-Driver:GPIO Configurations +# +# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:GPIO Configurations + +# +# ESP-Driver:GPTimer Configurations +# +CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y +# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set +# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set +# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:GPTimer Configurations + +# +# ESP-Driver:I2C Configurations +# +# CONFIG_I2C_ISR_IRAM_SAFE is not set +# CONFIG_I2C_ENABLE_DEBUG_LOG is not set +# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set +# end of ESP-Driver:I2C Configurations + +# +# ESP-Driver:I2S Configurations +# +# CONFIG_I2S_ISR_IRAM_SAFE is not set +# CONFIG_I2S_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:I2S Configurations + +# +# ESP-Driver:LEDC Configurations +# +# CONFIG_LEDC_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:LEDC Configurations + +# +# ESP-Driver:MCPWM Configurations +# +# CONFIG_MCPWM_ISR_IRAM_SAFE is not set +# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set +# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:MCPWM Configurations + +# +# ESP-Driver:PCNT Configurations +# +# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set +# CONFIG_PCNT_ISR_IRAM_SAFE is not set +# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:PCNT Configurations + +# +# ESP-Driver:RMT Configurations +# +# CONFIG_RMT_ISR_IRAM_SAFE is not set +# CONFIG_RMT_RECV_FUNC_IN_IRAM is not set +# CONFIG_RMT_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:RMT Configurations + +# +# ESP-Driver:Sigma Delta Modulator Configurations +# +# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set +# CONFIG_SDM_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Sigma Delta Modulator Configurations + +# +# ESP-Driver:SPI Configurations +# +# CONFIG_SPI_MASTER_IN_IRAM is not set +CONFIG_SPI_MASTER_ISR_IN_IRAM=y +# CONFIG_SPI_SLAVE_IN_IRAM is not set +CONFIG_SPI_SLAVE_ISR_IN_IRAM=y +# end of ESP-Driver:SPI Configurations + +# +# ESP-Driver:Temperature Sensor Configurations +# +# CONFIG_TEMP_SENSOR_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Temperature Sensor Configurations + +# +# ESP-Driver:UART Configurations +# +# CONFIG_UART_ISR_IN_IRAM is not set +# end of ESP-Driver:UART Configurations + +# +# ESP-Driver:USB Serial/JTAG Configuration +# +CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y +# end of ESP-Driver:USB Serial/JTAG Configuration + +# +# Event Loop Library +# +# CONFIG_ESP_EVENT_LOOP_PROFILING is not set +CONFIG_ESP_EVENT_POST_FROM_ISR=y +CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y +# end of Event Loop Library + +# +# Hardware Settings +# + +# +# Chip revision +# +CONFIG_ESP32S3_REV_MIN_0=y +# CONFIG_ESP32S3_REV_MIN_1 is not set +# CONFIG_ESP32S3_REV_MIN_2 is not set +CONFIG_ESP32S3_REV_MIN_FULL=0 +CONFIG_ESP_REV_MIN_FULL=0 + +# +# Maximum Supported ESP32-S3 Revision (Rev v0.99) +# +CONFIG_ESP32S3_REV_MAX_FULL=99 +CONFIG_ESP_REV_MAX_FULL=99 +CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL=0 +CONFIG_ESP_EFUSE_BLOCK_REV_MAX_FULL=199 + +# +# Maximum Supported ESP32-S3 eFuse Block Revision (eFuse Block Rev v1.99) +# +# end of Chip revision + +# +# MAC Config +# +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_BT=y +CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y +CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES_FOUR=y +CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES=4 +# CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_TWO is not set +CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_FOUR=y +CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES=4 +# CONFIG_ESP_MAC_USE_CUSTOM_MAC_AS_BASE_MAC is not set +# end of MAC Config + +# +# Sleep Config +# +# CONFIG_ESP_SLEEP_POWER_DOWN_FLASH is not set +CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y +CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU=y +CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y +CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=y +CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=2000 +# CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set +# CONFIG_ESP_SLEEP_DEBUG is not set +CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y +# end of Sleep Config + +# +# RTC Clock Config +# +CONFIG_RTC_CLK_SRC_INT_RC=y +# CONFIG_RTC_CLK_SRC_EXT_CRYS is not set +# CONFIG_RTC_CLK_SRC_EXT_OSC is not set +# CONFIG_RTC_CLK_SRC_INT_8MD256 is not set +CONFIG_RTC_CLK_CAL_CYCLES=1024 +# end of RTC Clock Config + +# +# Peripheral Control +# +CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y +# end of Peripheral Control + +# +# GDMA Configurations +# +CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y +# CONFIG_GDMA_ISR_IRAM_SAFE is not set +# CONFIG_GDMA_ENABLE_DEBUG_LOG is not set +# end of GDMA Configurations + +# +# Main XTAL Config +# +CONFIG_XTAL_FREQ_40=y +CONFIG_XTAL_FREQ=40 +# end of Main XTAL Config + +CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y +# end of Hardware Settings + +# +# ESP-MM: Memory Management Configurations +# +# CONFIG_ESP_MM_CACHE_MSYNC_C2M_CHUNKED_OPS is not set +# end of ESP-MM: Memory Management Configurations + +# +# ESP NETIF Adapter +# +CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120 +# CONFIG_ESP_NETIF_PROVIDE_CUSTOM_IMPLEMENTATION is not set +CONFIG_ESP_NETIF_TCPIP_LWIP=y +# CONFIG_ESP_NETIF_LOOPBACK is not set +CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y +CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y +# CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set +# CONFIG_ESP_NETIF_L2_TAP is not set +# CONFIG_ESP_NETIF_BRIDGE_EN is not set +# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set +# end of ESP NETIF Adapter + +# +# Partition API Configuration +# +# end of Partition API Configuration + +# +# PHY +# +CONFIG_ESP_PHY_ENABLED=y +CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y +# CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set +CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP_PHY_MAX_TX_POWER=20 +# CONFIG_ESP_PHY_REDUCE_TX_POWER is not set +CONFIG_ESP_PHY_ENABLE_USB=y +# CONFIG_ESP_PHY_ENABLE_CERT_TEST is not set +CONFIG_ESP_PHY_RF_CAL_PARTIAL=y +# CONFIG_ESP_PHY_RF_CAL_NONE is not set +# CONFIG_ESP_PHY_RF_CAL_FULL is not set +CONFIG_ESP_PHY_CALIBRATION_MODE=0 +# CONFIG_ESP_PHY_PLL_TRACK_DEBUG is not set +# CONFIG_ESP_PHY_RECORD_USED_TIME is not set +# end of PHY + +# +# Power Management +# +# CONFIG_PM_ENABLE is not set +# CONFIG_PM_SLP_IRAM_OPT is not set +CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y +CONFIG_PM_RESTORE_CACHE_TAGMEM_AFTER_LIGHT_SLEEP=y +# end of Power Management + +# +# ESP Ringbuf +# +# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set +# end of ESP Ringbuf + +# +# ESP Security Specific +# +# end of ESP Security Specific + +# +# ESP System Settings +# +# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80 is not set +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y +# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240 is not set +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160 + +# +# Cache config +# +CONFIG_ESP32S3_INSTRUCTION_CACHE_16KB=y +# CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB is not set +CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE=0x4000 +# CONFIG_ESP32S3_INSTRUCTION_CACHE_4WAYS is not set +CONFIG_ESP32S3_INSTRUCTION_CACHE_8WAYS=y +CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS=8 +# CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_16B is not set +CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_32B=y +CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE=32 +# CONFIG_ESP32S3_DATA_CACHE_16KB is not set +CONFIG_ESP32S3_DATA_CACHE_32KB=y +# CONFIG_ESP32S3_DATA_CACHE_64KB is not set +CONFIG_ESP32S3_DATA_CACHE_SIZE=0x8000 +# CONFIG_ESP32S3_DATA_CACHE_4WAYS is not set +CONFIG_ESP32S3_DATA_CACHE_8WAYS=y +CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS=8 +# CONFIG_ESP32S3_DATA_CACHE_LINE_16B is not set +CONFIG_ESP32S3_DATA_CACHE_LINE_32B=y +# CONFIG_ESP32S3_DATA_CACHE_LINE_64B is not set +CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE=32 +# end of Cache config + +# +# Memory +# +# CONFIG_ESP32S3_RTCDATA_IN_FAST_MEM is not set +# CONFIG_ESP32S3_USE_FIXED_STATIC_RAM_SIZE is not set +# end of Memory + +# +# Trace memory +# +# CONFIG_ESP32S3_TRAX is not set +CONFIG_ESP32S3_TRACEMEM_RESERVE_DRAM=0x0 +# end of Trace memory + +# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set +CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y +# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set +CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0 +CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y +CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y + +# +# Memory protection +# +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=y +CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y +# end of Memory protection + +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=3584 +CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y +# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set +# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set +CONFIG_ESP_MAIN_TASK_AFFINITY=0x0 +CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048 +CONFIG_ESP_CONSOLE_UART_DEFAULT=y +# CONFIG_ESP_CONSOLE_USB_CDC is not set +# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set +# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set +# CONFIG_ESP_CONSOLE_NONE is not set +# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set +CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y +CONFIG_ESP_CONSOLE_UART=y +CONFIG_ESP_CONSOLE_UART_NUM=0 +CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0 +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 +CONFIG_ESP_INT_WDT=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +CONFIG_ESP_INT_WDT_CHECK_CPU1=y +CONFIG_ESP_TASK_WDT_EN=y +CONFIG_ESP_TASK_WDT_INIT=y +# CONFIG_ESP_TASK_WDT_PANIC is not set +CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP_PANIC_HANDLER_IRAM is not set +# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP_DEBUG_OCDAWARE=y +CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y + +# +# Brownout Detector +# +CONFIG_ESP_BROWNOUT_DET=y +CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_1 is not set +CONFIG_ESP_BROWNOUT_DET_LVL=7 +# end of Brownout Detector + +CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y +CONFIG_ESP_SYSTEM_BBPLL_RECALIB=y +# end of ESP System Settings + +# +# IPC (Inter-Processor Call) +# +CONFIG_ESP_IPC_TASK_STACK_SIZE=1280 +CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y +CONFIG_ESP_IPC_ISR_ENABLE=y +# end of IPC (Inter-Processor Call) + +# +# ESP Timer (High Resolution Timer) +# +# CONFIG_ESP_TIMER_PROFILING is not set +CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y +CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y +CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 +CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1 +# CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL is not set +CONFIG_ESP_TIMER_TASK_AFFINITY=0x0 +CONFIG_ESP_TIMER_TASK_AFFINITY_CPU0=y +CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y +# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set +CONFIG_ESP_TIMER_IMPL_SYSTIMER=y +# end of ESP Timer (High Resolution Timer) + +# +# Wi-Fi +# +CONFIG_ESP_WIFI_ENABLED=y +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +# CONFIG_ESP_WIFI_STATIC_TX_BUFFER is not set +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER=y +CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER=y +# CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER is not set +CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0 +CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=5 +# CONFIG_ESP_WIFI_CSI_ENABLED is not set +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP_WIFI_TX_BA_WIN=6 +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP_WIFI_RX_BA_WIN=6 +CONFIG_ESP_WIFI_NVS_ENABLED=y +CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=y +# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1 is not set +CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP_WIFI_IRAM_OPT=y +# CONFIG_ESP_WIFI_EXTRA_IRAM_OPT is not set +CONFIG_ESP_WIFI_RX_IRAM_OPT=y +CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP_WIFI_ENABLE_SAE_PK=y +CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT=y +CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=y +# CONFIG_ESP_WIFI_SLP_IRAM_OPT is not set +CONFIG_ESP_WIFI_SLP_DEFAULT_MIN_ACTIVE_TIME=50 +CONFIG_ESP_WIFI_SLP_DEFAULT_MAX_ACTIVE_TIME=10 +CONFIG_ESP_WIFI_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 +# CONFIG_ESP_WIFI_FTM_ENABLE is not set +CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y +# CONFIG_ESP_WIFI_GCMP_SUPPORT is not set +CONFIG_ESP_WIFI_GMAC_SUPPORT=y +CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y +# CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set +CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7 +CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y +CONFIG_ESP_WIFI_MBEDTLS_TLS_CLIENT=y +# CONFIG_ESP_WIFI_WAPI_PSK is not set +# CONFIG_ESP_WIFI_SUITE_B_192 is not set +# CONFIG_ESP_WIFI_11KV_SUPPORT is not set +# CONFIG_ESP_WIFI_MBO_SUPPORT is not set +# CONFIG_ESP_WIFI_DPP_SUPPORT is not set +# CONFIG_ESP_WIFI_11R_SUPPORT is not set +# CONFIG_ESP_WIFI_WPS_SOFTAP_REGISTRAR is not set + +# +# WPS Configuration Options +# +# CONFIG_ESP_WIFI_WPS_STRICT is not set +# CONFIG_ESP_WIFI_WPS_PASSPHRASE is not set +# end of WPS Configuration Options + +# CONFIG_ESP_WIFI_DEBUG_PRINT is not set +# CONFIG_ESP_WIFI_TESTING_OPTIONS is not set +CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=y +# CONFIG_ESP_WIFI_ENT_FREE_DYNAMIC_BUFFER is not set +# end of Wi-Fi + +# +# FreeRTOS +# + +# +# Kernel +# +# CONFIG_FREERTOS_SMP is not set +# CONFIG_FREERTOS_UNICORE is not set +CONFIG_FREERTOS_HZ=100 +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set +# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set +CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y +CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1 +CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536 +# CONFIG_FREERTOS_USE_IDLE_HOOK is not set +# CONFIG_FREERTOS_USE_TICK_HOOK is not set +CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 +# CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set +CONFIG_FREERTOS_USE_TIMERS=y +CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc" +# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set +# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set +CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY=y +CONFIG_FREERTOS_TIMER_SERVICE_TASK_CORE_AFFINITY=0x7FFFFFFF +CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 +CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1 +# CONFIG_FREERTOS_USE_TRACE_FACILITY is not set +# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set +# CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set +# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set +# end of Kernel + +# +# Port +# +CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y +# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set +CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y +# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set +# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set +CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y +CONFIG_FREERTOS_ISR_STACKSIZE=1536 +CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y +# CONFIG_FREERTOS_FPU_IN_ISR is not set +CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y +CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y +# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set +CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y +# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set +# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set +# end of Port + +# +# Extra +# +# end of Extra + +CONFIG_FREERTOS_PORT=y +CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF +CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y +CONFIG_FREERTOS_DEBUG_OCDAWARE=y +CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y +CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y +CONFIG_FREERTOS_NUMBER_OF_CORES=2 +# end of FreeRTOS + +# +# Hardware Abstraction Layer (HAL) and Low Level (LL) +# +CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y +# CONFIG_HAL_ASSERTION_DISABLE is not set +# CONFIG_HAL_ASSERTION_SILENT is not set +# CONFIG_HAL_ASSERTION_ENABLE is not set +CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 +CONFIG_HAL_WDT_USE_ROM_IMPL=y +CONFIG_HAL_SPI_MASTER_FUNC_IN_IRAM=y +CONFIG_HAL_SPI_SLAVE_FUNC_IN_IRAM=y +# end of Hardware Abstraction Layer (HAL) and Low Level (LL) + +# +# Heap memory debugging +# +CONFIG_HEAP_POISONING_DISABLED=y +# CONFIG_HEAP_POISONING_LIGHT is not set +# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set +CONFIG_HEAP_TRACING_OFF=y +# CONFIG_HEAP_TRACING_STANDALONE is not set +# CONFIG_HEAP_TRACING_TOHOST is not set +# CONFIG_HEAP_USE_HOOKS is not set +# CONFIG_HEAP_TASK_TRACKING is not set +# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set +# CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set +# end of Heap memory debugging + +# +# Log +# + +# +# Log Level +# +# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set +# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set +# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set +CONFIG_LOG_DEFAULT_LEVEL_INFO=y +# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set +# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set +CONFIG_LOG_DEFAULT_LEVEL=3 +CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y +# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set +# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set +CONFIG_LOG_MAXIMUM_LEVEL=3 + +# +# Level Settings +# +# CONFIG_LOG_MASTER_LEVEL is not set +CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y +# CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set +# CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set +CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_AND_LINKED_LIST=y +# CONFIG_LOG_TAG_LEVEL_CACHE_ARRAY is not set +CONFIG_LOG_TAG_LEVEL_CACHE_BINARY_MIN_HEAP=y +CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31 +# end of Level Settings +# end of Log Level + +# +# Format +# +# CONFIG_LOG_COLORS is not set +CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y +# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set +# end of Format +# end of Log + +# +# LWIP +# +CONFIG_LWIP_ENABLE=y +CONFIG_LWIP_LOCAL_HOSTNAME="espressif" +# CONFIG_LWIP_NETIF_API is not set +CONFIG_LWIP_TCPIP_TASK_PRIO=18 +# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set +# CONFIG_LWIP_CHECK_THREAD_SAFETY is not set +CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y +# CONFIG_LWIP_L2_TO_L3_COPY is not set +# CONFIG_LWIP_IRAM_OPTIMIZATION is not set +# CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION is not set +CONFIG_LWIP_TIMERS_ONDEMAND=y +CONFIG_LWIP_ND6=y +# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set +CONFIG_LWIP_MAX_SOCKETS=10 +# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set +# CONFIG_LWIP_SO_LINGER is not set +CONFIG_LWIP_SO_REUSE=y +CONFIG_LWIP_SO_REUSE_RXTOALL=y +# CONFIG_LWIP_SO_RCVBUF is not set +# CONFIG_LWIP_NETBUF_RECVINFO is not set +CONFIG_LWIP_IP_DEFAULT_TTL=64 +CONFIG_LWIP_IP4_FRAG=y +CONFIG_LWIP_IP6_FRAG=y +# CONFIG_LWIP_IP4_REASSEMBLY is not set +# CONFIG_LWIP_IP6_REASSEMBLY is not set +CONFIG_LWIP_IP_REASS_MAX_PBUFS=10 +# CONFIG_LWIP_IP_FORWARD is not set +# CONFIG_LWIP_STATS is not set +CONFIG_LWIP_ESP_GRATUITOUS_ARP=y +CONFIG_LWIP_GARP_TMR_INTERVAL=60 +CONFIG_LWIP_ESP_MLDV6_REPORT=y +CONFIG_LWIP_MLDV6_TMR_INTERVAL=40 +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 +CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y +# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set +# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set +# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set +CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y +# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set +CONFIG_LWIP_DHCP_OPTIONS_LEN=68 +CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0 +CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1 + +# +# DHCP server +# +CONFIG_LWIP_DHCPS=y +CONFIG_LWIP_DHCPS_LEASE_UNIT=60 +CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y +CONFIG_LWIP_DHCPS_ADD_DNS=y +# end of DHCP server + +# CONFIG_LWIP_AUTOIP is not set +CONFIG_LWIP_IPV4=y +CONFIG_LWIP_IPV6=y +# CONFIG_LWIP_IPV6_AUTOCONFIG is not set +CONFIG_LWIP_IPV6_NUM_ADDRESSES=3 +# CONFIG_LWIP_IPV6_FORWARD is not set +# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set +CONFIG_LWIP_NETIF_LOOPBACK=y +CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8 + +# +# TCP +# +CONFIG_LWIP_MAX_ACTIVE_TCP=16 +CONFIG_LWIP_MAX_LISTENING_TCP=16 +CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y +CONFIG_LWIP_TCP_MAXRTX=12 +CONFIG_LWIP_TCP_SYNMAXRTX=12 +CONFIG_LWIP_TCP_MSS=1440 +CONFIG_LWIP_TCP_TMR_INTERVAL=250 +CONFIG_LWIP_TCP_MSL=60000 +CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT=20000 +CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5760 +CONFIG_LWIP_TCP_WND_DEFAULT=5760 +CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCP_ACCEPTMBOX_SIZE=6 +CONFIG_LWIP_TCP_QUEUE_OOSEQ=y +CONFIG_LWIP_TCP_OOSEQ_TIMEOUT=6 +CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=4 +# CONFIG_LWIP_TCP_SACK_OUT is not set +CONFIG_LWIP_TCP_OVERSIZE_MSS=y +# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set +CONFIG_LWIP_TCP_RTO_TIME=1500 +# end of TCP + +# +# UDP +# +CONFIG_LWIP_MAX_UDP_PCBS=16 +CONFIG_LWIP_UDP_RECVMBOX_SIZE=6 +# end of UDP + +# +# Checksums +# +# CONFIG_LWIP_CHECKSUM_CHECK_IP is not set +# CONFIG_LWIP_CHECKSUM_CHECK_UDP is not set +CONFIG_LWIP_CHECKSUM_CHECK_ICMP=y +# end of Checksums + +CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF +CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 +CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 +CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5 +CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3 +CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10 +# CONFIG_LWIP_PPP_SUPPORT is not set +# CONFIG_LWIP_SLIP_SUPPORT is not set + +# +# ICMP +# +CONFIG_LWIP_ICMP=y +# CONFIG_LWIP_MULTICAST_PING is not set +# CONFIG_LWIP_BROADCAST_PING is not set +# end of ICMP + +# +# LWIP RAW API +# +CONFIG_LWIP_MAX_RAW_PCBS=16 +# end of LWIP RAW API + +# +# SNTP +# +CONFIG_LWIP_SNTP_MAX_SERVERS=1 +# CONFIG_LWIP_DHCP_GET_NTP_SRV is not set +CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000 +CONFIG_LWIP_SNTP_STARTUP_DELAY=y +CONFIG_LWIP_SNTP_MAXIMUM_STARTUP_DELAY=5000 +# end of SNTP + +# +# DNS +# +CONFIG_LWIP_DNS_MAX_HOST_IP=1 +CONFIG_LWIP_DNS_MAX_SERVERS=3 +# CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set +# CONFIG_LWIP_DNS_SETSERVER_WITH_NETIF is not set +# end of DNS + +CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7 +CONFIG_LWIP_ESP_LWIP_ASSERT=y + +# +# Hooks +# +# CONFIG_LWIP_HOOK_TCP_ISN_NONE is not set +CONFIG_LWIP_HOOK_TCP_ISN_DEFAULT=y +# CONFIG_LWIP_HOOK_TCP_ISN_CUSTOM is not set +CONFIG_LWIP_HOOK_IP6_ROUTE_NONE=y +# CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_ROUTE_CUSTOM is not set +CONFIG_LWIP_HOOK_ND6_GET_GW_NONE=y +# CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT is not set +# CONFIG_LWIP_HOOK_ND6_GET_GW_CUSTOM is not set +CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_NONE=y +# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM is not set +CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set +# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set +CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_NONE=y +# CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_CUSTOM is not set +# CONFIG_LWIP_HOOK_IP6_INPUT_NONE is not set +CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y +# CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set +# end of Hooks + +# CONFIG_LWIP_DEBUG is not set +# end of LWIP + +# +# mbedTLS +# +CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y +# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set +# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set +CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y +CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384 +CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096 +# CONFIG_MBEDTLS_DYNAMIC_BUFFER is not set +# CONFIG_MBEDTLS_DEBUG is not set + +# +# mbedTLS v3.x related +# +# CONFIG_MBEDTLS_SSL_PROTO_TLS1_3 is not set +# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set +# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set +# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set +CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=y +CONFIG_MBEDTLS_PKCS7_C=y +# end of mbedTLS v3.x related + +# +# Certificate Bundle +# +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set +# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST is not set +CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200 +# end of Certificate Bundle + +# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set +CONFIG_MBEDTLS_CMAC_C=y +CONFIG_MBEDTLS_HARDWARE_AES=y +CONFIG_MBEDTLS_AES_USE_INTERRUPT=y +CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0 +CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y +CONFIG_MBEDTLS_HARDWARE_MPI=y +# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set +CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y +CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0 +CONFIG_MBEDTLS_HARDWARE_SHA=y +CONFIG_MBEDTLS_ROM_MD5=y +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set +# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set +CONFIG_MBEDTLS_HAVE_TIME=y +# CONFIG_MBEDTLS_PLATFORM_TIME_ALT is not set +# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set +CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y +CONFIG_MBEDTLS_SHA512_C=y +# CONFIG_MBEDTLS_SHA3_C is not set +CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y +# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set +# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set +# CONFIG_MBEDTLS_TLS_DISABLED is not set +CONFIG_MBEDTLS_TLS_SERVER=y +CONFIG_MBEDTLS_TLS_CLIENT=y +CONFIG_MBEDTLS_TLS_ENABLED=y + +# +# TLS Key Exchange Methods +# +# CONFIG_MBEDTLS_PSK_MODES is not set +CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y +# end of TLS Key Exchange Methods + +CONFIG_MBEDTLS_SSL_RENEGOTIATION=y +CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y +# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set +# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set +CONFIG_MBEDTLS_SSL_ALPN=y +CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y +CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y + +# +# Symmetric Ciphers +# +CONFIG_MBEDTLS_AES_C=y +# CONFIG_MBEDTLS_CAMELLIA_C is not set +# CONFIG_MBEDTLS_DES_C is not set +# CONFIG_MBEDTLS_BLOWFISH_C is not set +# CONFIG_MBEDTLS_XTEA_C is not set +CONFIG_MBEDTLS_CCM_C=y +CONFIG_MBEDTLS_GCM_C=y +# CONFIG_MBEDTLS_NIST_KW_C is not set +# end of Symmetric Ciphers + +# CONFIG_MBEDTLS_RIPEMD160_C is not set + +# +# Certificates +# +CONFIG_MBEDTLS_PEM_PARSE_C=y +CONFIG_MBEDTLS_PEM_WRITE_C=y +CONFIG_MBEDTLS_X509_CRL_PARSE_C=y +CONFIG_MBEDTLS_X509_CSR_PARSE_C=y +# end of Certificates + +CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y +CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y +# CONFIG_MBEDTLS_DHM_C is not set +CONFIG_MBEDTLS_ECDH_C=y +CONFIG_MBEDTLS_ECDSA_C=y +# CONFIG_MBEDTLS_ECJPAKE_C is not set +CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y +CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y +CONFIG_MBEDTLS_ECP_NIST_OPTIM=y +# CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM is not set +# CONFIG_MBEDTLS_POLY1305_C is not set +# CONFIG_MBEDTLS_CHACHA20_C is not set +# CONFIG_MBEDTLS_HKDF_C is not set +# CONFIG_MBEDTLS_THREADING_C is not set +CONFIG_MBEDTLS_ERROR_STRINGS=y +CONFIG_MBEDTLS_FS_IO=y +# end of mbedTLS + +# +# ESP-MQTT Configurations +# +CONFIG_MQTT_PROTOCOL_311=y +# CONFIG_MQTT_PROTOCOL_5 is not set +CONFIG_MQTT_TRANSPORT_SSL=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET=y +CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y +# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set +# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set +# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set +# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set +# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set +# CONFIG_MQTT_CUSTOM_OUTBOX is not set +# end of ESP-MQTT Configurations + +# +# Newlib +# +CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set +# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set +# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set +CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y +# CONFIG_NEWLIB_NANO_FORMAT is not set +CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y +# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set +# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set +# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set +# end of Newlib + +# +# NVS +# +# CONFIG_NVS_ENCRYPTION is not set +# CONFIG_NVS_ASSERT_ERROR_CHECK is not set +# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set +# end of NVS + +# +# PThreads +# +CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_PTHREAD_STACK_MIN=768 +CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y +# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set +# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set +CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread" +# end of PThreads + +# +# MMU Config +# +CONFIG_MMU_PAGE_SIZE_64KB=y +CONFIG_MMU_PAGE_MODE="64KB" +CONFIG_MMU_PAGE_SIZE=0x10000 +# end of MMU Config + +# +# Main Flash configuration +# + +# +# SPI Flash behavior when brownout +# +CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=y +CONFIG_SPI_FLASH_BROWNOUT_RESET=y +# end of SPI Flash behavior when brownout + +# +# Optional and Experimental Features (READ DOCS FIRST) +# + +# +# Features here require specific hardware (READ DOCS FIRST!) +# +# CONFIG_SPI_FLASH_HPM_ENA is not set +CONFIG_SPI_FLASH_HPM_AUTO=y +# CONFIG_SPI_FLASH_HPM_DIS is not set +CONFIG_SPI_FLASH_HPM_ON=y +CONFIG_SPI_FLASH_HPM_DC_AUTO=y +# CONFIG_SPI_FLASH_HPM_DC_DISABLE is not set +# CONFIG_SPI_FLASH_AUTO_SUSPEND is not set +CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 +# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set +# end of Optional and Experimental Features (READ DOCS FIRST) +# end of Main Flash configuration + +# +# SPI Flash driver +# +# CONFIG_SPI_FLASH_VERIFY_WRITE is not set +# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set +CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y +# CONFIG_SPI_FLASH_ROM_IMPL is not set +CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set +# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set +# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set +CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y +CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20 +CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1 +CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 +# CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set +# CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set +# CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST is not set + +# +# Auto-detect flash chips +# +CONFIG_SPI_FLASH_VENDOR_XMC_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_GD_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_ISSI_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_MXIC_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_WINBOND_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_BOYA_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_TH_SUPPORTED=y +CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_TH_CHIP=y +CONFIG_SPI_FLASH_SUPPORT_MXIC_OPI_CHIP=y +# end of Auto-detect flash chips + +CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y +# end of SPI Flash driver + +# +# TCP Transport +# + +# +# Websocket +# +CONFIG_WS_TRANSPORT=y +CONFIG_WS_BUFFER_SIZE=1024 +# CONFIG_WS_DYNAMIC_BUFFER is not set +# end of Websocket +# end of TCP Transport + +# +# USB-OTG +# +CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=256 +CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED=y +# CONFIG_USB_HOST_HW_BUFFER_BIAS_IN is not set +# CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT is not set + +# +# Hub Driver Configuration +# + +# +# Root Port configuration +# +CONFIG_USB_HOST_DEBOUNCE_DELAY_MS=250 +CONFIG_USB_HOST_RESET_HOLD_MS=30 +CONFIG_USB_HOST_RESET_RECOVERY_MS=30 +CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS=10 +# end of Root Port configuration + +CONFIG_USB_HOST_HUBS_SUPPORTED=y +CONFIG_USB_HOST_HUB_MULTI_LEVEL=y + +# +# Downstream Port configuration +# +CONFIG_USB_HOST_EXT_PORT_RESET_RECOVERY_DELAY_MS=30 +# CONFIG_USB_HOST_EXT_PORT_CUSTOM_POWER_ON_DELAY_ENABLE is not set +# end of Downstream Port configuration +# end of Hub Driver Configuration + +# CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK is not set +CONFIG_USB_OTG_SUPPORTED=y +# end of USB-OTG + +# +# Virtual file system +# +CONFIG_VFS_SUPPORT_IO=y +CONFIG_VFS_SUPPORT_DIR=y +CONFIG_VFS_SUPPORT_SELECT=y +CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +# CONFIG_VFS_SELECT_IN_RAM is not set +CONFIG_VFS_SUPPORT_TERMIOS=y +CONFIG_VFS_MAX_COUNT=8 + +# +# Host File System I/O (Semihosting) +# +CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# end of Host File System I/O (Semihosting) + +CONFIG_VFS_INITIALIZE_DEV_NULL=y +# end of Virtual file system + +# +# ESP-MQTT Configurations +# +# end of ESP-MQTT Configurations +# end of Component config + +# CONFIG_IDF_EXPERIMENTAL_FEATURES is not set + +# Deprecated options for backward compatibility +# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set +# CONFIG_NO_BLOBS is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set +CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y +# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set +CONFIG_LOG_BOOTLOADER_LEVEL=3 +# CONFIG_APP_ROLLBACK_ENABLE is not set +# CONFIG_FLASH_ENCRYPTION_ENABLED is not set +# CONFIG_FLASHMODE_QIO is not set +# CONFIG_FLASHMODE_QOUT is not set +CONFIG_FLASHMODE_DIO=y +# CONFIG_FLASHMODE_DOUT is not set +CONFIG_MONITOR_BAUD=115200 +CONFIG_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y +# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set +CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y +# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set +CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 +# CONFIG_CXX_EXCEPTIONS is not set +CONFIG_STACK_CHECK_NONE=y +# CONFIG_STACK_CHECK_NORM is not set +# CONFIG_STACK_CHECK_STRONG is not set +# CONFIG_STACK_CHECK_ALL is not set +# CONFIG_WARN_WRITE_STRINGS is not set +# CONFIG_EXTERNAL_COEX_ENABLE is not set +# CONFIG_ESP_WIFI_EXTERNAL_COEXIST_ENABLE is not set +# CONFIG_MCPWM_ISR_IN_IRAM is not set +# CONFIG_EVENT_LOOP_PROFILING is not set +CONFIG_POST_EVENTS_FROM_ISR=y +CONFIG_POST_EVENTS_FROM_IRAM_ISR=y +# CONFIG_ESP_SYSTEM_PD_FLASH is not set +CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_CRYS is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_OSC is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_INT_8MD256 is not set +CONFIG_ESP32S3_RTC_CLK_CAL_CYCLES=1024 +CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y +# CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set +CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP32_PHY_MAX_TX_POWER=20 +# CONFIG_REDUCE_PHY_TX_POWER is not set +# CONFIG_ESP32_REDUCE_PHY_TX_POWER is not set +CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y +CONFIG_PM_POWER_DOWN_TAGMEM_IN_LIGHT_SLEEP=y +# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_80 is not set +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160=y +# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240 is not set +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=160 +CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_MAIN_TASK_STACK_SIZE=3584 +CONFIG_CONSOLE_UART_DEFAULT=y +# CONFIG_CONSOLE_UART_CUSTOM is not set +# CONFIG_CONSOLE_UART_NONE is not set +# CONFIG_ESP_CONSOLE_UART_NONE is not set +CONFIG_CONSOLE_UART=y +CONFIG_CONSOLE_UART_NUM=0 +CONFIG_CONSOLE_UART_BAUDRATE=115200 +CONFIG_INT_WDT=y +CONFIG_INT_WDT_TIMEOUT_MS=300 +CONFIG_INT_WDT_CHECK_CPU1=y +CONFIG_TASK_WDT=y +CONFIG_ESP_TASK_WDT=y +# CONFIG_TASK_WDT_PANIC is not set +CONFIG_TASK_WDT_TIMEOUT_S=5 +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP32S3_DEBUG_OCDAWARE=y +CONFIG_BROWNOUT_DET=y +CONFIG_ESP32S3_BROWNOUT_DET=y +CONFIG_BROWNOUT_DET_LVL_SEL_7=y +CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_1 is not set +CONFIG_BROWNOUT_DET_LVL=7 +CONFIG_ESP32S3_BROWNOUT_DET_LVL=7 +CONFIG_IPC_TASK_STACK_SIZE=1280 +CONFIG_TIMER_TASK_STACK_SIZE=3584 +CONFIG_ESP32_WIFI_ENABLED=y +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +# CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +# CONFIG_ESP32_WIFI_CSI_ENABLED is not set +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=6 +CONFIG_ESP32_WIFI_NVS_ENABLED=y +CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y +# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_IRAM_OPT=y +CONFIG_ESP32_WIFI_RX_IRAM_OPT=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y +CONFIG_WPA_MBEDTLS_CRYPTO=y +CONFIG_WPA_MBEDTLS_TLS_CLIENT=y +# CONFIG_WPA_WAPI_PSK is not set +# CONFIG_WPA_SUITE_B_192 is not set +# CONFIG_WPA_11KV_SUPPORT is not set +# CONFIG_WPA_MBO_SUPPORT is not set +# CONFIG_WPA_DPP_SUPPORT is not set +# CONFIG_WPA_11R_SUPPORT is not set +# CONFIG_WPA_WPS_SOFTAP_REGISTRAR is not set +# CONFIG_WPA_WPS_STRICT is not set +# CONFIG_WPA_DEBUG_PRINT is not set +# CONFIG_WPA_TESTING_OPTIONS is not set +CONFIG_TIMER_TASK_PRIORITY=1 +CONFIG_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_TIMER_QUEUE_LENGTH=10 +# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set +# CONFIG_HAL_ASSERTION_SILIENT is not set +# CONFIG_L2_TO_L3_COPY is not set +CONFIG_ESP_GRATUITOUS_ARP=y +CONFIG_GARP_TMR_INTERVAL=60 +CONFIG_TCPIP_RECVMBOX_SIZE=32 +CONFIG_TCP_MAXRTX=12 +CONFIG_TCP_SYNMAXRTX=12 +CONFIG_TCP_MSS=1440 +CONFIG_TCP_MSL=60000 +CONFIG_TCP_SND_BUF_DEFAULT=5760 +CONFIG_TCP_WND_DEFAULT=5760 +CONFIG_TCP_RECVMBOX_SIZE=6 +CONFIG_TCP_QUEUE_OOSEQ=y +CONFIG_TCP_OVERSIZE_MSS=y +# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_TCP_OVERSIZE_DISABLE is not set +CONFIG_UDP_RECVMBOX_SIZE=6 +CONFIG_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF +# CONFIG_PPP_SUPPORT is not set +CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_SYSTIMER=y +CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_FRC1=y +# CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_SYSTIMER is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_FRC1 is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_NONE is not set +CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_ESP32_PTHREAD_STACK_MIN=768 +CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set +CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set +CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_SUPPORT_TERMIOS=y +CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# End of deprecated options diff --git a/sdkconfig.defaults b/sdkconfig.defaults new file mode 100644 index 0000000..bf1b553 --- /dev/null +++ b/sdkconfig.defaults @@ -0,0 +1,4 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_USB_HOST_HUBS_SUPPORTED=y