mirror of
https://github.com/Legolas-2025/Standalone-electricity-price-ticker.git
synced 2026-08-20 12:56:54 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13da0e0c87 | ||
|
|
8543493ac0 | ||
|
|
eb5ef5954b | ||
|
|
2fff8296c5 | ||
|
|
3f09e475fc | ||
|
|
eb34d07ccf | ||
|
|
dcc434f7e4 | ||
|
|
6c7a66be8e | ||
|
|
e1ad1b01c3 | ||
|
|
f77f50f1d4 | ||
|
|
45cabc17dd | ||
|
|
fedef66ae0 | ||
|
|
65fdb14cec | ||
|
|
3da3dec344 | ||
|
|
6a6cf113b3 | ||
|
|
26b1a309e1 | ||
|
|
4c5d7d2ebc | ||
|
|
b54c40cdf7 | ||
|
|
08db5888cb | ||
|
|
8f1f017232 | ||
|
|
189350e4fe | ||
|
|
827623112f | ||
|
|
fd5dad4cb5 |
File diff suppressed because it is too large
Load Diff
+139
-122
@@ -4,116 +4,154 @@ All notable changes to this project are documented here.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## v6.2.0 – DST (Daylight Saving Time) Handling Fixed (2026‑03‑29)
|
||||||
|
|
||||||
|
**Summary**
|
||||||
|
|
||||||
|
This release fixes a critical bug that caused incorrect price display on DST switch days. On March 29, 2026 (the spring forward day), the ticker at 12:41 showed prices for hours 13:00, 14:00, and 15:00 instead of the correct 12:00, 13:00, and 14:00.
|
||||||
|
|
||||||
|
### Problem: Arithmetic-Based Index Calculation
|
||||||
|
|
||||||
|
**Root Cause (v6.0.0 – v6.1.2):**
|
||||||
|
|
||||||
|
The code assumed every day has exactly 96 price entries:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
int startIndex = hourIndex * 4; // e.g., hour 12 → index 48
|
||||||
|
```
|
||||||
|
|
||||||
|
This assumption breaks on DST switch days:
|
||||||
|
|
||||||
|
| Day Type | Hours | Price Entries | Example |
|
||||||
|
|----------|-------|---------------|---------|
|
||||||
|
| Normal day | 24 | 96 | Array indices 0-95 |
|
||||||
|
| Spring forward (March) | 23 | 92 | Index 48 points to wrong time |
|
||||||
|
| Fall back (October) | 25 | 100 | Index 48 points to wrong time |
|
||||||
|
|
||||||
|
At 12:41 on March 29, 2026:
|
||||||
|
- ESP32 correctly reported `timeinfo.tm_hour = 12`
|
||||||
|
- Old code calculated `12 × 4 = 48`
|
||||||
|
- But array only had 92 entries (no index 48 that maps to local hour 12)
|
||||||
|
- Result: Displayed prices for 13:00, 14:00, 15:00 instead of 12:00, 13:00, 14:00
|
||||||
|
|
||||||
|
### Solution: Timestamp-Based Lookups
|
||||||
|
|
||||||
|
**New approach (v6.2.0):**
|
||||||
|
|
||||||
|
All price lookups now search the `unix_seconds` array using actual timestamps:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Find index by searching for matching hour in timestamps
|
||||||
|
int findPriceIndexForHour(const JsonArray& unixSeconds, int targetHour) {
|
||||||
|
for (size_t i = 0; i < unixSeconds.size(); i++) {
|
||||||
|
unsigned long unixTime = unixSeconds[i].as<unsigned long>();
|
||||||
|
time_t t = (time_t)unixTime;
|
||||||
|
struct tm* ptm = localtime(&t);
|
||||||
|
if (ptm != NULL && ptm->tm_hour == targetHour) {
|
||||||
|
return (int)i; // Found correct index for this hour
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**New functions added:**
|
||||||
|
- `findPriceIndexForHour()` – Finds first price index for a given hour
|
||||||
|
- `findCurrentPriceIndex()` – Finds current 15-minute slot using Unix timestamp
|
||||||
|
- `getHourFromPriceIndex()` – Gets hour from price array index
|
||||||
|
|
||||||
|
**Updated functions:**
|
||||||
|
- `getHourlyAverage()` – Now uses timestamp lookup instead of `hourIndex * 4`
|
||||||
|
- `display15MinuteDetails()` – Timestamp-based with hour verification in loop
|
||||||
|
- `displayPriceRow()` – Timestamp-based data index lookup
|
||||||
|
- `displayPrimaryList()` – Timestamp-based current hour detection
|
||||||
|
- `updateLeds()` – Timestamp-based current interval lookup
|
||||||
|
|
||||||
|
### Why This Is Future-Proof
|
||||||
|
|
||||||
|
| Scenario | Code Behavior |
|
||||||
|
|----------|--------------|
|
||||||
|
| Normal days (96 entries) | Works as before |
|
||||||
|
| DST spring forward (92 entries) | Timestamp lookup finds correct indices |
|
||||||
|
| DST fall back (100 entries) | Timestamp lookup finds correct indices |
|
||||||
|
| EU cancels DST | Only update `TZ_CET_CEST` string; code works unchanged |
|
||||||
|
|
||||||
|
If EU parliament ever cancels DST switching, you only need to update the `TZ_CET_CEST` line (one line of code). The price lookup logic requires no changes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v6.1.2 – LED Indicator Restored (ESP32 PWM Fix) (2026‑03‑11)
|
||||||
|
|
||||||
|
**Summary**
|
||||||
|
|
||||||
|
This release fixes a regression introduced in **v6.1.1** where the **white LED price indicator** could remain **dimly lit** even when the LCD backlight turned off, and the intended **blink/breathe patterns** no longer behaved correctly.
|
||||||
|
|
||||||
|
### Fixed: White LED Stuck Dim / Patterns Broken (PWM vs Digital)
|
||||||
|
|
||||||
|
**Problem (v6.1.1):**
|
||||||
|
|
||||||
|
- The sketch uses `analogWrite()` to drive the LED with PWM for breathe/blink patterns.
|
||||||
|
- However, in some "LED OFF" branches the code used `digitalWrite(LOW)` on the same `whiteLedPin`.
|
||||||
|
- On ESP32 (LEDC), once PWM is attached to a pin, `digitalWrite(LOW)` may **not fully disable** PWM output.
|
||||||
|
- Result:
|
||||||
|
- LED could remain **faintly on** (dim glow) when LEDs were supposed to be off.
|
||||||
|
- Some patterns could appear "stuck" or inconsistent.
|
||||||
|
|
||||||
|
**Solution (v6.1.2):**
|
||||||
|
|
||||||
|
- LED control is now **PWM‑only** inside `updateLeds()`:
|
||||||
|
- Use `analogWrite(whiteLedPin, 0)` instead of `digitalWrite(whiteLedPin, LOW)`.
|
||||||
|
- Use `analogWrite(whiteLedPin, 255)` instead of `digitalWrite(whiteLedPin, HIGH)`.
|
||||||
|
- Blink/double‑blink toggles now switch between PWM **0** and **255**.
|
||||||
|
- This ensures the LED is **truly off** whenever LED output is gated off.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## v6.1.1 – Daily Min/Max Includes Negative & Zero Prices (2026‑03‑07)
|
||||||
|
|
||||||
|
**Summary**
|
||||||
|
|
||||||
|
This release fixes a bug where the **daily lowest / highest hourly price marker** ignored negative prices (and also ignored 0.0), which could cause the ticker to incorrectly mark the **lowest positive** price as the daily minimum.
|
||||||
|
|
||||||
|
### Fixed: Daily Low/High Marker Ignored Negative & Zero Prices
|
||||||
|
|
||||||
|
**Problem (v6.1.0):**
|
||||||
|
|
||||||
|
- In `processJsonData()` the daily min/max scan used:
|
||||||
|
```cpp
|
||||||
|
if (hourlyAvg > 0) { ... }
|
||||||
|
```
|
||||||
|
- This had two side effects:
|
||||||
|
1. **Negative** hourly averages were completely skipped.
|
||||||
|
2. A true price of **0.0** was also skipped (even though 0 can be a valid market price).
|
||||||
|
|
||||||
|
**Solution (v6.1.1):**
|
||||||
|
|
||||||
|
- The min/max and average scan now:
|
||||||
|
- Treats an hour as valid based on **data availability** (having all 4×15‑minute entries), not based on value sign.
|
||||||
|
- Includes **all values** (negative, zero, positive).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## v6.1.0 – Midnight Fetch & Market Day Fix (2026‑01‑30)
|
## v6.1.0 – Midnight Fetch & Market Day Fix (2026‑01‑30)
|
||||||
|
|
||||||
**Summary**
|
**Summary**
|
||||||
|
|
||||||
This release fixes a bug where the ticker could remain indefinitely on the **“No data for today”** screen after midnight, even though the API was already returning fresh data. It also refines the after‑midnight retry schedule.
|
This release fixes a bug where the ticker could remain indefinitely on the **"No data for today"** screen after midnight, even though the API was already returning fresh data.
|
||||||
|
|
||||||
### Fixed: Stuck on NO_DATA_OFFSET After Midnight
|
### Fixed: Stuck on NO_DATA_OFFSET After Midnight
|
||||||
|
|
||||||
**Problem (v6.0.0):**
|
**Problem (v6.0.0):**
|
||||||
|
|
||||||
- After midnight, the device:
|
- The API can continue to serve **yesterday's market day** for some time after local midnight.
|
||||||
- Detected day rollover and entered a “no data” state.
|
- HTTP + JSON success always incremented `apiSuccessCount`, even if the data was "not for today".
|
||||||
- Scheduled an immediate API fetch.
|
- The scheduler treated such fetches as **successful** and never retried.
|
||||||
- However:
|
|
||||||
- The API can continue to serve **yesterday’s market day** for some time after local midnight.
|
|
||||||
- The code only checked the **first** `unix_seconds` entry against the current local day.
|
|
||||||
- HTTP + JSON success always incremented `apiSuccessCount`, even if `processJsonData()` subsequently decided the dataset was “not for today”.
|
|
||||||
- The scheduler treated such fetches as **successful**, pushed `nextScheduledFetchTime` 24 hours into the future, and never retried.
|
|
||||||
- Result: the ticker stayed on **NO_DATA_OFFSET** forever, until:
|
|
||||||
- A manual long‑press triggered a fresh fetch at a time when the API finally returned recognized “today” data, or
|
|
||||||
- The device was rebooted later in the day.
|
|
||||||
|
|
||||||
**Solution (v6.1.0):**
|
**Solution (v6.1.0):**
|
||||||
|
|
||||||
1. **Market Day Detection**:
|
- `processJsonData()` now determines the "market day" using the **last** `unix_seconds` timestamp.
|
||||||
- `processJsonData()` now determines the “market day” using the **last** `unix_seconds` timestamp from the API’s dataset (assumed to cover one full day in 15‑minute steps).
|
- A scheduled fetch is only successful if `lastProcessJsonAcceptedToday == true`.
|
||||||
- It compares that calendar date (local time) against the current local date.
|
- Midnight retry logic keeps trying until valid "today" data is received.
|
||||||
- If they differ:
|
|
||||||
- The dataset is treated as **“not for today”**.
|
|
||||||
- `isTodayDataAvailable = false`.
|
|
||||||
- The function returns **false**, and a new flag `lastProcessJsonAcceptedToday` remains `false`.
|
|
||||||
|
|
||||||
2. **Logical Failure vs HTTP/JSON Failure**:
|
|
||||||
- New global flag:
|
|
||||||
- `lastProcessJsonAcceptedToday` – `true` only when `processJsonData()` accepts the dataset as “today’s” data.
|
|
||||||
- In `handleDataFetching()`:
|
|
||||||
- A scheduled fetch is considered a **real success** only if:
|
|
||||||
- HTTP + JSON succeeded, **and**
|
|
||||||
- `lastProcessJsonAcceptedToday == true`.
|
|
||||||
- In all other cases (including “HTTP 200 + parse OK but data still for yesterday”):
|
|
||||||
- The fetch is treated as **failure** for scheduling purposes.
|
|
||||||
- If `midnightPhaseActive == true`, `scheduleAfterMidnightFailure()` is invoked to plan a retry.
|
|
||||||
|
|
||||||
3. **Midnight Phase Cleanup**:
|
|
||||||
- When a fetch finally provides a dataset for today:
|
|
||||||
- `isTodayDataAvailable = true`.
|
|
||||||
- `lastProcessJsonAcceptedToday = true`.
|
|
||||||
- `midnightPhaseActive` is cleared; `midnightRetryCount` reset to 0.
|
|
||||||
- `nextScheduledFetchTime` is set to 24 hours ahead.
|
|
||||||
|
|
||||||
As a result, the ticker will **keep retrying** after midnight until a correct market‑day dataset appears, instead of giving up after the first HTTP 200.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Changed: After‑Midnight Retry Schedule
|
|
||||||
|
|
||||||
In `scheduleAfterMidnightFailure()` the retry strategy when `midnightPhaseActive == true` has been tuned.
|
|
||||||
|
|
||||||
**Old behavior (v6.0.0, conceptual):**
|
|
||||||
|
|
||||||
- First few failures after midnight:
|
|
||||||
- Retried every 10 minutes up to 5 attempts.
|
|
||||||
- Afterwards:
|
|
||||||
- Switched to hourly retries (top of each hour).
|
|
||||||
|
|
||||||
**New behavior (v6.1.0 + user configuration):**
|
|
||||||
|
|
||||||
- Fast retry phase fully contained within the **first hour after midnight**.
|
|
||||||
- You configured:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
if (midnightRetryCount < 2) {
|
|
||||||
// Retry every 20 minutes for first 2 attempts (~1 hour window)
|
|
||||||
midnightRetryCount++;
|
|
||||||
nextScheduledFetchTime = now + 1200; // 20 minutes
|
|
||||||
debugPrint(2, "Midnight retry " + String(midnightRetryCount) + "/2 in 20 minutes");
|
|
||||||
} else {
|
|
||||||
// After that, retry only at top of each hour
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Timeline:
|
|
||||||
- 00:00 – initial attempt (triggered by day rollover).
|
|
||||||
- If dataset is still for previous day:
|
|
||||||
- 1st retry at ~00:20.
|
|
||||||
- 2nd retry at ~00:40.
|
|
||||||
- After that:
|
|
||||||
- Retries only at the **top of the next hours** (01:00, 02:00, …),
|
|
||||||
until a dataset with market day = today is accepted.
|
|
||||||
|
|
||||||
This configuration significantly reduces overnight API load while still ensuring the ticker picks up the new day as soon as the API publishes it.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Other Behavior (Retained From v6.0.0)
|
|
||||||
|
|
||||||
- **NVS caching** of daily data:
|
|
||||||
- On boot, if NVS contains a dataset whose date matches today’s local date:
|
|
||||||
- The JSON is deserialized and processed.
|
|
||||||
- A new API call is **skipped**.
|
|
||||||
- After each accepted “today” fetch:
|
|
||||||
- Raw JSON payload, date, and a timestamp are stored in NVS.
|
|
||||||
- **Manual long‑press refresh**:
|
|
||||||
- Still triggers immediate fetch via `nextScheduledFetchTime = now;`.
|
|
||||||
- Now also respects the improved “today” detection; “yesterday’s” data is not accepted as today.
|
|
||||||
- **CET/CEST time handling**:
|
|
||||||
- Unchanged, still uses `TZ_CET_CEST` with `configTzTime`.
|
|
||||||
- **Secondary menu and NVS status lines**:
|
|
||||||
- Retained from v6.0.0; updated only for version string and minor wording.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -125,30 +163,9 @@ First major redesign focused on reducing API traffic and improving resilience us
|
|||||||
|
|
||||||
### New
|
### New
|
||||||
|
|
||||||
- NVS namespace `"my-ticker"` introduced with keys:
|
- NVS namespace `"my-ticker"` with Wi‑Fi credentials and daily price data caching.
|
||||||
- `ssid`, `pass` – Wi‑Fi credentials.
|
- Boot behavior: Try to load and validate NVS data; if date matches today → reuse it and **skip** initial API call.
|
||||||
- `data_day`, `data_mon`, `data_year` – stored data calendar day.
|
- After‑midnight: NVS is overwritten with each successful new‑day dataset.
|
||||||
- `data_prc` – raw JSON string from API.
|
|
||||||
- `data_last_store` – Unix time when data was stored.
|
|
||||||
- Boot behavior:
|
|
||||||
- Try to load and validate NVS data.
|
|
||||||
- If date matches today → reuse it and **skip** initial API call.
|
|
||||||
- After‑midnight behavior:
|
|
||||||
- NVS is overwritten with each successful new‑day dataset.
|
|
||||||
- In‑RAM data for yesterday is invalidated at day rollover.
|
|
||||||
|
|
||||||
### UI / Menu
|
|
||||||
|
|
||||||
- Primary list:
|
|
||||||
- 15‑minute detail for current hour.
|
|
||||||
- Hourly averages for current + next 2 hours.
|
|
||||||
- Secondary list:
|
|
||||||
- Expanded to 20 lines to include:
|
|
||||||
- Time/date, last update, daily average.
|
|
||||||
- Wi‑Fi RSSI, IP address.
|
|
||||||
- API success rate, uptime.
|
|
||||||
- NVS status block.
|
|
||||||
- Credits & version line.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,92 @@
|
|||||||
|
# Hardware Wiring Diagram (Seeed XIAO ESP32‑C3 Electricity Price Ticker)
|
||||||
|
|
||||||
|
This document depicts the wiring for the **Electricity Price Ticker** project based on the connection instructions in `README.md`.
|
||||||
|
|
||||||
|
It includes:
|
||||||
|
- Required peripherals (LCD + button)
|
||||||
|
- Supported optional peripherals (presence sensor + white LED + optional TTP223 touch button alternative)
|
||||||
|
|
||||||
|
> Further reference (official board documentation):
|
||||||
|
> - https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/
|
||||||
|
|
||||||
|
> Notes:
|
||||||
|
> - Always connect **all grounds together** (ESP32 GND, LCD GND, sensor GND, LED GND).
|
||||||
|
> - Verify your **XIAO ESP32‑C3 pinout** in the Seeed documentation link above.
|
||||||
|
> - **Important:** The **10 kΩ pull-down resistor on D9 is mandatory at all times** (even if the presence sensor is not connected).
|
||||||
|
> - Button input is on **D2**. You may use either a **mechanical pushbutton** (default) *or* a **TTP223 touch module** (alternative), but not both in parallel unless you know what you’re doing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1) Wiring overview (diagram)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TB
|
||||||
|
MCU["Seeed XIAO ESP32-C3"]:::mcu
|
||||||
|
|
||||||
|
LCD["20x4 I2C LCD 2004<br/>PCF8574 backpack<br/>I2C addr 0x27"]:::lcd
|
||||||
|
|
||||||
|
%% Button input options (XIAO D2)
|
||||||
|
D2NODE["D2 node<br/>(buttonPin input)"]:::io
|
||||||
|
BTN["Mechanical pushbutton<br/>default option<br/>active LOW to GND"]:::btn
|
||||||
|
TTP["TTP223 capacitive touch<br/>optional alternative<br/>OUT is HIGH when touched"]:::touch
|
||||||
|
|
||||||
|
%% Presence-sensor input stage (XIAO D9) - always present
|
||||||
|
D9NODE["D9 node<br/>(presencePin input)"]:::io
|
||||||
|
RPD["10k pull-down resistor<br/>D9 to GND<br/>MANDATORY"]:::res
|
||||||
|
PRES["RCWL-0516 presence sensor<br/>optional"]:::pres
|
||||||
|
|
||||||
|
%% White LED output (XIAO D3)
|
||||||
|
LED1["White indicator LED<br/>optional"]:::led
|
||||||
|
|
||||||
|
PSU5V["5V supply<br/>USB-C or regulated 5V"]:::pwr
|
||||||
|
V33["3.3V rail from XIAO"]:::pwr
|
||||||
|
GND["Common GND"]:::gnd
|
||||||
|
|
||||||
|
%% Power
|
||||||
|
PSU5V -->|"5V"| MCU
|
||||||
|
PSU5V -->|"5V or 3V3 if LCD supports"| LCD
|
||||||
|
MCU -->|"3V3"| V33
|
||||||
|
V33 -->|"3V3"| PRES
|
||||||
|
V33 -->|"3V3"| TTP
|
||||||
|
|
||||||
|
%% Grounds
|
||||||
|
MCU --- GND
|
||||||
|
LCD --- GND
|
||||||
|
BTN --- GND
|
||||||
|
TTP --- GND
|
||||||
|
D2NODE --- GND
|
||||||
|
|
||||||
|
D9NODE --- GND
|
||||||
|
PRES --- GND
|
||||||
|
|
||||||
|
LED1 --- GND
|
||||||
|
PSU5V --- GND
|
||||||
|
|
||||||
|
%% I2C
|
||||||
|
MCU -->|"SDA D4"| LCD
|
||||||
|
MCU -->|"SCL D5"| LCD
|
||||||
|
|
||||||
|
%% Button input stage (always)
|
||||||
|
MCU -->|"D2 (buttonPin)"| D2NODE
|
||||||
|
BTN -->|"button to GND"| D2NODE
|
||||||
|
TTP -->|"OUT to D2 node"| D2NODE
|
||||||
|
|
||||||
|
%% Presence input stage (always)
|
||||||
|
MCU -->|"D9 (presencePin)"| D9NODE
|
||||||
|
D9NODE ---|"10k"| RPD
|
||||||
|
RPD -->|"to GND"| GND
|
||||||
|
PRES -->|"OUT to D9 node"| D9NODE
|
||||||
|
|
||||||
|
%% White LED (single LED, directly from pin with series resistor)
|
||||||
|
MCU -->|"D3 (whiteLedPin)"| LED1
|
||||||
|
|
||||||
|
classDef mcu fill:#e8f0ff,stroke:#2b5fd9,stroke-width:1px,color:#000;
|
||||||
|
classDef lcd fill:#fff4e5,stroke:#cc7a00,stroke-width:1px,color:#000;
|
||||||
|
classDef btn fill:#eaffea,stroke:#2d8a2d,stroke-width:1px,color:#000;
|
||||||
|
classDef touch fill:#e6fcff,stroke:#0077b6,stroke-width:1px,color:#000;
|
||||||
|
classDef pres fill:#f3e8ff,stroke:#7b2cbf,stroke-width:1px,color:#000;
|
||||||
|
classDef led fill:#ffe8ef,stroke:#c9184a,stroke-width:1px,color:#000;
|
||||||
|
classDef res fill:#f5f5f5,stroke:#444,stroke-width:1px,color:#000;
|
||||||
|
classDef pwr fill:#fff,stroke:#444,stroke-width:1px,color:#000;
|
||||||
|
classDef gnd fill:#fff,stroke:#000,stroke-width:1.5px,color:#000;
|
||||||
|
classDef io fill:#f5f5f5,stroke:#111,stroke-width:1px,color:#000;
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|

|
||||||
|
|
||||||
# Electricity Price Ticker for XIAO ESP32‑C3 (Energy‑Charts)
|
# Electricity Price Ticker for XIAO ESP32‑C3 (Energy‑Charts)
|
||||||
|
|
||||||
This project is an Arduino‑IDE‑friendly firmware for the **Seeed XIAO ESP32‑C3** that:
|
This project is an Arduino‑IDE‑friendly firmware for the **Seeed XIAO ESP32‑C3** that:
|
||||||
@@ -9,16 +11,61 @@ This project is an Arduino‑IDE‑friendly firmware for the **Seeed XIAO ESP32
|
|||||||
- Uses a white LED and an optional presence sensor to give quick visual feedback.
|
- Uses a white LED and an optional presence sensor to give quick visual feedback.
|
||||||
- Stores daily price data in **NVS** to survive reboots and reduce API calls.
|
- Stores daily price data in **NVS** to survive reboots and reduce API calls.
|
||||||
|
|
||||||
The latest sketch implements **Version 6.1.0**, focusing on:
|
The latest sketch implements **Version 6.2.0**, focusing on:
|
||||||
|
|
||||||
|
- **Version 6.2.0 FIX**: **DST (Daylight Saving Time) handling fully fixed** – the ticker now works correctly on ALL days including DST switch days (spring forward and fall back). Uses timestamp-based price lookups instead of arithmetic calculations.
|
||||||
|
- Version 6.1.2 fix: restore correct **white LED indicator** behavior (ESP32 PWM fix; no dim glow when off).
|
||||||
|
- Version 6.1.1 fix: Correct daily **low/high hourly markers** (now includes negative and **0.0** prices).
|
||||||
- Daily (not hourly) API fetching.
|
- Daily (not hourly) API fetching.
|
||||||
- Robust **NVS storage** of daily price data.
|
- Robust **NVS storage** of daily price data.
|
||||||
- Correct **CET/CEST** handling.
|
- Correct **CET/CEST** handling.
|
||||||
- Resilient **after‑midnight refresh** (no more getting stuck on “No data for today”).
|
- Resilient **after‑midnight refresh** (no more getting stuck on "No data for today").
|
||||||
- Preserved UI and button behavior from v5.5.
|
- Preserved UI and button behavior from v5.5.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## DST (Daylight Saving Time) – How It Works
|
||||||
|
|
||||||
|
### v6.2.0: Fully DST-Safe
|
||||||
|
|
||||||
|
**Important**: Starting with v6.2.0, the ticker is **fully DST-safe** and requires **no manual intervention** on DST switch days.
|
||||||
|
|
||||||
|
The firmware uses **timestamp-based price lookups** that work correctly regardless of whether the day has 23, 24, or 25 hours:
|
||||||
|
|
||||||
|
| Day Type | Hours in Day | Price Entries | Status |
|
||||||
|
|----------|-------------|---------------|--------|
|
||||||
|
| Normal | 24 | 96 | ✅ Works |
|
||||||
|
| Spring forward (March) | 23 | 92 | ✅ Works (fixed in v6.2.0) |
|
||||||
|
| Fall back (October) | 25 | 100 | ✅ Works (fixed in v6.2.0) |
|
||||||
|
|
||||||
|
### Timezone Configuration
|
||||||
|
|
||||||
|
The firmware uses the `TZ_CET_CEST` timezone string for displaying local time:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
const char* TZ_CET_CEST = "CET-1CEST,M3.5.0/02:00,M10.5.0/03:00";
|
||||||
|
```
|
||||||
|
|
||||||
|
**Current behavior:**
|
||||||
|
- Spring forward: Last Sunday of March at 02:00 → 03:00 (CEST, UTC+2)
|
||||||
|
- Fall back: Last Sunday of October at 03:00 → 02:00 (CET, UTC+1)
|
||||||
|
|
||||||
|
### Future-Proof: If EU Cancels DST
|
||||||
|
|
||||||
|
If the EU parliament ever cancels DST switching, you only need to update **one line of code**:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Option A - Stay on CET (UTC+1, winter time) permanently:
|
||||||
|
const char* TZ_CET_CEST = "CET-1";
|
||||||
|
|
||||||
|
// Option B - Stay on CEST (UTC+2, summer time) permanently:
|
||||||
|
const char* TZ_CET_CEST = "CEST-2";
|
||||||
|
```
|
||||||
|
|
||||||
|
The rest of the code works unchanged because it uses timestamp-based lookups.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Bidding Zones (BZN) / Region Selection
|
## Bidding Zones (BZN) / Region Selection
|
||||||
|
|
||||||
The firmware currently uses:
|
The firmware currently uses:
|
||||||
@@ -114,11 +161,12 @@ Typical pins used in the sketch:
|
|||||||
|
|
||||||
**Connections:**
|
**Connections:**
|
||||||
|
|
||||||
- **LCD backpack → XIAO ESP32‑C3**
|
| LCD Backpack | XIAO ESP32‑C3 |
|
||||||
- `VCC` → **5V** (or 3V3 if your module explicitly supports 3.3V I²C)
|
|-------------|---------------|
|
||||||
- `GND` → **GND**
|
| VCC | 5V |
|
||||||
- `SDA` → board I²C SDA pin (see XIAO ESP32‑C3 documentation)
|
| GND | GND |
|
||||||
- `SCL` → board I²C SCL pin
|
| SDA | I²C SDA |
|
||||||
|
| SCL | I²C SCL |
|
||||||
|
|
||||||
> Note: On many XIAO ESP32‑C3 board definitions, SDA/SCL are mapped internally. Just use the default I²C pins as documented by Seeed.
|
> Note: On many XIAO ESP32‑C3 board definitions, SDA/SCL are mapped internally. Just use the default I²C pins as documented by Seeed.
|
||||||
|
|
||||||
@@ -183,12 +231,14 @@ The presence sensor is used to control LCD backlight and LEDs to save power and
|
|||||||
|
|
||||||
Recommended module: **RCWL‑0516** microwave motion sensor.
|
Recommended module: **RCWL‑0516** microwave motion sensor.
|
||||||
|
|
||||||
**Wiring (from the v5.5 header, preserved in v6.x):**
|
**Wiring:**
|
||||||
|
|
||||||
- `VCC` → **3.3V**
|
| RCWL‑0516 | Connection |
|
||||||
- `GND` → **GND**
|
|-----------|------------|
|
||||||
- `OUT` → `GPIO 9` (`presencePin`)
|
| VCC | 3.3V |
|
||||||
- **Required**: 10 kΩ pull‑down resistor between `GPIO 9` and `GND`.
|
| GND | GND |
|
||||||
|
| OUT | GPIO 9 (`presencePin`) |
|
||||||
|
| **Required**: 10kΩ pull‑down | Between GPIO 9 and GND |
|
||||||
|
|
||||||
Characteristics:
|
Characteristics:
|
||||||
|
|
||||||
@@ -236,23 +286,16 @@ The LED is driven with various patterns to indicate price level; see “LED Pric
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Firmware Features (v6.1.0)
|
## Firmware Features (v6.2.0)
|
||||||
|
|
||||||
### Core Display & Pricing
|
### Core Display & Pricing
|
||||||
|
|
||||||
- Data source:
|
- Data source: `https://api.energy-charts.info/price?bzn=SI`
|
||||||
|
- Resolution: 15‑minute intervals with hourly averages
|
||||||
```text
|
- Display shows:
|
||||||
https://api.energy-charts.info/price?bzn=SI
|
- **Row 0**: Current hour, four 15‑minute values
|
||||||
```
|
- **Rows 1–3**: Current hour + next two hours as hourly averages
|
||||||
|
- Price calculation: Raw MWh → EUR/kWh with configurable surcharges (power company fee + VAT)
|
||||||
- Resolution:
|
|
||||||
- Prices in **15‑minute intervals** (`price[]`, `unix_seconds[]`).
|
|
||||||
- Display shows:
|
|
||||||
- **Row 0**: Current hour, four 15‑minute values in compact format (`XX XX XX XX`).
|
|
||||||
- **Rows 1–3**: Current hour + next two hours as hourly averages.
|
|
||||||
- Price calculation:
|
|
||||||
- Raw MWh prices are converted to **EUR/kWh**.
|
|
||||||
- Two configurable surcharges:
|
- Two configurable surcharges:
|
||||||
- `POWER_COMPANY_FEE_PERCENTAGE` (default `12.0` %).
|
- `POWER_COMPANY_FEE_PERCENTAGE` (default `12.0` %).
|
||||||
- `VAT_PERCENTAGE` (default `22.0` %).
|
- `VAT_PERCENTAGE` (default `22.0` %).
|
||||||
@@ -260,17 +303,27 @@ The LED is driven with various patterns to indicate price level; see “LED Pric
|
|||||||
- `LiquidCrystal_I2C` with custom characters for:
|
- `LiquidCrystal_I2C` with custom characters for:
|
||||||
- Local language letters.
|
- Local language letters.
|
||||||
- Low‑price and high‑price indicators.
|
- Low‑price and high‑price indicators.
|
||||||
|
- **Daily min/max markers**:
|
||||||
|
- The low/high hourly indicators consider **negative**, **0.0**, and positive prices (v6.1.1 fix).
|
||||||
|
|
||||||
### LED Price Signalling
|
### LED Price Signalling
|
||||||
|
|
||||||
The white LED (GPIO 5) reflects the **current 15‑minute interval** price:
|
The white LED (GPIO 5) reflects the **current 15‑minute interval** price:
|
||||||
|
|
||||||
- Very cheap (`<= 0.05 EUR/kWh`) → smooth breathing.
|
| Price Level | LED Behavior |
|
||||||
- Cheap / normal → steady on.
|
|-------------|--------------|
|
||||||
- Moderately expensive → slow blink.
|
| Negative / no data | LED off |
|
||||||
- Expensive → faster blink.
|
| ≤ 0.05 EUR/kWh | Smooth breathing |
|
||||||
- Very expensive → complex “double‑blink with long on” pattern.
|
| 0.05 – 0.15 | Steady on |
|
||||||
- Negative price or no data → LED off.
|
| 0.15 – 0.25 | Slow blink |
|
||||||
|
| 0.25 – 0.35 | Fast blink |
|
||||||
|
| 0.35 – 0.50 | Double blink |
|
||||||
|
| > 0.50 | Triple blink pattern |
|
||||||
|
|
||||||
|
**Important implementation note (from v6.1.2 on):**
|
||||||
|
|
||||||
|
- On ESP32, avoid mixing PWM (`analogWrite`) and `digitalWrite` on the same LED pin.
|
||||||
|
- The firmware now uses `analogWrite(pin, 0/255)` consistently to guarantee the LED is fully off when gated off.
|
||||||
|
|
||||||
LED is **disabled** when:
|
LED is **disabled** when:
|
||||||
|
|
||||||
@@ -349,7 +402,7 @@ After successful NTP time sync:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Daily Fetch Strategy (v6.1.0)
|
## Daily Fetch Strategy (v6.1.0+)
|
||||||
|
|
||||||
### Goals
|
### Goals
|
||||||
|
|
||||||
@@ -542,7 +595,7 @@ If NVS does not contain valid Wi‑Fi credentials, or if connecting fails repeat
|
|||||||
- `DNSServer` (from ESP32 core)
|
- `DNSServer` (from ESP32 core)
|
||||||
- `WebServer` (from ESP32 core)
|
- `WebServer` (from ESP32 core)
|
||||||
- `Preferences` (built‑in for ESP32)
|
- `Preferences` (built‑in for ESP32)
|
||||||
3. Open the v6.1 `.ino` file (e.g. `20260130_electricity_ticker_6_1_nvs_daily_fetch.ino`).
|
3. Open the v6.2.0 `.ino` file (e.g. `ESP32_standalone_electricity_ticker_6_1_2_nvs_daily_fetch.ino`).
|
||||||
4. In Tools:
|
4. In Tools:
|
||||||
- Board: `Seeed XIAO ESP32C3`
|
- Board: `Seeed XIAO ESP32C3`
|
||||||
- Port: choose the correct serial port.
|
- Port: choose the correct serial port.
|
||||||
@@ -555,13 +608,15 @@ If NVS does not contain valid Wi‑Fi credentials, or if connecting fails repeat
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
||||||
## Versioning & Changelog
|
## Versioning & Changelog
|
||||||
|
|
||||||
- **v5.5** – 15‑minute detail mode, LED based on current 15‑minute slot, improved DST handling (see file `20251027a_electricity_ticker_10_5_5_latest_DST_and_midnight_fix.ino`).
|
- **v6.2.0** – DST handling fully fixed via timestamp-based lookups
|
||||||
- **v6.0.0** – First NVS‑enabled version:
|
- **v6.1.2** – LED indicator restored (broken in previous version):
|
||||||
- Store daily price data in NVS.
|
- Avoid mixing PWM and `digitalWrite` on the same LED pin (ESP32 LEDC behavior).
|
||||||
- Reduce API calls to “boot + after‑midnight”.
|
- Ensures LED is fully off when gated off; patterns operate correctly.
|
||||||
- Add NVS status section to secondary menu.
|
- **v6.1.1** – Daily low/high marker fix:
|
||||||
|
- Daily min/max and average now include negative and **0.0** prices.
|
||||||
- **v6.1.0** – Midnight fetch & “today” detection fixes:
|
- **v6.1.0** – Midnight fetch & “today” detection fixes:
|
||||||
- Correctly detect **market day** using the last `unix_seconds` timestamp.
|
- Correctly detect **market day** using the last `unix_seconds` timestamp.
|
||||||
- Distinguish between:
|
- Distinguish between:
|
||||||
@@ -571,6 +626,11 @@ If NVS does not contain valid Wi‑Fi credentials, or if connecting fails repeat
|
|||||||
- Two retries every 20 minutes in the first hour (~00:20, ~00:40).
|
- Two retries every 20 minutes in the first hour (~00:20, ~00:40).
|
||||||
- Then hourly retries (top‑of‑hour) until today’s dataset is available.
|
- Then hourly retries (top‑of‑hour) until today’s dataset is available.
|
||||||
- Behavior on reboot and manual long‑press is unchanged, but now respects the improved “today” logic.
|
- Behavior on reboot and manual long‑press is unchanged, but now respects the improved “today” logic.
|
||||||
|
- **v6.0.0** – NVS storage & daily fetch:
|
||||||
|
- Store daily price data in NVS.
|
||||||
|
- Reduce API calls to “boot + after‑midnight”.
|
||||||
|
- Add NVS status section to secondary menu.
|
||||||
|
- **v5.5** – 15‑minute detail mode, LED based on current 15‑minute slot, improved DST handling (see file `20251027a_electricity_ticker_10_5_5_latest_DST_and_midnight_fix.ino`).
|
||||||
|
|
||||||
See [`CHANGELOG.md`](./CHANGELOG.md) for more details.
|
See [`CHANGELOG.md`](./CHANGELOG.md) for more details.
|
||||||
|
|
||||||
|
|||||||
+31
-11
@@ -2,25 +2,45 @@
|
|||||||
|
|
||||||
## Current firmware
|
## Current firmware
|
||||||
|
|
||||||
- **Version:** 6.0.0
|
- **Version:** 6.2.0
|
||||||
- **Release date:** 2026-01-27
|
- **Release date:** 2026-03-29
|
||||||
- **Target MCU:** Seeed XIAO ESP32‑C3
|
- **Target MCU:** Seeed XIAO ESP32‑C3
|
||||||
- **Display:** 20x4 I²C LCD (PCF8574, default address `0x27`)
|
- **Display:** 20x4 I²C LCD (PCF8574, default address `0x27`)
|
||||||
- **API endpoint:** `https://api.energy-charts.info/price?bzn=SI`
|
- **API endpoint:** `https://api.energy-charts.info/price?bzn=SI`
|
||||||
- **Resolution:** 15‑minute intervals, hourly averages for overview
|
- **Resolution:** 15‑minute intervals, hourly averages for overview
|
||||||
|
|
||||||
## Highlights of v6.0.0
|
## Highlights of v6.2.0
|
||||||
|
|
||||||
- Single **daily fetch** (on boot / after midnight) instead of hourly.
|
- **CRITICAL FIX**: DST (Daylight Saving Time) handling is now fully fixed for all days.
|
||||||
- **NVS storage** of daily data for resilience to power outages.
|
- Previously, the code assumed every day has exactly 96 price entries (24h × 4). This caused incorrect price display on DST switch days:
|
||||||
- Robust midnight rollover and retry logic:
|
- Spring forward (March): Only 92 entries → wrong prices displayed
|
||||||
- First immediate fetch.
|
- Fall back (October): 100 entries → wrong prices displayed
|
||||||
- Up to 5 × 10‑minute retries.
|
- **Solution**: All price lookups now use timestamp-based searching through the `unix_seconds` array instead of arithmetic calculation (`hourIndex * 4`).
|
||||||
- Then top‑of‑hour retries until successful.
|
- New functions: `findPriceIndexForHour()`, `findCurrentPriceIndex()`, `getHourFromPriceIndex()`
|
||||||
- Prevents yesterday’s prices from ever being shown as today’s.
|
- Updated functions: `getHourlyAverage()`, `display15MinuteDetails()`, `displayPriceRow()`, `displayPrimaryList()`, `updateLeds()`
|
||||||
- Secondary info menu extended with **NVS status** and clear version label.
|
- The ticker now works correctly on all days, including DST switch days, with no manual intervention.
|
||||||
|
- **Future-proof**: If EU cancels DST, only the `TZ_CET_CEST` string needs updating (one line of code).
|
||||||
|
|
||||||
For full details, see:
|
For full details, see:
|
||||||
|
|
||||||
- [CHANGELOG.md](./CHANGELOG.md)
|
- [CHANGELOG.md](./CHANGELOG.md)
|
||||||
- [README.md](./README.md)
|
- [README.md](./README.md)
|
||||||
|
|
||||||
|
## Previous firmware
|
||||||
|
|
||||||
|
- **Version:** 6.1.2
|
||||||
|
- **Release date:** 2026-03-11
|
||||||
|
- **Target MCU:** Seeed XIAO ESP32‑C3
|
||||||
|
|
||||||
|
## Highlights of v6.1.2
|
||||||
|
|
||||||
|
- Fix: restore proper white LED price indicator behavior on ESP32 by avoiding mixing PWM (`analogWrite`) and `digitalWrite` on the same pin.
|
||||||
|
- Fix: LED is now truly off when backlight/LED gating turns it off (no more "dim glow").
|
||||||
|
|
||||||
|
## Earlier firmware
|
||||||
|
|
||||||
|
- **Version:** 6.1.1 (2026-03-07) – Daily low/high marker includes negative and zero prices
|
||||||
|
- **Version:** 6.1.0 (2026-01-30) – Midnight fetch and market day detection fixes
|
||||||
|
- **Version:** 6.0.0 (2026-01-27) – NVS storage and daily fetch
|
||||||
|
|
||||||
|
For detailed history, see [CHANGELOG.md](./CHANGELOG.md).
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 434 KiB |
Reference in New Issue
Block a user