docs: add ArduinoJson to required libraries list (closes #4)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alex Petrochenko
2026-05-14 17:24:17 +01:00
co-authored by Claude Sonnet 4.6
parent 441c61676b
commit c606bcc589
+59 -8
View File
@@ -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
@@ -79,7 +80,7 @@ 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) |
@@ -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
@@ -283,7 +297,7 @@ 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 |
@@ -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)
@@ -412,7 +434,7 @@ 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 |
@@ -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)
@@ -441,11 +464,12 @@ 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!
@@ -472,7 +497,7 @@ 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 |
@@ -480,6 +505,7 @@ if (wifiRetry.currentRetry >= wifiRetry.maxRetries) {
| 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
@@ -491,7 +517,7 @@ 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 |
@@ -499,12 +525,14 @@ if (wifiRetry.currentRetry >= wifiRetry.maxRetries) {
~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())
@@ -518,7 +546,7 @@ 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 |
@@ -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"
@@ -821,7 +865,7 @@ 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) |
@@ -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