optimize AP mode
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 6m44s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 3m59s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 3m51s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 3m52s

- save wifi data
- show status led

Signed-off-by: Peter Siegmund <developer@mars3142.org>
This commit is contained in:
2026-01-15 00:36:19 +01:00
parent bccfb80791
commit 1fbc28a628
14 changed files with 213 additions and 257 deletions

View File

@@ -12,4 +12,5 @@ idf_component_register(SRCS
esp_event esp_event
json json
simulator simulator
persistence-manager
) )

View File

@@ -5,6 +5,7 @@
#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>
@@ -116,6 +117,17 @@ esp_err_t api_wifi_scan_handler(httpd_req_t *req)
return res; return res;
} }
static void reboot_task(void *param)
{
vTaskDelay(pdMS_TO_TICKS(100));
esp_restart();
}
static bool is_valid(const cJSON *string)
{
return string && cJSON_IsString(string) && string->valuestring && strlen(string->valuestring) > 0;
}
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");
@@ -128,12 +140,22 @@ esp_err_t api_wifi_config_handler(httpd_req_t *req)
} }
buf[ret] = '\0'; buf[ret] = '\0';
// Passwort maskieren
cJSON *json = cJSON_Parse(buf); cJSON *json = cJSON_Parse(buf);
if (json) if (json)
{ {
cJSON *ssid = cJSON_GetObjectItem(json, "ssid");
cJSON *pw = cJSON_GetObjectItem(json, "password"); cJSON *pw = cJSON_GetObjectItem(json, "password");
if (pw && cJSON_IsString(pw) && pw->valuestring) if (is_valid(ssid) && is_valid(pw))
{
persistence_manager_t pm;
if (persistence_manager_init(&pm, "wifi_config") == ESP_OK)
{
persistence_manager_set_string(&pm, "ssid", ssid->valuestring);
persistence_manager_set_string(&pm, "password", pw->valuestring);
persistence_manager_deinit(&pm);
}
}
if (is_valid(pw))
{ {
size_t pwlen = strlen(pw->valuestring); size_t pwlen = strlen(pw->valuestring);
char *masked = malloc(pwlen + 1); char *masked = malloc(pwlen + 1);
@@ -163,7 +185,9 @@ esp_err_t api_wifi_config_handler(httpd_req_t *req)
ESP_LOGI(TAG, "Received WiFi config: %s", buf); ESP_LOGI(TAG, "Received WiFi config: %s", buf);
} }
// TODO: Parse JSON and connect to WiFi // Define a reboot task function
xTaskCreate(reboot_task, "reboot_task", 2048, NULL, 5, NULL);
set_cors_headers(req); set_cors_headers(req);
httpd_resp_set_status(req, "200 OK"); httpd_resp_set_status(req, "200 OK");
return httpd_resp_sendstr(req, "{\"status\":\"ok\"}"); return httpd_resp_sendstr(req, "{\"status\":\"ok\"}");

View File

@@ -18,7 +18,7 @@ static void ws_clients_init(void)
} }
// Add a client to the list // Add a client to the list
static void add_client(int fd) static bool add_client(int fd)
{ {
for (int i = 0; i < WS_MAX_CLIENTS; i++) for (int i = 0; i < WS_MAX_CLIENTS; i++)
{ {
@@ -27,10 +27,11 @@ static void add_client(int fd)
ws_clients[i] = fd; ws_clients[i] = fd;
ws_client_count++; ws_client_count++;
ESP_LOGI(TAG, "WebSocket client connected: fd=%d (total: %d)", fd, ws_client_count); ESP_LOGI(TAG, "WebSocket client connected: fd=%d (total: %d)", fd, ws_client_count);
return; return true;
} }
} }
ESP_LOGW(TAG, "Max WebSocket clients reached, cannot add fd=%d", fd); ESP_LOGW(TAG, "Max WebSocket clients reached, cannot add fd=%d", fd);
return false;
} }
// Remove a client from the list // Remove a client from the list
@@ -83,6 +84,13 @@ esp_err_t websocket_handler(httpd_req_t *req)
{ {
// This is the handshake // This is the handshake
ESP_LOGI(TAG, "WebSocket handshake"); ESP_LOGI(TAG, "WebSocket handshake");
int fd = httpd_req_to_sockfd(req);
if (!add_client(fd))
{
// Zu viele Clients, Verbindung schließen
httpd_sess_trigger_close(req->handle, fd);
return ESP_FAIL;
}
return ESP_OK; return ESP_OK;
} }

