Compare commits

..
39 Commits
Author SHA1 Message Date
Amir 4e9b9495cd Update README for version 6.2.3 changes
Updated version number to 6.2.3 and added highlights for the new version.
2026-04-01 00:00:46 +02:00
Amir 1b17a7b80c Bump version to 6.2.3 and update release notes
Updated version to 6.2.3 with release date 2026-04-01. Added highlights for version 6.2.3 including a bug fix for state-based display refresh logic.
2026-03-31 23:59:01 +02:00
Amir 91dea24da8 Revise CHANGELOG for version 6.2.3 updates
Updated changelog to document the fix for state-based display refresh logic in version 6.2.3. The refresh logic now checks for hour changes instead of minute marks to ensure timely updates.
2026-03-31 23:55:27 +02:00
Amir 52c3727d39 Update to v6.2.3 - State-based display refresh logic
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.
  - Updated version strings throughout the UI.
2026-03-31 23:46:33 +02:00
Amir 824170a6d1 Update VERSION.md for version 6.2.2
Updated version information from 6.2.1 to 6.2.2, added highlights for the new version, and included bug fix details.
2026-03-31 21:38:45 +02:00
Amir dfb11004ba Update README for version 6.2.2 release
Updated version number to 6.2.2 and added highlights for the new version.
2026-03-31 21:36:15 +02:00
Amir 739b21609e Revise CHANGELOG for version 6.2.2 updates
Fixed a bug causing blank lines in the display for current prices and hour. Updated logic to ensure rows always show relevant information.
2026-03-31 21:32:19 +02:00
Amir 5ef53916ea Update to v6.2.2 - Screen blank lines issue fix
Update to v6.2.2 - Screen blank lines issue fix after v6.2.0 DST Fix

  - 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
2026-03-31 21:13:57 +02:00
Amir b55a7b4945 Update README for version 6.2.1 and highlights
Updated version number to 6.2.1 and added highlights for the latest change: a critical fix for current interval display issues.
2026-03-29 18:49:43 +02:00
Amir d7e27b4f56 Bump version to 6.2.1 and update highlights
Updated version from 6.2.0 to 6.2.1 and added highlights for the new version.
2026-03-29 18:44:33 +02:00
Amir 614be25a1f Revise CHANGELOG for version 6.2.1 updates
Fixed a bug in the price display by correcting the function to find the current 15-minute interval instead of the next one.
2026-03-29 18:43:05 +02:00
Amir 1e5fa661f0 Update to v6.2.1 - Screen refresh issue fix after v6.2.0 DST Fix
Fixed a bug where the display was showing prices one hour ahead of the current time.
2026-03-29 18:39:06 +02:00
Amir 13da0e0c87 Revise README for version 6.2.0 updates
Updated README for version 6.2.0, fixing DST handling and enhancing features.
2026-03-29 17:45:10 +02:00
Amir 8543493ac0 v6.2.0 - Fix DST handling and improve price lookup logic
This release fixes a critical bug related to price display during Daylight Saving Time transitions, implementing timestamp-based lookups for accurate price retrieval. It also includes new and updated functions for better handling of price indices and LED control.
2026-03-29 17:29:43 +02:00
Amir eb5ef5954b Bump version to 6.2.0 with DST improvements
Updated version information to 6.2.0 with critical DST fix and new functions.
2026-03-29 17:28:31 +02:00
Amir 2fff8296c5 Update to v6.2.0 - DST Fix
CRITICAL FIX: DST (Daylight Saving Time) handling is now fully fixed for all days.
2026-03-29 17:25:48 +02:00
Amir 3f09e475fc Update README for version 6.1.2 changes
Add image to README for project visualization.
2026-03-11 20:47:57 +01:00
Amir eb34d07ccf Add files via upload 2026-03-11 20:45:18 +01:00
Amir dcc434f7e4 Update README for version 6.1.2 changes 2026-03-11 20:19:54 +01:00
Amir 6c7a66be8e Bump version to 6.1.2 and update highlights
Updated version information to 6.1.2 with release date 2026-03-11. Added highlights and fixes for the new version.
2026-03-11 20:05:01 +01:00
Amir e1ad1b01c3 Fix LED indicator issues in v6.1.2 release
Restores LED indicator functionality by ensuring PWM control for the white LED, fixing issues from v6.1.1 where the LED remained dimly lit and patterns were inconsistent.
2026-03-11 20:04:13 +01:00
Amir f77f50f1d4 Update to v6.1.2 - broken LED indicator fix
VERSION 6.1.2 CHANGES (2026-03-11):
  ----------------------------------
FIX: Restore correct white LED price indicator behavior (to mach the one in prrevious v6.1) on ESP32 (XIAO ESP32C3) by avoiding mixing analogWrite() (LEDC PWM) and digitalWrite() on the same pin. When LED should be OFF we now use analogWrite(whiteLedPin, 0) (instead of digitalWrite LOW). When LED should be fully ON we now use analogWrite(whiteLedPin, 255) (instead of digitalWrite HIGH). Blink / double-blink patterns now toggle between PWM 0 and 255 so the LED is truly off when backlight/presence logic turns LEDs off (no more dimly-lit LED).

