mirror of
https://github.com/Legolas-2025/Standalone-electricity-price-ticker.git
synced 2026-08-18 12:44:54 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a0fd42161 | ||
|
|
a7d479546d | ||
|
|
0f6fadd1f8 | ||
|
|
28f7be344f | ||
|
|
5c870f240c | ||
|
|
707ebcd99c | ||
|
|
4e9b9495cd | ||
|
|
1b17a7b80c | ||
|
|
91dea24da8 | ||
|
|
52c3727d39 | ||
|
|
824170a6d1 | ||
|
|
dfb11004ba | ||
|
|
739b21609e | ||
|
|
5ef53916ea | ||
|
|
b55a7b4945 | ||
|
|
d7e27b4f56 | ||
|
|
614be25a1f | ||
|
|
1e5fa661f0 | ||
|
|
13da0e0c87 | ||
|
|
8543493ac0 | ||
|
|
eb5ef5954b | ||
|
|
2fff8296c5 | ||
|
|
3f09e475fc | ||
|
|
eb34d07ccf | ||
|
|
dcc434f7e4 | ||
|
|
6c7a66be8e | ||
|
|
e1ad1b01c3 | ||
|
|
f77f50f1d4 | ||
|
|
45cabc17dd | ||
|
|
fedef66ae0 | ||
|
|
65fdb14cec | ||
|
|
3da3dec344 | ||
|
|
6a6cf113b3 | ||
|
|
26b1a309e1 |
+205
-133
@@ -2,37 +2,221 @@
|
||||
|
||||
All notable changes to this project are documented here.
|
||||
|
||||
## v6.2.4 - Exact-boundary display refresh bug (2026-04-01):
|
||||
|
||||
**Summary**
|
||||
|
||||
Top of the hour auto display refresh glitch fix where display automatically refreshed but showed the PREVIOUS hour's data.
|
||||
|
||||
### Problem:
|
||||
- At the exact top of the hour (e.g., 20:00:00), the display automatically refreshed but showed the PREVIOUS hour's data (19:00). This happened because the "next-boundary" rounding logic in findCurrentPriceIndex() incorrectly excluded the current interval if the time was exactly on the boundary.
|
||||
|
||||
### Solution:
|
||||
- Simplified findCurrentPriceIndex() to use a robust "last entry <= now" comparison. This ensures the display transitions to the new hour instantaneously at XX:00:00.
|
||||
|
||||
---
|
||||
|
||||
## v6.2.3 - State-based display refresh logic fix (2026-04-01):
|
||||
|
||||
**Summary**
|
||||
|
||||
The refresh logic should be "State-Based" rather than "Event-Based." Instead of checking if the minute is zero, it should check if the current hour is different from the last recorded hour.
|
||||
|
||||
### Problem: Screen would occasionally fail to update if the ESP32 was busy
|
||||
- fetching data or reconnecting WiFi) during the exact 00/15/30/45 minute mark.
|
||||
|
||||
### Solution: Switched from "Event-Based" (refresh only AT minute X) to "State-Based"
|
||||
|
||||
(refresh IF current time != last refresh time).
|
||||
- This ensures the screen updates immediately even if the device was busy during the transition.
|
||||
|
||||
---
|
||||
|
||||
## v6.2.2 - Display blank lines issue fix (2026-03-31):
|
||||
|
||||
**Summary**
|
||||
|
||||
Fixed a bug where the display was showing blank lines
|
||||
|
||||
### Problem: Sometimes rows 0 and 1 (current 15-min prices and current hour) were blank
|
||||
|
||||
**Cause:** The "hour suppression" logic was hiding the current hour unexpectedly
|
||||
|
||||
### Solution:
|
||||
|
||||
- Row 1 (current hour) now ALWAYS shows - suppression logic only applies to rows 2-3
|
||||
- Row 0 (15-min details) also always shows for the current hour
|
||||
|
||||
---
|
||||
|
||||
## v6.2.1 – Current Interval Fix (2026‑03‑29)
|
||||
|
||||
**Summary**
|
||||
|
||||
Fixed a bug where the display was showing prices one hour ahead of the current time.
|
||||
|
||||
### Problem: Display Showing Next Hour Instead of Current
|
||||
|
||||
**Root Cause:**
|
||||
|
||||
The `findCurrentPriceIndex()` function was finding the **next** 15-minute interval (first entry with timestamp >= now), but it should find the **current** interval (the one we're currently IN).
|
||||
|
||||
For example, at 17:57:
|
||||
- The current 15-minute interval is **17:45-18:00** (price indexed at 17:45)
|
||||
- The **next** interval is 18:00-18:15 (price indexed at 18:00)
|
||||
- The buggy function returned the index for **18:00** instead of **17:45**
|
||||
- Result: Display showed hour **18** instead of hour **17**
|
||||
|
||||
### Solution
|
||||
|
||||
The fix calculates the **next 15-minute boundary** and finds the last entry **strictly before** that boundary:
|
||||
|
||||
```cpp
|
||||
// Calculate the next 15-minute boundary
|
||||
const int QUARTER_SECONDS = 15 * 60; // 900 seconds
|
||||
time_t nextQuarter = ((now + QUARTER_SECONDS - 1) / QUARTER_SECONDS) * QUARTER_SECONDS;
|
||||
|
||||
// Find the last entry strictly before nextQuarter
|
||||
for (size_t i = unixSeconds.size(); i > 0; i--) {
|
||||
if ((time_t)unixTime < nextQuarter) {
|
||||
return (int)(i - 1);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example:**
|
||||
- At 17:57: nextQuarter = 18:00, finds last entry < 18:00 = 17:45 ✅
|
||||
- At 18:00: nextQuarter = 18:15, finds last entry < 18:15 = 18:00 ✅
|
||||
- At 18:46: nextQuarter = 19:00, finds last entry < 19:00 = 18:45 ✅
|
||||
|
||||
---
|
||||
|
||||
## 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** hour as the daily minimum.
|
||||
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:
|
||||
|
||||
- `if (hourlyAvg > 0) { ... }`
|
||||
|
||||
```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).
|
||||
|
||||
- Additionally, the daily average was computed as `sum / 24.0` even though hours were being skipped from `sum`, making the daily average incorrect whenever any hour was excluded.
|
||||
|
||||
**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) when computing:
|
||||
- `lowestPriceIndex`
|
||||
- `highestPriceIndex`
|
||||
- `averagePrice`
|
||||
- Computes `averagePrice` using the number of valid hours (normally 24).
|
||||
- Includes **all values** (negative, zero, positive).
|
||||
|
||||
---
|
||||
|
||||
@@ -40,112 +224,21 @@ This release fixes a bug where the **daily lowest / highest hourly price marker*
|
||||
|
||||
**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
|
||||
|
||||
**Problem (v6.0.0):**
|
||||
|
||||
- After midnight, the device:
|
||||
- Detected day rollover and entered a “no data” state.
|
||||
- Scheduled an immediate API fetch.
|
||||
- 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.
|
||||
- The API can continue to serve **yesterday's market day** for some time after local midnight.
|
||||
- HTTP + JSON success always incremented `apiSuccessCount`, even if the data was "not for today".
|
||||
- The scheduler treated such fetches as **successful** and never retried.
|
||||
|
||||
**Solution (v6.1.0):**
|
||||
|
||||
1. **Market Day Detection**:
|
||||
- `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).
|
||||
- It compares that calendar date (local time) against the current local date.
|
||||
- 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.
|
||||
- `processJsonData()` now determines the "market day" using the **last** `unix_seconds` timestamp.
|
||||
- A scheduled fetch is only successful if `lastProcessJsonAcceptedToday == true`.
|
||||
- Midnight retry logic keeps trying until valid "today" data is received.
|
||||
|
||||
---
|
||||
|
||||
@@ -157,30 +250,9 @@ First major redesign focused on reducing API traffic and improving resilience us
|
||||
|
||||
### New
|
||||
|
||||
- NVS namespace `"my-ticker"` introduced with keys:
|
||||
- `ssid`, `pass` – Wi‑Fi credentials.
|
||||
- `data_day`, `data_mon`, `data_year` – stored data calendar day.
|
||||
- `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.
|
||||
- NVS namespace `"my-ticker"` with Wi‑Fi credentials and daily price data caching.
|
||||
- Boot behavior: Try to load and validate NVS data; if date matches today → reuse it and **skip** initial API call.
|
||||
- After‑midnight: NVS is overwritten with each successful new‑day dataset.
|
||||
|
||||
---
|
||||
|
||||
|
||||
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
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
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,25 +1,96 @@
|
||||

