update time on website via webSocket
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 4m44s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 4m37s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 4m22s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 4m24s

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2026-01-19 00:19:57 +01:00
parent b39a3be956
commit 501c2de874
11 changed files with 161 additions and 70 deletions

View File

@@ -11,7 +11,9 @@ idf_component_register(SRCS
esp_netif esp_netif
esp_event esp_event
json json
led-manager
simulator simulator
persistence-manager persistence-manager
message-manager message-manager
simulator
) )

View File

@@ -3,6 +3,7 @@
#include <cJSON.h> #include <cJSON.h>
void common_init(void);
cJSON *create_light_status_json(void); cJSON *create_light_status_json(void);
#endif // COMMON_H #endif // COMMON_H

View File

@@ -2,6 +2,7 @@
#include "api_handlers.h" #include "api_handlers.h"
#include "websocket_handler.h" #include "websocket_handler.h"
#include "common.h"
#include "storage.h" #include "storage.h"
#include <esp_http_server.h> #include <esp_http_server.h>
#include <esp_log.h> #include <esp_log.h>
@@ -86,6 +87,9 @@ static esp_err_t start_webserver(void)
return err; return err;
} }
// Common initialization
common_init();
ESP_LOGI(TAG, "HTTP server started successfully"); ESP_LOGI(TAG, "HTTP server started successfully");
return ESP_OK; return ESP_OK;
} }

View File

@@ -2,9 +2,40 @@
#include <cJSON.h> #include <cJSON.h>
#include <stdbool.h> #include <stdbool.h>
#include "api_server.h"
#include "color.h"
#include "message_manager.h"
#include "persistence_manager.h" #include "persistence_manager.h"
#include "simulator.h"
#include <string.h>
#include <time.h> #include <time.h>
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 // Gibt ein cJSON-Objekt with dem aktuellen Lichtstatus zurück
cJSON *create_light_status_json(void) cJSON *create_light_status_json(void)
{ {
@@ -36,19 +67,13 @@ cJSON *create_light_status_json(void)
persistence_manager_deinit(&pm); persistence_manager_deinit(&pm);
cJSON *color = cJSON_CreateObject(); cJSON *c = cJSON_CreateObject();
cJSON_AddNumberToObject(color, "r", 255); cJSON_AddNumberToObject(c, "r", color.red);
cJSON_AddNumberToObject(color, "g", 240); cJSON_AddNumberToObject(c, "g", color.green);
cJSON_AddNumberToObject(color, "b", 220); cJSON_AddNumberToObject(c, "b", color.blue);
cJSON_AddItemToObject(json, "color", color); cJSON_AddItemToObject(json, "color", c);
// Add current time as HH:MM only (suffix is handled in the frontend) cJSON_AddStringToObject(json, "clock", system_time);
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);
return json; return json;
} }

View File

@@ -45,7 +45,7 @@ void ClockScreenSaver::getCurrentTimeString(char *buffer, size_t bufferSize) con
char *simulated_time = get_time(); char *simulated_time = get_time();
if (simulated_time != nullptr) if (simulated_time != nullptr)
{ {
strncpy(buffer, simulated_time, bufferSize); snprintf(buffer, bufferSize, "%s Uhr", simulated_time);
return; return;
} }
} }

View File

@@ -13,7 +13,8 @@ extern "C"
typedef enum typedef enum
{ {
MESSAGE_TYPE_SETTINGS, MESSAGE_TYPE_SETTINGS,
MESSAGE_TYPE_BUTTON MESSAGE_TYPE_BUTTON,
MESSAGE_TYPE_SIMULATION
} message_type_t; } message_type_t;
typedef enum typedef enum
@@ -48,12 +49,21 @@ extern "C"
} value; } value;
} settings_message_t; } settings_message_t;
typedef struct
{
char time[6];
uint8_t red;
uint8_t green;
uint8_t blue;
} simulation_message_t;
typedef struct typedef struct
{ {
message_type_t type; message_type_t type;
union { union {
settings_message_t settings; settings_message_t settings;
button_message_t button; button_message_t button;
simulation_message_t simulation;
} data; } data;
} message_t; } message_t;

View File

