Compare commits

3 Commits

Author SHA1 Message Date
f7cedf24e8 shared website header
Some checks failed
ESP-IDF Build / build (esp32c6, release-v5.4) (push) Failing after 7m45s
ESP-IDF Build / build (esp32c6, release-v5.5) (push) Failing after 4m52s
ESP-IDF Build / build (esp32s3, release-v5.4) (push) Failing after 4m35s
ESP-IDF Build / build (esp32s3, release-v5.5) (push) Failing after 4m58s
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2026-01-18 10:59:08 +01:00
1c52f7d679 fixed devcontainer image
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2026-01-18 10:58:35 +01:00
7a73fc4b7b implement reset via back button
Signed-off-by: Peter Siegmund <developer@mars3142.org>
2026-01-18 10:58:24 +01:00
11 changed files with 223 additions and 53 deletions

View File

@@ -1,4 +1,4 @@
ARG DOCKER_TAG=latest ARG DOCKER_TAG=release-v5.4
FROM espressif/idf:${DOCKER_TAG} FROM espressif/idf:${DOCKER_TAG}
ENV LC_ALL=C.UTF-8 ENV LC_ALL=C.UTF-8

View File

@@ -29,6 +29,31 @@ static EventGroupHandle_t s_wifi_event_group;
static const char *TAG = "wifi_manager"; static const char *TAG = "wifi_manager";
static void wifi_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
ESP_LOGI(TAG, "WIFI_EVENT_STA_START: Connecting to AP...");
esp_wifi_connect();
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
ESP_LOGW(TAG, "WIFI_EVENT_STA_DISCONNECTED: Verbindung verloren, versuche erneut...");
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "IP_EVENT_STA_GOT_IP: IP-Adresse erhalten: " IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP)
{
ESP_LOGW(TAG, "IP_EVENT_STA_LOST_IP: IP-Adresse verloren!");
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
}
static void wifi_create_ap() static void wifi_create_ap()
{ {
ESP_ERROR_CHECK(esp_wifi_stop()); ESP_ERROR_CHECK(esp_wifi_stop());
@@ -58,6 +83,13 @@ void wifi_manager_init()
ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default()); ESP_ERROR_CHECK(esp_event_loop_create_default());
// Default WiFi Station
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
// Event Handler registrieren
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
// Try to load stored WiFi configuration // Try to load stored WiFi configuration
persistence_manager_t pm; persistence_manager_t pm;
char ssid[33] = {0}; char ssid[33] = {0};
@@ -97,9 +129,9 @@ void wifi_manager_init()
EventBits_t bits; EventBits_t bits;
do do
{ {
esp_wifi_connect(); ESP_LOGI(TAG, "Warte auf IP-Adresse (DHCP)...");
bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, bits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE,
5000 / portTICK_PERIOD_MS); 10000 / portTICK_PERIOD_MS);
if (bits & WIFI_CONNECTED_BIT) if (bits & WIFI_CONNECTED_BIT)
{ {
led_behavior_t led_behavior = { led_behavior_t led_behavior = {
@@ -108,7 +140,7 @@ void wifi_manager_init()
.mode = LED_MODE_SOLID, .mode = LED_MODE_SOLID,
}; };
led_status_set_behavior(led_behavior); led_status_set_behavior(led_behavior);
ESP_LOGI(TAG, "WiFi connection established successfully"); ESP_LOGI(TAG, "WiFi connection established successfully (mit IP)");
break; break;
} }
retries++; retries++;
@@ -116,7 +148,9 @@ void wifi_manager_init()
if (!(bits & WIFI_CONNECTED_BIT)) if (!(bits & WIFI_CONNECTED_BIT))
{ {
ESP_LOGW(TAG, "WiFi connection failed, switching to Access Point mode"); ESP_LOGW(TAG, "WiFi connection failed (keine IP?), switching to Access Point mode");
// AP-Netzwerkschnittstelle initialisieren, falls noch nicht geschehen
esp_netif_create_default_wifi_ap();
wifi_create_ap(); wifi_create_ap();
} }
} }

View File

