From 6464c2b602ec44a30f1cdf5269a355f2519db89b Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Sat, 26 Apr 2025 10:58:33 -0700 Subject: [PATCH] Share energy sum calculation across all cards (#25184) --- src/data/energy.ts | 16 +++++-- .../hui-energy-carbon-consumed-gauge-card.ts | 25 ++-------- .../energy/hui-energy-distribution-card.ts | 33 ++++--------- .../hui-energy-grid-neutrality-gauge-card.ts | 27 ++++------- .../cards/energy/hui-energy-sankey-card.ts | 33 +++---------- .../hui-energy-self-sufficiency-gauge-card.ts | 46 +++++-------------- .../hui-energy-solar-consumed-gauge-card.ts | 20 ++------ .../energy/hui-energy-usage-graph-card.ts | 45 ++++++------------ 8 files changed, 72 insertions(+), 173 deletions(-) diff --git a/src/data/energy.ts b/src/data/energy.ts index 7c6dee5ade..057b7a9f78 100644 --- a/src/data/energy.ts +++ b/src/data/energy.ts @@ -782,12 +782,19 @@ export const getEnergyWaterUnit = (hass: HomeAssistant): string => export const energyStatisticHelpUrl = "/docs/energy/faq/#troubleshooting-missing-entities"; -interface EnergySumData { +export interface EnergySumData { to_grid?: Record; from_grid?: Record; to_battery?: Record; from_battery?: Record; solar?: Record; + total: { + to_grid?: number; + from_grid?: number; + to_battery?: number; + from_battery?: number; + solar?: number; + }; } interface EnergyConsumptionData { @@ -860,29 +867,30 @@ const getSummedDataPartial = ( } } - const summedData: EnergySumData = {}; + const summedData: EnergySumData = { total: {} }; Object.entries(statIds).forEach(([key, subStatIds]) => { const totalStats: Record = {}; const sets: Record> = {}; + let sum = 0; subStatIds!.forEach((id) => { const stats = compare ? data.statsCompare[id] : data.stats[id]; if (!stats) { return; } - const set = {}; stats.forEach((stat) => { if (stat.change === null || stat.change === undefined) { return; } const val = stat.change; - // Get total of solar and to grid to calculate the solar energy used + sum += val; totalStats[stat.start] = stat.start in totalStats ? totalStats[stat.start] + val : val; }); sets[id] = set; }); summedData[key] = totalStats; + summedData.total[key] = sum; }); return summedData; diff --git a/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts index 3351ce2dd1..734f806de4 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-carbon-consumed-gauge-card.ts @@ -11,10 +11,9 @@ import "../../../../components/ha-svg-icon"; import "../../../../components/ha-tooltip"; import type { EnergyData } from "../../../../data/energy"; import { - energySourcesByType, getEnergyDataCollection, + getSummedData, } from "../../../../data/energy"; -import { calculateStatisticsSumGrowth } from "../../../../data/recorder"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../../../types"; import { createEntityNotFoundWarning } from "../../components/hui-warning"; @@ -92,14 +91,9 @@ class HuiEnergyCarbonGaugeCard `; } - const prefs = this._data.prefs; - const types = energySourcesByType(prefs); + const { summedData, compareSummedData: _ } = getSummedData(this._data); - const totalGridConsumption = - calculateStatisticsSumGrowth( - this._data.stats, - types.grid![0].flow_from.map((flow) => flow.stat_energy_from) - ) ?? 0; + const totalGridConsumption = summedData.total.from_grid ?? 0; let value: number | undefined; @@ -111,18 +105,9 @@ class HuiEnergyCarbonGaugeCard ) : 0; - const totalSolarProduction = types.solar - ? calculateStatisticsSumGrowth( - this._data.stats, - types.solar.map((source) => source.stat_energy_from) - ) || 0 - : 0; + const totalSolarProduction = summedData.total.solar ?? 0; - const totalGridReturned = - calculateStatisticsSumGrowth( - this._data.stats, - types.grid![0].flow_to.map((flow) => flow.stat_energy_to) - ) || 0; + const totalGridReturned = summedData.total.to_grid ?? 0; const totalEnergyConsumed = totalGridConsumption + diff --git a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts index c28a0c772e..2eb162ec1c 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts @@ -26,6 +26,7 @@ import { getEnergyGasUnit, getEnergyWaterUnit, formatConsumptionShort, + getSummedData, } from "../../../../data/energy"; import { calculateStatisticsSumGrowth } from "../../../../data/recorder"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; @@ -109,11 +110,9 @@ class HuiEnergyDistrubutionCard const hasWater = types.water !== undefined; const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0; - const totalFromGrid = - calculateStatisticsSumGrowth( - this._data.stats, - types.grid![0].flow_from.map((flow) => flow.stat_energy_from) - ) ?? 0; + const { summedData, compareSummedData: _ } = getSummedData(this._data); + + const totalFromGrid = summedData.total.from_grid ?? 0; let waterUsage: number | null = null; if (hasWater) { @@ -136,37 +135,21 @@ class HuiEnergyDistrubutionCard let totalSolarProduction: number | null = null; if (hasSolarProduction) { - totalSolarProduction = - calculateStatisticsSumGrowth( - this._data.stats, - types.solar!.map((source) => source.stat_energy_from) - ) || 0; + totalSolarProduction = summedData.total.solar ?? 0; } let totalBatteryIn: number | null = null; let totalBatteryOut: number | null = null; if (hasBattery) { - totalBatteryIn = - calculateStatisticsSumGrowth( - this._data.stats, - types.battery!.map((source) => source.stat_energy_to) - ) || 0; - totalBatteryOut = - calculateStatisticsSumGrowth( - this._data.stats, - types.battery!.map((source) => source.stat_energy_from) - ) || 0; + totalBatteryIn = summedData.total.to_battery ?? 0; + totalBatteryOut = summedData.total.from_battery ?? 0; } let returnedToGrid: number | null = null; if (hasReturnToGrid) { - returnedToGrid = - calculateStatisticsSumGrowth( - this._data.stats, - types.grid![0].flow_to.map((flow) => flow.stat_energy_to) - ) || 0; + returnedToGrid = summedData.total.to_grid ?? 0; } let solarConsumption: number | null = null; diff --git a/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts index 180967aea8..457bc63ba7 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-grid-neutrality-gauge-card.ts @@ -9,12 +9,11 @@ import "../../../../components/ha-gauge"; import type { LevelDefinition } from "../../../../components/ha-gauge"; import "../../../../components/ha-svg-icon"; import "../../../../components/ha-tooltip"; -import type { - EnergyData, - GridSourceTypeEnergyPreference, +import type { EnergyData } from "../../../../data/energy"; +import { + getEnergyDataCollection, + getSummedData, } from "../../../../data/energy"; -import { getEnergyDataCollection } from "../../../../data/energy"; -import { calculateStatisticsSumGrowth } from "../../../../data/recorder"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../../../types"; import type { LovelaceCard } from "../../types"; @@ -75,27 +74,17 @@ class HuiEnergyGridGaugeCard "ui.panel.lovelace.cards.energy.loading" )}`; } - - const prefs = this._data.prefs; - const gridSource = prefs.energy_sources.find( - (src) => src.type === "grid" - ) as GridSourceTypeEnergyPreference | undefined; + const { summedData, compareSummedData: _ } = getSummedData(this._data); let value: number | undefined; - if (!gridSource) { + if (!("from_grid" in summedData.total)) { return nothing; } - const consumedFromGrid = calculateStatisticsSumGrowth( - this._data.stats, - gridSource.flow_from.map((flow) => flow.stat_energy_from) - ); + const consumedFromGrid = summedData.total.from_grid ?? 0; - const returnedToGrid = calculateStatisticsSumGrowth( - this._data.stats, - gridSource.flow_to.map((flow) => flow.stat_energy_to) - ); + const returnedToGrid = summedData.total.to_grid ?? 0; if (consumedFromGrid !== null && returnedToGrid !== null) { if (returnedToGrid > consumedFromGrid) { diff --git a/src/panels/lovelace/cards/energy/hui-energy-sankey-card.ts b/src/panels/lovelace/cards/energy/hui-energy-sankey-card.ts index a23b6dbc3e..216dd3e08d 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-sankey-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-sankey-card.ts @@ -8,9 +8,9 @@ import type { EnergyData } from "../../../../data/energy"; import { energySourcesByType, getEnergyDataCollection, + getSummedData, } from "../../../../data/energy"; import { - calculateStatisticsSumGrowth, calculateStatisticSumGrowth, getStatisticLabel, } from "../../../../data/recorder"; @@ -80,6 +80,7 @@ class HuiEnergySankeyCard const prefs = this._data.prefs; const types = energySourcesByType(prefs); + const { summedData, compareSummedData: _ } = getSummedData(this._data); const computedStyle = getComputedStyle(this); @@ -98,11 +99,7 @@ class HuiEnergySankeyCard nodes.push(homeNode); if (types.grid) { - const totalFromGrid = - calculateStatisticsSumGrowth( - this._data.stats, - types.grid![0].flow_from.map((flow) => flow.stat_energy_from) - ) ?? 0; + const totalFromGrid = summedData.total.from_grid ?? 0; nodes.push({ id: "grid", @@ -125,11 +122,7 @@ class HuiEnergySankeyCard // Add solar if available if (types.solar) { - const totalSolarProduction = - calculateStatisticsSumGrowth( - this._data.stats, - types.solar.map((source) => source.stat_energy_from) - ) || 0; + const totalSolarProduction = summedData.total.solar ?? 0; nodes.push({ id: "solar", @@ -155,16 +148,8 @@ class HuiEnergySankeyCard if (types.battery) { // Add battery source - const totalBatteryOut = - calculateStatisticsSumGrowth( - this._data.stats, - types.battery.map((source) => source.stat_energy_from) - ) || 0; - const totalBatteryIn = - calculateStatisticsSumGrowth( - this._data.stats, - types.battery.map((source) => source.stat_energy_to) - ) || 0; + const totalBatteryOut = summedData.total.from_battery ?? 0; + const totalBatteryIn = summedData.total.to_battery ?? 0; const netBattery = totalBatteryOut - totalBatteryIn; const netBatteryOut = Math.max(netBattery, 0); const netBatteryIn = Math.max(-netBattery, 0); @@ -209,11 +194,7 @@ class HuiEnergySankeyCard // Add grid return if available if (types.grid && types.grid[0].flow_to) { - const totalToGrid = - calculateStatisticsSumGrowth( - this._data.stats, - types.grid[0].flow_to.map((flow) => flow.stat_energy_to) - ) ?? 0; + const totalToGrid = summedData.total.to_grid ?? 0; nodes.push({ id: "grid_return", diff --git a/src/panels/lovelace/cards/energy/hui-energy-self-sufficiency-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-self-sufficiency-gauge-card.ts index e9b5acba71..18e94255c2 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-self-sufficiency-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-self-sufficiency-gauge-card.ts @@ -10,10 +10,9 @@ import "../../../../components/ha-svg-icon"; import "../../../../components/ha-tooltip"; import type { EnergyData } from "../../../../data/energy"; import { - energySourcesByType, getEnergyDataCollection, + getSummedData, } from "../../../../data/energy"; -import { calculateStatisticsSumGrowth } from "../../../../data/recorder"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../../../types"; import type { LovelaceCard } from "../../types"; @@ -75,56 +74,35 @@ class HuiEnergySelfSufficiencyGaugeCard )}`; } - const prefs = this._data.prefs; - const types = energySourcesByType(prefs); - // The strategy only includes this card if we have a grid. - const hasConsumption = true; + const { summedData, compareSummedData: _ } = getSummedData(this._data); - const hasSolarProduction = types.solar !== undefined; - const hasBattery = types.battery !== undefined; - const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0; + const hasSolarProduction = summedData.solar !== undefined; + const hasBattery = + summedData.to_battery !== undefined || + summedData.from_battery !== undefined; + const hasReturnToGrid = summedData.to_grid !== undefined; - const totalFromGrid = - calculateStatisticsSumGrowth( - this._data.stats, - types.grid![0].flow_from.map((flow) => flow.stat_energy_from) - ) ?? 0; + const totalFromGrid = summedData.total.from_grid ?? 0; let totalSolarProduction: number | null = null; if (hasSolarProduction) { - totalSolarProduction = - calculateStatisticsSumGrowth( - this._data.stats, - types.solar!.map((source) => source.stat_energy_from) - ) || 0; + totalSolarProduction = summedData.total.solar ?? 0; } let totalBatteryIn: number | null = null; let totalBatteryOut: number | null = null; if (hasBattery) { - totalBatteryIn = - calculateStatisticsSumGrowth( - this._data.stats, - types.battery!.map((source) => source.stat_energy_to) - ) || 0; - totalBatteryOut = - calculateStatisticsSumGrowth( - this._data.stats, - types.battery!.map((source) => source.stat_energy_from) - ) || 0; + totalBatteryIn = summedData.total.to_battery ?? 0; + totalBatteryOut = summedData.total.from_battery ?? 0; } let returnedToGrid: number | null = null; if (hasReturnToGrid) { - returnedToGrid = - calculateStatisticsSumGrowth( - this._data.stats, - types.grid![0].flow_to.map((flow) => flow.stat_energy_to) - ) || 0; + returnedToGrid = summedData.total.to_grid ?? 0; } let solarConsumption: number | null = null; diff --git a/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts b/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts index 061dd2f7a5..83e5e32c72 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-solar-consumed-gauge-card.ts @@ -9,10 +9,9 @@ import "../../../../components/ha-gauge"; import "../../../../components/ha-svg-icon"; import type { EnergyData } from "../../../../data/energy"; import { - energySourcesByType, getEnergyDataCollection, + getSummedData, } from "../../../../data/energy"; -import { calculateStatisticsSumGrowth } from "../../../../data/recorder"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../../../types"; import type { LovelaceCard } from "../../types"; @@ -74,23 +73,14 @@ class HuiEnergySolarGaugeCard )}`; } - const prefs = this._data.prefs; - const types = energySourcesByType(prefs); - - if (!types.solar) { + const { summedData, compareSummedData: _ } = getSummedData(this._data); + if (!("solar" in summedData.total)) { return nothing; } - const totalSolarProduction = - calculateStatisticsSumGrowth( - this._data.stats, - types.solar.map((source) => source.stat_energy_from) - ) || 0; + const totalSolarProduction = summedData.total.solar; - const productionReturnedToGrid = calculateStatisticsSumGrowth( - this._data.stats, - types.grid![0].flow_to.map((flow) => flow.stat_energy_to) - ); + const productionReturnedToGrid = summedData.total.to_grid ?? null; let value: number | undefined; diff --git a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts index cda61fecbf..5fe0782aa3 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-usage-graph-card.ts @@ -14,8 +14,11 @@ import { getEnergyColor } from "./common/color"; import { formatNumber } from "../../../../common/number/format_number"; import "../../../../components/chart/ha-chart-base"; import "../../../../components/ha-card"; -import type { EnergyData } from "../../../../data/energy"; -import { getEnergyDataCollection } from "../../../../data/energy"; +import type { EnergyData, EnergySumData } from "../../../../data/energy"; +import { + getEnergyDataCollection, + getSummedData, +} from "../../../../data/energy"; import type { Statistics, StatisticsMetaData } from "../../../../data/recorder"; import { getStatisticLabel } from "../../../../data/recorder"; import type { FrontendLocaleData } from "../../../../data/translation"; @@ -279,11 +282,14 @@ export class HuiEnergyUsageGraphCard this._compareStart = energyData.startCompare; this._compareEnd = energyData.endCompare; + const { summedData, compareSummedData } = getSummedData(energyData); + if (energyData.statsCompare) { datasets.push( ...this._processDataSet( energyData.statsCompare, energyData.statsMetadata, + compareSummedData!, statIds, colorIndices, computedStyles, @@ -308,6 +314,7 @@ export class HuiEnergyUsageGraphCard ...this._processDataSet( energyData.stats, energyData.statsMetadata, + summedData, statIds, colorIndices, computedStyles, @@ -325,6 +332,7 @@ export class HuiEnergyUsageGraphCard private _processDataSet( statistics: Statistics, statisticsMetaData: Record, + summedData: EnergySumData, statIdsByCat: { to_grid?: string[] | undefined; from_grid?: string[] | undefined; @@ -352,24 +360,11 @@ export class HuiEnergyUsageGraphCard used_battery?: Record>; } = {}; - const summedData: { - to_grid?: Record; - from_grid?: Record; - to_battery?: Record; - from_battery?: Record; - solar?: Record; - } = {}; - Object.entries(statIdsByCat).forEach(([key, statIds]) => { - const sum = [ - "solar", - "to_grid", - "from_grid", - "to_battery", - "from_battery", - ].includes(key); const add = !["solar", "from_battery"].includes(key); - const totalStats: Record = {}; + if (!add) { + return; + } const sets: Record> = {}; statIds!.forEach((id) => { const stats = statistics[id]; @@ -383,23 +378,13 @@ export class HuiEnergyUsageGraphCard return; } const val = stat.change; - // Get total of solar and to grid to calculate the solar energy used - if (sum) { - totalStats[stat.start] = - stat.start in totalStats ? totalStats[stat.start] + val : val; - } - if (add && !(stat.start in set)) { + if (!(stat.start in set)) { set[stat.start] = val; } }); sets[id] = set; }); - if (sum) { - summedData[key] = totalStats; - } - if (add) { - combinedData[key] = sets; - } + combinedData[key] = sets; }); const grid_to_battery = {};