docs: bring README/CONTRIBUTING/INSTALLATION up to v1.9.8

README:
- Journey section: add v1.9.5 (weather recovery), v1.9.6 (stability fixes via
  dual AI review), v1.9.7 (config validation), v1.9.8 (live config updates)
- Memory Evolution table: add v1.9.6 and v1.9.8 rows
- Project Structure: list all modular firmware files + tests/test_device.py
- Add Testing section explaining the 73-case hardware-in-the-loop suite
- Bump current version footer to v1.9.8

CONTRIBUTING:
- Flashing section: OTA preferred, FTDI fallback with exact esptool commands
- Add test suite invocation
- Add recovery section (triple power-cycle factory reset since v1.9.6)

INSTALLATION:
- New Factory Reset section explaining the 3x power-cycle trick
- New Verifying Installation section pointing to test_device.py
This commit is contained in:
Alex Petrochenko
2026-05-19 14:16:47 +01:00
parent d0aa68e64f
commit f42ede5fa4
3 changed files with 120 additions and 11 deletions
+33 -5
View File
@@ -73,14 +73,42 @@ Feature requests are welcome! Please include:
arduino-cli compile --fqbn esp8266:esp8266:generic firmware/weather_clock
```
### Testing
### Flashing
```bash
# Upload via FTDI (first time)
arduino-cli upload -p /dev/cu.usbserial* --fqbn esp8266:esp8266:generic
# Upload via OTA (subsequent)
# OTA upload (preferred, when device is on the network)
curl -u admin:admin -F "file=@build/*.bin" http://192.168.x.x/update
# Initial flash via FTDI (3.3V! ESP-01S in socket — no soldering)
# 1. Pull ESP-01S from socket on TJ-56-654 PCB
# 2. Connect: FTDI 3V3→3V3, GND→GND, TX↔RX crossed, GND→GPIO0
# 3. Power on with GPIO0 grounded → bootloader mode
esptool.py --port /dev/cu.usbserial-0001 --baud 115200 write_flash \
--flash_size 1MB --flash_mode dout 0x0 firmware.bin
```
### Running the test suite
Hardware-in-the-loop tests verify functionality and resilience:
```bash
python3 tests/test_device.py <device-ip>
```
73 test cases cover REST API, config validation, fuzz testing, and heap stability.
Safe to run repeatedly — validation rejects garbage, only WiFi changes reboot the device.
### Recovery (bricked device)
Triple power-cycle (≤10s apart, 3 times) triggers factory reset — clears WiFi
credentials and shows AP info on the OLED. Connect to `TJ56654-Setup` /
`12345678` and reconfigure via `http://192.168.4.1/config`.
If that fails, full reset via FTDI:
```bash
esptool.py --port /dev/cu.usbserial-0001 erase_flash
esptool.py --port /dev/cu.usbserial-0001 write_flash 0x0 firmware.bin
```
## Project Structure
+59 -6
View File
@@ -27,7 +27,7 @@ I bought a cute weather clock kit from AliExpress ([TJ-56-654](https://pt.aliexp
- [The Investigation](#the-investigation)
- [The Solution: Custom Firmware](#the-solution-custom-firmware)
- [Technical Deep Dive](#technical-deep-dive)
- [The Journey: v1.7 → v1.9.4](#the-journey-v17--v194)
- [The Journey: v1.7 → v1.9.8](#the-journey-v17--v198)
- [What's Next: Home Assistant Integration](#whats-next-home-assistant-integration)
- [How to Flash This Firmware](#how-to-flash-this-firmware)
- [Web Interface](#web-interface)
@@ -395,7 +395,7 @@ Wire.begin(0, 2); // SDA=GPIO0, SCL=GPIO2
---
## The Journey: v1.7 → v1.9.4
## The Journey: v1.7 → v1.9.8
### v1.7: Display Discovery ✅
@@ -558,7 +558,7 @@ Split monolithic 2,100-line `.ino` into focused modules:
| `web_server.cpp` | Web UI and REST API |
| `wifi_manager.cpp` | WiFi connection and resilience |
### v1.9.4: Bug Fixes & Cleanup ✅ (Current)
### v1.9.4: Community Bug Fixes 🐛
Community-reported bugs fixed:
@@ -568,6 +568,33 @@ Community-reported bugs fixed:
- **ArduinoJson v7**: Updated `StaticJsonDocument` → `JsonDocument` for library compatibility
- **Compiler warnings**: Removed unused variable, fixed sprintf buffer size
### v1.9.5: Weather Recovery 🌦
- **Permanent weather lockup after 3 API failures** (#9): state machine no longer
deadlocks; resets to IDLE after max retries so periodic refresh resumes
### v1.9.6: Long-Running Stability ⏱
Six bugs found via dual AI code review (Sonnet + Gemini):
- **WEATHER_REQUESTING timeout watchdog** (C1): 15s timeout prevents TCP-hang deadlock
- **millis() rollover at 49.7 days** (C2): all retry timers now use subtraction-safe comparison
- **DST formula mathematically wrong** (H1): now uses tm_wday-based last-Sunday calculation
- **String heap fragmentation** (H2): API handlers use `snprintf+sendContent` instead of `String +=`
- **Volatile shared state** (H3): `weatherState`/`ntpState` written from callbacks, read in loop
- **Triple power-cycle factory reset**: 3 quick power cycles within 10s clears WiFi creds (no FTDI needed)
### v1.9.7: Config Validation 🛡
- **`/config` accepted any input, could brick device**: now rejects empty/all-same/oversized
SSIDs (HTTP 400), clamps numeric fields to safe ranges. Closes M1 from internal backlog.
### v1.9.8: Live Config Updates ⚡ (Current)
- **Restart only on WiFi/network changes**: brightness, timezone, intervals, coordinates,
display options apply live. Eliminates display blackouts on minor config tweaks.
- **73/73 automated tests pass** on hardware including full fuzz suite
### Memory Evolution
| Version | RAM Usage | IRAM Usage | Flash Usage | Notes |
@@ -577,6 +604,8 @@ Community-reported bugs fixed:
| v1.9.0 | 37,516 (46%) | **61,987 (94%)** | 408,540 (38%) | Async libs added |
| v1.9.1 | 37,644 (46%) | **61,987 (94%)** | 408,844 (38%) | Hybrid WiFi fix |
| v1.9.2 | 37,800 (47%) | **61,987 (94%)** | 409,100 (39%) | WiFi resilience |
| v1.9.6 | 37,268 (46%) | **61,987 (94%)** | 410,436 (39%) | String → snprintf |
| v1.9.8 | 37,664 (46%) | **61,987 (94%)** | 411,380 (39%) | Live config updates |
**Verdict**: Stable memory usage, no leaks detected after 24h+ uptime tests.
@@ -996,8 +1025,17 @@ Device reboots immediately.
```
esp8266-weather-clock/
├── firmware/
│ └── weather_clock/
│ └── weather_clock.ino # Main firmware (~2,100 lines)
│ └── weather_clock/ # Modular firmware
│ ├── weather_clock.ino # setup() and loop()
│ ├── config.h # Config struct, EEPROM layout
│ ├── globals.h # Shared state
│ ├── display.cpp # OLED rendering
│ ├── ntp_client.cpp # Async NTP + DST
│ ├── weather.cpp # Open-Meteo API
│ ├── web_server.cpp # HTTP UI + REST API
│ └── wifi_manager.cpp # WiFi resilience
├── tests/
│ └── test_device.py # HW-in-the-loop test suite (73 cases)
├── docs/
│ ├── HARDWARE.md # Hardware specifications
│ └── INSTALLATION.md # Flashing guide
@@ -1005,6 +1043,21 @@ esp8266-weather-clock/
└── README.md # This file
```
## Testing
```bash
# Hardware-in-the-loop test suite — verifies API, fuzzes config endpoint,
# checks heap stability over 5 samples
python3 tests/test_device.py 192.168.x.x
```
The test suite covers:
- All REST API endpoints (functional validation)
- Boundary fuzz against `/config` (oversized SSID, invalid intervals, etc.)
- Malformed JSON fuzz against `/api/config` import
- Heap stability check (must stay >20KB, drift <2KB across runs)
---
## License
@@ -1038,4 +1091,4 @@ Now go make something cool. 🚀
**Author**: apetrochenko
**Date**: 2026-01-06
**Firmware Version**: v1.9.4 (Production Ready)
**Firmware Version**: v1.9.8 (Production Ready)
+28
View File
@@ -499,6 +499,34 @@ To save memory, disable unused features:
---
## Factory Reset (no tools needed)
Since v1.9.6, three quick power cycles within 10 seconds trigger a factory reset:
1. Power off the device
2. Power on (briefly, < 10 sec)
3. Power off, power on
4. Power off, power on
On the third boot the OLED shows `FACTORY RESET! WiFi: TJ56654-Setup Pass: 12345678`.
Connect your phone to that AP and reconfigure WiFi at `http://192.168.4.1/config`.
Use this if:
- Forgot configured WiFi credentials
- Device stuck in "No WiFi" loop after router change
- Tests/fuzzing corrupted config
## Verifying Installation
Run the test suite against your device:
```bash
python3 tests/test_device.py 192.168.x.x
```
A healthy device passes all 73 tests, with heap drift under 1 KB across the run.
## Getting Help
If you're still stuck: