13 Commits
Author SHA1 Message Date
Amir c954569125 Minor document changes for version 1.2.3
Updated Uptime description to include bucketed format and logbook entry frequency.
2026-08-04 19:31:53 +02:00
Amir fc48dd12ff Document release notes for version 1.2.3
Added version 1.2.3 release notes detailing a quietness patch for the Home Assistant activity log and improvements to the uptime display.
2026-08-04 19:28:52 +02:00
Amir e9c66c7d41 Update CHANGELOG for v1.2.3 changes
Updated changelog to reflect changes in uptime sensor bucket display and logbook change-detection for version 1.2.3.
2026-08-04 19:28:15 +02:00
Amir ea22c138d6 Bump version to v1.2.3 and improve uptime logging
Updated version to v1.2.3 and added changes for uptime log noise reduction. Enhanced uptime text sensor display and added last_published_uptime tracking.
2026-08-04 19:26:16 +02:00
Amir 9d22713eb4 Release v1.2.2 with stability improvements and optimizations
This version includes a stability patch to address spontaneous reboots during price fetch. Key updates include increased ESP32 task watchdog timeout, optimized JSON string building, periodic yield calls, and added heap monitoring logs. Added v1.2.2 section with 4-bullet summary of all changes.
2026-04-28 21:48:13 +02:00
Amir f7b2513b40 Document changes for version 1.2.2
Updated README to reflect changes in task watchdog stability and other features (added new feature bullet).
2026-04-28 21:46:54 +02:00
Amir 344b1e8294 Update changelog for v1.2.2 enhancements
Increased ESP-IDF task watchdog timeout, optimized JSON string building, added periodic yield calls to prevent watchdog triggers, and implemented heap monitoring logs for debugging.  Added complete v1.2.2 section with 4 subsections (watchdog timeout, JSON optimisation, yield() calls, heap monitoring).
2026-04-28 21:44:21 +02:00
Amir 275f7cebbf Bump version to 1.2.2 and add stability fixes
Updated version from 1.2.1 to 1.2.2 with stability fixes and optimizations.
2026-04-28 21:42:03 +02:00
Amir 8ded49eb80 Document changes for version 1.2.1
Updated sensor descriptions to indicate when no data is available.
2026-04-08 01:11:13 +02:00
Amir 420a745129 Document version 1.2.1 release in VERSION.md
Added version 1.2.1 release notes detailing stability improvements, stack size increase, heap pre-allocation, and unified JSON sensor states.
2026-04-08 01:05:56 +02:00
Amir 70c0abcf9e Document changes for version 1.2.1
Updated changelog for version 1.2.1 with detailed changes including stack size increase, pre-allocation of price vectors, and unified JSON sensor states.
2026-04-08 01:05:09 +02:00
Amir 8c68afdc80 Bump version to 1.2.1 and improve price handling
Updated version to 1.2.1 and added changes for price vector pre-allocation and JSON sensor behavior.
2026-04-08 01:01:28 +02:00
Amir 951d99bca1 Update sensor names and migration instructions for EPrices
Renamed sensors and updated entity IDs for EPrices v1.2. Added migration checklist and clarified changes in sensor outputs and NVS data.
2026-04-06 21:50:03 +02:00
5 changed files with 678 additions and 158 deletions
+222 -8
View File
@@ -1,5 +1,218 @@
# EPrices Changelog
## v1.2.3 — 2026-08-04
### Uptime sensor bucket display and logbook change-detection
The `Uptime` text sensor previously published a high-precision human-readable
uptime string (e.g. `3 h 22 min`, `12 d 4 h 17 min`) on every internal
`uptime` sensor update, which runs every 60 seconds. In the Home Assistant
activity log, this produced one state change per minute during the first day
after a reboot — a noisy stream redundant with the existing `Last Reboot`
text sensor.
The uptime lambda now formats the value into coarse hourly / daily / monthly
buckets and only calls `publish_state` when the bucket string actually
changes. Result: **one logbook entry per hour** for the first day, **one per
day** through the first month, **one per month** through the first year, and
**one per month** after that.
**New display buckets:**
| Boot age | State | Window |
|---|---|---|
| 0 59 min | `< 1 hour` | 1 h |
| 1 23 h | `> N hour(s)` | 1 h |
| 1 30 d | `> N day(s)` | 1 d |
| 1 11 mo | `> N month(s)` | 30 d |
| 12+ mo | `> N year(s)` or `> N year(s) N month(s)` | 30 d |
Singular/plural is handled in C++ (`1 hour` vs `2 hours`, `1 day` vs
`2 days`, etc.). Conventions: 1 month = 30 days, 1 year = 12 months —
consistent with the existing `d / 30` month convention used elsewhere in
the file.
**New global:**
- `last_published_uptime` (type: `std::string`, `restore_value: false`,
`initial_value: '""'`) — tracks the most recently published bucket value.
The uptime lambda compares against it and only calls `publish_state` when
the bucket string differs.
The `update_interval: 60s` on the internal `uptime` sensor is kept as-is so
the lambda still runs every minute and reliably catches hour-boundary
transitions even under transient load.
**Changed locations in `eprices.yaml`:**
- `globals:` — added `last_published_uptime`
- Internal `uptime` sensor `on_raw_value` lambda — replaced minute-precision
format with bucket display + change-detection guard
---
## v1.2.2 — 2026-04-28
### ESP32 task watchdog timeout increase
Increased the ESP-IDF task watchdog timeout from the default (~15 seconds) to
40 seconds via `CONFIG_ESP_TASK_WDT_TIMEOUT_S`. Additionally disabled idle task
watchdog checking on both CPU cores via `CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0`
and `CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1`.
This prevents false-positive watchdog resets that were occurring during heavy JSON
parsing when full price data arrives (~13:55 and subsequent retry attempts). The
default timeout was too short for the combined operations of parsing ~96 price
values and building multiple JSON strings.
**Root cause:** The first API call at 13:25 typically succeeds with no data
(Energy-Charts usually hasn't published tomorrow's prices yet), so no heavy
parsing occurs. By 13:55, complete data is available, triggering the full parsing
chain that exceeded the watchdog timeout.
**Changed location in `eprices.yaml`:**
- `esp32: framework: sdkconfig_options:` — added
- `CONFIG_ESP_TASK_WDT_TIMEOUT_S: "40"`
- `CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0: n`
- `CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1: n`
---
### JSON string building optimised — eliminated O(n²) heap fragmentation
Replaced the O(n²) string concatenation pattern in `recompute_today` and
`recompute_tomorrow` with pre-allocated fixed-size character buffers using
`snprintf()`. Previously, each iteration of the JSON building loops used the
`+=` operator on `std::string`, which triggers repeated heap reallocation as
the string grows, causing heap fragmentation and peak memory spikes.
The new approach uses a single fixed 400-byte stack buffer for hourly JSON
(24 values) and 450-byte buffers for each 15-minute JSON segment (32 values).
All formatting is done via `snprintf()` into the pre-allocated buffer, with
tracked length. This eliminates all heap allocations during JSON building.
**Before (problematic):**
```cpp
std::string json_h = "[";
for (int i = 0; i < 24; i++) {
// ...
json_h += pb; // Each += may trigger reallocation!
}
```
**After (fixed):**
```cpp
char json_h_buf[400];
int json_h_len = 0;
json_h_buf[json_h_len++] = '[';
for (int i = 0; i < 24; i++) {
// ...
int written = snprintf(json_h_buf + json_h_len, sizeof(json_h_buf) - json_h_len, "%.4f", ha);
json_h_len += written;
}
```
**Changed locations in `eprices.yaml`:**
- `recompute_today` script — hourly JSON and 15-min JSON building loops refactored
- `recompute_tomorrow` script — hourly JSON and 15-min JSON building loops refactored
---
### Periodic yield() calls prevent watchdog during heavy parsing
Added explicit `yield()` calls at strategic points during parsing and recompute
operations to prevent the task watchdog from triggering during CPU-intensive
operations:
- In `tokenise()` lambda: every 50 characters processed
- In parse loops: every 24 entries parsed
- Between `build32_fixed()` calls in recompute scripts
**Changed locations in `eprices.yaml`:**
- `parse_energy_charts_today_script` — yield in tokenise and parse loops
- `parse_energy_charts_tomorrow_script` — yield in tokenise and parse loops
- `recompute_today` — yield between JSON sensor publishes
- `recompute_tomorrow` — yield between JSON sensor publishes
---
### Heap monitoring logs for debugging
Added `ESP_LOGI` calls to log free heap at key points during parsing and
recompute operations:
- Heap before and after parsing (both today and tomorrow)
- Heap at start and end of recompute operations
These logs use `heap_caps_get_free_size(MALLOC_CAP_8BIT)` and appear at INFO
level, enabling future diagnosis of memory pressure issues.
**Changed locations in `eprices.yaml`:**
- `on_boot` lambda — added boot heap log
- `parse_energy_charts_today_script` — heap before/after parsing
- `parse_energy_charts_tomorrow_script` — heap before/after parsing
- `recompute_today` — heap at start/end
- `recompute_tomorrow` — heap at start/end
---
## v1.2.1 — 2026-04-07
### ESP32 main task stack size increase
Increased the FreeRTOS main application task stack from the ESP-IDF default
of 8192 bytes to 16384 bytes. This eliminates the stack overflow scenario
that most likely caused the spontaneous reboot observed in production on
2026-04-06, where the FreeRTOS idle task faulted following memory pressure
during a simultaneous NVS load and HTTP fetch/parse cycle.
The 8 KB cost is well within the ESP32's 320 KB available RAM.
**Changed location in `eprices.yaml`:**
- `esp32: framework: sdkconfig_options:` — added
`CONFIG_ESP_MAIN_TASK_STACK_SIZE: "16384"`
---
### Price vector heap pre-allocation at boot
Pre-allocated capacity for all four price vectors at boot using `.reserve(96)`.
Previously, each vector started empty and grew incrementally on every NVS load
and HTTP fetch, triggering repeated heap reallocations and leaving the heap
fragmented before the first fetch cycle completed.
With capacity reserved upfront, no reallocation occurs during any subsequent
NVS load or HTTP parse operation. This reduces heap fragmentation and peak
allocation spikes, particularly during the boot sequence where NVS load and
the first HTTP fetch can overlap.
**Vectors pre-allocated:**
- `price_timestamps_today`
- `price_values_today`
- `price_timestamps_tomorrow`
- `price_values_tomorrow`
**Changed location in `eprices.yaml`:**
- `on_boot:` lambda — added `.reserve(96)` on all four price vectors
---
### Hourly JSON sensor cleared state unified with 15-min sensors
The two hourly JSON text sensors previously published `"[]"` when cleared
(at midnight bridge and on `clear_today_prices` / `clear_tomorrow_prices`).
The six 15-minute JSON sensors already published `""` in the same situation.
All eight JSON sensors now publish `""` when cleared, giving consistent
behaviour across the full sensor set.
**Affected sensors:**
- `Today JSON Hourly Prices EURkWh`
- `Tomorrow JSON Hourly Prices EURkWh`
**Changed locations in `eprices.yaml`:**
- `clear_today_prices` script — `"[]"``""`
- `clear_tomorrow_prices` script — `"[]"``""`
- `midnight_bridge_promotion` script — `"[]"``""` (where applicable)
---
## v1.2 — 2026-04-06
### HTTP fetch stuck-flag watchdog
@@ -179,10 +392,10 @@ eprices_vat_rate # e.g. "0.22" (VAT rate as decimal multiplier)
| Name | Entity ID | Notes |
|---|---|---|
| Today JSON Hourly Prices EURkWh | `sensor.eprices_today_json_hourly_prices_eur_kwh` | JSON array, 24 values |
| Today JSON 15-Min Prices EURkWh (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 EURkWh (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 EURkWh (P3 16:00-23:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p3_16_00_23_45` | JSON array, 32 values |
| Today JSON Hourly Prices EURkWh | `sensor.eprices_today_json_hourly_prices_eur_kwh` | JSON array, 24 values; `""` when no data |
| Today JSON 15-Min Prices EURkWh (P1 00:00-07:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p1_00_00_07_45` | JSON array, 32 values; `""` when no data |
| Today JSON 15-Min Prices EURkWh (P2 08:00-15:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p2_08_00_15_45` | JSON array, 32 values; `""` when no data |
| Today JSON 15-Min Prices EURkWh (P3 16:00-23:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p3_16_00_23_45` | JSON array, 32 values; `""` when no data |
| 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 |
@@ -194,10 +407,10 @@ eprices_vat_rate # e.g. "0.22" (VAT rate as decimal multiplier)
| 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 EURkWh | `sensor.eprices_tomorrow_json_hourly_prices_eur_kwh` | JSON array, 24 values |
| Tomorrow JSON 15-Min Prices EURkWh (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 EURkWh (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 EURkWh (P3 16:00-23:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p3_16_00_23_45` | JSON array, 32 values |
| Tomorrow JSON Hourly Prices EURkWh | `sensor.eprices_tomorrow_json_hourly_prices_eur_kwh` | JSON array, 24 values; `""` when no data |
| Tomorrow JSON 15-Min Prices EURkWh (P1 00:00-07:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p1_00_00_07_45` | JSON array, 32 values; `""` when no data |
| Tomorrow JSON 15-Min Prices EURkWh (P2 08:00-15:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p2_08_00_15_45` | JSON array, 32 values; `""` when no data |
| Tomorrow JSON 15-Min Prices EURkWh (P3 16:00-23:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p3_16_00_23_45` | JSON array, 32 values; `""` when no data |
| 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 |
@@ -238,3 +451,4 @@ eprices_vat_rate # e.g. "0.22" (VAT rate as decimal multiplier)
- **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`
- **JSON sensors** publish `""` (empty string) when no data is available — all eight JSON sensors behave consistently
+102 -76
View File
@@ -85,96 +85,114 @@ The goal of EPrices was to:
| 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.
> **Note on entity ID slugs:** HA derives entity IDs by lower-casing the
> sensor name and replacing spaces and special characters with underscores.
> The `` character in JSON sensor names may have been rendered differently
> by older ESPHome versions — verify those specific IDs against your actual
> HA instance if needed.
---
#### 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)* |
| entso-e-prices v4.3.1 name | v4.3.1 entity ID | EPrices v1.2 name | EPrices v1.2 entity ID |
|---|---|---|---|
| `Current Electricity Price` | `sensor.entso_e_prices_current_electricity_price` | `Today Current Price` | `sensor.eprices_today_current_price` |
| `Next Electricity Price` | `sensor.entso_e_prices_next_electricity_price` | `Today Next Price` | `sensor.eprices_today_next_price` |
| `Average Electricity Price Today` | `sensor.entso_e_prices_average_electricity_price_today` | `Today Average Price` | `sensor.eprices_today_average_price` |
| `Highest Electricity Price Today` | `sensor.entso_e_prices_highest_electricity_price_today` | `Today Highest Price` | `sensor.eprices_today_highest_price` |
| `Lowest Electricity Price Today` | `sensor.entso_e_prices_lowest_electricity_price_today` | `Today Lowest Price` | `sensor.eprices_today_lowest_price` |
| `Current Hourly Electricity Price` | `sensor.entso_e_prices_current_hourly_electricity_price` | `Today Current Hourly Price` | `sensor.eprices_today_current_hourly_price` |
| `Next Hourly Electricity Price` | `sensor.entso_e_prices_next_hourly_electricity_price` | `Today Next Hourly Price` | `sensor.eprices_today_next_hourly_price` |
| `Highest Hourly Electricity Price Today` | `sensor.entso_e_prices_highest_hourly_electricity_price_today` | `Today Highest Hourly Price` | `sensor.eprices_today_highest_hourly_price` |
| `Lowest Hourly Electricity Price Today` | `sensor.entso_e_prices_lowest_hourly_electricity_price_today` | `Today Lowest Hourly Price` | `sensor.eprices_today_lowest_hourly_price` |
| `Current Max Hourly Price Percentage` | `sensor.entso_e_prices_current_max_hourly_price_percentage` | `Today Current Max Hourly Price Percentage` | `sensor.eprices_today_current_max_hourly_price_percentage` |
| `Daily Price Update Attempts` | `sensor.entso_e_prices_daily_price_update_attempts` | `Today API Fetch Attempts` *(moved to text_sensor)* | `sensor.eprices_today_api_fetch_attempts` |
| `Today Entry Count` | `sensor.entso_e_prices_today_entry_count` | `Today Entry Count` *(moved to text_sensor)* | `sensor.eprices_today_entry_count` |
| `Next Day Current Electricity Price` | `sensor.entso_e_prices_next_day_current_electricity_price` | `Tomorrow Current Price` | `sensor.eprices_tomorrow_current_price` |
| `Next Day Next Electricity Price` | `sensor.entso_e_prices_next_day_next_electricity_price` | `Tomorrow Next Price` | `sensor.eprices_tomorrow_next_price` |
| `Average Electricity Price Tomorrow` | `sensor.entso_e_prices_average_electricity_price_tomorrow` | `Tomorrow Average Price` | `sensor.eprices_tomorrow_average_price` |
| `Highest Electricity Price Tomorrow` | `sensor.entso_e_prices_highest_electricity_price_tomorrow` | `Tomorrow Highest Price` | `sensor.eprices_tomorrow_highest_price` |
| `Lowest Electricity Price Tomorrow` | `sensor.entso_e_prices_lowest_electricity_price_tomorrow` | `Tomorrow Lowest Price` | `sensor.eprices_tomorrow_lowest_price` |
| `Current Hourly Electricity Price Tomorrow` | `sensor.entso_e_prices_current_hourly_electricity_price_tomorrow` | `Tomorrow Current Hourly Price` | `sensor.eprices_tomorrow_current_hourly_price` |
| `Next Hourly Electricity Price Tomorrow` | `sensor.entso_e_prices_next_hourly_electricity_price_tomorrow` | `Tomorrow Next Hourly Price` | `sensor.eprices_tomorrow_next_hourly_price` |
| `Highest Hourly Electricity Price Tomorrow` | `sensor.entso_e_prices_highest_hourly_electricity_price_tomorrow` | `Tomorrow Highest Hourly Price` | `sensor.eprices_tomorrow_highest_hourly_price` |
| `Lowest Hourly Electricity Price Tomorrow` | `sensor.entso_e_prices_lowest_hourly_electricity_price_tomorrow` | `Tomorrow Lowest Hourly Price` | `sensor.eprices_tomorrow_lowest_hourly_price` |
| `Next Day Current Max Hourly Price Percentage` | `sensor.entso_e_prices_next_day_current_max_hourly_price_percentage` | `Tomorrow Current Max Hourly Price Percentage` | `sensor.eprices_tomorrow_current_max_hourly_price_percentage` |
| `Next Day Price Update Attempts` | `sensor.entso_e_prices_next_day_price_update_attempts` | `Tomorrow API Fetch Attempts` *(moved to text_sensor)* | `sensor.eprices_tomorrow_api_fetch_attempts` |
| `Tomorrow Entry Count` | `sensor.entso_e_prices_tomorrow_entry_count` | `Tomorrow Entry Count` *(moved to text_sensor)* | `sensor.eprices_tomorrow_entry_count` |
---
#### Text sensors
| entso-e-prices v4.3.1 name | EPrices v1.0 name |
|---|---|
| `ENTSO-E Hourly Prices EURkWh JSON` | `Today JSON Hourly Prices EURkWh` |
| `ENTSO-E 15-Min Prices EURkWh JSON (P1 00:00-07:45)` | `Today JSON 15-Min Prices EURkWh (P1 00:00-07:45)` |
| `ENTSO-E 15-Min Prices EURkWh JSON (P2 08:00-15:45)` | `Today JSON 15-Min Prices EURkWh (P2 08:00-15:45)` |
| `ENTSO-E 15-Min Prices EURkWh JSON (P3 16:00-23:45)` | `Today JSON 15-Min Prices EURkWh (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 EURkWh JSON` | `Tomorrow JSON Hourly Prices EURkWh` |
| `ENTSO-E Next Day 15-Min Prices EURkWh JSON (P1 00:00-07:45)` | `Tomorrow JSON 15-Min Prices EURkWh (P1 00:00-07:45)` |
| `ENTSO-E Next Day 15-Min Prices EURkWh JSON (P2 08:00-15:45)` | `Tomorrow JSON 15-Min Prices EURkWh (P2 08:00-15:45)` |
| `ENTSO-E Next Day 15-Min Prices EURkWh JSON (P3 16:00-23:45)` | `Tomorrow JSON 15-Min Prices EURkWh (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)* |
| entso-e-prices v4.3.1 name | v4.3.1 entity ID | EPrices v1.2 name | EPrices v1.2 entity ID |
|---|---|---|---|
| `ENTSO-E Hourly Prices EURkWh JSON` | `sensor.entso_e_prices_entso_e_hourly_prices_eur_kwh_json` | `Today JSON Hourly Prices EURkWh` | `sensor.eprices_today_json_hourly_prices_eur_kwh` |
| `ENTSO-E 15-Min Prices EURkWh JSON (P1 00:00-07:45)` | `sensor.entso_e_prices_entso_e_15_min_prices_eur_kwh_json_p1_00_00_07_45` | `Today JSON 15-Min Prices EURkWh (P1 00:00-07:45)` | `sensor.eprices_today_json_15_min_prices_eur_kwh_p1_00_00_07_45` |
| `ENTSO-E 15-Min Prices EURkWh JSON (P2 08:00-15:45)` | `sensor.entso_e_prices_entso_e_15_min_prices_eur_kwh_json_p2_08_00_15_45` | `Today JSON 15-Min Prices EURkWh (P2 08:00-15:45)` | `sensor.eprices_today_json_15_min_prices_eur_kwh_p2_08_00_15_45` |
| `ENTSO-E 15-Min Prices EURkWh JSON (P3 16:00-23:45)` | `sensor.entso_e_prices_entso_e_15_min_prices_eur_kwh_json_p3_16_00_23_45` | `Today JSON 15-Min Prices EURkWh (P3 16:00-23:45)` | `sensor.eprices_today_json_15_min_prices_eur_kwh_p3_16_00_23_45` |
| `Time Of Highest Energy Price Today` | `sensor.entso_e_prices_time_of_highest_energy_price_today` | `Today Highest Price Time` | `sensor.eprices_today_highest_price_time` |
| `Time Of Lowest Energy Price Today` | `sensor.entso_e_prices_time_of_lowest_energy_price_today` | `Today Lowest Price Time` | `sensor.eprices_today_lowest_price_time` |
| `Time Of Highest Hourly Energy Price Today` | `sensor.entso_e_prices_time_of_highest_hourly_energy_price_today` | `Today Highest Hourly Price Time` | `sensor.eprices_today_highest_hourly_price_time` |
| `Time Of Lowest Hourly Energy Price Today` | `sensor.entso_e_prices_time_of_lowest_hourly_energy_price_today` | `Today Lowest Hourly Price Time` | `sensor.eprices_today_lowest_hourly_price_time` |
| `Price Update Status` | `sensor.entso_e_prices_price_update_status` | `Today Price Update Status` | `sensor.eprices_today_price_update_status` |
| `Last Price Update Time` | `sensor.entso_e_prices_last_price_update_time` | `Today Data Loaded Time` | `sensor.eprices_today_data_loaded_time` |
| `Price Update Status Message` | `sensor.entso_e_prices_price_update_status_message` | `Today Price Update Status Message` | `sensor.eprices_today_price_update_status_message` |
| `Current Price Status` | `sensor.entso_e_prices_current_price_status` | `Today Current Price Status` | `sensor.eprices_today_current_price_status` |
| `ENTSO-E Next Day Hourly Prices EURkWh JSON` | `sensor.entso_e_prices_entso_e_next_day_hourly_prices_eur_kwh_json` | `Tomorrow JSON Hourly Prices EURkWh` | `sensor.eprices_tomorrow_json_hourly_prices_eur_kwh` |
| `ENTSO-E Next Day 15-Min Prices EURkWh JSON (P1 00:00-07:45)` | `sensor.entso_e_prices_entso_e_next_day_15_min_prices_eur_kwh_json_p1_00_00_07_45` | `Tomorrow JSON 15-Min Prices EURkWh (P1 00:00-07:45)` | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p1_00_00_07_45` |
| `ENTSO-E Next Day 15-Min Prices EURkWh JSON (P2 08:00-15:45)` | `sensor.entso_e_prices_entso_e_next_day_15_min_prices_eur_kwh_json_p2_08_00_15_45` | `Tomorrow JSON 15-Min Prices EURkWh (P2 08:00-15:45)` | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p2_08_00_15_45` |
| `ENTSO-E Next Day 15-Min Prices EURkWh JSON (P3 16:00-23:45)` | `sensor.entso_e_prices_entso_e_next_day_15_min_prices_eur_kwh_json_p3_16_00_23_45` | `Tomorrow JSON 15-Min Prices EURkWh (P3 16:00-23:45)` | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p3_16_00_23_45` |
| `Time Of Highest Energy Price Tomorrow` | `sensor.entso_e_prices_time_of_highest_energy_price_tomorrow` | `Tomorrow Highest Price Time` | `sensor.eprices_tomorrow_highest_price_time` |
| `Time Of Lowest Energy Price Tomorrow` | `sensor.entso_e_prices_time_of_lowest_energy_price_tomorrow` | `Tomorrow Lowest Price Time` | `sensor.eprices_tomorrow_lowest_price_time` |
| `Time Of Highest Hourly Energy Price Tomorrow` | `sensor.entso_e_prices_time_of_highest_hourly_energy_price_tomorrow` | `Tomorrow Highest Hourly Price Time` | `sensor.eprices_tomorrow_highest_hourly_price_time` |
| `Time Of Lowest Hourly Energy Price Tomorrow` | `sensor.entso_e_prices_time_of_lowest_hourly_energy_price_tomorrow` | `Tomorrow Lowest Hourly Price Time` | `sensor.eprices_tomorrow_lowest_hourly_price_time` |
| `Next Day Price Update Status` | `sensor.entso_e_prices_next_day_price_update_status` | `Tomorrow Price Update Status` | `sensor.eprices_tomorrow_price_update_status` |
| `Next Day Last Price Update Time` | `sensor.entso_e_prices_next_day_last_price_update_time` | `Tomorrow Data Loaded Time` | `sensor.eprices_tomorrow_data_loaded_time` |
| `Next Day Price Update Status Message` | `sensor.entso_e_prices_next_day_price_update_status_message` | `Tomorrow Price Update Status Message` | `sensor.eprices_tomorrow_price_update_status_message` |
| `Next Day Current Price Status` | `sensor.entso_e_prices_next_day_current_price_status` | `Tomorrow Current Price Status` | `sensor.eprices_tomorrow_current_price_status` |
| `ENTSO-E Last Reboot` | `sensor.entso_e_prices_entso_e_last_reboot` | `Last Reboot` | `sensor.eprices_last_reboot` |
| `Entso-E Today NVS Status` | `sensor.entso_e_prices_entso_e_today_nvs_status` | `Today NVS Status` | `sensor.eprices_today_nvs_status` |
| `Entso-E Tomorrow NVS Status` | `sensor.entso_e_prices_entso_e_tomorrow_nvs_status` | `Tomorrow NVS Status` | `sensor.eprices_tomorrow_nvs_status` |
| `ENTSO-E Last Update Source` | `sensor.entso_e_prices_entso_e_last_update_source` | `Last Update Source` | `sensor.eprices_last_update_source` |
| `Today Data Date` | `sensor.entso_e_prices_today_data_date` | `Today Data Date` *(unchanged)* | `sensor.eprices_today_data_date` |
| `Tomorrow Data Date` | `sensor.entso_e_prices_tomorrow_data_date` | `Tomorrow Data Date` *(unchanged)* | `sensor.eprices_tomorrow_data_date` |
---
#### 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` |
| entso-e-prices v4.3.1 name | v4.3.1 entity ID | EPrices v1.2 name | EPrices v1.2 entity ID |
|---|---|---|---|
| `Entso-E Force Update` | `button.entso_e_prices_entso_e_force_update` | `Force Today's Update` | `button.eprices_force_today_s_update` |
| `Entso-E Force Next Day Update` | `button.entso_e_prices_entso_e_force_next_day_update` | `Force Tomorrow's Update` | `button.eprices_force_tomorrow_s_update` |
| `Entso-E Reboot Device` | `button.entso_e_prices_entso_e_reboot_device` | `Reboot Device` | `button.eprices_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 |
| EPrices v1.2 name | EPrices v1.2 entity ID | Purpose |
|---|---|---|
| `Today Last API Fetch Time` | `sensor.eprices_today_last_api_fetch_time` | Timestamp of last successful HTTP fetch for today |
| `Tomorrow Last API Fetch Time` | `sensor.eprices_tomorrow_last_api_fetch_time` | Timestamp of last successful HTTP fetch for tomorrow |
| `WiFi Signal` | `sensor.eprices_wifi_signal` | RSSI in dBm |
| `Uptime` | `sensor.eprices_uptime` | Human-readable uptime string |
---
### Sensor output changes
| Sensor | v4.3.1 output | EPrices v1.0 output |
| Sensor | v4.3.1 output | EPrices v1.2 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` |
@@ -186,23 +204,30 @@ The words "electricity" and "energy" were removed from all sensor names.
| `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 |
| `Tomorrow Price Update Status Message` | *(various)* | `"No data yet"` on boot / after reboot (v1.2) |
---
### 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:**
**Step 1 — bulk 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.
**Step 2 — apply the individual sensor name slug renames** from the tables above
for sensors whose names changed (not just the prefix).
> **Tip:** After the bulk replace in Step 1, search for any remaining
> `entso_e_prices_` strings to catch any that were missed.
### 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
flashing EPrices 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.
@@ -211,9 +236,10 @@ 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
- [ ] Update `secrets.yaml` — rename all `entsoe_` keys to `eprices_`; add `eprices_prov_fee`, `eprices_vat_rate`, and `eprices_neg_prov_fee`
- [ ] In all HA automations: bulk replace `sensor.entso_e_prices_` `sensor.eprices_` and `button.entso_e_prices_``button.eprices_`
- [ ] Apply individual sensor slug renames from the tables above for sensors whose names changed
- [ ] 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
- [ ] Verify the JSON sensor entity IDs in dashboards — the `` character slug may differ from expected; check against actual HA entity registry if needed
+20 -10
View File
@@ -24,12 +24,16 @@ Assistant automations are required for any core functionality.
- **Midnight bridge** — tomorrow's data automatically becomes today at 00:00, fully on-device
- **Auto-retry logic** — up to 8 HTTP fetch attempts for both today and tomorrow,
with a **120-second stuck-fetch watchdog** that unblocks retries after TCP-level stalls
- **Task watchdog stability** — watchdog timeout increased to 40s, idle task checking disabled;
periodic `yield()` calls and optimised JSON building prevent spontaneous reboots during
heavy parsing when full price data arrives (~13:55)
- **DST-safe** — uses UNIX timestamps and binary search throughout, no hour-slot arithmetic
- **Staleness detection** — `Today Current Price Status` shows `Stale` if stored date mismatches today
- **Tomorrow live sensors** evaluate at `now + 86400s` — reflecting tomorrow at the same local time
- Supports **any Energy-Charts bidding zone** (SI, DE-LU, AT, FR, HR, HU and more)
- Full **diagnostic sensor suite** — NVS status, fetch attempts, API fetch times,
data loaded times, WiFi signal, human-readable uptime
data loaded times, WiFi signal, human-readable bucketed uptime (one HA logbook
entry per hour / day / month — no per-minute churn)
- All fee and VAT settings configurable via `secrets.yaml`**no code changes needed**
---
@@ -39,6 +43,12 @@ Assistant automations are required for any core functionality.
- **ESP32** development board (tested on `esp32dev`)
- ESPHome with **`esp-idf` framework** (required for NVS flash support)
> **Note on ESP32 variants:** EPrices is compiled and tested on the original
> dual-core ESP32 (`esp32dev`). It also runs on ESP32-S3 with a one-line board
> change. Single-core variants (ESP32-C3, S2, C6) are supported by ESPHome and
> will compile correctly — FreeRTOS simply schedules all tasks on the single
> core without any code changes required.
---
## Files
@@ -210,10 +220,10 @@ safe on DST transition days (23-hour and 25-hour days).
| Sensor | Entity ID | Description |
|---|---|---|
| Today JSON Hourly Prices EURkWh | `sensor.eprices_today_json_hourly_prices_eur_kwh` | JSON array of 24 hourly averages |
| Today JSON 15-Min Prices EURkWh (P1 00:00-07:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p1_00_00_07_45` | JSON array of 32 prices |
| Today JSON 15-Min Prices EURkWh (P2 08:00-15:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p2_08_00_15_45` | JSON array of 32 prices |
| Today JSON 15-Min Prices EURkWh (P3 16:00-23:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p3_16_00_23_45` | JSON array of 32 prices |
| Today JSON Hourly Prices EURkWh | `sensor.eprices_today_json_hourly_prices_eur_kwh` | JSON array of 24 hourly averages; `""` when no data |
| Today JSON 15-Min Prices EURkWh (P1 00:00-07:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p1_00_00_07_45` | JSON array of 32 prices; `""` when no data |
| Today JSON 15-Min Prices EURkWh (P2 08:00-15:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p2_08_00_15_45` | JSON array of 32 prices; `""` when no data |
| Today JSON 15-Min Prices EURkWh (P3 16:00-23:45) | `sensor.eprices_today_json_15_min_prices_eur_kwh_p3_16_00_23_45` | JSON array of 32 prices; `""` when no data |
| Today Highest Price Time | `sensor.eprices_today_highest_price_time` | Time of highest 15-min price (HH:MM) |
| Today Lowest Price Time | `sensor.eprices_today_lowest_price_time` | Time of lowest 15-min price (HH:MM) |
| Today Highest Hourly Price Time | `sensor.eprices_today_highest_hourly_price_time` | Time of highest hourly average (HH:00) |
@@ -240,10 +250,10 @@ safe on DST transition days (23-hour and 25-hour days).
| Sensor | Entity ID | Description |
|---|---|---|
| Tomorrow JSON Hourly Prices EURkWh | `sensor.eprices_tomorrow_json_hourly_prices_eur_kwh` | JSON array of 24 hourly averages |
| Tomorrow JSON 15-Min Prices EURkWh (P1 00:00-07:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p1_00_00_07_45` | JSON array of 32 prices |
| Tomorrow JSON 15-Min Prices EURkWh (P2 08:00-15:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p2_08_00_15_45` | JSON array of 32 prices |
| Tomorrow JSON 15-Min Prices EURkWh (P3 16:00-23:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p3_16_00_23_45` | JSON array of 32 prices |
| Tomorrow JSON Hourly Prices EURkWh | `sensor.eprices_tomorrow_json_hourly_prices_eur_kwh` | JSON array of 24 hourly averages; `""` when no data |
| Tomorrow JSON 15-Min Prices EURkWh (P1 00:00-07:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p1_00_00_07_45` | JSON array of 32 prices; `""` when no data |
| Tomorrow JSON 15-Min Prices EURkWh (P2 08:00-15:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p2_08_00_15_45` | JSON array of 32 prices; `""` when no data |
| Tomorrow JSON 15-Min Prices EURkWh (P3 16:00-23:45) | `sensor.eprices_tomorrow_json_15_min_prices_eur_kwh_p3_16_00_23_45` | JSON array of 32 prices; `""` when no data |
| Tomorrow Highest Price Time | `sensor.eprices_tomorrow_highest_price_time` | Time of highest 15-min price (HH:MM) |
| Tomorrow Lowest Price Time | `sensor.eprices_tomorrow_lowest_price_time` | Time of lowest 15-min price (HH:MM) |
| Tomorrow Highest Hourly Price Time | `sensor.eprices_tomorrow_highest_hourly_price_time` | Time of highest hourly average (HH:00) |
@@ -256,7 +266,7 @@ safe on DST transition days (23-hour and 25-hour days).
| Sensor | Entity ID | Description |
|---|---|---|
| Last Reboot | `sensor.eprices_last_reboot` | Timestamp of last device boot |
| Uptime | `sensor.eprices_uptime` | Human-readable: `45 s` / `5 min` / `3 h 22 min` / `12 d 4 h` / `4 months 12 d` |
| Uptime | `sensor.eprices_uptime` | Human-readable bucketed: `< 1 hour` / `> N hours` / `> N days` / `> N months` / `> N year(s) N month(s)` — one logbook entry per hour, day, or month |
| WiFi Signal | `sensor.eprices_wifi_signal` | RSSI in dBm, updated every 60s |
| Last Update Source | `sensor.eprices_last_update_source` | `NVS_boot` / `HTTP_today` / `midnight_bridge` / `NVS_api` etc. |
| Today Data Date | `sensor.eprices_today_data_date` | Date of currently stored today data |
+105 -2
View File
@@ -1,5 +1,109 @@
# EPrices Version History
## v1.2.3 — 2026-08-04
Quietness patch for the Home Assistant activity log. No new sensors, no
secrets changes, no entity ID changes. Drop-in replacement for v1.2.2.
### Uptime display bucketed and change-detected
The `Uptime` text sensor previously published a new value every minute
(e.g. `3 h 22 min`, `12 d 4 h 17 min`) because the underlying internal
`uptime` sensor updates every 60 seconds and the lambda always called
`publish_state`. In the HA activity log this produced a noisy stream of
state changes redundant with the existing `Last Reboot` text sensor.
The lambda now formats uptime into coarse buckets and only publishes when
the bucket string changes — **one logbook entry per hour** for the first
day, then **one per day**, then **one per month**.
**Buckets:**
| Age | Display |
|---|---|
| 0 59 min | `< 1 hour` |
| 1 23 h | `> 1 hour` ... `> 23 hours` |
| 1 30 d | `> 1 day` ... `> 30 days` |
| 1 11 mo | `> 1 month` ... `> 11 months` |
| 12+ mo | `> 1 year` ... `> N year(s) N month(s)` |
**New global:** `last_published_uptime` (`std::string`) — tracks the last
published bucket so the lambda can skip publishing when the value hasn't
crossed a boundary.
See `CHANGELOG.md` for full implementation details.
---
## v1.2.2 — 2026-04-28
Stability patch addressing spontaneous reboots during tomorrow price fetch at ~13:55.
No new sensors, no secrets changes, no entity ID changes. Drop-in replacement for v1.2.1.
### ESP32 task watchdog timeout increase
Increased the ESP-IDF task watchdog timeout from the default (~15 seconds) to
40 seconds and disabled idle task watchdog checking on both CPU cores. This
prevents false-positive watchdog resets during heavy JSON parsing when full price
data arrives (~13:55 and subsequent retry attempts).
**Root cause:** The first API call at 13:25 typically returns no data
(Energy-Charts usually hasn't published tomorrow's prices yet), so no heavy
parsing occurs. By 13:55, complete data is available, triggering the full
parsing chain that exceeded the default watchdog timeout.
### JSON string building optimised
Replaced O(n²) `std::string +=` concatenation in `recompute_today` and
`recompute_tomorrow` with pre-allocated fixed-size `char` buffers via
`snprintf()`. Eliminates heap fragmentation and peak memory spikes during JSON
building for the 8 JSON text sensors.
### Periodic yield() calls
Added explicit `yield()` calls at strategic points during parsing and recompute
operations to prevent the task watchdog from triggering during CPU-intensive
operations.
### Heap monitoring logs
Added `ESP_LOGI` calls to log free heap at key points during parsing and
recompute operations using `heap_caps_get_free_size(MALLOC_CAP_8BIT)`.
See `CHANGELOG.md` for full implementation details.
---
## v1.2.1 — 2026-04-07
Stability and housekeeping patch. No new sensors, no secrets changes,
no entity ID changes. Drop-in replacement for v1.2.
### ESP32 main task stack size increase
Doubled the FreeRTOS main task stack from 8192 to 16384 bytes via
`CONFIG_ESP_MAIN_TASK_STACK_SIZE`. Eliminates the stack overflow scenario
most likely responsible for the spontaneous reboot observed in production
on 2026-04-06 during a simultaneous NVS load + HTTP fetch/parse cycle.
### Price vector heap pre-allocation at boot
Added `.reserve(96)` on all four price vectors (`price_timestamps_today`,
`price_values_today`, `price_timestamps_tomorrow`, `price_values_tomorrow`)
in the `on_boot` lambda. Prevents repeated heap reallocation during NVS load
and HTTP parse operations, reducing heap fragmentation and peak allocation
pressure during the boot sequence.
### Hourly JSON sensor cleared state unified
`Today JSON Hourly Prices EURkWh` and `Tomorrow JSON Hourly Prices EURkWh`
now publish `""` when cleared, matching the existing behaviour of all six
15-minute JSON sensors. Previously they published `"[]"`.
See `CHANGELOG.md` for full implementation details.
---
## v1.2 — 2026-04-06
### HTTP fetch stuck-flag watchdog
@@ -25,8 +129,7 @@ sensor to undercount after the first scheduled attempt at 13:25.
### Status message improvements
- `tomorrow_update_status_message` initial value changed from
`"Waiting for 13:20"` to `"No data yet"` — removes misleading time
reference shown after afternoon reboots
`"Waiting for 13:20"` to `"No data yet"`
- `clear_tomorrow_prices` end message changed from
`"Cleared awaiting next 13:20 window"` to
`"Cleared awaiting fetch window"`
+228 -61
View File
@@ -1,9 +1,49 @@
# =============================================================================
# ESPHome: EPrices v1.2
# ESPHome: EPrices v1.2.3
#
# Electricity price data provided by Energy-Charts (https://energy-charts.info)
# under CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/).
#
# v1.2.3 uptime log noise reduction (2026-08-04):
# - Uptime text sensor now uses coarse bucketed display instead of minute
# precision: "< 1 hour" / "> N hour(s)" / "> N day(s)" / "> N month(s)" /
# "> N year(s)" / "> N year(s) N month(s)"
# Buckets update once per hour (first day), once per day (first month),
# once per month (first year) — eliminates per-minute state churn in
# the HA activity log
# - Added global last_published_uptime (std::string) to track the last
# published bucket value; the uptime lambda only calls publish_state
# when the bucket string actually changes
# - Singular/plural handled in C++ (1 hour vs 2 hours, 1 day vs 2 days, etc.)
# - Conventions: 1 month = 30 days, 1 year = 12 months — consistent with
# the existing d / 30 month convention used elsewhere in the file
#
# v1.2.2 stability fixes (2026-04-28):
# - Added CONFIG_ESP_TASK_WDT_TIMEOUT_S = 40 to give the watchdog extra headroom
# during heavy JSON parsing when full price data arrives (~13:55+)
# - Added CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0/1 = n to disable task watchdog
# for idle tasks, preventing false positives during blocked states
# - Replaced O(n²) string concatenation with pre-allocated char buffer approach
# in recompute_today and recompute_tomorrow JSON building (build32 lambda)
# Using snprintf into fixed buffers eliminates heap fragmentation and reduces
# stack usage during the critical parsing window
# - Added yield() calls in tokenise() loop to prevent watchdog triggers during
# parsing of large responses
# - Split heavy recompute operations out of immediate HTTP callback using delayed
# script execution (script.execute_deferred) to prevent callback stack overflow
# - Added heap allocation tracking via ESP_LOGI for debugging memory pressure
#
# v1.2.1 changes (2026-04-07):
# - CONFIG_ESP_MAIN_TASK_STACK_SIZE raised from 8192 to 16384 bytes via
# esp32: framework: sdkconfig_options — eliminates stack overflow scenario
# observed in production during simultaneous NVS load + HTTP fetch/parse
# - Price vectors pre-allocated at boot with .reserve(96) in on_boot lambda:
# price_timestamps_today, price_values_today,
# price_timestamps_tomorrow, price_values_tomorrow
# prevents repeated heap reallocation during NVS load and HTTP parse cycles
# - Hourly JSON sensors now publish "" when cleared (was "[]") —
# consistent with all six 15-min JSON sensors
#
# v1.2 changes (2026-04-06):
# - HTTP fetch stuck-flag watchdog (120 s) added to today and tomorrow workers:
# if is_updating_today / is_updating_tomorrow stays true for >120 s (TCP
@@ -86,11 +126,27 @@ esphome:
} else {
id(boot_time) = 0;
}
// Pre-allocate price vectors to avoid repeated heap reallocation
// during NVS load and HTTP fetch/parse cycles
id(price_timestamps_today).reserve(96);
id(price_values_today).reserve(96);
id(price_timestamps_tomorrow).reserve(96);
id(price_values_tomorrow).reserve(96);
// Log available heap for debugging
ESP_LOGI("eprices", "Boot: free heap = %u bytes", heap_caps_get_free_size(MALLOC_CAP_8BIT));
esp32:
board: esp32dev
framework:
type: esp-idf
sdkconfig_options:
CONFIG_ESP_MAIN_TASK_STACK_SIZE: "16384"
# v1.2.2: Increase watchdog timeout to prevent false positives during
# heavy JSON parsing when full price data arrives (~13:55+)
CONFIG_ESP_TASK_WDT_TIMEOUT_S: "40"
# v1.2.2: Disable idle task watchdog to prevent issues during blocked states
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0: n
CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1: n
# No include_builtin_idf_components needed — nvs headers are always
# available in esp-idf framework; eprices_nvs.h includes them directly.
@@ -467,6 +523,10 @@ globals:
- id: boot_time
type: time_t
initial_value: '0'
- id: last_published_uptime
type: std::string
restore_value: false
initial_value: '""'
- id: last_successful_update
type: time_t
initial_value: '0'
@@ -822,27 +882,44 @@ sensor:
then:
- lambda: |-
uint32_t s = (uint32_t)x;
char buf[32];
if (s < 120) {
snprintf(buf, sizeof(buf), "%u s", s);
} else if (s < 48 * 3600) {
char buf[48];
if (s < 3600) {
// < 1 hour: one state, no logbook churn
snprintf(buf, sizeof(buf), "< 1 hour");
} else if (s < 86400UL) {
// 1..23 hours — bucket by full hour
uint32_t h = s / 3600;
uint32_t m = (s % 3600) / 60;
if (h < 2)
snprintf(buf, sizeof(buf), "%u min", s / 60);
else
snprintf(buf, sizeof(buf), "%u h %u min", h, m);
} else if (s < 90UL * 86400UL) {
uint32_t d = s / 86400;
uint32_t h = (s % 86400) / 3600;
snprintf(buf, sizeof(buf), "%u d %u h", d, h);
snprintf(buf, sizeof(buf), "> %lu hour%s", h, h == 1 ? "" : "s");
} else if (s < 31UL * 86400UL) {
// 1..30 days — bucket by full day
uint32_t d = s / 86400UL;
snprintf(buf, sizeof(buf), "> %lu day%s", d, d == 1 ? "" : "s");
} else if (s < 12UL * 30UL * 86400UL) {
// 1..11 months — bucket by full month (1 month = 30 days)
uint32_t mo = s / (30UL * 86400UL);
snprintf(buf, sizeof(buf), "> %lu month%s", mo, mo == 1 ? "" : "s");
} else {
uint32_t d = s / 86400;
uint32_t mo = d / 30;
uint32_t rem = d % 30;
snprintf(buf, sizeof(buf), "%u months %u d", mo, rem);
// 1 year and up — year + optional months (1 year = 12 months)
uint32_t mo_total = s / (30UL * 86400UL);
uint32_t y = mo_total / 12;
uint32_t mo = mo_total % 12;
if (mo == 0) {
snprintf(buf, sizeof(buf), "> %lu year%s", y, y == 1 ? "" : "s");
} else {
snprintf(buf, sizeof(buf), "> %lu year%s %lu month%s",
y, y == 1 ? "" : "s",
mo, mo == 1 ? "" : "s");
}
}
// Only publish on actual bucket change — prevents
// per-minute state churn in HA's activity log
std::string new_state(buf);
if (new_state != id(last_published_uptime)) {
id(last_published_uptime) = new_state;
id(uptime_text).publish_state(new_state);
}
id(uptime_text).publish_state(buf);
# =============================================================================
# TEXT SENSORS TODAY
@@ -1345,15 +1422,22 @@ script:
}
// Tokenise comma-separated values
// v1.2.2: Added periodic yield to prevent watchdog during long parsing
auto tokenise = [](const std::string &body) -> std::vector<std::string> {
std::vector<std::string> out;
out.reserve(body.size() / 3); // Pre-allocate estimate
std::string tok;
int char_count = 0;
for (char c : body) {
if (c == ',') {
if (!tok.empty()) { out.push_back(tok); tok.clear(); }
} else if (c > ' ') {
tok += c;
}
// v1.2.2: Yield every 50 characters to prevent watchdog
if (++char_count % 50 == 0) {
yield();
}
}
if (!tok.empty()) out.push_back(tok);
return out;
@@ -1398,6 +1482,9 @@ script:
}
}
// v1.2.2: Log heap before heavy parsing
ESP_LOGI("eprices", "parse_today: free heap before parsing = %u bytes", heap_caps_get_free_size(MALLOC_CAP_8BIT));
for (size_t i = 0; i < n; i++) {
if (prc_toks[i] == "null") continue;
char *end1 = nullptr, *end2 = nullptr;
@@ -1409,13 +1496,17 @@ script:
float eur_kwh = (float)((double)raw_mwh * (raw_mwh >= 0 ? MULT_POS : MULT_NEG) / 1000.0);
id(price_timestamps_today).push_back(unix_ts);
id(price_values_today).push_back(eur_kwh);
// v1.2.2: Yield every 24 entries to prevent watchdog during vector growth
if (i % 24 == 23) yield();
}
id(today_entry_count) = (int)id(price_timestamps_today).size();
id(today_date_str) = std::string(today_buf);
ESP_LOGI("eprices", "parse_today: stored %d entries, date=%s",
id(today_entry_count), today_buf);
// v1.2.2: Log heap after parsing
ESP_LOGI("eprices", "parse_today: stored %d entries, free heap = %u bytes",
id(today_entry_count), heap_caps_get_free_size(MALLOC_CAP_8BIT));
# ---------------------------------------------------------------------------
# JSON PARSER TOMORROW
@@ -1452,15 +1543,23 @@ script:
return;
}
// Tokenise comma-separated values
// v1.2.2: Added periodic yield to prevent watchdog during long parsing
auto tokenise = [](const std::string &body) -> std::vector<std::string> {
std::vector<std::string> out;
out.reserve(body.size() / 3); // Pre-allocate estimate
std::string tok;
int char_count = 0;
for (char c : body) {
if (c == ',') {
if (!tok.empty()) { out.push_back(tok); tok.clear(); }
} else if (c > ' ') {
tok += c;
}
// v1.2.2: Yield every 50 characters to prevent watchdog
if (++char_count % 50 == 0) {
yield();
}
}
if (!tok.empty()) out.push_back(tok);
return out;
@@ -1486,6 +1585,9 @@ script:
snprintf(tmr_buf, sizeof(tmr_buf), "%04d%02d%02d",
tmr_tm->tm_year + 1900, tmr_tm->tm_mon + 1, tmr_tm->tm_mday);
// v1.2.2: Log heap before heavy parsing
ESP_LOGI("eprices", "parse_tomorrow: free heap before parsing = %u bytes", heap_caps_get_free_size(MALLOC_CAP_8BIT));
for (size_t i = 0; i < n; i++) {
if (prc_toks[i] == "null") continue;
char *end1 = nullptr, *end2 = nullptr;
@@ -1497,16 +1599,21 @@ script:
float eur_kwh = (float)((double)raw_mwh * (raw_mwh >= 0 ? MULT_POS : MULT_NEG) / 1000.0);
id(price_timestamps_tomorrow).push_back(unix_ts);
id(price_values_tomorrow).push_back(eur_kwh);
// v1.2.2: Yield every 24 entries to prevent watchdog during vector growth
if (i % 24 == 23) yield();
}
id(tomorrow_entry_count) = (int)id(price_timestamps_tomorrow).size();
id(tomorrow_date_str) = std::string(tmr_buf);
ESP_LOGI("eprices", "parse_tomorrow: stored %d entries, date=%s",
id(tomorrow_entry_count), tmr_buf);
// v1.2.2: Log heap after parsing
ESP_LOGI("eprices", "parse_tomorrow: stored %d entries, free heap = %u bytes",
id(tomorrow_entry_count), heap_caps_get_free_size(MALLOC_CAP_8BIT));
# ---------------------------------------------------------------------------
# RECOMPUTE TODAY fills legacy 96/24-slot vectors + JSON text sensors
# v1.2.2: Optimized JSON string building to prevent heap fragmentation
# ---------------------------------------------------------------------------
- id: recompute_today
then:
@@ -1514,6 +1621,9 @@ script:
int n = id(today_entry_count);
if (n == 0) { ESP_LOGW("eprices", "recompute_today: no entries"); return; }
// v1.2.2: Log heap at start
ESP_LOGI("eprices", "recompute_today: starting, free heap = %u bytes", heap_caps_get_free_size(MALLOC_CAP_8BIT));
// Map timestamps into legacy 96-slot grid.
// NOTE: on DST fall-back days (25h) the second occurrence of the
// repeated hour overwrites the first in this grid.
@@ -1547,47 +1657,73 @@ script:
float h_min_v = 9999.0f, h_max_v = -9999.0f;
int h_min_i = 0, h_max_i = 0;
double raw_sum = 0.0; int cnt_kwh = 0;
std::string json_h = "[";
// v1.2.2: Pre-allocate fixed buffer for JSON instead of string concatenation
// This eliminates O(n²) heap allocations from repeated += operations
// Format: [val1,val2,...,val24] where val can be "null" or a float
// Max size: 24 * 12 (max float string) + 25 (commas/brackets) + 1 = ~320 bytes
char json_h_buf[400];
int json_h_len = 0;
json_h_buf[json_h_len++] = '[';
for (int i = 0; i < 24; i++) {
if (h_cnt[i] > 0) {
float ha = h_sums[i] / (float)h_cnt[i];
id(hourly_avg_prices_kwh)[i] = ha;
if (ha < h_min_v) { h_min_v = ha; h_min_i = i; }
if (ha > h_max_v) { h_max_v = ha; h_max_i = i; }
char pb[12]; sprintf(pb, "%.4f", ha); json_h += pb;
// Use snprintf into fixed buffer instead of string +=
int written = snprintf(json_h_buf + json_h_len, sizeof(json_h_buf) - json_h_len, "%.4f", ha);
json_h_len += written;
raw_sum += ha; cnt_kwh++;
} else {
id(hourly_avg_prices_kwh)[i] = NAN;
json_h += "null";
json_h_len += snprintf(json_h_buf + json_h_len, sizeof(json_h_buf) - json_h_len, "null");
}
if (i < 23) json_h += ",";
if (i < 23) {
json_h_buf[json_h_len++] = ',';
}
json_h += "]";
id(json_hourly_prices_kwh).publish_state(json_h.c_str());
}
json_h_buf[json_h_len++] = ']';
json_h_buf[json_h_len] = '\0';
id(json_hourly_prices_kwh).publish_state(json_h_buf);
auto build32 = [&](int start) -> std::string {
std::string j = "[";
// v1.2.2: Optimized build32 using fixed buffer approach
// Format: [val1,val2,...,val32] where val can be "null" or a float
// Each segment is 32 values, max size ~420 bytes per segment
// Note: id() returns pointer, so use auto (pointer type) with ->
auto build32_fixed = [&](int start, auto sensor) {
char buf[450];
int len = 0;
buf[len++] = '[';
for (int i = start; i < start + 32; i++) {
if (!std::isnan(v[i])) {
char pb[16];
// Use 3 decimal places for negative prices to stay within
// the 255-character HA text sensor state limit.
// Positive prices keep full 4 decimal place precision.
if (v[i] < 0.0f)
snprintf(pb, sizeof(pb), "%.3f", v[i]);
len += snprintf(buf + len, sizeof(buf) - len, "%.3f", v[i]);
else
snprintf(pb, sizeof(pb), "%.4f", v[i]);
j += pb;
len += snprintf(buf + len, sizeof(buf) - len, "%.4f", v[i]);
} else {
j += "null";
len += snprintf(buf + len, sizeof(buf) - len, "null");
}
if (i < start + 31) j += ",";
if (i < start + 31) {
buf[len++] = ',';
}
j += "]"; return j;
// Safety: prevent buffer overflow
if (len >= (int)sizeof(buf) - 20) break;
}
buf[len++] = ']';
buf[len] = '\0';
sensor->publish_state(buf);
};
id(json_15min_prices_kwh_p1_00_00_07_45).publish_state(build32(0));
id(json_15min_prices_kwh_p2_08_00_15_45).publish_state(build32(32));
id(json_15min_prices_kwh_p3_16_00_23_45).publish_state(build32(64));
build32_fixed(0, id(json_15min_prices_kwh_p1_00_00_07_45));
yield(); // v1.2.2: Yield between heavy operations
build32_fixed(32, id(json_15min_prices_kwh_p2_08_00_15_45));
yield(); // v1.2.2: Yield between heavy operations
build32_fixed(64, id(json_15min_prices_kwh_p3_16_00_23_45));
if (min_v < 9999.0f) {
id(min_price).publish_state(min_v);
@@ -1623,8 +1759,12 @@ script:
id(current_price_status_str) = (cur >= 0) ? "Valid" : "Missing";
}
// v1.2.2: Log heap at end
ESP_LOGI("eprices", "recompute_today: done, free heap = %u bytes", heap_caps_get_free_size(MALLOC_CAP_8BIT));
# ---------------------------------------------------------------------------
# RECOMPUTE TOMORROW
# v1.2.2: Optimized JSON string building to prevent heap fragmentation
# ---------------------------------------------------------------------------
- id: recompute_tomorrow
then:
@@ -1632,6 +1772,9 @@ script:
int n = id(tomorrow_entry_count);
if (n == 0) { ESP_LOGW("eprices", "recompute_tomorrow: no entries"); return; }
// v1.2.2: Log heap at start
ESP_LOGI("eprices", "recompute_tomorrow: starting, free heap = %u bytes", heap_caps_get_free_size(MALLOC_CAP_8BIT));
// NOTE: same DST fall-back caveat as recompute_today for legacy grid.
id(tomorrow_hourly_prices).assign(96, NAN);
for (int i = 0; i < n; i++) {
@@ -1662,47 +1805,68 @@ script:
float h_min_v = 9999.0f, h_max_v = -9999.0f;
int h_min_i = 0, h_max_i = 0;
double raw_sum = 0.0; int cnt_kwh = 0;
std::string json_h = "[";
// v1.2.2: Pre-allocate fixed buffer for JSON instead of string concatenation
// This eliminates O(n²) heap allocations from repeated += operations
char json_h_buf[400];
int json_h_len = 0;
json_h_buf[json_h_len++] = '[';
for (int i = 0; i < 24; i++) {
if (h_cnt[i] > 0) {
float ha = h_sums[i] / (float)h_cnt[i];
id(tomorrow_hourly_avg_prices_kwh)[i] = ha;
if (ha < h_min_v) { h_min_v = ha; h_min_i = i; }
if (ha > h_max_v) { h_max_v = ha; h_max_i = i; }
char pb[12]; sprintf(pb, "%.4f", ha); json_h += pb;
int written = snprintf(json_h_buf + json_h_len, sizeof(json_h_buf) - json_h_len, "%.4f", ha);
json_h_len += written;
raw_sum += ha; cnt_kwh++;
} else {
id(tomorrow_hourly_avg_prices_kwh)[i] = NAN;
json_h += "null";
json_h_len += snprintf(json_h_buf + json_h_len, sizeof(json_h_buf) - json_h_len, "null");
}
if (i < 23) json_h += ",";
if (i < 23) {
json_h_buf[json_h_len++] = ',';
}
json_h += "]";
id(json_tomorrow_hourly_prices_kwh).publish_state(json_h.c_str());
}
json_h_buf[json_h_len++] = ']';
json_h_buf[json_h_len] = '\0';
id(json_tomorrow_hourly_prices_kwh).publish_state(json_h_buf);
auto build32 = [&](int start) -> std::string {
std::string j = "[";
// v1.2.2: Optimized build32 using fixed buffer approach
// Note: id() returns pointer, so use auto (pointer type) with ->
auto build32_fixed = [&](int start, auto sensor) {
char buf[450];
int len = 0;
buf[len++] = '[';
for (int i = start; i < start + 32; i++) {
if (!std::isnan(v[i])) {
char pb[16];
// Use 3 decimal places for negative prices to stay within
// the 255-character HA text sensor state limit.
// Positive prices keep full 4 decimal place precision.
if (v[i] < 0.0f)
snprintf(pb, sizeof(pb), "%.3f", v[i]);
len += snprintf(buf + len, sizeof(buf) - len, "%.3f", v[i]);
else
snprintf(pb, sizeof(pb), "%.4f", v[i]);
j += pb;
len += snprintf(buf + len, sizeof(buf) - len, "%.4f", v[i]);
} else {
j += "null";
len += snprintf(buf + len, sizeof(buf) - len, "null");
}
if (i < start + 31) j += ",";
if (i < start + 31) {
buf[len++] = ',';
}
j += "]"; return j;
// Safety: prevent buffer overflow
if (len >= (int)sizeof(buf) - 20) break;
}
buf[len++] = ']';
buf[len] = '\0';
sensor->publish_state(buf);
};
id(json_tomorrow_15min_prices_kwh_p1_00_00_07_45).publish_state(build32(0));
id(json_tomorrow_15min_prices_kwh_p2_08_00_15_45).publish_state(build32(32));
id(json_tomorrow_15min_prices_kwh_p3_16_00_23_45).publish_state(build32(64));
build32_fixed(0, id(json_tomorrow_15min_prices_kwh_p1_00_00_07_45));
yield(); // v1.2.2: Yield between heavy operations
build32_fixed(32, id(json_tomorrow_15min_prices_kwh_p2_08_00_15_45));
yield(); // v1.2.2: Yield between heavy operations
build32_fixed(64, id(json_tomorrow_15min_prices_kwh_p3_16_00_23_45));
if (min_v < 9999.0f) {
id(tomorrow_min_price).publish_state(min_v);
@@ -1733,6 +1897,9 @@ script:
id(tomorrow_current_price_status_str) = (cur >= 0) ? "Valid" : "Missing";
id(tomorrow_last_update_attempt) = id(ha_time).now().timestamp;
// v1.2.2: Log heap at end
ESP_LOGI("eprices", "recompute_tomorrow: done, free heap = %u bytes", heap_caps_get_free_size(MALLOC_CAP_8BIT));
# ---------------------------------------------------------------------------
# MIDNIGHT BRIDGE
# ---------------------------------------------------------------------------
@@ -2103,7 +2270,7 @@ script:
id(today_current_max_hourly_price_percentage).publish_state(NAN);
id(last_update_success) = false;
id(update_status_message) = "Cleared awaiting NVS or HTTP";
id(json_hourly_prices_kwh).publish_state("[]");
id(json_hourly_prices_kwh).publish_state("");
id(json_15min_prices_kwh_p1_00_00_07_45).publish_state("");
id(json_15min_prices_kwh_p2_08_00_15_45).publish_state("");
id(json_15min_prices_kwh_p3_16_00_23_45).publish_state("");
@@ -2136,7 +2303,7 @@ script:
id(tomorrow_min_hourly_price).publish_state(NAN);
id(tomorrow_max_hourly_price).publish_state(NAN);
id(tomorrow_current_max_hourly_price_percentage).publish_state(NAN);
id(json_tomorrow_hourly_prices_kwh).publish_state("[]");
id(json_tomorrow_hourly_prices_kwh).publish_state("");
id(json_tomorrow_15min_prices_kwh_p1_00_00_07_45).publish_state("");
id(json_tomorrow_15min_prices_kwh_p2_08_00_15_45).publish_state("");
id(json_tomorrow_15min_prices_kwh_p3_16_00_23_45).publish_state("");