- CHANGELOG.md: Add v1.9.2 WiFi resilience changes - README.md: Update journey section, memory stats, project structure - docs/v1.9.1_HYBRID_FIX.md: Minor formatting fixes - docs/v1.9.2_WIFI_RESILIENCE.md: New detailed documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
7.4 KiB
7.4 KiB
v1.9.1 - Hybrid Async Fix
Problem in v1.9.0
Symptoms:
- Display shows blank screen ~10 seconds after boot
- "DNS resolution failed" errors in logs
- Time appears only after 10+ seconds
Root Cause:
void setup() {
loadConfig();
setupWiFi(); // Returns IMMEDIATELY (async)
setupOTA(); // WiFi NOT ready!
setupWebServer(); // WiFi NOT ready!
testInternetConnectivity(); // WiFi NOT ready! -> "DNS resolution failed"
}
WiFi became fully asynchronous, but this is wrong for setup():
- OTA, web server, NTP require a ready WiFi connection
testInternetConnectivity()ran before WiFi connected- Time on display appeared only when async WiFi finally connected
Solution: Hybrid Model
| Phase | WiFi Mode | Blocking | Reason |
|---|---|---|---|
| setup() | Synchronous | 10 sec | Required for OTA/web/NTP initialization |
| loop() | Asynchronous | 0 sec | Don't freeze on reconnect |
Code Changes
1. setupWiFi() - Now Synchronous
void ICACHE_FLASH_ATTR setupWiFi() {
Serial.println("WiFi Setup - Synchronous for initial connection");
WiFi.hostname(config.hostname);
if (strlen(config.ssid) > 0) {
WiFi.mode(WIFI_STA);
WiFi.begin(config.ssid, config.password);
// SYNCHRONOUS wait (max 10 seconds)
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
showNumber(attempts, false); // Show progress on display
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
// WiFi ready for OTA/web/NTP!
showIP();
wifiConnState = WIFI_CONN_CONNECTED;
return;
}
}
// Fallback to WiFiManager if credentials didn't work
// ...
}
2. loop() - Async Reconnect
void loop() {
// WiFi reconnection (async, non-blocking)
static unsigned long lastWiFiCheck = 0;
if (millis() - lastWiFiCheck > 5000) {
if (WiFi.status() != WL_CONNECTED && wifiConnState == WIFI_CONN_CONNECTED) {
Serial.println("WiFi disconnected, attempting async reconnect...");
WiFi.begin(); // Async reconnect
wifiConnState = WIFI_CONN_CONNECTING;
wifiConnectStart = millis();
}
lastWiFiCheck = millis();
}
processWiFiConnection(); // Async reconnect handling
// Other async operations
processNTPResponse();
fetchWeatherAsync();
// ...
}
Test Results
Before (v1.9.0)
[0-5s] -> Display init
[5-15s] -> WiFi connecting (async, setup() returns immediately)
[15-20s] -> OTA/web init WITHOUT WiFi -> Errors!
[20s] -> testInternetConnectivity() WITHOUT WiFi -> "DNS resolution failed"
[15-25s] -> WiFi finally connects (async)
[25-30s] -> NTP sync begins
Display blank for 10+ seconds
"DNS resolution failed" errors
After (v1.9.1)
[0-5s] -> Display init + startup animation
[5-15s] -> WiFi connection (SYNCHRONOUS, setup() waits)
WiFi connected!
[15-20s] -> OTA/web/NTP init WITH WiFi
Internet test: PASSED
No DNS errors!
[20-30s] -> First async NTP sync
Time synced!
Display shows time immediately after WiFi connects (~15 sec)
No "DNS resolution failed" errors
Proper initialization order
Startup Timeline
+----------------------------------------------------------+
| SETUP PHASE (Synchronous WiFi) |
+----------------------------------------------------------+
| |
| [0s] +-------------+ |
| | Display | Startup animation |
| [5s] | Init | "Weather Clock v1.9.1" |
| +-------------+ |
| |
| [5s] +---------------------------------+ |
| | WiFi Connect (SYNCHRONOUS) | |
| | - Connecting to network... | |
| [15s] | - Connected! IP assigned | |
| +---------------------------------+ |
| | |
| WiFi is READY here |
| | |
| [15s] +---------------------------------+ |
| | OTA Init (needs WiFi) | |
| | Web Server (needs WiFi) | |
| [20s] | NTP Client (needs WiFi) | |
| | Internet Test (needs WiFi) | |
| +---------------------------------+ |
| |
| [20s] Setup complete! -> loop() starts |
| |
+----------------------------------------------------------+
+----------------------------------------------------------+
| LOOP PHASE (Async Operations) |
+----------------------------------------------------------+
| |
| [Every loop] +------------------------+ |
| | WiFi Health Check | |
| | (every 5 sec) | |
| | If disconnected: | |
| | -> Async reconnect | |
| +------------------------+ |
| |
| [Every loop] +------------------------+ |
| | Async NTP Processing | |
| | (non-blocking) | |
| +------------------------+ |
| |
| [Every 30m] +------------------------+ |
| | Async Weather Fetch | |
| | (non-blocking) | |
| +------------------------+ |
| |
| Loop time: <1ms (no blocking!) |
| |
+----------------------------------------------------------+
Benefits of Hybrid Approach
In setup():
- Correct initialization order - WiFi -> OTA -> web -> NTP
- No DNS errors - internet connectivity test runs AFTER WiFi
- Predictable behavior - setup() completes when everything is ready
- Display shows time immediately - no need to wait for async WiFi
In loop():
- No freeze on reconnect - async handling of WiFi loss
- Async NTP - doesn't block loop
- Async weather - doesn't block loop
- Exponential backoff - smart retry on errors
- Loop <1ms - always responsive device
Memory
| Resource | v1.9.0 | v1.9.1 | Change |
|---|---|---|---|
| RAM | 37,516 | 37,644 | +128 bytes |
| IRAM | 61,987 | 61,987 | 0 bytes |
| Flash | 408,540 | 408,844 | +304 bytes |
Minimal memory changes (+0.3%) for critical UX improvement.
Conclusion
v1.9.1 implements the ideal balance:
- Setup: Synchronous for reliable initialization
- Loop: Asynchronous for responsiveness
Result:
- Display shows time after 15 sec (instead of 25+ sec)
- No "DNS resolution failed" errors
- Correct startup order
- Device doesn't freeze on WiFi loss during operation
Status: Production ready