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();