More support for no-grid energy dashboard (#25644)

* More support for no-grid energy dashboard

* Update src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>

* lint

---------

Co-authored-by: Petar Petrov <MindFreeze@users.noreply.github.com>
This commit is contained in:
karwosts 2025-06-10 23:11:46 -07:00 committed by GitHub
parent 9d6a7e7e6f
commit b3f0a6328e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 96 additions and 72 deletions

View File

@ -64,7 +64,9 @@ export class EnergyViewStrategy extends ReactiveElement {
(source) => source.type === "solar" (source) => source.type === "solar"
); );
const hasGas = prefs.energy_sources.some((source) => source.type === "gas"); const hasGas = prefs.energy_sources.some((source) => source.type === "gas");
const hasBattery = prefs.energy_sources.some(
(source) => source.type === "battery"
);
const hasWater = prefs.energy_sources.some( const hasWater = prefs.energy_sources.some(
(source) => source.type === "water" (source) => source.type === "water"
); );
@ -74,8 +76,8 @@ export class EnergyViewStrategy extends ReactiveElement {
collection_key: "energy_dashboard", collection_key: "energy_dashboard",
}); });
// Only include if we have a grid source. // Only include if we have a grid or battery.
if (hasGrid) { if (hasGrid || hasBattery) {
view.cards!.push({ view.cards!.push({
title: hass.localize("ui.panel.energy.cards.energy_usage_graph_title"), title: hass.localize("ui.panel.energy.cards.energy_usage_graph_title"),
type: "energy-usage-graph", type: "energy-usage-graph",
@ -110,8 +112,8 @@ export class EnergyViewStrategy extends ReactiveElement {
}); });
} }
// Only include if we have a grid. // Only include if we have a grid or battery.
if (hasGrid) { if (hasGrid || hasBattery) {
view.cards!.push({ view.cards!.push({
title: hass.localize("ui.panel.energy.cards.energy_distribution_title"), title: hass.localize("ui.panel.energy.cards.energy_distribution_title"),
type: "energy-distribution", type: "energy-distribution",
@ -120,7 +122,7 @@ export class EnergyViewStrategy extends ReactiveElement {
}); });
} }
if (hasGrid || hasSolar || hasGas || hasWater) { if (hasGrid || hasSolar || hasGas || hasWater || hasBattery) {
view.cards!.push({ view.cards!.push({
title: hass.localize( title: hass.localize(
"ui.panel.energy.cards.energy_sources_table_title" "ui.panel.energy.cards.energy_sources_table_title"

View File

@ -102,14 +102,13 @@ class HuiEnergyDistrubutionCard
const prefs = this._data.prefs; const prefs = this._data.prefs;
const types = energySourcesByType(prefs); const types = energySourcesByType(prefs);
// The strategy only includes this card if we have a grid. const hasGrid =
const hasConsumption = true; !!types.grid?.[0].flow_from.length || !!types.grid?.[0].flow_to.length;
const hasSolarProduction = types.solar !== undefined; const hasSolarProduction = types.solar !== undefined;
const hasBattery = types.battery !== undefined; const hasBattery = types.battery !== undefined;
const hasGas = types.gas !== undefined; const hasGas = types.gas !== undefined;
const hasWater = types.water !== undefined; const hasWater = types.water !== undefined;
const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0; const hasReturnToGrid = !!types.grid?.[0].flow_to.length;
const { summedData, compareSummedData: _ } = getSummedData(this._data); const { summedData, compareSummedData: _ } = getSummedData(this._data);
const { consumption, compareConsumption: __ } = computeConsumptionData( const { consumption, compareConsumption: __ } = computeConsumptionData(
@ -163,14 +162,14 @@ class HuiEnergyDistrubutionCard
} }
let batteryFromGrid: null | number = null; let batteryFromGrid: null | number = null;
let batteryToGrid: null | number = null; let batteryToGrid: null | number = null;
if (hasBattery) { if (hasBattery && hasGrid) {
batteryToGrid = consumption.total.battery_to_grid; batteryToGrid = consumption.total.battery_to_grid;
batteryFromGrid = consumption.total.grid_to_battery; batteryFromGrid = consumption.total.grid_to_battery;
} }
let solarToBattery: null | number = null; let solarToBattery: null | number = null;
let solarToGrid: null | number = null; let solarToGrid: null | number = null;
if (hasSolarProduction) { if (hasSolarProduction && hasGrid) {
solarToGrid = consumption.total.solar_to_grid; solarToGrid = consumption.total.solar_to_grid;
} }
if (hasSolarProduction && hasBattery) { if (hasSolarProduction && hasBattery) {
@ -182,7 +181,9 @@ class HuiEnergyDistrubutionCard
batteryConsumption = Math.max(consumption.total.used_battery, 0); batteryConsumption = Math.max(consumption.total.used_battery, 0);
} }
const gridConsumption = Math.max(consumption.total.used_grid, 0); const gridConsumption = hasGrid
? Math.max(consumption.total.used_grid, 0)
: 0;
const totalHomeConsumption = Math.max(0, consumption.total.used_total); const totalHomeConsumption = Math.max(0, consumption.total.used_total);
@ -206,7 +207,11 @@ class HuiEnergyDistrubutionCard
// This fallback is used in the demo // This fallback is used in the demo
let electricityMapUrl = "https://app.electricitymap.org"; let electricityMapUrl = "https://app.electricitymap.org";
if (this._data.co2SignalEntity && this._data.fossilEnergyConsumption) { if (
hasGrid &&
this._data.co2SignalEntity &&
this._data.fossilEnergyConsumption
) {
// Calculate high carbon consumption // Calculate high carbon consumption
const highCarbonEnergy = Object.values( const highCarbonEnergy = Object.values(
this._data.fossilEnergyConsumption this._data.fossilEnergyConsumption
@ -225,7 +230,7 @@ class HuiEnergyDistrubutionCard
if (gridConsumption !== totalFromGrid) { if (gridConsumption !== totalFromGrid) {
// Only get the part that was used for consumption and not the battery // Only get the part that was used for consumption and not the battery
highCarbonConsumption = highCarbonConsumption =
highCarbonEnergy * (gridConsumption / totalFromGrid); highCarbonEnergy * (gridConsumption! / totalFromGrid);
} else { } else {
highCarbonConsumption = highCarbonEnergy; highCarbonConsumption = highCarbonEnergy;
} }
@ -378,41 +383,43 @@ class HuiEnergyDistrubutionCard
</div>` </div>`
: ""} : ""}
<div class="row"> <div class="row">
<div class="circle-container grid"> ${hasGrid
<div class="circle"> ? html`<div class="circle-container grid">
<ha-svg-icon .path=${mdiTransmissionTower}></ha-svg-icon> <div class="circle">
${returnedToGrid !== null <ha-svg-icon .path=${mdiTransmissionTower}></ha-svg-icon>
? html`<span class="return"> ${returnedToGrid !== null
<ha-svg-icon ? html`<span class="return">
class="small" <ha-svg-icon
.path=${mdiArrowLeft} class="small"
></ha-svg-icon .path=${mdiArrowLeft}
>${formatConsumptionShort( ></ha-svg-icon
>${formatConsumptionShort(
this.hass,
returnedToGrid,
"kWh"
)}
</span>`
: ""}
<span class="consumption">
${hasReturnToGrid
? html`<ha-svg-icon
class="small"
.path=${mdiArrowRight}
></ha-svg-icon>`
: ""}${formatConsumptionShort(
this.hass, this.hass,
returnedToGrid, totalFromGrid,
"kWh" "kWh"
)} )}
</span>` </span>
: ""} </div>
<span class="consumption"> <span class="label"
${hasReturnToGrid >${this.hass.localize(
? html`<ha-svg-icon "ui.panel.lovelace.cards.energy.energy_distribution.grid"
class="small" )}</span
.path=${mdiArrowRight} >
></ha-svg-icon>` </div> `
: ""}${formatConsumptionShort( : html`<div class="grid-spacer"></div>`}
this.hass,
totalFromGrid,
"kWh"
)}
</span>
</div>
<span class="label"
>${this.hass.localize(
"ui.panel.lovelace.cards.energy.energy_distribution.grid"
)}</span
>
</div>
<div class="circle-container home"> <div class="circle-container home">
<div <div
class="circle ${classMap({ class="circle ${classMap({
@ -480,22 +487,27 @@ class HuiEnergyDistrubutionCard
shape-rendering="geometricPrecision" shape-rendering="geometricPrecision"
/>` />`
: ""} : ""}
<circle ${hasGrid
? svg`<circle
class="grid" class="grid"
cx="40" cx="40"
cy="40" cy="40"
r="38" r="38"
stroke-dasharray="${homeHighCarbonCircumference ?? stroke-dasharray="${
CIRCLE_CIRCUMFERENCE - homeHighCarbonCircumference ??
homeSolarCircumference! - CIRCLE_CIRCUMFERENCE -
(homeBatteryCircumference || homeSolarCircumference! -
0)} ${homeHighCarbonCircumference !== undefined (homeBatteryCircumference || 0)
? CIRCLE_CIRCUMFERENCE - homeHighCarbonCircumference } ${
: homeSolarCircumference! + homeHighCarbonCircumference !== undefined
(homeBatteryCircumference || 0)}" ? CIRCLE_CIRCUMFERENCE - homeHighCarbonCircumference
: homeSolarCircumference! +
(homeBatteryCircumference || 0)
}"
stroke-dashoffset="0" stroke-dashoffset="0"
shape-rendering="geometricPrecision" shape-rendering="geometricPrecision"
/> />`
: nothing}
</svg>` </svg>`
: ""} : ""}
</div> </div>
@ -619,15 +631,19 @@ class HuiEnergyDistrubutionCard
d="M55,100 v-15 c0,-35 10,-30 30,-30 h20" d="M55,100 v-15 c0,-35 10,-30 30,-30 h20"
vector-effect="non-scaling-stroke" vector-effect="non-scaling-stroke"
></path> ></path>
<path ${
id="battery-grid" hasGrid
class=${classMap({ ? svg`<path
"battery-from-grid": Boolean(batteryFromGrid), id="battery-grid"
"battery-to-grid": Boolean(batteryToGrid), class=${classMap({
})} "battery-from-grid": Boolean(batteryFromGrid),
d="M45,100 v-15 c0,-35 -10,-30 -30,-30 h-20" "battery-to-grid": Boolean(batteryToGrid),
vector-effect="non-scaling-stroke" })}
></path> d="M45,100 v-15 c0,-35 -10,-30 -30,-30 h-20"
vector-effect="non-scaling-stroke"
></path>`
: nothing
}
` `
: ""} : ""}
${hasBattery && hasSolarProduction ${hasBattery && hasSolarProduction
@ -638,12 +654,14 @@ class HuiEnergyDistrubutionCard
vector-effect="non-scaling-stroke" vector-effect="non-scaling-stroke"
></path>` ></path>`
: ""} : ""}
<path ${hasGrid
class="grid" ? svg`<path
id="grid" class="grid"
d="M0,${hasBattery ? 50 : hasSolarProduction ? 56 : 53} H100" id="grid"
vector-effect="non-scaling-stroke" d="M0,${hasBattery ? 50 : hasSolarProduction ? 56 : 53} H100"
></path> vector-effect="non-scaling-stroke"
></path>`
: nothing}
${solarToGrid && this._animate ${solarToGrid && this._animate
? svg`<circle ? svg`<circle
r="1" r="1"
@ -839,6 +857,10 @@ class HuiEnergyDistrubutionCard
.spacer { .spacer {
width: 84px; width: 84px;
} }
.grid-spacer {
width: 84px;
height: 100px;
}
.circle { .circle {
width: 80px; width: 80px;
height: 80px; height: 80px;