View File

@@ -51,7 +51,6 @@ static void dns_server_task(void *pvParameters)
// Fragen: 1, Antworten: 1 // Fragen: 1, Antworten: 1
buf[7] = 1; buf[7] = 1;
// Antwort anhängen (Name Pointer auf Frage) // Antwort anhängen (Name Pointer auf Frage)
int qlen = len - 12;
int pos = len; int pos = len;
buf[pos++] = 0xC0; buf[pos++] = 0xC0;
buf[pos++] = 0x0C; // Name pointer buf[pos++] = 0x0C; // Name pointer

View File

@@ -16,204 +16,120 @@
#include <lwip/sys.h> #include <lwip/sys.h>
#include <mdns.h> #include <mdns.h>
#include <nvs_flash.h> #include <nvs_flash.h>
#include <persistence_manager.h>
#include <sdkconfig.h> #include <sdkconfig.h>
#include <string.h> #include <string.h>
// Event group to signal when we are connected // Event group to signal WiFi connection status
static EventGroupHandle_t s_wifi_event_group; static EventGroupHandle_t s_wifi_event_group;
// The bits for the event group // Event group bits
#define WIFI_CONNECTED_BIT BIT0 #define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1 #define WIFI_FAIL_BIT BIT1
static const char *TAG = "wifi_manager"; static const char *TAG = "wifi_manager";
static int s_retry_num = 0; static void wifi_create_ap()
static int s_current_network_index = 0;
// WiFi network configuration structure
typedef struct
{ {
const char *ssid; ESP_ERROR_CHECK(esp_wifi_stop());
const char *password; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
} wifi_network_config_t;
// Array of configured WiFi networks
static const wifi_network_config_t s_wifi_networks[] = {
#if CONFIG_WIFI_ENABLED
{CONFIG_WIFI_SSID_1, CONFIG_WIFI_PASSWORD_1},
#if CONFIG_WIFI_NETWORK_COUNT >= 2
{CONFIG_WIFI_SSID_2, CONFIG_WIFI_PASSWORD_2},
#endif
#if CONFIG_WIFI_NETWORK_COUNT >= 3
{CONFIG_WIFI_SSID_3, CONFIG_WIFI_PASSWORD_3},
#endif
#if CONFIG_WIFI_NETWORK_COUNT >= 4
{CONFIG_WIFI_SSID_4, CONFIG_WIFI_PASSWORD_4},
#endif
#if CONFIG_WIFI_NETWORK_COUNT >= 5
{CONFIG_WIFI_SSID_5, CONFIG_WIFI_PASSWORD_5},
#endif
#endif
};
static const int s_wifi_network_count = sizeof(s_wifi_networks) / sizeof(s_wifi_networks[0]);
static void try_next_network(void);
static void connect_to_network(int index)
{
#if CONFIG_WIFI_ENABLED
if (index >= s_wifi_network_count)
{
ESP_LOGE(TAG, "No more networks to try");
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
return;
}
const wifi_network_config_t *network = &s_wifi_networks[index];
// Skip empty SSIDs
if (network->ssid == NULL || strlen(network->ssid) == 0)
{
ESP_LOGW(TAG, "Skipping empty SSID at index %d", index);
s_current_network_index++;
s_retry_num = 0;
try_next_network();
return;
}
ESP_DIAG_EVENT(TAG, "Trying to connect to network %d: %s", index + 1, network->ssid);
wifi_config_t wifi_config = {
.sta =
{
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
},
};
strncpy((char *)wifi_config.sta.ssid, network->ssid, sizeof(wifi_config.sta.ssid) - 1);
strncpy((char *)wifi_config.sta.password, network->password, sizeof(wifi_config.sta.password) - 1);
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
esp_wifi_connect();
#endif
}
static void try_next_network(void)
{
#if CONFIG_WIFI_ENABLED
s_current_network_index++;
s_retry_num = 0;
if (s_current_network_index < s_wifi_network_count)
{
connect_to_network(s_current_network_index);
}
else
{
ESP_LOGE(TAG, "Failed to connect to any configured network");
led_behavior_t led0_behavior = {
.index = 0,
.mode = LED_MODE_BLINK,
.color = {.red = 50, .green = 0, .blue = 0},
.on_time_ms = 1000,
.off_time_ms = 500,
};
led_status_set_behavior(led0_behavior);
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
#endif
}
static void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
#if CONFIG_WIFI_ENABLED
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
led_behavior_t led0_behavior = {
.index = 0,
.mode = LED_MODE_BLINK,
.color = {.red = 50, .green = 50, .blue = 0},
.on_time_ms = 200,
.off_time_ms = 200,
};
led_status_set_behavior(led0_behavior);
connect_to_network(s_current_network_index);
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
if (s_retry_num < CONFIG_WIFI_CONNECT_RETRIES)
{
led_behavior_t led0_behavior = {
.index = 0,
.mode = LED_MODE_BLINK,
.color = {.red = 50, .green = 50, .blue = 0},
.on_time_ms = 200,
.off_time_ms = 200,
};
led_status_set_behavior(led0_behavior);
s_retry_num++;
ESP_DIAG_EVENT(TAG, "Retrying network %d (%d/%d)", s_current_network_index + 1, s_retry_num,
CONFIG_WIFI_CONNECT_RETRIES);
esp_wifi_connect();
return;
}
// Retries exhausted for current network, try next one
ESP_LOGW(TAG, "Failed to connect to network %d after %d retries, trying next...", s_current_network_index + 1,
CONFIG_WIFI_CONNECT_RETRIES);
try_next_network();
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
led_behavior_t led0_behavior = {
.index = 0,
.mode = LED_MODE_SOLID,
.color = {.red = 0, .green = 50, .blue = 0},
};
led_status_set_behavior(led0_behavior);
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_DIAG_EVENT(TAG, "Got IP address:" IPSTR " (network %d: %s)", IP2STR(&event->ip_info.ip),
s_current_network_index + 1, s_wifi_networks[s_current_network_index].ssid);
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
#endif
}
void wifi_manager_init()
{
s_wifi_event_group = xEventGroupCreate();
s_retry_num = 0;
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
// Access Point erstellen
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t ap_config = {.ap = {.ssid = "system-control", wifi_config_t ap_config = {.ap = {.ssid = "system-control",
.ssid_len = strlen("system-control"), .ssid_len = strlen("system-control"),
.password = "", .password = "",
.max_connection = 4, .max_connection = 4,
.authmode = WIFI_AUTH_OPEN}}; .authmode = WIFI_AUTH_OPEN}};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config));
ESP_ERROR_CHECK(esp_wifi_start()); ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "Access Point 'system-control' started");
dns_server_start("192.168.4.1");
ESP_LOGI(TAG, "Access Point 'system-control' gestartet"); led_behavior_t led_behavior = {
.color = {.red = 50, .green = 0, .blue = 0},
.index = 0,
.mode = LED_MODE_SOLID,
};
led_status_set_behavior(led_behavior);
}
// DNS Hijack Server starten (alle DNS-Anfragen auf AP-IP umleiten) void wifi_manager_init()
dns_server_start("192.168.4.1"); // ggf. dynamisch ermitteln {
s_wifi_event_group = xEventGroupCreate();
// API-Server starten ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
// Try to load stored WiFi configuration
persistence_manager_t pm;
char ssid[33] = {0};
char password[65] = {0};
bool have_ssid = false, have_password = false;
if (persistence_manager_init(&pm, "wifi_config") == ESP_OK)
{
persistence_manager_get_string(&pm, "ssid", ssid, sizeof(ssid), "");
persistence_manager_get_string(&pm, "password", password, sizeof(password), "");
have_ssid = strlen(ssid) > 0;
have_password = strlen(password) > 0;
}
if (have_ssid && have_password)
{
led_behavior_t led_behavior = {
.on_time_ms = 250,
.off_time_ms = 100,
.color = {.red = 50, .green = 50, .blue = 0},
.index = 0,
.mode = LED_MODE_BLINK,
};
led_status_set_behavior(led_behavior);
ESP_LOGI(TAG, "Found WiFi configuration: SSID='%s'", ssid);
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
wifi_config_t wifi_config = {0};
strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1);
strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1);
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
int retries = 0;
EventBits_t bits;
do
{
esp_wifi_connect();
bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE,
5000 / portTICK_PERIOD_MS);
if (bits & WIFI_CONNECTED_BIT)
{
led_behavior_t led_behavior = {
.index = 0,
.color = {.red = 0, .green = 50, .blue = 0},
.mode = LED_MODE_SOLID,
};
led_status_set_behavior(led_behavior);
ESP_LOGI(TAG, "WiFi connection established successfully");
break;
}
retries++;
} while (!(bits & WIFI_CONNECTED_BIT) && retries < CONFIG_WIFI_CONNECT_RETRIES);
if (!(bits & WIFI_CONNECTED_BIT))
{
ESP_LOGW(TAG, "WiFi connection failed, switching to Access Point mode");
wifi_create_ap();
}
}
else
{
// Create Access Point
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_create_ap();
}
// API server start
api_server_config_t s_config = API_SERVER_CONFIG_DEFAULT(); api_server_config_t s_config = API_SERVER_CONFIG_DEFAULT();
ESP_ERROR_CHECK(api_server_start(&s_config)); ESP_ERROR_CHECK(api_server_start(&s_config));
} }

