From 0869ecc8ce7b9a0088ea24bdde3fbca81b8b9fe2 Mon Sep 17 00:00:00 2001 From: Peter Siegmund Date: Sun, 15 Mar 2026 22:41:28 +0100 Subject: [PATCH] code enhancements - add MQTT - add ESP32-C6 - fix simulation Signed-off-by: Peter Siegmund --- firmware/Makefile | 2 +- firmware/components/api-server/CMakeLists.txt | 2 +- .../components/api-server/idf_component.yml | 2 +- .../components/api-server/src/api_handlers.c | 2 +- .../components/api-server/src/api_server.c | 4 +- .../api-server/src/websocket_handler.c | 5 +- .../connectivity-manager/idf_component.yml | 7 +- .../components/led-manager/idf_component.yml | 4 +- .../message-manager/src/message_manager.c | 9 - .../my_mqtt_client/idf_component.yml | 3 + .../my_mqtt_client/include/my_mqtt_client.h | 8 +- .../my_mqtt_client/src/my_mqtt_client.c | 121 ++++++++++++- .../components/simulator/src/simulator.cpp | 171 ++++++++---------- firmware/main/CMakeLists.txt | 1 + firmware/main/idf_component.yml | 4 +- firmware/main/include/u8g2_mqtt.h | 9 + firmware/main/src/app_task.cpp | 27 ++- firmware/main/src/main.cpp | 23 +++ firmware/main/src/u8g2_mqtt.cpp | 78 ++++++++ firmware/sdkconfig.defaults.esp32c6 | 23 ++- firmware/sdkconfig.defaults.esp32s3 | 2 + firmware/sdkconfig.release | 2 + firmware/storage/www/captive.html | 3 +- 23 files changed, 373 insertions(+), 139 deletions(-) create mode 100644 firmware/main/include/u8g2_mqtt.h create mode 100644 firmware/main/src/u8g2_mqtt.cpp diff --git a/firmware/Makefile b/firmware/Makefile index 26d1f9a..d62bde7 100644 --- a/firmware/Makefile +++ b/firmware/Makefile @@ -1,2 +1,2 @@ release: - idf.py -B build-release -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.release" fullclean build + idf.py -B build-release -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.release" -DIDF_TARGET=esp32c6 fullclean build size diff --git a/firmware/components/api-server/CMakeLists.txt b/firmware/components/api-server/CMakeLists.txt index 9a6ef99..64b6999 100644 --- a/firmware/components/api-server/CMakeLists.txt +++ b/firmware/components/api-server/CMakeLists.txt @@ -15,5 +15,5 @@ idf_component_register(SRCS simulator persistence-manager message-manager - simulator + my_mqtt_client ) diff --git a/firmware/components/api-server/idf_component.yml b/firmware/components/api-server/idf_component.yml index 3c6c200..bb9d826 100644 --- a/firmware/components/api-server/idf_component.yml +++ b/firmware/components/api-server/idf_component.yml @@ -2,4 +2,4 @@ dependencies: idf: version: '>=5.0.0' espressif/mdns: - version: '*' + version: ^1.10.1 diff --git a/firmware/components/api-server/src/api_handlers.c b/firmware/components/api-server/src/api_handlers.c index 32aacd2..a474e04 100644 --- a/firmware/components/api-server/src/api_handlers.c +++ b/firmware/components/api-server/src/api_handlers.c @@ -67,7 +67,7 @@ esp_err_t api_capabilities_get_handler(httpd_req_t *req) // Thread only available for esp32c6 or esp32h2 bool thread = false; #if defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2) - thread = true; + thread = false; #endif cJSON *json = cJSON_CreateObject(); cJSON_AddBoolToObject(json, "thread", thread); diff --git a/firmware/components/api-server/src/api_server.c b/firmware/components/api-server/src/api_server.c index 69e57f8..cc93277 100644 --- a/firmware/components/api-server/src/api_server.c +++ b/firmware/components/api-server/src/api_server.c @@ -7,6 +7,7 @@ #include #include #include +#include #include static const char *TAG = "api_server"; @@ -55,7 +56,7 @@ static esp_err_t start_webserver(void) config.server_port = s_config.port; config.lru_purge_enable = true; config.max_uri_handlers = 32; - config.max_open_sockets = 7; + config.max_open_sockets = (CONFIG_LWIP_MAX_SOCKETS - 3); config.uri_match_fn = httpd_uri_match_wildcard; ESP_LOGI(TAG, "Starting HTTP server on port %d", config.server_port); @@ -175,6 +176,7 @@ esp_err_t api_server_ws_broadcast_status(bool on, const char *mode, const char * "{\"type\":\"status\",\"on\":%s,\"mode\":\"%s\",\"schema\":\"%s\"," "\"color\":{\"r\":%d,\"g\":%d,\"b\":%d}}", on ? "true" : "false", mode, schema, r, g, b); + return api_server_ws_broadcast(buffer); } diff --git a/firmware/components/api-server/src/websocket_handler.c b/firmware/components/api-server/src/websocket_handler.c index 310c7b1..7e682c3 100644 --- a/firmware/components/api-server/src/websocket_handler.c +++ b/firmware/components/api-server/src/websocket_handler.c @@ -2,9 +2,10 @@ #include "api_server.h" #include "common.h" -#include "message_manager.h" +#include "my_mqtt_client.h" #include #include +#include #include static const char *TAG = "websocket_handler"; @@ -267,6 +268,8 @@ esp_err_t websocket_broadcast(httpd_handle_t server, const char *message) } } + mqtt_publish(message); + return ret; } diff --git a/firmware/components/connectivity-manager/idf_component.yml b/firmware/components/connectivity-manager/idf_component.yml index d4e8932..4bcb890 100644 --- a/firmware/components/connectivity-manager/idf_component.yml +++ b/firmware/components/connectivity-manager/idf_component.yml @@ -1,7 +1,4 @@ -## IDF Component Manager Manifest File dependencies: - ## Required IDF version idf: - version: '>=4.1.0' - - espressif/ble_conn_mgr: '^0.1.3' + version: '>=5.0.0' + espressif/ble_conn_mgr: '^0.1.6' diff --git a/firmware/components/led-manager/idf_component.yml b/firmware/components/led-manager/idf_component.yml index 74aa711..f91d118 100755 --- a/firmware/components/led-manager/idf_component.yml +++ b/firmware/components/led-manager/idf_component.yml @@ -1,2 +1,4 @@ dependencies: - espressif/led_strip: '~3.0.1' + idf: + version: '>=5.0.0' + espressif/led_strip: '~3.0.3' diff --git a/firmware/components/message-manager/src/message_manager.c b/firmware/components/message-manager/src/message_manager.c index 5550cae..8ee98a5 100644 --- a/firmware/components/message-manager/src/message_manager.c +++ b/firmware/components/message-manager/src/message_manager.c @@ -103,15 +103,6 @@ static void message_manager_task(void *param) message_listeners[i](&msg); } } - - uint8_t mac[6]; - esp_read_mac(mac, ESP_MAC_WIFI_STA); - const esp_app_desc_t *app_desc = esp_app_get_description(); - char topic[60]; - snprintf(topic, sizeof(topic), "device/%s/%02x%02x", app_desc->project_name, mac[4], mac[5]); - - char *data = "{\"key\":\"value\"}"; - mqtt_client_publish(topic, data, strlen(data), 0, false); } } } diff --git a/firmware/components/my_mqtt_client/idf_component.yml b/firmware/components/my_mqtt_client/idf_component.yml index a63de67..4066ab2 100644 --- a/firmware/components/my_mqtt_client/idf_component.yml +++ b/firmware/components/my_mqtt_client/idf_component.yml @@ -1,2 +1,5 @@ dependencies: + idf: + version: '>=5.0.0' espressif/mqtt: ^1.0.0 + espressif/cjson: "*" diff --git a/firmware/components/my_mqtt_client/include/my_mqtt_client.h b/firmware/components/my_mqtt_client/include/my_mqtt_client.h index eab36f9..c7268c3 100644 --- a/firmware/components/my_mqtt_client/include/my_mqtt_client.h +++ b/firmware/components/my_mqtt_client/include/my_mqtt_client.h @@ -3,11 +3,13 @@ #include #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif -void mqtt_client_start(void); -void mqtt_client_publish(const char *topic, const char *data, size_t len, int qos, bool retain); + void mqtt_client_start(void); + void mqtt_publish(const char *message); + void mqtt_client_publish(const char *topic, const char *data, size_t len, int qos, bool retain); #ifdef __cplusplus } diff --git a/firmware/components/my_mqtt_client/src/my_mqtt_client.c b/firmware/components/my_mqtt_client/src/my_mqtt_client.c index ffbe2fd..425fef5 100644 --- a/firmware/components/my_mqtt_client/src/my_mqtt_client.c +++ b/firmware/components/my_mqtt_client/src/my_mqtt_client.c @@ -8,6 +8,12 @@ #include "mqtt_client.h" #include "sdkconfig.h" +#include +#include +#include + +#define DEVICE_TOPIC_MAX_LEN 60 + static const char *TAG = "mqtt_client"; static esp_mqtt_client_handle_t client = NULL; @@ -108,12 +114,123 @@ void mqtt_client_start(void) } } +void get_device_topic(char *topic, size_t topic_len) +{ + uint8_t mac[6]; + esp_read_mac(mac, ESP_MAC_WIFI_STA); + const esp_app_desc_t *app_desc = esp_app_get_description(); + snprintf(topic, topic_len, "device/%s/%02x%02x", app_desc->project_name, mac[4], mac[5]); +} + +void mqtt_publish(const char *message) +{ + // Uptime in ms + int64_t uptime_ms = esp_timer_get_time() / 1000; + + // UTC time as ISO8601 + struct timeval tv; + gettimeofday(&tv, NULL); + struct tm tm_utc; + gmtime_r(&tv.tv_sec, &tm_utc); + char timestamp[32]; + strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", &tm_utc); + + // Firmware version + const esp_app_desc_t *app_desc = esp_app_get_description(); + const char *firmware = app_desc->version; + + // Reset reason + esp_reset_reason_t reset_reason = esp_reset_reason(); + const char *reset_reason_str = "UNKNOWN"; + switch (reset_reason) + { + case ESP_RST_POWERON: + reset_reason_str = "POWERON"; + break; + case ESP_RST_EXT: + reset_reason_str = "EXT"; + break; + case ESP_RST_SW: + reset_reason_str = "SW"; + break; + case ESP_RST_PANIC: + reset_reason_str = "PANIC"; + break; + case ESP_RST_INT_WDT: + reset_reason_str = "INT_WDT"; + break; + case ESP_RST_TASK_WDT: + reset_reason_str = "TASK_WDT"; + break; + case ESP_RST_WDT: + reset_reason_str = "WDT"; + break; + case ESP_RST_DEEPSLEEP: + reset_reason_str = "DEEPSLEEP"; + break; + case ESP_RST_BROWNOUT: + reset_reason_str = "BROWNOUT"; + break; + case ESP_RST_SDIO: + reset_reason_str = "SDIO"; + break; + default: + break; + } + + // Create JSON object + uint8_t mac[6]; + esp_read_mac(mac, ESP_MAC_WIFI_STA); + cJSON *root = cJSON_CreateObject(); + char mac_str[18]; + snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + cJSON_AddStringToObject(root, "device_id", mac_str); + cJSON_AddNumberToObject(root, "uptime", uptime_ms); + cJSON_AddStringToObject(root, "timestamp", timestamp); + cJSON_AddStringToObject(root, "firmware", firmware); + cJSON_AddStringToObject(root, "reset_reason", reset_reason_str); + + // Insert message as JSON object if possible + char topic_with_type[128]; + strncpy(topic_with_type, "", sizeof(topic_with_type)); + topic_with_type[sizeof(topic_with_type) - 1] = '\0'; + + cJSON *msg_obj = cJSON_Parse(message); + if (msg_obj) + { + cJSON *type_item = cJSON_DetachItemFromObject(msg_obj, "type"); + if (type_item && cJSON_IsString(type_item)) + { + // Extend topic + strncat(topic_with_type, type_item->valuestring, sizeof(topic_with_type) - strlen(topic_with_type) - 1); + } + cJSON_AddItemToObject(root, "message", msg_obj); + cJSON_Delete(type_item); // Free memory + } + else + { + cJSON_AddStringToObject(root, "message", message); + } + + // Publish JSON via MQTT + char *json_str = cJSON_PrintUnformatted(root); + mqtt_client_publish(topic_with_type, json_str, strlen(json_str), 0, true); + cJSON_Delete(root); + free(json_str); +} + void mqtt_client_publish(const char *topic, const char *data, size_t len, int qos, bool retain) { if (client) { - int msg_id = esp_mqtt_client_publish(client, topic, data, len, qos, retain); - ESP_LOGI(TAG, "Publish: topic=%s, msg_id=%d, qos=%d, retain=%d, len=%d", topic, msg_id, qos, retain, (int)len); + char base_topic[DEVICE_TOPIC_MAX_LEN]; + get_device_topic(base_topic, sizeof(base_topic)); + char full_topic[DEVICE_TOPIC_MAX_LEN + 64]; + snprintf(full_topic, sizeof(full_topic), "%s/%s", base_topic, topic); + + int msg_id = esp_mqtt_client_publish(client, full_topic, data, len, qos, retain); + ESP_LOGV(TAG, "Publish: topic=%s, msg_id=%d, qos=%d, retain=%d, len=%d", full_topic, msg_id, qos, retain, + (int)len); } else { diff --git a/firmware/components/simulator/src/simulator.cpp b/firmware/components/simulator/src/simulator.cpp index 3eb1a7d..ea9a693 100644 --- a/firmware/components/simulator/src/simulator.cpp +++ b/firmware/components/simulator/src/simulator.cpp @@ -4,6 +4,7 @@ #include "led_strip_ws2812.h" #include "message_manager.h" #include "persistence_manager.h" +#include "simulator.h" #include "storage.h" #include #include @@ -15,28 +16,7 @@ #include #include -static const char *TAG = "simulator"; -static char *time = NULL; - -static char *time_to_string(int hhmm) -{ - static char buffer[20]; - snprintf(buffer, sizeof(buffer), "%02d:%02d", hhmm / 100, hhmm % 100); - return buffer; -} - -static TaskHandle_t simulation_task_handle = NULL; -static SemaphoreHandle_t simulation_mutex = NULL; - -static void ensure_mutex_initialized(void) -{ - if (simulation_mutex == NULL) - { - simulation_mutex = xSemaphoreCreateMutex(); - } -} - -// The struct is extended with a 'next' pointer to form a linked list. +// Type definitions typedef struct light_item_node_t { char time[5]; @@ -46,23 +26,42 @@ typedef struct light_item_node_t struct light_item_node_t *next; } light_item_node_t; -// Global pointers for the head and tail of the list. -static light_item_node_t *head = NULL; -static light_item_node_t *tail = NULL; - -// Interpolation mode selection typedef enum { INTERPOLATION_RGB, INTERPOLATION_HSV } interpolation_mode_t; -// You can change this to test different interpolation methods +// Constants and global variables +static const char *TAG = "simulator"; +static char *time = NULL; +static TaskHandle_t simulation_task_handle = NULL; +static SemaphoreHandle_t simulation_mutex = NULL; +static light_item_node_t *head = NULL; static const interpolation_mode_t interpolation_mode = INTERPOLATION_RGB; -char *get_time(void) +// Helper function: converts hhmm format to minutes of the day +static int hhmm_to_minutes(const char time[5]) { - return time; + int t = atoi(time); + return (t / 100) * 60 + (t % 100); +} + +// Helper function: converts int hhmm to string +static char *time_to_string(int hhmm) +{ + static char buffer[20]; + snprintf(buffer, sizeof(buffer), "%02d:%02d", hhmm / 100, hhmm % 100); + return buffer; +} + +// Helper function: ensures mutex is initialized +static void ensure_mutex_initialized(void) +{ + if (simulation_mutex == NULL) + { + simulation_mutex = xSemaphoreCreateMutex(); + } } // Main interpolation function that selects the appropriate method @@ -78,10 +77,11 @@ static rgb_t interpolate_color(rgb_t start, rgb_t end, float factor) } } +// Linked list management esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t blue, uint8_t white, uint8_t brightness, uint8_t saturation) { - // Allocate memory for a new node in PSRAM. + // Allocate memory for new node in PSRAM. light_item_node_t *new_node = (light_item_node_t *)heap_caps_malloc(sizeof(light_item_node_t), MALLOC_CAP_DEFAULT); if (new_node == NULL) { @@ -106,18 +106,22 @@ esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t new_node->blue = (uint8_t)(color.blue * brightness_factor); new_node->next = NULL; - // Append the new node to the end of the list. - if (head == NULL) + // Insert sorted: find the correct position + if (head == NULL || hhmm_to_minutes(new_node->time) < hhmm_to_minutes(head->time)) { - // If the list is empty, the new node becomes both head and tail. + // New head + new_node->next = head; head = new_node; - tail = new_node; } else { - // Otherwise, append the new node to the end and update tail. - tail->next = new_node; - tail = new_node; + light_item_node_t *prev = head; + while (prev->next != NULL && hhmm_to_minutes(prev->next->time) < hhmm_to_minutes(new_node->time)) + { + prev = prev->next; + } + new_node->next = prev->next; + prev->next = new_node; } return ESP_OK; @@ -136,7 +140,6 @@ void cleanup_light_items(void) } head = NULL; - tail = NULL; ESP_LOGI(TAG, "Cleaned up all light items."); } @@ -153,6 +156,8 @@ static void initialize_light_items(void) load_file(filename); persistence_manager_deinit(&persistence); + // The list is now sorted because add_light_item inserts sorted + if (head == NULL) { ESP_LOGW(TAG, "Light schedule is empty. Simulation will not run."); @@ -199,43 +204,7 @@ static light_item_node_t *find_best_light_item_for_time(int hhmm) return best_item; } -static light_item_node_t *find_next_light_item_for_time(int hhmm) -{ - light_item_node_t *current = head; - light_item_node_t *next_item = NULL; - int next_time = 9999; // Initialize with a value larger than any possible time - - // First pass: find the soonest time after hhmm - while (current != NULL) - { - int current_time = atoi(current->time); - if (current_time > hhmm && current_time < next_time) - { - next_time = current_time; - next_item = current; - } - current = current->next; - } - - // If no item is found for the rest of the day, wrap around to the beginning of the next day - if (next_item == NULL) - { - current = head; - next_time = 9999; - while (current != NULL) - { - int current_time = atoi(current->time); - if (current_time < next_time) - { - next_time = current_time; - next_item = current; - } - current = current->next; - } - } - return next_item; -} - +// Messaging static void send_simulation_message(const char *time, rgb_t color) { message_t msg = {}; @@ -247,6 +216,12 @@ static void send_simulation_message(const char *time, rgb_t color) message_manager_post(&msg, pdMS_TO_TICKS(100)); } +// Public API +char *get_time(void) +{ + return time; +} + void start_simulate_day(void) { initialize_light_items(); @@ -308,49 +283,55 @@ void simulate_cycle(void *args) time = time_to_string(hhmm); light_item_node_t *current_item = find_best_light_item_for_time(hhmm); - light_item_node_t *next_item = find_next_light_item_for_time(hhmm); + light_item_node_t *next_item = NULL; if (current_item != NULL) { rgb_t color = {0, 0, 0}; - // Use head as fallback if next_item is NULL - next_item = next_item ? next_item : head; + // Cyclic interpolation: if current_item is the tail element, set next_item to head + if (current_item->next == NULL && head != NULL) + { + next_item = head; + } + else + { + next_item = current_item->next; + } + if (next_item != NULL) { - int current_item_time_min = (atoi(current_item->time) / 100) * 60 + (atoi(current_item->time) % 100); - int next_item_time_min = (atoi(next_item->time) / 100) * 60 + (atoi(next_item->time) % 100); + int current_minutes = hhmm_to_minutes(current_item->time); + int next_minutes = hhmm_to_minutes(next_item->time); - if (next_item_time_min < current_item_time_min) + // Cyclic transition: if next_minutes < current_minutes, add day length + if (next_minutes < current_minutes) { - next_item_time_min += total_minutes_in_day; + next_minutes += total_minutes_in_day; } - int minutes_since_current_item_start = current_minute_of_day - current_item_time_min; - if (minutes_since_current_item_start < 0) + int minutes_since_current = current_minute_of_day - current_minutes; + if (minutes_since_current < 0) { - minutes_since_current_item_start += total_minutes_in_day; + minutes_since_current += total_minutes_in_day; } - int interval_duration = next_item_time_min - current_item_time_min; - if (interval_duration == 0) + int interval = next_minutes - current_minutes; + if (interval == 0) { - interval_duration = 1; + interval = 1; } - float interpolation_factor = (float)minutes_since_current_item_start / (float)interval_duration; + float factor = (float)minutes_since_current / (float)interval; - // Prepare colors for interpolation rgb_t start_rgb = {.red = current_item->red, .green = current_item->green, .blue = current_item->blue}; rgb_t end_rgb = {.red = next_item->red, .green = next_item->green, .blue = next_item->blue}; - // Use the interpolation function - color = interpolate_color(start_rgb, end_rgb, interpolation_factor); + color = interpolate_color(start_rgb, end_rgb, factor); led_strip_update(LED_STATE_SIMULATION, color); } else { - // No next_item and no head, use only current color = (rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue}; led_strip_update(LED_STATE_SIMULATION, color); } @@ -402,7 +383,7 @@ void stop_simulation_task(void) simulation_task_handle = NULL; xSemaphoreGive(simulation_mutex); - // Prüfe ob der Task noch existiert bevor er gelöscht wird + // Check if the task still exists before deleting it eTaskState state = eTaskGetState(handle_to_delete); if (state != eDeleted && state != eInvalid) { diff --git a/firmware/main/CMakeLists.txt b/firmware/main/CMakeLists.txt index d3309e1..dd5f9fb 100755 --- a/firmware/main/CMakeLists.txt +++ b/firmware/main/CMakeLists.txt @@ -3,6 +3,7 @@ idf_component_register(SRCS src/app_task.cpp src/button_handling.c src/i2c_checker.c + src/u8g2_mqtt.cpp src/hal/u8g2_esp32_hal.c INCLUDE_DIRS "include" PRIV_REQUIRES diff --git a/firmware/main/idf_component.yml b/firmware/main/idf_component.yml index 0a9790b..7d8e0f2 100755 --- a/firmware/main/idf_component.yml +++ b/firmware/main/idf_component.yml @@ -3,5 +3,5 @@ dependencies: git: https://github.com/olikraus/u8g2.git # u8g2_hal: # git: https://github.com/mkfrey/u8g2-hal-esp-idf.git - espressif/button: ^4.1.4 - espressif/esp_insights: ^1.2.7 + espressif/button: ^4.1.6 + espressif/esp_insights: ^1.3.2 diff --git a/firmware/main/include/u8g2_mqtt.h b/firmware/main/include/u8g2_mqtt.h new file mode 100644 index 0000000..ed1d55b --- /dev/null +++ b/firmware/main/include/u8g2_mqtt.h @@ -0,0 +1,9 @@ +#pragma once + +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" + +extern QueueHandle_t display_mqtt_queue; + +void u8g2_mqtt_task(void *pvParameters); diff --git a/firmware/main/src/app_task.cpp b/firmware/main/src/app_task.cpp index ef12984..450696f 100644 --- a/firmware/main/src/app_task.cpp +++ b/firmware/main/src/app_task.cpp @@ -11,6 +11,7 @@ #include "my_mqtt_client.h" #include "persistence_manager.h" #include "simulator.h" +#include "u8g2_mqtt.h" #include "ui/ClockScreenSaver.h" #include "ui/ScreenSaver.h" #include "ui/SplashScreen.h" @@ -33,6 +34,7 @@ u8g2_t u8g2; uint8_t last_value = 0; menu_options_t options; uint8_t received_signal; +uint64_t last_mqtt_sync = 0; std::shared_ptr m_widget; std::vector> m_history; @@ -148,27 +150,27 @@ static void handle_button(uint8_t button) { switch (button) { - case 1: + case CONFIG_BUTTON_UP: m_widget->OnButtonClicked(ButtonType::UP); break; - case 3: + case CONFIG_BUTTON_LEFT: m_widget->OnButtonClicked(ButtonType::LEFT); break; - case 5: + case CONFIG_BUTTON_RIGHT: m_widget->OnButtonClicked(ButtonType::RIGHT); break; - case 6: + case CONFIG_BUTTON_DOWN: m_widget->OnButtonClicked(ButtonType::DOWN); break; - case 16: + case CONFIG_BUTTON_BACK: m_widget->OnButtonClicked(ButtonType::BACK); break; - case 18: + case CONFIG_BUTTON_SELECT: m_widget->OnButtonClicked(ButtonType::SELECT); break; @@ -259,6 +261,10 @@ void app_task(void *args) start_simulation(); + display_mqtt_queue = xQueueCreate(1, 1024); + + xTaskCreatePinnedToCore(u8g2_mqtt_task, "mqtt_disp", 4096, nullptr, 5, nullptr, tskNO_AFFINITY); + auto oldTime = esp_timer_get_time(); while (true) @@ -279,6 +285,15 @@ void app_task(void *args) m_inactivityTracker->update(deltaMs); } + // MQTT + auto now = esp_timer_get_time(); + if (now - last_mqtt_sync > 1000000) + { + uint8_t *u8g2_buf = u8g2_GetBufferPtr(&u8g2); + xQueueOverwrite(display_mqtt_queue, u8g2_buf); + last_mqtt_sync = now; + } + u8g2_SendBuffer(&u8g2); if (xQueueReceive(buttonQueue, &received_signal, pdMS_TO_TICKS(10)) == pdTRUE) diff --git a/firmware/main/src/main.cpp b/firmware/main/src/main.cpp index cf29983..912dbc0 100644 --- a/firmware/main/src/main.cpp +++ b/firmware/main/src/main.cpp @@ -5,6 +5,7 @@ #include "persistence_manager.h" #include "wifi_manager.h" #include +#include #include #include #include @@ -13,9 +14,31 @@ #include #include +#define WIFI_ENABLE GPIO_NUM_3 +#define WIFI_ANT_CONFIG GPIO_NUM_14 + __BEGIN_DECLS + void app_main(void) { +#if defined(CONFIG_IDF_TARGET_ESP32C6) + // GPIO für WiFi Enable konfigurieren + gpio_config_t io_conf = {.pin_bit_mask = (1ULL << WIFI_ENABLE), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE}; + gpio_config(&io_conf); + gpio_set_level(WIFI_ENABLE, 0); // LOW + + vTaskDelay(pdMS_TO_TICKS(100)); + + // GPIO for WiFi antenna configuration + io_conf.pin_bit_mask = (1ULL << WIFI_ANT_CONFIG); + gpio_config(&io_conf); + gpio_set_level(WIFI_ANT_CONFIG, 1); // HIGH +#endif + // Initialize NVS esp_err_t err = nvs_flash_init(); if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) diff --git a/firmware/main/src/u8g2_mqtt.cpp b/firmware/main/src/u8g2_mqtt.cpp new file mode 100644 index 0000000..2e84f72 --- /dev/null +++ b/firmware/main/src/u8g2_mqtt.cpp @@ -0,0 +1,78 @@ +#include "u8g2_mqtt.h" + +#include "esp_timer.h" +#include +#include +#include +#include + +#define BUFFER_SIZE (128 * 64 / 8) +QueueHandle_t display_mqtt_queue = nullptr; + +void u8g2_mqtt_task(void *pvParameters) +{ + static uint8_t current_buffer[BUFFER_SIZE]; + static uint8_t previous_buffer[BUFFER_SIZE] = {0}; + static uint8_t mqtt_payload[BUFFER_SIZE * 2 + 1]; + + uint64_t last_keyframe_time = 0; + const uint64_t KEYFRAME_INTERVAL_US = 5000000; // 5 seconds in microseconds + + while (true) + { + // Blocks without CPU load until app_task provides a frame + if (xQueueReceive(display_mqtt_queue, current_buffer, portMAX_DELAY) == pdTRUE) + { + int payload_size = 0; + uint64_t current_time = esp_timer_get_time(); + + // Time-based I-frame decision (or on initial start) + bool is_keyframe = (current_time - last_keyframe_time >= KEYFRAME_INTERVAL_US) || (last_keyframe_time == 0); + + if (is_keyframe) + { + mqtt_payload[payload_size++] = 0x01; // Header: I-frame + last_keyframe_time = current_time; + + for (int i = 0; i < BUFFER_SIZE;) + { + uint8_t count = 1; + while (i + count < BUFFER_SIZE && current_buffer[i] == current_buffer[i + count] && count < 255) + { + count++; + } + mqtt_payload[payload_size++] = count; + mqtt_payload[payload_size++] = current_buffer[i]; + i += count; + } + } + else + { + mqtt_payload[payload_size++] = 0x00; // Header: P-frame (Diff) + uint8_t xor_buffer[BUFFER_SIZE]; + + for (int i = 0; i < BUFFER_SIZE; i++) + { + xor_buffer[i] = current_buffer[i] ^ previous_buffer[i]; + } + + for (int i = 0; i < BUFFER_SIZE;) + { + uint8_t count = 1; + while (i + count < BUFFER_SIZE && xor_buffer[i] == xor_buffer[i + count] && count < 255) + { + count++; + } + mqtt_payload[payload_size++] = count; + mqtt_payload[payload_size++] = xor_buffer[i]; + i += count; + } + } + + // --- MQTT SEND --- + mqtt_client_publish("stream", (char *)mqtt_payload, payload_size, 0, false); + + memcpy(previous_buffer, current_buffer, BUFFER_SIZE); + } + } +} diff --git a/firmware/sdkconfig.defaults.esp32c6 b/firmware/sdkconfig.defaults.esp32c6 index c5e08d6..1b92cb3 100644 --- a/firmware/sdkconfig.defaults.esp32c6 +++ b/firmware/sdkconfig.defaults.esp32c6 @@ -4,17 +4,24 @@ CONFIG_IDF_TARGET="esp32c6" # # Display Settings # -CONFIG_DISPLAY_SDA_PIN=9 -CONFIG_DISPLAY_SCL_PIN=8 +CONFIG_DISPLAY_SDA_PIN=22 +CONFIG_DISPLAY_SCL_PIN=23 # end of Display Settings # # Button Configuration # -CONFIG_BUTTON_UP=7 -CONFIG_BUTTON_DOWN=4 -CONFIG_BUTTON_LEFT=6 -CONFIG_BUTTON_RIGHT=5 -CONFIG_BUTTON_SELECT=19 -CONFIG_BUTTON_BACK=20 +CONFIG_BUTTON_UP=18 +CONFIG_BUTTON_DOWN=17 +CONFIG_BUTTON_LEFT=20 +CONFIG_BUTTON_RIGHT=19 +CONFIG_BUTTON_SELECT=1 +CONFIG_BUTTON_BACK=2 # end of Button Configuration + +CONFIG_WLED_DIN_PIN=21 +CONFIG_STATUS_WLED_PIN=16 + +CONFIG_API_SERVER_HOSTNAME="system-control" + +CONFIG_LWIP_MAX_SOCKETS=20 diff --git a/firmware/sdkconfig.defaults.esp32s3 b/firmware/sdkconfig.defaults.esp32s3 index 7c2cf11..b8d2350 100644 --- a/firmware/sdkconfig.defaults.esp32s3 +++ b/firmware/sdkconfig.defaults.esp32s3 @@ -1,2 +1,4 @@ # default ESP target CONFIG_IDF_TARGET="esp32s3" + +CONFIG_API_SERVER_HOSTNAME="system-client" diff --git a/firmware/sdkconfig.release b/firmware/sdkconfig.release index 22f3cf1..c6adc70 100644 --- a/firmware/sdkconfig.release +++ b/firmware/sdkconfig.release @@ -3,4 +3,6 @@ CONFIG_APP_REPRODUCIBLE_BUILD=y # Compiler options CONFIG_COMPILER_OPTIMIZATION_PERF=y +# CONFIG_COMPILER_OPTIMIZATION_SIZE=y CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y +CONFIG_DEBUG_INFO=n diff --git a/firmware/storage/www/captive.html b/firmware/storage/www/captive.html index 9e17dbe..0bef855 100644 --- a/firmware/storage/www/captive.html +++ b/firmware/storage/www/captive.html @@ -46,8 +46,7 @@
-