docs: update documentation for v1.9.2
- 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>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
4a7e889052
commit
abf3a513c9
+135
-135
@@ -1,71 +1,71 @@
|
||||
# v1.9.1 - Hybrid Async Fix
|
||||
|
||||
## Проблема в v1.9.0
|
||||
## Problem in v1.9.0
|
||||
|
||||
**Симптомы:**
|
||||
- Дисплей показывает пустой экран ~10 секунд после загрузки
|
||||
- Ошибка "DNS resolution failed" в логах
|
||||
- Время появляется только через 10+ секунд
|
||||
**Symptoms:**
|
||||
- Display shows blank screen ~10 seconds after boot
|
||||
- "DNS resolution failed" errors in logs
|
||||
- Time appears only after 10+ seconds
|
||||
|
||||
**Причина:**
|
||||
**Root Cause:**
|
||||
```cpp
|
||||
void setup() {
|
||||
loadConfig();
|
||||
setupWiFi(); // ← Возвращается СРАЗУ (async)
|
||||
setupOTA(); // ← WiFi НЕ готов! ✗
|
||||
setupWebServer(); // ← WiFi НЕ готов! ✗
|
||||
testInternetConnectivity(); // ← WiFi НЕ готов! → "DNS resolution failed"
|
||||
setupWiFi(); // Returns IMMEDIATELY (async)
|
||||
setupOTA(); // WiFi NOT ready!
|
||||
setupWebServer(); // WiFi NOT ready!
|
||||
testInternetConnectivity(); // WiFi NOT ready! -> "DNS resolution failed"
|
||||
}
|
||||
```
|
||||
|
||||
WiFi стал **полностью асинхронным**, но это **неправильно для setup()**:
|
||||
- OTA, web server, NTP **требуют готовое WiFi соединение**
|
||||
- `testInternetConnectivity()` запускался **до подключения WiFi**
|
||||
- Время на дисплее появлялось только когда async WiFi наконец подключался
|
||||
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
|
||||
|
||||
| Фаза | WiFi режим | Блокировка | Причина |
|
||||
|------|------------|------------|---------|
|
||||
| **setup()** | **Синхронный** | 10 сек | Нужен для инициализации OTA/web/NTP |
|
||||
| **loop()** | **Асинхронный** | 0 сек | Не замораживать при reconnect |
|
||||
| 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() - теперь синхронный
|
||||
#### 1. setupWiFi() - Now Synchronous
|
||||
|
||||
```cpp
|
||||
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);
|
||||
|
||||
// СИНХРОННОЕ ожидание (max 10 секунд)
|
||||
|
||||
// SYNCHRONOUS wait (max 10 seconds)
|
||||
int attempts = 0;
|
||||
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
|
||||
delay(500);
|
||||
showNumber(attempts, false); // Показываем прогресс на дисплее
|
||||
showNumber(attempts, false); // Show progress on display
|
||||
attempts++;
|
||||
}
|
||||
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
// ✅ WiFi готов для OTA/web/NTP!
|
||||
// WiFi ready for OTA/web/NTP!
|
||||
showIP();
|
||||
wifiConnState = WIFI_CONN_CONNECTED;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to WiFiManager если credentials не сработали
|
||||
|
||||
// Fallback to WiFiManager if credentials didn't work
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. loop() - async reconnect
|
||||
#### 2. loop() - Async Reconnect
|
||||
|
||||
```cpp
|
||||
void loop() {
|
||||
@@ -73,146 +73,146 @@ void loop() {
|
||||
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...");
|
||||
Serial.println("WiFi disconnected, attempting async reconnect...");
|
||||
WiFi.begin(); // Async reconnect
|
||||
wifiConnState = WIFI_CONN_CONNECTING;
|
||||
wifiConnectStart = millis();
|
||||
}
|
||||
lastWiFiCheck = millis();
|
||||
}
|
||||
|
||||
processWiFiConnection(); // Async обработка reconnect
|
||||
|
||||
// Остальные async операции
|
||||
|
||||
processWiFiConnection(); // Async reconnect handling
|
||||
|
||||
// Other async operations
|
||||
processNTPResponse();
|
||||
fetchWeatherAsync();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Результаты тестирования
|
||||
## Test Results
|
||||
|
||||
### До (v1.9.0)
|
||||
### Before (v1.9.0)
|
||||
```
|
||||
⏱️ 0-5s → Display init
|
||||
⏱️ 5-15s → WiFi connecting (async, setup() возвращается сразу)
|
||||
⏱️ 15-20s → OTA/web init БЕЗ WiFi → ✗ Errors!
|
||||
⏱️ 20s → testInternetConnectivity() БЕЗ WiFi → "DNS resolution failed"
|
||||
⏱️ 15-25s → WiFi finally connects (async)
|
||||
⏱️ 25-30s → NTP sync начинается
|
||||
[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
|
||||
Display blank for 10+ seconds
|
||||
"DNS resolution failed" errors
|
||||
```
|
||||
|
||||
### После (v1.9.1)
|
||||
### 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 С WiFi
|
||||
✅ Internet test: PASSED
|
||||
✅ No DNS errors!
|
||||
⏱️ 20-30s → First async NTP sync
|
||||
✅ Time synced!
|
||||
[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
|
||||
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 SibWings... │ │
|
||||
│ [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 │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
+----------------------------------------------------------+
|
||||
| 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!) │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
+----------------------------------------------------------+
|
||||
| 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
|
||||
|
||||
### ✅ В setup():
|
||||
1. **Правильный порядок инициализации** - WiFi → OTA → web → NTP
|
||||
2. **Нет ошибок DNS** - internet connectivity test запускается ПОСЛЕ WiFi
|
||||
3. **Предсказуемое поведение** - setup() завершается когда всё готово
|
||||
4. **Дисплей показывает время сразу** - не нужно ждать async WiFi
|
||||
### In setup():
|
||||
1. **Correct initialization order** - WiFi -> OTA -> web -> NTP
|
||||
2. **No DNS errors** - internet connectivity test runs AFTER WiFi
|
||||
3. **Predictable behavior** - setup() completes when everything is ready
|
||||
4. **Display shows time immediately** - no need to wait for async WiFi
|
||||
|
||||
### ✅ В loop():
|
||||
1. **Не зависает при reconnect** - async обработка потери WiFi
|
||||
2. **Async NTP** - не блокирует loop
|
||||
3. **Async weather** - не блокирует loop
|
||||
4. **Exponential backoff** - умные retry при ошибках
|
||||
5. **Loop <1ms** - всегда отзывчивое устройство
|
||||
### In loop():
|
||||
1. **No freeze on reconnect** - async handling of WiFi loss
|
||||
2. **Async NTP** - doesn't block loop
|
||||
3. **Async weather** - doesn't block loop
|
||||
4. **Exponential backoff** - smart retry on errors
|
||||
5. **Loop <1ms** - always responsive device
|
||||
|
||||
## Память
|
||||
## Memory
|
||||
|
||||
| Ресурс | v1.9.0 | v1.9.1 | Изменение |
|
||||
|--------|--------|--------|-----------|
|
||||
| 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 |
|
||||
|
||||
Минимальные изменения памяти (+0.3%) для критического улучшения UX.
|
||||
Minimal memory changes (+0.3%) for critical UX improvement.
|
||||
|
||||
## Заключение
|
||||
## Conclusion
|
||||
|
||||
**v1.9.1 реализует идеальный баланс:**
|
||||
- Setup: Синхронный для надежной инициализации
|
||||
- Loop: Асинхронный для отзывчивости
|
||||
**v1.9.1 implements the ideal balance:**
|
||||
- Setup: Synchronous for reliable initialization
|
||||
- Loop: Asynchronous for responsiveness
|
||||
|
||||
**Результат:**
|
||||
- ✅ Дисплей показывает время через 15 сек (вместо 25+ сек)
|
||||
- ✅ Никаких ошибок "DNS resolution failed"
|
||||
- ✅ Правильный порядок старта
|
||||
- ✅ Устройство не зависает при потере WiFi в работе
|
||||
**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 🚀
|
||||
**Status**: Production ready
|
||||
|
||||
Reference in New Issue
Block a user