Releases: SkiLtY/ha-ep-cube
Release list
v1.3.0
⚡ EP Cube Integration v1.3.0 — Dashboard Refresh + Signed Grid Net
TL;DR — single-session UX overhaul. (1) Dashboard restructured into three tabs (Now / Today / Control) — the single Overview mixed instant gauges with daily kWh rollups and control widgets in one viewport. (2) Now tab swaps the SC/SS instant percentage gauges (which pinned at 100% most of the day because a 20 kWh battery buffers the difference) for live power gauges in W: Solar, Battery, Grid. (3) Two new derived sensors (
grid_net_today+grid_net_yesterday) give a single signed-kWh view, mirroring the Now-tab Grid gauge's centered-needle convention. (4) SC + SS today/yesterday now return0.0instead ofunknownwhen their divisor is below the jitter floor — fixes the "Entity is non-numeric" overlay that hit gauges every morning before sunrise.
✨ What's new
Signed grid net — two new sensors
| Sensor | State class | Source |
|---|---|---|
sensor.ep_cube_grid_net_today |
TOTAL (signed kWh) | gridelectricityfrom − gridelectricityto from today bucket |
sensor.ep_cube_grid_net_yesterday |
TOTAL (signed kWh) | Same against yesterday bucket |
Positive = net importer for the period; negative = net exporter. The existing monotonic grid_import_* / grid_export_* sensors stay (Energy Dashboard wiring needs TOTAL_INCREASING) — the new pair is purely for at-a-glance dashboard gauges. Translations shipped for en / de / it / nl.
Dashboard refresh — three tabs
The single Overview view mixed timescales (instant gauges next to daily kWh totals next to control widgets), making it hard to read at a glance. Restructured into three tabs:
Now — what's happening right this second
- Three live power gauges in W (Solar / Battery / Grid), refreshed on the 30s coordinator cadence with no dead-band wrapping — the jitter IS the live feedback
- Power-flow card
- Battery status
Today — daily rollups, mirroring HA's native Energy tab
- Three gauges across the top (Grid net / Self-consumption / Self-sufficiency), refreshed every 5 min from the stats coordinator
- Energy today + Energy yesterday entity lists
- Matching three-gauge row above Energy yesterday so the rollup view is symmetric
Control — operating mode + mode-specific settings (unchanged behaviour, just moved into its own tab)
Unified colour palette
Replaced the per-gauge ad-hoc colouring with a consistent palette across all signed gauges:
- Solar — Material Design Green (#43a047) — renewable generation
- Battery — purple (#8353d1) when discharging (< 0, energy out), blue (#488fc2) when charging (> 0, energy in)
- Grid — purple (#8353d1) when exporting (< 0, energy out), blue (#488fc2) when importing (> 0, energy in)
- Percentage gauges (SC / SS today and yesterday) — Material green throughout, no severity bands
Purple/blue carries the same "energy going out" / "energy coming in" meaning on both Battery and Grid gauges, so a glance tells you which way energy flows regardless of which gauge you're reading. Matches HA's Energy Dashboard convention (purple = returned, blue = consumed). The previous 7-stop and 14-stop gradient experiments were dropped — interesting visually but added no signal users couldn't read from the numeric value.
🔧 Behaviour changes
- SC + SS today/yesterday now return
0.0instead ofunknownwhen their divisor (solar / load in kWh) is below the jitter floor (~0.05 kWh). HA's gauge card surfacesunknownas an "Entity is non-numeric" error overlay, which made those gauges error-out every morning before sunrise. Genuine missing-data states (no stats fetched yet / bucket keys absent / malformed values) still returnunknown— that signal is preserved for when something's actually wrong upstream. - Energy today entity list was pointing at the back-compat-preserved
sensor.ep_cube_self_consumption, which reads 0 on cube firmware02200242022220260515+(the cube stopped exposingselfHelpRateonhomeDeviceInfoin that build). Swapped to the new derivedsensor.ep_cube_self_consumption_todayand added aSelf-sufficiencyrow. - Energy yesterday entity list gains the matching
Self-consumptionandSelf-sufficiencyrows it was missing.
🧪 Tests
Suite grows from 216 → 223 cases (+7). All in tests/test_derived_pct_sensors.py:
- 7 new cases ×
_grid_net: net-importer, net-exporter, balanced, empty/missing/non-numeric handling, bucket-routing - 2 existing SC + SS jitter-floor tests updated to assert
== 0.0instead ofis None
All green on Python 3.12 (validate.yml).
📦 Upgrading
- HACS users: bump v1.2.0 → v1.3.0 in the HACS UI. Restart HA.
- Manual users: re-copy
custom_components/ep_cube/from the release zip. Restart HA. - If you use the dashboard YAML (
dashboards/ep_cube.yaml): the layout has shifted from a single Overview view to three tabs. Easiest path is to repaste the file via Raw configuration editor. If you customised the dashboard, the three new view blocks (- title: Now/- title: Today/- title: Control) are pasteable independently.
🛣 What's next
- v1.4 — lifetime totals (Phase 4.2 Tier 3 — RestoreSensor + coordinator-startup state-seeding) + eco metrics (
coal/treeNumfromqueryDataElectricityV2). Originally pencilled for v1.3 but today's UX work filled the slot; the roadmap items roll forward unchanged. - HACS Default merge — hacs/default#8364 is in queue (834 PRs deep at submission; expected wait weeks-to-months). All bot checks green.
- v1.x — HomeAssistant-OctopusEnergy consumption swap, gated on Octopus Home Mini arrival (ETA 2026-06-17 → 2026-07-01).
☕ Support
If this saves you the hours, consider tossing a ko-fi in the tank. Thanks!
Not affiliated with or endorsed by Canadian Solar or EP Cube.
v1.2.0
🪜 EP Cube Integration v1.2.0 — Mislabel Fix + Cube-Native Rollups + Instant Tiles
TL;DR — three additive threads in one release. (1) The cube's
selfHelpRatefield has been mislabeled as "Self-consumption" since v0.5; it actually reports self-sufficiency. Display name corrected, entity_id preserved for back-compat. (2) Eight new cube-native monthly + yearly rollups replace the HA-sideutility_meterhelpers that previous releases shipped in the example package — more accurate, no drift on month/year roll. (3) Three new instant-KPI sensors + dashboard gauges answer "what's the house doing right now?" without users reading the power-flow card.
✨ What's new
15 new sensors, all additive — no new cloud calls, all derive from existing polls.
Self-consumption / sufficiency surface — corrected + expanded
| Sensor | Unit | Source |
|---|---|---|
sensor.ep_cube_self_sufficiency_pct |
% | Cube's selfHelpRate (renamed from self_consumption_pct; unique_id preserved) |
sensor.ep_cube_self_consumption_today |
% | (solar_today − grid_export_today) / solar_today × 100 |
sensor.ep_cube_self_consumption_yesterday |
% | Same against yesterday bucket |
sensor.ep_cube_self_sufficiency_today |
% | (load_today − grid_import_today) / load_today × 100 |
sensor.ep_cube_self_sufficiency_yesterday |
% | Same against yesterday bucket |
Verified 2026-06-10 against the live cube: on a 0.23 kWh import + 14.36 kWh load + 11.93 kWh export + 29.95 kWh solar day, the existing sensor read 99% (= 98.4% self-sufficiency, which it is) while true self-consumption was only ~60% (11.93/29.95 exported). The new pair surfaces both numbers honestly.
Cube-native monthly + yearly rollups
| Sensor | Unit | Source |
|---|---|---|
sensor.ep_cube_grid_import_month / _year |
kWh | queryDataElectricityV2 scope=2 / scope=3 |
sensor.ep_cube_grid_export_month / _year |
kWh | Same |
sensor.ep_cube_solar_month / _year |
kWh | Same |
sensor.ep_cube_backup_loads_month / _year |
kWh | Same |
All state_class=TOTAL (not TOTAL_INCREASING) because they reset at the month/year boundary — HA's statistics engine handles the snap-back as a normal delta rather than spurious counter resets. More accurate than the HA-side utility_meter helpers that previous releases shipped in the example package: cube-native rollups don't drift if HA is down at month/year roll, because the cube itself owns the boundary snap-back.
Instant-KPI tiles
| Sensor | Unit | Behaviour |
|---|---|---|
sensor.ep_cube_self_consumption_right_now |
% | (solar_w − export_w) / solar_w × 100, unknown below 50 W solar |
sensor.ep_cube_self_sufficiency_right_now |
% | (load_w − import_w) / load_w × 100, unknown below 50 W load |
sensor.ep_cube_grid_flow_right_now |
W | Signed; ±200 W dead-band rounded to 0 to stop the gauge twitching on routine in-house transients (kettle / microwave / fridge cycles) |
Dashboard grows a "Right now" horizontal-stack of three type: gauge cards above the power-flow card, with severity bands matching what the cube is doing (self-consumption / sufficiency green > 70%, yellow 30-70%, red < 30%; grid flow green when exporting, yellow on modest import, red on sustained import > 3 kW).
💥 What changes for existing users
The self-consumption rename is the only user-visible change. Existing installs:
- Display name changes from "Self-consumption" to "Self-sufficiency" automatically on update.
entity_idstayssensor.ep_cube_self_consumption—unique_idis preserved across the rename, so Energy Dashboard wiring, automations, templates, and history all keep working unchanged.- Fresh installs (post-v1.2) get
sensor.ep_cube_self_sufficiencyas the entity_id slug.
If you'd like to manually update the entity_id on an existing install: Settings → Devices & services → EP Cube → click the renamed sensor → settings (gear) → change the entity_id manually. Optional; the legacy slug stays sticky otherwise.
🔧 Behaviour changes
- Dashboard refresh:
dashboards/ep_cube.yamladds the three gauge cards in a "Right now" horizontal-stack at the top of the Overview view. - Example package cleanup:
examples/ha_config/packages/ep_cube.yamldrops theutility_meterrollups for solar / grid / backup / nonbackup (monthly + yearly). Use the new cube-native sensors directly. The battery charge/dischargeutility_meterrollups stay — the cube doesn't expose signed battery flow on the stats endpoint, so the client-side delta-tracker +utility_meterchain is still the only path for monthly/yearly battery totals. - Translations:
de/it/nlpreviously followed the English mislabel ("Eigenverbrauchsquote" / "Autoconsumo" / "Zelfverbruik"). Corrected to "Autarkiegrad" / "Autosufficienza" / "Zelfvoorziening". Plus 14 new entries per locale for the v1.2 additions.
🧪 Tests
Suite grows from 178 → 216 cases (+38). All in tests/test_derived_pct_sensors.py:
- 19 cases ×
_self_consumption_pct+_self_sufficiency_pct(kWh-domain bucket value_fns): normal-case math, divisor below jitter threshold → None, clamp to [0,100], empty bucket / missing field / non-numeric handling, bucket-routing - 19 cases ×
_instant_self_consumption_pct+_instant_self_sufficiency_pct+_instant_grid_flow_w(W-domain): pass-through, sub-50-W noise floor, ±200 W dead-band edge handling
All green on Python 3.12 (validate.yml).
📦 Upgrading
- HACS users: bump v1.1.3 → v1.2.0 in the HACS UI. Restart HA. The renamed sensor's display name updates immediately; entity_id stays put.
- Manual users: re-copy
custom_components/ep_cube/from the release zip. Restart HA. - If you use the example package (
examples/ha_config/packages/ep_cube.yaml): the file in this release drops 8utility_meterentries (solar/grid/backup × month/year). If you have customisations layered on top, merge them onto the v1.2 base. - If you use the dashboard YAML (
dashboards/ep_cube.yaml): the v1.2 file adds a "Right now" horizontal-stack of three gauges at the top. If you customised the dashboard, the gauges are pasteable as a single horizontal-stack block.
🛣 What's next
- v1.3 — lifetime totals (Phase 4.2 Tier 3 — RestoreSensor + coordinator-startup state-seeding) + eco metrics (
coal/treeNumfromqueryDataElectricityV2). - HACS Default merge — hacs/default#8364 is in queue (834 PRs deep at submission; expected wait weeks-to-months). All bot checks green.
- v1.x — HomeAssistant-OctopusEnergy consumption swap, gated on Octopus Home Mini arrival (ETA 2026-06-17 → 2026-07-01).
☕ Support
If this saves you the hours, consider tossing a ko-fi in the tank. Thanks!
Not affiliated with or endorsed by Canadian Solar or EP Cube.
v1.1.3
🪜 EP Cube Integration v1.1.3 — Hassfest Compliance
TL;DR — clearing the way for HACS Default submission.
validate.ymlhas been red since v1.0 becausepredbat_inverter_modeselect shipped translation keys that don't match Home Assistant's[a-z0-9-_]+rule. Dropping the offending state translation block — i18n on "Timed Export" → DE/IT/NL locale labels is lost (Eco stays "Eco" in every language regardless). No runtime behaviour change; this is a CI-pipeline fix.
🐛 What's fixed
Hassfest no longer fails on Invalid translation key 'Eco' at data['entity']['select']['predbat_inverter_mode']['state']. The select's wire-format values ("Eco" / "Timed Export") are dictated by Predbat — predbat/inverter.py:2212,2214 does case-sensitive string equality against "Eco" and "Timed Export", so we can't lowercase the state values without breaking the Predbat bridge. The only fix that satisfies both constraints is dropping the state translation block entirely. HA falls back to displaying the raw state values, which is fine since "Eco" is "Eco" in every locale anyway and "Timed Export" is a Predbat-specific technical term.
Lost: localized labels for "Timed Export" (Zeitgesteuerter Export / Esportazione temporizzata / Geplande export). If you'd like them back, the fix path is HA-side — open an issue against home-assistant/core to allow uppercase translation keys for entity state values, or against predbat/predbat to make Predbat's state matcher case-insensitive.
📦 Upgrading
- HACS users: bump v1.1.2 → v1.1.3. Restart HA. If you're in
de/it/nland yourselect.ep_cube_predbat_inverter_modedropdown showed translated labels for "Timed Export", they'll revert to the raw English string. - Manual users: re-copy
custom_components/ep_cube/. Restart HA.
🛣 What's next
HACS Default submission immediately after this release lands — validate.yml goes green, the smoke test from v1.1.2 cleared the install path, and we have a real-cube-verified production deployment for ~3 weeks. Submission is the only remaining gate.
☕ Support
If this saves you the hours, consider tossing a ko-fi in the tank. Thanks!
Not affiliated with or endorsed by Canadian Solar or EP Cube.
v1.1.2
🪜 EP Cube Integration v1.1.2 — Dashboard Fix + Backup-Loads Clarification
TL;DR — small patch on top of v1.1.1, surfaced by the HACS pre-submission smoke test. One broken entity row in the bundled dashboard YAML, one README clarification about what
backup_todayactually measures. No code change, no behaviour change. Upgrade if you use the bundleddashboards/ep_cube.yamlor have ever wondered what "Backup loads today" means on your install.
🐛 What's fixed
dashboards/ep_cube.yaml — "Energy today" card showed "Entity not found" on the Self-consumption row. Referenced sensor.ep_cube_self_consumption_pct (matching the integration's translation_key) but HA derives entity-IDs from the entity's display name, not the translation key — so the actual ID is sensor.ep_cube_self_consumption. README already documents this slug-derivation gotcha for users editing dashboards; the bundled YAML was the one place that didn't follow its own advice.
Bug was present from v1.1.0 (when the Energy today card was first added). Silent on existing installs unless you scrolled past the row.
📝 What's documented
README — backup-loads semantics callout. New > [!NOTE] admonition under the Sensors table explaining that backup_today / backup_yesterday count kWh delivered through the cube's backup-output terminal, not "loads that stayed up during an outage". Whether this reads as whole-house or essential-circuits-only depends entirely on what your installer wired through the backup panel. Adds context that outage-resilience under UK G99/G100 regs needs an external EPS Gateway — the cube physically refuses to supply via the backup terminal during a grid outage without one, even though it continues to meter kWh through it under normal operation.
🧪 HACS pre-submission smoke test
This release was triggered by the HACS Custom Repository install-path verification on a throwaway HA instance. Findings:
- ✅ HACS install via official one-liner: clean
- ✅ Custom Repository → Integration download: clean
- ✅ Config flow against live cube (multi-region + captcha + bearer): clean
- ✅ All 33 entities (26 sensors + 5 controls + 2 Predbat stubs) populated with sane live data
- ✅ Logs clean, Repairs clean (Predbat-priority detector correctly silent without a Predbat container)
- ✅ Brand icon rendering on device detail page (HA core's
/api/brands/integration/ep_cube/icon.pngserving fromcustom_components/ep_cube/brand/) ⚠️ HACS store list shows "icon not available" — HACS fetches store icons direct frombrands.home-assistant.ioCDN, bypassing HA's brands endpoint. Not fixable from the integration side; resolves itself once HACS Default is accepted and we re-submit tohome-assistant/brands(post-submission).- 🐛 Dashboard YAML self-consumption row → fixed above
📦 Upgrading
- HACS users: bump v1.1.1 → v1.1.2. If you've pasted the dashboard YAML, re-open it via Raw Configuration Editor and re-paste from
dashboards/ep_cube.yaml(or hand-edit the one line). Otherwise nothing to do. - Manual users: re-copy
custom_components/ep_cube/from the release zip if you want the version bump in HA's UI. No functional difference vs v1.1.1 in the integration code itself.
🛣 What's next
- HACS Default submission — smoke test passed (this release is the "fix the hiccups" pass), submitting next.
- v1.2 — cube-native monthly + annual rollups (Phase 4.2 Tier 3) + dashboard KPI tile refresh.
- v1.3 — lifetime totals (Phase 4.2 Tier 4) + eco metrics (Tier 5).
☕ Support
If this saves you the hours, consider tossing a ko-fi in the tank. Thanks!
Not affiliated with or endorsed by Canadian Solar or EP Cube.
v1.1.1
🪜 EP Cube Integration v1.1.1 — Stale-Entity Cleanup
TL;DR — small patch on top of v1.1.0. The two sensors deleted in v1.1.0 (
grid_today+nonbackup_today) were leaving behindunavailableghost rows in the HA entity registry until the user manually deleted them. This release sweeps those rows on every setup. Upgrade and the ghosts disappear after the next HA restart. No behavioural change otherwise.
🧹 What's fixed
After upgrading to v1.1.0, users would see two leftover entities in HA's UI:
sensor.ep_cube_grid_today(or whatever name your HA had localised it to)sensor.ep_cube_non_backup_loads_today
Both stuck on unavailable because the integration no longer registers them. v1.1.1 adds a one-time sweep on every config-entry setup that removes any registry row whose unique_id matches the deleted set:
<entry_id>_grid_today(deleted v1.1.0 — direction-ambiguous)<entry_id>_nonbackup_today(deleted v1.1.0 — zero on EU firmware)
Scoped to the config entry only, so a multi-entry / multi-cube setup where one entry was upgraded ahead of others doesn't yank entries from siblings. Idempotent — running against an already-clean registry is a no-op.
🧪 Tests
Suite grows 176 → 178 cases. 2 new in tests/test_setup.py::TestStaleEntityPurge:
test_pre_existing_grid_today_entry_purged_on_setup— seeds the registry with both ghost rows, runs setup, asserts both are gone afterwardstest_purge_is_idempotent_when_registry_clean— fresh install with no ghosts; setup still succeeds, no-op purge
All 178 green on Python 3.12.
📦 Upgrading
- HACS users: bump v1.1.0 → v1.1.1. Restart HA — that's when the sweep runs. Refresh the Devices & services page and the ghost rows are gone.
- Manual users: re-copy
custom_components/ep_cube/from the release zip. Restart HA.
🛣 What's next
Roadmap unchanged from v1.1.0:
- v1.2 — cube-native monthly + annual rollups (Phase 4.2 Tier 3)
- v1.3 — lifetime totals (Phase 4.2 Tier 4) + eco metrics (Tier 5)
- HACS Default submission — held until v1.1 has bedded in
☕ Support
If this saves you the hours, consider tossing a ko-fi in the tank. Thanks!
Not affiliated with or endorsed by Canadian Solar or EP Cube.
v1.1.0
🪜 EP Cube Integration v1.1.0 — Stats Endpoint + Clean Grid Import/Export Split
TL;DR — 6 new sensors backed by the cube's own electricity-rollup endpoint (
queryDataElectricityV2). Grid energy today is now correctly split into import + export rather than the single direction-ambiguous counter v1.0 inherited from v0.5.0. Yesterday's energy quartet is genuinely new surface for daily-summary automations.
✨ What's new
Six new sensors fed by a second background coordinator polling the cube's stats endpoint every 5 min for today + at slower cadences for the wider rollups. ≈320 cloud calls/day total, ~5× the existing homeDeviceInfo budget.
| Sensor | Unit | State class | Source |
|---|---|---|---|
sensor.ep_cube_grid_import_today |
kWh | TOTAL_INCREASING |
gridElectricityFrom (scope=1 today) |
sensor.ep_cube_grid_export_today |
kWh | TOTAL_INCREASING |
gridElectricityTo (scope=1 today) |
sensor.ep_cube_grid_import_yesterday |
kWh | TOTAL |
gridElectricityFrom (scope=1 yesterday) |
sensor.ep_cube_grid_export_yesterday |
kWh | TOTAL |
gridElectricityTo (scope=1 yesterday) |
sensor.ep_cube_solar_yesterday |
kWh | TOTAL |
solarElectricity (scope=1 yesterday) |
sensor.ep_cube_backup_loads_yesterday |
kWh | TOTAL |
backUpElectricity (scope=1 yesterday) |
Today's pair (grid_import_today / grid_export_today) is the direct fix for the v0.5.0 limitation that v1.0 inherited: the cube's homeDeviceInfo field gridElectricity is direction-ambiguous — equals export on export-heavy days, import on import-heavy days. The new pair reads from the stats endpoint which exposes both directions cleanly, so HA's Energy Dashboard wires up correctly without Riemann tricks.
The yesterday quartet is genuinely new — no previous integration version surfaced it. Useful for "did I export much yesterday?" automations and for Energy Dashboard back-fill semantics.
💥 Breaking changes
- Removed
sensor.ep_cube_grid_today— the direction-ambiguous counter. Replaced bygrid_import_today+grid_export_today. If you referencegrid_todayin automations / templates / dashboards, update them to the new pair. - Removed
sensor.ep_cube_nonbackup_today— reported 0 across every window on EU firmware. Misleading. If you have a non-backed-loads-aware setup outside the EU, please open an issue — happy to restore as a region-gated sensor.
🔧 Behaviour changes
- Dashboard refresh:
dashboards/ep_cube.yamlnow has Energy today + Energy yesterday entities cards between the Battery card and the Operating-mode picker. Drop-in users get the new sensors surfaced without further wiring. - Stats coordinator runs alongside the existing 60 s
homeDeviceInfocoordinator. Today's bucket polls every 5 min; month every 1 h; year every 6 h; lifetime totals every 12 h; yesterday re-fetches only on HA-local date roll + once at startup. Best-effort: if a wider bucket fetch fails the previous cached value stays put and the integration doesn't flag.
🧪 Tests
Suite grows from 160 → 176 cases. 8 new in tests/test_api_client.py::TestGetStats covering the helper (URL composition per scope, lowercase normalisation, 403 reauth retry path, US /app-api prefix). 8 new in tests/test_stats_coordinator.py covering the cadence logic (per-bucket refresh thresholds, day-roll yesterday refetch, today-failure-raises-vs-non-today-best-effort error handling). All green on Python 3.12.
📦 Upgrading
- HACS users: bump v1.0.0 → v1.1.0 in the HACS UI. Restart HA. Update any automation / template / dashboard reference to
sensor.ep_cube_grid_todayto use the new import/export pair. - Manual users: re-copy
custom_components/ep_cube/from the release zip. Restart HA.
🛣 What's next
- v1.2 — cube-native monthly + annual rollups (Phase 4.2 Tier 3). Will replace the
utility_meterhelpers inexamples/ha_config/packages/. - v1.3 — lifetime totals (Phase 4.2 Tier 4) + eco metrics (Phase 4.2 Tier 5).
- HACS Default submission — held until v1.1 has bedded in.
- v1.x — HomeAssistant-OctopusEnergy — half-hourly smart-meter consumption replaces Riemann
load_today. Gated on Octopus Home Mini arrival.
☕ Support
If this saves you some hours, consider tossing a ko-fi in the tank. Thanks!
Not affiliated with or endorsed by Canadian Solar or EP Cube.
v1.0.0
🪜 EP Cube Integration v1.0.0 — Opinionated Predbat Bridge (Breaking Change)
TL;DR — the TOU editor card and
ep_cube.set_tou_scheduleservice are gone. From v1.0 the integration treats Predbat as the single source of truth for time-of-use control. If your cube has user-painted TOU slots while Predbat is running, an HA Repair issue offers a one-click wipe. If you want manual TOU control without Predbat, use the EP Cube mobile app or Bobsilvio/epcube.
Why this is breaking
The TOU editor existed as a relief valve for "Predbat isn't doing what I want right now." But overriding at the cube creates state divergence: Predbat re-plans against its model of the cube while the cube is actually running a hand-painted schedule the model doesn't know about. The right fix for "Predbat isn't doing what I want" is to tune Predbat (metric10_weight, metric_battery_value_scaling, etc.) — not to override at the device.
The honest product framing for v1.0:
- Predbat + variable tariff users (the integration's primary audience): everything they need is in the shim's
charge_start/discharge_start/charge_freeze/idleservices. They never neededset_tou_scheduleand shouldn't be tempted by it. - Fixed-tariff non-Predbat users (the audience the editor was secretly built for): better served by Bobsilvio's
bobsilvio/epcubeHACS integration, or just the EP Cube mobile app. Neither has an "is Predbat tuned right?" problem to fight.
💥 Breaking changes
- Removed
ep_cube.set_tou_scheduleservice. Any automation calling it will fail. Migrate to one of:- EP Cube mobile app for ad-hoc manual TOU painting.
- Bobsilvio's epcube integration if you want manual TOU as a permanent HA-native control surface (deliberately different design from this one).
- Removed
www/ep-cube-tou-editor.js(the Lovelace editor card). If you have it installed:- Settings → Dashboards → Resources → delete the
/local/ep-cube-tou-editor.jsentry. - The
dashboards/ep_cube.yamlno longer references it. If you customised the dashboard, thetype: custom:ep-cube-tou-editorcard block can be removed.
- Settings → Dashboards → Resources → delete the
- Removed
tou_scheduleandtou_pricesextra-state-attributes fromselect.ep_cube_operating_mode. If you read these from automations, switch to the EP Cube app or a different integration for the underlying data. parse_tou_schedule()andparse_tou_prices()helpers removed fromservices.py. They were public-but-internal — listed here for anyone who imported them in a custom module.
✨ New: HA Repair flow for the Predbat-priority transition
When the coordinator detects:
- The cube has at least one non-shim slot in any of the six non-DST tier lists (workday + weekend × peak / mid-peak / off-peak), AND
- At least one entity in the
predbat.*domain exists on this HA instance,
…an HA Repair issue appears (Settings → System → Repairs) titled "Manual TOU slots on the cube while Predbat is running." Selecting Submit wipes the six non-DST tier lists on the cube in a single guided flow:
- Abandons any in-flight Predbat shim override first (user-wins).
- Reads current state, strips stale shim-signature slots, builds an overrides dict that empties the six tier lists.
- Uses the 2-write dance when the cube is in non-TOU mode (write A flips to TOU and lands the empty lists; write B flips back to the original mode). Same provenance and quirks as the shim's existing dance — see services.py
PredbatShim. - Triggers a coordinator refresh — the detection helper auto-clears the issue once it sees the wiped snapshot.
DST tier lists are deliberately left intact. They're shared-across-year state; the conflict surface lives entirely in the six non-DST lists.
The repair flow is idempotent and non-destructive without confirmation — the issue surfaces, the user reads, the user clicks. No auto-wipe.
🧹 What was cleaned up
custom_components/ep_cube/services.pylost ~400 lines:handle_set_tou_schedule+SET_TOU_SCHEDULE_SCHEMA+_PRICES_SCHEMA+_USER_SLOT_RE+_parse_user_slot+_validate_day_profile+_user_slot_to_wire+_slot_wire_to_user+_existing_house_price+_DEFAULT_PRICE_BY_USER_FIELD+parse_tou_schedule+parse_tou_prices.tests/test_set_tou_schedule.pyremoved (~900 lines, 30+ cases). The 2-write dance is still under test coverage viatests/test_shim.py(the shim uses the same pattern for charge / discharge / freeze).const.DEFAULT_TIER_PRICE_*removed (onlyset_tou_schedulereferenced them).services.yamllost theset_tou_scheduleentry; the shim services remain.
📦 What's kept
- Predbat shim:
charge_start,charge_stop,discharge_start,discharge_stop,charge_freeze,discharge_freeze,idle— unchanged. debug_freezediagnostic service — unchanged.- All 22 sensors, 1 select (operating mode), 2 numbers (reserve SoCs), 2 switches (grid-charge allow + DST) — unchanged.
select.ep_cube_predbat_inverter_mode+number.ep_cube_predbat_charge_limit(Predbat-entity-first stubs from v0.7 line) — unchanged.parse_tou_schedulecallers in dashboards / cards — none in the public repo; if you forked the editor card it'll stop working without the helpers.
🧪 Tests
Suite shrinks from 229 → ~200 with the deletion. The repair flow itself gets light coverage in this release; the cube-write path is exercised by the existing shim tests. Suite is green on Python 3.12.
📦 Upgrading
- HACS users: bump v0.7.0 → v1.0.0 in the HACS UI. Restart HA. After restart, if the cube has manual TOU slots and you run Predbat, an HA Repair will appear — click through to wipe them.
- Manual users: re-copy
custom_components/ep_cube/from the release zip. If you'd installedwww/ep-cube-tou-editor.js, remove it from/config/www/and remove the Lovelace resource entry. Restart HA. - Automation cleanup: search your
automations.yamland node-red flows forep_cube.set_tou_schedule— these calls now fail. Decide per-call: drop entirely (let Predbat handle it), move to the EP Cube app, or migrate to Bobsilvio's integration.
🛣 What's next
- v1.0.x —
queryDataElectricityV2cloud-stats expansion (signed grid import/export,*_yesterdayvariants, lifetime totals withRestoreSensorseeding) — waiting on a mitmproxy capture session. - v1.1 — HACS Default submission.
- v1.x — HomeAssistant-OctopusEnergy — half-hourly smart-meter consumption replaces Riemann
load_today. Gated on Octopus Home Mini arrival.
Why v1.0 not v0.8
This is the first release whose scope matches the original product statement: "let Predbat control the EP Cube." Earlier versions stopped short of being opinionated about it and shipped escape hatches that, in practice, made the Predbat path harder to reason about. v1.0 deletes the escape hatches and replaces them with a one-click handoff.
☕ Support
If this saves you the mitmproxy sessions and Python hours it took to build, consider tossing a ko-fi in the tank. Thanks!
Not affiliated with or endorsed by Canadian Solar or EP Cube.
v0.7.0
💷 EP Cube Integration v0.7.0 — Per-Tier Rate Entry on TOU Card
Phase 4.1++ closes the TOU editor's last functional gap: per-tier prices are now editable from the card, not just preserved server-side. The cube's internal tier prices are cosmetic for Predbat users (Predbat optimises against your real Octopus tariff), but for fixed-tariff and non-Predbat users they're load-bearing — and previously the only way to set them was via the EP Cube mobile app.
✨ What's new
-
Per-tier rate inputs in the editor card — three p/kWh inputs per tab (Peak / Mid-peak / Off-peak × Workday / Weekend), inline with each tier's "Add slot" button. Hydrates on first load from a new
tou_pricesextra-state-attribute onselect.ep_cube_operating_mode. Type a value to override; leave blank to keep the cube's existing per-tier price. Placeholder shows the cube's factory default for empty tiers (40/25/5p/kWh for peak / mid-peak / off-peak). -
Optional
pricesarg onep_cube.set_tou_schedule— pass a dict with up to 6 keys (peak_workday,mid_peak_workday,off_peak_workday,peak_weekend,mid_peak_weekend,off_peak_weekend) to override per-tier prices in p/kWh. Range 0-999. Tiers omitted from the dict fall through to preserve-from-cube semantics (existing behaviour). Backwards-compatible — pre-v0.7 callers withoutpricessee no behaviour change. -
parse_tou_prices()helper +tou_pricesattribute — sibling toparse_tou_schedule/tou_schedule. Returns the price of the first non-shim slot in each tier, orNoneif the tier is empty (so the card can show a placeholder instead of a misleading "real" value). Automations can read it directly from the select entity's attributes.
🐛 Fixed
- Card hydration race — the card's first-load guard used to flip
trueas soon as the schedule attribute appeared, even iftou_priceswasn't there yet. Result: rate inputs stayed blank forever until the user clicked "Reload from cube" manually. v0.7.0 only sets the guard once prices have successfully landed, so schedule-only ticks keep retrying.
🧹 Cleanup
- Dropped the v0.6.3 legacy synthetic-price tokens (
0.01/0.20/1.00) from the strip set — the one-release migration window has expired. Any pre-v0.6.3 in-flight Predbat overrides have long since rotated out. Slots with these prices on your cube would now look like genuine user slots, which was the whole reason for the v0.6.3 migration in the first place (they collided with realistic fixed-tariff prices and were silently dropping user data from the editor card's hydrated view).
📋 Service contract (new field highlighted)
service: ep_cube.set_tou_schedule
data:
peak_workday: ["16:00-19:00"]
mid_peak_workday: ["04:30-16:00", "19:00-23:59"]
off_peak_workday: ["00:30-04:30"]
peak_weekend: []
mid_peak_weekend: []
off_peak_weekend: []
switch_to_tou: false # optional
prices: # ✨ NEW in v0.7.0, optional
off_peak_workday: 8.5 # p/kWh
peak_workday: 28.5
device_id: "5613" # optionalEach prices key independently routes through: (1) explicit value → (2) cube's existing price → (3) factory default. Pass none of them and behaviour matches pre-v0.7. Pass some of them and only those tiers get overridden.
📏 Precision note
The cube's wire format is 2dp on the £-scale = 1p resolution per tier. Sub-p input (e.g. 19.25 p/kWh from an Octopus Agile rate) rounds to 19 on save. Documented in the service description, the card hint text, and services.py. For Predbat users this is irrelevant (Predbat optimises against the real Octopus rate, not the cube's internal prices). For fixed-tariff users UK tariffs are usually quoted to whole pence anyway.
🧪 Tests
Suite is now 229 tests (was 200 in v0.6.4):
- 23 new cases in
tests/test_set_tou_schedule.pycovering thepricesarg (full override, partial preservation, empty-cube-tier with explicit price, schema validation, sub-penny rounding, string coercion, 2-write dance interaction, zero-price accepted) and the newparse_tou_priceshelper +_first_non_shim_priceshared scanner. - 3 hard-coded legacy synthetic prices in
tests/test_shim.pyreplaced withSHIM_PRICE_*constants. - 1 new case verifying legacy prices (
0.01/0.20/1.00) now flow through as user slots instead of being stripped.
All green on Python 3.12 in CI + local Synology container.
📦 Upgrading
- HACS users: bump from v0.6.4 → v0.7.0 in the HACS UI.
- Manual users: re-copy
custom_components/ep_cube/+www/ep-cube-tou-editor.jsfrom the release zip; bump the card's resource version (Settings → Dashboards → Resources → change/local/ep-cube-tou-editor.js?v=0.6.4to?v=0.7.0) so browsers re-fetch. - HA restart required — Python-side service + entity changes.
🛣 What's next
- v0.7.x — placeholder defaults driven by
predbat.metric_low_rate_threshold/high_rate_threshold(or the current Octopus rate via the BottlecapDave integration) instead of static constants. Chipped during v0.7.0 testing. - v0.7.x —
queryDataElectricityV2cloud-stats expansion (signed grid import/export,*_yesterdayvariants, lifetime totals withRestoreSensorseeding) — waiting on a mitmproxy capture session. - v0.8 — auto-paint TOU schedule from Predbat's rate thresholds + Agile forecast (one-click "match Predbat's bucketing").
☕ Support
If this saves you the mitmproxy sessions and Python hours it took to build, consider tossing a ko-fi in the tank. Thanks!
Not affiliated with or endorsed by Canadian Solar or EP Cube.
v0.6.4
🛠 EP Cube Integration v0.6.4 — TOU Schedule Edits Always Land
Fixes a real silent-drop bug surfaced during 2026-05-29 live-cube testing: editing the TOU schedule while the cube was in self-consumption or backup mode would claim "Schedule saved" but actually do nothing. The cube's documented TOU → non-TOU transition quirk extends to any schedule write made while the cube isn't already in TOU mode — the HTTP call returns 200 but the cube silently ignores the tier-list portion of the payload.
✨ What's new
-
2-write dance in
ep_cube.set_tou_schedule— when the cube is in non-TOU mode and the user doesn't tick "Switch to TOU when saving", the service now performs two writes:- Write A: flip to TOU mode + apply new tier lists (cube honours both because TOU → TOU never drops the write)
- Write B: flip back to the user's original mode (cube switches mode, drops the tier-list write — but the tier lists from A are already on the cube)
Net result: schedule edits always land on the cube regardless of current mode, with no user-visible mode change for the operator. Single-write behaviour preserved when the cube is already in TOU mode or when the user explicitly wants to end up in TOU.
-
"Clear all" button on the editor card — one-click wipe of every slot on both workday + weekend profiles. Confirmation modal before save. Useful for starting fresh, removing leftover shim slots from prior Predbat overrides, or preparing for an auto-paint pass (Phase 4.3 — coming v0.8). Calls the same
set_tou_scheduleservice with empty tier lists, just bypasses the manual remove-one-at-a-time loop.
🐛 Fixed
- Silent-drop on save while in non-TOU mode — edits to the TOU schedule no longer require the cube to be in TOU mode at save time. Previously you had to either (a) tick "Switch to TOU when saving" first, or (b) manually switch to TOU via the operating-mode dropdown, edit, then switch back. Now any save just works.
- Leftover shim slots from prior Predbat /
debug_freezeoverrides auto-clean on next save — because everyset_tou_schedulenow writes via TOU mode briefly, leftover shim-signature slots in the cube's TOU memory get stripped fromlive_cleanbefore write A goes out. So one save (even an empty one via the new "Clear all" button) clears any accumulated cruft.
🧪 Tests
Four new pytest cases in tests/test_set_tou_schedule.py::TestServiceDispatch covering the 2-write decision matrix:
- 2-write when cube is non-TOU + no
switch_to_touflag - Single write when
switch_to_tou=True(user wants TOU end-state) - Single write when cube is already in TOU mode
- The user's specific scenario: clearing slots from self-consumption mode lands correctly
Existing tests updated where the await-count assertion changed from 1 to 2. Suite now 183 tests, all green.
📦 Upgrading
- HACS users: bump from v0.6.3 → v0.6.4 in the HACS UI.
- Manual users: re-copy
custom_components/ep_cube/+www/ep-cube-tou-editor.jsfrom the release zip; browser hard-refresh for the card. - HA restart required — Python-side service changes.
📋 Behaviour change to note
Saves now cost 2 cloud writes (was 1) when the cube isn't already in TOU mode. Writes are cheap and infrequent (users edit schedules occasionally, not minutely), so this is well within the cube's tolerance — but worth flagging for anyone monitoring write rates.
If the cube rejects write B (rare), the cube is left in TOU mode rather than the user's original mode. The service raises HomeAssistantError with a message explaining this, so it's visible in the UI rather than silent.
🛣 What's next
- v0.7 — per-tier rate entry on the editor card (chipped); drop legacy shim-price tokens from migration window.
- v0.7 —
queryDataElectricityV2cloud-stats expansion. - v0.8 — auto-paint TOU schedule from Predbat's low-rate threshold + Agile forecast.
☕ Support
If this saves you the mitmproxy sessions and Python hours it took to build, consider tossing a ko-fi in the tank. Thanks!
Not affiliated with or endorsed by Canadian Solar or EP Cube.
v0.6.3
🎯 EP Cube Integration v0.6.3 — Shim Price Refactor
Small but important fix that closes a real footgun: the synthetic prices the Predbat shim used to tag its temporary override slots (0.01 / 0.20 / 1.00) collided with realistic UK tariff prices. A user on a fixed-rate tariff with 1.00 p/kWh peak slots would have those slots silently stripped from the TOU editor card's hydrated view (introduced in v0.6.2). Surfaced during a live debug_freeze test on 2026-05-29.
✨ What's new
- Shim synthetic prices migrated to
2.22/3.33/4.44— all above Agile's 100p/kWh daily cap, so no realistic UK retail tariff hits them. The repeating-digit pattern also makes shim slots visually identifiable if a user ever spots one in the EP Cube mobile app's schedule view. - One-release migration window —
_SHIM_PRICE_TOKENSkeeps the legacy values (0.01/0.20/1.00) in its match set so any leftover shim slots from a pre-v0.6.3 Predbat run still get stripped cleanly on next read. Legacy entries will be dropped in v0.7 once any in-flight overrides have rotated out of cube memory. - One new pytest case verifying both new + legacy synthetic prices are stripped (suite now 179 tests).
🐛 Fixed
- Editor card hiding genuine user slots at
1.00p/kWh — fixed-tariff users with realistic peak prices at the £1 cap would see those slots vanish from the hydrated card view. Migration window catches both old and new synthetic prices, so any leftover shim slots from prior Predbat runs are still cleaned up.
📦 Upgrading
- HACS users: bump from v0.6.2 → v0.6.3 in the HACS UI.
- Manual users: re-copy
custom_components/ep_cube/from the release zip. - HA restart required — Python-side constants change.
- No card change —
www/ep-cube-tou-editor.jsunchanged in this release.
📋 User-facing constraint update
Previously: "don't manually configure slots at 0.01 / 0.20 / 1.00."
Now: "don't manually configure slots with prices in the synthetic set." The synthetic set is currently {0.01, 0.20, 1.00, 2.22, 3.33, 4.44} during the v0.6.3 migration window; will narrow to {2.22, 3.33, 4.44} in v0.7.
In practice this matters only for fixed-tariff users (Predbat / Agile users won't manually set these prices since the cube's TOU prices are internal tier-priority labels for their setup).
🛣 What's next
- v0.7 — per-tier rate entry on the editor card (chipped); drop legacy shim-price tokens from migration window.
- v0.7 —
queryDataElectricityV2cloud-stats expansion. - v0.8 — auto-paint TOU schedule from Predbat's low-rate threshold + Agile forecast.
☕ Support
If this saves you the mitmproxy sessions and Python hours it took to build, consider tossing a ko-fi in the tank. Thanks!
Not affiliated with or endorsed by Canadian Solar or EP Cube.