@@ -17,21 +17,29 @@ static QueueHandle_t message_queue = NULL;
static message_listener_t message_listeners[MAX_MESSAGE_LISTENERS] = {0}; static message_listener_t message_listeners[MAX_MESSAGE_LISTENERS] = {0};
static size_t message_listener_count = 0; static size_t message_listener_count = 0;
void message_manager_register_listener(message_listener_t listener) { void message_manager_register_listener(message_listener_t listener)
if (listener && message_listener_count < MAX_MESSAGE_LISTENERS) { {
if (listener && message_listener_count < MAX_MESSAGE_LISTENERS)
{
// Doppelte Registrierung vermeiden // Doppelte Registrierung vermeiden
for (size_t i = 0; i < message_listener_count; ++i) { for (size_t i = 0; i < message_listener_count; ++i)
if (message_listeners[i] == listener) return; {
if (message_listeners[i] == listener)
return;
} }
message_listeners[message_listener_count++] = listener; message_listeners[message_listener_count++] = listener;
} }
} }
void message_manager_unregister_listener(message_listener_t 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) { for (size_t i = 0; i < message_listener_count; ++i)
{
if (message_listeners[i] == listener)
{
// Nachfolgende Listener nach vorne schieben // 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[j] = message_listeners[j + 1];
} }
message_listeners[--message_listener_count] = NULL; message_listeners[--message_listener_count] = NULL;
@@ -70,17 +78,23 @@ static void message_manager_task(void *param)
break; break;
} }
persistence_manager_deinit(&pm); 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; break;
case MESSAGE_TYPE_BUTTON: case MESSAGE_TYPE_BUTTON:
ESP_LOGI(TAG, "Button event: id=%d, type=%d", msg.data.button.button_id, msg.data.button.event_type); ESP_LOGD(TAG, "Button event: id=%d, type=%d", msg.data.button.button_id, msg.data.button.event_type);
// TODO: Weiterverarbeitung/Callback für Button-Events 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; break;
} }
// Observer Pattern: Listener benachrichtigen // Observer Pattern: Listener benachrichtigen
for (size_t i = 0; i < message_listener_count; ++i) { for (size_t i = 0; i < message_listener_count; ++i)
if (message_listeners[i]) { {
if (message_listeners[i])
{
message_listeners[i](&msg); message_listeners[i](&msg);
} }
} }
@@ -101,6 +115,6 @@ bool message_manager_post(const message_t *msg, TickType_t timeout)
{ {
if (!message_queue) if (!message_queue)
return false; 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; return xQueueSend(message_queue, msg, timeout) == pdTRUE;
} }

View File

@@ -27,7 +27,7 @@ esp_err_t persistence_manager_init(persistence_manager_t *pm, const char *nvs_na
if (err == ESP_OK) if (err == ESP_OK)
{ {
pm->initialized = true; 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; return ESP_OK;
} }
ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(err)); ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(err));

View File

@@ -5,5 +5,6 @@ idf_component_register(SRCS
PRIV_REQUIRES PRIV_REQUIRES
led-manager led-manager
persistence-manager persistence-manager
message-manager
spiffs spiffs
) )

View File

@@ -2,6 +2,7 @@
#include "color.h" #include "color.h"
#include "led_strip_ws2812.h" #include "led_strip_ws2812.h"
#include "message_manager.h"
#include "persistence_manager.h" #include "persistence_manager.h"
#include "storage.h" #include "storage.h"
#include <esp_heap_caps.h> #include <esp_heap_caps.h>
@@ -15,12 +16,12 @@
#include <string.h> #include <string.h>
static const char *TAG = "simulator"; static const char *TAG = "simulator";
static char *time; static char *time = NULL;
static char *time_to_string(int hhmm) static char *time_to_string(int hhmm)
{ {
static char buffer[20]; 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; return buffer;
} }
@@ -235,6 +236,17 @@ static light_item_node_t *find_next_light_item_for_time(int hhmm)
return next_item; 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) void start_simulate_day(void)
{ {
initialize_light_items(); 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); light_item_node_t *current_item = find_best_light_item_for_time(1200);
if (current_item != NULL) if (current_item != NULL)
{ {
led_strip_update(LED_STATE_DAY, rgb_t color = {.red = current_item->red, .green = current_item->green, .blue = current_item->blue};
(rgb_t){.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); light_item_node_t *current_item = find_best_light_item_for_time(0);
if (current_item != NULL) if (current_item != NULL)
{ {
led_strip_update(LED_STATE_NIGHT, rgb_t color = {.red = current_item->red, .green = current_item->green, .blue = current_item->blue};
(rgb_t){.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 *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 = 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); rgb_t color = {0, 0, 0};
int next_item_time_min = (atoi(next_item->time) / 100) * 60 + (atoi(next_item->time) % 100);
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 (next_item_time_min < current_item_time_min)
if (minutes_since_current_item_start < 0) {
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);
} }
send_simulation_message(time, 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});
} }
vTaskDelay(pdMS_TO_TICKS(delay_ms)); vTaskDelay(pdMS_TO_TICKS(delay_ms));

View File

@@ -65,18 +65,32 @@ function updateStatusFromData(status) {
} }
if (status.schema) { if (status.schema) {
document.getElementById('active-schema').value = status.schema; const activeSchemaEl = document.getElementById('active-schema');
if (activeSchemaEl) {
activeSchemaEl.value = status.schema;
}
const schemaNames = { const schemaNames = {
'schema_01.csv': 'Schema 1', 'schema_01.csv': 'Schema 1',
'schema_02.csv': 'Schema 2', 'schema_02.csv': 'Schema 2',
'schema_03.csv': 'Schema 3' '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) { if (status.color) {
updateColorPreview(status.color.r, status.color.g, status.color.b); 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) { function updateColorPreview(r, g, b) {