fix(web): only reboot on WiFi/network config changes (v1.9.8)

Previously every successful config save triggered ESP.restart() — meant rapid
config changes caused reboot cascades and made the test suite unable to run
multiple cases against /config. Now restarts only if ssid/password/hostname/
ntp_server changed; other settings (brightness, timezone, intervals, coords,
display options) apply live without reboot.

tests/test_device.py: fixed expected field names for /api/time
(time/hours/minutes/epoch instead of current/timezone_offset/ntp_synced).

Test suite: 73/73 passing on hardware. Heap drift <1KB across full fuzz run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alex Petrochenko
2026-05-19 14:12:21 +01:00
co-authored by Claude Sonnet 4.6
parent 5b9285aeb9
commit d0aa68e64f
4 changed files with 40 additions and 23 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
#include <Arduino.h>
// Firmware version
#define FIRMWARE_VERSION "1.9.7"
#define FIRMWARE_VERSION "1.9.8"
// OLED I2C Configuration
#define I2C_SDA 0 // GPIO0 (I2C Data) - SWAPPED!
+23 -13
View File
@@ -298,6 +298,9 @@ static bool isValidSSID(const String& s) {
}
void ICACHE_FLASH_ATTR handleConfigSave() {
// Track if reboot is required (only WiFi/network changes need it)
bool needsRestart = false;
// Validate before saving — reject obviously bad input rather than brick the device
if (server.hasArg("ssid")) {
String s = server.arg("ssid");
@@ -305,6 +308,7 @@ void ICACHE_FLASH_ATTR handleConfigSave() {
server.send(400, "text/plain", "Invalid SSID (1-31 printable chars, not all-same)");
return;
}
if (s != String(config.ssid)) needsRestart = true;
safeStringCopy(s, config.ssid, sizeof(config.ssid));
}
if (server.hasArg("password")) {
@@ -313,6 +317,7 @@ void ICACHE_FLASH_ATTR handleConfigSave() {
server.send(400, "text/plain", "Password too long (max 63 chars)");
return;
}
if (p != String(config.password)) needsRestart = true;
safeStringCopy(p, config.password, sizeof(config.password));
}
if (server.hasArg("timezone")) {
@@ -328,6 +333,7 @@ void ICACHE_FLASH_ATTR handleConfigSave() {
server.send(400, "text/plain", "Invalid hostname length (1-31)");
return;
}
if (h != String(config.hostname)) needsRestart = true; // mDNS bind on boot
safeStringCopy(h, config.hostname, sizeof(config.hostname));
}
if (server.hasArg("city_name")) {
@@ -360,6 +366,7 @@ void ICACHE_FLASH_ATTR handleConfigSave() {
server.send(400, "text/plain", "Invalid NTP server (1-63 chars)");
return;
}
if (n != String(config.ntp_server)) needsRestart = true; // NTPClient re-init
safeStringCopy(n, config.ntp_server, sizeof(config.ntp_server));
}
if (server.hasArg("display_rotation_sec")) {
@@ -372,19 +379,22 @@ void ICACHE_FLASH_ATTR handleConfigSave() {
saveConfig();
String html = F("<!DOCTYPE html><html><head>");
html += F("<meta charset='UTF-8'>");
html += F("<meta http-equiv='refresh' content='5;url=/'>");
html += F("<style>body{font-family:Arial;text-align:center;margin-top:50px;}</style>");
html += F("</head><body>");
html += F("<h1>Configuration Saved!</h1>");
html += F("<p>Device will reboot in 5 seconds...</p>");
html += F("</body></html>");
server.send(200, "text/html", html);
delay(1000);
ESP.restart();
// Only restart for WiFi/network changes; other settings apply live
if (needsRestart) {
server.send(200, "text/html",
F("<!DOCTYPE html><meta charset='UTF-8'>"
"<meta http-equiv='refresh' content='5;url=/'>"
"<h1>Configuration Saved!</h1>"
"<p>WiFi/network changed — device will reboot in 5 seconds...</p>"));
delay(1000);
ESP.restart();
} else {
server.send(200, "text/html",
F("<!DOCTYPE html><meta charset='UTF-8'>"
"<meta http-equiv='refresh' content='2;url=/'>"
"<h1>Configuration Saved</h1>"
"<p>Applied without reboot. Returning to main page...</p>"));
}
}
void ICACHE_FLASH_ATTR handleAPITime() {