Compare commits
15 Commits
dc40acfd06
...
feature/sv
| Author | SHA1 | Date | |
|---|---|---|---|
|
99678087cb
|
|||
|
fe4bd11a21
|
|||
|
684ce36270
|
|||
|
98b5df1ff2
|
|||
|
8128b958cb
|
|||
|
955b4bef04
|
|||
|
81141d8859
|
|||
|
e01006cd49
|
|||
|
c28d7d08df
|
|||
|
df50aaedda
|
|||
|
1f02d35a97
|
|||
|
501c2de874
|
|||
|
b39a3be956
|
|||
|
3ec7bf7acb
|
|||
|
a12dfe7760
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -24,3 +24,5 @@
|
|||||||
**/*_front.png
|
**/*_front.png
|
||||||
**/*_schematic*.png
|
**/*_schematic*.png
|
||||||
**/wiki/*
|
**/wiki/*
|
||||||
|
*.FCBak
|
||||||
|
firmware/**/node_modules
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||||
|
|
||||||
|
idf_build_set_property(BOOTLOADER_EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/bootloader_components_extra/" APPEND)
|
||||||
|
|
||||||
project(system_control)
|
project(system_control)
|
||||||
|
|
||||||
|
target_add_binary_data(${PROJECT_NAME}.elf "main/isrgrootx1.pem" TEXT)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
idf_component_register(SRCS "hooks.c"
|
||||||
|
REQUIRES extra_component)
|
||||||
|
|
||||||
|
# We need to force GCC to integrate this static library into the
|
||||||
|
# bootloader link. Indeed, by default, as the hooks in the bootloader are weak,
|
||||||
|
# the linker would just ignore the symbols in the extra. (i.e. not strictly
|
||||||
|
# required)
|
||||||
|
# To do so, we need to define the symbol (function) `bootloader_hooks_include`
|
||||||
|
# within hooks.c source file.
|
||||||
21
firmware/bootloader_components/my_boot_hooks/hooks.c
Normal file
21
firmware/bootloader_components/my_boot_hooks/hooks.c
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
/* Function used to tell the linker to include this file
|
||||||
|
* with all its symbols.
|
||||||
|
*/
|
||||||
|
void bootloader_hooks_include(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep in my mind that a lot of functions cannot be called from here
|
||||||
|
* as system initialization has not been performed yet, including
|
||||||
|
* BSS, SPI flash, or memory protection. */
|
||||||
|
void bootloader_before_init(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI("HOOK", "This hook is called BEFORE bootloader initialization");
|
||||||
|
}
|
||||||
|
|
||||||
|
void bootloader_after_init(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI("HOOK", "This hook is called AFTER bootloader initialization");
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
idf_component_register(SRCS "extra_component.c")
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
|
*/
|
||||||
|
#include "esp_log.h"
|
||||||
|
|
||||||
|
void bootloader_extra_dir_function(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI("EXTRA", "This function is called from an extra component");
|
||||||
|
}
|
||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -2,14 +2,19 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "message_manager.h"
|
#include "message_manager.h"
|
||||||
|
|
||||||
|
#include "esp_heap_caps.h"
|
||||||
|
#include "led_segment.h"
|
||||||
|
#include "persistence_manager.h"
|
||||||
|
#include "storage.h"
|
||||||
#include <cJSON.h>
|
#include <cJSON.h>
|
||||||
#include <esp_http_server.h>
|
#include <esp_http_server.h>
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
#include <esp_wifi.h>
|
#include <esp_wifi.h>
|
||||||
#include <persistence_manager.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#define MAX_BODY_SIZE 4096
|
||||||
|
|
||||||
static const char *TAG = "api_handlers";
|
static const char *TAG = "api_handlers";
|
||||||
|
|
||||||
// Helper function to set CORS headers
|
// Helper function to set CORS headers
|
||||||
@@ -59,7 +64,7 @@ esp_err_t api_capabilities_get_handler(httpd_req_t *req)
|
|||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "GET /api/capabilities");
|
ESP_LOGI(TAG, "GET /api/capabilities");
|
||||||
|
|
||||||
// Thread nur für esp32c6 oder esp32h2 verfügbar
|
// Thread only available for esp32c6 or esp32h2
|
||||||
bool thread = false;
|
bool thread = false;
|
||||||
#if defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2)
|
#if defined(CONFIG_IDF_TARGET_ESP32C6) || defined(CONFIG_IDF_TARGET_ESP32H2)
|
||||||
thread = true;
|
thread = true;
|
||||||
@@ -90,7 +95,7 @@ esp_err_t api_wifi_scan_handler(httpd_req_t *req)
|
|||||||
|
|
||||||
uint16_t ap_num = 0;
|
uint16_t ap_num = 0;
|
||||||
esp_wifi_scan_get_ap_num(&ap_num);
|
esp_wifi_scan_get_ap_num(&ap_num);
|
||||||
wifi_ap_record_t *ap_list = calloc(ap_num, sizeof(wifi_ap_record_t));
|
wifi_ap_record_t *ap_list = heap_caps_calloc(ap_num, sizeof(wifi_ap_record_t), MALLOC_CAP_DEFAULT);
|
||||||
if (!ap_list)
|
if (!ap_list)
|
||||||
{
|
{
|
||||||
return send_error_response(req, 500, "Memory allocation failed");
|
return send_error_response(req, 500, "Memory allocation failed");
|
||||||
@@ -132,6 +137,7 @@ static bool is_valid(const cJSON *string)
|
|||||||
esp_err_t api_wifi_config_handler(httpd_req_t *req)
|
esp_err_t api_wifi_config_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "POST /api/wifi/config");
|
ESP_LOGI(TAG, "POST /api/wifi/config");
|
||||||
|
ESP_LOGI(TAG, "Request content length: %d", req->content_len);
|
||||||
|
|
||||||
char buf[256];
|
char buf[256];
|
||||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||||
@@ -159,7 +165,7 @@ esp_err_t api_wifi_config_handler(httpd_req_t *req)
|
|||||||
if (is_valid(pw))
|
if (is_valid(pw))
|
||||||
{
|
{
|
||||||
size_t pwlen = strlen(pw->valuestring);
|
size_t pwlen = strlen(pw->valuestring);
|
||||||
char *masked = malloc(pwlen + 1);
|
char *masked = heap_caps_malloc(pwlen + 1, MALLOC_CAP_DEFAULT);
|
||||||
if (masked)
|
if (masked)
|
||||||
{
|
{
|
||||||
memset(masked, '*', pwlen);
|
memset(masked, '*', pwlen);
|
||||||
@@ -245,6 +251,7 @@ esp_err_t api_wifi_status_handler(httpd_req_t *req)
|
|||||||
esp_err_t api_light_power_handler(httpd_req_t *req)
|
esp_err_t api_light_power_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "POST /api/light/power");
|
ESP_LOGI(TAG, "POST /api/light/power");
|
||||||
|
ESP_LOGI(TAG, "Request content length: %d", req->content_len);
|
||||||
|
|
||||||
char buf[64];
|
char buf[64];
|
||||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||||
@@ -298,6 +305,7 @@ esp_err_t api_light_thunder_handler(httpd_req_t *req)
|
|||||||
esp_err_t api_light_mode_handler(httpd_req_t *req)
|
esp_err_t api_light_mode_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "POST /api/light/mode");
|
ESP_LOGI(TAG, "POST /api/light/mode");
|
||||||
|
ESP_LOGI(TAG, "Request content length: %d", req->content_len);
|
||||||
|
|
||||||
char buf[64];
|
char buf[64];
|
||||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||||
@@ -333,7 +341,7 @@ esp_err_t api_light_mode_handler(httpd_req_t *req)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
msg.data.settings.value.int_value = -1; // Unbekannter Modus
|
msg.data.settings.value.int_value = -1; // Unknown mode
|
||||||
}
|
}
|
||||||
message_manager_post(&msg, pdMS_TO_TICKS(100));
|
message_manager_post(&msg, pdMS_TO_TICKS(100));
|
||||||
}
|
}
|
||||||
@@ -347,6 +355,7 @@ esp_err_t api_light_mode_handler(httpd_req_t *req)
|
|||||||
esp_err_t api_light_schema_handler(httpd_req_t *req)
|
esp_err_t api_light_schema_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "POST /api/light/schema");
|
ESP_LOGI(TAG, "POST /api/light/schema");
|
||||||
|
ESP_LOGI(TAG, "Request content length: %d", req->content_len);
|
||||||
|
|
||||||
char buf[128];
|
char buf[128];
|
||||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||||
@@ -400,31 +409,114 @@ esp_err_t api_wled_config_get_handler(httpd_req_t *req)
|
|||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "GET /api/wled/config");
|
ESP_LOGI(TAG, "GET /api/wled/config");
|
||||||
|
|
||||||
// TODO: Implement actual LED config retrieval
|
extern led_segment_t segments[LED_SEGMENT_MAX_LEN];
|
||||||
const char *response = "{"
|
extern size_t segment_count;
|
||||||
"\"segments\":["
|
size_t required_size = sizeof(segments) * segment_count;
|
||||||
"{\"name\":\"Main Light\",\"start\":0,\"leds\":60},"
|
|
||||||
"{\"name\":\"Accent Light\",\"start\":60,\"leds\":30}"
|
cJSON *json = cJSON_CreateObject();
|
||||||
"]"
|
|
||||||
"}";
|
persistence_manager_t pm;
|
||||||
return send_json_response(req, response);
|
if (persistence_manager_init(&pm, "led_config") == ESP_OK)
|
||||||
|
{
|
||||||
|
persistence_manager_get_blob(&pm, "segments", segments, required_size, NULL);
|
||||||
|
uint8_t segment_count = persistence_manager_get_int(&pm, "segment_count", 0);
|
||||||
|
persistence_manager_deinit(&pm);
|
||||||
|
|
||||||
|
cJSON *segments_arr = cJSON_CreateArray();
|
||||||
|
for (uint8_t i = 0; i < segment_count; ++i)
|
||||||
|
{
|
||||||
|
cJSON *seg = cJSON_CreateObject();
|
||||||
|
cJSON_AddStringToObject(seg, "name", segments[i].name);
|
||||||
|
cJSON_AddNumberToObject(seg, "start", segments[i].start);
|
||||||
|
cJSON_AddNumberToObject(seg, "leds", segments[i].leds);
|
||||||
|
cJSON_AddItemToArray(segments_arr, seg);
|
||||||
|
}
|
||||||
|
cJSON_AddItemToObject(json, "segments", segments_arr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cJSON_AddItemToObject(json, "segments", cJSON_CreateArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
char *response = cJSON_PrintUnformatted(json);
|
||||||
|
cJSON_Delete(json);
|
||||||
|
esp_err_t res = send_json_response(req, response);
|
||||||
|
free(response);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t api_wled_config_post_handler(httpd_req_t *req)
|
esp_err_t api_wled_config_post_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "POST /api/wled/config");
|
ESP_LOGI(TAG, "POST /api/wled/config");
|
||||||
|
ESP_LOGI(TAG, "Request content length: %d", req->content_len);
|
||||||
|
|
||||||
char buf[512];
|
char *buf = heap_caps_malloc(MAX_BODY_SIZE, MALLOC_CAP_DEFAULT);
|
||||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
if (!buf)
|
||||||
if (ret <= 0)
|
return send_error_response(req, 500, "Memory allocation failed");
|
||||||
|
int total = 0, ret;
|
||||||
|
while (total < MAX_BODY_SIZE - 1)
|
||||||
{
|
{
|
||||||
return send_error_response(req, 400, "Failed to receive request body");
|
ret = httpd_req_recv(req, buf + total, MAX_BODY_SIZE - 1 - total);
|
||||||
|
if (ret <= 0)
|
||||||
|
break;
|
||||||
|
total += ret;
|
||||||
}
|
}
|
||||||
buf[ret] = '\0';
|
buf[total] = '\0';
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Received WLED config: %s", buf);
|
ESP_LOGI(TAG, "Received WLED config: %s", buf);
|
||||||
|
|
||||||
// TODO: Parse JSON and save LED configuration
|
cJSON *json = cJSON_Parse(buf);
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
if (!json)
|
||||||
|
{
|
||||||
|
return send_error_response(req, 400, "Invalid JSON");
|
||||||
|
}
|
||||||
|
|
||||||
|
cJSON *segments_arr = cJSON_GetObjectItem(json, "segments");
|
||||||
|
if (!cJSON_IsArray(segments_arr))
|
||||||
|
{
|
||||||
|
cJSON_Delete(json);
|
||||||
|
return send_error_response(req, 400, "Missing segments array");
|
||||||
|
}
|
||||||
|
|
||||||
|
extern led_segment_t segments[LED_SEGMENT_MAX_LEN];
|
||||||
|
extern size_t segment_count;
|
||||||
|
size_t count = cJSON_GetArraySize(segments_arr);
|
||||||
|
if (count > LED_SEGMENT_MAX_LEN)
|
||||||
|
count = LED_SEGMENT_MAX_LEN;
|
||||||
|
segment_count = count;
|
||||||
|
for (size_t i = 0; i < LED_SEGMENT_MAX_LEN; ++i)
|
||||||
|
{
|
||||||
|
cJSON *seg = cJSON_GetArrayItem(segments_arr, i);
|
||||||
|
cJSON *name = cJSON_GetObjectItem(seg, "name");
|
||||||
|
cJSON *start = cJSON_GetObjectItem(seg, "start");
|
||||||
|
cJSON *leds = cJSON_GetObjectItem(seg, "leds");
|
||||||
|
if (cJSON_IsString(name) && cJSON_IsNumber(start) && cJSON_IsNumber(leds) && i < count)
|
||||||
|
{
|
||||||
|
strncpy(segments[i].name, name->valuestring, sizeof(segments[i].name) - 1);
|
||||||
|
segments[i].name[sizeof(segments[i].name) - 1] = '\0';
|
||||||
|
segments[i].start = (uint16_t)start->valuedouble;
|
||||||
|
segments[i].leds = (uint16_t)leds->valuedouble;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Invalid entry, skip or set defaults
|
||||||
|
segments[i].name[0] = '\0';
|
||||||
|
segments[i].start = 0;
|
||||||
|
segments[i].leds = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cJSON_Delete(json);
|
||||||
|
|
||||||
|
persistence_manager_t pm;
|
||||||
|
if (persistence_manager_init(&pm, "led_config") == ESP_OK)
|
||||||
|
{
|
||||||
|
persistence_manager_set_blob(&pm, "segments", segments, sizeof(led_segment_t) * segment_count);
|
||||||
|
persistence_manager_set_int(&pm, "segment_count", (int32_t)segment_count);
|
||||||
|
persistence_manager_deinit(&pm);
|
||||||
|
}
|
||||||
|
|
||||||
set_cors_headers(req);
|
set_cors_headers(req);
|
||||||
return httpd_resp_sendstr(req, "{\"status\":\"ok\"}");
|
return httpd_resp_sendstr(req, "{\"status\":\"ok\"}");
|
||||||
}
|
}
|
||||||
@@ -432,6 +524,16 @@ esp_err_t api_wled_config_post_handler(httpd_req_t *req)
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Schema API
|
// Schema API
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
static char *heap_caps_strdup(const char *src, uint32_t caps)
|
||||||
|
{
|
||||||
|
if (!src)
|
||||||
|
return NULL;
|
||||||
|
size_t len = strlen(src) + 1;
|
||||||
|
char *dst = heap_caps_malloc(len, caps);
|
||||||
|
if (dst)
|
||||||
|
memcpy(dst, src, len);
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
esp_err_t api_schema_get_handler(httpd_req_t *req)
|
esp_err_t api_schema_get_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
@@ -448,13 +550,11 @@ esp_err_t api_schema_get_handler(httpd_req_t *req)
|
|||||||
|
|
||||||
ESP_LOGI(TAG, "Requested schema: %s", filename);
|
ESP_LOGI(TAG, "Requested schema: %s", filename);
|
||||||
|
|
||||||
// Schema-Datei lesen
|
// Read schema file
|
||||||
char path[128];
|
char path[128];
|
||||||
snprintf(path, sizeof(path), "%s", filename);
|
snprintf(path, sizeof(path), "%s", filename);
|
||||||
|
|
||||||
int line_count = 0;
|
int line_count = 0;
|
||||||
extern char **read_lines_filtered(const char *filename, int *out_count);
|
|
||||||
extern void free_lines(char **lines, int count);
|
|
||||||
char **lines = read_lines_filtered(path, &line_count);
|
char **lines = read_lines_filtered(path, &line_count);
|
||||||
|
|
||||||
set_cors_headers(req);
|
set_cors_headers(req);
|
||||||
@@ -465,11 +565,11 @@ esp_err_t api_schema_get_handler(httpd_req_t *req)
|
|||||||
return httpd_resp_sendstr(req, "");
|
return httpd_resp_sendstr(req, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gesamtlänge berechnen
|
// Calculate total length
|
||||||
size_t total_len = 0;
|
size_t total_len = 0;
|
||||||
for (int i = 0; i < line_count; ++i)
|
for (int i = 0; i < line_count; ++i)
|
||||||
total_len += strlen(lines[i]) + 1;
|
total_len += strlen(lines[i]) + 1;
|
||||||
char *csv = malloc(total_len + 1);
|
char *csv = heap_caps_malloc(total_len + 1, MALLOC_CAP_DEFAULT);
|
||||||
char *p = csv;
|
char *p = csv;
|
||||||
for (int i = 0; i < line_count; ++i)
|
for (int i = 0; i < line_count; ++i)
|
||||||
{
|
{
|
||||||
@@ -488,28 +588,78 @@ esp_err_t api_schema_get_handler(httpd_req_t *req)
|
|||||||
esp_err_t api_schema_post_handler(httpd_req_t *req)
|
esp_err_t api_schema_post_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "POST /api/schema/*");
|
ESP_LOGI(TAG, "POST /api/schema/*");
|
||||||
|
ESP_LOGI(TAG, "Request content length: %d", req->content_len);
|
||||||
|
|
||||||
// Extract filename from URI
|
// Extract filename from URI
|
||||||
const char *uri = req->uri;
|
if (!req)
|
||||||
const char *filename = strrchr(uri, '/');
|
|
||||||
if (filename == NULL)
|
|
||||||
{
|
{
|
||||||
|
ESP_LOGE(TAG, "Request pointer is NULL");
|
||||||
|
return send_error_response(req, 500, "Internal error: req is NULL");
|
||||||
|
}
|
||||||
|
const char *uri = req->uri;
|
||||||
|
ESP_LOGI(TAG, "Request URI: %s", uri ? uri : "(null)");
|
||||||
|
if (!uri)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Request URI is NULL");
|
||||||
|
return send_error_response(req, 400, "Invalid schema path (no URI)");
|
||||||
|
}
|
||||||
|
const char *filename = strrchr(uri, '/');
|
||||||
|
if (filename == NULL || filename[1] == '\0')
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Could not extract filename from URI: %s", uri);
|
||||||
return send_error_response(req, 400, "Invalid schema path");
|
return send_error_response(req, 400, "Invalid schema path");
|
||||||
}
|
}
|
||||||
filename++;
|
filename++;
|
||||||
|
ESP_LOGI(TAG, "Extracted filename: %s", filename);
|
||||||
|
|
||||||
char buf[2048];
|
// Dynamically read POST body (like api_wled_config_post_handler)
|
||||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
char *buf = heap_caps_malloc(MAX_BODY_SIZE, MALLOC_CAP_DEFAULT);
|
||||||
if (ret <= 0)
|
if (!buf)
|
||||||
{
|
{
|
||||||
return send_error_response(req, 400, "Failed to receive request body");
|
ESP_LOGE(TAG, "Memory allocation failed for POST body");
|
||||||
|
return send_error_response(req, 500, "Memory allocation failed");
|
||||||
}
|
}
|
||||||
buf[ret] = '\0';
|
int total = 0, ret;
|
||||||
|
while (total < MAX_BODY_SIZE - 1)
|
||||||
|
{
|
||||||
|
ret = httpd_req_recv(req, buf + total, MAX_BODY_SIZE - 1 - total);
|
||||||
|
if (ret <= 0)
|
||||||
|
break;
|
||||||
|
total += ret;
|
||||||
|
}
|
||||||
|
buf[total] = '\0';
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Saving schema %s, size: %d bytes", filename, ret);
|
ESP_LOGI(TAG, "Saving schema %s, size: %d bytes", filename, total);
|
||||||
|
|
||||||
// TODO: Save schema to storage
|
// Split CSV body into line array
|
||||||
|
int line_count = 0;
|
||||||
|
// Count lines
|
||||||
|
for (int i = 0; i < total; ++i)
|
||||||
|
if (buf[i] == '\n')
|
||||||
|
line_count++;
|
||||||
|
if (total > 0 && buf[total - 1] != '\n')
|
||||||
|
line_count++; // last line without \n
|
||||||
|
|
||||||
|
char **lines = (char **)heap_caps_malloc(line_count * sizeof(char *), MALLOC_CAP_DEFAULT);
|
||||||
|
int idx = 0;
|
||||||
|
char *saveptr = NULL;
|
||||||
|
char *line = strtok_r(buf, "\n", &saveptr);
|
||||||
|
while (line && idx < line_count)
|
||||||
|
{
|
||||||
|
// Ignore empty lines
|
||||||
|
if (line[0] != '\0')
|
||||||
|
lines[idx++] = heap_caps_strdup(line, MALLOC_CAP_DEFAULT);
|
||||||
|
line = strtok_r(NULL, "\n", &saveptr);
|
||||||
|
}
|
||||||
|
int actual_count = idx;
|
||||||
|
esp_err_t err = write_lines(filename, lines, actual_count);
|
||||||
|
for (int i = 0; i < actual_count; ++i)
|
||||||
|
free(lines[i]);
|
||||||
|
free(lines);
|
||||||
set_cors_headers(req);
|
set_cors_headers(req);
|
||||||
|
|
||||||
|
if (err != ESP_OK)
|
||||||
|
return send_error_response(req, 500, "Failed to save schema");
|
||||||
return httpd_resp_sendstr(req, "{\"status\":\"ok\"}");
|
return httpd_resp_sendstr(req, "{\"status\":\"ok\"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,6 +682,7 @@ esp_err_t api_devices_scan_handler(httpd_req_t *req)
|
|||||||
esp_err_t api_devices_pair_handler(httpd_req_t *req)
|
esp_err_t api_devices_pair_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "POST /api/devices/pair");
|
ESP_LOGI(TAG, "POST /api/devices/pair");
|
||||||
|
ESP_LOGI(TAG, "Request content length: %d", req->content_len);
|
||||||
|
|
||||||
char buf[256];
|
char buf[256];
|
||||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||||
@@ -562,6 +713,7 @@ esp_err_t api_devices_paired_handler(httpd_req_t *req)
|
|||||||
esp_err_t api_devices_update_handler(httpd_req_t *req)
|
esp_err_t api_devices_update_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "POST /api/devices/update");
|
ESP_LOGI(TAG, "POST /api/devices/update");
|
||||||
|
ESP_LOGI(TAG, "Request content length: %d", req->content_len);
|
||||||
|
|
||||||
char buf[256];
|
char buf[256];
|
||||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||||
@@ -581,6 +733,7 @@ esp_err_t api_devices_update_handler(httpd_req_t *req)
|
|||||||
esp_err_t api_devices_unpair_handler(httpd_req_t *req)
|
esp_err_t api_devices_unpair_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "POST /api/devices/unpair");
|
ESP_LOGI(TAG, "POST /api/devices/unpair");
|
||||||
|
ESP_LOGI(TAG, "Request content length: %d", req->content_len);
|
||||||
|
|
||||||
char buf[128];
|
char buf[128];
|
||||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||||
@@ -600,6 +753,7 @@ esp_err_t api_devices_unpair_handler(httpd_req_t *req)
|
|||||||
esp_err_t api_devices_toggle_handler(httpd_req_t *req)
|
esp_err_t api_devices_toggle_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "POST /api/devices/toggle");
|
ESP_LOGI(TAG, "POST /api/devices/toggle");
|
||||||
|
ESP_LOGI(TAG, "Request content length: %d", req->content_len);
|
||||||
|
|
||||||
char buf[128];
|
char buf[128];
|
||||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||||
@@ -652,6 +806,7 @@ esp_err_t api_scenes_get_handler(httpd_req_t *req)
|
|||||||
esp_err_t api_scenes_post_handler(httpd_req_t *req)
|
esp_err_t api_scenes_post_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "POST /api/scenes");
|
ESP_LOGI(TAG, "POST /api/scenes");
|
||||||
|
ESP_LOGI(TAG, "Request content length: %d", req->content_len);
|
||||||
|
|
||||||
char buf[512];
|
char buf[512];
|
||||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||||
@@ -690,6 +845,7 @@ esp_err_t api_scenes_delete_handler(httpd_req_t *req)
|
|||||||
esp_err_t api_scenes_activate_handler(httpd_req_t *req)
|
esp_err_t api_scenes_activate_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "POST /api/scenes/activate");
|
ESP_LOGI(TAG, "POST /api/scenes/activate");
|
||||||
|
ESP_LOGI(TAG, "Request content length: %d", req->content_len);
|
||||||
|
|
||||||
char buf[128];
|
char buf[128];
|
||||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||||
@@ -746,7 +902,7 @@ esp_err_t api_static_file_handler(httpd_req_t *req)
|
|||||||
const char *uri = req->uri;
|
const char *uri = req->uri;
|
||||||
wifi_mode_t mode = 0;
|
wifi_mode_t mode = 0;
|
||||||
esp_wifi_get_mode(&mode);
|
esp_wifi_get_mode(&mode);
|
||||||
// Im AP-Modus immer captive.html ausliefern
|
// Always serve captive.html in AP mode
|
||||||
if (mode == WIFI_MODE_AP || mode == WIFI_MODE_APSTA)
|
if (mode == WIFI_MODE_AP || mode == WIFI_MODE_APSTA)
|
||||||
{
|
{
|
||||||
if (strcmp(uri, "/") == 0 || strcmp(uri, "/index.html") == 0)
|
if (strcmp(uri, "/") == 0 || strcmp(uri, "/index.html") == 0)
|
||||||
@@ -817,7 +973,7 @@ esp_err_t api_captive_portal_handler(httpd_req_t *req)
|
|||||||
{
|
{
|
||||||
ESP_LOGI(TAG, "Captive portal detection: %s", req->uri);
|
ESP_LOGI(TAG, "Captive portal detection: %s", req->uri);
|
||||||
|
|
||||||
// captive.html direkt ausliefern (Status 200, text/html)
|
// Serve captive.html directly (status 200, text/html)
|
||||||
const char *base_path = CONFIG_API_SERVER_STATIC_FILES_PATH;
|
const char *base_path = CONFIG_API_SERVER_STATIC_FILES_PATH;
|
||||||
char filepath[256];
|
char filepath[256];
|
||||||
snprintf(filepath, sizeof(filepath), "%s/captive.html", base_path);
|
snprintf(filepath, sizeof(filepath), "%s/captive.html", base_path);
|
||||||
@@ -826,7 +982,7 @@ esp_err_t api_captive_portal_handler(httpd_req_t *req)
|
|||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "captive.html not found: %s", filepath);
|
ESP_LOGE(TAG, "captive.html not found: %s", filepath);
|
||||||
httpd_resp_set_status(req, "500 Internal Server Error");
|
httpd_resp_set_status(req, "500 Internal Server Error");
|
||||||
httpd_resp_sendstr(req, "Captive Portal nicht verfügbar");
|
httpd_resp_sendstr(req, "Captive portal not available");
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
httpd_resp_set_type(req, "text/html");
|
httpd_resp_set_type(req, "text/html");
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,39 @@
|
|||||||
#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>
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -28,14 +60,20 @@ cJSON *create_light_status_json(void)
|
|||||||
}
|
}
|
||||||
cJSON_AddStringToObject(json, "mode", mode_str);
|
cJSON_AddStringToObject(json, "mode", mode_str);
|
||||||
|
|
||||||
cJSON_AddStringToObject(json, "schema", "schema_03.csv");
|
int variant = persistence_manager_get_int(&pm, "light_variant", 3);
|
||||||
cJSON *color = cJSON_CreateObject();
|
char schema_filename[20];
|
||||||
cJSON_AddNumberToObject(color, "r", 255);
|
snprintf(schema_filename, sizeof(schema_filename), "schema_%02d.csv", variant);
|
||||||
cJSON_AddNumberToObject(color, "g", 240);
|
cJSON_AddStringToObject(json, "schema", schema_filename);
|
||||||
cJSON_AddNumberToObject(color, "b", 220);
|
|
||||||
cJSON_AddItemToObject(json, "color", color);
|
|
||||||
|
|
||||||
persistence_manager_deinit(&pm);
|
persistence_manager_deinit(&pm);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
cJSON_AddStringToObject(json, "clock", system_time);
|
||||||
|
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
firmware/components/led-manager/include/led_segment.h
Normal file
16
firmware/components/led-manager/include/led_segment.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define LED_SEGMENT_MAX_LEN 15
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
char name[32];
|
||||||
|
uint16_t start;
|
||||||
|
uint16_t leds;
|
||||||
|
} led_segment_t;
|
||||||
|
|
||||||
|
led_segment_t segments[LED_SEGMENT_MAX_LEN];
|
||||||
|
size_t segment_count;
|
||||||
@@ -97,22 +97,21 @@ esp_err_t led_status_init(int gpio_num)
|
|||||||
.max_leds = STATUS_LED_COUNT,
|
.max_leds = STATUS_LED_COUNT,
|
||||||
.led_model = LED_MODEL_WS2812,
|
.led_model = LED_MODEL_WS2812,
|
||||||
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRBW,
|
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRBW,
|
||||||
.flags =
|
.flags = {.invert_out = 0},
|
||||||
{
|
|
||||||
.invert_out = false,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
led_strip_rmt_config_t rmt_config = {
|
led_strip_rmt_config_t rmt_config = {
|
||||||
.clk_src = RMT_CLK_SRC_DEFAULT,
|
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||||
.resolution_hz = 10 * 1000 * 1000, // 10MHz
|
.resolution_hz = 10 * 1000 * 1000, // 10MHz
|
||||||
.mem_block_symbols = 0,
|
.mem_block_symbols = 0,
|
||||||
.flags =
|
.flags = {.with_dma = 0},
|
||||||
{
|
|
||||||
.with_dma = false,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
|
esp_err_t ret = led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip);
|
||||||
ESP_LOGI(TAG, "LED strip initialized.");
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to init status LED: %s", esp_err_to_name(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Status LED initialized.");
|
||||||
|
|
||||||
// Create mutex
|
// Create mutex
|
||||||
mutex = xSemaphoreCreateMutex();
|
mutex = xSemaphoreCreateMutex();
|
||||||
|
|||||||
@@ -72,17 +72,21 @@ esp_err_t led_strip_init(void)
|
|||||||
.max_leds = MAX_LEDS,
|
.max_leds = MAX_LEDS,
|
||||||
.led_model = LED_MODEL_WS2812,
|
.led_model = LED_MODEL_WS2812,
|
||||||
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
|
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
|
||||||
.flags = {.invert_out = false},
|
.flags = {.invert_out = 0},
|
||||||
};
|
};
|
||||||
|
|
||||||
led_strip_rmt_config_t rmt_config = {
|
led_strip_rmt_config_t rmt_config = {
|
||||||
.clk_src = RMT_CLK_SRC_DEFAULT,
|
.clk_src = RMT_CLK_SRC_DEFAULT,
|
||||||
.resolution_hz = 0,
|
.resolution_hz = 0,
|
||||||
.mem_block_symbols = 0,
|
.mem_block_symbols = 0,
|
||||||
.flags = {.with_dma = true},
|
.flags = {.with_dma = 0},
|
||||||
};
|
};
|
||||||
|
esp_err_t ret = led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip);
|
||||||
ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip));
|
if (ret != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to init main LED strip: %s", esp_err_to_name(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
led_command_queue = xQueueCreate(5, sizeof(led_command_t));
|
led_command_queue = xQueueCreate(5, sizeof(led_command_t));
|
||||||
if (led_command_queue == NULL)
|
if (led_command_queue == NULL)
|
||||||
|
|||||||
@@ -3,4 +3,6 @@ idf_component_register(
|
|||||||
INCLUDE_DIRS "include"
|
INCLUDE_DIRS "include"
|
||||||
PRIV_REQUIRES
|
PRIV_REQUIRES
|
||||||
persistence-manager
|
persistence-manager
|
||||||
|
my_mqtt_client
|
||||||
|
app_update
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
#include "message_manager.h"
|
#include "message_manager.h"
|
||||||
|
#include "my_mqtt_client.h"
|
||||||
|
#include <esp_app_desc.h>
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
|
#include <esp_mac.h>
|
||||||
|
#include <esp_system.h>
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/queue.h>
|
#include <freertos/queue.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
#include <persistence_manager.h>
|
#include <persistence_manager.h>
|
||||||
|
#include <sdkconfig.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define MESSAGE_QUEUE_LENGTH 16
|
#define MESSAGE_QUEUE_LENGTH 16
|
||||||
@@ -17,21 +22,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,20 +83,35 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,6 +129,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;
|
||||||
}
|
}
|
||||||
|
|||||||
7
firmware/components/my_mqtt_client/CMakeLists.txt
Normal file
7
firmware/components/my_mqtt_client/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS "src/my_mqtt_client.c"
|
||||||
|
INCLUDE_DIRS "include"
|
||||||
|
REQUIRES
|
||||||
|
mqtt
|
||||||
|
app_update
|
||||||
|
)
|
||||||
21
firmware/components/my_mqtt_client/Kconfig
Normal file
21
firmware/components/my_mqtt_client/Kconfig
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
menu "MQTT Client Settings"
|
||||||
|
|
||||||
|
config MQTT_CLIENT_BROKER_URL
|
||||||
|
string "MQTT Broker URL (TLS)"
|
||||||
|
default "mqtts://example.com:8883"
|
||||||
|
help
|
||||||
|
Die Adresse des MQTT-Brokers (z.B. mqtts://broker.example.com:8883)
|
||||||
|
|
||||||
|
config MQTT_CLIENT_USERNAME
|
||||||
|
string "MQTT Username"
|
||||||
|
default "user"
|
||||||
|
help
|
||||||
|
Benutzername für die Authentifizierung (optional)
|
||||||
|
|
||||||
|
config MQTT_CLIENT_PASSWORD
|
||||||
|
string "MQTT Password"
|
||||||
|
default "password"
|
||||||
|
help
|
||||||
|
Passwort für die Authentifizierung (optional)
|
||||||
|
|
||||||
|
endmenu
|
||||||
18
firmware/components/my_mqtt_client/README.md
Normal file
18
firmware/components/my_mqtt_client/README.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# MQTT Client Component for ESP-IDF
|
||||||
|
|
||||||
|
Diese Komponente stellt eine einfache MQTT-Client-Implementierung bereit, die Daten an einen TLS-gesicherten MQTT-Broker sendet.
|
||||||
|
|
||||||
|
## Dateien
|
||||||
|
- mqtt_client.c: Implementierung des MQTT-Clients
|
||||||
|
- mqtt_client.h: Header-Datei
|
||||||
|
- CMakeLists.txt: Build-Konfiguration
|
||||||
|
- Kconfig: Konfiguration für die Komponente
|
||||||
|
|
||||||
|
## Abhängigkeiten
|
||||||
|
- ESP-IDF (empfohlen: >= v4.0)
|
||||||
|
- Komponenten: esp-mqtt, esp-tls
|
||||||
|
|
||||||
|
## Nutzung
|
||||||
|
1. Füge die Komponente in dein Projekt ein.
|
||||||
|
2. Passe die Konfiguration in `Kconfig` an.
|
||||||
|
3. Binde die Komponente in deinem Code ein und nutze die API aus `mqtt_client.h`.
|
||||||
2
firmware/components/my_mqtt_client/idf_component.yml
Normal file
2
firmware/components/my_mqtt_client/idf_component.yml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
dependencies:
|
||||||
|
espressif/mqtt: ^1.0.0
|
||||||
14
firmware/components/my_mqtt_client/include/my_mqtt_client.h
Normal file
14
firmware/components/my_mqtt_client/include/my_mqtt_client.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
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);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
122
firmware/components/my_mqtt_client/src/my_mqtt_client.c
Normal file
122
firmware/components/my_mqtt_client/src/my_mqtt_client.c
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#include "my_mqtt_client.h"
|
||||||
|
#include "esp_app_desc.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_interface.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_mac.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "mqtt_client.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
static const char *TAG = "mqtt_client";
|
||||||
|
static esp_mqtt_client_handle_t client = NULL;
|
||||||
|
|
||||||
|
extern const uint8_t isrgrootx1_pem_start[] asm("_binary_isrgrootx1_pem_start");
|
||||||
|
extern const uint8_t isrgrootx1_pem_end[] asm("_binary_isrgrootx1_pem_end");
|
||||||
|
|
||||||
|
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
|
||||||
|
{
|
||||||
|
esp_mqtt_event_handle_t event = event_data;
|
||||||
|
int msg_id;
|
||||||
|
|
||||||
|
switch ((esp_mqtt_event_id_t)event_id)
|
||||||
|
{
|
||||||
|
case MQTT_EVENT_CONNECTED:
|
||||||
|
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
|
||||||
|
msg_id = esp_mqtt_client_subscribe(client, "topic/qos0", 0);
|
||||||
|
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
|
||||||
|
msg_id = esp_mqtt_client_subscribe(client, "topic/qos1", 1);
|
||||||
|
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
|
||||||
|
msg_id = esp_mqtt_client_unsubscribe(client, "topic/qos1");
|
||||||
|
ESP_LOGI(TAG, "sent unsubscribe successful, msg_id=%d", msg_id);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MQTT_EVENT_DISCONNECTED:
|
||||||
|
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MQTT_EVENT_SUBSCRIBED:
|
||||||
|
ESP_LOGI(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d, return code=0x%02x ", event->msg_id, (uint8_t)*event->data);
|
||||||
|
msg_id = esp_mqtt_client_publish(client, "topic/qos0", "data", 0, 0, 0);
|
||||||
|
ESP_LOGI(TAG, "sent publish successful, msg_id=%d", 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:");
|
||||||
|
ESP_LOGI(TAG, "TOPIC=%.*s\r\n", event->topic_len, event->topic);
|
||||||
|
ESP_LOGI(TAG, "DATA=%.*s\r\n", event->data_len, event->data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MQTT_EVENT_ERROR:
|
||||||
|
ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
|
||||||
|
if (event->error_handle)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "error_type: %d", event->error_handle->error_type);
|
||||||
|
ESP_LOGE(TAG, "esp-tls error code: 0x%x", event->error_handle->esp_tls_last_esp_err);
|
||||||
|
ESP_LOGE(TAG, "tls_stack_err: 0x%x", event->error_handle->esp_tls_stack_err);
|
||||||
|
ESP_LOGE(TAG, "transport_sock_errno: %d", event->error_handle->esp_transport_sock_errno);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ESP_LOGI(TAG, "Other event id:%d", event->event_id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mqtt_client_start(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Starte MQTT-Client mit URI: %s", CONFIG_MQTT_CLIENT_BROKER_URL);
|
||||||
|
|
||||||
|
uint8_t mac[6];
|
||||||
|
esp_read_mac(mac, ESP_MAC_WIFI_STA);
|
||||||
|
const esp_app_desc_t *app_desc = esp_app_get_description();
|
||||||
|
char client_id[60];
|
||||||
|
snprintf(client_id, sizeof(client_id), "%s/%02x%02x", app_desc->project_name, mac[4], mac[5]);
|
||||||
|
|
||||||
|
const esp_mqtt_client_config_t mqtt_cfg = {
|
||||||
|
.broker.address.uri = CONFIG_MQTT_CLIENT_BROKER_URL,
|
||||||
|
.broker.verification.certificate = (const char *)isrgrootx1_pem_start,
|
||||||
|
.broker.verification.certificate_len = isrgrootx1_pem_end - isrgrootx1_pem_start,
|
||||||
|
.credentials.username = CONFIG_MQTT_CLIENT_USERNAME,
|
||||||
|
.credentials.client_id = client_id,
|
||||||
|
.credentials.authentication.password = CONFIG_MQTT_CLIENT_PASSWORD,
|
||||||
|
};
|
||||||
|
client = esp_mqtt_client_init(&mqtt_cfg);
|
||||||
|
if (client == NULL)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Fehler bei esp_mqtt_client_init!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
|
||||||
|
esp_err_t err = esp_mqtt_client_start(client);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "esp_mqtt_client_start fehlgeschlagen: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "MQTT-Client gestartet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "Publish aufgerufen, aber Client ist nicht initialisiert!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -205,6 +205,33 @@ extern "C"
|
|||||||
void persistence_manager_get_string(const persistence_manager_t *pm, const char *key, char *out_value,
|
void persistence_manager_get_string(const persistence_manager_t *pm, const char *key, char *out_value,
|
||||||
size_t max_len, const char *default_value);
|
size_t max_len, const char *default_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set a blob (binary data) value for a key in NVS storage.
|
||||||
|
*
|
||||||
|
* This function stores arbitrary binary data under the given key.
|
||||||
|
*
|
||||||
|
* @param pm Pointer to the persistence manager structure.
|
||||||
|
* @param key Key to set.
|
||||||
|
* @param value Pointer to the data to store.
|
||||||
|
* @param length Length of the data in bytes.
|
||||||
|
*/
|
||||||
|
void persistence_manager_set_blob(persistence_manager_t *pm, const char *key, const void *value, size_t length);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get a blob (binary data) value for a key from NVS storage.
|
||||||
|
*
|
||||||
|
* This function retrieves binary data previously stored under the given key.
|
||||||
|
*
|
||||||
|
* @param pm Pointer to the persistence manager structure.
|
||||||
|
* @param key Key to retrieve.
|
||||||
|
* @param out_value Buffer to store the retrieved data.
|
||||||
|
* @param max_length Maximum length of the output buffer in bytes.
|
||||||
|
* @param out_length Pointer to variable to receive the actual data length.
|
||||||
|
* @return true if the blob was found and read successfully, false otherwise.
|
||||||
|
*/
|
||||||
|
bool persistence_manager_get_blob(const persistence_manager_t *pm, const char *key, void *out_value,
|
||||||
|
size_t max_length, size_t *out_length);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
#include "persistence_manager.h"
|
#include "persistence_manager.h"
|
||||||
#include <esp_log.h>
|
#include <esp_log.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -27,7 +26,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));
|
||||||
@@ -174,6 +173,17 @@ void persistence_manager_set_string(persistence_manager_t *pm, const char *key,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void persistence_manager_set_blob(persistence_manager_t *pm, const char *key, const void *value, size_t length)
|
||||||
|
{
|
||||||
|
if (!persistence_manager_is_initialized(pm) || !value || length == 0)
|
||||||
|
return;
|
||||||
|
esp_err_t err = nvs_set_blob(pm->nvs_handle, key, value, length);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to set blob key '%s': %s", key, esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool persistence_manager_get_bool(const persistence_manager_t *pm, const char *key, bool default_value)
|
bool persistence_manager_get_bool(const persistence_manager_t *pm, const char *key, bool default_value)
|
||||||
{
|
{
|
||||||
if (!persistence_manager_is_initialized(pm))
|
if (!persistence_manager_is_initialized(pm))
|
||||||
@@ -245,3 +255,20 @@ void persistence_manager_get_string(const persistence_manager_t *pm, const char
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool persistence_manager_get_blob(const persistence_manager_t *pm, const char *key, void *out_value, size_t max_length,
|
||||||
|
size_t *out_length)
|
||||||
|
{
|
||||||
|
if (!persistence_manager_is_initialized(pm) || !out_value || max_length == 0)
|
||||||
|
return false;
|
||||||
|
size_t required_size = 0;
|
||||||
|
esp_err_t err = nvs_get_blob(pm->nvs_handle, key, NULL, &required_size);
|
||||||
|
if (err != ESP_OK || required_size == 0 || required_size > max_length)
|
||||||
|
return false;
|
||||||
|
err = nvs_get_blob(pm->nvs_handle, key, out_value, &required_size);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
return false;
|
||||||
|
if (out_length)
|
||||||
|
*out_length = required_size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,5 +5,6 @@ idf_component_register(SRCS
|
|||||||
PRIV_REQUIRES
|
PRIV_REQUIRES
|
||||||
led-manager
|
led-manager
|
||||||
persistence-manager
|
persistence-manager
|
||||||
|
message-manager
|
||||||
spiffs
|
spiffs
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C"
|
extern "C"
|
||||||
{
|
{
|
||||||
@@ -8,6 +10,14 @@ extern "C"
|
|||||||
void load_file(const char *filename);
|
void load_file(const char *filename);
|
||||||
char **read_lines_filtered(const char *filename, int *out_count);
|
char **read_lines_filtered(const char *filename, int *out_count);
|
||||||
void free_lines(char **lines, int count);
|
void free_lines(char **lines, int count);
|
||||||
|
/**
|
||||||
|
* Write an array of lines to a file (CSV or other text).
|
||||||
|
* @param filename File name (without /spiffs/)
|
||||||
|
* @param lines Array of lines (null-terminated strings)
|
||||||
|
* @param count Number of lines
|
||||||
|
* @return ESP_OK on success, error code otherwise
|
||||||
|
*/
|
||||||
|
esp_err_t write_lines(const char *filename, char **lines, int count);
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,10 +82,10 @@ esp_err_t add_light_item(const char time[5], uint8_t red, uint8_t green, uint8_t
|
|||||||
uint8_t brightness, uint8_t saturation)
|
uint8_t brightness, uint8_t saturation)
|
||||||
{
|
{
|
||||||
// Allocate memory for a new node in PSRAM.
|
// Allocate memory for a new node in PSRAM.
|
||||||
light_item_node_t *new_node = (light_item_node_t *)heap_caps_malloc(sizeof(light_item_node_t), MALLOC_CAP_SPIRAM);
|
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)
|
if (new_node == NULL)
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "Failed to allocate memory in PSRAM for new light_item_node_t.");
|
ESP_LOGE(TAG, "Failed to allocate memory for new light_item_node_t.");
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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));
|
||||||
@@ -353,7 +373,7 @@ void start_simulation_task(void)
|
|||||||
stop_simulation_task();
|
stop_simulation_task();
|
||||||
|
|
||||||
simulation_config_t *config =
|
simulation_config_t *config =
|
||||||
(simulation_config_t *)heap_caps_malloc(sizeof(simulation_config_t), MALLOC_CAP_SPIRAM);
|
(simulation_config_t *)heap_caps_malloc(sizeof(simulation_config_t), MALLOC_CAP_DEFAULT);
|
||||||
if (config == NULL)
|
if (config == NULL)
|
||||||
{
|
{
|
||||||
ESP_LOGE(TAG, "Failed to allocate memory for simulation config.");
|
ESP_LOGE(TAG, "Failed to allocate memory for simulation config.");
|
||||||
|
|||||||
@@ -130,3 +130,27 @@ void free_lines(char **lines, int count)
|
|||||||
free(lines[i]);
|
free(lines[i]);
|
||||||
free(lines);
|
free(lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_err_t write_lines(const char *filename, char **lines, int count)
|
||||||
|
{
|
||||||
|
char fullpath[128];
|
||||||
|
snprintf(fullpath, sizeof(fullpath), "/spiffs/%s", filename[0] == '/' ? filename + 1 : filename);
|
||||||
|
FILE *f = fopen(fullpath, "w");
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to open file for writing: %s", fullpath);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < count; ++i)
|
||||||
|
{
|
||||||
|
if (fprintf(f, "%s\n", lines[i]) < 0)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Failed to write line %d", i);
|
||||||
|
fclose(f);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
ESP_LOGI(TAG, "Wrote %d lines to %s", count, fullpath);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|||||||
13
firmware/debug-storybook.log
Normal file
13
firmware/debug-storybook.log
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[17:12:14.017] [INFO] [36mInitializing Storybook[39m
|
||||||
|
[17:12:14.195] [DEBUG] Getting package.json info for /Users/mars3142/Coding/git.mars3142.dev/system-control/firmware/package.json...
|
||||||
|
[17:12:14.195] [DEBUG] Getting CLI versions from NPM for storybook...
|
||||||
|
[17:12:14.195] [DEBUG] Executing command: npm info storybook version
|
||||||
|
[17:12:14.850] [INFO] Adding Storybook version 10.2.8 to your project
|
||||||
|
[17:12:14.851] [ERROR] Unable to initialize Storybook in this directory.
|
||||||
|
|
||||||
|
Storybook couldn't detect a supported framework or configuration for your project. Make sure you're inside a framework project (e.g., React, Vue, Svelte, Angular, Next.js) and that its dependencies are installed.
|
||||||
|
|
||||||
|
Tips:
|
||||||
|
- Run init in an empty directory or create a new framework app first.
|
||||||
|
- If this directory contains unrelated files, try a new directory for Storybook.
|
||||||
|
[17:12:14.853] [INFO] Storybook collects completely anonymous usage telemetry. We use it to shape Storybook's roadmap and prioritize features. You can learn more, including how to opt out, at https://storybook.js.org/telemetry
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
idf_component_register(SRCS
|
idf_component_register(SRCS
|
||||||
main.cpp
|
src/main.cpp
|
||||||
app_task.cpp
|
src/app_task.cpp
|
||||||
button_handling.c
|
src/button_handling.c
|
||||||
i2c_checker.c
|
src/i2c_checker.c
|
||||||
hal/u8g2_esp32_hal.c
|
src/hal/u8g2_esp32_hal.c
|
||||||
INCLUDE_DIRS "."
|
INCLUDE_DIRS "include"
|
||||||
PRIV_REQUIRES
|
PRIV_REQUIRES
|
||||||
analytics
|
analytics
|
||||||
insa
|
insa
|
||||||
@@ -21,6 +21,7 @@ idf_component_register(SRCS
|
|||||||
app_update
|
app_update
|
||||||
rmaker_common
|
rmaker_common
|
||||||
driver
|
driver
|
||||||
|
my_mqtt_client
|
||||||
)
|
)
|
||||||
|
|
||||||
spiffs_create_partition_image(storage ../storage FLASH_IN_PROJECT)
|
spiffs_create_partition_image(storage ../storage FLASH_IN_PROJECT)
|
||||||
|
|||||||
@@ -6,84 +6,6 @@ menu "System Control"
|
|||||||
help
|
help
|
||||||
Enable or disable WiFi connectivity.
|
Enable or disable WiFi connectivity.
|
||||||
|
|
||||||
config WIFI_NETWORK_COUNT
|
|
||||||
depends on WIFI_ENABLED
|
|
||||||
int "Number of WiFi Networks"
|
|
||||||
default 1
|
|
||||||
range 1 5
|
|
||||||
help
|
|
||||||
Number of WiFi networks to configure (1-5).
|
|
||||||
|
|
||||||
config WIFI_SSID_1
|
|
||||||
depends on WIFI_ENABLED
|
|
||||||
string "WiFi SSID 1"
|
|
||||||
default "YourSSID1"
|
|
||||||
help
|
|
||||||
The SSID of the first WiFi network.
|
|
||||||
|
|
||||||
config WIFI_PASSWORD_1
|
|
||||||
depends on WIFI_ENABLED
|
|
||||||
string "WiFi Password 1"
|
|
||||||
default "YourPassword1"
|
|
||||||
help
|
|
||||||
The password of the first WiFi network.
|
|
||||||
|
|
||||||
config WIFI_SSID_2
|
|
||||||
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 2
|
|
||||||
string "WiFi SSID 2"
|
|
||||||
default ""
|
|
||||||
help
|
|
||||||
The SSID of the second WiFi network.
|
|
||||||
|
|
||||||
config WIFI_PASSWORD_2
|
|
||||||
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 2
|
|
||||||
string "WiFi Password 2"
|
|
||||||
default ""
|
|
||||||
help
|
|
||||||
The password of the second WiFi network.
|
|
||||||
|
|
||||||
config WIFI_SSID_3
|
|
||||||
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 3
|
|
||||||
string "WiFi SSID 3"
|
|
||||||
default ""
|
|
||||||
help
|
|
||||||
The SSID of the third WiFi network.
|
|
||||||
|
|
||||||
config WIFI_PASSWORD_3
|
|
||||||
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 3
|
|
||||||
string "WiFi Password 3"
|
|
||||||
default ""
|
|
||||||
help
|
|
||||||
The password of the third WiFi network.
|
|
||||||
|
|
||||||
config WIFI_SSID_4
|
|
||||||
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 4
|
|
||||||
string "WiFi SSID 4"
|
|
||||||
default ""
|
|
||||||
help
|
|
||||||
The SSID of the fourth WiFi network.
|
|
||||||
|
|
||||||
config WIFI_PASSWORD_4
|
|
||||||
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 4
|
|
||||||
string "WiFi Password 4"
|
|
||||||
default ""
|
|
||||||
help
|
|
||||||
The password of the fourth WiFi network.
|
|
||||||
|
|
||||||
config WIFI_SSID_5
|
|
||||||
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 5
|
|
||||||
string "WiFi SSID 5"
|
|
||||||
default ""
|
|
||||||
help
|
|
||||||
The SSID of the fifth WiFi network.
|
|
||||||
|
|
||||||
config WIFI_PASSWORD_5
|
|
||||||
depends on WIFI_ENABLED && WIFI_NETWORK_COUNT >= 5
|
|
||||||
string "WiFi Password 5"
|
|
||||||
default ""
|
|
||||||
help
|
|
||||||
The password of the fifth WiFi network.
|
|
||||||
|
|
||||||
config WIFI_CONNECT_RETRIES
|
config WIFI_CONNECT_RETRIES
|
||||||
depends on WIFI_ENABLED
|
depends on WIFI_ENABLED
|
||||||
int "WiFi Connection Retry Attempts per Network"
|
int "WiFi Connection Retry Attempts per Network"
|
||||||
@@ -105,4 +27,42 @@ menu "System Control"
|
|||||||
help
|
help
|
||||||
GPIO pin number for the SCL line of the display.
|
GPIO pin number for the SCL line of the display.
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
|
menu "Button Configuration"
|
||||||
|
config BUTTON_UP
|
||||||
|
int "Button UP GPIO Pin"
|
||||||
|
default 1
|
||||||
|
help
|
||||||
|
GPIO pin number for the up button.
|
||||||
|
|
||||||
|
config BUTTON_DOWN
|
||||||
|
int "Button DOWN GPIO Pin"
|
||||||
|
default 6
|
||||||
|
help
|
||||||
|
GPIO pin number for the down button.
|
||||||
|
|
||||||
|
config BUTTON_LEFT
|
||||||
|
int "Button LEFT GPIO Pin"
|
||||||
|
default 3
|
||||||
|
help
|
||||||
|
GPIO pin number for the left button.
|
||||||
|
|
||||||
|
config BUTTON_RIGHT
|
||||||
|
int "Button RIGHT GPIO Pin"
|
||||||
|
default 5
|
||||||
|
help
|
||||||
|
GPIO pin number for the right button.
|
||||||
|
|
||||||
|
config BUTTON_SELECT
|
||||||
|
int "Button SELECT GPIO Pin"
|
||||||
|
default 18
|
||||||
|
help
|
||||||
|
GPIO pin number for the select button.
|
||||||
|
|
||||||
|
config BUTTON_BACK
|
||||||
|
int "Button BACK GPIO Pin"
|
||||||
|
default 16
|
||||||
|
help
|
||||||
|
GPIO pin number for the back button.
|
||||||
|
endmenu
|
||||||
endmenu
|
endmenu
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#define BUTTON_UP GPIO_NUM_1
|
|
||||||
#define BUTTON_DOWN GPIO_NUM_6
|
|
||||||
#define BUTTON_LEFT GPIO_NUM_3
|
|
||||||
#define BUTTON_RIGHT GPIO_NUM_5
|
|
||||||
#define BUTTON_SELECT GPIO_NUM_18
|
|
||||||
#define BUTTON_BACK GPIO_NUM_16
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
#include "i2c_checker.h"
|
|
||||||
|
|
||||||
#include "driver/i2c.h"
|
|
||||||
#include "esp_insights.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "hal/u8g2_esp32_hal.h"
|
|
||||||
|
|
||||||
static const char *TAG = "i2c_checker";
|
|
||||||
|
|
||||||
esp_err_t i2c_device_check(i2c_port_t i2c_port, uint8_t device_address)
|
|
||||||
{
|
|
||||||
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
|
|
||||||
i2c_master_start(cmd);
|
|
||||||
// Send the device address with the write bit (LSB = 0)
|
|
||||||
i2c_master_write_byte(cmd, (device_address << 1) | I2C_MASTER_WRITE, true);
|
|
||||||
i2c_master_stop(cmd);
|
|
||||||
|
|
||||||
esp_err_t ret = i2c_master_cmd_begin(i2c_port, cmd, pdMS_TO_TICKS(100));
|
|
||||||
|
|
||||||
i2c_cmd_link_delete(cmd);
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_err_t i2c_bus_scan_and_check(void)
|
|
||||||
{
|
|
||||||
// 1. Configure and install I2C bus
|
|
||||||
i2c_config_t conf = {
|
|
||||||
.mode = I2C_MODE_MASTER,
|
|
||||||
.sda_io_num = I2C_MASTER_SDA_PIN,
|
|
||||||
.scl_io_num = I2C_MASTER_SCL_PIN,
|
|
||||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
|
||||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
|
||||||
.master.clk_speed = I2C_MASTER_FREQ_HZ,
|
|
||||||
};
|
|
||||||
esp_err_t err = i2c_param_config(I2C_MASTER_NUM, &conf);
|
|
||||||
if (err != ESP_OK)
|
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "I2C parameter configuration failed: %s", esp_err_to_name(err));
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
err = i2c_driver_install(I2C_MASTER_NUM, conf.mode, 0, 0, 0);
|
|
||||||
if (err != ESP_OK)
|
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "I2C driver installation failed: %s", esp_err_to_name(err));
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "I2C driver initialized. Searching for device...");
|
|
||||||
|
|
||||||
// 2. Check if the device is present
|
|
||||||
err = i2c_device_check(I2C_MASTER_NUM, DISPLAY_I2C_ADDRESS);
|
|
||||||
|
|
||||||
if (err == ESP_OK)
|
|
||||||
{
|
|
||||||
ESP_LOGI(TAG, "Device found at address 0x%02X!", DISPLAY_I2C_ADDRESS);
|
|
||||||
// Here you could now call e.g. setup_screen()
|
|
||||||
}
|
|
||||||
else if (err == ESP_ERR_TIMEOUT)
|
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "Timeout! Device at address 0x%02X is not responding.", DISPLAY_I2C_ADDRESS);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ESP_LOGE(TAG, "Error communicating with address 0x%02X: %s", DISPLAY_I2C_ADDRESS, esp_err_to_name(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Uninstall I2C driver if it is no longer needed
|
|
||||||
i2c_driver_delete(I2C_MASTER_NUM);
|
|
||||||
ESP_DIAG_EVENT(TAG, "I2C driver uninstalled.");
|
|
||||||
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
10
firmware/main/include/common.h
Normal file
10
firmware/main/include/common.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
|
||||||
|
#define BUTTON_UP ((gpio_num_t)CONFIG_BUTTON_UP)
|
||||||
|
#define BUTTON_DOWN ((gpio_num_t)CONFIG_BUTTON_DOWN)
|
||||||
|
#define BUTTON_LEFT ((gpio_num_t)CONFIG_BUTTON_LEFT)
|
||||||
|
#define BUTTON_RIGHT ((gpio_num_t)CONFIG_BUTTON_RIGHT)
|
||||||
|
#define BUTTON_SELECT ((gpio_num_t)CONFIG_BUTTON_SELECT)
|
||||||
|
#define BUTTON_BACK ((gpio_num_t)CONFIG_BUTTON_BACK)
|
||||||
@@ -13,10 +13,11 @@
|
|||||||
#include "u8g2.h"
|
#include "u8g2.h"
|
||||||
|
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "driver/i2c.h"
|
|
||||||
#include "driver/spi_master.h"
|
#include "driver/spi_master.h"
|
||||||
#include "hal/i2c_types.h"
|
#include "hal/i2c_types.h"
|
||||||
|
|
||||||
|
#include "driver/i2c_master.h"
|
||||||
|
|
||||||
#define U8G2_ESP32_HAL_UNDEFINED GPIO_NUM_NC
|
#define U8G2_ESP32_HAL_UNDEFINED GPIO_NUM_NC
|
||||||
|
|
||||||
#define I2C_MASTER_NUM I2C_NUM_0 // I2C port number for master dev
|
#define I2C_MASTER_NUM I2C_NUM_0 // I2C port number for master dev
|
||||||
31
firmware/main/isrgrootx1.pem
Normal file
31
firmware/main/isrgrootx1.pem
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||||
|
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||||
|
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||||
|
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||||
|
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||||
|
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||||
|
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||||
|
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||||
|
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||||
|
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||||
|
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||||
|
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||||
|
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||||
|
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||||
|
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||||
|
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||||
|
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||||
|
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||||
|
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||||
|
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||||
|
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||||
|
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||||
|
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||||
|
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||||
|
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||||
|
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||||
|
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||||
|
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||||
|
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "i2c_checker.h"
|
#include "i2c_checker.h"
|
||||||
#include "led_status.h"
|
#include "led_status.h"
|
||||||
#include "message_manager.h"
|
#include "message_manager.h"
|
||||||
|
#include "my_mqtt_client.h"
|
||||||
#include "persistence_manager.h"
|
#include "persistence_manager.h"
|
||||||
#include "simulator.h"
|
#include "simulator.h"
|
||||||
#include "ui/ClockScreenSaver.h"
|
#include "ui/ClockScreenSaver.h"
|
||||||
@@ -36,7 +37,7 @@ uint8_t received_signal;
|
|||||||
std::shared_ptr<Widget> m_widget;
|
std::shared_ptr<Widget> m_widget;
|
||||||
std::vector<std::shared_ptr<Widget>> m_history;
|
std::vector<std::shared_ptr<Widget>> m_history;
|
||||||
std::unique_ptr<InactivityTracker> m_inactivityTracker;
|
std::unique_ptr<InactivityTracker> m_inactivityTracker;
|
||||||
// Persistence Manager für C-API
|
// Persistence Manager for C-API
|
||||||
persistence_manager_t g_persistence_manager;
|
persistence_manager_t g_persistence_manager;
|
||||||
|
|
||||||
extern QueueHandle_t buttonQueue;
|
extern QueueHandle_t buttonQueue;
|
||||||
@@ -196,10 +197,10 @@ void app_task(void *args)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display initialisieren, damit Info angezeigt werden kann
|
// Initialize display so that info can be shown
|
||||||
setup_screen();
|
setup_screen();
|
||||||
|
|
||||||
// BACK-Button prüfen und ggf. Einstellungen löschen (mit Countdown)
|
// Check BACK button and delete settings if necessary (with countdown)
|
||||||
gpio_config_t io_conf = {};
|
gpio_config_t io_conf = {};
|
||||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||||
io_conf.mode = GPIO_MODE_INPUT;
|
io_conf.mode = GPIO_MODE_INPUT;
|
||||||
@@ -224,7 +225,7 @@ void app_task(void *args)
|
|||||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
if (gpio_get_level(BUTTON_BACK) != 0)
|
if (gpio_get_level(BUTTON_BACK) != 0)
|
||||||
{
|
{
|
||||||
// Button losgelassen, abbrechen
|
// Button released, abort
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (i == 1)
|
if (i == 1)
|
||||||
@@ -252,6 +253,8 @@ void app_task(void *args)
|
|||||||
|
|
||||||
wifi_manager_init();
|
wifi_manager_init();
|
||||||
|
|
||||||
|
mqtt_client_start();
|
||||||
|
|
||||||
message_manager_register_listener(on_message_received);
|
message_manager_register_listener(on_message_received);
|
||||||
|
|
||||||
start_simulation();
|
start_simulation();
|
||||||
@@ -7,13 +7,17 @@
|
|||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
|
||||||
#include "u8g2_esp32_hal.h"
|
#include "hal/u8g2_esp32_hal.h"
|
||||||
|
|
||||||
static const char *TAG = "u8g2_hal";
|
static const char *TAG = "u8g2_hal";
|
||||||
static const unsigned int I2C_TIMEOUT_MS = 1000;
|
static const unsigned int I2C_TIMEOUT_MS = 1000;
|
||||||
|
|
||||||
static spi_device_handle_t handle_spi; // SPI handle.
|
static spi_device_handle_t handle_spi; // SPI handle.
|
||||||
static i2c_cmd_handle_t handle_i2c; // I2C handle.
|
static i2c_master_bus_handle_t i2c_bus; // I2C bus handle (new driver).
|
||||||
|
static i2c_master_dev_handle_t i2c_dev; // I2C device handle (new driver).
|
||||||
|
static uint8_t i2c_tx_buf[256]; // Buffer for one I2C transaction.
|
||||||
|
static size_t i2c_tx_len; // Current length in buffer.
|
||||||
|
static uint8_t current_i2c_addr7; // Current 7-bit device address.
|
||||||
static u8g2_esp32_hal_t u8g2_esp32_hal; // HAL state data.
|
static u8g2_esp32_hal_t u8g2_esp32_hal; // HAL state data.
|
||||||
static bool i2c_transfer_failed = false; // Flag to track I2C transfer errors
|
static bool i2c_transfer_failed = false; // Flag to track I2C transfer errors
|
||||||
|
|
||||||
@@ -148,21 +152,23 @@ uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
i2c_config_t conf = {0};
|
// Neue I2C-Master-API: Bus einmalig anlegen
|
||||||
conf.mode = I2C_MODE_MASTER;
|
if (i2c_bus == NULL)
|
||||||
ESP_LOGI(TAG, "sda_io_num %d", u8g2_esp32_hal.bus.i2c.sda);
|
{
|
||||||
conf.sda_io_num = u8g2_esp32_hal.bus.i2c.sda;
|
i2c_master_bus_config_t bus_cfg = {
|
||||||
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
|
.i2c_port = I2C_MASTER_NUM,
|
||||||
ESP_LOGI(TAG, "scl_io_num %d", u8g2_esp32_hal.bus.i2c.scl);
|
.scl_io_num = u8g2_esp32_hal.bus.i2c.scl,
|
||||||
conf.scl_io_num = u8g2_esp32_hal.bus.i2c.scl;
|
.sda_io_num = u8g2_esp32_hal.bus.i2c.sda,
|
||||||
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
|
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||||
ESP_LOGI(TAG, "clk_speed %d", I2C_MASTER_FREQ_HZ);
|
.flags = {.enable_internal_pullup = true},
|
||||||
conf.master.clk_speed = I2C_MASTER_FREQ_HZ;
|
};
|
||||||
ESP_LOGI(TAG, "i2c_param_config %d", conf.mode);
|
|
||||||
ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_NUM, &conf));
|
ESP_LOGI(TAG, "sda_io_num %d", u8g2_esp32_hal.bus.i2c.sda);
|
||||||
ESP_LOGI(TAG, "i2c_driver_install %d", I2C_MASTER_NUM);
|
ESP_LOGI(TAG, "scl_io_num %d", u8g2_esp32_hal.bus.i2c.scl);
|
||||||
ESP_ERROR_CHECK(
|
ESP_LOGI(TAG, "clk_speed %d", I2C_MASTER_FREQ_HZ);
|
||||||
i2c_driver_install(I2C_MASTER_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0));
|
ESP_LOGI(TAG, "i2c_new_master_bus %d", I2C_MASTER_NUM);
|
||||||
|
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &i2c_bus));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,37 +180,55 @@ uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void
|
|||||||
uint8_t *data_ptr = (uint8_t *)arg_ptr;
|
uint8_t *data_ptr = (uint8_t *)arg_ptr;
|
||||||
ESP_LOG_BUFFER_HEXDUMP(TAG, data_ptr, arg_int, ESP_LOG_VERBOSE);
|
ESP_LOG_BUFFER_HEXDUMP(TAG, data_ptr, arg_int, ESP_LOG_VERBOSE);
|
||||||
|
|
||||||
while (arg_int > 0)
|
// Bytes in lokalen Puffer sammeln, tatsächliche Übertragung bei END_TRANSFER
|
||||||
|
if (i2c_tx_len + (size_t)arg_int > sizeof(i2c_tx_buf))
|
||||||
{
|
{
|
||||||
I2C_ERROR_CHECK(i2c_master_write_byte(handle_i2c, *data_ptr, ACK_CHECK_EN));
|
ESP_LOGW(TAG, "I2C tx buffer overflow (%zu + %d)", i2c_tx_len, arg_int);
|
||||||
if (i2c_transfer_failed)
|
i2c_transfer_failed = true;
|
||||||
{
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
data_ptr++;
|
|
||||||
arg_int--;
|
|
||||||
}
|
}
|
||||||
|
memcpy(&i2c_tx_buf[i2c_tx_len], data_ptr, arg_int);
|
||||||
|
i2c_tx_len += (size_t)arg_int;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case U8X8_MSG_BYTE_START_TRANSFER: {
|
case U8X8_MSG_BYTE_START_TRANSFER: {
|
||||||
uint8_t i2c_address = u8x8_GetI2CAddress(u8x8);
|
uint8_t i2c_address = u8x8_GetI2CAddress(u8x8);
|
||||||
handle_i2c = i2c_cmd_link_create();
|
|
||||||
i2c_transfer_failed = false; // Reset error flag at start of transfer
|
|
||||||
ESP_LOGD(TAG, "Start I2C transfer to %02X.", i2c_address >> 1);
|
ESP_LOGD(TAG, "Start I2C transfer to %02X.", i2c_address >> 1);
|
||||||
I2C_ERROR_CHECK(i2c_master_start(handle_i2c));
|
i2c_transfer_failed = false; // Reset error flag at start of transfer
|
||||||
I2C_ERROR_CHECK(i2c_master_write_byte(handle_i2c, i2c_address | I2C_MASTER_WRITE, ACK_CHECK_EN));
|
|
||||||
|
// Für neuen Treiber: Device-Handle für diese 7-Bit-Adresse anlegen (oder wiederverwenden)
|
||||||
|
uint8_t addr7 = i2c_address >> 1;
|
||||||
|
if (i2c_dev == NULL || addr7 != current_i2c_addr7)
|
||||||
|
{
|
||||||
|
if (i2c_dev)
|
||||||
|
{
|
||||||
|
i2c_master_bus_rm_device(i2c_dev);
|
||||||
|
i2c_dev = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_device_config_t dev_cfg = {
|
||||||
|
.device_address = addr7,
|
||||||
|
.scl_speed_hz = I2C_MASTER_FREQ_HZ,
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus, &dev_cfg, &i2c_dev));
|
||||||
|
current_i2c_addr7 = addr7;
|
||||||
|
}
|
||||||
|
i2c_tx_len = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case U8X8_MSG_BYTE_END_TRANSFER: {
|
case U8X8_MSG_BYTE_END_TRANSFER: {
|
||||||
ESP_LOGD(TAG, "End I2C transfer.");
|
ESP_LOGD(TAG, "End I2C transfer.");
|
||||||
if (!i2c_transfer_failed)
|
if (!i2c_transfer_failed && i2c_dev != NULL && i2c_tx_len > 0)
|
||||||
{
|
{
|
||||||
I2C_ERROR_CHECK(i2c_master_stop(handle_i2c));
|
esp_err_t rc = i2c_master_transmit(i2c_dev, i2c_tx_buf, i2c_tx_len, I2C_TIMEOUT_MS);
|
||||||
I2C_ERROR_CHECK(i2c_master_cmd_begin(I2C_MASTER_NUM, handle_i2c, pdMS_TO_TICKS(I2C_TIMEOUT_MS)));
|
if (rc != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "I2C error: i2c_master_transmit = %d", rc);
|
||||||
|
i2c_transfer_failed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i2c_cmd_link_delete(handle_i2c);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
63
firmware/main/src/i2c_checker.c
Normal file
63
firmware/main/src/i2c_checker.c
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#include "i2c_checker.h"
|
||||||
|
|
||||||
|
#include "driver/i2c_master.h"
|
||||||
|
#include "esp_insights.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "hal/u8g2_esp32_hal.h"
|
||||||
|
|
||||||
|
static const char *TAG = "i2c_checker";
|
||||||
|
|
||||||
|
static esp_err_t i2c_device_check(i2c_master_bus_handle_t i2c_bus, uint8_t device_address)
|
||||||
|
{
|
||||||
|
// Use the new I2C master driver to probe for the device.
|
||||||
|
return i2c_master_probe(i2c_bus, device_address, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2c_bus_scan_and_check(void)
|
||||||
|
{
|
||||||
|
// 1. Configure and create I2C master bus using the new driver API
|
||||||
|
i2c_master_bus_handle_t i2c_bus = NULL;
|
||||||
|
i2c_master_bus_config_t bus_cfg = {
|
||||||
|
.i2c_port = I2C_MASTER_NUM,
|
||||||
|
.scl_io_num = I2C_MASTER_SCL_PIN,
|
||||||
|
.sda_io_num = I2C_MASTER_SDA_PIN,
|
||||||
|
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||||
|
.flags = {.enable_internal_pullup = true},
|
||||||
|
};
|
||||||
|
|
||||||
|
esp_err_t err = i2c_new_master_bus(&bus_cfg, &i2c_bus);
|
||||||
|
if (err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "I2C bus creation failed: %s", esp_err_to_name(err));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "I2C master bus initialized. Searching for device...");
|
||||||
|
|
||||||
|
// 2. Check if the device is present using the new API
|
||||||
|
err = i2c_device_check(i2c_bus, DISPLAY_I2C_ADDRESS);
|
||||||
|
|
||||||
|
if (err == ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Device found at address 0x%02X!", DISPLAY_I2C_ADDRESS);
|
||||||
|
// Here you could now call e.g. setup_screen()
|
||||||
|
}
|
||||||
|
else if (err == ESP_ERR_TIMEOUT)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Timeout! Device at address 0x%02X is not responding.", DISPLAY_I2C_ADDRESS);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "Error communicating with address 0x%02X: %s", DISPLAY_I2C_ADDRESS, esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Delete I2C master bus if it is no longer needed
|
||||||
|
esp_err_t del_err = i2c_del_master_bus(i2c_bus);
|
||||||
|
if (del_err != ESP_OK)
|
||||||
|
{
|
||||||
|
ESP_LOGW(TAG, "Failed to delete I2C master bus: %s", esp_err_to_name(del_err));
|
||||||
|
}
|
||||||
|
ESP_DIAG_EVENT(TAG, "I2C master bus deleted.");
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
@@ -45,3 +45,8 @@ CONFIG_SPIRAM_USE_CAPS_ALLOC=y
|
|||||||
|
|
||||||
# HTTP Server WebSocket Support
|
# HTTP Server WebSocket Support
|
||||||
CONFIG_HTTPD_WS_SUPPORT=y
|
CONFIG_HTTPD_WS_SUPPORT=y
|
||||||
|
|
||||||
|
# MQTT
|
||||||
|
CONFIG_MQTT_CLIENT_BROKER_URL="mqtts://mqtt.mars3142.dev:8883"
|
||||||
|
CONFIG_MQTT_CLIENT_USERNAME="mars3142"
|
||||||
|
CONFIG_MQTT_CLIENT_PASSWORD="KPkEyzs9aur3Y7LfEybnd8PsxWd94ouQZGNGJ24y"
|
||||||
|
|||||||
@@ -1,2 +1,20 @@
|
|||||||
# default ESP target
|
# default ESP target
|
||||||
CONFIG_IDF_TARGET="esp32c6"
|
CONFIG_IDF_TARGET="esp32c6"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Display Settings
|
||||||
|
#
|
||||||
|
CONFIG_DISPLAY_SDA_PIN=9
|
||||||
|
CONFIG_DISPLAY_SCL_PIN=8
|
||||||
|
# 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
|
||||||
|
# end of Button Configuration
|
||||||
|
|||||||
@@ -557,6 +557,11 @@ body {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-item.visible {
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-item:last-child {
|
.status-item:last-child {
|
||||||
|
|||||||
1574
firmware/storage/www/css/style.css
Normal file
1574
firmware/storage/www/css/style.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -92,18 +92,18 @@
|
|||||||
<div class="control-section">
|
<div class="control-section">
|
||||||
<h3 data-i18n="control.status.title">Aktueller Status</h3>
|
<h3 data-i18n="control.status.title">Aktueller Status</h3>
|
||||||
<div class="status-display">
|
<div class="status-display">
|
||||||
<div class="status-item">
|
<div class="status-item visible">
|
||||||
<span class="status-label" data-i18n="control.status.mode">Modus</span>
|
<span class="status-label" data-i18n="control.status.mode">Modus</span>
|
||||||
<span class="status-value" id="current-mode" data-i18n="mode.simulation">Simulation</span>
|
<span class="status-value" id="current-mode" data-i18n="mode.simulation">Simulation</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-item">
|
<div class="status-item visible">
|
||||||
<span class="status-label" data-i18n="control.status.schema">Schema</span>
|
|
||||||
<span class="status-value" id="current-schema">Schema 1</span>
|
|
||||||
</div>
|
|
||||||
<div class="status-item">
|
|
||||||
<span class="status-label" data-i18n="control.status.color">Aktuelle Farbe</span>
|
<span class="status-label" data-i18n="control.status.color">Aktuelle Farbe</span>
|
||||||
<div class="current-color-preview" id="current-color"></div>
|
<div class="current-color-preview" id="current-color"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="status-item">
|
||||||
|
<span class="status-label" data-i18n="control.status.clock">Uhrzeit</span>
|
||||||
|
<span class="status-value" id="current-clock">--:-- Uhr</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ document.addEventListener('touchend', (e) => {
|
|||||||
lastTouchEnd = now;
|
lastTouchEnd = now;
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
// Initialization
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
initI18n();
|
initI18n();
|
||||||
initTheme();
|
initTheme();
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const translations = {
|
|||||||
'control.status.mode': 'Modus',
|
'control.status.mode': 'Modus',
|
||||||
'control.status.schema': 'Schema',
|
'control.status.schema': 'Schema',
|
||||||
'control.status.color': 'Aktuelle Farbe',
|
'control.status.color': 'Aktuelle Farbe',
|
||||||
|
'control.status.clock': 'Uhrzeit',
|
||||||
|
|
||||||
// Common
|
// Common
|
||||||
'common.on': 'AN',
|
'common.on': 'AN',
|
||||||
@@ -174,7 +175,8 @@ const translations = {
|
|||||||
// General
|
// General
|
||||||
'loading': 'Laden...',
|
'loading': 'Laden...',
|
||||||
'error': 'Fehler',
|
'error': 'Fehler',
|
||||||
'success': 'Erfolg'
|
'success': 'Erfolg',
|
||||||
|
'clock.suffix': 'Uhr'
|
||||||
},
|
},
|
||||||
|
|
||||||
en: {
|
en: {
|
||||||
@@ -216,6 +218,7 @@ const translations = {
|
|||||||
'control.status.mode': 'Mode',
|
'control.status.mode': 'Mode',
|
||||||
'control.status.schema': 'Schema',
|
'control.status.schema': 'Schema',
|
||||||
'control.status.color': 'Current Color',
|
'control.status.color': 'Current Color',
|
||||||
|
'control.status.clock': "Time",
|
||||||
|
|
||||||
// Common
|
// Common
|
||||||
'common.on': 'ON',
|
'common.on': 'ON',
|
||||||
@@ -349,7 +352,8 @@ const translations = {
|
|||||||
// General
|
// General
|
||||||
'loading': 'Loading...',
|
'loading': 'Loading...',
|
||||||
'error': 'Error',
|
'error': 'Error',
|
||||||
'success': 'Success'
|
'success': 'Success',
|
||||||
|
'clock.suffix': "o'clock"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,20 @@ function updateSimulationOptions() {
|
|||||||
} else {
|
} else {
|
||||||
options.classList.remove('visible');
|
options.classList.remove('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[
|
||||||
|
'control.status.clock'
|
||||||
|
].forEach(i18nKey => {
|
||||||
|
const label = document.querySelector(`.status-item .status-label[data-i18n="${i18nKey}"]`);
|
||||||
|
const item = label ? label.closest('.status-item') : null;
|
||||||
|
if (item) {
|
||||||
|
if (currentMode === 'simulation') {
|
||||||
|
item.classList.add('visible');
|
||||||
|
} else {
|
||||||
|
item.classList.remove('visible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setActiveSchema() {
|
async function setActiveSchema() {
|
||||||
@@ -173,8 +187,6 @@ async function loadLightStatus() {
|
|||||||
// Update schema
|
// Update schema
|
||||||
if (status.schema) {
|
if (status.schema) {
|
||||||
document.getElementById('active-schema').value = status.schema;
|
document.getElementById('active-schema').value = status.schema;
|
||||||
const schemaNum = status.schema.replace('schema_0', '').replace('.csv', '');
|
|
||||||
document.getElementById('current-schema').textContent = t(`schema.name.${schemaNum}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update current color
|
// Update current color
|
||||||
@@ -184,6 +196,15 @@ async function loadLightStatus() {
|
|||||||
colorPreview.style.backgroundColor = `rgb(${status.color.r}, ${status.color.g}, ${status.color.b})`;
|
colorPreview.style.backgroundColor = `rgb(${status.color.r}, ${status.color.g}, ${status.color.b})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update clock/time
|
||||||
|
if (status.clock) {
|
||||||
|
const clockEl = document.getElementById('current-clock');
|
||||||
|
if (clockEl) {
|
||||||
|
// Use one translation key for the suffix, language is handled by t()
|
||||||
|
clockEl.textContent = status.clock + ' ' + t('clock.suffix');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Light status not available');
|
console.log('Light status not available');
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
12
firmware/website.bak/.run/dev.run.xml
Normal file
12
firmware/website.bak/.run/dev.run.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="dev" type="js.build_tools.npm" nameIsGenerated="true">
|
||||||
|
<package-json value="$PROJECT_DIR$/package.json" />
|
||||||
|
<command value="run" />
|
||||||
|
<scripts>
|
||||||
|
<script value="dev" />
|
||||||
|
</scripts>
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<envs />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
8
firmware/website.bak/.vite/deps/_metadata.json
Normal file
8
firmware/website.bak/.vite/deps/_metadata.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"hash": "15486339",
|
||||||
|
"configHash": "5ec1f82b",
|
||||||
|
"lockfileHash": "2bc40369",
|
||||||
|
"browserHash": "9efd6930",
|
||||||
|
"optimized": {},
|
||||||
|
"chunks": {}
|
||||||
|
}
|
||||||
3
firmware/website.bak/.vite/deps/package.json
Normal file
3
firmware/website.bak/.vite/deps/package.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"type": "module"
|
||||||
|
}
|
||||||
19
firmware/website.bak/index.html
Normal file
19
firmware/website.bak/index.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<meta name="theme-color" content="#1a1a2e">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
|
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||||
|
<title>System Control</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
32
firmware/website.bak/jsconfig.json
Normal file
32
firmware/website.bak/jsconfig.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
/**
|
||||||
|
* svelte-preprocess cannot figure out whether you have
|
||||||
|
* a value or a type, so tell TypeScript to enforce using
|
||||||
|
* `import type` instead of `import` for Types.
|
||||||
|
*/
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
/**
|
||||||
|
* To have warnings / errors of the Svelte compiler at the
|
||||||
|
* correct position, enable source maps by default.
|
||||||
|
*/
|
||||||
|
"sourceMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
/**
|
||||||
|
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||||
|
* Disable this if you'd like to use dynamic types.
|
||||||
|
*/
|
||||||
|
"checkJs": true
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Use global.d.ts instead of compilerOptions.types
|
||||||
|
* to avoid limiting type declarations.
|
||||||
|
*/
|
||||||
|
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||||
|
}
|
||||||
1560
firmware/website.bak/package-lock.json
generated
Normal file
1560
firmware/website.bak/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
firmware/website.bak/package.json
Normal file
20
firmware/website.bak/package.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "learn-svelte",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^6.1.1",
|
||||||
|
"svelte": "^5.38.1",
|
||||||
|
"vite": "^7.1.2",
|
||||||
|
"vite-plugin-compression": "^0.5.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource/atkinson-hyperlegible": "^5.2.6",
|
||||||
|
"@picocss/pico": "^2.1.1",
|
||||||
|
"gsap": "^3.13.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
firmware/website.bak/public/favicon.svg
Normal file
1
firmware/website.bak/public/favicon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">🚂</text></svg>
|
||||||
|
After Width: | Height: | Size: 109 B |
27
firmware/website.bak/src/App.svelte
Normal file
27
firmware/website.bak/src/App.svelte
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Header from "./compoents/Header.svelte";
|
||||||
|
import Index from "./Index.svelte";
|
||||||
|
import Captive from "./Captive.svelte";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
|
||||||
|
const isCaptive = writable(false);
|
||||||
|
|
||||||
|
function checkHash() {
|
||||||
|
isCaptive.set(window.location.hash === "#/captive");
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
checkHash();
|
||||||
|
window.addEventListener("hashchange", checkHash);
|
||||||
|
return () => window.removeEventListener("hashchange", checkHash);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
{#if $isCaptive}
|
||||||
|
<Captive />
|
||||||
|
{:else}
|
||||||
|
<Index />
|
||||||
|
{/if}
|
||||||
5
firmware/website.bak/src/Captive.svelte
Normal file
5
firmware/website.bak/src/Captive.svelte
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { t } from "./i18n/store";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>{$t("welcome")} - Captive Portal</h1>
|
||||||
5
firmware/website.bak/src/Index.svelte
Normal file
5
firmware/website.bak/src/Index.svelte
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { t } from "./i18n/store";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h1>{$t("welcome")}</h1>
|
||||||
70
firmware/website.bak/src/app.css
Normal file
70
firmware/website.bak/src/app.css
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
:root {
|
||||||
|
--bg-color: #1a1a2e;
|
||||||
|
--card-bg: #16213e;
|
||||||
|
--accent: #0f3460;
|
||||||
|
--text: #eaeaea;
|
||||||
|
--text-muted: #a0a0a0;
|
||||||
|
--success: #00d26a;
|
||||||
|
--error: #ff6b6b;
|
||||||
|
--border: #2a2a4a;
|
||||||
|
--input-bg: #1a1a2e;
|
||||||
|
--shadow: rgba(0, 0, 0, 0.3);
|
||||||
|
--primary: #c41e3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] {
|
||||||
|
--bg-color: #faf8f5;
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--accent: #fef2f2;
|
||||||
|
--text: #1a1a2e;
|
||||||
|
--text-muted: #6b7280;
|
||||||
|
--success: #c41e3a;
|
||||||
|
--error: #dc2626;
|
||||||
|
--border: #e5d9d0;
|
||||||
|
--input-bg: #ffffff;
|
||||||
|
--shadow: rgba(196, 30, 58, 0.1);
|
||||||
|
--primary: #c41e3a;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="light"] .header h1 {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: var(--bg-color);
|
||||||
|
color: var(--text);
|
||||||
|
min-height: 100vh;
|
||||||
|
transition: background 0.3s, color 0.3s;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
body {
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@supports (padding: max(0px)) {
|
||||||
|
body {
|
||||||
|
padding-left: max(12px, env(safe-area-inset-left));
|
||||||
|
padding-right: max(12px, env(safe-area-inset-right));
|
||||||
|
padding-bottom: max(12px, env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
}
|
||||||
133
firmware/website.bak/src/compoents/Header.svelte
Normal file
133
firmware/website.bak/src/compoents/Header.svelte
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import Toggle from "./Toggle.svelte";
|
||||||
|
import {toggleTheme} from "../theme";
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
import {writable} from "svelte/store";
|
||||||
|
import {lang, t} from "../i18n/store";
|
||||||
|
|
||||||
|
const theme = writable<"dark" | "light">("dark");
|
||||||
|
|
||||||
|
function applyInitialTheme() {
|
||||||
|
const userTheme = localStorage.getItem("theme");
|
||||||
|
if (userTheme) {
|
||||||
|
document.documentElement.setAttribute("data-theme", userTheme);
|
||||||
|
} else if (window.matchMedia("(prefers-color-scheme: light)").matches) {
|
||||||
|
document.documentElement.setAttribute("data-theme", "light");
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute("data-theme", "dark");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateThemeFromDom() {
|
||||||
|
const t = document.documentElement.getAttribute("data-theme");
|
||||||
|
theme.set(t === "light" ? "light" : "dark");
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleThemeToggle() {
|
||||||
|
toggleTheme();
|
||||||
|
updateThemeFromDom();
|
||||||
|
}
|
||||||
|
|
||||||
|
let themeIcon = $state("🌙");
|
||||||
|
let themeLabel = $state("Dark");
|
||||||
|
let currentLangCode = $state($lang);
|
||||||
|
let currentLang = $state("Deutsch");
|
||||||
|
let currentFlag = $state("🇩🇪");
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
theme.subscribe(($theme) => {
|
||||||
|
themeIcon = $theme === "light" ? "☀️" : "🌙";
|
||||||
|
themeLabel = $theme === "light" ? "Light" : "Dark";
|
||||||
|
});
|
||||||
|
lang.subscribe(($lang) => {
|
||||||
|
currentLangCode = $lang;
|
||||||
|
currentLang = $lang === "de" ? "Deutsch" : "English";
|
||||||
|
currentFlag = $lang === "de" ? "🇩🇪" : "🇬🇧";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function handleLangChange(newLang: "de" | "en") {
|
||||||
|
lang.set(newLang);
|
||||||
|
|
||||||
|
localStorage.setItem("lang", newLang);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
applyInitialTheme();
|
||||||
|
updateThemeFromDom();
|
||||||
|
window.addEventListener("storage", updateThemeFromDom);
|
||||||
|
|
||||||
|
// Listener für OS-Theme-Änderung
|
||||||
|
const mql = window.matchMedia("(prefers-color-scheme: light)");
|
||||||
|
const osThemeListener = () => {
|
||||||
|
// Nur reagieren, wenn kein User-Theme gesetzt ist
|
||||||
|
if (!localStorage.getItem("theme")) {
|
||||||
|
applyInitialTheme();
|
||||||
|
updateThemeFromDom();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mql.addEventListener("change", osThemeListener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("storage", updateThemeFromDom);
|
||||||
|
mql.removeEventListener("change", osThemeListener);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-controls">
|
||||||
|
<Toggle
|
||||||
|
label={currentLang}
|
||||||
|
icon={currentFlag}
|
||||||
|
ariaLabel="Sprache wechseln"
|
||||||
|
onClick={() => {
|
||||||
|
const newLang = currentLangCode === "de" ? "en" : "de";
|
||||||
|
handleLangChange(newLang);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Toggle
|
||||||
|
label={themeLabel}
|
||||||
|
icon={themeIcon}
|
||||||
|
ariaLabel="Theme wechseln"
|
||||||
|
onClick={handleThemeToggle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1>🚂 System Control</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 380px) {
|
||||||
|
.header h1 {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
37
firmware/website.bak/src/compoents/Toggle.svelte
Normal file
37
firmware/website.bak/src/compoents/Toggle.svelte
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
const { label, icon, ariaLabel, onClick } = $props<{
|
||||||
|
label: string;
|
||||||
|
icon: string;
|
||||||
|
ariaLabel: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button class="toggle" aria-label={ariaLabel} onclick={onClick}>
|
||||||
|
<span class="icon" id="icon">{icon}</span>
|
||||||
|
<span class="label" id="label">{label}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
background: var(--card-bg);
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle:hover {
|
||||||
|
border-color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
6
firmware/website.bak/src/i18n/de.json
Normal file
6
firmware/website.bak/src/i18n/de.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"hello": "Hallo Welt",
|
||||||
|
"welcome": "Willkommen",
|
||||||
|
"language": "Sprache",
|
||||||
|
"save": "Speichern"
|
||||||
|
}
|
||||||
6
firmware/website.bak/src/i18n/en.json
Normal file
6
firmware/website.bak/src/i18n/en.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"hello": "Hello World",
|
||||||
|
"welcome": "Welcome",
|
||||||
|
"language": "Language",
|
||||||
|
"save": "Save"
|
||||||
|
}
|
||||||
12
firmware/website.bak/src/i18n/index.ts
Normal file
12
firmware/website.bak/src/i18n/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import de from './de.json';
|
||||||
|
import en from './en.json';
|
||||||
|
|
||||||
|
export const translations = { de, en };
|
||||||
|
|
||||||
|
export type Lang = keyof typeof translations;
|
||||||
|
|
||||||
|
export function getInitialLang(): Lang {
|
||||||
|
const navLang = navigator.language.slice(0, 2);
|
||||||
|
if (navLang in translations) return navLang as Lang;
|
||||||
|
return 'en';
|
||||||
|
}
|
||||||
16
firmware/website.bak/src/i18n/store.ts
Normal file
16
firmware/website.bak/src/i18n/store.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { writable, derived } from 'svelte/store';
|
||||||
|
import { translations, getInitialLang, type Lang } from './index';
|
||||||
|
|
||||||
|
function getLang(): Lang {
|
||||||
|
const stored = localStorage.getItem('lang');
|
||||||
|
if (stored && stored in translations) return stored as Lang;
|
||||||
|
return getInitialLang();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const lang = writable<Lang>(getLang());
|
||||||
|
|
||||||
|
export const t = derived(lang, $lang => {
|
||||||
|
return (key: string) => {
|
||||||
|
return translations[$lang][key] || key;
|
||||||
|
};
|
||||||
|
});
|
||||||
9
firmware/website.bak/src/main.js
Normal file
9
firmware/website.bak/src/main.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { mount } from 'svelte'
|
||||||
|
import App from './App.svelte'
|
||||||
|
import './app.css'
|
||||||
|
|
||||||
|
const app = mount(App, {
|
||||||
|
target: document.getElementById('app'),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default app
|
||||||
18
firmware/website.bak/src/theme.ts
Normal file
18
firmware/website.bak/src/theme.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// src/theme.ts
|
||||||
|
export function setTheme(theme: 'light' | 'dark') {
|
||||||
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
localStorage.setItem('theme', theme);
|
||||||
|
|
||||||
|
const icon = document.getElementById('theme-icon');
|
||||||
|
const label = document.getElementById('theme-label');
|
||||||
|
const metaTheme = document.querySelector('meta[name="theme-color"]') as HTMLMetaElement | null;
|
||||||
|
|
||||||
|
if (icon) icon.textContent = theme === 'light' ? '☀️' : '🌙';
|
||||||
|
if (label) label.textContent = theme === 'light' ? 'Light' : 'Dark';
|
||||||
|
if (metaTheme) metaTheme.content = theme === 'light' ? '#f0f2f5' : '#1a1a2e';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toggleTheme() {
|
||||||
|
const current = document.documentElement.getAttribute('data-theme') as 'light' | 'dark' | null || 'dark';
|
||||||
|
setTheme(current === 'dark' ? 'light' : 'dark');
|
||||||
|
}
|
||||||
2
firmware/website.bak/src/vite-env.d.ts
vendored
Normal file
2
firmware/website.bak/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/// <reference types="svelte" />
|
||||||
|
/// <reference types="vite/client" />
|
||||||
17
firmware/website.bak/svelte.config.js
Normal file
17
firmware/website.bak/svelte.config.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||||
|
|
||||||
|
/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */
|
||||||
|
export default {
|
||||||
|
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||||
|
// for more information about preprocessors
|
||||||
|
preprocess: vitePreprocess(),
|
||||||
|
compilerOptions: {
|
||||||
|
runes: true,
|
||||||
|
},
|
||||||
|
vitePlugin: {
|
||||||
|
inspector: {
|
||||||
|
showToggleButton: 'always',
|
||||||
|
toggleButtonPos: 'bottom-right'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
firmware/website.bak/vite.config.js
Normal file
14
firmware/website.bak/vite.config.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||||
|
import viteCompression from 'vite-plugin-compression';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
svelte(),
|
||||||
|
viteCompression()
|
||||||
|
],
|
||||||
|
build: {
|
||||||
|
outDir: '../storage/website',
|
||||||
|
assetsDir: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
29
firmware/website/.gitignore
vendored
Normal file
29
firmware/website/.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
# Output
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
.netlify
|
||||||
|
.wrangler
|
||||||
|
/.svelte-kit
|
||||||
|
/build
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
!.env.test
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
# Paraglide
|
||||||
|
src/lib/paraglide
|
||||||
|
project.inlang/cache/
|
||||||
|
|
||||||
|
*storybook.log
|
||||||
|
storybook-static
|
||||||
1
firmware/website/.npmrc
Normal file
1
firmware/website/.npmrc
Normal file
@@ -0,0 +1 @@
|
|||||||
|
engine-strict=true
|
||||||
9
firmware/website/.prettierignore
Normal file
9
firmware/website/.prettierignore
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Package Managers
|
||||||
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
yarn.lock
|
||||||
|
bun.lock
|
||||||
|
bun.lockb
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/static/
|
||||||
16
firmware/website/.prettierrc
Normal file
16
firmware/website/.prettierrc
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.svelte",
|
||||||
|
"options": {
|
||||||
|
"parser": "svelte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tailwindStylesheet": "./src/routes/layout.css"
|
||||||
|
}
|
||||||
17
firmware/website/.storybook/main.ts
Normal file
17
firmware/website/.storybook/main.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { StorybookConfig } from '@storybook/sveltekit';
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
"stories": [
|
||||||
|
"../src/**/*.mdx",
|
||||||
|
"../src/**/*.stories.@(js|ts|svelte)"
|
||||||
|
],
|
||||||
|
"addons": [
|
||||||
|
"@storybook/addon-svelte-csf",
|
||||||
|
"@chromatic-com/storybook",
|
||||||
|
"@storybook/addon-vitest",
|
||||||
|
"@storybook/addon-a11y",
|
||||||
|
"@storybook/addon-docs"
|
||||||
|
],
|
||||||
|
"framework": "@storybook/sveltekit"
|
||||||
|
};
|
||||||
|
export default config;
|
||||||
21
firmware/website/.storybook/preview.ts
Normal file
21
firmware/website/.storybook/preview.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import type { Preview } from '@storybook/sveltekit'
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
parameters: {
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
a11y: {
|
||||||
|
// 'todo' - show a11y violations in the test UI only
|
||||||
|
// 'error' - fail CI on a11y violations
|
||||||
|
// 'off' - skip a11y checks entirely
|
||||||
|
test: 'todo'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
||||||
7
firmware/website/.storybook/vitest.setup.ts
Normal file
7
firmware/website/.storybook/vitest.setup.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
|
||||||
|
import { setProjectAnnotations } from '@storybook/sveltekit';
|
||||||
|
import * as projectAnnotations from './preview';
|
||||||
|
|
||||||
|
// This is an important step to apply the right configuration when testing your stories.
|
||||||
|
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
|
||||||
|
setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
|
||||||
5
firmware/website/.vscode/settings.json
vendored
Normal file
5
firmware/website/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"files.associations": {
|
||||||
|
"*.css": "tailwindcss"
|
||||||
|
}
|
||||||
|
}
|
||||||
42
firmware/website/README.md
Normal file
42
firmware/website/README.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# sv
|
||||||
|
|
||||||
|
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||||
|
|
||||||
|
## Creating a project
|
||||||
|
|
||||||
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# create a new project
|
||||||
|
npx sv create my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
To recreate this project with the same configuration:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# recreate this project
|
||||||
|
npx sv create --template minimal --types ts --add prettier eslint vitest="usages:unit,component" tailwindcss="plugins:typography" storybook paraglide="languageTags:en, de+demo:yes" --install npm website
|
||||||
|
```
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To create a production version of your app:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can preview the production build with `npm run preview`.
|
||||||
|
|
||||||
|
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||||
42
firmware/website/eslint.config.js
Normal file
42
firmware/website/eslint.config.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
|
||||||
|
import storybook from 'eslint-plugin-storybook';
|
||||||
|
|
||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { includeIgnoreFile } from '@eslint/compat';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import svelte from 'eslint-plugin-svelte';
|
||||||
|
import { defineConfig } from 'eslint/config';
|
||||||
|
import globals from 'globals';
|
||||||
|
import ts from 'typescript-eslint';
|
||||||
|
import svelteConfig from './svelte.config.js';
|
||||||
|
|
||||||
|
const gitignorePath = path.resolve(import.meta.dirname, '.gitignore');
|
||||||
|
|
||||||
|
export default defineConfig(
|
||||||
|
includeIgnoreFile(gitignorePath),
|
||||||
|
js.configs.recommended,
|
||||||
|
...ts.configs.recommended,
|
||||||
|
...svelte.configs.recommended,
|
||||||
|
prettier,
|
||||||
|
...svelte.configs.prettier,
|
||||||
|
{
|
||||||
|
languageOptions: { globals: { ...globals.browser, ...globals.node } },
|
||||||
|
rules: {
|
||||||
|
// typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects.
|
||||||
|
// see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors
|
||||||
|
'no-undef': 'off'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
projectService: true,
|
||||||
|
extraFileExtensions: ['.svelte'],
|
||||||
|
parser: ts.parser,
|
||||||
|
svelteConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
4
firmware/website/messages/de.json
Normal file
4
firmware/website/messages/de.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
||||||
|
"hello_world": "Hello, {name} from de!"
|
||||||
|
}
|
||||||
4
firmware/website/messages/en.json
Normal file
4
firmware/website/messages/en.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://inlang.com/schema/inlang-message-format",
|
||||||
|
"hello_world": "Hello, {name} from en!"
|
||||||
|
}
|
||||||
6278
firmware/website/package-lock.json
generated
Normal file
6278
firmware/website/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
57
firmware/website/package.json
Normal file
57
firmware/website/package.json
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"name": "website",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"prepare": "svelte-kit sync || echo ''",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
|
"lint": "prettier --check . && eslint .",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"test:unit": "vitest",
|
||||||
|
"test": "npm run test:unit -- --run",
|
||||||
|
"storybook": "storybook dev -p 6006",
|
||||||
|
"build-storybook": "storybook build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@chromatic-com/storybook": "^5.0.1",
|
||||||
|
"@eslint/compat": "^2.0.2",
|
||||||
|
"@eslint/js": "^9.39.2",
|
||||||
|
"@inlang/paraglide-js": "^2.10.0",
|
||||||
|
"@storybook/addon-a11y": "^10.2.8",
|
||||||
|
"@storybook/addon-docs": "^10.2.8",
|
||||||
|
"@storybook/addon-svelte-csf": "^5.0.11",
|
||||||
|
"@storybook/addon-vitest": "^10.2.8",
|
||||||
|
"@storybook/sveltekit": "^10.2.8",
|
||||||
|
"@sveltejs/adapter-auto": "^7.0.0",
|
||||||
|
"@sveltejs/kit": "^2.50.2",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
||||||
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
"@types/node": "^24",
|
||||||
|
"@vitest/browser-playwright": "^4.0.18",
|
||||||
|
"@vitest/coverage-v8": "^4.0.18",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-storybook": "^10.2.8",
|
||||||
|
"eslint-plugin-svelte": "^3.14.0",
|
||||||
|
"globals": "^17.3.0",
|
||||||
|
"playwright": "^1.58.1",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
|
"prettier-plugin-svelte": "^3.4.1",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
||||||
|
"storybook": "^10.2.8",
|
||||||
|
"svelte": "^5.49.2",
|
||||||
|
"svelte-check": "^4.3.6",
|
||||||
|
"tailwindcss": "^4.1.18",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"typescript-eslint": "^8.54.0",
|
||||||
|
"vite": "^7.3.1",
|
||||||
|
"vitest": "^4.0.18",
|
||||||
|
"vitest-browser-svelte": "^2.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
firmware/website/project.inlang/settings.json
Normal file
12
firmware/website/project.inlang/settings.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://inlang.com/schema/project-settings",
|
||||||
|
"modules": [
|
||||||
|
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@4/dist/index.js",
|
||||||
|
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@2/dist/index.js"
|
||||||
|
],
|
||||||
|
"plugin.inlang.messageFormat": {
|
||||||
|
"pathPattern": "./messages/{locale}.json"
|
||||||
|
},
|
||||||
|
"baseLocale": "en",
|
||||||
|
"locales": ["en", "de"]
|
||||||
|
}
|
||||||
13
firmware/website/src/app.d.ts
vendored
Normal file
13
firmware/website/src/app.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||||
|
// for information about these interfaces
|
||||||
|
declare global {
|
||||||
|
namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
// interface Locals {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface PageState {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
15
firmware/website/src/app.html
Normal file
15
firmware/website/src/app.html
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
|
||||||
|
<html lang="%paraglide.lang%">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
firmware/website/src/demo.spec.ts
Normal file
7
firmware/website/src/demo.spec.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
describe('sum test', () => {
|
||||||
|
it('adds 1 + 2 to equal 3', () => {
|
||||||
|
expect(1 + 2).toBe(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
13
firmware/website/src/hooks.server.ts
Normal file
13
firmware/website/src/hooks.server.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import type { Handle } from '@sveltejs/kit';
|
||||||
|
import { paraglideMiddleware } from '$lib/paraglide/server';
|
||||||
|
|
||||||
|
const handleParaglide: Handle = ({ event, resolve }) =>
|
||||||
|
paraglideMiddleware(event.request, ({ request, locale }) => {
|
||||||
|
event.request = request;
|
||||||
|
|
||||||
|
return resolve(event, {
|
||||||
|
transformPageChunk: ({ html }) => html.replace('%paraglide.lang%', locale)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export const handle: Handle = handleParaglide;
|
||||||
3
firmware/website/src/hooks.ts
Normal file
3
firmware/website/src/hooks.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { deLocalizeUrl } from '$lib/paraglide/runtime';
|
||||||
|
|
||||||
|
export const reroute = (request) => deLocalizeUrl(request.url).pathname;
|
||||||
1
firmware/website/src/lib/assets/favicon.svg
Normal file
1
firmware/website/src/lib/assets/favicon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
1
firmware/website/src/lib/index.ts
Normal file
1
firmware/website/src/lib/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
// place files you want to import through the `$lib` alias in this folder.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user