NOTE: This version is identical to v6.1.1 except for: updateLeds() LED control fix (PWM-only on whiteLedPin), UI version strings ("v6.1.2") & header comment version/date.
2026-03-11 20:01:08 +01:00
Amir 45cabc17dd Update to v6.1.2 - broken LED indicator fix
VERSION 6.1.2 CHANGES (2026-03-12):
  ----------------------------------
Fixed LED indicator not working properly (constantly dimly lit, not responding to price patterns, staying on when LCD backlight turns off). Root cause: Mixing analogWrite() and digitalWrite() on ESP32 causes PWM channel to remain active even after digitalWrite(LOW), resulting in dim LED. LED now properly turns off when backlight is off (areLedsOn = false). LED now correctly shows breathing/blinking patterns based on price thresholds.

  VERSION 6.1.1 CHANGES (2026-03-07):
  ----------------------------------
Fixed daily lowest/highest hourly indicator (and daily average) now correctly considers ALL hourly averages, including negative values and 0.0 (which can be real prices). The daily average is now computed using the number of valid hours actually present in the dataset (validHourCount), instead of always dividing by 24.

  NOTE:
  - This version includes all fixes from v6.1.1 plus the LED fix.
2026-03-11 19:50:48 +01:00
Amir fedef66ae0 Update to v6.1.1 - daily lowest hourly indicator fix
NOTE:
  - This version is identical to v6.1 except for:
      * processJsonData() min/max/average fix
      * UI version strings ("v6.1.1")
      * header comment version/date
