Skip to content

Commit 5921ad9

Browse files
author
Antonio ...
committed
feat: v2.6.1 — dashboard grid-lost indicator (issue #36)
v2.6.0 plumbed the optional grid_available sensor into the card config but did not surface it. Add a compact "Grid lost" warning pill in the hero pill-row, shown only when grid_available reads explicitly off. - _gridAvailable() mirrors the backend is_grid_available tri-state: null when unmapped or unavailable/unknown (pill hidden — no false alarm at boot), true/false only on a real on/off. - _renderGridLostPill() renders only on explicit false; independent of the hero banner so it coexists with Force/Boost/Night/Charging. Added to the structural key (gl) so it appears/disappears via one rebuild. - New i18n key hero.grid_lost (EN/IT/NL) + .grid-lost-pill CSS (reduced-motion safe). Frontend-only; no schema/entity/config change, counts unchanged. VERSION 2.6.1.
1 parent 789bb30 commit 5921ad9

4 files changed

Lines changed: 85 additions & 2 deletions

File tree

‎CLAUDE.md‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,31 @@ async def _set_amperage(self, target_amperage: int):
757757

758758
## Version History
759759

760+
### v2.6.1 (2026-06-06)
761+
**Dashboard: grid-lost indicator for issue #36 (frontend-only)**
762+
763+
v2.6.0 plumbed the optional `grid_available` sensor through to the card config
764+
but did not surface it visually. v2.6.1 adds a compact **"Grid lost" warning
765+
pill** in the hero pill-row (next to the priority / forecast pills), shown
766+
**only** when `grid_available` reads explicitly off.
767+
768+
- New frontend helper `_gridAvailable()` mirrors the backend
769+
`ChargingModel.is_grid_available` tri-state: `null` when the sensor is unmapped
770+
OR its state is `unavailable`/`unknown` (→ pill hidden, no false alarm at boot),
771+
`true`/`false` only on a real on/off. `_renderGridLostPill()` renders the pill
772+
only on an explicit `false`.
773+
- Rendered as an independent pill (not folded into the single hero banner), so it
774+
coexists with the Force / Boost / Night / Charging banners instead of hiding
775+
them. Added to `_computeStructuralKey` (`gl`) so it appears/disappears via one
776+
rebuild. New i18n key `hero.grid_lost` (EN/IT/NL), new `.grid-lost-pill` CSS
777+
(system-red, reduced-motion safe).
778+
779+
Frontend-only — no schema/entity/config change, counts unchanged (69 / 55). The
780+
`?v=`+content-hash cache-buster picks up the new bundle on the next reload.
781+
`VERSION = "2.6.1"`.
782+
783+
---
784+
760785
### v2.6.0 (2026-06-06)
761786
**Mass bug-fixing & improvement release — 7 issues (#36–#42)**
762787

‎custom_components/ev_smart_charger/const.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# ========== INTEGRATION METADATA ==========
44
DOMAIN = "ev_smart_charger"
5-
VERSION = "2.6.0"
5+
VERSION = "2.6.1"
66
DEFAULT_NAME = "EV Smart Charger"
77
FRONTEND_URL_BASE = "/api/ev_smart_charger/frontend"
88
FRONTEND_CARD_FILENAME = "ev-smart-charger-dashboard.js"

‎custom_components/ev_smart_charger/frontend/ev-smart-charger-dashboard.js‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ const FRONTEND_LOCALES = {
581581
"hero.banner.night_charge": "Night Smart Charge Active",
582582
"hero.banner.charging": "EV Charging",
583583
"hero.forecast_tomorrow": "Tomorrow Forecast",
584+
"hero.grid_lost": "⚠ Grid lost",
584585
"toast.boost.target_reached": "Boost can't start: EV at {ev}% (≥ target {target}%). Raise the target or wait for the battery to drain.",
585586
"toast.boost.missing_soc": "Boost can't start: EV SOC sensor unavailable. Check the mapped sensor.",
586587
"toast.dismiss_aria": "Dismiss notification",
@@ -729,6 +730,7 @@ const FRONTEND_LOCALES = {
729730
"hero.banner.night_charge": "Night Smart Charge in corso",
730731
"hero.banner.charging": "EV in ricarica",
731732
"hero.forecast_tomorrow": "Forecast Domani",
733+
"hero.grid_lost": "⚠ Rete assente",
732734
"toast.boost.target_reached": "Boost non può partire: EV al {ev}% (≥ target {target}%). Alza il target o aspetta che la batteria scenda.",
733735
"toast.boost.missing_soc": "Boost non può partire: sensore SOC EV non disponibile. Controlla la mappatura.",
734736
"toast.dismiss_aria": "Chiudi notifica",
@@ -877,6 +879,7 @@ const FRONTEND_LOCALES = {
877879
"hero.banner.night_charge": "Slim nachtelijk laden actief",
878880
"hero.banner.charging": "EV aan het laden",
879881
"hero.forecast_tomorrow": "Verwachting morgen",
882+
"hero.grid_lost": "⚠ Net weg",
880883
"toast.boost.target_reached": "Boost kan niet starten: EV op {ev}% (≥ doel {target}%). Verhoog het doel of wacht tot de batterij zakt.",
881884
"toast.boost.missing_soc": "Boost kan niet starten: EV SOC-sensor niet beschikbaar. Controleer de mapping.",
882885
"toast.dismiss_aria": "Melding sluiten",
@@ -1862,6 +1865,35 @@ class EvSmartChargerDashboard extends HTMLElement {
18621865
return `<span class="priority-pill state-${modifier}">${label}</span>`;
18631866
}
18641867

1868+
/**
1869+
* v2.6.0 (issue #36): tri-state read of the optional grid_available
1870+
* binary_sensor, mirroring the backend ChargingModel.is_grid_available.
1871+
* Returns true (present) / false (lost) / null (unmapped or unavailable/
1872+
* unknown → "don't know", never a false alarm).
1873+
*/
1874+
_gridAvailable() {
1875+
const entityId = this._config?.grid_available_entity;
1876+
if (!entityId) return null;
1877+
const raw = this._stateObj(entityId)?.state;
1878+
if (raw === undefined || raw === null || raw === "unavailable" || raw === "unknown") {
1879+
return null;
1880+
}
1881+
return ["on", "true", "1", "yes", "home", "present"].includes(
1882+
String(raw).toLowerCase()
1883+
);
1884+
}
1885+
1886+
/**
1887+
* v2.6.0 (issue #36): render a compact "Grid lost" warning pill next to the
1888+
* priority / forecast pills, shown ONLY when grid_available is explicitly
1889+
* off. Empty string otherwise (unmapped / present / unknown) — so existing
1890+
* dashboards and a boot-time `unavailable` never surface a false alarm.
1891+
*/
1892+
_renderGridLostPill() {
1893+
if (this._gridAvailable() !== false) return "";
1894+
return `<span class="grid-lost-pill" role="status" aria-live="polite">${this._t("hero.grid_lost")}</span>`;
1895+
}
1896+
18651897
/**
18661898
* v1.11.8: Render the next-day PV forecast as an orange chip placed
18671899
* next to the priority pill. Returns an empty string when the
@@ -2594,6 +2626,7 @@ class EvSmartChargerDashboard extends HTMLElement {
25942626
<div class="hero-pill-row">
25952627
${this._renderPriorityPill(priorityState)}
25962628
${this._renderForecastPill()}
2629+
${this._renderGridLostPill()}
25972630
</div>
25982631
<h1>${this._config.title || this._t("title.default")}</h1>
25992632
<div class="evsc-metric-row">
@@ -2841,6 +2874,9 @@ class EvSmartChargerDashboard extends HTMLElement {
28412874
// left the banner stale in three-phase — Risk #5), and a power-only signal
28422875
// (no status entity mapped) still rebuilds.
28432876
const chargingOnForKey = this._isDrawingNow();
2877+
// v2.6.0 (issue #36): the grid-lost pill appears/disappears, so it is
2878+
// structural — include it so a transition triggers exactly one rebuild.
2879+
const gridLost = this._gridAvailable() === false;
28442880

28452881
return JSON.stringify({
28462882
view: this._view,
@@ -2855,6 +2891,7 @@ class EvSmartChargerDashboard extends HTMLElement {
28552891
bo: boostOn,
28562892
ni: nightOn,
28572893
ch: chargingOnForKey,
2894+
gl: gridLost,
28582895
});
28592896
}
28602897

@@ -3972,6 +4009,27 @@ class EvSmartChargerDashboard extends HTMLElement {
39724009
box-shadow: 0 0 10px currentColor;
39734010
animation: evsc-pulse-slow 3.2s ease-in-out infinite;
39744011
}
4012+
/* v2.6.0 (issue #36): grid-lost warning pill — shown only when the
4013+
optional grid_available sensor reads explicitly off. */
4014+
.grid-lost-pill {
4015+
display: inline-flex; align-items: center; gap: 8px;
4016+
padding: 6px 12px;
4017+
border-radius: var(--evsc-radius-pill);
4018+
font-size: 0.85rem;
4019+
font-weight: 600;
4020+
color: #fff;
4021+
background: var(--evsc-sys-red);
4022+
box-shadow: 0 0 14px 1px var(--evsc-sys-red);
4023+
}
4024+
.grid-lost-pill::before {
4025+
content: ""; width: 7px; height: 7px; border-radius: 50%;
4026+
background: #fff;
4027+
box-shadow: 0 0 10px #fff;
4028+
animation: evsc-pulse-slow 1.8s ease-in-out infinite;
4029+
}
4030+
@media (prefers-reduced-motion: reduce) {
4031+
.grid-lost-pill::before { animation: none; }
4032+
}
39754033
@keyframes evsc-pulse-slow {
39764034
0%, 100% { opacity: 1; box-shadow: 0 0 10px currentColor; }
39774035
50% { opacity: 0.55; box-shadow: 0 0 16px currentColor; }

‎custom_components/ev_smart_charger/manifest.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@
1111
"iot_class": "local_push",
1212
"issue_tracker": "https://github.com/antbald/ha-ev-smart-charger/issues",
1313
"requirements": [],
14-
"version": "2.6.0"
14+
"version": "2.6.1"
1515
}

0 commit comments

Comments
 (0)