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 <noreply@anthropic.com>
This commit is contained in:
Alex Petrochenko
2026-09-20 15:40:46 +01:00
co-authored by Claude Fable 5.1
parent 75b6a21f3c
commit aee1d3e0ef
4 changed files with 27 additions and 4 deletions
+14
View File
@@ -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
+2 -2
View File
@@ -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
+1 -1
View File
@@ -9,7 +9,7 @@
#include <Arduino.h>
// 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!
+10 -1
View File
@@ -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();