2026-03-11 18:48:55 +01:00
Amir 65fdb14cec Fix LED indicator stuck on; bump to v6.1.2 2026-03-11 18:32:23 +01:00
Amir 3da3dec344 Wiring diagram for XIAO ESP32-C3 connections 2026-03-07 11:31:14 +01:00
Amir 6a6cf113b3 Wiring diagram and peripheral details
Wiring diagram and notes for the Electricity Price Ticker project, including optional peripherals and GPIO connections.
2026-03-07 11:17:58 +01:00
Amir 26b1a309e1 Add files via upload 2026-03-07 11:05:41 +01:00
Amir 4c5d7d2ebc Price ticker v6.1.1 - lowest price indicator update
NOTE:  This version is identical to v6.1 except for the FIX: Daily lowest/highest hourly indicator (and daily average) now correctly considers ALL hourly averages, including negative values and 0.0 (which can be real prices).
2026-03-07 10:40:18 +01:00
Amir b54c40cdf7 Delete ESP32_stanadlone_electricity_ticker_6_1_1.ino 2026-03-07 10:29:47 +01:00
Amir 08db5888cb Update VERSION.md for firmware 6.1.1 release
Updated version information for firmware 6.1.1, including fixes for price markers and daily average calculations.
2026-03-07 10:19:23 +01:00
Amir 8f1f017232 Update README for firmware version 6.1.1
Updated README to reflect changes in firmware version from 6.1.0 to 6.1.1, including fixes for daily low/high hourly markers and other enhancements.
2026-03-07 10:17:10 +01:00
Amir 189350e4fe Fix daily min/max calculations for negative and zero prices
This release addresses a bug in the daily min/max price calculations, ensuring that negative and zero prices are now included. It also corrects the daily average computation by considering all valid hours.
2026-03-07 10:08:48 +01:00
Amir 827623112f Create new sketch file ESP32_stanadlone_electricity_ticker_6_1_1.ino by copying and updating from v6.1 2026-03-07 10:01:08 +01:00
Amir fd5dad4cb5 Delete 20251005_electricity_ticker_10_5.5_latest_scrl23_old.ino 2026-02-01 22:11:16 +01:00
Amir 25fc31fb3b Revise README for version 6.1.0
Updated project details and version information in README.
2026-02-01 22:07:41 +01:00
Amir aafe962c4c Price ticker v6.1.0 - latest 2026-02-01 21:50:38 +01:00
Amir a57af528f8 Update CHANGELOG for v6.1.0 release
Documented the changes and fixes in version 6.1.0, including improvements to market day detection and retry logic after midnight.
2026-01-31 01:59:03 +01:00
Amir ce52d6b9c0 VERSION 6.0 CHANGES (2026-01-27)
Update credits in electricity ticker.
2026-01-28 01:17:15 +01:00
14 changed files with 13193 additions and 1862 deletions
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++) {
+231 -125
View File
@@ -1,147 +1,253 @@
# 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`
## 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.
---
## [6.0.0] - 2026-01-27
## v6.2.2 - Display blank lines issue fix (2026-03-31):
### Added
- **Daily fetch + NVS storage (ESP32C3 NVS)**
- Introduced nonvolatile 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 EnergyCharts API)
- `data_last_store` (UNIX timestamp of last successful store)
- On boot, after WiFi 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 days data and trigger a new fetch for the new day.
- As soon as a localtime 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.
Fixed a bug where the display was showing blank lines
- **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 15minute segment price.
- The entire successful JSON payload and metadata are saved back into NVS.
### Problem: Sometimes rows 0 and 1 (current 15-min prices and current hour) were blank
- **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:
- Yesterdays stored data is **not** used for display (to avoid confusion).
- The display starts in `"No data for today"` until the first successful fetch.
**Cause:** The "hour suppression" logic was hiding the current hour unexpectedly
- **Extended secondary (info) menu**
- The secondary info screen (reachable via doubleclick on the button) is extended from 16 to **20 lines**, still shown as 4line pages.
- Existing info retained:
- Current time and date.
- Last successful update timestamp.
- Daily average price.
- WiFi RSSI and device IP.
- API success ratio.
- Uptime.
- Credits and version information.
- **New NVS status section** (first defined around lines 1215, 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
### Solution:
### Changed
- **API call strategy**
- Removed regular **topofhour** 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 userinitiated **longpress** (manual refresh).
- This significantly reduces network load while keeping behavior safe and predictable.
- **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 15minute detail + hourly display mode.
- **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`).
- **Longpress threshold (in repository version)**
- Longpress detection threshold extended from 2s to **3s** (in your repository copy) to avoid accidental manual refreshes.
### Fixed / Ensured
- Yesterdays data is **never shown** as if it were todays:
- On boot: previousday NVS data is ignored for display.
- After midnight: inRAM 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 **todays** data:
- LED off → no today data (or negative price).
- LED patterns → valid today data and a positive price in the current 15minute interval.
- 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
---
## [5.5.0] - 2025-10-27
## v6.2.1 Current Interval Fix (20260329)
> First 15minute detail version and DSTfixed base, which v6.0 builds upon.
**Summary**
### Added
- **15minute detail mode**:
- Primary display shows:
- Current hour average.
- Four 15minute prices in compact format (`XX XX XX XX`).
- Next 2 hours averages.
- LED behavior switched from hourlybased to **15minutesegment based**.
- **Compact price format**:
- 15minute values shown as 2digit 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, WiFi and API stats, uptime, credits.
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 ✅
---
## Older versions
## v6.2.0 DST (Daylight Saving Time) Handling Fixed (20260329)
Earlier versions (≤5.4) introduced the basic ticker behavior, LCD layout, EnergyCharts API integration, and the initial presence sensor / backlight / LED logic.
**Summary**
Those versions are not fully documented here, but key uservisible behavior is maintained in v6.0 unless explicitly noted in this changelog.
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) (20260311)
**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 **PWMonly** inside `updateLeds()`:
- Use `analogWrite(whiteLedPin, 0)` instead of `digitalWrite(whiteLedPin, LOW)`.
- Use `analogWrite(whiteLedPin, 255)` instead of `digitalWrite(whiteLedPin, HIGH)`.
- Blink/doubleblink 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 (20260307)
**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×15minute entries), not based on value sign.
- Includes **all values** (negative, zero, positive).
---
## v6.1.0 Midnight Fetch & Market Day Fix (20260130)
**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 (20260127)
**Summary**
First major redesign focused on reducing API traffic and improving resilience using nonvolatile storage.
### New
- NVS namespace `"my-ticker"` with WiFi 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.
- Aftermidnight: NVS is overwritten with each successful newday dataset.
---
## v5.x Earlier Versions
Earlier versions (v5.x and below) had:
- No NVSbased 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
+92
View File
@@ -0,0 +1,92 @@
# Hardware Wiring Diagram (Seeed XIAO ESP32C3 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 ESP32C3 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 youre 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;
+595 -242
View File
@@ -1,309 +1,662 @@
# Electricity Price Ticker for XIAO ESP32C3 (Slovenia, EnergyCharts)
![Standalone ESP32 Electricity Price Ticker](device_photo.jpg)
# Electricity Price Ticker for XIAO ESP32C3 (EnergyCharts)
This project is an ArduinoIDEfriendly firmware for the **Seeed XIAO ESP32C3** that:
- Connects to WiFi.
- Fetches dayahead electricity prices for **Slovenia** from [EnergyCharts.info](https://energy-charts.info).
- Computes final consumer prices (including configurable fees + VAT).
- Fetches **dayahead electricity prices** from [EnergyCharts.info](https://energy-charts.info).
- Computes final consumer prices (including configurable powercompany fee + VAT).
- Displays current and upcoming prices on a **20x4 I²C 2004 LCD**.
- Uses an LED and 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.
The code currently implements **Version 6.0** of the ticker, focusing on:
The latest sketch implements **Version 6.2.3**.
---
## Version Highlights
### 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 for resilience to reboots.
- Automatic CET/CEST handling.
- Robust **NVS storage** of daily price data.
- Correct **CET/CEST** handling.
- Resilient **aftermidnight refresh** (no more getting stuck on "No data for today").
- Preserved UI and button behavior from v5.5.
All available bidding zones (modify in the code):
- AT - Austria
- BE - Belgium
- BG - Bulgaria
- CH - Switzerland
- CZ - Czech Republic
- DE-LU - Germany, Luxembourg
- DE-AT-LU - Germany, Austria, Luxembourg
- DK1 - Denmark 1
- DK2 - Denmark 2
- EE - Estionia
- ES - Spain
- FI - Finland
- FR - France
- GR - Greece
- HR - Croatia
- HU - Hungary
- IT-Calabria - Italy Calabria
- IT-Centre-North - Italy Centre North
- IT-Centre-South - Italy Centre South
- IT-North - Italy North
- IT-SACOAC - Italy Sardinia Corsica AC
- IT-SACODC - Italy Sardinia Corsica DC
- IT-Sardinia - Italy Sardinia
- IT-Sicily - Italy Sicily
- IT-South - Italy South
- LT - Lithuania
- LV - Latvia
- ME - Montenegro
- NL - Netherlands
- NO1 - Norway 1
- NO2 - Norway 2
- NO2NSL - Norway North Sea Link
- NO3 - Norway 3
- NO4 - Norway 4
- NO5 - Norway 5
- PL - Poland
- PT - Portugal
- RO - Romania
- RS - Serbia
- SE1 - Sweden 1
- SE2 - Sweden 2
- SE3 - Sweden 3
- SE4 - Sweden 4
- SI - Slovenia
- SK - Slovakia
---
## 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.
---
## Features
## Bidding Zones (BZN) / Region Selection
### Core display & pricing
The firmware currently uses:
- Data source: [EnergyCharts.info dayahead price API](https://api.energy-charts.info/price?bzn=SI) (bidding zone = `SI`).
- Resolution:
- Prices in **15minute intervals** (4 per hour).
- Display shows:
- Current hour:
- Hourly average.
- Four 15minute prices in compact format (`XX XX XX XX`).
- Next 2 hours averages.
- Price calculation:
- Raw MWh prices from API are converted to EUR/kWh.
- Optional surcharges:
- `POWER_COMPANY_FEE_PERCENTAGE` (default 12%).
- `VAT_PERCENTAGE` (default 22%).
- LCD:
- **20x4 I²C (PCF8574 / 0x27)**.
- Custom characters for local language and low/high price markers.
```text
https://api.energy-charts.info/price?bzn=SI
```
### LED behavior
Where `bzn` is the **bidding zone** code. You can change this in the `.ino`:
- One **white LED** connected to GPIO 5.
- LED indicates **current 15minute segment** price:
- Very cheap (`<= 0.05 EUR/kWh`): smooth breathing.
- Cheap, normal, expensive, very expensive: different steady / blinking / doubleblink patterns.
- Negative or no data: LED off.
```cpp
const char* api_url = "https://api.energy-charts.info/price?bzn=SI";
```
### Presence sensor & backlight
to any supported BZN.
- Optional **RCWL0516** presence sensor on GPIO 9:
- Presence = backlight on, LED enabled.
- No presence for a while = LCD backlight off, LED disabled.
- If no presence sensor is detected, the backlight is kept on.
All available bidding zones (from the original README):
### Button behavior
- `AT` Austria
- `BE` Belgium
- `BG` Bulgaria
- `CH` Switzerland
- `CZ` Czech Republic
- `DE-LU` Germany, Luxembourg
- `DE-AT-LU` Germany, Austria, Luxembourg
- `DK1` Denmark 1
- `DK2` Denmark 2
- `EE` Estonia
- `ES` Spain
- `FI` Finland
- `FR` France
- `GR` Greece
- `HR` Croatia
- `HU` Hungary
- `IT-Calabria` Italy Calabria
- `IT-Centre-North` Italy Centre North
- `IT-Centre-South` Italy Centre South
- `IT-North` Italy North
- `IT-SACOAC` Italy Sardinia Corsica AC
- `IT-SACODC` Italy Sardinia Corsica DC
- `IT-Sardinia` Italy Sardinia
- `IT-Sicily` Italy Sicily
- `IT-South` Italy South
- `LT` Lithuania
- `LV` Latvia
- `ME` Montenegro
- `NL` Netherlands
- `NO1` Norway 1
- `NO2` Norway 2
- `NO2NSL` Norway North Sea Link
- `NO3` Norway 3
- `NO4` Norway 4
- `NO5` Norway 5
- `PL` Poland
- `PT` Portugal
- `RO` Romania
- `RS` Serbia
- `SE1` Sweden 1
- `SE2` Sweden 2
- `SE3` Sweden 3
- `SE4` Sweden 4
- `SI` Slovenia
- `SK` Slovakia
- One button (or capacitive touch) on GPIO 4.
- Uses internal pullup by default.
- Actions:
- **Single short press**:
- Scrolls through the hourly view on the **primary** screen.
- **Double press**:
- Toggles between:
- Primary price view
- Secondary info view
- **Long press (≈3s in your current repo)**:
- Forces a **manual data refresh** (API fetch), regardless of daily schedule.
> Always verify uptodate BZN support in the EnergyCharts API docs.
---
## Version 6.0 Daily Fetch + NVS Storage
## Hardware Setup (Detailed)
Version 6.0 is focused on:
This section merges the original v5.5 instructions with the current v6.1 hardware expectations.
Follow it carefully to reproduce the working setup.
1. **Reducing API calls**
2. **Persisting daily data in NVS**
3. **Making the device robust to power outages**
### 1. Daily fetch instead of hourly
Previously (v5.5), the firmware fetched data **every hour** at the top of the hour.
In **v6.0**, the behavior is:
- **On boot (after WiFi + time sync):**
- Try to load **todays** data from NVS:
- If found: use it directly, **no API call** at boot.
- If not found or invalid: schedule an immediate API fetch.
- **After midnight (local time):**
- Detect day rollover via localtime.
- Immediately:
- Invalidate yesterdays data (`isTodayDataAvailable = false`).
- Force display to show **“No data for today”**.
- Turn off the white LED (no price indication).
- Enter **midnight fetch phase**.
#### Midnight fetch phase
When the date changes:
1. First fetch is scheduled **immediately**.
2. If it fails:
- The device **stays in “No data for today”**, LED off.
- Retry schedule:
- 10minute interval, up to 5 attempts.
- After 5 failures, retry once at each **top of the hour** until it succeeds.
As soon as a fetch for **today** succeeds:
- `isTodayDataAvailable` is set to `true`.
- The enforced `NO_DATA_OFFSET` state is cleared back to `CURRENT_PRICES`.
- The LCD and LED resume showing todays prices.
> Importantly, **yesterdays data is never reused** for today, both at boot and after midnight.
### 2. NVS (nonvolatile) storage of daily data
The ESP32C3s builtin NVS is used via `Preferences`:
- Namespace: `"my-ticker"`.
- Keys (in addition to the existing WiFi credentials):
- `data_day`, `data_mon`, `data_year` calendar date of the stored data.
- `data_prc` full raw JSON response from the EnergyCharts API.
- `data_last_store` UNIX timestamp when the data was last written.
#### On boot
After time sync:
- If NVS has data whose date **matches today**:
- `data_prc` JSON is deserialized into the inRAM `doc` (`StaticJsonDocument`).
- `processJsonData()` is run, just like after a live fetch.
- `isTodayDataAvailable = true`.
- No initial API call is needed.
- If the stored date does **not** match today (or parsing fails):
- The stored data is **ignored** for display.
- The system starts from “No data for today” and will fetch new data.
#### After each successful fetch for today
- The entire JSON payload is stored as `data_prc` with the corresponding date and timestamp.
- This means:
- A reboot **later in the same day** can immediately restore the latest prices from NVS.
- Reboots the next morning will detect the date mismatch and fetch new data instead of showing stale data.
### 3. Secondary info (status) screen
The secondary menu (doubleclick to toggle) has **20 lines**, shown in 4line pages.
It typically includes:
- Current date and time.
- Last successful update timestamp.
- Daily average price in EUR/kWh (with company fee + VAT applied).
- WiFi RSSI and IP address.
- API success ratio and total calls.
- Device uptime.
- **NVS status section**:
- `NVS status:`
- `Data day: DD.MM.YYYY` or `Data day: none`
- `Last save: DD.MM.YY` or `Last save: none`
- `NVS: OK (today)` / `NVS: old data` / `NVS: empty`
- Credits + version line:
- `energy-charts.info`
- `dynamic electricity`
- `price ticker v6.0`
- `by Amir Toki^ 2025`
(Exact ordering and layout may differ slightly, based on your latest edits.)
---
## Hardware setup
### Microcontroller
### 1. Microcontroller
- **Seeed XIAO ESP32C3**
### LCD (I²C 2004)
Typical pins used in the sketch:
- 20x4 (2004) character LCD with I²C backpack (PCF8574).
- Default I²C address: `0x27` (configurable in code).
- Library: `LiquidCrystal_I2C`.
- `GPIO 5` → white LED (`whiteLedPin`)
- `GPIO 21` → builtin LED (`builtinLedPin`)
- `GPIO 4` → user button / touch input (`buttonPin`)
- `GPIO 9` → presence sensor (`presencePin`)
- I²C pins → boarddefault SDA/SCL (check XIAO ESP32C3 pinout)
### Button / touch input
---
- GPIO 4, internal pullup enabled.
### 2. 20x4 I²C LCD (2004) PCF8574 Backpack
**Mechanical pushbutton (default)**
- LCD: **20x4 2004 character display** with I²C backpack (PCF8574 or compatible).
- Default I²C address (in code): `0x27`
(Change in the sketch if your module differs: `LiquidCrystal_I2C lcd(0x27, 20, 4);`)
- One leg to GPIO 4, other leg to GND.
**Connections:**
**TTP223 capacitive touch (alternative)**
| LCD Backpack | XIAO ESP32C3 |
|-------------|---------------|
| VCC | 5V |
| GND | GND |
| SDA | I²C SDA |
| SCL | I²C SCL |
- VCC → 3.3 V
- GND → GND
- OUT → GPIO 4
> Note: On many XIAO ESP32C3 board definitions, SDA/SCL are mapped internally. Just use the default I²C pins as documented by Seeed.
Code change required for TTP223:
---
### 3. Pushbutton (Default) / Capacitive Touch Alternative
The firmware assumes a **momentary pushbutton** on `GPIO 4` by default.
#### Mechanical Pushbutton (default config)
- One leg → `GPIO 4`
- Other leg → `GND`
- No external pullup is required; code uses:
```cpp
// In handleButton() or wherever the button is read:
int reading = !digitalRead(buttonPin);
// Change to:
int reading = digitalRead(buttonPin); // if TTP223 output is HIGH when touched
pinMode(buttonPin, INPUT_PULLUP);
```
(Your current code uses `!digitalRead(buttonPin)` assuming an activeLOW mechanical button.)
And reads the button as **activeLOW**:
### Presence sensor (RCWL0516)
```cpp
int reading = !digitalRead(buttonPin);
```
- VCC → 3.3 V
- GND → GND
- OUT → GPIO 9
- 10 kΩ pulldown resistor between GPIO 9 and GND.
So:
If no sensor is detected at boot, the firmware keeps the LCD backlight always on.
- Button **pressed**`reading == 1`
- Button **released**`reading == 0`
### White LED
#### Alternative: TTP223 Capacitive Touch Button
- LED (with appropriate series resistor) on GPIO 5.
If you prefer a TTP223 capacitive touch input instead of a mechanical button:
**Wiring:**
- `VCC` → **3.3V**
- `GND` → **GND**
- `OUT``GPIO 4` (same as the pushbutton pin)
**Logic:**
- TTP223 output is **HIGH when touched**.
If you use TTP223, you may want to **remove the logical inversion** in the code:
```cpp
// For mechanical button (active LOW):
int reading = !digitalRead(buttonPin);
// For TTP223 (active HIGH), change to:
int reading = digitalRead(buttonPin);
```
Everything else (debounce, longpress, doubleclick) remains compatible.
---
## WiFi provisioning
### 4. Presence Sensor (RCWL0516, optional but supported)
If stored WiFi credentials are invalid or missing:
The presence sensor is used to control LCD backlight and LEDs to save power and avoid annoying blinking when nobody is around.
1. Device starts in AP mode with SSID: `MyTicker_Setup`.
2. DNS server redirects to a simple captive “WiFi Setup” page.
3. You can enter SSID and password, which are stored in NVS (`Preferences`).
4. The device restarts and attempts to connect with the new credentials.
Recommended module: **RCWL0516** microwave motion sensor.
**Wiring:**
| RCWL0516 | Connection |
|-----------|------------|
| VCC | 3.3V |
| GND | GND |
| OUT | GPIO 9 (`presencePin`) |
| **Required**: 10kΩ pulldown | Between GPIO 9 and GND |
Characteristics:
- The module can be hidden behind nonmetallic surfaces.
- Firmware automatically detects if the presence sensor is connected at boot:
- If **detected**:
- Presence toggles backlight on and enables LED output.
- Absence for `backlightOffDelay` (default 30 s) turns the backlight off and disables LED output.
- If **not detected**:
- Backlight is kept on permanently.
- LEDs are allowed to operate normally.
---
## Building and flashing
### 5. White LED / LED Strip Output
1. Open the `.ino` file in **Arduino IDE** (ensure ESP32 board support is installed).
2. Select:
- Board: `Seeed XIAO ESP32C3`
- Port: correct serial port
3. Install the required libraries if missing:
The sketch uses a **white LED** (or LED strip control line) on `GPIO 5` (`whiteLedPin`).
**Basic single LED wiring:**
- `GPIO 5` → series resistor (e.g. 220470 Ω) → LED anode
- LED cathode → GND
**For LED strips or higher currents:**
- Use a suitable NPN transistor / MOSFET:
- GPIO 5 → gate/base (with proper gate/base resistor)
- LED strip or load → external supply (with common GND)
- Transistor sink/source → GND / load as per standard MOSFET wiring
- Ensure the **strip power supply shares ground** with the ESP32C3 board.
- Do **not** drive large loads directly from the GPIO pin.
The LED is driven with various patterns to indicate price level; see “LED Price Signalling” below.
---
### 6. Power
- XIAO ESP32C3:
- Via USBC (recommended for development).
- Or via 5V pin if you have a regulated 5V supply (check Seeed docs).
- Ensure **all modules** (LCD, presence sensor, LED driver) share a **common ground** with the XIAO.
---
## Firmware Features (v6.2.0)
### Core Display & Pricing
- Data source: `https://api.energy-charts.info/price?bzn=SI`
- Resolution: 15minute intervals with hourly averages
- Display shows:
- **Row 0**: Current hour, four 15minute values
- **Rows 13**: 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` %).
- LCD:
- `LiquidCrystal_I2C` with custom characters for:
- Local language letters.
- Lowprice and highprice indicators.
- **Daily min/max markers**:
- 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 15minute interval** price:
| 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:
- No data for today.
- Time is not synced.
- Presence sensor has timed out (no presence, if installed).
### Presence Sensor & Backlight
- If presence sensor is **connected**:
- Presence detected → LCD backlight on, LEDs enabled.
- No presence for `backlightOffDelay` (30 s by default) → LCD backlight off, LEDs disabled.
- If **no presence sensor** is detected at boot:
- LCD backlight is always on.
- LEDs are not gated by presence.
### Button Behavior
One button (or touch) on GPIO 4 controls the UI:
- **Single short press**:
- On primary screen: scrolls the time offset (future hours).
- On secondary screen: scrolls through the 20line status text (4 lines at a time).
- **Double press**:
- Toggles between:
- Primary price view.
- Secondary status/info view.
- **Long press (~3 seconds in v6.1)**:
- While held:
- LCD shows: “Long press detected! Release to refresh”.
- On release:
- Forces a **manual data refresh**:
- Sets `nextScheduledFetchTime = now`.
- Shows “Manual Refresh… Please wait…”.
- `handleDataFetching()` will perform an immediate API fetch outside the normal schedule.
An **autoscroll timeout** resets the view to “current hour / top of lists” after inactivity.
---
## NVS Storage (Daily Data Cache)
This firmware uses ESP32C3 **Preferences API** (`Preferences`) under namespace `"my-ticker"`.
Stored keys:
- **WiFi credentials:**
- `ssid`
- `pass`
- **Daily price data:**
- `data_day` calendar day (131)
- `data_mon` month (011)
- `data_year` full year (e.g. 2026)
- `data_prc` full raw JSON payload from the API
- `data_last_store` Unix time (`time_t`) when data was last written
### On Boot
After successful NTP time sync:
1. Attempt to load `data_day`, `data_mon`, `data_year`, and `data_prc` from NVS.
2. If **stored date matches current local date**:
- Deserialize `data_prc` into `StaticJsonDocument doc`.
- Run `processJsonData()` as if it were fresh from the API.
- Set `isTodayDataAvailable = true`.
- **Skip** the initial API call to save traffic.
3. If the stored date does **not** match today or JSON parsing fails:
- NVS data is **ignored** for display.
- System starts from “No data for today”.
- Schedules an immediate API fetch.
### After Each Successful Fetch for Today
- Raw JSON payload is stored into NVS as `data_prc`, along with date (`data_day`, `data_mon`, `data_year`) and `data_last_store`.
- On reboot later the same day, the device will show prices immediately from NVS without hitting the API.
---
## Daily Fetch Strategy (v6.1.0+)
### Goals
- **Avoid hourly polling** of the API.
- Fetch:
- Once after boot (if no valid NVS data for today).
- Once per **new day** (after midnight), with robust retries while the nextday dataset is not yet published.
### Time Sync & First Fetch
- `configTzTime(TZ_CET_CEST, "pool.ntp.org")` is used to enable CET/CEST aware `localtime()` and `getLocalTime()`.
- Until time sync completes, the UI only shows “Syncing Time… Please wait…”.
- On first successful sync:
- `isTimeSynced = true`.
- `trackedDay` is set to the current `tm_mday`.
- Either NVS is used (if it has todays data) or an initial fetch is scheduled.
### DayRollover Detection
In the main `loop()`:
- `trackedDay` holds the last seen local day.
- Each iteration:
- Get `localtime()` for `now`.
- If `tm_mday != trackedDay`:
- Day rollover detected (midnight).
- `trackedDay` updated.
- Immediately:
- `isTodayDataAvailable = false`.
- `displayState = NO_DATA_OFFSET`.
- `timeOffsetHours = 0`.
- White LED turned off.
- **Midnight phase** is entered:
- `midnightPhaseActive = true`.
- `midnightRetryCount = 0`.
- `nextScheduledFetchTime = now` (immediate attempt).
- LCD updated to “No data for today. Press & hold to refresh manually”.
### “Today” Detection (Market Day Logic)
The EnergyCharts API can keep serving **yesterdays** market day for some time after local midnight.
To avoid accidentally accepting yesterdays data as todays, v6.1 uses a more robust rule.
In `processJsonData()`:
1. Read `unix_seconds[]`.
2. Interpret the **LAST** timestamp as representing the end of the datasets market day.
3. Convert it to local time (`localtime()`).
4. Compare its date (day, month, year) to the current local date.
- If they **match**:
- Dataset is accepted as “todays” data.
- `isTodayDataAvailable = true`.
- `lastProcessJsonAcceptedToday = true`.
- Prices are processed (hourly averages, min/max, daily average).
- If they **do not match**:
- Dataset is considered to belong to a **different** day (e.g. yesterday).
- `isTodayDataAvailable = false`.
- `lastProcessJsonAcceptedToday = false`.
- Function returns without updating display data.
This prevents the device from accidentally treating “yesterdays dayahead curve” as if it were already “today”.
### Midnight Retry Logic (v6.1.0 + your tuning)
When `midnightPhaseActive == true`, any scheduled fetch that:
- Fails at HTTP/JSON level, **or**
- Succeeds at HTTP/JSON level but `processJsonData()` **rejects** the dataset as “not today”
is treated as a **failure** for scheduling.
The retry rules:
1. **First hour after midnight fast retries:**
In `scheduleAfterMidnightFailure()` (with your current config):
```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 first attempt at rollover.
- If data is still yesterdays:
- 00:20 1st retry.
- 00:40 2nd retry.
- All “fast retries” remain fully within the first postmidnight hour.
2. **After the first hour hourly retries:**
Once `midnightRetryCount >= 2`, next retries are scheduled at the **top of each hour**:
```cpp
struct tm* ti = localtime(&now);
if (ti != NULL) {
time_t nextHour = now - (ti->tm_min * 60) - ti->tm_sec + 3600;
nextScheduledFetchTime = nextHour;
debugPrint(2, "Midnight retries exhausted; next fetch top-of-hour");
} else {
nextScheduledFetchTime = now + 3600;
debugPrint(2, "Midnight retries exhausted; fallback 1h");
}
```
So after ~00:40, if still no valid dataset for today, the device tries again at ~01:00, 02:00, 03:00, … until success.
3. **Success condition & exit from midnight phase:**
A scheduled fetch is treated as a **real success** only if:
- HTTP + JSON succeed **and**
- `lastProcessJsonAcceptedToday == true` (datasets last timestamps date matches today).
When this happens:
- `isTodayDataAvailable = true`.
- `midnightPhaseActive = false`.
- `midnightRetryCount = 0`.
- LCD leaves `NO_DATA_OFFSET` back to `CURRENT_PRICES`.
- White LED resumes price indication.
- `nextScheduledFetchTime` is set ≈24 hours ahead (until the next midnight rollover resets it).
---
## Secondary Status Screen (Debug / Info)
A **secondary screen** (toggled via **doubleclick**) provides 20 lines of status information, displayed 4 lines at a time:
Typical content:
1. Current date and time (`HH:MM DD.MM.YYYY`)
2. Separator line (`--------------------`)
3. “Zadnja posodobitev:” (Last update header)
4. Last successful fetch (for today) date & time
5. Blank
6. “Dnevno povprečje:” (Daily average)
7. Daily average price in EUR/kWh (with surcharges) or “Cene niso na voljo.”
8. Blank
9. WiFi status and RSSI
10. Local IP address
11. API success rate (`API: xx% (succ/fail)`)
12. Device uptime in days, hours, minutes
1316. **NVS status block**:
- `NVS status:`
- `Data day: DD.MM.YYYY` or `Data day: none`
- `Last save: DD.MM.YY` or `Last save: none`
- `NVS: OK (today)` / `NVS: old data` / `NVS: empty`
1720. Credits and version:
- `energy-charts.info`
- `dynamic electricity`
- `price ticker v6.1`
- `by Legolas-2025` (or your preferred credit line)
---
## WiFi Provisioning
If NVS does not contain valid WiFi credentials, or if connecting fails repeatedly:
1. The device starts an **Access Point** with SSID:
```text
MyTicker_Setup
```
2. LCD shows “No WiFi access! Setup WiFi: SSID: MyTicker_Setup” and the AP IP.
3. A simple captive portal is served:
- Open any URL while connected to `MyTicker_Setup`.
- Enter SSID and password in the HTML form.
- Values are stored in NVS: `ssid`, `pass`.
- Device reboots and attempts to connect with the new credentials.
---
## Building & Uploading
1. Install **Arduino IDE** with ESP32 board support (including XIAO ESP32C3).
2. Install required libraries:
- `LiquidCrystal_I2C`
- `ArduinoJson`
4. Compile and upload.
- `DNSServer` (from ESP32 core)
- `WebServer` (from ESP32 core)
- `Preferences` (builtin for ESP32)
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.
5. Upload the sketch.
6. Open Serial Monitor at **115200 baud** to see:
- WiFi connection logs.
- NTP sync messages.
- NVS load/save status.
- Midnight rollover and retry debug output.
---
## Versioning and files
Key documentation files:
## Versioning & Changelog
- [`VERSION.md`](./VERSION.md) current firmware version and compatibility notes.
- [`CHANGELOG.md`](./CHANGELOG.md) detailed list of changes between versions.
- `*.ino` main firmware source file (v6.0 and later).
- **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:
- HTTP/JSON success, but data for **wrong day** (treated as failure).
- Full success with accepted “today” dataset.
- Robust midnight retry scheme:
- Two retries every 20 minutes in the first hour (~00:20, ~00:40).
- Then hourly retries (topofhour) until todays dataset is available.
- Behavior on reboot and manual longpress 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 + aftermidnight”.
- Add NVS status section to secondary menu.
- **v5.5** 15minute detail mode, LED based on current 15minute 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.
---
## License
This project is licensed under the MIT License see the [LICENSE](./LICENSE) file for details.
This project is licensed under the MIT License see the [`LICENSE`](./LICENSE) file for details.
+54 -15
View File
@@ -2,23 +2,62 @@
## Current firmware
- **Version:** 6.0.0
- **Release date:** 2026-01-27
- **Target MCU:** Seeed XIAO ESP32C3
- **Display:** 20x4 I²C LCD (PCF8574, default address `0x27`)
- **API endpoint:** `https://api.energy-charts.info/price?bzn=SI`
- **Resolution:** 15minute intervals, hourly averages for overview
- **Version:** 6.2.3
- **Release date:** 2026-04-01
- **Target MCU:** Seeed XIAO ESP32C3
- **Display:** 20x4 I²C LCD (PCF8574, default address `0x27`)
- **API endpoint:** `https://api.energy-charts.info/price?bzn=SI`
- **Resolution:** 15minute intervals, hourly averages for overview
## Highlights of v6.0.0
## Highlights of v6.2.3
- 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 × 10minute retries.
- Then topofhour retries until successful.
- Prevents yesterdays prices from ever being shown as todays.
- Secondary info menu extended with **NVS status** and clear version label.
- **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 ESP32C3
## 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:
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 434 KiB