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 | |
|---|---|---|---|
|
|
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++) {
|
||||
|
||||
+170
-122
@@ -1,147 +1,195 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
This project follows a simple semantic versioning style:
|
||||
`MAJOR.MINOR.PATCH`
|
||||
All notable changes to this project are documented here.
|
||||
|
||||
---
|
||||
|
||||
## [6.0.0] - 2026-01-27
|
||||
## v6.1.1 – Daily Min/Max Includes Negative & Zero Prices (2026‑03‑07)
|
||||
|
||||
### 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 release fixes a bug where the **daily lowest / highest hourly price marker** ignored negative prices (and also ignored 0.0), which could cause the ticker to incorrectly mark the **lowest positive** hour as the daily minimum.
|
||||
|
||||
- **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.
|
||||
### Fixed: Daily Low/High Marker Ignored Negative & Zero Prices
|
||||
|
||||
- **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.
|
||||
**Problem (v6.1.0):**
|
||||
|
||||
- **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
|
||||
- In `processJsonData()` the daily min/max scan used:
|
||||
|
||||
### 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.
|
||||
- `if (hourlyAvg > 0) { ... }`
|
||||
|
||||
- **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.
|
||||
- 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).
|
||||
|
||||
- **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`).
|
||||
- Additionally, the daily average was computed as `sum / 24.0` even though hours were being skipped from `sum`, making the daily average incorrect whenever any hour was excluded.
|
||||
|
||||
- **Long‑press threshold (in repository version)**
|
||||
- Long‑press detection threshold extended from 2s to **3s** (in your repository copy) to avoid accidental manual refreshes.
|
||||
**Solution (v6.1.1):**
|
||||
|
||||
### 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.
|
||||
- The min/max and average scan now:
|
||||
- Treats an hour as valid based on **data availability** (having all 4×15‑minute entries), not based on value sign.
|
||||
- Includes **all values** (negative, zero, positive) when computing:
|
||||
- `lowestPriceIndex`
|
||||
- `highestPriceIndex`
|
||||
- `averagePrice`
|
||||
- Computes `averagePrice` using the number of valid hours (normally 24).
|
||||
|
||||
---
|
||||
|
||||
## [5.5.0] - 2025-10-27
|
||||
## v6.1.0 – Midnight Fetch & Market Day Fix (2026‑01‑30)
|
||||
|
||||
> 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.
|
||||
This release fixes a bug where the ticker could remain indefinitely on the **“No data for today”** screen after midnight, even though the API was already returning fresh data. It also refines the after‑midnight retry schedule.
|
||||
|
||||
### Fixed: Stuck on NO_DATA_OFFSET After Midnight
|
||||
|
||||
**Problem (v6.0.0):**
|
||||
|
||||
- After midnight, the device:
|
||||
- Detected day rollover and entered a “no data” state.
|
||||
- Scheduled an immediate API fetch.
|
||||
- However:
|
||||
- The API can continue to serve **yesterday’s market day** for some time after local midnight.
|
||||
- The code only checked the **first** `unix_seconds` entry against the current local day.
|
||||
- HTTP + JSON success always incremented `apiSuccessCount`, even if `processJsonData()` subsequently decided the dataset was “not for today”.
|
||||
- The scheduler treated such fetches as **successful**, pushed `nextScheduledFetchTime` 24 hours into the future, and never retried.
|
||||
- Result: the ticker stayed on **NO_DATA_OFFSET** forever, until:
|
||||
- A manual long‑press triggered a fresh fetch at a time when the API finally returned recognized “today” data, or
|
||||
- The device was rebooted later in the day.
|
||||
|
||||
**Solution (v6.1.0):**
|
||||
|
||||
1. **Market Day Detection**:
|
||||
- `processJsonData()` now determines the “market day” using the **last** `unix_seconds` timestamp from the API’s dataset (assumed to cover one full day in 15‑minute steps).
|
||||
- It compares that calendar date (local time) against the current local date.
|
||||
- If they differ:
|
||||
- The dataset is treated as **“not for today”**.
|
||||
- `isTodayDataAvailable = false`.
|
||||
- The function returns **false**, and a new flag `lastProcessJsonAcceptedToday` remains `false`.
|
||||
|
||||
2. **Logical Failure vs HTTP/JSON Failure**:
|
||||
- New global flag:
|
||||
- `lastProcessJsonAcceptedToday` – `true` only when `processJsonData()` accepts the dataset as “today’s” data.
|
||||
- In `handleDataFetching()`:
|
||||
- A scheduled fetch is considered a **real success** only if:
|
||||
- HTTP + JSON succeeded, **and**
|
||||
- `lastProcessJsonAcceptedToday == true`.
|
||||
- In all other cases (including “HTTP 200 + parse OK but data still for yesterday”):
|
||||
- The fetch is treated as **failure** for scheduling purposes.
|
||||
- If `midnightPhaseActive == true`, `scheduleAfterMidnightFailure()` is invoked to plan a retry.
|
||||
|
||||
3. **Midnight Phase Cleanup**:
|
||||
- When a fetch finally provides a dataset for today:
|
||||
- `isTodayDataAvailable = true`.
|
||||
- `lastProcessJsonAcceptedToday = true`.
|
||||
- `midnightPhaseActive` is cleared; `midnightRetryCount` reset to 0.
|
||||
- `nextScheduledFetchTime` is set to 24 hours ahead.
|
||||
|
||||
As a result, the ticker will **keep retrying** after midnight until a correct market‑day dataset appears, instead of giving up after the first HTTP 200.
|
||||
|
||||
---
|
||||
|
||||
## Older versions
|
||||
### Changed: After‑Midnight Retry Schedule
|
||||
|
||||
Earlier versions (≤5.4) introduced the basic ticker behavior, LCD layout, Energy‑Charts API integration, and the initial presence sensor / backlight / LED logic.
|
||||
In `scheduleAfterMidnightFailure()` the retry strategy when `midnightPhaseActive == true` has been tuned.
|
||||
|
||||
Those versions are not fully documented here, but key user‑visible behavior is maintained in v6.0 unless explicitly noted in this changelog.
|
||||
**Old behavior (v6.0.0, conceptual):**
|
||||
|
||||
- First few failures after midnight:
|
||||
- Retried every 10 minutes up to 5 attempts.
|
||||
- Afterwards:
|
||||
- Switched to hourly retries (top of each hour).
|
||||
|
||||
**New behavior (v6.1.0 + user configuration):**
|
||||
|
||||
- Fast retry phase fully contained within the **first hour after midnight**.
|
||||
- You configured:
|
||||
|
||||
```cpp
|
||||
if (midnightRetryCount < 2) {
|
||||
// Retry every 20 minutes for first 2 attempts (~1 hour window)
|
||||
midnightRetryCount++;
|
||||
nextScheduledFetchTime = now + 1200; // 20 minutes
|
||||
debugPrint(2, "Midnight retry " + String(midnightRetryCount) + "/2 in 20 minutes");
|
||||
} else {
|
||||
// After that, retry only at top of each hour
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
- Timeline:
|
||||
- 00:00 – initial attempt (triggered by day rollover).
|
||||
- If dataset is still for previous day:
|
||||
- 1st retry at ~00:20.
|
||||
- 2nd retry at ~00:40.
|
||||
- After that:
|
||||
- Retries only at the **top of the next hours** (01:00, 02:00, …),
|
||||
until a dataset with market day = today is accepted.
|
||||
|
||||
This configuration significantly reduces overnight API load while still ensuring the ticker picks up the new day as soon as the API publishes it.
|
||||
|
||||
---
|
||||
|
||||
### Other Behavior (Retained From v6.0.0)
|
||||
|
||||
- **NVS caching** of daily data:
|
||||
- On boot, if NVS contains a dataset whose date matches today’s local date:
|
||||
- The JSON is deserialized and processed.
|
||||
- A new API call is **skipped**.
|
||||
- After each accepted “today” fetch:
|
||||
- Raw JSON payload, date, and a timestamp are stored in NVS.
|
||||
- **Manual long‑press refresh**:
|
||||
- Still triggers immediate fetch via `nextScheduledFetchTime = now;`.
|
||||
- Now also respects the improved “today” detection; “yesterday’s” data is not accepted as today.
|
||||
- **CET/CEST time handling**:
|
||||
- Unchanged, still uses `TZ_CET_CEST` with `configTzTime`.
|
||||
- **Secondary menu and NVS status lines**:
|
||||
- Retained from v6.0.0; updated only for version string and minor wording.
|
||||
|
||||
---
|
||||
|
||||
## 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"` introduced with keys:
|
||||
- `ssid`, `pass` – Wi‑Fi credentials.
|
||||
- `data_day`, `data_mon`, `data_year` – stored data calendar day.
|
||||
- `data_prc` – raw JSON string from API.
|
||||
- `data_last_store` – Unix time when data was stored.
|
||||
- Boot behavior:
|
||||
- Try to load and validate NVS data.
|
||||
- If date matches today → reuse it and **skip** initial API call.
|
||||
- After‑midnight behavior:
|
||||
- NVS is overwritten with each successful new‑day dataset.
|
||||
- In‑RAM data for yesterday is invalidated at day rollover.
|
||||
|
||||
### UI / Menu
|
||||
|
||||
- Primary list:
|
||||
- 15‑minute detail for current hour.
|
||||
- Hourly averages for current + next 2 hours.
|
||||
- Secondary list:
|
||||
- Expanded to 20 lines to include:
|
||||
- Time/date, last update, daily average.
|
||||
- Wi‑Fi RSSI, IP address.
|
||||
- API success rate, uptime.
|
||||
- NVS status block.
|
||||
- Credits & version line.
|
||||
|
||||
---
|
||||
|
||||
## 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
@@ -1,309 +1,584 @@
|
||||
# Electricity Price Ticker for XIAO ESP32‑C3 (Slovenia, Energy‑Charts)
|
||||
# Electricity Price Ticker for XIAO ESP32‑C3 (Energy‑Charts)
|
||||
|
||||
This project is an Arduino‑IDE‑friendly firmware for the **Seeed XIAO ESP32‑C3** that:
|
||||
|
||||
- Connects to Wi‑Fi.
|
||||
- Fetches day‑ahead electricity prices for **Slovenia** from [Energy‑Charts.info](https://energy-charts.info).
|
||||
- Computes final consumer prices (including configurable fees + VAT).
|
||||
- Fetches **day‑ahead electricity prices** from [Energy‑Charts.info](https://energy-charts.info) (Bundesnetzagentur / SMARD.de).
|
||||
- Computes final consumer prices (including configurable power‑company 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.1.1**, focusing on:
|
||||
|
||||
- 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 **after‑midnight 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
|
||||
---
|
||||
|
||||
## Bidding Zones (BZN) / Region Selection
|
||||
|
||||
The firmware currently uses:
|
||||
|
||||
```text
|
||||
https://api.energy-charts.info/price?bzn=SI
|
||||
```
|
||||
|
||||
Where `bzn` is the **bidding zone** code. You can change this in the `.ino`:
|
||||
|
||||
```cpp
|
||||
const char* api_url = "https://api.energy-charts.info/price?bzn=SI";
|
||||
```
|
||||
|
||||
to any supported BZN.
|
||||
|
||||
All available bidding zones (from the original README):
|
||||
|
||||
- `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
|
||||
|
||||
> Always verify up‑to‑date BZN support in the Energy‑Charts API docs.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
## Hardware Setup (Detailed)
|
||||
|
||||
### Core display & pricing
|
||||
This section merges the original v5.5 instructions with the current v6.1 hardware expectations.
|
||||
Follow it carefully to reproduce the working setup.
|
||||
|
||||
- Data source: [Energy‑Charts.info day‑ahead price API](https://api.energy-charts.info/price?bzn=SI) (bidding zone = `SI`).
|
||||
- Resolution:
|
||||
- Prices in **15‑minute intervals** (4 per hour).
|
||||
- Display shows:
|
||||
- Current hour:
|
||||
- Hourly average.
|
||||
- Four 15‑minute 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.
|
||||
|
||||
### LED behavior
|
||||
|
||||
- One **white LED** connected to GPIO 5.
|
||||
- LED indicates **current 15‑minute segment** price:
|
||||
- Very cheap (`<= 0.05 EUR/kWh`): smooth breathing.
|
||||
- Cheap, normal, expensive, very expensive: different steady / blinking / double‑blink patterns.
|
||||
- Negative or no data: LED off.
|
||||
|
||||
### Presence sensor & backlight
|
||||
|
||||
- Optional **RCWL‑0516** 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.
|
||||
|
||||
### Button behavior
|
||||
|
||||
- One button (or capacitive touch) on GPIO 4.
|
||||
- Uses internal pull‑up 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.
|
||||
|
||||
---
|
||||
|
||||
## Version 6.0 – Daily Fetch + NVS Storage
|
||||
|
||||
Version 6.0 is focused on:
|
||||
|
||||
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 Wi‑Fi + time sync):**
|
||||
- Try to load **today’s** 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 yesterday’s 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:
|
||||
- 10‑minute 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 today’s prices.
|
||||
|
||||
> Importantly, **yesterday’s data is never re‑used** for today, both at boot and after midnight.
|
||||
|
||||
### 2. NVS (non‑volatile) storage of daily data
|
||||
|
||||
The ESP32‑C3’s built‑in NVS is used via `Preferences`:
|
||||
|
||||
- Namespace: `"my-ticker"`.
|
||||
- Keys (in addition to the existing Wi‑Fi credentials):
|
||||
- `data_day`, `data_mon`, `data_year` – calendar date of the stored data.
|
||||
- `data_prc` – full raw JSON response from the Energy‑Charts 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 in‑RAM `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 (double‑click to toggle) has **20 lines**, shown in 4‑line pages.
|
||||
|
||||
It typically includes:
|
||||
|
||||
- Current date and time.
|
||||
- Last successful update timestamp.
|
||||
- Daily average price in EUR/kWh (with company fee + VAT applied).
|
||||
- Wi‑Fi 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 ESP32‑C3**
|
||||
|
||||
### 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` → built‑in LED (`builtinLedPin`)
|
||||
- `GPIO 4` → user button / touch input (`buttonPin`)
|
||||
- `GPIO 9` → presence sensor (`presencePin`)
|
||||
- I²C pins → board‑default SDA/SCL (check XIAO ESP32‑C3 pinout)
|
||||
|
||||
### Button / touch input
|
||||
---
|
||||
|
||||
- GPIO 4, internal pull‑up 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 ESP32‑C3**
|
||||
- `VCC` → **5V** (or 3V3 if your module explicitly supports 3.3V I²C)
|
||||
- `GND` → **GND**
|
||||
- `SDA` → board I²C SDA pin (see XIAO ESP32‑C3 documentation)
|
||||
- `SCL` → board I²C SCL pin
|
||||
|
||||
- VCC → 3.3 V
|
||||
- GND → GND
|
||||
- OUT → GPIO 4
|
||||
> Note: On many XIAO ESP32‑C3 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 pull‑up 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 active‑LOW mechanical button.)
|
||||
And reads the button as **active‑LOW**:
|
||||
|
||||
### Presence sensor (RCWL‑0516)
|
||||
```cpp
|
||||
int reading = !digitalRead(buttonPin);
|
||||
```
|
||||
|
||||
- VCC → 3.3 V
|
||||
- GND → GND
|
||||
- OUT → GPIO 9
|
||||
- 10 kΩ pull‑down 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, long‑press, double‑click) remains compatible.
|
||||
|
||||
---
|
||||
|
||||
## Wi‑Fi provisioning
|
||||
### 4. Presence Sensor (RCWL‑0516, optional but supported)
|
||||
|
||||
If stored Wi‑Fi 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 “Wi‑Fi 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: **RCWL‑0516** microwave motion sensor.
|
||||
|
||||
**Wiring (from the v5.5 header, preserved in v6.x):**
|
||||
|
||||
- `VCC` → **3.3V**
|
||||
- `GND` → **GND**
|
||||
- `OUT` → `GPIO 9` (`presencePin`)
|
||||
- **Required**: 10 kΩ pull‑down resistor between `GPIO 9` and `GND`.
|
||||
|
||||
Characteristics:
|
||||
|
||||
- The module can be hidden behind non‑metallic 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. 220–470 Ω) → 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 ESP32‑C3 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 ESP32‑C3:
|
||||
- Via USB‑C (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.1.1)
|
||||
|
||||
### Core Display & Pricing
|
||||
|
||||
- Data source:
|
||||
|
||||
```text
|
||||
https://api.energy-charts.info/price?bzn=SI
|
||||
```
|
||||
|
||||
- Resolution:
|
||||
- Prices in **15‑minute intervals** (`price[]`, `unix_seconds[]`).
|
||||
- Display shows:
|
||||
- **Row 0**: Current hour, four 15‑minute values in compact format (`XX XX XX XX`).
|
||||
- **Rows 1–3**: Current hour + next two hours as hourly averages.
|
||||
- Price calculation:
|
||||
- Raw MWh prices are converted to **EUR/kWh**.
|
||||
- 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.
|
||||
- Low‑price and high‑price indicators.
|
||||
- **Daily min/max markers**:
|
||||
- The low/high hourly indicators now consider **negative**, **0.0**, and positive prices (v6.1.1 fix).
|
||||
|
||||
### LED Price Signalling
|
||||
|
||||
The white LED (GPIO 5) reflects the **current 15‑minute interval** price:
|
||||
|
||||
- Very cheap (`<= 0.05 EUR/kWh`) → smooth breathing.
|
||||
- Cheap / normal → steady on.
|
||||
- Moderately expensive → slow blink.
|
||||
- Expensive → faster blink.
|
||||
- Very expensive → complex “double‑blink with long on” pattern.
|
||||
- Negative price or no data → LED off.
|
||||
|
||||
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 20‑line 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 **auto‑scroll timeout** resets the view to “current hour / top of lists” after inactivity.
|
||||
|
||||
---
|
||||
|
||||
## NVS Storage (Daily Data Cache)
|
||||
|
||||
This firmware uses ESP32‑C3 **Preferences API** (`Preferences`) under namespace `"my-ticker"`.
|
||||
|
||||
Stored keys:
|
||||
|
||||
- **Wi‑Fi credentials:**
|
||||
- `ssid`
|
||||
- `pass`
|
||||
- **Daily price data:**
|
||||
- `data_day` – calendar day (1–31)
|
||||
- `data_mon` – month (0–11)
|
||||
- `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 next‑day 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 today’s data) or an initial fetch is scheduled.
|
||||
|
||||
### Day‑Rollover 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 Energy‑Charts API can keep serving **yesterday’s** market day for some time after local midnight.
|
||||
To avoid accidentally accepting yesterday’s data as today’s, v6.1 uses a more robust rule.
|
||||
|
||||
In `processJsonData()`:
|
||||
|
||||
1. Read `unix_seconds[]`.
|
||||
2. Interpret the **LAST** timestamp as representing the end of the dataset’s 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 “today’s” 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 “yesterday’s day‑ahead 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 yesterday’s:
|
||||
- 00:20 – 1st retry.
|
||||
- 00:40 – 2nd retry.
|
||||
- All “fast retries” remain fully within the first post‑midnight 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` (dataset’s last timestamp’s 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 **double‑click**) 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. Wi‑Fi status and RSSI
|
||||
10. Local IP address
|
||||
11. API success rate (`API: xx% (succ/fail)`)
|
||||
12. Device uptime in days, hours, minutes
|
||||
13–16. **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`
|
||||
17–20. Credits and version:
|
||||
- `energy-charts.info`
|
||||
- `dynamic electricity`
|
||||
- `price ticker v6.1`
|
||||
- `by Legolas-2025` (or your preferred credit line)
|
||||
|
||||
---
|
||||
|
||||
## Wi‑Fi Provisioning
|
||||
|
||||
If NVS does not contain valid Wi‑Fi credentials, or if connecting fails repeatedly:
|
||||
|
||||
1. The device starts an **Access Point** with SSID:
|
||||
|
||||
```text
|
||||
MyTicker_Setup
|
||||
```
|
||||
|
||||
2. LCD shows “No Wi‑Fi access! Setup Wi‑Fi: 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 ESP32‑C3).
|
||||
2. Install required libraries:
|
||||
- `LiquidCrystal_I2C`
|
||||
- `ArduinoJson`
|
||||
4. Compile and upload.
|
||||
- `DNSServer` (from ESP32 core)
|
||||
- `WebServer` (from ESP32 core)
|
||||
- `Preferences` (built‑in for ESP32)
|
||||
3. Open the v6.1 `.ino` file (e.g. `20260130_electricity_ticker_6_1_nvs_daily_fetch.ino`).
|
||||
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:
|
||||
- Wi‑Fi connection logs.
|
||||
- NTP sync messages.
|
||||
- NVS load/save status.
|
||||
- Midnight rollover and retry debug output.
|
||||
|
||||
---
|
||||
|
||||
## Versioning and files
|
||||
## Versioning & Changelog
|
||||
|
||||
Key documentation files:
|
||||
- **v5.5** – 15‑minute detail mode, LED based on current 15‑minute slot, improved DST handling (see file `20251027a_electricity_ticker_10_5_5_latest_DST_and_midnight_fix.ino`).
|
||||
- **v6.0.0** – First NVS‑enabled version:
|
||||
- Store daily price data in NVS.
|
||||
- Reduce API calls to “boot + after‑midnight”.
|
||||
- Add NVS status section to secondary menu.
|
||||
- **v6.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 (top‑of‑hour) until today’s dataset is available.
|
||||
- Behavior on reboot and manual long‑press is unchanged, but now respects the improved “today” logic.
|
||||
|
||||
- [`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).
|
||||
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.
|
||||
|
||||
+19
-5
@@ -2,6 +2,25 @@
|
||||
|
||||
## Current firmware
|
||||
|
||||
- **Version:** 6.1.1
|
||||
- **Release date:** 2026-03-07
|
||||
- **Target MCU:** Seeed XIAO ESP32‑C3
|
||||
- **Display:** 20x4 I²C LCD (PCF8574, default address `0x27`)
|
||||
- **API endpoint:** `https://api.energy-charts.info/price?bzn=SI`
|
||||
- **Resolution:** 15‑minute intervals, hourly averages for overview
|
||||
|
||||
## Highlights of v6.1.1
|
||||
|
||||
- Fix: daily **lowest/highest hourly price marker** now includes **negative** and **0.0** prices.
|
||||
- Fix: daily average is computed over the number of valid hours (instead of always dividing by 24 even when hours were skipped).
|
||||
|
||||
For full details, see:
|
||||
|
||||
- [CHANGELOG.md](./CHANGELOG.md)
|
||||
- [README.md](./README.md)
|
||||
|
||||
## Previous firmware
|
||||
|
||||
- **Version:** 6.0.0
|
||||
- **Release date:** 2026-01-27
|
||||
- **Target MCU:** Seeed XIAO ESP32‑C3
|
||||
@@ -19,8 +38,3 @@
|
||||
- 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.
|
||||
|
||||
For full details, see:
|
||||
|
||||
- [CHANGELOG.md](./CHANGELOG.md)
|
||||
- [README.md](./README.md)
|
||||
|
||||
Reference in New Issue
Block a user