From 501c2de87479e6555a1c33f387b0aec959015841 Mon Sep 17 00:00:00 2001 From: Peter Siegmund Date: Mon, 19 Jan 2026 00:19:57 +0100 Subject: [PATCH] update time on website via webSocket Signed-off-by: Peter Siegmund --- firmware/components/api-server/CMakeLists.txt | 2 + .../components/api-server/include/common.h | 1 + .../components/api-server/src/api_server.c | 4 + firmware/components/api-server/src/common.c | 49 +++++++--- .../insa/src/ui/ClockScreenSaver.cpp | 2 +- .../message-manager/include/message_manager.h | 12 ++- .../message-manager/src/message_manager.c | 42 +++++--- .../src/persistence_manager.c | 2 +- firmware/components/simulator/CMakeLists.txt | 1 + .../components/simulator/src/simulator.cpp | 98 +++++++++++-------- firmware/storage/www/js/websocket.js | 18 +++- 11 files changed, 161 insertions(+), 70 deletions(-) diff --git a/firmware/components/api-server/CMakeLists.txt b/firmware/components/api-server/CMakeLists.txt index 6f49796..9a6ef99 100644 --- a/firmware/components/api-server/CMakeLists.txt +++ b/firmware/components/api-server/CMakeLists.txt @@ -11,7 +11,9 @@ idf_component_register(SRCS esp_netif esp_event json + led-manager simulator persistence-manager message-manager + simulator ) diff --git a/firmware/components/api-server/include/common.h b/firmware/components/api-server/include/common.h index ffb9a73..f5d153a 100644 --- a/firmware/components/api-server/include/common.h +++ b/firmware/components/api-server/include/common.h @@ -3,6 +3,7 @@ #include +void common_init(void); cJSON *create_light_status_json(void); #endif // COMMON_H diff --git a/firmware/components/api-server/src/api_server.c b/firmware/components/api-server/src/api_server.c index f25f0db..69e57f8 100644 --- a/firmware/components/api-server/src/api_server.c +++ b/firmware/components/api-server/src/api_server.c @@ -2,6 +2,7 @@ #include "api_handlers.h" #include "websocket_handler.h" +#include "common.h" #include "storage.h" #include #include @@ -86,6 +87,9 @@ static esp_err_t start_webserver(void) return err; } + // Common initialization + common_init(); + ESP_LOGI(TAG, "HTTP server started successfully"); return ESP_OK; } diff --git a/firmware/components/api-server/src/common.c b/firmware/components/api-server/src/common.c index 9668892..f3ed6d4 100644 --- a/firmware/components/api-server/src/common.c +++ b/firmware/components/api-server/src/common.c @@ -2,9 +2,40 @@ #include #include +#include "api_server.h" +#include "color.h" +#include "message_manager.h" #include "persistence_manager.h" +#include "simulator.h" +#include #include +const char *system_time = NULL; +rgb_t color = {0, 0, 0}; + +static void on_message_received(const message_t *msg) +{ + if (msg->type == MESSAGE_TYPE_SIMULATION) + { + system_time = msg->data.simulation.time; + color.red = msg->data.simulation.red; + color.green = msg->data.simulation.green; + color.blue = msg->data.simulation.blue; + + cJSON *json = create_light_status_json(); + cJSON_AddStringToObject(json, "type", "status"); + char *response = cJSON_PrintUnformatted(json); + cJSON_Delete(json); + api_server_ws_broadcast(response); + free(response); + } +} + +void common_init(void) +{ + message_manager_register_listener(on_message_received); +} + // Gibt ein cJSON-Objekt with dem aktuellen Lichtstatus zurück cJSON *create_light_status_json(void) { @@ -36,19 +67,13 @@ cJSON *create_light_status_json(void) persistence_manager_deinit(&pm); - cJSON *color = cJSON_CreateObject(); - cJSON_AddNumberToObject(color, "r", 255); - cJSON_AddNumberToObject(color, "g", 240); - cJSON_AddNumberToObject(color, "b", 220); - cJSON_AddItemToObject(json, "color", color); + cJSON *c = cJSON_CreateObject(); + cJSON_AddNumberToObject(c, "r", color.red); + cJSON_AddNumberToObject(c, "g", color.green); + cJSON_AddNumberToObject(c, "b", color.blue); + cJSON_AddItemToObject(json, "color", c); - // Add current time as HH:MM only (suffix is handled in the frontend) - time_t now = time(NULL); - struct tm tm_info; - localtime_r(&now, &tm_info); - char time_str[8]; - strftime(time_str, sizeof(time_str), "%H:%M", &tm_info); - cJSON_AddStringToObject(json, "clock", time_str); + cJSON_AddStringToObject(json, "clock", system_time); return json; } diff --git a/firmware/components/insa/src/ui/ClockScreenSaver.cpp b/firmware/components/insa/src/ui/ClockScreenSaver.cpp index e2dde67..3692c37 100644 --- a/firmware/components/insa/src/ui/ClockScreenSaver.cpp +++ b/firmware/components/insa/src/ui/ClockScreenSaver.cpp @@ -45,7 +45,7 @@ void ClockScreenSaver::getCurrentTimeString(char *buffer, size_t bufferSize) con char *simulated_time = get_time(); if (simulated_time != nullptr) { - strncpy(buffer, simulated_time, bufferSize); + snprintf(buffer, bufferSize, "%s Uhr", simulated_time); return; } } diff --git a/firmware/components/message-manager/include/message_manager.h b/firmware/components/message-manager/include/message_manager.h index df32389..c53a116 100644 --- a/firmware/components/message-manager/include/message_manager.h +++ b/firmware/components/message-manager/include/message_manager.h @@ -13,7 +13,8 @@ extern "C" typedef enum { MESSAGE_TYPE_SETTINGS, - MESSAGE_TYPE_BUTTON + MESSAGE_TYPE_BUTTON, + MESSAGE_TYPE_SIMULATION } message_type_t; typedef enum @@ -48,12 +49,21 @@ extern "C" } value; } settings_message_t; + typedef struct + { + char time[6]; + uint8_t red; + uint8_t green; + uint8_t blue; + } simulation_message_t; + typedef struct { message_type_t type; union { settings_message_t settings; button_message_t button; + simulation_message_t simulation; } data; } message_t; diff --git a/firmware/components/message-manager/src/message_manager.c b/firmware/components/message-manager/src/message_manager.c index b628138..db67e25 100644 --- a/firmware/components/message-manager/src/message_manager.c +++ b/firmware/components/message-manager/src/message_manager.c @@ -17,21 +17,29 @@ static QueueHandle_t message_queue = NULL; static message_listener_t message_listeners[MAX_MESSAGE_LISTENERS] = {0}; static size_t message_listener_count = 0; -void message_manager_register_listener(message_listener_t listener) { - if (listener && message_listener_count < MAX_MESSAGE_LISTENERS) { +void message_manager_register_listener(message_listener_t listener) +{ + if (listener && message_listener_count < MAX_MESSAGE_LISTENERS) + { // Doppelte Registrierung vermeiden - for (size_t i = 0; i < message_listener_count; ++i) { - if (message_listeners[i] == listener) return; + for (size_t i = 0; i < message_listener_count; ++i) + { + if (message_listeners[i] == listener) + return; } message_listeners[message_listener_count++] = listener; } } -void message_manager_unregister_listener(message_listener_t listener) { - for (size_t i = 0; i < message_listener_count; ++i) { - if (message_listeners[i] == listener) { +void message_manager_unregister_listener(message_listener_t listener) +{ + for (size_t i = 0; i < message_listener_count; ++i) + { + if (message_listeners[i] == listener) + { // Nachfolgende Listener nach vorne schieben - for (size_t j = i; j < message_listener_count - 1; ++j) { + for (size_t j = i; j < message_listener_count - 1; ++j) + { message_listeners[j] = message_listeners[j + 1]; } message_listeners[--message_listener_count] = NULL; @@ -70,17 +78,23 @@ static void message_manager_task(void *param) break; } persistence_manager_deinit(&pm); - ESP_LOGI(TAG, "Setting written: %s", msg.data.settings.key); + ESP_LOGD(TAG, "Setting written: %s", msg.data.settings.key); } break; case MESSAGE_TYPE_BUTTON: - ESP_LOGI(TAG, "Button event: id=%d, type=%d", msg.data.button.button_id, msg.data.button.event_type); - // TODO: Weiterverarbeitung/Callback für Button-Events + ESP_LOGD(TAG, "Button event: id=%d, type=%d", msg.data.button.button_id, msg.data.button.event_type); + break; + case MESSAGE_TYPE_SIMULATION: + /// just logging + ESP_LOGD(TAG, "Simulation event: time=%s, color=(%d,%d,%d)", msg.data.simulation.time, + msg.data.simulation.red, msg.data.simulation.green, msg.data.simulation.blue); break; } // Observer Pattern: Listener benachrichtigen - for (size_t i = 0; i < message_listener_count; ++i) { - if (message_listeners[i]) { + for (size_t i = 0; i < message_listener_count; ++i) + { + if (message_listeners[i]) + { message_listeners[i](&msg); } } @@ -101,6 +115,6 @@ bool message_manager_post(const message_t *msg, TickType_t timeout) { if (!message_queue) return false; - ESP_LOGI(TAG, "Post: type=%d", msg->type); + ESP_LOGD(TAG, "Post: type=%d", msg->type); return xQueueSend(message_queue, msg, timeout) == pdTRUE; } diff --git a/firmware/components/persistence-manager/src/persistence_manager.c b/firmware/components/persistence-manager/src/persistence_manager.c index 07d668d..41260a7 100644 --- a/firmware/components/persistence-manager/src/persistence_manager.c +++ b/firmware/components/persistence-manager/src/persistence_manager.c @@ -27,7 +27,7 @@ esp_err_t persistence_manager_init(persistence_manager_t *pm, const char *nvs_na if (err == ESP_OK) { pm->initialized = true; - ESP_LOGI(TAG, "Initialized with namespace: %s", pm->nvs_namespace); + ESP_LOGD(TAG, "Initialized with namespace: %s", pm->nvs_namespace); return ESP_OK; } ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(err)); diff --git a/firmware/components/simulator/CMakeLists.txt b/firmware/components/simulator/CMakeLists.txt index de0f075..b19e264 100644 --- a/firmware/components/simulator/CMakeLists.txt +++ b/firmware/components/simulator/CMakeLists.txt @@ -5,5 +5,6 @@ idf_component_register(SRCS PRIV_REQUIRES led-manager persistence-manager + message-manager spiffs ) diff --git a/firmware/components/simulator/src/simulator.cpp b/firmware/components/simulator/src/simulator.cpp index 81211fc..e556ecc 100644 --- a/firmware/components/simulator/src/simulator.cpp +++ b/firmware/components/simulator/src/simulator.cpp @@ -2,6 +2,7 @@ #include "color.h" #include "led_strip_ws2812.h" +#include "message_manager.h" #include "persistence_manager.h" #include "storage.h" #include @@ -15,12 +16,12 @@ #include static const char *TAG = "simulator"; -static char *time; +static char *time = NULL; static char *time_to_string(int hhmm) { static char buffer[20]; - snprintf(buffer, sizeof(buffer), "%02d:%02d Uhr", hhmm / 100, hhmm % 100); + snprintf(buffer, sizeof(buffer), "%02d:%02d", hhmm / 100, hhmm % 100); return buffer; } @@ -235,6 +236,17 @@ static light_item_node_t *find_next_light_item_for_time(int hhmm) return next_item; } +static void send_simulation_message(const char *time, rgb_t color) +{ + message_t msg = {}; + msg.type = MESSAGE_TYPE_SIMULATION; + strncpy(msg.data.simulation.time, time, sizeof(msg.data.simulation.time) - 1); + msg.data.simulation.red = color.red; + msg.data.simulation.green = color.green; + msg.data.simulation.blue = color.blue; + message_manager_post(&msg, pdMS_TO_TICKS(100)); +} + void start_simulate_day(void) { initialize_light_items(); @@ -242,8 +254,9 @@ void start_simulate_day(void) light_item_node_t *current_item = find_best_light_item_for_time(1200); if (current_item != NULL) { - led_strip_update(LED_STATE_DAY, - (rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue}); + rgb_t color = {.red = current_item->red, .green = current_item->green, .blue = current_item->blue}; + led_strip_update(LED_STATE_DAY, color); + send_simulation_message("12:00", color); } } @@ -254,8 +267,9 @@ void start_simulate_night(void) light_item_node_t *current_item = find_best_light_item_for_time(0); if (current_item != NULL) { - led_strip_update(LED_STATE_NIGHT, - (rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue}); + rgb_t color = {.red = current_item->red, .green = current_item->green, .blue = current_item->blue}; + led_strip_update(LED_STATE_NIGHT, color); + send_simulation_message("00:00", color); } } @@ -296,45 +310,51 @@ void simulate_cycle(void *args) 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); - if (current_item != NULL && next_item != NULL) + if (current_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); + rgb_t color = {0, 0, 0}; - if (next_item_time_min < current_item_time_min) + // Use head as fallback if next_item is NULL + next_item = next_item ? next_item : head; + if (next_item != NULL) { - next_item_time_min += total_minutes_in_day; - } + 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 minutes_since_current_item_start = current_minute_of_day - current_item_time_min; - if (minutes_since_current_item_start < 0) + if (next_item_time_min < current_item_time_min) + { + next_item_time_min += 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) + { + minutes_since_current_item_start += total_minutes_in_day; + } + + int interval_duration = next_item_time_min - current_item_time_min; + if (interval_duration == 0) + { + interval_duration = 1; + } + + float interpolation_factor = (float)minutes_since_current_item_start / (float)interval_duration; + + // 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); + led_strip_update(LED_STATE_SIMULATION, color); + } + else { - minutes_since_current_item_start += total_minutes_in_day; + // 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); } - - int interval_duration = next_item_time_min - current_item_time_min; - if (interval_duration == 0) - { - interval_duration = 1; - } - - float interpolation_factor = (float)minutes_since_current_item_start / (float)interval_duration; - - // 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 - rgb_t final_rgb = interpolate_color(start_rgb, end_rgb, interpolation_factor); - - led_strip_update(LED_STATE_SIMULATION, final_rgb); - } - else if (current_item != NULL) - { - // No next item, just use current - led_strip_update( - LED_STATE_SIMULATION, - (rgb_t){.red = current_item->red, .green = current_item->green, .blue = current_item->blue}); + send_simulation_message(time, color); } vTaskDelay(pdMS_TO_TICKS(delay_ms)); diff --git a/firmware/storage/www/js/websocket.js b/firmware/storage/www/js/websocket.js index 56c8e39..0f84517 100644 --- a/firmware/storage/www/js/websocket.js +++ b/firmware/storage/www/js/websocket.js @@ -65,18 +65,32 @@ function updateStatusFromData(status) { } if (status.schema) { - document.getElementById('active-schema').value = status.schema; + const activeSchemaEl = document.getElementById('active-schema'); + if (activeSchemaEl) { + activeSchemaEl.value = status.schema; + } const schemaNames = { 'schema_01.csv': 'Schema 1', 'schema_02.csv': 'Schema 2', 'schema_03.csv': 'Schema 3' }; - document.getElementById('current-schema').textContent = schemaNames[status.schema] || status.schema; + const currentSchemaEl = document.getElementById('current-schema'); + if (currentSchemaEl) { + currentSchemaEl.textContent = schemaNames[status.schema] || status.schema; + } } if (status.color) { updateColorPreview(status.color.r, status.color.g, status.color.b); } + + // Update clock/time + if (status.clock) { + const clockEl = document.getElementById('current-clock'); + if (clockEl) { + clockEl.textContent = status.clock + ' ' + (typeof t === 'function' ? t('clock.suffix') : ''); + } + } } function updateColorPreview(r, g, b) {