Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,15 @@ jobs:
enable-cache: true
- run: uv sync
- run: uv run pytest

js-tests:
name: js tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
- run: npm ci
- run: npm test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ dist/
# Claude Code — local-only, not shared
.claude/settings.local.json
.claude/worktrees/

# Node / frontend test tooling
node_modules/
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,24 @@ If the dashboard schema is updated in a future release, the integration raises a

The generated YAML is a snapshot of your entity IDs at the moment it runs. Home Assistant 2026.6 onwards builds entity IDs from the device's area (so a device in "Loft" gets `sensor.loft_givenergy_inverter_…`), and Home Assistant doesn't rewrite existing dashboards when entities are renamed. So if you move a device between areas, rename entities, or use **Recreate entity IDs**, just run `generate_dashboard` again afterwards to re-point the cards.

### Dashboard strategy (live, self-maintaining)

To avoid that snapshot problem entirely, there's also a dashboard *strategy* that builds the same six-tab layout but resolves every entity from the registry each time the dashboard loads — so it doesn't go stale when a device moves area or an entity is renamed. I added it because the static YAML kept silently rotting on my own install after area reassignments. To use it, create a new dashboard, open the **raw configuration editor**, and set the whole config to:

```yaml
strategy:
type: custom:givenergy
mode: classic # the only mode in this release
max_power_kw: 10 # optional; default 10; Overview 24h chart y-axis envelope (kW)
serial: SA2114G047 # optional; pin one inverter on a multi-plant install
```

The strategy and the bundled cell-heatmap card are served by the integration itself, so there's nothing extra to install for them. `power-flow-card-plus` and `apexcharts-card` are still needed for the Overview/Energy charts (install them via **HACS → Frontend**); where they're missing the strategy shows a short placeholder rather than a broken card. `generate_dashboard` remains available as an editable static starting point if you'd rather hand-tweak a copy.

One caveat worth knowing: on a **hard refresh** (Ctrl/Cmd+Shift+R, which bypasses the browser cache) the dashboard may occasionally show "Error loading the dashboard strategy: Timeout waiting for strategy element …". This is a Home Assistant limitation common to all network-loaded dashboard strategies — HA gives the strategy module a fixed 5-second window to register, and a cold re-fetch can lose that race when it's queued behind other custom-card resources. A normal reload serves the module from cache and isn't affected, so it doesn't bite in day-to-day use; if you do hit it, reload again.

This is new in this release and currently reproduces the `classic` layout only; the broader set of modes explored in [the redesign brief](docs/design/dashboard-redesign-brief.md) is still to come.

### Voice assistants & LLM access

Home Assistant's voice assistants (Assist) and LLM tools (Claude / OpenAI via MCP) can only see entities that are explicitly **exposed**. HA auto-exposes a curated allowlist of sensor device classes — `temperature`, `humidity`, and a few others — but `power`, `energy`, and `battery` are **not** on that list, so none of this integration's headline sensors are visible to voice or LLM queries by default. Asking "what's my battery at?" silently returns nothing until you fix it.
Expand Down
47 changes: 27 additions & 20 deletions custom_components/givenergy_local/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,14 @@
_DASHBOARD_STORAGE_KEY = f"{DOMAIN}.dashboard"
_DASHBOARD_STORAGE_VERSION = 1

# Bundled cell-balance heatmap card, served from this integration's package and
# auto-loaded on the frontend so the generated dashboard's custom:ge-cell-heatmap
# resolves without a manual HACS/resource install. Bump _CARD_VERSION whenever
# the JS changes, to bust the browser cache.
_CARD_FILENAME = "ge-cell-heatmap.js"
_CARD_URL = f"/{DOMAIN}/{_CARD_FILENAME}"
_CARD_VERSION = "2"
# Bundled frontend module: the dashboard strategy (custom:givenergy) and the
# cell-balance heatmap card (custom:ge-cell-heatmap) are shipped together in a
# single JS file, served from this integration's package and auto-loaded so both
# resolve on any install without a manual HACS/resource registration. Bump
# _STRATEGY_VERSION whenever the JS changes, to bust the browser cache.
_STRATEGY_FILENAME = "ge-strategy.js"
_STRATEGY_URL = f"/{DOMAIN}/{_STRATEGY_FILENAME}"
_STRATEGY_VERSION = "3"

# Per-config-entry topology cache. PlantCapabilities is persisted as
# `to_dict()` directly (no envelope) following HA Core's Store convention —
Expand Down Expand Up @@ -245,12 +246,12 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:


async def _async_register_frontend_card(hass: HomeAssistant) -> None:
"""Serve and auto-load the bundled cell-heatmap card.
"""Serve and auto-load the bundled frontend module (strategy + heatmap card).

The card module ships inside this integration's ``www/`` dir; we expose it
at a stable URL and register it as an extra frontend module so the generated
dashboard's ``custom:ge-cell-heatmap`` resolves on any dashboard without a
manual HACS/resource install.
The single JS file ships inside this integration's ``www/`` dir; we expose
it at a stable URL and register it as an extra frontend module so both
``custom:givenergy`` (the dashboard strategy) and ``custom:ge-cell-heatmap``
resolve on any install without a manual HACS/resource registration.

Called once from :func:`async_setup` (component scope), so the static-path
registration happens a single time for the integration regardless of how
Expand All @@ -262,16 +263,16 @@ async def _async_register_frontend_card(hass: HomeAssistant) -> None:
# skips where there is nothing to serve from anyway.
return
try:
card_path = Path(__file__).parent / "www" / _CARD_FILENAME
module_path = Path(__file__).parent / "www" / _STRATEGY_FILENAME
await hass.http.async_register_static_paths(
[StaticPathConfig(_CARD_URL, str(card_path), False)]
[StaticPathConfig(_STRATEGY_URL, str(module_path), False)]
)
add_extra_js_url(hass, f"{_CARD_URL}?v={_CARD_VERSION}")
add_extra_js_url(hass, f"{_STRATEGY_URL}?v={_STRATEGY_VERSION}")
except Exception as exc: # noqa: BLE001
# The bundled card is cosmetic (a dashboard heatmap). Registering it once
# at component scope means a failure here is genuinely unexpected, but it
# must still never take down the integration — log and carry on.
_LOGGER.warning("Could not register the bundled cell-heatmap card: %s", exc)
# The bundled module is cosmetic (dashboard frontend). Registering it
# once at component scope means a failure here is genuinely unexpected,
# but it must still never take down the integration — log and carry on.
_LOGGER.warning("Could not register the bundled frontend module: %s", exc)


async def _build_capture_header(
Expand Down Expand Up @@ -663,7 +664,13 @@ async def handle_generate_dashboard(call: ServiceCall) -> None:
"message": (
f"Dashboard ready — [download YAML]({url})\n\n"
"Go to **Settings → Dashboards → Add Dashboard** "
"and paste the contents into the raw config editor." + warning
"and paste the contents into the raw config editor.\n\n"
"**Tip:** for a dashboard that resolves entities live "
"(and so survives renames and area moves), use the "
"`custom:givenergy` strategy instead — set the raw "
"config to `strategy: { type: custom:givenergy, "
"mode: classic }`. This static YAML stays available as "
"an editable starting point." + warning
),
"notification_id": f"givenergy_dashboard_{inv}",
},
Expand Down
106 changes: 0 additions & 106 deletions custom_components/givenergy_local/www/ge-cell-heatmap.js

This file was deleted.

Loading
Loading