@@ -28,6 +28,15 @@ extern "C"
bool initialized; bool initialized;
} persistence_manager_t; } persistence_manager_t;
/**
* @brief Erases the entire NVS flash (factory reset).
*
* Warning: This will remove all stored data and namespaces!
*
* @return esp_err_t ESP_OK on success, otherwise error code.
*/
esp_err_t persistence_manager_factory_reset(void);
/** /**
* @brief Initialize the persistence manager with a given NVS namespace. * @brief Initialize the persistence manager with a given NVS namespace.
* *

View File

@@ -1,9 +1,21 @@
#include "persistence_manager.h" #include "persistence_manager.h"
#include <esp_log.h> #include <esp_log.h>
#include <string.h> #include <string.h>
#define TAG "persistence_manager" #define TAG "persistence_manager"
esp_err_t persistence_manager_factory_reset(void)
{
// Erase the entire NVS flash (factory reset)
esp_err_t err = nvs_flash_erase();
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Factory reset failed: %s", esp_err_to_name(err));
}
return err;
}
esp_err_t 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)

View File

@@ -2,6 +2,7 @@
#include "analytics.h" #include "analytics.h"
#include "button_handling.h" #include "button_handling.h"
#include "common.h"
#include "common/InactivityTracker.h" #include "common/InactivityTracker.h"
#include "hal/u8g2_esp32_hal.h" #include "hal/u8g2_esp32_hal.h"
#include "i2c_checker.h" #include "i2c_checker.h"
@@ -182,7 +183,55 @@ void app_task(void *args)
return; return;
} }
// Display initialisieren, damit Info angezeigt werden kann
setup_screen(); setup_screen();
// BACK-Button prüfen und ggf. Einstellungen löschen (mit Countdown)
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << BUTTON_BACK);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&io_conf);
vTaskDelay(pdMS_TO_TICKS(10));
if (gpio_get_level(BUTTON_BACK) == 0)
{
u8g2_SetFont(&u8g2, u8g2_font_ncenB08_tr);
for (int i = 5; i > 0; --i)
{
u8g2_ClearBuffer(&u8g2);
u8g2_DrawStr(&u8g2, 5, 20, "BACK gedrueckt!");
u8g2_DrawStr(&u8g2, 5, 35, "Halte fuer Reset...");
char buf[32];
snprintf(buf, sizeof(buf), "Loesche in %d s", i);
u8g2_DrawStr(&u8g2, 5, 55, buf);
u8g2_SendBuffer(&u8g2);
vTaskDelay(pdMS_TO_TICKS(1000));
if (gpio_get_level(BUTTON_BACK) != 0)
{
// Button losgelassen, abbrechen
break;
}
if (i == 1)
{
// After 5 seconds still pressed: perform factory reset
u8g2_ClearBuffer(&u8g2);
u8g2_DrawStr(&u8g2, 5, 30, "Alle Einstellungen ");
u8g2_DrawStr(&u8g2, 5, 45, "werden geloescht...");
u8g2_SendBuffer(&u8g2);
persistence_manager_factory_reset();
vTaskDelay(pdMS_TO_TICKS(1000));
u8g2_ClearBuffer(&u8g2);
u8g2_DrawStr(&u8g2, 5, 35, "Fertig. Neustart...");
u8g2_SendBuffer(&u8g2);
vTaskDelay(pdMS_TO_TICKS(1000));
esp_restart();
}
}
}
setup_buttons(); setup_buttons();
init_ui(); init_ui();

View File

@@ -114,18 +114,6 @@
updateConnectBtn(); updateConnectBtn();
}); });
// Toggle password visibility
function togglePassword() {
const input = document.getElementById('password');
const btn = document.getElementById('password-btn');
if (input.type === 'password') {
input.type = 'text';
btn.textContent = '🙈';
} else {
input.type = 'password';
btn.textContent = '👁️';
}
}
</script> </script>
</body> </body>

View File

@@ -1,29 +1,57 @@
@media (max-width: 600px) {
.header {
flex-direction: row;
align-items: flex-start;
text-align: left;
}
.header h1 {
flex: 1 1 100%;
text-align: center;
order: 2;
margin-top: 8px;
}
.header-controls {
order: 1;
flex: 1 1 auto;
justify-content: flex-start;
display: flex;
gap: 8px;
}
}
/* Captive Portal CSS - WiFi setup specific styles */ /* Captive Portal CSS - WiFi setup specific styles */
/* Base styles are in shared.css */ /* Base styles are in shared.css */
body { body {
padding: 16px; padding: 12px;
display: flex;
align-items: center;
justify-content: center;
} }
.container { .container {
max-width: 900px;
margin: 0 auto;
width: 100%; width: 100%;
max-width: 400px;
} }
/* Header */ /* Header */
.header { .header {
text-align: center; display: flex;
margin-bottom: 24px; justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 10px;
} }
.header h1 { .header h1 {
font-size: 1.4rem; font-size: 1.5rem;
margin-bottom: 8px; margin: 0;
} }
.header p { .header p {
color: var(--text-muted); color: var(--text-muted);
font-size: 0.9rem; font-size: 0.9rem;
@@ -163,27 +191,6 @@ select {
font-size: 1.1rem; font-size: 1.1rem;
} }
/* Password Toggle */
.password-toggle {
position: relative;
}
.password-toggle input {
padding-right: 50px;
}
.password-toggle button {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: var(--text-muted);
font-size: 1.2rem;
cursor: pointer;
padding: 4px;
}
/* Info Box */ /* Info Box */
.info-box { .info-box {

View File

@@ -20,10 +20,6 @@ body {
gap: 10px; gap: 10px;
} }
.header h1 {
font-size: 1.5rem;
margin: 0;
}
.form-group { .form-group {
margin-bottom: 12px; margin-bottom: 12px;

View File

@@ -1,3 +1,60 @@
/* Passwortfeld Toggle (zentral für alle Seiten) */
.password-toggle {
position: relative;
display: flex;
align-items: center;
gap: 0;
}
.password-toggle input {
padding-right: 50px;
flex: 1;
}
.password-toggle button {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
color: var(--text-muted);
font-size: 1.2rem;
cursor: pointer;
padding: 4px;
transition: color 0.2s;
}
.password-toggle button:active {
color: var(--accent);
}
/* Passwortfeld Toggle */
.password-toggle {
display: flex;
align-items: center;
gap: 6px;
}
.password-toggle input[type="password"],
.password-toggle input[type="text"] {
flex: 1;
}
.password-toggle button {
background: none;
border: none;
font-size: 1.2em;
cursor: pointer;
color: var(--text-muted);
padding: 0 6px;
transition: color 0.2s;
}
.password-toggle button:active {
color: var(--accent);
}
/* Shared CSS - Base styles for all pages */ /* Shared CSS - Base styles for all pages */
/* CSS Variables - Dark Mode (default) */ /* CSS Variables - Dark Mode (default) */

View File

@@ -162,8 +162,11 @@
<div class="form-group"> <div class="form-group">
<label for="password" data-i18n="wifi.password">WLAN Passwort</label> <label for="password" data-i18n="wifi.password">WLAN Passwort</label>
<div class="password-toggle">
<input type="password" id="password" data-i18n-placeholder="wifi.password.placeholder" <input type="password" id="password" data-i18n-placeholder="wifi.password.placeholder"
placeholder="Passwort eingeben" autocomplete="off"> placeholder="Passwort eingeben" autocomplete="off">
<button type="button" onclick="togglePassword()" id="password-btn">👁️</button>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@@ -1,3 +1,18 @@
/**
* Passwortfeld sichtbar/unsichtbar schalten (shared)
*/
function togglePassword() {
const input = document.getElementById('password');
const btn = document.getElementById('password-btn');
if (!input || !btn) return;
if (input.type === 'password') {
input.type = 'text';
btn.textContent = '🙈';
} else {
input.type = 'password';
btn.textContent = '👁️';
}
}
// Shared WiFi configuration functions // Shared WiFi configuration functions
// Used by both captive.html and index.html // Used by both captive.html and index.html