Files
esp8266-weather-clock-opens…/docs/v1.9.1_HYBRID_FIX.md
T
Alex Petrochenko 8f0df02e77 Initial commit: v1.9.1 production firmware
Complete reverse engineering of TJ-56-654 weather clock from AliExpress.

Security fixes:
- Eliminated WiFi password leak vulnerability
- Removed dependency on Chinese cloud services (QWeather)
- Secure WiFiManager captive portal setup
- No hardcoded credentials

Features:
- Fully async architecture (zero blocking operations)
- OTA firmware updates (web + ArduinoOTA)
- NTP time sync with timezone + DST support
- Open-Meteo weather API (free, no registration)
- 3 display modes: time, weather, sunrise/sunset
- REST API + web interface
- EEPROM config persistence

Performance:
- Loop time: <1ms (was 10ms+)
- Memory: 409KB flash (38%), 38KB RAM (46%), 62KB IRAM (94%)
- Zero blocking delays

Hardware:
- ESP-01S (ESP8266EX, 1MB flash)
- GM009605v4.3 OLED display (128x64, I2C)
- Custom I2C mapping: SDA=GPIO0, SCL=GPIO2

Documentation:
- Complete installation guide
- Hardware specifications
- API documentation
- Troubleshooting guide
- Version history v1.5 → v1.9.1

Built with Claude Code (Opus 4.5)
Author: Andrey Petrochenko
Date: 2026-01-03
2026-01-03 14:01:31 +00:00

10 KiB
Raw Blame History

v1.9.1 - Hybrid Async Fix

Проблема в v1.9.0

Симптомы:

  • Дисплей показывает пустой экран ~10 секунд после загрузки
  • Ошибка "DNS resolution failed" в логах
  • Время появляется только через 10+ секунд

Причина:

void setup() {
  loadConfig();
  setupWiFi();              // ← Возвращается СРАЗУ (async)
  setupOTA();               // ← WiFi НЕ готов! ✗
  setupWebServer();         // ← WiFi НЕ готов! ✗
  testInternetConnectivity(); // ← WiFi НЕ готов! → "DNS resolution failed"
}

WiFi стал полностью асинхронным, но это неправильно для setup():

  • OTA, web server, NTP требуют готовое WiFi соединение
  • testInternetConnectivity() запускался до подключения WiFi
  • Время на дисплее появлялось только когда async WiFi наконец подключался

Решение: Гибридная модель

Фаза WiFi режим Блокировка Причина
setup() Синхронный 10 сек Нужен для инициализации OTA/web/NTP
loop() Асинхронный 0 сек Не замораживать при reconnect

Изменения в коде

1. setupWiFi() - теперь синхронный

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 секунд)
    int attempts = 0;
    while (WiFi.status() != WL_CONNECTED && attempts < 20) {
      delay(500);
      showNumber(attempts, false);  // Показываем прогресс на дисплее
      attempts++;
    }
    
    if (WiFi.status() == WL_CONNECTED) {
      // ✅ WiFi готов для OTA/web/NTP!
      showIP();
      wifiConnState = WIFI_CONN_CONNECTED;
      return;
    }
  }
  
  // Fallback to WiFiManager если credentials не сработали
  // ...
}

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
  
  // Остальные async операции
  processNTPResponse();
  fetchWeatherAsync();
  // ...
}

Результаты тестирования

До (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 начинается

❌ Display blank for 10+ seconds
❌ "DNS resolution failed" errors

После (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!

✅ 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                  │
│                                                          │
└──────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────┐
│ 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!)                         │
│                                                          │
└──────────────────────────────────────────────────────────┘

Преимущества гибридного подхода

✅ В setup():

  1. Правильный порядок инициализации - WiFi → OTA → web → NTP
  2. Нет ошибок DNS - internet connectivity test запускается ПОСЛЕ WiFi
  3. Предсказуемое поведение - setup() завершается когда всё готово
  4. Дисплей показывает время сразу - не нужно ждать async WiFi

✅ В loop():

  1. Не зависает при reconnect - async обработка потери WiFi
  2. Async NTP - не блокирует loop
  3. Async weather - не блокирует loop
  4. Exponential backoff - умные retry при ошибках
  5. Loop <1ms - всегда отзывчивое устройство

Память

Ресурс v1.9.0 v1.9.1 Изменение
RAM 37,516 37,644 +128 bytes
IRAM 61,987 61,987 0 bytes
Flash 408,540 408,844 +304 bytes

Минимальные изменения памяти (+0.3%) для критического улучшения UX.

Заключение

v1.9.1 реализует идеальный баланс:

  • Setup: Синхронный для надежной инициализации
  • Loop: Асинхронный для отзывчивости

Результат:

  • ✅ Дисплей показывает время через 15 сек (вместо 25+ сек)
  • ✅ Никаких ошибок "DNS resolution failed"
  • ✅ Правильный порядок старта
  • ✅ Устройство не зависает при потере WiFi в работе

Status: Production ready 🚀