docs: add ArduinoJson to required libraries list (closes #4)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
441c61676b
commit
c606bcc589
@@ -60,6 +60,7 @@ When you first set up the device, it creates an access point with a default pass
|
|||||||
5. **Your WiFi password is displayed in plaintext on the config page**
|
5. **Your WiFi password is displayed in plaintext on the config page**
|
||||||
|
|
||||||
Anyone within WiFi range could:
|
Anyone within WiFi range could:
|
||||||
|
|
||||||
- Connect to the device's AP (weak default password)
|
- Connect to the device's AP (weak default password)
|
||||||
- Browse to 192.168.4.1
|
- Browse to 192.168.4.1
|
||||||
- Read your WiFi password in plaintext
|
- Read your WiFi password in plaintext
|
||||||
@@ -78,13 +79,13 @@ This is a textbook example of poor IoT security design. No thanks.
|
|||||||
|
|
||||||
### Original Hardware Specifications
|
### Original Hardware Specifications
|
||||||
|
|
||||||
| Component | Details |
|
| Component | Details |
|
||||||
|-----------|---------|
|
| ----------- | ---------------------------------------- |
|
||||||
| **MCU** | ESP-01S (ESP8266EX, 1MB flash, 80KB RAM) |
|
| **MCU** | ESP-01S (ESP8266EX, 1MB flash, 80KB RAM) |
|
||||||
| **Display** | GM009605v4.3 OLED (128x64, I2C) |
|
| **Display** | GM009605v4.3 OLED (128x64, I2C) |
|
||||||
| **Power** | 5V USB (Micro-USB) |
|
| **Power** | 5V USB (Micro-USB) |
|
||||||
| **Case** | Transparent acrylic (40x40x43mm) |
|
| **Case** | Transparent acrylic (40x40x43mm) |
|
||||||
| **PCB** | TJ-56-654 main board |
|
| **PCB** | TJ-56-654 main board |
|
||||||
|
|
||||||
### What It Came With
|
### What It Came With
|
||||||
|
|
||||||
@@ -119,6 +120,7 @@ The transparent case made inspection easy - just unscrew the brass standoffs. In
|
|||||||
- **No additional sensors** (temperature/humidity were from weather API, not local)
|
- **No additional sensors** (temperature/humidity were from weather API, not local)
|
||||||
|
|
||||||
The ESP-01S pinout is printed right on the PCB:
|
The ESP-01S pinout is printed right on the PCB:
|
||||||
|
|
||||||
```
|
```
|
||||||
3V3 | GND
|
3V3 | GND
|
||||||
TX | GPIO0 (I2C SDA)
|
TX | GPIO0 (I2C SDA)
|
||||||
@@ -135,6 +137,7 @@ To flash custom firmware, you need:
|
|||||||
3. **Steady hands**
|
3. **Steady hands**
|
||||||
|
|
||||||
**Wiring:**
|
**Wiring:**
|
||||||
|
|
||||||
```
|
```
|
||||||
FTDI ESP-01S
|
FTDI ESP-01S
|
||||||
────────────────────
|
────────────────────
|
||||||
@@ -146,12 +149,14 @@ FTDI ESP-01S
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Boot into flash mode:**
|
**Boot into flash mode:**
|
||||||
|
|
||||||
1. Connect GPIO0 to GND
|
1. Connect GPIO0 to GND
|
||||||
2. Power on the device
|
2. Power on the device
|
||||||
3. Remove GPIO0 to GND connection after boot
|
3. Remove GPIO0 to GND connection after boot
|
||||||
4. Device is now in programming mode
|
4. Device is now in programming mode
|
||||||
|
|
||||||
**Programming:**
|
**Programming:**
|
||||||
|
|
||||||
- Use Arduino IDE with ESP8266 board support
|
- Use Arduino IDE with ESP8266 board support
|
||||||
- Select board: "Generic ESP8266 Module"
|
- Select board: "Generic ESP8266 Module"
|
||||||
- Flash size: 1MB (FS:64KB OTA:~470KB)
|
- Flash size: 1MB (FS:64KB OTA:~470KB)
|
||||||
@@ -176,6 +181,7 @@ I decided to write a complete replacement firmware with:
|
|||||||
### Features Implemented
|
### Features Implemented
|
||||||
|
|
||||||
#### 🌐 Network & Time
|
#### 🌐 Network & Time
|
||||||
|
|
||||||
- **WiFiManager** captive portal for secure first-time setup
|
- **WiFiManager** captive portal for secure first-time setup
|
||||||
- **Hybrid WiFi**: Synchronous on boot (ensures proper init), async reconnect during operation
|
- **Hybrid WiFi**: Synchronous on boot (ensures proper init), async reconnect during operation
|
||||||
- **NTP time sync** with configurable server and interval
|
- **NTP time sync** with configurable server and interval
|
||||||
@@ -183,12 +189,14 @@ I decided to write a complete replacement firmware with:
|
|||||||
- **mDNS**: Access via `http://tj56654-clock.local/`
|
- **mDNS**: Access via `http://tj56654-clock.local/`
|
||||||
|
|
||||||
#### 🌦️ Weather Data
|
#### 🌦️ Weather Data
|
||||||
|
|
||||||
- **Open-Meteo API**: Free, no registration, no API key
|
- **Open-Meteo API**: Free, no registration, no API key
|
||||||
- **Configurable location**: Latitude/longitude + city name
|
- **Configurable location**: Latitude/longitude + city name
|
||||||
- **Data**: Temperature, sunrise, sunset, daylight duration
|
- **Data**: Temperature, sunrise, sunset, daylight duration
|
||||||
- **Smart updates**: Async fetch every 30 minutes (configurable)
|
- **Smart updates**: Async fetch every 30 minutes (configurable)
|
||||||
|
|
||||||
#### 🔄 OTA Updates
|
#### 🔄 OTA Updates
|
||||||
|
|
||||||
- **Web-based OTA**: Upload .bin files via browser at `/update`
|
- **Web-based OTA**: Upload .bin files via browser at `/update`
|
||||||
- **ArduinoOTA**: Update directly from Arduino IDE
|
- **ArduinoOTA**: Update directly from Arduino IDE
|
||||||
- **Non-blocking**: System stays responsive during updates
|
- **Non-blocking**: System stays responsive during updates
|
||||||
@@ -245,33 +253,39 @@ All endpoints return JSON:
|
|||||||
The firmware uses **zero blocking operations** in the main loop. Everything is state-machine-based:
|
The firmware uses **zero blocking operations** in the main loop. Everything is state-machine-based:
|
||||||
|
|
||||||
#### Weather State Machine
|
#### Weather State Machine
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
enum WeatherState { IDLE, REQUESTING, SUCCESS, FAILED };
|
enum WeatherState { IDLE, REQUESTING, SUCCESS, FAILED };
|
||||||
```
|
```
|
||||||
|
|
||||||
Uses `AsyncHTTPRequest` library:
|
Uses `AsyncHTTPRequest` library:
|
||||||
|
|
||||||
- Non-blocking HTTP requests
|
- Non-blocking HTTP requests
|
||||||
- Callback-based response handling
|
- Callback-based response handling
|
||||||
- Exponential backoff on failures (1s → 2s → 4s)
|
- Exponential backoff on failures (1s → 2s → 4s)
|
||||||
- Maximum 3 retries before giving up
|
- Maximum 3 retries before giving up
|
||||||
|
|
||||||
#### NTP State Machine
|
#### NTP State Machine
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
enum NTPState { IDLE, REQUEST_SENT, WAITING, SUCCESS, FAILED };
|
enum NTPState { IDLE, REQUEST_SENT, WAITING, SUCCESS, FAILED };
|
||||||
```
|
```
|
||||||
|
|
||||||
Custom manual NTP implementation:
|
Custom manual NTP implementation:
|
||||||
|
|
||||||
- Builds raw UDP packets (48 bytes)
|
- Builds raw UDP packets (48 bytes)
|
||||||
- Non-blocking `parsePacket()` checks
|
- Non-blocking `parsePacket()` checks
|
||||||
- 5-second timeout
|
- 5-second timeout
|
||||||
- Independent epoch tracking for accuracy between syncs
|
- Independent epoch tracking for accuracy between syncs
|
||||||
|
|
||||||
#### WiFi State Machine
|
#### WiFi State Machine
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
enum WiFiConnectionState { IDLE, CONNECTING, CONNECTED, FAILED };
|
enum WiFiConnectionState { IDLE, CONNECTING, CONNECTED, FAILED };
|
||||||
```
|
```
|
||||||
|
|
||||||
**Hybrid model** (this was critical!):
|
**Hybrid model** (this was critical!):
|
||||||
|
|
||||||
- **Setup phase**: Synchronous connection (waits up to 10 seconds)
|
- **Setup phase**: Synchronous connection (waits up to 10 seconds)
|
||||||
- Why? OTA, web server, NTP all need WiFi ready
|
- Why? OTA, web server, NTP all need WiFi ready
|
||||||
- Without this, device shows blank display for 10+ seconds
|
- Without this, device shows blank display for 10+ seconds
|
||||||
@@ -282,11 +296,11 @@ enum WiFiConnectionState { IDLE, CONNECTING, CONNECTED, FAILED };
|
|||||||
|
|
||||||
ESP8266 has strict memory limits:
|
ESP8266 has strict memory limits:
|
||||||
|
|
||||||
| Memory Type | Total | Used | Usage | Status |
|
| Memory Type | Total | Used | Usage | Status |
|
||||||
|-------------|-------|------|-------|--------|
|
| ----------- | --------- | ------- | ------- | ----------- |
|
||||||
| **Flash** | 1,048,576 | 408,844 | 38% | ✅ Plenty |
|
| **Flash** | 1,048,576 | 408,844 | 38% | ✅ Plenty |
|
||||||
| **RAM** | 80,192 | 37,644 | 46% | ✅ Safe |
|
| **RAM** | 80,192 | 37,644 | 46% | ✅ Safe |
|
||||||
| **IRAM** | 65,536 | 61,987 | **94%** | ⚠️ Critical |
|
| **IRAM** | 65,536 | 61,987 | **94%** | ⚠️ Critical |
|
||||||
|
|
||||||
**IRAM Crisis Solution:**
|
**IRAM Crisis Solution:**
|
||||||
|
|
||||||
@@ -304,6 +318,7 @@ Applied to 26 functions (web handlers, display, config utilities), reducing IRAM
|
|||||||
**String Safety:**
|
**String Safety:**
|
||||||
|
|
||||||
Avoid String concatenation in loops (causes heap fragmentation):
|
Avoid String concatenation in loops (causes heap fragmentation):
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// ❌ BAD - 140+ concatenations
|
// ❌ BAD - 140+ concatenations
|
||||||
String html = "";
|
String html = "";
|
||||||
@@ -353,22 +368,27 @@ struct Config {
|
|||||||
This took **3 firmware iterations** to get right:
|
This took **3 firmware iterations** to get right:
|
||||||
|
|
||||||
**v1.5**: Assumed TM1637 (7-segment LED driver)
|
**v1.5**: Assumed TM1637 (7-segment LED driver)
|
||||||
|
|
||||||
- ❌ Wrong - device has OLED, not 7-segment LEDs
|
- ❌ Wrong - device has OLED, not 7-segment LEDs
|
||||||
|
|
||||||
**v1.6**: Tried TM1650 (another LED driver)
|
**v1.6**: Tried TM1650 (another LED driver)
|
||||||
|
|
||||||
- ❌ Wrong - I2C addresses didn't match
|
- ❌ Wrong - I2C addresses didn't match
|
||||||
|
|
||||||
**v1.7**: Identified GM009605v4.3 (SSD1306-compatible OLED)
|
**v1.7**: Identified GM009605v4.3 (SSD1306-compatible OLED)
|
||||||
|
|
||||||
- ✅ Correct! Used Adafruit_SSD1306 library
|
- ✅ Correct! Used Adafruit_SSD1306 library
|
||||||
- ✅ Discovered swapped pins: SDA on GPIO0, SCL on GPIO2
|
- ✅ Discovered swapped pins: SDA on GPIO0, SCL on GPIO2
|
||||||
|
|
||||||
**Pin mapping quirk:**
|
**Pin mapping quirk:**
|
||||||
|
|
||||||
Standard ESP8266 I2C uses GPIO4 (SDA) and GPIO5 (SCL), but ESP-01S only exposes GPIO0 and GPIO2. The board designer mapped:
|
Standard ESP8266 I2C uses GPIO4 (SDA) and GPIO5 (SCL), but ESP-01S only exposes GPIO0 and GPIO2. The board designer mapped:
|
||||||
|
|
||||||
- GPIO0 → SDA (unusual)
|
- GPIO0 → SDA (unusual)
|
||||||
- GPIO2 → SCL (unusual)
|
- GPIO2 → SCL (unusual)
|
||||||
|
|
||||||
This is **backwards** from typical breakout boards, but works perfectly once configured:
|
This is **backwards** from typical breakout boards, but works perfectly once configured:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
Wire.begin(0, 2); // SDA=GPIO0, SCL=GPIO2
|
Wire.begin(0, 2); // SDA=GPIO0, SCL=GPIO2
|
||||||
```
|
```
|
||||||
@@ -389,6 +409,7 @@ Wire.begin(0, 2); // SDA=GPIO0, SCL=GPIO2
|
|||||||
**Goals**: Fix memory issues, eliminate security holes
|
**Goals**: Fix memory issues, eliminate security holes
|
||||||
|
|
||||||
**Changes:**
|
**Changes:**
|
||||||
|
|
||||||
- IRAM optimization (added `ICACHE_FLASH_ATTR` to 26 functions)
|
- IRAM optimization (added `ICACHE_FLASH_ATTR` to 26 functions)
|
||||||
- Removed hardcoded WiFi credentials
|
- Removed hardcoded WiFi credentials
|
||||||
- Fixed NTP interval bug (config value was ignored)
|
- Fixed NTP interval bug (config value was ignored)
|
||||||
@@ -403,6 +424,7 @@ Wire.begin(0, 2); // SDA=GPIO0, SCL=GPIO2
|
|||||||
**Goals**: Eliminate all blocking operations
|
**Goals**: Eliminate all blocking operations
|
||||||
|
|
||||||
**Changes:**
|
**Changes:**
|
||||||
|
|
||||||
- Async HTTP weather fetch (AsyncHTTPRequest library)
|
- Async HTTP weather fetch (AsyncHTTPRequest library)
|
||||||
- Custom async NTP implementation (manual UDP packets)
|
- Custom async NTP implementation (manual UDP packets)
|
||||||
- Async WiFi connection (state machine)
|
- Async WiFi connection (state machine)
|
||||||
@@ -411,12 +433,12 @@ Wire.begin(0, 2); // SDA=GPIO0, SCL=GPIO2
|
|||||||
|
|
||||||
**Performance:**
|
**Performance:**
|
||||||
|
|
||||||
| Operation | Before (v1.8) | After (v1.9.0) | Improvement |
|
| Operation | Before (v1.8) | After (v1.9.0) | Improvement |
|
||||||
|-----------|---------------|----------------|-------------|
|
| -------------- | -------------- | -------------- | ------------------- |
|
||||||
| Weather fetch | 1-10s blocking | 0ms | ✅ Async callback |
|
| Weather fetch | 1-10s blocking | 0ms | ✅ Async callback |
|
||||||
| NTP sync | 5-20s blocking | 0ms | ✅ Non-blocking UDP |
|
| NTP sync | 5-20s blocking | 0ms | ✅ Non-blocking UDP |
|
||||||
| WiFi reconnect | 15s blocking | 0ms | ✅ State machine |
|
| WiFi reconnect | 15s blocking | 0ms | ✅ State machine |
|
||||||
| Loop time | 10ms minimum | <1ms | ✅ 10x faster |
|
| Loop time | 10ms minimum | <1ms | ✅ 10x faster |
|
||||||
|
|
||||||
**Result**: Device stays responsive during OTA updates while weather is fetching!
|
**Result**: Device stays responsive during OTA updates while weather is fetching!
|
||||||
|
|
||||||
@@ -429,6 +451,7 @@ After deploying v1.9.0, the display showed **blank screen for 10 seconds** after
|
|||||||
**Root Cause:**
|
**Root Cause:**
|
||||||
|
|
||||||
Making WiFi fully async broke the **initialization order**:
|
Making WiFi fully async broke the **initialization order**:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
void setup() {
|
void setup() {
|
||||||
setupWiFi(); // Returns immediately (async)
|
setupWiFi(); // Returns immediately (async)
|
||||||
@@ -440,12 +463,13 @@ void setup() {
|
|||||||
|
|
||||||
**Solution: Hybrid Model**
|
**Solution: Hybrid Model**
|
||||||
|
|
||||||
| Phase | WiFi Mode | Blocking? | Why? |
|
| Phase | WiFi Mode | Blocking? | Why? |
|
||||||
|-------|-----------|-----------|------|
|
| --------- | ------------ | --------- | --------------------------- |
|
||||||
| `setup()` | Synchronous | 10s max | OTA/web/NTP need WiFi ready |
|
| `setup()` | Synchronous | 10s max | OTA/web/NTP need WiFi ready |
|
||||||
| `loop()` | Asynchronous | 0s | Don't freeze on reconnect |
|
| `loop()` | Asynchronous | 0s | Don't freeze on reconnect |
|
||||||
|
|
||||||
**Results:**
|
**Results:**
|
||||||
|
|
||||||
- ✅ Display shows time immediately after WiFi connects (~15 sec boot)
|
- ✅ Display shows time immediately after WiFi connects (~15 sec boot)
|
||||||
- ✅ No "DNS resolution failed" errors
|
- ✅ No "DNS resolution failed" errors
|
||||||
- ✅ Proper initialization order guaranteed
|
- ✅ Proper initialization order guaranteed
|
||||||
@@ -460,6 +484,7 @@ After WiFi outages, the device would **clear stored credentials** and enter AP m
|
|||||||
**Root Cause:**
|
**Root Cause:**
|
||||||
|
|
||||||
Aggressive credential clearing on connection failure:
|
Aggressive credential clearing on connection failure:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
if (wifiRetry.currentRetry >= wifiRetry.maxRetries) {
|
if (wifiRetry.currentRetry >= wifiRetry.maxRetries) {
|
||||||
memset(config.ssid, 0, sizeof(config.ssid)); // ❌ Clears credentials!
|
memset(config.ssid, 0, sizeof(config.ssid)); // ❌ Clears credentials!
|
||||||
@@ -471,15 +496,16 @@ if (wifiRetry.currentRetry >= wifiRetry.maxRetries) {
|
|||||||
|
|
||||||
**Solution: Resilient WiFi**
|
**Solution: Resilient WiFi**
|
||||||
|
|
||||||
| Feature | Before (v1.9.1) | After (v1.9.2) |
|
| Feature | Before (v1.9.1) | After (v1.9.2) |
|
||||||
|---------|-----------------|----------------|
|
| ------------------- | -------------------------- | ------------------------------- |
|
||||||
| Credential clearing | After 5 failed attempts | Never |
|
| Credential clearing | After 5 failed attempts | Never |
|
||||||
| Retry strategy | Give up after 5 tries | Infinite with backoff |
|
| Retry strategy | Give up after 5 tries | Infinite with backoff |
|
||||||
| Max retry interval | N/A | 5 minutes |
|
| Max retry interval | N/A | 5 minutes |
|
||||||
| Fallback AP | After clearing credentials | After ~5 min (dual STA+AP mode) |
|
| Fallback AP | After clearing credentials | After ~5 min (dual STA+AP mode) |
|
||||||
| Clock during outage | Blank display | Shows last synced time |
|
| Clock during outage | Blank display | Shows last synced time |
|
||||||
|
|
||||||
**Key Changes:**
|
**Key Changes:**
|
||||||
|
|
||||||
- **Never clear credentials** on connection failure
|
- **Never clear credentials** on connection failure
|
||||||
- **Exponential backoff**: 5s → 10s → 20s → ... → 5min max
|
- **Exponential backoff**: 5s → 10s → 20s → ... → 5min max
|
||||||
- **Fallback AP** ("TJ56654-Setup") enabled after ~5 min, while still retrying
|
- **Fallback AP** ("TJ56654-Setup") enabled after ~5 min, while still retrying
|
||||||
@@ -490,21 +516,23 @@ if (wifiRetry.currentRetry >= wifiRetry.maxRetries) {
|
|||||||
|
|
||||||
**Network Activity Summary:**
|
**Network Activity Summary:**
|
||||||
|
|
||||||
| Service | Interval | Endpoint | Protocol |
|
| Service | Interval | Endpoint | Protocol |
|
||||||
|---------|----------|----------|----------|
|
| ------- | ---------- | ------------------ | ------------- |
|
||||||
| NTP | 1 hour | pool.ntp.org:123 | UDP |
|
| NTP | 1 hour | pool.ntp.org:123 | UDP |
|
||||||
| Weather | 30 min | api.open-meteo.com | HTTP |
|
| Weather | 30 min | api.open-meteo.com | HTTP |
|
||||||
| mDNS | continuous | 224.0.0.251 | UDP multicast |
|
| mDNS | continuous | 224.0.0.251 | UDP multicast |
|
||||||
|
|
||||||
~50 requests/day total.
|
~50 requests/day total.
|
||||||
|
|
||||||
**Results:**
|
**Results:**
|
||||||
|
|
||||||
- ✅ Credentials persist through WiFi outages
|
- ✅ Credentials persist through WiFi outages
|
||||||
- ✅ Device automatically reconnects when WiFi returns
|
- ✅ Device automatically reconnects when WiFi returns
|
||||||
- ✅ Clock continues running with last synced time
|
- ✅ Clock continues running with last synced time
|
||||||
- ✅ User can reconfigure via fallback AP if needed
|
- ✅ User can reconfigure via fallback AP if needed
|
||||||
|
|
||||||
**Startup Timeline:**
|
**Startup Timeline:**
|
||||||
|
|
||||||
```
|
```
|
||||||
[0-5s] Display init, startup animation
|
[0-5s] Display init, startup animation
|
||||||
[5-15s] WiFi connection (SYNCHRONOUS in setup())
|
[5-15s] WiFi connection (SYNCHRONOUS in setup())
|
||||||
@@ -517,13 +545,13 @@ if (wifiRetry.currentRetry >= wifiRetry.maxRetries) {
|
|||||||
|
|
||||||
### Memory Evolution
|
### Memory Evolution
|
||||||
|
|
||||||
| Version | RAM Usage | IRAM Usage | Flash Usage | Notes |
|
| Version | RAM Usage | IRAM Usage | Flash Usage | Notes |
|
||||||
|---------|-----------|------------|-------------|-------|
|
| ------- | ------------ | ---------------- | ------------- | --------------------- |
|
||||||
| v1.7 | 34,980 (43%) | **61,987 (94%)** | 407,500 (38%) | IRAM crisis |
|
| v1.7 | 34,980 (43%) | **61,987 (94%)** | 407,500 (38%) | IRAM crisis |
|
||||||
| v1.8 | 36,980 (46%) | **45,120 (68%)** | 407,800 (38%) | ICACHE_FLASH_ATTR fix |
|
| v1.8 | 36,980 (46%) | **45,120 (68%)** | 407,800 (38%) | ICACHE_FLASH_ATTR fix |
|
||||||
| v1.9.0 | 37,516 (46%) | **61,987 (94%)** | 408,540 (38%) | Async libs added |
|
| 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.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.2 | 37,800 (47%) | **61,987 (94%)** | 409,100 (39%) | WiFi resilience |
|
||||||
|
|
||||||
**Verdict**: Stable memory usage, no leaks detected after 24h+ uptime tests.
|
**Verdict**: Stable memory usage, no leaks detected after 24h+ uptime tests.
|
||||||
|
|
||||||
@@ -536,6 +564,7 @@ The firmware is designed to be extensible. Next planned features:
|
|||||||
### Custom Display Screens
|
### Custom Display Screens
|
||||||
|
|
||||||
Pull data from Home Assistant via REST API:
|
Pull data from Home Assistant via REST API:
|
||||||
|
|
||||||
- **Smart home stats**: Energy usage, room temperatures
|
- **Smart home stats**: Energy usage, room temperatures
|
||||||
- **Sensor data**: Air quality, CO2 levels
|
- **Sensor data**: Air quality, CO2 levels
|
||||||
- **Automation states**: Alarm status, door locks
|
- **Automation states**: Alarm status, door locks
|
||||||
@@ -549,6 +578,7 @@ Pull data from Home Assistant via REST API:
|
|||||||
### WebSocket Live Updates
|
### WebSocket Live Updates
|
||||||
|
|
||||||
Replace polling with WebSocket for:
|
Replace polling with WebSocket for:
|
||||||
|
|
||||||
- Real-time config changes without page refresh
|
- Real-time config changes without page refresh
|
||||||
- Live display preview in web UI
|
- Live display preview in web UI
|
||||||
- Push notifications for firmware updates
|
- Push notifications for firmware updates
|
||||||
@@ -579,6 +609,7 @@ Replace polling with WebSocket for:
|
|||||||
- `WiFiManager` (by tzapu)
|
- `WiFiManager` (by tzapu)
|
||||||
- `AsyncHTTPRequest_Generic`
|
- `AsyncHTTPRequest_Generic`
|
||||||
- `ESPAsyncTCP`
|
- `ESPAsyncTCP`
|
||||||
|
- `ArduinoJson` (by Benoit Blanchon)
|
||||||
|
|
||||||
3. **Board Configuration**
|
3. **Board Configuration**
|
||||||
- Board: "Generic ESP8266 Module"
|
- Board: "Generic ESP8266 Module"
|
||||||
@@ -591,6 +622,7 @@ Replace polling with WebSocket for:
|
|||||||
### First Flash (via FTDI)
|
### First Flash (via FTDI)
|
||||||
|
|
||||||
1. **Wire the ESP-01S**:
|
1. **Wire the ESP-01S**:
|
||||||
|
|
||||||
```
|
```
|
||||||
FTDI 3.3V → ESP-01S 3V3
|
FTDI 3.3V → ESP-01S 3V3
|
||||||
FTDI GND → ESP-01S GND
|
FTDI GND → ESP-01S GND
|
||||||
@@ -633,6 +665,7 @@ Replace polling with WebSocket for:
|
|||||||
## Web Interface
|
## Web Interface
|
||||||
|
|
||||||
### Home Page (`/`)
|
### Home Page (`/`)
|
||||||
|
|
||||||
Current time display with live updates via JavaScript (fetches `/api/time` every second).
|
Current time display with live updates via JavaScript (fetches `/api/time` every second).
|
||||||
|
|
||||||
### Configuration Page (`/config`)
|
### Configuration Page (`/config`)
|
||||||
@@ -640,11 +673,13 @@ Current time display with live updates via JavaScript (fetches `/api/time` every
|
|||||||
Comprehensive settings form:
|
Comprehensive settings form:
|
||||||
|
|
||||||
**WiFi Settings**
|
**WiFi Settings**
|
||||||
|
|
||||||
- SSID
|
- SSID
|
||||||
- Password
|
- Password
|
||||||
- Hostname (for mDNS)
|
- Hostname (for mDNS)
|
||||||
|
|
||||||
**Time Settings**
|
**Time Settings**
|
||||||
|
|
||||||
- Timezone offset (seconds from UTC)
|
- Timezone offset (seconds from UTC)
|
||||||
- DST enabled (European rules)
|
- DST enabled (European rules)
|
||||||
- NTP server address
|
- NTP server address
|
||||||
@@ -652,6 +687,7 @@ Comprehensive settings form:
|
|||||||
- Hour format (12h/24h)
|
- Hour format (12h/24h)
|
||||||
|
|
||||||
**Weather Settings**
|
**Weather Settings**
|
||||||
|
|
||||||
- Enabled/disabled toggle
|
- Enabled/disabled toggle
|
||||||
- Latitude
|
- Latitude
|
||||||
- Longitude
|
- Longitude
|
||||||
@@ -659,6 +695,7 @@ Comprehensive settings form:
|
|||||||
- Update interval (seconds)
|
- Update interval (seconds)
|
||||||
|
|
||||||
**Display Settings**
|
**Display Settings**
|
||||||
|
|
||||||
- Brightness (0-7)
|
- Brightness (0-7)
|
||||||
- Rotation (0°, 90°, 180°, 270°)
|
- Rotation (0°, 90°, 180°, 270°)
|
||||||
- Display rotation interval (seconds)
|
- Display rotation interval (seconds)
|
||||||
@@ -692,6 +729,7 @@ All endpoints return JSON (except `/update` which is for file upload).
|
|||||||
Current time information.
|
Current time information.
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"current": "14:23:45",
|
"current": "14:23:45",
|
||||||
@@ -707,6 +745,7 @@ Current time information.
|
|||||||
System status overview.
|
System status overview.
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"wifi": {
|
"wifi": {
|
||||||
@@ -733,6 +772,7 @@ System status overview.
|
|||||||
Current weather data.
|
Current weather data.
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"temperature": 15.4,
|
"temperature": 15.4,
|
||||||
@@ -751,6 +791,7 @@ Current weather data.
|
|||||||
Export full configuration as JSON.
|
Export full configuration as JSON.
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ssid": "MyNetwork",
|
"ssid": "MyNetwork",
|
||||||
@@ -780,6 +821,7 @@ Import configuration from JSON.
|
|||||||
**Request Body**: Same structure as export response (password field optional for security).
|
**Request Body**: Same structure as export response (password field optional for security).
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"status": "ok"
|
"status": "ok"
|
||||||
@@ -793,6 +835,7 @@ Device automatically reboots after import.
|
|||||||
Factory reset (clears EEPROM).
|
Factory reset (clears EEPROM).
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"status": "cleared"
|
"status": "cleared"
|
||||||
@@ -806,6 +849,7 @@ Device reboots to WiFiManager captive portal.
|
|||||||
Remote reboot.
|
Remote reboot.
|
||||||
|
|
||||||
**Response:**
|
**Response:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"status": "rebooting"
|
"status": "rebooting"
|
||||||
@@ -820,15 +864,15 @@ Device reboots immediately.
|
|||||||
|
|
||||||
### What Changed from Original Firmware
|
### What Changed from Original Firmware
|
||||||
|
|
||||||
| Issue | Original | Custom Firmware |
|
| Issue | Original | Custom Firmware |
|
||||||
|-------|----------|-----------------|
|
| ---------------------- | ------------------------------ | --------------------------------- |
|
||||||
| **WiFi Password Leak** | Plaintext in open AP | No open AP after setup |
|
| **WiFi Password Leak** | Plaintext in open AP | No open AP after setup |
|
||||||
| **Persistent AP** | Always active | Only on first boot or failure |
|
| **Persistent AP** | Always active | Only on first boot or failure |
|
||||||
| **API Keys** | QWeather requires registration | Open-Meteo (no key needed) |
|
| **API Keys** | QWeather requires registration | Open-Meteo (no key needed) |
|
||||||
| **Cloud Dependency** | Chinese servers | Direct API calls, no intermediary |
|
| **Cloud Dependency** | Chinese servers | Direct API calls, no intermediary |
|
||||||
| **Firmware Updates** | Manual FTDI only | OTA via WiFi (password-protected) |
|
| **Firmware Updates** | Manual FTDI only | OTA via WiFi (password-protected) |
|
||||||
| **Config Access** | No authentication | Admin password required |
|
| **Config Access** | No authentication | Admin password required |
|
||||||
| **Code Transparency** | Closed source | Open source (you're reading it!) |
|
| **Code Transparency** | Closed source | Open source (you're reading it!) |
|
||||||
|
|
||||||
### Best Practices Implemented
|
### Best Practices Implemented
|
||||||
|
|
||||||
@@ -842,16 +886,19 @@ Device reboots immediately.
|
|||||||
### Recommended Post-Flash Steps
|
### Recommended Post-Flash Steps
|
||||||
|
|
||||||
1. **Change OTA password**: Edit line ~60 in `.ino` file:
|
1. **Change OTA password**: Edit line ~60 in `.ino` file:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
ArduinoOTA.setPassword("admin"); // Change this!
|
ArduinoOTA.setPassword("admin"); // Change this!
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Change web admin password**: Edit line ~430:
|
2. **Change web admin password**: Edit line ~430:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
if (!server.authenticate("admin", "admin")) { // Change this!
|
if (!server.authenticate("admin", "admin")) { // Change this!
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Set strong WiFi AP fallback password**: Edit line ~780:
|
3. **Set strong WiFi AP fallback password**: Edit line ~780:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
WiFi.softAP("TJ56654-Clock", "12345678"); // Change this!
|
WiFi.softAP("TJ56654-Clock", "12345678"); // Change this!
|
||||||
```
|
```
|
||||||
@@ -900,6 +947,7 @@ Device reboots immediately.
|
|||||||
**Firmware**: Written from scratch with love and frustration
|
**Firmware**: Written from scratch with love and frustration
|
||||||
|
|
||||||
**Libraries Used**:
|
**Libraries Used**:
|
||||||
|
|
||||||
- [ESP8266 Arduino Core](https://github.com/esp8266/Arduino)
|
- [ESP8266 Arduino Core](https://github.com/esp8266/Arduino)
|
||||||
- [Adafruit SSD1306](https://github.com/adafruit/Adafruit_SSD1306)
|
- [Adafruit SSD1306](https://github.com/adafruit/Adafruit_SSD1306)
|
||||||
- [WiFiManager](https://github.com/tzapu/WiFiManager)
|
- [WiFiManager](https://github.com/tzapu/WiFiManager)
|
||||||
@@ -907,9 +955,11 @@ Device reboots immediately.
|
|||||||
- [NTPClient](https://github.com/arduino-libraries/NTPClient)
|
- [NTPClient](https://github.com/arduino-libraries/NTPClient)
|
||||||
|
|
||||||
**APIs**:
|
**APIs**:
|
||||||
|
|
||||||
- [Open-Meteo](https://open-meteo.com/) - Free weather API, no registration required
|
- [Open-Meteo](https://open-meteo.com/) - Free weather API, no registration required
|
||||||
|
|
||||||
**Tools**:
|
**Tools**:
|
||||||
|
|
||||||
- Arduino IDE 2.x
|
- Arduino IDE 2.x
|
||||||
- FTDI FT232RL USB-to-Serial adapter
|
- FTDI FT232RL USB-to-Serial adapter
|
||||||
- Lots of coffee ☕
|
- Lots of coffee ☕
|
||||||
@@ -946,6 +996,7 @@ This project is released into the public domain. Do whatever you want with it. I
|
|||||||
This project started as "I don't trust this device" and ended as "I built something better."
|
This project started as "I don't trust this device" and ended as "I built something better."
|
||||||
|
|
||||||
The original firmware had security holes you could drive a truck through. The custom replacement:
|
The original firmware had security holes you could drive a truck through. The custom replacement:
|
||||||
|
|
||||||
- ✅ Doesn't leak WiFi passwords
|
- ✅ Doesn't leak WiFi passwords
|
||||||
- ✅ Uses free, open APIs
|
- ✅ Uses free, open APIs
|
||||||
- ✅ Updates over WiFi
|
- ✅ Updates over WiFi
|
||||||
|
|||||||
Reference in New Issue
Block a user