|
||||
|
||||
# Electricity Price Ticker for XIAO ESP32‑C3 (Energy‑Charts)
|
||||
|
||||
This project is an Arduino‑IDE‑friendly firmware for the **Seeed XIAO ESP32‑C3** that:
|
||||
|
||||
- Connects to Wi‑Fi.
|
||||
- Fetches **day‑ahead electricity prices** from [Energy‑Charts.info](https://energy-charts.info) (Bundesnetzagentur / SMARD.de).
|
||||
- Fetches **day‑ahead electricity prices** from [Energy‑Charts.info](https://energy-charts.info).
|
||||
- Computes final consumer prices (including configurable power‑company fee + VAT).
|
||||
- Displays current and upcoming prices on a **20x4 I²C 2004 LCD**.
|
||||
- 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.
|
||||
|
||||
The latest sketch implements **Version 6.1.1**, focusing on:
|
||||
The latest sketch implements **Version 6.2.4**.
|
||||
|
||||
---
|
||||
|
||||
## Version Highlights
|
||||
|
||||
### v6.2.4 - Exact-boundary display refresh bug (critical fix of v6.2.3 update)
|
||||
- Problem: At the exact top of the hour (e.g., 20:00:00), the display automatically refreshed but showed the PREVIOUS hour's data (19:00). This happened because the "next-boundary" rounding logic in findCurrentPriceIndex() incorrectly excluded the current interval if the time was exactly on the boundary.
|
||||
- Fix: Simplified findCurrentPriceIndex() to use a robust "last entry <= now" comparison. This ensures the display transitions to the new hour instantaneously at XX:00:00.
|
||||
|
||||
### v6.2.3 - State-based display refresh logic (critical fix of v6.2.2 update)
|
||||
- Problem: Screen would occasionally fail to update if the ESP32 was busy (fetching data or reconnecting WiFi) during the exact 00/15/30/45 minute mark.
|
||||
- Fix: Switched from "Event-Based" (refresh only AT minute X) to "State-Based" (refresh IF current time != last refresh time). This ensures the screen updates immediately even if the device was busy during the transition.
|
||||
|
||||
### v6.2.2 - Display Blank Lines Issue Fix (critical fix of v6.2.1 update)
|
||||
- Problem: Sometimes rows 0 and 1 (current 15-min prices and current hour) were blank.
|
||||
- Cause: The "hour suppression" logic was hiding the current hour unexpectedly.
|
||||
- Fix:
|
||||
- Row 1 (current hour) now ALWAYS shows - suppression logic only applies to rows 2-3.
|
||||
- Row 0 (15-min details) also always shows for the current hour.
|
||||
|
||||
### v6.2.1 – Current Interval Fix (critical fix of v6.2.0 update)
|
||||
- Fixed display showing prices one hour ahead of the current time.
|
||||
- `findCurrentPriceIndex()` now correctly returns the current 15-minute interval.
|
||||
|
||||
Major update 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.
|
||||
- Robust **NVS storage** of daily price data.
|
||||
- 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.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
The firmware currently uses:
|
||||
@@ -115,11 +186,12 @@ Typical pins used in the sketch:
|
||||
|
||||
**Connections:**
|
||||
|
||||
- **LCD backpack → XIAO ESP32‑C3**
|
||||
- `VCC` → **5V** (or 3V3 if your module explicitly supports 3.3V I²C)
|
||||
- `GND` → **GND**
|
||||
- `SDA` → board I²C SDA pin (see XIAO ESP32‑C3 documentation)
|
||||
- `SCL` → board I²C SCL pin
|
||||
| LCD Backpack | XIAO ESP32‑C3 |
|
||||
|-------------|---------------|
|
||||
| VCC | 5V |
|
||||
| GND | GND |
|
||||
| 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.
|
||||
|
||||
@@ -184,12 +256,14 @@ The presence sensor is used to control LCD backlight and LEDs to save power and
|
||||
|
||||
Recommended module: **RCWL‑0516** microwave motion sensor.
|
||||
|
||||
**Wiring (from the v5.5 header, preserved in v6.x):**
|
||||
**Wiring:**
|
||||
|
||||
- `VCC` → **3.3V**
|
||||
- `GND` → **GND**
|
||||
- `OUT` → `GPIO 9` (`presencePin`)
|
||||
- **Required**: 10 kΩ pull‑down resistor between `GPIO 9` and `GND`.
|
||||
| RCWL‑0516 | Connection |
|
||||
|-----------|------------|
|
||||
| VCC | 3.3V |
|
||||
| GND | GND |
|
||||
| OUT | GPIO 9 (`presencePin`) |
|
||||
| **Required**: 10kΩ pull‑down | Between GPIO 9 and GND |
|
||||
|
||||
Characteristics:
|
||||
|
||||
@@ -237,23 +311,16 @@ The LED is driven with various patterns to indicate price level; see “LED Pric
|
||||
|
||||
---
|
||||
|
||||
## Firmware Features (v6.1.1)
|
||||
## Firmware Features (v6.2.0)
|
||||
|
||||
### Core Display & Pricing
|
||||
|
||||
- Data source:
|
||||
|
||||
```text
|
||||
https://api.energy-charts.info/price?bzn=SI
|
||||
```
|
||||
|
||||
- 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**.
|
||||
- Data source: `https://api.energy-charts.info/price?bzn=SI`
|
||||
- Resolution: 15‑minute intervals with hourly averages
|
||||
- Display shows:
|
||||
- **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)
|
||||
- Two configurable surcharges:
|
||||
- `POWER_COMPANY_FEE_PERCENTAGE` (default `12.0` %).
|
||||
- `VAT_PERCENTAGE` (default `22.0` %).
|
||||
@@ -262,18 +329,26 @@ The LED is driven with various patterns to indicate price level; see “LED Pric
|
||||
- Local language letters.
|
||||
- Low‑price and high‑price indicators.
|
||||
- **Daily min/max markers**:
|
||||
- The low/high hourly indicators now consider **negative**, **0.0**, and positive prices (v6.1.1 fix).
|
||||
- The low/high hourly indicators consider **negative**, **0.0**, and positive prices (v6.1.1 fix).
|
||||
|
||||
### LED Price Signalling
|
||||
|
||||
The white LED (GPIO 5) reflects the **current 15‑minute interval** price:
|
||||
|
||||
- Very cheap (`<= 0.05 EUR/kWh`) → smooth breathing.
|
||||
- Cheap / normal → steady on.
|
||||
- Moderately expensive → slow blink.
|
||||
- Expensive → faster blink.
|
||||
- Very expensive → complex “double‑blink with long on” pattern.
|
||||
- Negative price or no data → LED off.
|
||||
| Price Level | LED Behavior |
|
||||
|-------------|--------------|
|
||||
| Negative / no data | LED off |
|
||||
| ≤ 0.05 EUR/kWh | Smooth breathing |
|
||||
| 0.05 – 0.15 | Steady on |
|
||||
| 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:
|
||||
|
||||
@@ -545,7 +620,7 @@ If NVS does not contain valid Wi‑Fi credentials, or if connecting fails repeat
|
||||
- `DNSServer` (from ESP32 core)
|
||||
- `WebServer` (from ESP32 core)
|
||||
- `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:
|
||||
- Board: `Seeed XIAO ESP32C3`
|
||||
- Port: choose the correct serial port.
|
||||
@@ -558,13 +633,15 @@ If NVS does not contain valid Wi‑Fi credentials, or if connecting fails repeat
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 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.0.0** – First NVS‑enabled version:
|
||||
- Store daily price data in NVS.
|
||||
- Reduce API calls to “boot + after‑midnight”.
|
||||
- Add NVS status section to secondary menu.
|
||||
- **v6.2.0** – DST handling fully fixed via timestamp-based lookups
|
||||
- **v6.1.2** – LED indicator restored (broken in previous version):
|
||||
- Avoid mixing PWM and `digitalWrite` on the same LED pin (ESP32 LEDC behavior).
|
||||
- Ensures LED is fully off when gated off; patterns operate correctly.
|
||||
- **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:
|
||||
- Correctly detect **market day** using the last `unix_seconds` timestamp.
|
||||
- Distinguish between:
|
||||
@@ -574,6 +651,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).
|
||||
- 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.
|
||||
- **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.
|
||||
|
||||
|
||||
+109
-29
@@ -2,39 +2,119 @@
|
||||
|
||||
## Current firmware
|
||||
|
||||
- **Version:** 6.1.1
|
||||
- **Release date:** 2026-03-07
|
||||
- **Target MCU:** Seeed XIAO ESP32‑C3
|
||||
- **Display:** 20x4 I²C LCD (PCF8574, default address `0x27`)
|
||||
- **API endpoint:** `https://api.energy-charts.info/price?bzn=SI`
|
||||
- **Resolution:** 15‑minute intervals, hourly averages for overview
|
||||
- **Version:** 7.0
|
||||
- **Release date:** 2026-04-03
|
||||
- **Target MCU:** Seeed XIAO ESP32‑C3
|
||||
- **Display:** 20x4 I²C LCD (PCF8574, default address `0x27`)
|
||||
- **API endpoint:** `https://api.energy-charts.info/price?bzn=SI`
|
||||
- **Resolution:** 15‑minute intervals, hourly averages for overview
|
||||
|
||||
## Highlights of v6.1.1
|
||||
## Highlights of v7.0
|
||||
|
||||
- Fix: daily **lowest/highest hourly price marker** now includes **negative** and **0.0** prices.
|
||||
- Fix: daily average is computed over the number of valid hours (instead of always dividing by 24 even when hours were skipped).
|
||||
### MAJOR UPGRADE: Rolling 48-Hour Logic & Midnight Bridge
|
||||
|
||||
This version is the **"Golden Build"** for this hardware. It represents the culmination of hardware stability fixes from v6.2.4 combined with revolutionary new 48-hour price prediction capabilities.
|
||||
|
||||
#### 1. The Midnight Bridge (Rollover Logic)
|
||||
|
||||
The most complex part of electricity tickers is handling the midnight transition. This code now correctly detects the moment the local clock moves from 23:59:59 to 00:00:00.
|
||||
|
||||
**The Swap:** Instead of waiting for a slow API call at midnight (which usually fails because the server hasn't updated yet), the code instantly promotes the "Tomorrow" buffer to become "Today" data.
|
||||
|
||||
**The NVS Update:** The code correctly serializes the new "Today" data and saves it to NVS immediately after the swap. This ensures that if power cuts at 00:05 AM, the device reboots with the correct data already loaded.
|
||||
|
||||
#### 2. Dual-Buffer NVS System
|
||||
|
||||
The ticker now stores "Today" and "Tomorrow" data independently in NVS:
|
||||
|
||||
- **Today buffer (`doc`)**: Contains the current day's price data
|
||||
- **Tomorrow buffer (`docTomorrow`)**: Contains the next day's price data
|
||||
- **NVS keys**: `data_prc`/`data_day`/`data_mon`/`data_year` for today, `data_prc_t`/`data_store_t` for tomorrow
|
||||
|
||||
#### 3. Smart Fetching & API URL
|
||||
|
||||
The logic for fetching tomorrow's data is implemented correctly:
|
||||
|
||||
- **URL Construction**: Adding `&start=YYYY-MM-DD` dynamically after 14:00 (2 PM) queries the Energy-Charts API for the next day
|
||||
- **Validation**: In `processJsonData()`, the code compares the timestamp in the JSON against the target date, preventing the "Tomorrow" buffer from being filled with "Today's" data if the API is lagging
|
||||
|
||||
#### 4. Seamless 48H Scrolling
|
||||
|
||||
If next-day data is available, the button allows scrolling up to **47 hours ahead**:
|
||||
|
||||
- **Visual Distinction**: Using `HH:>>` for tomorrow's hours prevents the user from confusing a cheap price "tomorrow" with a cheap price "today"
|
||||
- **Index Safety**: The code correctly uses `lowestPriceIndexTomorrow` and `highestPriceIndexTomorrow` when the display is in the "tomorrow" range, ensuring the Min/Max icons appear on the correct 15-minute segments
|
||||
|
||||
#### 5. Hardware Stability (Inherited from v6.2.4)
|
||||
|
||||
All v6.2.4 hardware stability fixes are preserved:
|
||||
|
||||
- **Refresh Logic**: "State-Based" refresh ensures the display updates exactly at 00, 15, 30, and 45 minutes past the hour, even if the CPU is busy with a background fetch
|
||||
- **LED Indicators**: White LED for low price and Built-in LED for connectivity remain pinned to the actual current price, even when the user is scrolling through future data on the screen
|
||||
|
||||
#### Final "Sanity Check" Verdict
|
||||
|
||||
**Status:** Verified. The code is safe to deploy. The transition from 15-minute intervals to the midnight rollover is now seamless. The "1 AM fetch gap" that plagues most electricity tickers has been successfully bypassed.
|
||||
|
||||
---
|
||||
|
||||
## Highlights of v6.2.4
|
||||
|
||||
- **BUG FIX:** Exact-boundary display refresh bug
|
||||
- Problem: At the exact top of the hour (e.g., 20:00:00), the display automatically refreshed but showed the PREVIOUS hour's data (19:00). This happened because the "next-boundary" rounding logic in findCurrentPriceIndex() incorrectly excluded the current interval if the time was exactly on the boundary.
|
||||
- Fix: Simplified findCurrentPriceIndex() to use a robust "last entry <= now" comparison. This ensures the display transitions to the new hour instantaneously at XX:00:00.
|
||||
|
||||
## Highlights of v6.2.3
|
||||
|
||||
- **BUG FIX**: State-based display refresh logic
|
||||
- Problem: Screen would occasionally fail to update if the ESP32 was busy (fetching data or reconnecting WiFi) during the exact 00/15/30/45 minute mark.
|
||||
- Fix: Switched from "Event-Based" (refresh only AT minute X) to "State-Based" (refresh IF current time != last refresh time). This ensures the screen updates immediately even if the device was busy during the transition.
|
||||
|
||||
## Highlights of v6.2.2
|
||||
|
||||
- **BUG FIX**: Display blank lines issue
|
||||
- Problem: Sometimes rows 0 and 1 (current 15-min prices and current hour) were blank
|
||||
- Cause: The "hour suppression" logic was hiding the current hour unexpectedly
|
||||
- Fix:
|
||||
- Row 1 (current hour) now ALWAYS shows - suppression logic only applies to rows 2-3
|
||||
- Row 0 (15-min details) also always shows for the current hour
|
||||
|
||||
## Highlights of v6.2.1
|
||||
|
||||
- **BUG FIX**: Fixed `findCurrentPriceIndex()` to return the correct current interval.
|
||||
- Problem: At 17:57, it returned index for 18:00 instead of 17:45, causing display to show hour 18 instead of hour 17.
|
||||
- Fix: Now calculates next 15-minute boundary and finds the last entry before that boundary.
|
||||
|
||||
## Highlights of v6.2.0
|
||||
|
||||
- **CRITICAL FIX**: DST (Daylight Saving Time) handling is now fully fixed for all days.
|
||||
- Previously, the code assumed every day has exactly 96 price entries (24h × 4). This caused incorrect price display on DST switch days:
|
||||
- Spring forward (March): Only 92 entries → wrong prices displayed
|
||||
- Fall back (October): 100 entries → wrong prices displayed
|
||||
- **Solution**: All price lookups now use timestamp-based searching through the `unix_seconds` array instead of arithmetic calculation (`hourIndex * 4`).
|
||||
- New functions: `findPriceIndexForHour()`, `findCurrentPriceIndex()`, `getHourFromPriceIndex()`
|
||||
- Updated functions: `getHourlyAverage()`, `display15MinuteDetails()`, `displayPriceRow()`, `displayPrimaryList()`, `updateLeds()`
|
||||
- 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).
|
||||
|
||||
## 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 full details, see:
|
||||
|
||||
- [CHANGELOG.md](./CHANGELOG.md)
|
||||
- [README.md](./README.md)
|
||||
|
||||
## Previous firmware
|
||||
|
||||
- **Version:** 6.0.0
|
||||
- **Release date:** 2026-01-27
|
||||
- **Target MCU:** Seeed XIAO ESP32‑C3
|
||||
- **Display:** 20x4 I²C LCD (PCF8574, default address `0x27`)
|
||||
- **API endpoint:** `https://api.energy-charts.info/price?bzn=SI`
|
||||
- **Resolution:** 15‑minute intervals, hourly averages for overview
|
||||
|
||||
## Highlights of v6.0.0
|
||||
|
||||
- Single **daily fetch** (on boot / after midnight) instead of hourly.
|
||||
- **NVS storage** of daily data for resilience to power outages.
|
||||
- Robust midnight rollover and retry logic:
|
||||
- First immediate fetch.
|
||||
- Up to 5 × 10‑minute retries.
|
||||
- Then top‑of‑hour retries until successful.
|
||||
- Prevents yesterday’s prices from ever being shown as today’s.
|
||||
- Secondary info menu extended with **NVS status** and clear version label.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 434 KiB |
Reference in New Issue
Block a user