Home Assistant integration for the Canadian Solar EP Cube residential battery with a Predbat-compatible shim for Octopus Agile tariff optimisation
Note
v1.2 renamed sensor.ep_cube_self_consumption → sensor.ep_cube_self_sufficiency to fix a long-standing mislabel — the cube's selfHelpRate field reports self-sufficiency (% of load met from own generation), not self-consumption (% of generation used onsite). The underlying unique_id is preserved, so existing dashboards, automations and Energy Dashboard wiring keep working unchanged; only the display name updates. Fresh installs get the corrected sensor.ep_cube_self_sufficiency slug. v1.2 also adds 4 derived percentage sensors (today + yesterday × self-consumption / self-sufficiency) for symmetry with the Energy Dashboard's today / yesterday cards.
A live drop-in Lovelace dashboard that mirrors the EP Cube mobile app — animated power flow, mode-aware controls, ships with the integration.
- 🔋 41 sensors + 5 control entities surfacing every cube state — SoC, power flow, instant KPIs, mode, reserves, daily / yesterday / monthly / yearly energy, self-consumption + sufficiency %, lifetime stats
- ⚡ Predbat shim translates rate-based commands into the cube's TOU model — full Octopus Agile optimisation, no manual scheduling
- 🎨 Drop-in animated dashboard mirroring the EP Cube mobile app — power flow, mode picker, mode-specific control cards
- 🌍 Multi-region — EU live, US/JP/Other supported via config-flow region picker
- 🔐 One-time email + password setup with silent re-auth on token expiry — no JSESSIONID-paste UX, no recurring auth chores
- 🧪 197-test pytest suite + CI on every PR
Important
HACS Default submission is in flight (hacs/default#8364, in the maintainer queue). Until it merges, install via HACS Custom Repository — same one-click install path, just an extra "add repo URL" step the first time.
1. Add the repository in HACS
HACS sidebar → ⋮ → Custom repositories → add https://github.com/SkiLtY/ha-ep-cube as type Integration → Add.
2. Install + restart
Find Canadian Solar EP Cube in HACS → Download → restart Home Assistant when prompted.
3. Add the integration
Settings → Devices & services → Add integration → search Canadian Solar EP Cube → enter your region, the email + password for the EP Cube mobile app, and submit.
The integration runs the captcha-solving login flow, fetches your device list, and registers 26 sensors + 5 control entities under one device.
Manual install (without HACS)
cd /path/to/homeassistant/config
mkdir -p custom_components
git clone https://github.com/SkiLtY/ha-ep-cube /tmp/ha-ep-cube
cp -r /tmp/ha-ep-cube/custom_components/ep_cube custom_components/Restart HA, then continue from step 3 above.
Tip
Want Predbat / Octopus Agile optimisation too? After the integration is live, also:
- Copy
examples/ha_config/into your HA config dir (or merge into your existingconfiguration.yaml) — adds the Riemann daily-kWh sensors Predbat needs. - Install Predbat via the
nipar44/predbat_addonDocker container. Full runbook in docs/PREDBAT.md.
Want the animated power-flow dashboard? See Dashboard — one HACS frontend card + one paste of dashboards/ep_cube.yaml.
| Region | Host | Status |
|---|---|---|
| EU | monitoring-eu.epcube.com |
✅ Verified live (UK, DE, IT, NL, FR, ES, …) |
| US | epcube-monitoring.com (path prefix /app-api) |
🧪 Experimental — host derived from public sources, not live-tested. Please open an issue with your result. |
| JP | monitoring-jp.epcube.com |
⬜ Untested |
| Other | User-supplied | Escape hatch for AU, CA, custom mocks, or any market where the above is wrong. Capture the host from your app's network traffic and paste it in. |
41 sensors, all grouped under one device per EP Cube:
| Group | Sensors |
|---|---|
| Battery | battery_soc · battery_soc_kwh · battery_capacity_kwh · battery_power · battery_charge_today · battery_discharge_today |
| Power flow | grid_power · solar_power · load_power |
| Right now | instant_self_consumption_pct · instant_self_sufficiency_pct · instant_grid_flow_w |
| Daily energy (kWh) | solar_today · backup_today · grid_import_today · grid_export_today · solar_dc_today · solar_ac_today |
| Yesterday energy (kWh) | solar_yesterday · backup_yesterday · grid_import_yesterday · grid_export_yesterday |
| Monthly energy (kWh) | solar_month · backup_month · grid_import_month · grid_export_month |
| Yearly energy (kWh) | solar_year · backup_year · grid_import_year · grid_export_year |
| Efficiency (%) | self_sufficiency_pct · self_sufficiency_today · self_sufficiency_yesterday · self_consumption_today · self_consumption_yesterday |
| Mode + reserve | operating_mode · reserve_soc |
| Lifetime / KPI | earning_yesterday · grid_outage_count · off_grid_seconds · winter_protect |
Tip
Monthly + yearly rollups are cube-native — they read the cube's own boundary-aware accounting via queryDataElectricityV2 scope=2/3. Don't drift if HA is down at month/year roll, unlike HA-side utility_meter helpers (which previous releases shipped as a workaround). If you've been using the helpers from examples/ha_config/packages/ep_cube.yaml, you can delete the ep_cube_{solar,grid,backup}_{month,year} entries — the integration provides them directly now.
Note
backup_today / backup_yesterday are install-dependent. They count kWh delivered through the cube's backup-output terminal — not "loads that stayed up during an outage". If your installer wired the whole house through the backup terminal (common in the UK with a critical-loads panel), these sensors read as whole-house consumption. If only essential circuits are wired through the backup output, you'll see just fridge / lighting / router etc. Outage-resilience under UK G99/G100 regs requires a separate EPS Gateway; without one the cube refuses to supply via the backup terminal during an actual grid outage even though it meters kWh through it under normal grid-up operation.
All shim services accept only an optional device_id. Window and SoC parameters are read from the entities Predbat publishes — see docs/ARCHITECTURE.md for the full contract.
| Service | Purpose |
|---|---|
ep_cube.charge_start |
Force grid charge until end of Predbat's planned charge window; target SoC from best_charge_limit |
ep_cube.charge_stop |
Cancel active charge override, restore baseline |
ep_cube.discharge_start |
Force discharge until end of planned export window; target SoC from best_export_limit |
ep_cube.discharge_stop |
Cancel active discharge override |
ep_cube.charge_freeze |
Hold battery at current SoC until end of charge window |
ep_cube.discharge_freeze |
Alias for charge_freeze; end-time taken from export window |
ep_cube.idle |
Restore baseline TOU schedule |
Important
v1.0 is an opinionated Predbat bridge. The integration treats Predbat as the single source of truth for time-of-use control. There is no user-facing TOU writer service. If the cube has user-painted TOU slots while Predbat is installed, an HA Repair issue offers a one-click wipe to hand control back to Predbat. Manual TOU painting belongs in the EP Cube mobile app or a different integration (Bobsilvio/epcube is a good fit for fixed-tariff non-Predbat users).
Mode switching and reserve-SoC writes are exposed as entities, not services:
select.ep_cube_operating_mode
switch.ep_cube_allow_grid_charge
number.ep_cube_self_consumption_reserve
number.ep_cube_backup_reserve
Call select.select_option / number.set_value from automations.
The integration installs cleanly on a default HA setup. Two optional extras live in examples/ha_config/:
packages/ep_cube.yaml— Riemann daily kWh sensors (load / PV / signed grid import / signed grid export),utility_metermonthly + yearly rollups, andinput_numbercharge / discharge rate entities.configuration.yaml— minimal example showing thehomeassistant.packagesblock.
| Use case | Need the package? |
|---|---|
| Running Predbat | ✅ Yes — load_today is a hard dependency; charge_rate/discharge_rate must be writable input_number entities |
| Monthly/yearly history in Energy dashboard | ✅ Recommended |
| Just install and view live sensors | ❌ No |
To install: copy examples/ha_config/ into your HA config directory (merging into any existing configuration.yaml), then restart HA.
dashboards/ep_cube.yaml is a drop-in Lovelace dashboard mirroring the EP Cube mobile app: animated power flow, battery status, operating-mode picker, and mode-specific control cards that swap automatically when you change mode.
Install steps:
- Power Flow Card Plus — HACS → Frontend → search "Power Flow Card Plus" by flixlix → Install → restart HA.
- New dashboard — Settings → Dashboards → Add Dashboard → "New dashboard from scratch" (title: EP Cube, icon:
mdi:home-battery). - Paste YAML — open dashboard → Edit → ⋮ → Raw configuration editor → replace contents with
dashboards/ep_cube.yaml→ Save.
Tip
Entity IDs assume the default device name EP Cube. If you renamed the device, or have multiple cubes, edit the YAML accordingly — HA appends _2, _3, etc. to disambiguate.
The integration ships cube-native daily kWh sensors that wire straight into HA's Energy dashboard (Settings → Dashboards → Energy) — no helper package needed.
| Slot | Entity |
|---|---|
| Solar production | sensor.ep_cube_solar_today |
| Grid consumption | sensor.ep_cube_grid_import_today |
| Return to grid | sensor.ep_cube_grid_export_today |
| Home battery SoC | sensor.ep_cube_battery_soc_kwh |
Tip
Prefer Riemann-integrated power-to-energy sensors instead? The optional helper package ships sensor.ep_cube_import_today / sensor.ep_cube_export_today derived from sensor.ep_cube_grid_power. Useful if you don't trust the cube's daily-counter midnight roll, or want sub-minute resolution.
The EP Cube has no documented local API — no Modbus, no MQTT. All control goes through Canadian Solar's cloud via mobile-app endpoints.
One existing community integration exists (Bobsilvio/epcube) but carries no licence file, so it cannot legally be forked. This is a clean-room build.
The end goal: working Octopus Agile tariff optimisation via Predbat. Predbat operates on a rate-based, time-windowed contract; the EP Cube exposes a mode + TOU-schedule contract. This integration includes a shim service layer that translates between the two.
graph TB
subgraph Cloud ["☁️ Canadian Solar Cloud"]
EP[monitoring-eu.epcube.com]
end
subgraph HA ["🏠 Home Assistant"]
INT[ep_cube integration]
SHIM[Predbat shim<br/>services.py]
STATE[predbat_state.py]
SENS[26 sensors + control entities]
end
subgraph Predbat ["📈 Predbat container"]
PB[nipar44/predbat_addon]
OCT[Octopus Agile rates]
SOL[Solcast PV forecast]
end
EP <-->|Bearer token auth<br/>silent re-auth| INT
INT --> SENS
PB -->|best_charge_* / best_export_* entities| STATE
STATE --> SHIM
SHIM -->|switchMode writes| INT
OCT --> PB
SOL --> PB
ha-ep-cube/
├── custom_components/ep_cube/ ← HA integration
│ ├── services.py ← Predbat shim service handlers
│ └── predbat_state.py ← Reads predbat.best_charge_* / best_export_* entities
├── mock_server/ ← FastAPI mock of the EP Cube cloud (dev without hardware)
├── dashboards/
│ └── ep_cube.yaml ← Lovelace dashboard (animated power flow + mode controls)
├── examples/
│ └── ha_config/ ← Drop-in YAML for Predbat helpers + Energy dashboard rollups
├── docs/
│ ├── ARCHITECTURE.md ← Predbat shim contract + design notes
│ ├── PREDBAT.md ← Predbat install + tariff (BottlecapDave) + Solcast runbook
│ ├── PHASE_3_2.md ← Bearer-token + captcha refactor notes
│ ├── MITMPROXY_SETUP.md ← Cloud-API capture tooling (for contributors)
│ ├── TROUBLESHOOTING.md ← Known wire-level gotchas
│ └── predbat_apps.yaml.example ← Predbat custom-inverter template
└── docker-compose.yml ← HA + mock-server stack
Requires Docker.
git clone https://github.com/SkiLtY/ha-ep-cube
cd ha-ep-cube
docker compose up -dThis brings up:
| Service | URL | Notes |
|---|---|---|
| Home Assistant | http://localhost:8123 | custom_components/ep_cube/ volume-mounted |
| Mock EP Cube cloud | http://localhost:8765 | FastAPI mock of the mobile-app surface |
Add the integration via HA's UI:
- Settings → Devices & services → Add integration → Canadian Solar EP Cube
- Region: Other (escape hatch for custom hosts)
- Base URL:
http://mock:8765(Docker network DNS) - API prefix:
/api - Username + password: any string — the mock accepts anything and returns a stub Bearer token
The mock's deviceList resolves a single device (devId=5613); no manual ID needed.
pip install -r requirements-test.txt
pytestTests use pytest-homeassistant-custom-component and run without Docker. The CI matrix (.github/workflows/validate.yml) runs the same on Python 3.12 against every PR and weekly. (3.13 is pending an aiodns/pycares fix in HA's test stack.)
Coverage spans: API client (envelope unwrapping, 403→re-auth retry, switch_mode verification), Predbat shim (idempotency, baseline snapshotting, auto-revert), predbat_state parsing, config flow (region routing + migration), and entity registration.
This stack uses HA Container (lightweight, no Supervisor). HA Container cannot install add-ons — Predbat runs as a sibling nipar44/predbat_addon Docker container (the upstream-recommended replacement for the now-deprecated AppDaemon install path). See docs/PREDBAT.md.
| Phase | Status | What |
|---|---|---|
| 1 | ✅ | Mock cloud + HA integration skeleton + 9 sensors + DeviceInfo |
| 2a | ✅ | Predbat shim: 7 services, baseline snapshot, idempotency, auto-revert |
| 2b | ✅ | Predbat as nipar44/predbat_addon container, plan loop validated end-to-end |
| 2b.1 | ✅ | Shim reads params from predbat.best_charge_* / predbat.best_export_* entities |
| 2c | ✅ | Live Octopus Agile rates via public REST API (no Octopus account required) |
| 2c+ | ✅ | Solcast PV forecast wired in (split E/W array) |
| 3 | ✅ | Hardware reconciliation — live cloud bring-up against monitoring-eu.epcube.com |
| 3.1 | ✅ | charge_freeze → mid-peak TOU slot; force-export gap documented; stable device name |
| 3.2 | ✅ | Mobile-app Bearer-token auth replaces JSESSIONID-cookie paste |
| 3.3 | ✅ | Animated power-flow Lovelace dashboard |
| 3.4 | ✅ | Feature-parity vs Bobsilvio/epcube — control entities + daily kWh sensors + i18n |
| 3.5 | ✅ | Bobsilvio-parity metrics expansion — 5 sensors + 4 utility_meter rollups |
| 4 | ✅ | HACS distribution — first release v0.5.0 shipped 2026-05-28. 150-test pytest suite + CI matrix, demo-first README, curated release-notes workflow, brand assets self-hosted, helpers shipped. HACS Default submission opened 2026-06-09 as hacs/default#8364 — all 11 bot checks green first try, awaiting maintainer merge. |
| 4.1 | ✅ | TOU schedule editor — shipped in v0.6.0 → v0.7.0. Subsequently removed in v1.0 — see below. |
| v1.0 | ✅ | Opinionated Predbat bridge — TOU editor card + set_tou_schedule service removed; HA Repair flow added that detects user-painted TOU slots while Predbat is installed and offers a one-click wipe. Predbat is the single source of truth for TOU. Manual TOU painting belongs in the EP Cube mobile app or Bobsilvio/epcube. |
| 4.2 (Tier 1+2) | ✅ | Cloud-stats expansion — queryDataElectricityV2 shipped in v1.1.0 + v1.1.1: signed grid import/export, yesterday's energy quartet, 5-min stats coordinator, ghost-entity sweep on upgrade. Patch releases v1.1.2 (dashboard entity-id fix + backup-loads/EPS Gateway README clarification) + v1.1.3 (hassfest compliance — drop predbat_inverter_mode state translation block) shipped during HACS pre-submission smoke test. |
| v1.2 | ✅ | Mislabel fix + cube-native rollups + instant tiles — shipped 2026-06-10 in v1.2.0. 15 new sensors (4 derived %s + 8 cube-native monthly/yearly rollups + 3 instant-KPI tiles), self-consumption sensor renamed to self-sufficiency with unique_id preserved for back-compat, dashboard grows a "Right now" gauge stack, utility_meter helpers dropped from the example package in favour of cube-native rollups. Suite 178 → 216. |
| v1.3 | ✅ | Dashboard refresh + signed grid net — shipped 2026-06-12 in v1.3.0. Dashboard restructured into three tabs (Now / Today / Control); Now-tab swaps SC/SS instant % gauges (low signal — both pin at 100% most of the day) for live Solar/Battery/Grid power gauges in W; Today-tab grows a signed Grid gauge + matching yesterday row using 2 new derived sensors (grid_net_today + grid_net_yesterday). SC + SS today/yesterday return 0.0 instead of unknown below the jitter floor (fixes "Entity is non-numeric" overlay every morning). Unified colour palette: HA Energy Dashboard's purple-out / blue-in across both signed gauges + Material green for renewables. Suite 216 → 223. |
| 4+ | ⏸️ | HomeAssistant-OctopusEnergy — half-hourly smart-meter consumption replaces Riemann load_today. Gated on Octopus Home Mini arrival. |
| v1.4 | 📋 | Lifetime totals + eco metrics — RestoreSensor + coordinator-startup state-seeding for lifetime kWh counters; coal / treeNum eco metrics from queryDataElectricityV2. Originally pencilled for v1.3; today's UX work filled that slot first. |
Hacking life to keep it simple. Solving technical challenges along the way.
I'm an integration engineer who loves taking complex technical puzzles and turning them into simple solutions.
When I'm not at a terminal, you'll usually find me logging miles in the pool or on the bike, training for that next aquabike event.
If any of my tools, scripts, or tinkering have helped you solve a challenge of your own, tossing a ko-fi in the tank helps keep the engine running. Thanks for the support! ☕
— Michael Skilton (@SkiLtY)
MIT — see LICENSE.
Not affiliated with or endorsed by Canadian Solar or EP Cube.
