mirror of
https://github.com/Legolas-2025/EPrices.git
synced 2026-08-17 12:34:51 +02:00
Add files via upload
This commit is contained in:
+129
@@ -0,0 +1,129 @@
|
||||
# EPrices v1.0 – Changelog
|
||||
|
||||
## v1.0 — 2026-04-05
|
||||
|
||||
Initial release of EPrices.
|
||||
|
||||
---
|
||||
|
||||
### Project & device renames
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| ESPHome node name | `eprices` |
|
||||
| Friendly name | `EPrices` |
|
||||
| Helper file | `eprices_nvs.h` |
|
||||
| C++ namespace | `eprices_nvs` |
|
||||
| NVS partition namespace | `eprices` |
|
||||
| Secret key prefix | `eprices_` |
|
||||
|
||||
---
|
||||
|
||||
### Secrets file keys
|
||||
|
||||
```yaml
|
||||
wifi_ssid
|
||||
wifi_password
|
||||
eprices_fallback_ap_ssid
|
||||
eprices_fallback_ap_password
|
||||
eprices_api_encryption_key
|
||||
eprices_timezone # e.g. "Europe/Ljubljana"
|
||||
eprices_country_bzn # e.g. "SI"
|
||||
eprices_prov_fee # e.g. "0.12" (provider fee as decimal multiplier)
|
||||
eprices_vat_rate # e.g. "0.22" (VAT rate as decimal multiplier)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Sensors
|
||||
|
||||
#### Numeric sensors (`sensor:`)
|
||||
|
||||
| Name | Entity ID | Notes |
|
||||
|---|---|---|
|
||||
| Today Current Price | `sensor.eprices_today_current_price` | |
|
||||
| Today Next Price | `sensor.eprices_today_next_price` | |
|
||||
| Today Average Price | `sensor.eprices_today_average_price` | |
|
||||
| Today Highest Price | `sensor.eprices_today_highest_price` | |
|
||||
| Today Lowest Price | `sensor.eprices_today_lowest_price` | |
|
||||
| Today Current Hourly Price | `sensor.eprices_today_current_hourly_price` | |
|
||||
| Today Next Hourly Price | `sensor.eprices_today_next_hourly_price` | |
|
||||
| Today Highest Hourly Price | `sensor.eprices_today_highest_hourly_price` | |
|
||||
| Today Lowest Hourly Price | `sensor.eprices_today_lowest_hourly_price` | |
|
||||
| Today Current Max Hourly Price Percentage | `sensor.eprices_today_current_max_hourly_price_percentage` | |
|
||||
| Tomorrow Current Price | `sensor.eprices_tomorrow_current_price` | Evaluates at now + 86400s |
|
||||
| Tomorrow Next Price | `sensor.eprices_tomorrow_next_price` | Evaluates at now + 86400s |
|
||||
| Tomorrow Average Price | `sensor.eprices_tomorrow_average_price` | |
|
||||
| Tomorrow Highest Price | `sensor.eprices_tomorrow_highest_price` | |
|
||||
| Tomorrow Lowest Price | `sensor.eprices_tomorrow_lowest_price` | |
|
||||
| Tomorrow Current Hourly Price | `sensor.eprices_tomorrow_current_hourly_price` | Evaluates at now + 86400s |
|
||||
| Tomorrow Next Hourly Price | `sensor.eprices_tomorrow_next_hourly_price` | Evaluates at now + 86400s |
|
||||
| Tomorrow Highest Hourly Price | `sensor.eprices_tomorrow_highest_hourly_price` | |
|
||||
| Tomorrow Lowest Hourly Price | `sensor.eprices_tomorrow_lowest_hourly_price` | |
|
||||
| Tomorrow Current Max Hourly Price Percentage | `sensor.eprices_tomorrow_current_max_hourly_price_percentage` | Evaluates at now + 86400s |
|
||||
| WiFi Signal | `sensor.eprices_wifi_signal` | dBm, diagnostic |
|
||||
| Uptime | `sensor.eprices_uptime` | Human-readable string, diagnostic |
|
||||
|
||||
#### Text sensors (`text_sensor:`)
|
||||
|
||||
| Name | Entity ID | Notes |
|
||||
|---|---|---|
|
||||
| Today JSON Hourly Prices EUR⁄kWh | `sensor.eprices_today_json_hourly_prices_eur_kwh` | JSON array, 24 values |
|
||||
| Today JSON 15-Min Prices EUR⁄kWh (P1 00:00-07:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p1_00_00_07_45` | JSON array, 32 values |
|
||||
| Today JSON 15-Min Prices EUR⁄kWh (P2 08:00-15:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p2_08_00_15_45` | JSON array, 32 values |
|
||||
| Today JSON 15-Min Prices EUR⁄kWh (P3 16:00-23:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p3_16_00_23_45` | JSON array, 32 values |
|
||||
| Today Highest Price Time | `sensor.eprices_today_highest_price_time` | HH:MM |
|
||||
| Today Lowest Price Time | `sensor.eprices_today_lowest_price_time` | HH:MM |
|
||||
| Today Highest Hourly Price Time | `sensor.eprices_today_highest_hourly_price_time` | HH:00 |
|
||||
| Today Lowest Hourly Price Time | `sensor.eprices_today_lowest_hourly_price_time` | HH:00 |
|
||||
| Today Current Price Status | `sensor.eprices_today_current_price_status` | `Valid` / `Missing` / `Stale` |
|
||||
| Today Data Loaded Time | `sensor.eprices_today_data_loaded_time` | Stamped on NVS load and HTTP fetch |
|
||||
| Today Last API Fetch Time | `sensor.eprices_today_last_api_fetch_time` | Stamped on HTTP fetch only; `Never` if NVS only; diagnostic |
|
||||
| Today Price Update Status | `sensor.eprices_today_price_update_status` | `SUCCESS` / `FAILED/WAITING`; diagnostic |
|
||||
| Today Price Update Status Message | `sensor.eprices_today_price_update_status_message` | Detailed status string; diagnostic |
|
||||
| Today API Fetch Attempts | `sensor.eprices_today_api_fetch_attempts` | HTTP fetch count; resets at midnight; diagnostic |
|
||||
| Today Entry Count | `sensor.eprices_today_entry_count` | Number of stored price points; diagnostic |
|
||||
| Tomorrow JSON Hourly Prices EUR⁄kWh | `sensor.eprices_tomorrow_json_hourly_prices_eur_kwh` | JSON array, 24 values |
|
||||
| Tomorrow JSON 15-Min Prices EUR⁄kWh (P1 00:00-07:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p1_00_00_07_45` | JSON array, 32 values |
|
||||
| Tomorrow JSON 15-Min Prices EUR⁄kWh (P2 08:00-15:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p2_08_00_15_45` | JSON array, 32 values |
|
||||
| Tomorrow JSON 15-Min Prices EUR⁄kWh (P3 16:00-23:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p3_16_00_23_45` | JSON array, 32 values |
|
||||
| Tomorrow Highest Price Time | `sensor.eprices_tomorrow_highest_price_time` | HH:MM |
|
||||
| Tomorrow Lowest Price Time | `sensor.eprices_tomorrow_lowest_price_time` | HH:MM |
|
||||
| Tomorrow Highest Hourly Price Time | `sensor.eprices_tomorrow_highest_hourly_price_time` | HH:00 |
|
||||
| Tomorrow Lowest Hourly Price Time | `sensor.eprices_tomorrow_lowest_hourly_price_time` | HH:00 |
|
||||
| Tomorrow Current Price Status | `sensor.eprices_tomorrow_current_price_status` | `Valid` / `Missing` / `Waiting...` |
|
||||
| Tomorrow Data Loaded Time | `sensor.eprices_tomorrow_data_loaded_time` | Stamped on NVS load and HTTP fetch; `Outside fetch window` before 13:20 |
|
||||
| Tomorrow Last API Fetch Time | `sensor.eprices_tomorrow_last_api_fetch_time` | Stamped on HTTP fetch only; `Never` if NVS only; diagnostic |
|
||||
| Tomorrow Price Update Status | `sensor.eprices_tomorrow_price_update_status` | `SUCCESS` / `FAILED/WAITING`; diagnostic |
|
||||
| Tomorrow Price Update Status Message | `sensor.eprices_tomorrow_price_update_status_message` | Detailed status string; diagnostic |
|
||||
| Tomorrow API Fetch Attempts | `sensor.eprices_tomorrow_api_fetch_attempts` | HTTP fetch count; resets at 13:25; diagnostic |
|
||||
| Tomorrow Entry Count | `sensor.eprices_tomorrow_entry_count` | Number of stored price points; diagnostic |
|
||||
| Last Reboot | `sensor.eprices_last_reboot` | Boot timestamp; diagnostic |
|
||||
| Last Update Source | `sensor.eprices_last_update_source` | `NVS_boot` / `HTTP_today` / `midnight_bridge` etc.; diagnostic |
|
||||
| Today NVS Status | `sensor.eprices_today_nvs_status` | NVS load/store result; diagnostic |
|
||||
| Tomorrow NVS Status | `sensor.eprices_tomorrow_nvs_status` | NVS load/store result; diagnostic |
|
||||
| Today Data Date | `sensor.eprices_today_data_date` | Date of stored today data; diagnostic |
|
||||
| Tomorrow Data Date | `sensor.eprices_tomorrow_data_date` | Date of stored tomorrow data; diagnostic |
|
||||
|
||||
#### Buttons
|
||||
|
||||
| Name | Entity ID |
|
||||
|---|---|
|
||||
| Force Today's Update | `button.eprices_force_today_s_update` |
|
||||
| Force Tomorrow's Update | `button.eprices_force_tomorrow_s_update` |
|
||||
| Reboot Device | `button.eprices_reboot_device` |
|
||||
|
||||
---
|
||||
|
||||
### Key behaviours
|
||||
|
||||
- **Price calculation:** `price_eur_kwh = (raw_eur_mwh / 1000) × (1 + prov_fee) × (1 + vat_rate)`
|
||||
- **Negative prices:** formatted as `%.3f` (3 decimal places) to stay within the 255-character HA text sensor state limit; positive prices use `%.4f`
|
||||
- **DST-safe:** all price indexing uses UNIX timestamps and binary search — no hour-slot arithmetic
|
||||
- **NVS persistence:** prices survive reboots; stale data (date mismatch) is discarded and triggers a fresh HTTP fetch
|
||||
- **Today Current Price Status** shows `Stale` if stored date does not match today's date
|
||||
- **Tomorrow live sensors** evaluate at `now + 86400s` so they reflect tomorrow at the same local time
|
||||
- **Tomorrow Data Loaded Time** shows `Outside fetch window` when device boots before 13:20
|
||||
- **API fetch times** reset to `Never` at midnight bridge and on `clear_tomorrow_prices`
|
||||
- **API fetch attempt counters** reset to `0` at midnight and publish `0` immediately on boot
|
||||
- **Uptime** displayed as human-readable string: `45 s` / `5 min` / `3 h 22 min` / `12 d 4 h` / `4 months 12 d`
|
||||
@@ -0,0 +1,219 @@
|
||||
# Migrating from Entso-E Prices to EPrices
|
||||
|
||||
This document explains the rationale behind EPrices and documents all changes
|
||||
relative to the last public release of the predecessor project,
|
||||
**entso-e-prices v4.3.1**.
|
||||
|
||||
It is intended for users who ran entso-e-prices v4.3.1 and want to migrate
|
||||
their Home Assistant automations, dashboards, and energy configurations to EPrices.
|
||||
|
||||
---
|
||||
|
||||
## Why EPrices was created
|
||||
|
||||
The original entso-e-prices project fetched electricity prices from the
|
||||
**ENTSOE Transparency Platform XML API**. That API requires a private token,
|
||||
has rate limits, and returns data in a complex XML format that required
|
||||
significant parsing logic and an external helper file (`entsoe_http_idf.h`).
|
||||
Additionally, all automation logic (midnight bridge, retry scheduling, NVS
|
||||
management) was split across external HA automations, making the system fragile
|
||||
and difficult to maintain.
|
||||
|
||||
The goal of EPrices was to:
|
||||
|
||||
1. **Eliminate the need for a private API token** — the Energy-Charts API
|
||||
(Fraunhofer ISE) is public, free, and returns clean JSON. No registration,
|
||||
no token rotation, no rate limit concerns.
|
||||
|
||||
2. **Move all core logic into the device** — midnight bridge, retry scheduling,
|
||||
NVS persistence, and boot recovery all run on the ESP32 itself. No external
|
||||
HA automations are required for any core functionality.
|
||||
|
||||
3. **Simplify the codebase** — the two external helper files
|
||||
(`entsoe_http_idf.h` and `entsoe_storage_v2.h`) were eliminated. All logic
|
||||
is now either inline in `eprices.yaml` or in the single helper `eprices_nvs.h`.
|
||||
|
||||
4. **Improve sensor clarity and user experience** — sensor names were
|
||||
systematically restructured with Today/Tomorrow prefixes, redundant words
|
||||
removed, and diagnostic sensors clearly separated from operational ones.
|
||||
|
||||
5. **Make the system DST-safe** — the original slot arithmetic
|
||||
`(hour × 4) + (min / 15)` breaks on DST transition days. EPrices uses
|
||||
UNIX timestamps and binary search throughout, making it fully DST-safe.
|
||||
|
||||
---
|
||||
|
||||
## Summary of all changes from entso-e-prices v4.3.1 to EPrices v1.0
|
||||
|
||||
### API
|
||||
|
||||
| Item | entso-e-prices v4.3.1 | EPrices v1.0 |
|
||||
|---|---|---|
|
||||
| Data source | ENTSOE Transparency Platform XML | Energy-Charts JSON (Fraunhofer ISE) |
|
||||
| Authentication | Private API token required | No token — public API |
|
||||
| Data format | XML | JSON |
|
||||
| Price resolution | 15-minute | 15-minute |
|
||||
| Base price unit | €/MWh | €/MWh (converted to €/kWh in firmware) |
|
||||
|
||||
### Project structure
|
||||
|
||||
| Item | entso-e-prices v4.3.1 | EPrices v1.0 |
|
||||
|---|---|---|
|
||||
| ESPHome node name | `entso-e-prices` | `eprices` |
|
||||
| Friendly name | `Entso-E Prices` | `EPrices` |
|
||||
| Helper files | `entsoe_http_idf.h` + `entsoe_storage_v2.h` | `eprices_nvs.h` only |
|
||||
| NVS namespace | `entsoe2` | `eprices` |
|
||||
| Secret key prefix | `entsoe_` | `eprices_` |
|
||||
| Provider fee / VAT | Hardcoded in lambdas | Configurable via `secrets.yaml` |
|
||||
| Core automation logic | Split across external HA automations | Fully on-device |
|
||||
|
||||
### NVS storage
|
||||
|
||||
| Item | entso-e-prices v4.3.1 | EPrices v1.0 |
|
||||
|---|---|---|
|
||||
| Storage format | Fixed 96-slot float blobs | Dynamic timestamp + price arrays |
|
||||
| DST handling | Hour-slot arithmetic `(h×4)+(m/15)` — breaks on DST | UNIX timestamp binary search — DST-safe |
|
||||
| Boot recovery | Via external HA automation | On-device `boot_recovery_today/tomorrow_script` |
|
||||
| Midnight bridge | Via external HA automation | On-device `midnight_bridge_promotion` script |
|
||||
|
||||
### Fetch scheduling and retry logic
|
||||
|
||||
| Item | entso-e-prices v4.3.1 | EPrices v1.0 |
|
||||
|---|---|---|
|
||||
| Today fetch trigger | External HA automation | On-device auto-retry (up to 8 attempts) |
|
||||
| Tomorrow fetch schedule | External HA automation | On-device 13:25 → 13:55 → 14:55 … 19:55 |
|
||||
| Manual force update | Button | Button (unchanged) |
|
||||
| Midnight bridge | External HA automation at 00:00 | On-device `on_time: 00:00:00` |
|
||||
|
||||
### Sensor naming
|
||||
|
||||
All sensors were renamed with a systematic **Today / Tomorrow** prefix.
|
||||
The words "electricity" and "energy" were removed from all sensor names.
|
||||
"Next Day" was replaced with "Tomorrow" throughout.
|
||||
|
||||
#### Numeric sensors
|
||||
|
||||
| entso-e-prices v4.3.1 name | EPrices v1.0 name |
|
||||
|---|---|
|
||||
| `Current Electricity Price` | `Today Current Price` |
|
||||
| `Next Electricity Price` | `Today Next Price` |
|
||||
| `Average Electricity Price Today` | `Today Average Price` |
|
||||
| `Highest Electricity Price Today` | `Today Highest Price` |
|
||||
| `Lowest Electricity Price Today` | `Today Lowest Price` |
|
||||
| `Current Hourly Electricity Price` | `Today Current Hourly Price` |
|
||||
| `Next Hourly Electricity Price` | `Today Next Hourly Price` |
|
||||
| `Highest Hourly Electricity Price Today` | `Today Highest Hourly Price` |
|
||||
| `Lowest Hourly Electricity Price Today` | `Today Lowest Hourly Price` |
|
||||
| `Current Max Hourly Price Percentage` | `Today Current Max Hourly Price Percentage` |
|
||||
| `Daily Price Update Attempts` | `Today API Fetch Attempts` *(moved to text_sensor)* |
|
||||
| `Today Entry Count` | `Today Entry Count` *(moved to text_sensor)* |
|
||||
| `Next Day Current Electricity Price` | `Tomorrow Current Price` |
|
||||
| `Next Day Next Electricity Price` | `Tomorrow Next Price` |
|
||||
| `Average Electricity Price Tomorrow` | `Tomorrow Average Price` |
|
||||
| `Highest Electricity Price Tomorrow` | `Tomorrow Highest Price` |
|
||||
| `Lowest Electricity Price Tomorrow` | `Tomorrow Lowest Price` |
|
||||
| `Current Hourly Electricity Price Tomorrow` | `Tomorrow Current Hourly Price` |
|
||||
| `Next Hourly Electricity Price Tomorrow` | `Tomorrow Next Hourly Price` |
|
||||
| `Highest Hourly Electricity Price Tomorrow` | `Tomorrow Highest Hourly Price` |
|
||||
| `Lowest Hourly Electricity Price Tomorrow` | `Tomorrow Lowest Hourly Price` |
|
||||
| `Next Day Current Max Hourly Price Percentage` | `Tomorrow Current Max Hourly Price Percentage` |
|
||||
| `Next Day Price Update Attempts` | `Tomorrow API Fetch Attempts` *(moved to text_sensor)* |
|
||||
| `Tomorrow Entry Count` | `Tomorrow Entry Count` *(moved to text_sensor)* |
|
||||
|
||||
#### Text sensors
|
||||
|
||||
| entso-e-prices v4.3.1 name | EPrices v1.0 name |
|
||||
|---|---|
|
||||
| `ENTSO-E Hourly Prices EUR⁄kWh JSON` | `Today JSON Hourly Prices EUR⁄kWh` |
|
||||
| `ENTSO-E 15-Min Prices EUR⁄kWh JSON (P1 00:00-07:45)` | `Today JSON 15-Min Prices EUR⁄kWh (P1 00:00-07:45)` |
|
||||
| `ENTSO-E 15-Min Prices EUR⁄kWh JSON (P2 08:00-15:45)` | `Today JSON 15-Min Prices EUR⁄kWh (P2 08:00-15:45)` |
|
||||
| `ENTSO-E 15-Min Prices EUR⁄kWh JSON (P3 16:00-23:45)` | `Today JSON 15-Min Prices EUR⁄kWh (P3 16:00-23:45)` |
|
||||
| `Time Of Highest Energy Price Today` | `Today Highest Price Time` |
|
||||
| `Time Of Lowest Energy Price Today` | `Today Lowest Price Time` |
|
||||
| `Time Of Highest Hourly Energy Price Today` | `Today Highest Hourly Price Time` |
|
||||
| `Time Of Lowest Hourly Energy Price Today` | `Today Lowest Hourly Price Time` |
|
||||
| `Price Update Status` | `Today Price Update Status` |
|
||||
| `Last Price Update Time` | `Today Data Loaded Time` |
|
||||
| `Price Update Status Message` | `Today Price Update Status Message` |
|
||||
| `Current Price Status` | `Today Current Price Status` |
|
||||
| `ENTSO-E Next Day Hourly Prices EUR⁄kWh JSON` | `Tomorrow JSON Hourly Prices EUR⁄kWh` |
|
||||
| `ENTSO-E Next Day 15-Min Prices EUR⁄kWh JSON (P1 00:00-07:45)` | `Tomorrow JSON 15-Min Prices EUR⁄kWh (P1 00:00-07:45)` |
|
||||
| `ENTSO-E Next Day 15-Min Prices EUR⁄kWh JSON (P2 08:00-15:45)` | `Tomorrow JSON 15-Min Prices EUR⁄kWh (P2 08:00-15:45)` |
|
||||
| `ENTSO-E Next Day 15-Min Prices EUR⁄kWh JSON (P3 16:00-23:45)` | `Tomorrow JSON 15-Min Prices EUR⁄kWh (P3 16:00-23:45)` |
|
||||
| `Time Of Highest Energy Price Tomorrow` | `Tomorrow Highest Price Time` |
|
||||
| `Time Of Lowest Energy Price Tomorrow` | `Tomorrow Lowest Price Time` |
|
||||
| `Time Of Highest Hourly Energy Price Tomorrow` | `Tomorrow Highest Hourly Price Time` |
|
||||
| `Time Of Lowest Hourly Energy Price Tomorrow` | `Tomorrow Lowest Hourly Price Time` |
|
||||
| `Next Day Price Update Status` | `Tomorrow Price Update Status` |
|
||||
| `Next Day Last Price Update Time` | `Tomorrow Data Loaded Time` |
|
||||
| `Next Day Price Update Status Message` | `Tomorrow Price Update Status Message` |
|
||||
| `Next Day Current Price Status` | `Tomorrow Current Price Status` |
|
||||
| `ENTSO-E Last Reboot` | `Last Reboot` |
|
||||
| `Entso-E Today NVS Status` | `Today NVS Status` |
|
||||
| `Entso-E Tomorrow NVS Status` | `Tomorrow NVS Status` |
|
||||
| `ENTSO-E Last Update Source` | `Last Update Source` |
|
||||
| `Today Data Date` | `Today Data Date` *(unchanged)* |
|
||||
| `Tomorrow Data Date` | `Tomorrow Data Date` *(unchanged)* |
|
||||
|
||||
#### Buttons
|
||||
|
||||
| entso-e-prices v4.3.1 name | EPrices v1.0 name |
|
||||
|---|---|
|
||||
| `Entso-E Force Update` | `Force Today's Update` |
|
||||
| `Entso-E Force Next Day Update` | `Force Tomorrow's Update` |
|
||||
| `Entso-E Reboot Device` | `Reboot Device` |
|
||||
|
||||
### New sensors in EPrices v1.0 (no equivalent in v4.3.1)
|
||||
|
||||
| Sensor | Purpose |
|
||||
|---|---|
|
||||
| `Today Last API Fetch Time` | Timestamp of last successful HTTP fetch for today |
|
||||
| `Tomorrow Last API Fetch Time` | Timestamp of last successful HTTP fetch for tomorrow |
|
||||
| `WiFi Signal` | RSSI in dBm |
|
||||
| `Uptime` | Human-readable uptime string |
|
||||
|
||||
### Sensor output changes
|
||||
|
||||
| Sensor | v4.3.1 output | EPrices v1.0 output |
|
||||
|---|---|---|
|
||||
| `Last Reboot` | `Last reboot: 2026-04-04 20:10:10` | `2026-04-04 20:10:10` |
|
||||
| `Today NVS Status` | `Today NVS: Stored 96 pts for 2026-04-04` | `Stored 96 pts for 2026-04-04` |
|
||||
| `Tomorrow NVS Status` | `Tomorrow NVS: Stored 96 pts for 2026-04-05` | `Stored 96 pts for 2026-04-05` |
|
||||
| `Today Entry Count` | `96.0` | `96` |
|
||||
| `Tomorrow Entry Count` | `96.0` | `96` |
|
||||
| `Today API Fetch Attempts` | `0.0` | `0` |
|
||||
| `Tomorrow API Fetch Attempts` | `0.0` | `0` |
|
||||
| `Today Current Price Status` | `Valid` / `Missing` | `Valid` / `Missing` / `Stale` |
|
||||
| `Today Data Loaded Time` | Stamped on HTTP fetch only | Stamped on NVS load and HTTP fetch |
|
||||
| `Tomorrow Data Loaded Time` | `Never` after NVS boot load | Stamped on NVS load and HTTP fetch |
|
||||
|
||||
### HA entity ID changes
|
||||
|
||||
All entity IDs changed due to the node name change from `entso-e-prices` to `eprices`.
|
||||
|
||||
**Find and replace in all your automations and dashboards:**
|
||||
```
|
||||
sensor.entso_e_prices_ → sensor.eprices_
|
||||
button.entso_e_prices_ → button.eprices_
|
||||
```
|
||||
|
||||
Then apply the individual sensor name slug changes from the tables above.
|
||||
|
||||
### NVS data
|
||||
|
||||
The NVS namespace changed from `entsoe2` to `eprices`. On first boot after
|
||||
flashing EPrices v1.0 the device will not find any stored data and will
|
||||
trigger a fresh HTTP fetch automatically. This is expected and safe.
|
||||
No manual NVS erase is required.
|
||||
|
||||
---
|
||||
|
||||
## Migration checklist
|
||||
|
||||
- [ ] Flash `eprices.yaml` to the device
|
||||
- [ ] Update `secrets.yaml` — rename all `entsoe_` keys to `eprices_`; add `eprices_prov_fee` and `eprices_vat_rate`
|
||||
- [ ] In all HA automations: replace `entso_e_prices_` with `eprices_` in all entity IDs
|
||||
- [ ] Apply individual sensor name slug renames from the tables above
|
||||
- [ ] Remove any external HA automations that handled midnight bridge, boot recovery, or fetch scheduling — these are now all on-device
|
||||
- [ ] Verify the device fetches fresh data on first boot (check `Today NVS Status` and `Today Price Update Status Message`)
|
||||
- [ ] Update any HA dashboard cards referencing old entity IDs
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# EPrices – Version History
|
||||
|
||||
## v1.0 — 2026-04-05
|
||||
|
||||
First public release.
|
||||
|
||||
### What EPrices is
|
||||
|
||||
EPrices is an ESPHome firmware for ESP32 that fetches day-ahead electricity
|
||||
spot prices from the public Energy-Charts API and exposes them as Home Assistant
|
||||
sensors. No API token, no cloud subscription, no external automations required
|
||||
for core functionality — just an ESP32, ESPHome, and your WiFi network.
|
||||
|
||||
Prices are fetched for today and tomorrow in 15-minute resolution, converted
|
||||
from raw €/MWh to €/kWh with your provider fee and VAT applied, and stored
|
||||
persistently in ESP32 NVS flash so they survive reboots without re-fetching.
|
||||
|
||||
### Core features
|
||||
|
||||
- 15-minute and hourly resolution price arrays exposed as JSON text sensors
|
||||
- Current, next, average, highest and lowest price sensors for both today and tomorrow
|
||||
- Tomorrow live sensors evaluate at `now + 86400s` — showing tomorrow at the same local time
|
||||
- NVS persistence — prices survive reboots without re-fetching
|
||||
- Midnight bridge — tomorrow's data automatically promoted to today at 00:00
|
||||
- Auto-retry logic — up to 8 HTTP fetch attempts for both today and tomorrow
|
||||
- DST-safe — UNIX timestamps and binary search throughout, no hour-slot arithmetic
|
||||
- Staleness detection — `Today Current Price Status` shows `Stale` if stored date mismatches today
|
||||
- Full diagnostic sensor suite — NVS status, fetch attempts, API fetch times, data loaded
|
||||
times, WiFi signal, human-readable uptime
|
||||
- Supports any Energy-Charts bidding zone (SI, DE-LU, AT, FR, HR, HU and more)
|
||||
- Provider fee and VAT rate configurable via `secrets.yaml` — no code changes needed
|
||||
|
||||
### Sensor highlights
|
||||
|
||||
- **Today/Tomorrow JSON Hourly Prices EUR⁄kWh** — 24-value JSON arrays
|
||||
- **Today/Tomorrow JSON 15-Min Prices EUR⁄kWh (P1/P2/P3)** — three 32-value JSON arrays
|
||||
covering 00:00-07:45, 08:00-15:45 and 16:00-23:45
|
||||
- **Today/Tomorrow Data Loaded Time** — stamped on both NVS load and HTTP fetch
|
||||
- **Today/Tomorrow Last API Fetch Time** — stamped on HTTP fetch only; `Never` if NVS only
|
||||
- **Today/Tomorrow API Fetch Attempts** — HTTP-only counter, resets at midnight
|
||||
- **Uptime** — human-readable: `45 s` → `5 min` → `3 h 22 min` → `12 d 4 h` → `4 months 12 d`
|
||||
|
||||
### Files
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `eprices.yaml` | Main ESPHome configuration |
|
||||
| `eprices_nvs.h` | NVS helper — save/load price arrays to ESP32 flash |
|
||||
| `secrets.yaml` | Local secrets — not committed to git |
|
||||
| `CHANGELOG.md` | Full sensor and entity ID reference for v1.0 |
|
||||
| `README.md` | Installation guide and full sensor reference |
|
||||
| `ENTSO-E-PRICES-MIGRATION.md` | Optional — migration guide from the predecessor project |
|
||||
+2097
File diff suppressed because it is too large
Load Diff
+128
@@ -0,0 +1,128 @@
|
||||
#pragma once
|
||||
// EPrices NVS helper
|
||||
// Namespace: "eprices"
|
||||
// Keys: td_count, td_date, td_prices, td_ts
|
||||
// tm_count, tm_date, tm_prices, tm_ts
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cstdint>
|
||||
#include "esphome/core/log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs.h"
|
||||
|
||||
namespace eprices_nvs {
|
||||
|
||||
static const char *TAG = "eprices_nvs";
|
||||
static const char *NS = "eprices";
|
||||
|
||||
// ---- internal: ensure NVS is initialised once ----
|
||||
inline bool ensure_init() {
|
||||
static bool done = false;
|
||||
if (done) return true;
|
||||
esp_err_t e = nvs_flash_init();
|
||||
if (e == ESP_ERR_NVS_NO_FREE_PAGES || e == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
nvs_flash_erase();
|
||||
e = nvs_flash_init();
|
||||
}
|
||||
done = (e == ESP_OK);
|
||||
if (!done) ESP_LOGE(TAG, "nvs_flash_init failed: %s", esp_err_to_name(e));
|
||||
return done;
|
||||
}
|
||||
|
||||
// ---- save ----
|
||||
inline bool save(const char *count_key, const char *date_key,
|
||||
const char *prices_key, const char *ts_key,
|
||||
int count, const std::string &date,
|
||||
const std::vector<float> &prices,
|
||||
const std::vector<int64_t> ×tamps) {
|
||||
if (!ensure_init()) return false;
|
||||
nvs_handle_t h;
|
||||
esp_err_t e = nvs_open(NS, NVS_READWRITE, &h);
|
||||
if (e != ESP_OK) { ESP_LOGW(TAG, "save open failed: %s", esp_err_to_name(e)); return false; }
|
||||
|
||||
nvs_set_i32(h, count_key, (int32_t)count);
|
||||
nvs_set_blob(h, date_key, date.c_str(), date.size() + 1);
|
||||
if (count > 0) {
|
||||
nvs_set_blob(h, prices_key, prices.data(), (size_t)count * sizeof(float));
|
||||
nvs_set_blob(h, ts_key, timestamps.data(), (size_t)count * sizeof(int64_t));
|
||||
}
|
||||
nvs_commit(h);
|
||||
nvs_close(h);
|
||||
ESP_LOGI(TAG, "save(%s): %d entries for %s", count_key, count, date.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- load: returns false if missing, stale or corrupt ----
|
||||
inline bool load(const char *count_key, const char *date_key,
|
||||
const char *prices_key, const char *ts_key,
|
||||
const std::string &expected_date,
|
||||
std::vector<float> &out_prices,
|
||||
std::vector<int64_t> &out_timestamps,
|
||||
int &out_count) {
|
||||
if (!ensure_init()) return false;
|
||||
nvs_handle_t h;
|
||||
esp_err_t e = nvs_open(NS, NVS_READONLY, &h);
|
||||
if (e != ESP_OK) { ESP_LOGW(TAG, "load open failed: %s", esp_err_to_name(e)); return false; }
|
||||
|
||||
int32_t count = 0;
|
||||
e = nvs_get_i32(h, count_key, &count);
|
||||
if (e != ESP_OK || count <= 0 || count > 110) {
|
||||
ESP_LOGW(TAG, "load(%s): bad count %d", count_key, (int)count);
|
||||
nvs_close(h); return false;
|
||||
}
|
||||
|
||||
char date_buf[12] = {};
|
||||
size_t date_len = sizeof(date_buf);
|
||||
nvs_get_blob(h, date_key, date_buf, &date_len);
|
||||
if (expected_date != std::string(date_buf)) {
|
||||
ESP_LOGW(TAG, "load(%s): stale %s != %s", count_key, date_buf, expected_date.c_str());
|
||||
nvs_close(h); return false;
|
||||
}
|
||||
|
||||
std::vector<float> pv((size_t)count);
|
||||
std::vector<int64_t> ts((size_t)count);
|
||||
size_t sp = (size_t)count * sizeof(float);
|
||||
size_t st = (size_t)count * sizeof(int64_t);
|
||||
nvs_get_blob(h, prices_key, pv.data(), &sp);
|
||||
nvs_get_blob(h, ts_key, ts.data(), &st);
|
||||
nvs_close(h);
|
||||
|
||||
out_prices = pv;
|
||||
out_timestamps = ts;
|
||||
out_count = (int)count;
|
||||
ESP_LOGI(TAG, "load(%s): %d entries for %s", count_key, (int)count, expected_date.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- clear one slot (set count=0) ----
|
||||
inline void clear_slot(const char *count_key) {
|
||||
if (!ensure_init()) return;
|
||||
nvs_handle_t h;
|
||||
if (nvs_open(NS, NVS_READWRITE, &h) == ESP_OK) {
|
||||
nvs_set_i32(h, count_key, 0);
|
||||
nvs_commit(h);
|
||||
nvs_close(h);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- public wrappers ----
|
||||
inline bool save_today(int count, const std::string &date,
|
||||
const std::vector<float> &p, const std::vector<int64_t> &ts) {
|
||||
return save("td_count","td_date","td_prices","td_ts", count, date, p, ts);
|
||||
}
|
||||
inline bool save_tomorrow(int count, const std::string &date,
|
||||
const std::vector<float> &p, const std::vector<int64_t> &ts) {
|
||||
return save("tm_count","tm_date","tm_prices","tm_ts", count, date, p, ts);
|
||||
}
|
||||
inline bool load_today(const std::string &expected_date,
|
||||
std::vector<float> &p, std::vector<int64_t> &ts, int &cnt) {
|
||||
return load("td_count","td_date","td_prices","td_ts", expected_date, p, ts, cnt);
|
||||
}
|
||||
inline bool load_tomorrow(const std::string &expected_date,
|
||||
std::vector<float> &p, std::vector<int64_t> &ts, int &cnt) {
|
||||
return load("tm_count","tm_date","tm_prices","tm_ts", expected_date, p, ts, cnt);
|
||||
}
|
||||
inline void clear_tomorrow_slot() { clear_slot("tm_count"); }
|
||||
|
||||
} // namespace eprices_nvs
|
||||
Reference in New Issue
Block a user