View File

@@ -12,9 +12,9 @@ typedef struct
typedef struct typedef struct
{ {
uint8_t h; float h;
uint8_t s; float s;
uint8_t v; float v;
} hsv_t; } hsv_t;
__BEGIN_DECLS __BEGIN_DECLS

View File

@@ -45,10 +45,10 @@ rgb_t interpolate_color_hsv(rgb_t start, rgb_t end, float factor)
// Interpolate HSV values // Interpolate HSV values
hsv_t interpolated_hsv; hsv_t interpolated_hsv;
interpolated_hsv.h = fmod(h1 + (h2 - h1) * factor, 360.0); interpolated_hsv.h = fmodf(h1 + (h2 - h1) * factor, 360.0f);
if (interpolated_hsv.h < 0) if (interpolated_hsv.h < 0.0f)
{ {
interpolated_hsv.h += 360.0; interpolated_hsv.h += 360.0f;
} }
interpolated_hsv.s = start_hsv.s + (end_hsv.s - start_hsv.s) * factor; interpolated_hsv.s = start_hsv.s + (end_hsv.s - start_hsv.s) * factor;
interpolated_hsv.v = start_hsv.v + (end_hsv.v - start_hsv.v) * factor; interpolated_hsv.v = start_hsv.v + (end_hsv.v - start_hsv.v) * factor;

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <esp_err.h>
#include <nvs.h> #include <nvs.h>
#include <nvs_flash.h> #include <nvs_flash.h>
#include <stdbool.h> #include <stdbool.h>
@@ -33,14 +34,14 @@ extern "C"
* @param pm Pointer to the persistence manager structure. * @param pm Pointer to the persistence manager structure.
* @param nvs_namespace Namespace to use for NVS operations. * @param nvs_namespace Namespace to use for NVS operations.
*/ */
void persistence_manager_init(persistence_manager_t *pm, const char *nvs_namespace); esp_err_t persistence_manager_init(persistence_manager_t *pm, const char *nvs_namespace);
/** /**
* @brief Deinitialize the persistence manager and release resources. * @brief Deinitialize the persistence manager and release resources.
* *
* @param pm Pointer to the persistence manager structure. * @param pm Pointer to the persistence manager structure.
*/ */
void persistence_manager_deinit(persistence_manager_t *pm); esp_err_t persistence_manager_deinit(persistence_manager_t *pm);
/** /**
* @brief Check if the persistence manager is initialized. * @brief Check if the persistence manager is initialized.

View File

@@ -4,10 +4,10 @@
#define TAG "persistence_manager" #define TAG "persistence_manager"
void persistence_manager_init(persistence_manager_t *pm, const char *nvs_namespace) esp_err_t persistence_manager_init(persistence_manager_t *pm, const char *nvs_namespace)
{ {
if (!pm) if (!pm)
return; return ESP_ERR_INVALID_ARG;
strncpy(pm->nvs_namespace, nvs_namespace ? nvs_namespace : "config", sizeof(pm->nvs_namespace) - 1); strncpy(pm->nvs_namespace, nvs_namespace ? nvs_namespace : "config", sizeof(pm->nvs_namespace) - 1);
pm->nvs_namespace[sizeof(pm->nvs_namespace) - 1] = '\0'; pm->nvs_namespace[sizeof(pm->nvs_namespace) - 1] = '\0';
pm->initialized = false; pm->initialized = false;
@@ -16,20 +16,20 @@ void persistence_manager_init(persistence_manager_t *pm, const char *nvs_namespa
{ {
pm->initialized = true; pm->initialized = true;
ESP_LOGI(TAG, "Initialized with namespace: %s", pm->nvs_namespace); ESP_LOGI(TAG, "Initialized with namespace: %s", pm->nvs_namespace);
return ESP_OK;
} }
else ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(err));
{ return err;
ESP_LOGE(TAG, "Failed to open NVS handle: %s", esp_err_to_name(err));
}
} }
void persistence_manager_deinit(persistence_manager_t *pm) esp_err_t persistence_manager_deinit(persistence_manager_t *pm)
{ {
if (pm && pm->initialized) if (pm && pm->initialized)
{ {
nvs_close(pm->nvs_handle); nvs_close(pm->nvs_handle);
pm->initialized = false; pm->initialized = false;
} }
return ESP_OK;
} }
bool persistence_manager_is_initialized(const persistence_manager_t *pm) bool persistence_manager_is_initialized(const persistence_manager_t *pm)

View File

@@ -186,10 +186,7 @@ void app_task(void *args)
setup_buttons(); setup_buttons();
init_ui(); init_ui();
#if CONFIG_WIFI_ENABLED
wifi_manager_init(); wifi_manager_init();
analytics_init();
#endif
start_simulation(); start_simulation();

View File

@@ -27,25 +27,9 @@
</button> </button>
</div> </div>
<h1>🚂 System Control</h1> <h1>🚂 System Control</h1>
<p data-i18n="captive.subtitle">WLAN-Einrichtung</p>
</div> </div>
<div class="card"> <div class="card">
<div id="scan-section">
<button class="btn btn-secondary" onclick="scanNetworks()" data-i18n="captive.scan">
📡 Netzwerke suchen
</button>
<div id="loading" class="loading">
<div class="spinner"></div>
<p data-i18n="captive.scanning">Suche nach Netzwerken...</p>
</div>
<div id="network-list" class="network-list" style="display: none;"></div>
</div>
<div class="divider"><span data-i18n="captive.or.manual">oder manuell eingeben</span></div>
<div class="form-group"> <div class="form-group">
<label for="ssid" data-i18n="wifi.ssid">WLAN-Name (SSID)</label> <label for="ssid" data-i18n="wifi.ssid">WLAN-Name (SSID)</label>
<input type="text" id="ssid" data-i18n-placeholder="wifi.ssid.placeholder" <input type="text" id="ssid" data-i18n-placeholder="wifi.ssid.placeholder"
@@ -62,7 +46,8 @@
</div> </div>
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-primary" onclick="saveWifi()" data-i18n="captive.connect"> <button class="btn btn-primary" id="connect-btn" onclick="saveWifi()" data-i18n="captive.connect"
disabled>
💾 Verbinden 💾 Verbinden
</button> </button>
</div> </div>
@@ -112,12 +97,21 @@
setTheme(current === 'dark' ? 'light' : 'dark'); setTheme(current === 'dark' ? 'light' : 'dark');
} }
// Button aktivieren/deaktivieren
function updateConnectBtn() {
const ssid = document.getElementById('ssid').value;
const pw = document.getElementById('password').value;
const btn = document.getElementById('connect-btn');
btn.disabled = !(ssid.length > 0 && pw.length > 0);
}
// Initialize on load // Initialize on load
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initTheme(); initTheme();
initI18n(); initI18n();
// Auto-scan on load document.getElementById('ssid').addEventListener('input', updateConnectBtn);
setTimeout(scanNetworks, 500); document.getElementById('password').addEventListener('input', updateConnectBtn);
updateConnectBtn();
}); });
// Toggle password visibility // Toggle password visibility

View File

@@ -39,6 +39,15 @@ body {
box-shadow: 0 4px 20px var(--shadow); box-shadow: 0 4px 20px var(--shadow);
} }
#connect-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
box-shadow: none;
background-color: #888 !important;
}
}
/* Form Group - More spacing */ /* Form Group - More spacing */
.form-group { .form-group {
margin-bottom: 16px; margin-bottom: 16px;

View File

@@ -16,7 +16,6 @@
<body> <body>
<div class="container"> <div class="container">
<div class="header"> <div class="header">
<h1>🚂 System Control</h1>
<div class="header-controls"> <div class="header-controls">
<button class="lang-toggle" onclick="toggleLanguage()" aria-label="Sprache wechseln"> <button class="lang-toggle" onclick="toggleLanguage()" aria-label="Sprache wechseln">
<span class="lang-flag" id="lang-flag">🇩🇪</span> <span class="lang-flag" id="lang-flag">🇩🇪</span>
@@ -27,6 +26,7 @@
<span class="theme-toggle-label" id="theme-label">Dark</span> <span class="theme-toggle-label" id="theme-label">Dark</span>
</button> </button>
</div> </div>
<h1>🚂 System Control</h1>
</div> </div>
<div class="tabs"> <div class="tabs">

View File

@@ -43,6 +43,13 @@ async function scanNetworks() {
try { try {
const response = await fetch('/api/wifi/scan'); const response = await fetch('/api/wifi/scan');
if (!response.ok) {
// Fehlerhafte API-Antwort, aber ESP32 ist erreichbar
const errorText = await response.text();
showStatus('wifi-status', t('wifi.error.scan') + ': ' + errorText, 'error');
if (loading) loading.classList.remove('active');
return;
}
const networks = await response.json(); const networks = await response.json();
if (loading) { if (loading) {
@@ -92,43 +99,43 @@ async function scanNetworks() {
if (loading) { if (loading) {
loading.classList.remove('active'); loading.classList.remove('active');
} }
// Nur bei Netzwerkfehlern Demo-Daten anzeigen
// Demo mode for local testing if (error instanceof TypeError) {
const demoNetworks = [ const demoNetworks = [
{ ssid: 'Demo-Netzwerk', rssi: -45 }, { ssid: 'Demo-Netzwerk', rssi: -45 },
{ ssid: 'Gast-WLAN', rssi: -67 }, { ssid: 'Gast-WLAN', rssi: -67 },
{ ssid: 'Nachbar-WiFi', rssi: -82 } { ssid: 'Nachbar-WiFi', rssi: -82 }
]; ];
if (networkList) {
if (networkList) { demoNetworks.forEach(network => {
demoNetworks.forEach(network => { const signalIcon = getSignalIcon(network.rssi);
const signalIcon = getSignalIcon(network.rssi); const item = document.createElement('div');
const item = document.createElement('div'); item.className = 'network-item';
item.className = 'network-item'; item.onclick = () => selectNetwork(network.ssid, item);
item.onclick = () => selectNetwork(network.ssid, item); item.innerHTML = `
item.innerHTML = ` <span class="network-name">
<span class="network-name"> <span class="signal-icon">${signalIcon}</span>
<span class="signal-icon">${signalIcon}</span> ${escapeHtml(network.ssid)}
${escapeHtml(network.ssid)} </span>
</span> <span class="network-signal">${network.rssi} dBm</span>
<span class="network-signal">${network.rssi} dBm</span> `;
`; networkList.appendChild(item);
networkList.appendChild(item); });
}); networkList.style.display = 'block';
networkList.style.display = 'block'; }
if (select) {
select.innerHTML = `<option value="">${t('wifi.scan.hint')}</option>`;
demoNetworks.forEach(network => {
const option = document.createElement('option');
option.value = network.ssid;
option.textContent = `${network.ssid} (${network.rssi} dBm)`;
select.appendChild(option);
});
}
showStatus('wifi-status', 'Demo: ' + t('wifi.networks.found', { count: demoNetworks.length }), 'info');
} else {
showStatus('wifi-status', t('wifi.error.scan') + ': ' + error.message, 'error');
} }
if (select) {
select.innerHTML = `<option value="">${t('wifi.scan.hint')}</option>`;
demoNetworks.forEach(network => {
const option = document.createElement('option');
option.value = network.ssid;
option.textContent = `${network.ssid} (${network.rssi} dBm)`;
select.appendChild(option);
});
}
showStatus('wifi-status', 'Demo: ' + t('wifi.networks.found', { count: demoNetworks.length }), 'info');
} }
} }