From aee1d3e0ef7f9d8891d1c73d63ae61d258b39dce Mon Sep 17 00:00:00 2001 From: Alex Petrochenko Date: Sun, 20 Sep 2026 15:40:46 +0100 Subject: [PATCH] fix(wifi): persist password alongside SSID after SDK/WiFiManager connect (v1.9.10) After a successful connect via SDK-cached credentials (Try 1) or the WiFiManager captive portal, only the SSID was saved to EEPROM. On the next boot config.password was empty, so setupWiFi() and the reconnect loop called WiFi.begin(ssid) as if the network were open and failed with WL_WRONG_PASSWORD. v1.9.6 masked this: with an empty password the retry loop fell back to a bare WiFi.begin(), which reuses the SDK-stored credentials. Commit 02834ba (v1.9.7) replaced that fallback with WiFi.begin(config.ssid), and v1.9.9 (M2) added the same call to setupWiFi(), so every version after 1.9.6 never reconnects after a reboot when the device was set up through the portal. Read the password back with WiFi.psk() and store it next to the SSID in all three places that sync the SSID. Devices already in this state recover by entering the password once in the web UI (fallback AP after ~2.5 min). Verified: builds with PlatformIO (espressif8266 core 3.1.2, esp01_1m). Fixes #12 Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 14 ++++++++++++++ README.md | 4 ++-- firmware/weather_clock/config.h | 2 +- firmware/weather_clock/wifi_manager.cpp | 11 ++++++++++- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed2f84e..ba6c024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.9.10] - 2026-09-20 + +### Fixed + +- **WiFi password lost after captive-portal setup, no reconnect after reboot** (#12): + after a successful connect via SDK-cached credentials (Try 1) or the WiFiManager + portal, only the SSID was written to EEPROM. On the next boot `config.password` + was empty, so `setupWiFi()` and the reconnect loop called `WiFi.begin(ssid)` as + if the network were open and failed with `WL_WRONG_PASSWORD`. v1.9.6 masked this + through the bare `WiFi.begin()` fallback in the retry loop, which 02834ba (v1.9.7) + replaced with `WiFi.begin(config.ssid)`. Now the password is read back with + `WiFi.psk()` and saved next to the SSID in all three places that sync the SSID. + Devices already stuck recover by entering the password once in the web UI. + ## [1.9.9] - 2026-05-19 ### Fixed diff --git a/README.md b/README.md index 14a58db..32b9068 100644 --- a/README.md +++ b/README.md @@ -266,7 +266,7 @@ For architecture details (async state machines, memory budget, EEPROM layout, fa ## Version History -The clock has gone through many iterations — display hardware discovery (v1.5–v1.7), stability and security fixes (v1.8), full async refactor (v1.9.0), and a long series of bug fixes informed by community reports and AI-assisted code review (v1.9.1–v1.9.9). +The clock has gone through many iterations — display hardware discovery (v1.5–v1.7), stability and security fixes (v1.8), full async refactor (v1.9.0), and a long series of bug fixes informed by community reports and AI-assisted code review (v1.9.1–v1.9.10). See [CHANGELOG.md](CHANGELOG.md) for the full version history with technical details. @@ -668,4 +668,4 @@ This project is released into the public domain. Do whatever you want with it. I --- -**Author**: apetrochenko · **License**: MIT · **Firmware**: v1.9.9 +**Author**: apetrochenko · **License**: MIT · **Firmware**: v1.9.10 diff --git a/firmware/weather_clock/config.h b/firmware/weather_clock/config.h index 452e448..09a2563 100644 --- a/firmware/weather_clock/config.h +++ b/firmware/weather_clock/config.h @@ -9,7 +9,7 @@ #include // Firmware version -#define FIRMWARE_VERSION "1.9.9" +#define FIRMWARE_VERSION "1.9.10" // OLED I2C Configuration #define I2C_SDA 0 // GPIO0 (I2C Data) - SWAPPED! diff --git a/firmware/weather_clock/wifi_manager.cpp b/firmware/weather_clock/wifi_manager.cpp index e30bf2a..f156c6e 100644 --- a/firmware/weather_clock/wifi_manager.cpp +++ b/firmware/weather_clock/wifi_manager.cpp @@ -28,9 +28,10 @@ void ICACHE_FLASH_ATTR processWiFiConnection() { WiFi.mode(WIFI_STA); } - // Sync connected SSID to config + // Sync connected SSID + password to config if (strlen(config.ssid) == 0) { safeStringCopy(WiFi.SSID(), config.ssid, sizeof(config.ssid)); + safeStringCopy(WiFi.psk(), config.password, sizeof(config.password)); saveConfig(); } @@ -96,7 +97,12 @@ void ICACHE_FLASH_ATTR setupWiFi() { Serial.print("DNS: "); Serial.println(WiFi.dnsIP()); + // Persist the password too, not just the SSID. The SDK keeps the + // password in its own flash area, but every later boot reads + // config.password: an empty one is treated as an open network + // (WiFi.begin(ssid)) and fails with WL_WRONG_PASSWORD (issue #12). safeStringCopy(WiFi.SSID(), config.ssid, sizeof(config.ssid)); + safeStringCopy(WiFi.psk(), config.password, sizeof(config.password)); saveConfig(); showIP(); @@ -166,7 +172,10 @@ void ICACHE_FLASH_ATTR setupWiFi() { Serial.print("IP: "); Serial.println(WiFi.localIP()); + // Save both SSID and password (issue #12): WiFiManager stores them in the + // SDK flash, but the next boot connects from config.* only. safeStringCopy(WiFi.SSID(), config.ssid, sizeof(config.ssid)); + safeStringCopy(WiFi.psk(), config.password, sizeof(config.password)); saveConfig(); showIP();