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 | |
|---|---|---|---|
|
|
bbef4f2bc7 | ||
|
|
a99caded8e | ||
|
|
bc41d9bb96 | ||
|
|
b38b4ce7a5 | ||
|
|
5ff76a57f9 | ||
|
|
4ac7449163 | ||
|
|
d571e69942 | ||
|
|
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 | ||
|
|
4c5d7d2ebc | ||
|
|
b54c40cdf7 | ||
|
|
08db5888cb | ||
|
|
8f1f017232 | ||
|
|
189350e4fe | ||
|
|
827623112f | ||
|
|
fd5dad4cb5 | ||
|
|
25fc31fb3b | ||
|
|
aafe962c4c | ||
|
|
a57af528f8 | ||
|
|
ce52d6b9c0 |
File diff suppressed because it is too large
Load Diff
@@ -1160,7 +1160,7 @@ void displaySecondaryList() {
|
||||
snprintf(lines[16], sizeof(lines[16]), "energy-charts.info");
|
||||
snprintf(lines[17], sizeof(lines[17]), "dynamic electricity");
|
||||
snprintf(lines[18], sizeof(lines[18]), "price ticker v6.0 ");
|
||||
snprintf(lines[19], sizeof(lines[19]), "by Amir Toki^ 2025");
|
||||
snprintf(lines[19], sizeof(lines[19]), "by Legolas-2025");
|
||||
|
||||
// Render current window
|
||||
for (int i = 0; i < 4; i++) {
|
||||
|
||||
+517
-121
@@ -1,147 +1,543 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
All notable changes to this project are documented here.
|
||||
|
||||
This project follows a simple semantic versioning style:
|
||||
`MAJOR.MINOR.PATCH`
|
||||
## v7.1 - Negative Price Provider Fee (2026-04-06)
|
||||
|
||||
**Summary**
|
||||
|
||||
Added a separate configurable provider fee for negative spot prices, correctly
|
||||
modelling contracts where the provider's fee structure differs between positive
|
||||
and negative market prices.
|
||||
|
||||
### What changed
|
||||
|
||||
**New constant (in `// Price computation` globals block):**
|
||||
```cpp
|
||||
const float NEG_PRICE_COMPANY_FEE_PERCENTAGE = 30.0;
|
||||
```
|
||||
|
||||
**Price calculation is now:**
|
||||
|
||||
| Market price | Formula |
|
||||
|---|---|
|
||||
| Positive (`raw >= 0`) | `raw × (1 + POWER_COMPANY_FEE_PERCENTAGE/100) × (1 + VAT_PERCENTAGE/100)` |
|
||||
| Negative (`raw < 0`) | `raw × (1 - NEG_PRICE_COMPANY_FEE_PERCENTAGE/100) × (1 + VAT_PERCENTAGE/100)` |
|
||||
|
||||
The switch happens on the **raw API price** before any multiplier is applied.
|
||||
VAT is applied to both cases, consistent with net billing contracts where VAT
|
||||
is calculated on the monthly net sum (mathematically equivalent due to VAT
|
||||
being a linear multiplier).
|
||||
|
||||
**Key values for `NEG_PRICE_COMPANY_FEE_PERCENTAGE`:**
|
||||
|
||||
| Value | Meaning |
|
||||
|---|---|
|
||||
| `30.0` | Provider keeps 30%, pays you 70% of the negative market price |
|
||||
| `0.0` | Provider passes the full negative price to you (no fee deducted) |
|
||||
|
||||
**All 5 fee calculation sites updated:**
|
||||
|
||||
| Function | Purpose |
|
||||
|---|---|
|
||||
| `updateLeds()` | LED brightness reflects correct negative price |
|
||||
| `format15MinPrice()` | 15-min row values on primary display |
|
||||
| `displayPriceRow()` | Hourly price rows on primary display |
|
||||
| `displaySecondaryList()` | Daily average on secondary info screen |
|
||||
| Version strings | `connectToWiFi()` LCD and `displaySecondaryList()` credit line |
|
||||
|
||||
---
|
||||
|
||||
## [6.0.0] - 2026-01-27
|
||||
## v7.0 - Rolling 48-Hour Logic & Midnight Bridge (2026-04-03)
|
||||
|
||||
### Added
|
||||
- **Daily fetch + NVS storage (ESP32‑C3 NVS)**
|
||||
- Introduced non‑volatile storage for daily price data using the existing `Preferences` (NVS) subsystem under the `"my-ticker"` namespace.
|
||||
- Stored fields:
|
||||
- `data_day`, `data_mon`, `data_year` (calendar date of the data)
|
||||
- `data_prc` (raw JSON returned from the Energy‑Charts API)
|
||||
- `data_last_store` (UNIX timestamp of last successful store)
|
||||
- On boot, after Wi‑Fi and NTP time sync:
|
||||
- The ticker checks NVS for stored data.
|
||||
- If the stored date matches **today**, the JSON is deserialized and used directly (no initial API call needed).
|
||||
- If the stored date is **not** today (or invalid), the data is ignored and the ticker behaves as if no data is available yet.
|
||||
**Summary**
|
||||
|
||||
- **Midnight rollover + daily fetch logic**
|
||||
- The system now performs **one daily fetch per day**, instead of hourly:
|
||||
- **On boot**: fetch only if NVS does not already contain valid data for today.
|
||||
- **After local midnight**: invalidate the previous day’s data and trigger a new fetch for the new day.
|
||||
- As soon as a local‑time day change is detected:
|
||||
- `isTodayDataAvailable` is set to `false`.
|
||||
- The display state is forced to `"No data for today"` (`NO_DATA_OFFSET`).
|
||||
- The white LED is turned off (same behavior as “no data”).
|
||||
- A midnight fetch phase is activated.
|
||||
This is the **"Golden Build"** for this hardware platform. It combines all the hardware stability fixes from v6.2.4 with a revolutionary new 48-hour price prediction system that eliminates the "1 AM fetch gap" problem that plagues most electricity tickers.
|
||||
|
||||
- **Midnight fetch retry strategy**
|
||||
- When midnight is detected:
|
||||
- First fetch is scheduled **immediately**.
|
||||
- If it fails (HTTP error or JSON/“day mismatch”), the system stays in `"No data for today"` and the LEDs remain off.
|
||||
- Retry policy during midnight phase:
|
||||
1. Retry every **10 minutes**, up to **5 attempts**.
|
||||
2. If still unsuccessful, retry only once **at the top of each following hour** until fresh data for the new day is retrieved.
|
||||
- As soon as a successful fetch for today is obtained:
|
||||
- `isTodayDataAvailable = true`.
|
||||
- The enforced `NO_DATA_OFFSET` state is cleared back to `CURRENT_PRICES`.
|
||||
- The white LED resumes indicating the current 15‑minute segment price.
|
||||
- The entire successful JSON payload and metadata are saved back into NVS.
|
||||
### New Features
|
||||
|
||||
- **Improved resilience after power loss**
|
||||
- If the device reboots during the same day:
|
||||
- It can **restore and reuse** the last successfully stored daily data from NVS.
|
||||
- This avoids unnecessary API calls and gives a fast “warm start” after power outages.
|
||||
- If the device reboots on the **next** day:
|
||||
- Yesterday’s stored data is **not** used for display (to avoid confusion).
|
||||
- The display starts in `"No data for today"` until the first successful fetch.
|
||||
#### 1. Dual-Buffer NVS System
|
||||
|
||||
- **Extended secondary (info) menu**
|
||||
- The secondary info screen (reachable via double‑click on the button) is extended from 16 to **20 lines**, still shown as 4‑line pages.
|
||||
- Existing info retained:
|
||||
- Current time and date.
|
||||
- Last successful update timestamp.
|
||||
- Daily average price.
|
||||
- Wi‑Fi RSSI and device IP.
|
||||
- API success ratio.
|
||||
- Uptime.
|
||||
- Credits and version information.
|
||||
- **New NVS status section** (first defined around lines 12–15, later rearranged in your version):
|
||||
- Shows:
|
||||
- NVS data status (`NVS status:`)
|
||||
- Stored data date (`Data day: DD.MM.YYYY` or `Data day: none`)
|
||||
- Last store timestamp (`Last save: DD.MM.YY` or `Last save: none`)
|
||||
- Basic quick status:
|
||||
- `NVS: OK (today)` – valid data for today loaded from NVS
|
||||
- `NVS: old data` – NVS has data, but not from today (ignored for display)
|
||||
- `NVS: empty` – no stored price data present
|
||||
The ticker now stores "Today" and "Tomorrow" data independently in NVS, allowing seamless display of up to 47 hours of price data.
|
||||
|
||||
### Changed
|
||||
- **API call strategy**
|
||||
- Removed regular **top‑of‑hour** automatic fetching.
|
||||
- API calls now occur only:
|
||||
- Once at boot (if NVS has no valid data for today).
|
||||
- After midnight (with the retry strategy described above).
|
||||
- On user‑initiated **long‑press** (manual refresh).
|
||||
- This significantly reduces network load while keeping behavior safe and predictable.
|
||||
**New NVS Keys:**
|
||||
- `data_prc_t` – Raw JSON payload for tomorrow's prices
|
||||
- `data_store_t` – Unix timestamp when tomorrow's data was stored
|
||||
|
||||
- **UI & behavior around day boundaries**
|
||||
- At midnight / day change:
|
||||
- Display is immediately set to:
|
||||
- Line 0: `No data for today`
|
||||
- Line 1: `Press & hold to`
|
||||
- Line 2: `refresh manually`
|
||||
- White LED is turned off (no price indication until new data arrives).
|
||||
- Once data for the new day is available:
|
||||
- The ticker returns to the usual 15‑minute detail + hourly display mode.
|
||||
**New Global Variables:**
|
||||
- `StaticJsonDocument<Config::JSON_BUFFER_SIZE> docTomorrow` – Tomorrow's price data buffer
|
||||
- `bool isTomorrowDataAvailable` – Flag indicating tomorrow's data availability
|
||||
- `float averagePriceTomorrow` – Tomorrow's daily average price
|
||||
- `int lowestPriceIndexTomorrow` – Index of tomorrow's lowest price hour
|
||||
- `int highestPriceIndexTomorrow` – Index of tomorrow's highest price hour
|
||||
|
||||
- **Versioning and info screens**
|
||||
- Version bumped to **v6.0** and reflected in:
|
||||
- Source file header comment.
|
||||
- Secondary menu text (`price ticker v6.0`).
|
||||
- New version documentation (`VERSION.md` / `CHANGELOG.md` / `README.md`).
|
||||
#### 2. The Midnight Bridge (Rollover Logic)
|
||||
|
||||
- **Long‑press threshold (in repository version)**
|
||||
- Long‑press detection threshold extended from 2s to **3s** (in your repository copy) to avoid accidental manual refreshes.
|
||||
**Problem:**
|
||||
Most electricity tickers fail at midnight because they rely on slow API calls to fetch new data. The Energy-Charts API typically doesn't publish next-day data until 1-2 AM, leaving users with a "No Data" screen for hours.
|
||||
|
||||
### Fixed / Ensured
|
||||
- Yesterday’s data is **never shown** as if it were today’s:
|
||||
- On boot: previous‑day NVS data is ignored for display.
|
||||
- After midnight: in‑RAM data is invalidated and the UI explicitly shows `"No data for today"` until fresh data is fetched.
|
||||
- LED state is always consistent with the availability of **today’s** data:
|
||||
- LED off → no today data (or negative price).
|
||||
- LED patterns → valid today data and a positive price in the current 15‑minute interval.
|
||||
**Solution:**
|
||||
The Midnight Bridge detects the moment the local clock moves from 23:59:59 to 00:00:00 and instantly promotes the pre-fetched "Tomorrow" data to become "Today" data.
|
||||
|
||||
**Implementation (in `loop()`):**
|
||||
```cpp
|
||||
if (daycheck->tm_mday != trackedDay) {
|
||||
// Midnight rollover detected
|
||||
if (isTomorrowDataAvailable) {
|
||||
// Swap tomorrow to today instantly
|
||||
doc = docTomorrow;
|
||||
docTomorrow.clear();
|
||||
|
||||
// Update all statistics
|
||||
averagePrice = averagePriceTomorrow;
|
||||
lowestPriceIndex = lowestPriceIndexTomorrow;
|
||||
highestPriceIndex = highestPriceIndexTomorrow;
|
||||
|
||||
// Save to NVS and clear tomorrow slot
|
||||
serializeJson(doc, payload);
|
||||
saveDataToNVS(payload, false);
|
||||
clearTomorrowNVS();
|
||||
|
||||
timeOffsetHours = 0;
|
||||
displayPrices();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**NVS Power-Failure Protection:**
|
||||
Immediately after the swap, the new "Today" data is serialized and saved to NVS. If power is cut at 00:05 AM, the device reboots with correct data already loaded.
|
||||
|
||||
#### 3. Smart Fetching & Tomorrow's Data
|
||||
|
||||
**Automatic Tomorrow Fetch:**
|
||||
After 14:00 (2 PM) local time, the ticker automatically fetches tomorrow's data using the `&start=YYYY-MM-DD` parameter:
|
||||
|
||||
```cpp
|
||||
void fetchAndProcessData(bool fetchTomorrow) {
|
||||
String url = api_url;
|
||||
if (fetchTomorrow) {
|
||||
time_t now = time(nullptr);
|
||||
now += 24 * 3600; // Add 24 hours
|
||||
struct tm* tmr = localtime(&now);
|
||||
char dateStr[20];
|
||||
snprintf(dateStr, sizeof(dateStr), "%04d-%02d-%02d",
|
||||
tmr->tm_year + 1900, tmr->tm_mon + 1, tmr->tm_mday);
|
||||
url += "&start=";
|
||||
url += dateStr;
|
||||
}
|
||||
// ... HTTP request follows
|
||||
}
|
||||
```
|
||||
|
||||
**Smart Scheduling (`handleDataFetching()`):**
|
||||
```cpp
|
||||
struct tm* ti = localtime(&now);
|
||||
// Priority 1: If it's after 14:00 and we don't have tomorrow's data yet
|
||||
if (ti->tm_hour >= 14 && !isTomorrowDataAvailable) {
|
||||
fetchAndProcessData(true); // Fetch tomorrow
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Seamless 48H Scrolling
|
||||
|
||||
**Extended Display Range:**
|
||||
When tomorrow's data is available, users can scroll up to **47 hours ahead**:
|
||||
|
||||
```cpp
|
||||
int maxOffsetLimit = isTomorrowDataAvailable ? 47 : 23;
|
||||
if (displayStartHourOffset > maxOffsetLimit)
|
||||
displayStartHourOffset %= (maxOffsetLimit + 1);
|
||||
```
|
||||
|
||||
**Visual Distinction for Tomorrow:**
|
||||
Future hours are marked with `HH:>>` to clearly distinguish tomorrow's prices from today's:
|
||||
|
||||
```cpp
|
||||
if (showTomorrow) {
|
||||
snprintf(buffer, sizeof(buffer), "%02d:>>", localHourIndex);
|
||||
} else {
|
||||
snprintf(buffer, sizeof(buffer), "%02d:00", localHourIndex);
|
||||
}
|
||||
```
|
||||
|
||||
**Correct Min/Max Indicators:**
|
||||
The code correctly uses tomorrow's statistics when displaying future hours:
|
||||
|
||||
```cpp
|
||||
int lowIdx = showTomorrow ? lowestPriceIndexTomorrow : lowestPriceIndex;
|
||||
int highIdx = showTomorrow ? highestPriceIndexTomorrow : highestPriceIndex;
|
||||
```
|
||||
|
||||
#### 5. Hardware Stability (Preserved from v6.2.4)
|
||||
|
||||
All v6.2.4 stability fixes remain intact:
|
||||
|
||||
- **State-Based Refresh**: Display updates exactly at 00, 15, 30, and 45 minutes past the hour, even if the CPU is busy
|
||||
- **LED Indicators Pinned to Current**: White LED and built-in LED reflect actual current prices, regardless of what the user is viewing on screen
|
||||
|
||||
### Technical Implementation Details
|
||||
|
||||
#### Dual-Buffer Display Helpers
|
||||
|
||||
**`display15MinuteDetails(int row, int totalHourOffset)`:**
|
||||
- Now accepts `totalHourOffset` (0-47) instead of just hour index
|
||||
- Automatically selects correct buffer (`doc` or `docTomorrow`) based on offset
|
||||
- Shows past segments ("> ") for current hour
|
||||
|
||||
**`displayPriceRow(int row, int totalHourOffset, bool isCurrentHourRow)`:**
|
||||
- Extended to handle tomorrow's data with visual indicators
|
||||
- Correctly applies hour suppression logic only to today's hours
|
||||
|
||||
#### NVS Persistence Updates
|
||||
|
||||
**`saveDataToNVS(const String& rawJson, bool isTomorrow)`:**
|
||||
```cpp
|
||||
if (isTomorrow) {
|
||||
preferences.putString("data_prc_t", rawJson);
|
||||
preferences.putULong("data_store_t", (unsigned long)now);
|
||||
} else {
|
||||
// Original "today" save logic
|
||||
preferences.putInt("data_day", timeinfo.tm_mday);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**`loadDataFromNVS()`:**
|
||||
- Now loads both today and tomorrow data from NVS
|
||||
- Validates and processes both buffers independently
|
||||
|
||||
#### API Date Validation
|
||||
|
||||
The `processJsonData()` function now validates data against the correct target date:
|
||||
|
||||
```cpp
|
||||
time_t targetTime = time(nullptr);
|
||||
if (isTomorrow) targetTime += 24 * 3600;
|
||||
struct tm* targetDayTm = localtime(&targetTime);
|
||||
|
||||
bool sameDate = (lastDataTm->tm_mday == targetDayTm->tm_mday &&
|
||||
lastDataTm->tm_mon == targetDayTm->tm_mon &&
|
||||
lastDataTm->tm_year == targetDayTm->tm_year);
|
||||
```
|
||||
|
||||
### Why This Is the "Golden Build"
|
||||
|
||||
| Feature | v6.2.4 | v7.0 |
|
||||
|---------|--------|------|
|
||||
| Display hours ahead | 23 hours (today only) | 47 hours (today + tomorrow) |
|
||||
| Midnight transition | "No Data" until API updates | Seamless swap from buffer |
|
||||
| Power failure resilience | Relies on API availability | NVS contains valid data |
|
||||
| Visual tomorrow indication | None | `HH:>>` format |
|
||||
| Tomorrow min/max markers | N/A | Correct indices |
|
||||
| Fetch strategy | Once per day | Smart: today + tomorrow after 14:00 |
|
||||
|
||||
### User Experience: Behavior & Display States
|
||||
|
||||
The display changes based on which data buffer is being used and the status of the fetch:
|
||||
|
||||
| **State** | **Display Output** | **LED Behavior** |
|
||||
|----------|-------------------|-----------------|
|
||||
| **Normal (Today)** | Shows current prices and 15-min details. Hours are marked as HH:00. | White LED reflects current price status (Breathe, Solid, or Blink). |
|
||||
| **Scrolling (Tomorrow)** | Future prices are displayed. Hours are marked with HH:>> to indicate "Tomorrow". | **Pinned to Today:** The LEDs continue showing the _actual current_ price status even while you scroll through tomorrow. |
|
||||
| **No Data** | Displays: "No data for today, Press & hold to, refresh manually." | White LED is turned **OFF** to avoid misleading price signals. |
|
||||
| **Connecting** | "Elec. Rate SI v7.0" followed by "Connecting..." and progress dots. | Built-in LED is **OFF** until connection is established. |
|
||||
|
||||
### API Call Intervals & Retry Strategy (v7.0)
|
||||
|
||||
The exact API call intervals in version 7.0 vary depending on the device's state, data availability, and time of day:
|
||||
|
||||
#### Primary Scheduling (Daily Fetch)
|
||||
|
||||
The device aims to maintain a rolling 48-hour data window by fetching today's and tomorrow's data at specific times:
|
||||
|
||||
- **Initial Boot:** An API call is attempted immediately upon startup and time synchronization.
|
||||
- **Tomorrow's Data (Smart Fetching):** Starting at **14:00 (2 PM) local time**, the device begins checking for the next day's prices. It will attempt to fetch this data periodically until successful.
|
||||
- **Midnight Rollover:** At exactly **00:00:00**, the device "promotes" tomorrow's data to the today buffer. If tomorrow's data was already successfully fetched and stored, **no API call is needed at midnight**.
|
||||
|
||||
#### Retry Logic (Exponential Backoff)
|
||||
|
||||
If a scheduled API call fails (e.g., due to a temporary server error or WiFi glitch), the device uses a safety-oriented retry interval:
|
||||
|
||||
- **Max Retries:** 5 attempts (`HTTP_GET_RETRY_MAX`)
|
||||
- **Interval Formula:** Uses a backoff factor of **2** (`HTTP_GET_BACKOFF_FACTOR`)
|
||||
- **Typical Progression:** After a failure, it waits a short period, then doubles that wait time for each subsequent failure until the maximum retry count is reached
|
||||
|
||||
#### "Midnight Phase" Recovery
|
||||
|
||||
If the device reaches midnight but **does not** have tomorrow's data ready (meaning the afternoon fetches failed), it enters a high-priority state called `midnightPhaseActive`:
|
||||
|
||||
- **Interval:** It bypasses the standard daily schedule and retries the API **more aggressively** (initially every minute).
|
||||
- **Goal:** To clear the "No Data" screen and restore the price display as quickly as possible once the energy provider's server updates.
|
||||
|
||||
#### Background Monitoring
|
||||
|
||||
While not a full API call, the device performs these checks constantly:
|
||||
|
||||
- **Loop Pacing:** The main system loop runs every **100ms** to check if it's time for a scheduled fetch.
|
||||
- **Display Refresh:** The screen logic checks the time every loop but only refreshes the UI every **15 minutes** (at :00, :15, :30, :45) to match the price data intervals.
|
||||
|
||||
---
|
||||
|
||||
## [5.5.0] - 2025-10-27
|
||||
## v6.2.4 - Exact-boundary display refresh bug (2026-04-01):
|
||||
|
||||
> First 15‑minute detail version and DST‑fixed base, which v6.0 builds upon.
|
||||
**Summary**
|
||||
|
||||
### Added
|
||||
- **15‑minute detail mode**:
|
||||
- Primary display shows:
|
||||
- Current hour average.
|
||||
- Four 15‑minute prices in compact format (`XX XX XX XX`).
|
||||
- Next 2 hours’ averages.
|
||||
- LED behavior switched from hourly‑based to **15‑minute‑segment based**.
|
||||
- **Compact price format**:
|
||||
- 15‑minute values shown as 2‑digit hundredths (e.g. `+99 -07 24 11`).
|
||||
- **DST / timezone handling**:
|
||||
- `configTzTime()` with `TZ_CET_CEST` for automatic CET ↔ CEST switching.
|
||||
- **Aggressive retry and state enforcement**:
|
||||
- Improved logic for:
|
||||
- Handling stale data.
|
||||
- Enforcing `"No data for today"` state when needed.
|
||||
- Aggressively retrying fetches after HTTP/JSON failures.
|
||||
- **UI and usability tweaks**:
|
||||
- Button:
|
||||
- Single click: scroll primary list (hourly view).
|
||||
- Double click: switch primary/secondary list.
|
||||
- Long press: manual refresh.
|
||||
- Secondary info list:
|
||||
- Date/time, last update, daily average, Wi‑Fi and API stats, uptime, credits.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Older versions
|
||||
## v6.2.3 - State-based display refresh logic fix (2026-04-01):
|
||||
|
||||
Earlier versions (≤5.4) introduced the basic ticker behavior, LCD layout, Energy‑Charts API integration, and the initial presence sensor / backlight / LED logic.
|
||||
**Summary**
|
||||
|
||||
Those versions are not fully documented here, but key user‑visible behavior is maintained in v6.0 unless explicitly noted in this changelog.
|
||||
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** 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)
|
||||
|
||||
**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.
|
||||
|
||||
### Fixed: Stuck on NO_DATA_OFFSET After Midnight
|
||||
|
||||
**Problem (v6.0.0):**
|
||||
|
||||
- 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):**
|
||||
|
||||
- `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.
|
||||
|
||||
---
|
||||
|
||||
## v6.0.0 – NVS Storage & Daily Fetch (2026‑01‑27)
|
||||
|
||||
**Summary**
|
||||
|
||||
First major redesign focused on reducing API traffic and improving resilience using non‑volatile storage.
|
||||
|
||||
### New
|
||||
|
||||
- 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.
|
||||
|
||||
---
|
||||
|
||||
## v5.x – Earlier Versions
|
||||
|
||||
Earlier versions (v5.x and below) had:
|
||||
|
||||
- No NVS‑based caching of daily API data.
|
||||
- More frequent API calls (e.g., hourly refresh pattern).
|
||||
- Less robust handling of DST and daily boundaries.
|
||||
|
||||
For exact details, see older `.ino` files and their header comments in this repository.
|
||||
|
||||
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
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;
|
||||
+135
-15
@@ -2,23 +2,143 @@
|
||||
|
||||
## Current 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
|
||||
- **Version:** 7.1
|
||||
- **Release date:** 2026-04-06
|
||||
- **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
|
||||
## Highlights of v7.1
|
||||
|
||||
- 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.
|
||||
### Negative Price Provider Fee
|
||||
|
||||
Added a separate provider fee for negative spot prices via a new constant
|
||||
`NEG_PRICE_COMPANY_FEE_PERCENTAGE`. Positive and negative market prices now
|
||||
use independent fee multipliers, correctly modelling contracts where the
|
||||
provider's fee structure differs between the two cases.
|
||||
|
||||
**Price calculation:**
|
||||
|
||||
| Market price | Formula |
|
||||
|---|---|
|
||||
| Positive (`raw >= 0`) | `raw × (1 + POWER_COMPANY_FEE_PERCENTAGE/100) × (1 + VAT_PERCENTAGE/100)` |
|
||||
| Negative (`raw < 0`) | `raw × (1 - NEG_PRICE_COMPANY_FEE_PERCENTAGE/100) × (1 + VAT_PERCENTAGE/100)` |
|
||||
|
||||
VAT is applied to both, consistent with net billing where VAT is calculated
|
||||
on the monthly net sum (linear equivalence applies).
|
||||
|
||||
All 5 fee calculation sites updated: `updateLeds()`, `format15MinPrice()`,
|
||||
`displayPriceRow()`, `displaySecondaryList()` (daily average), and version strings.
|
||||
|
||||
See `CHANGELOG.md` for full implementation details.
|
||||
|
||||
---
|
||||
|
||||
## Highlights of v7.0
|
||||
|
||||
### 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:
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 434 KiB |
Reference in New Issue
Block a user