Share energy sum calculation across all cards (#25184)

This commit is contained in:
karwosts 2025-04-26 10:58:33 -07:00 committed by GitHub
parent 8901c1fb31
commit 6464c2b602
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 72 additions and 173 deletions

View File

@ -782,12 +782,19 @@ export const getEnergyWaterUnit = (hass: HomeAssistant): string =>
export const energyStatisticHelpUrl = export const energyStatisticHelpUrl =
"/docs/energy/faq/#troubleshooting-missing-entities"; "/docs/energy/faq/#troubleshooting-missing-entities";
interface EnergySumData { export interface EnergySumData {
to_grid?: Record<number, number>; to_grid?: Record<number, number>;
from_grid?: Record<number, number>; from_grid?: Record<number, number>;
to_battery?: Record<number, number>; to_battery?: Record<number, number>;
from_battery?: Record<number, number>; from_battery?: Record<number, number>;
solar?: Record<number, number>; solar?: Record<number, number>;
total: {
to_grid?: number;
from_grid?: number;
to_battery?: number;
from_battery?: number;
solar?: number;
};
} }
interface EnergyConsumptionData { interface EnergyConsumptionData {
@ -860,29 +867,30 @@ const getSummedDataPartial = (
} }
} }
const summedData: EnergySumData = {}; const summedData: EnergySumData = { total: {} };
Object.entries(statIds).forEach(([key, subStatIds]) => { Object.entries(statIds).forEach(([key, subStatIds]) => {
const totalStats: Record<number, number> = {}; const totalStats: Record<number, number> = {};
const sets: Record<string, Record<number, number>> = {}; const sets: Record<string, Record<number, number>> = {};
let sum = 0;
subStatIds!.forEach((id) => { subStatIds!.forEach((id) => {
const stats = compare ? data.statsCompare[id] : data.stats[id]; const stats = compare ? data.statsCompare[id] : data.stats[id];
if (!stats) { if (!stats) {
return; return;
} }
const set = {}; const set = {};
stats.forEach((stat) => { stats.forEach((stat) => {
if (stat.change === null || stat.change === undefined) { if (stat.change === null || stat.change === undefined) {
return; return;
} }
const val = stat.change; const val = stat.change;
// Get total of solar and to grid to calculate the solar energy used sum += val;
totalStats[stat.start] = totalStats[stat.start] =
stat.start in totalStats ? totalStats[stat.start] + val : val; stat.start in totalStats ? totalStats[stat.start] + val : val;
}); });
sets[id] = set; sets[id] = set;
}); });
summedData[key] = totalStats; summedData[key] = totalStats;
summedData.total[key] = sum;
}); });
return summedData; return summedData;

View File

@ -11,10 +11,9 @@ import "../../../../components/ha-svg-icon";
import "../../../../components/ha-tooltip"; import "../../../../components/ha-tooltip";
import type { EnergyData } from "../../../../data/energy"; import type { EnergyData } from "../../../../data/energy";
import { import {
energySourcesByType,
getEnergyDataCollection, getEnergyDataCollection,
getSummedData,
} from "../../../../data/energy"; } from "../../../../data/energy";
import { calculateStatisticsSumGrowth } from "../../../../data/recorder";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import { createEntityNotFoundWarning } from "../../components/hui-warning"; import { createEntityNotFoundWarning } from "../../components/hui-warning";
@ -92,14 +91,9 @@ class HuiEnergyCarbonGaugeCard
</hui-warning>`; </hui-warning>`;
} }
const prefs = this._data.prefs; const { summedData, compareSummedData: _ } = getSummedData(this._data);
const types = energySourcesByType(prefs);
const totalGridConsumption = const totalGridConsumption = summedData.total.from_grid ?? 0;
calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
) ?? 0;
let value: number | undefined; let value: number | undefined;
@ -111,18 +105,9 @@ class HuiEnergyCarbonGaugeCard
) )
: 0; : 0;
const totalSolarProduction = types.solar const totalSolarProduction = summedData.total.solar ?? 0;
? calculateStatisticsSumGrowth(
this._data.stats,
types.solar.map((source) => source.stat_energy_from)
) || 0
: 0;
const totalGridReturned = const totalGridReturned = summedData.total.to_grid ?? 0;
calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
) || 0;
const totalEnergyConsumed = const totalEnergyConsumed =
totalGridConsumption + totalGridConsumption +

View File

@ -26,6 +26,7 @@ import {
getEnergyGasUnit, getEnergyGasUnit,
getEnergyWaterUnit, getEnergyWaterUnit,
formatConsumptionShort, formatConsumptionShort,
getSummedData,
} from "../../../../data/energy"; } from "../../../../data/energy";
import { calculateStatisticsSumGrowth } from "../../../../data/recorder"; import { calculateStatisticsSumGrowth } from "../../../../data/recorder";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
@ -109,11 +110,9 @@ class HuiEnergyDistrubutionCard
const hasWater = types.water !== undefined; const hasWater = types.water !== undefined;
const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0; const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0;
const totalFromGrid = const { summedData, compareSummedData: _ } = getSummedData(this._data);
calculateStatisticsSumGrowth(
this._data.stats, const totalFromGrid = summedData.total.from_grid ?? 0;
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
) ?? 0;
let waterUsage: number | null = null; let waterUsage: number | null = null;
if (hasWater) { if (hasWater) {
@ -136,37 +135,21 @@ class HuiEnergyDistrubutionCard
let totalSolarProduction: number | null = null; let totalSolarProduction: number | null = null;
if (hasSolarProduction) { if (hasSolarProduction) {
totalSolarProduction = totalSolarProduction = summedData.total.solar ?? 0;
calculateStatisticsSumGrowth(
this._data.stats,
types.solar!.map((source) => source.stat_energy_from)
) || 0;
} }
let totalBatteryIn: number | null = null; let totalBatteryIn: number | null = null;
let totalBatteryOut: number | null = null; let totalBatteryOut: number | null = null;
if (hasBattery) { if (hasBattery) {
totalBatteryIn = totalBatteryIn = summedData.total.to_battery ?? 0;
calculateStatisticsSumGrowth( totalBatteryOut = summedData.total.from_battery ?? 0;
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;
} }
let returnedToGrid: number | null = null; let returnedToGrid: number | null = null;
if (hasReturnToGrid) { if (hasReturnToGrid) {
returnedToGrid = returnedToGrid = summedData.total.to_grid ?? 0;
calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
) || 0;
} }
let solarConsumption: number | null = null; let solarConsumption: number | null = null;

View File

@ -9,12 +9,11 @@ import "../../../../components/ha-gauge";
import type { LevelDefinition } from "../../../../components/ha-gauge"; import type { LevelDefinition } from "../../../../components/ha-gauge";
import "../../../../components/ha-svg-icon"; import "../../../../components/ha-svg-icon";
import "../../../../components/ha-tooltip"; import "../../../../components/ha-tooltip";
import type { import type { EnergyData } from "../../../../data/energy";
EnergyData, import {
GridSourceTypeEnergyPreference, getEnergyDataCollection,
getSummedData,
} from "../../../../data/energy"; } from "../../../../data/energy";
import { getEnergyDataCollection } from "../../../../data/energy";
import { calculateStatisticsSumGrowth } from "../../../../data/recorder";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import type { LovelaceCard } from "../../types"; import type { LovelaceCard } from "../../types";
@ -75,27 +74,17 @@ class HuiEnergyGridGaugeCard
"ui.panel.lovelace.cards.energy.loading" "ui.panel.lovelace.cards.energy.loading"
)}`; )}`;
} }
const { summedData, compareSummedData: _ } = getSummedData(this._data);
const prefs = this._data.prefs;
const gridSource = prefs.energy_sources.find(
(src) => src.type === "grid"
) as GridSourceTypeEnergyPreference | undefined;
let value: number | undefined; let value: number | undefined;
if (!gridSource) { if (!("from_grid" in summedData.total)) {
return nothing; return nothing;
} }
const consumedFromGrid = calculateStatisticsSumGrowth( const consumedFromGrid = summedData.total.from_grid ?? 0;
this._data.stats,
gridSource.flow_from.map((flow) => flow.stat_energy_from)
);
const returnedToGrid = calculateStatisticsSumGrowth( const returnedToGrid = summedData.total.to_grid ?? 0;
this._data.stats,
gridSource.flow_to.map((flow) => flow.stat_energy_to)
);
if (consumedFromGrid !== null && returnedToGrid !== null) { if (consumedFromGrid !== null && returnedToGrid !== null) {
if (returnedToGrid > consumedFromGrid) { if (returnedToGrid > consumedFromGrid) {

View File

@ -8,9 +8,9 @@ import type { EnergyData } from "../../../../data/energy";
import { import {
energySourcesByType, energySourcesByType,
getEnergyDataCollection, getEnergyDataCollection,
getSummedData,
} from "../../../../data/energy"; } from "../../../../data/energy";
import { import {
calculateStatisticsSumGrowth,
calculateStatisticSumGrowth, calculateStatisticSumGrowth,
getStatisticLabel, getStatisticLabel,
} from "../../../../data/recorder"; } from "../../../../data/recorder";
@ -80,6 +80,7 @@ class HuiEnergySankeyCard
const prefs = this._data.prefs; const prefs = this._data.prefs;
const types = energySourcesByType(prefs); const types = energySourcesByType(prefs);
const { summedData, compareSummedData: _ } = getSummedData(this._data);
const computedStyle = getComputedStyle(this); const computedStyle = getComputedStyle(this);
@ -98,11 +99,7 @@ class HuiEnergySankeyCard
nodes.push(homeNode); nodes.push(homeNode);
if (types.grid) { if (types.grid) {
const totalFromGrid = const totalFromGrid = summedData.total.from_grid ?? 0;
calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
) ?? 0;
nodes.push({ nodes.push({
id: "grid", id: "grid",
@ -125,11 +122,7 @@ class HuiEnergySankeyCard
// Add solar if available // Add solar if available
if (types.solar) { if (types.solar) {
const totalSolarProduction = const totalSolarProduction = summedData.total.solar ?? 0;
calculateStatisticsSumGrowth(
this._data.stats,
types.solar.map((source) => source.stat_energy_from)
) || 0;
nodes.push({ nodes.push({
id: "solar", id: "solar",
@ -155,16 +148,8 @@ class HuiEnergySankeyCard
if (types.battery) { if (types.battery) {
// Add battery source // Add battery source
const totalBatteryOut = const totalBatteryOut = summedData.total.from_battery ?? 0;
calculateStatisticsSumGrowth( const totalBatteryIn = summedData.total.to_battery ?? 0;
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 netBattery = totalBatteryOut - totalBatteryIn; const netBattery = totalBatteryOut - totalBatteryIn;
const netBatteryOut = Math.max(netBattery, 0); const netBatteryOut = Math.max(netBattery, 0);
const netBatteryIn = Math.max(-netBattery, 0); const netBatteryIn = Math.max(-netBattery, 0);
@ -209,11 +194,7 @@ class HuiEnergySankeyCard
// Add grid return if available // Add grid return if available
if (types.grid && types.grid[0].flow_to) { if (types.grid && types.grid[0].flow_to) {
const totalToGrid = const totalToGrid = summedData.total.to_grid ?? 0;
calculateStatisticsSumGrowth(
this._data.stats,
types.grid[0].flow_to.map((flow) => flow.stat_energy_to)
) ?? 0;
nodes.push({ nodes.push({
id: "grid_return", id: "grid_return",

View File

@ -10,10 +10,9 @@ import "../../../../components/ha-svg-icon";
import "../../../../components/ha-tooltip"; import "../../../../components/ha-tooltip";
import type { EnergyData } from "../../../../data/energy"; import type { EnergyData } from "../../../../data/energy";
import { import {
energySourcesByType,
getEnergyDataCollection, getEnergyDataCollection,
getSummedData,
} from "../../../../data/energy"; } from "../../../../data/energy";
import { calculateStatisticsSumGrowth } from "../../../../data/recorder";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import type { LovelaceCard } 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. // 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 hasSolarProduction = summedData.solar !== undefined;
const hasBattery = types.battery !== undefined; const hasBattery =
const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0; summedData.to_battery !== undefined ||
summedData.from_battery !== undefined;
const hasReturnToGrid = summedData.to_grid !== undefined;
const totalFromGrid = const totalFromGrid = summedData.total.from_grid ?? 0;
calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
) ?? 0;
let totalSolarProduction: number | null = null; let totalSolarProduction: number | null = null;
if (hasSolarProduction) { if (hasSolarProduction) {
totalSolarProduction = totalSolarProduction = summedData.total.solar ?? 0;
calculateStatisticsSumGrowth(
this._data.stats,
types.solar!.map((source) => source.stat_energy_from)
) || 0;
} }
let totalBatteryIn: number | null = null; let totalBatteryIn: number | null = null;
let totalBatteryOut: number | null = null; let totalBatteryOut: number | null = null;
if (hasBattery) { if (hasBattery) {
totalBatteryIn = totalBatteryIn = summedData.total.to_battery ?? 0;
calculateStatisticsSumGrowth( totalBatteryOut = summedData.total.from_battery ?? 0;
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;
} }
let returnedToGrid: number | null = null; let returnedToGrid: number | null = null;
if (hasReturnToGrid) { if (hasReturnToGrid) {
returnedToGrid = returnedToGrid = summedData.total.to_grid ?? 0;
calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
) || 0;
} }
let solarConsumption: number | null = null; let solarConsumption: number | null = null;

View File

@ -9,10 +9,9 @@ import "../../../../components/ha-gauge";
import "../../../../components/ha-svg-icon"; import "../../../../components/ha-svg-icon";
import type { EnergyData } from "../../../../data/energy"; import type { EnergyData } from "../../../../data/energy";
import { import {
energySourcesByType,
getEnergyDataCollection, getEnergyDataCollection,
getSummedData,
} from "../../../../data/energy"; } from "../../../../data/energy";
import { calculateStatisticsSumGrowth } from "../../../../data/recorder";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types"; import type { HomeAssistant } from "../../../../types";
import type { LovelaceCard } from "../../types"; import type { LovelaceCard } from "../../types";
@ -74,23 +73,14 @@ class HuiEnergySolarGaugeCard
)}`; )}`;
} }
const prefs = this._data.prefs; const { summedData, compareSummedData: _ } = getSummedData(this._data);
const types = energySourcesByType(prefs); if (!("solar" in summedData.total)) {
if (!types.solar) {
return nothing; return nothing;
} }
const totalSolarProduction = const totalSolarProduction = summedData.total.solar;
calculateStatisticsSumGrowth(
this._data.stats,
types.solar.map((source) => source.stat_energy_from)
) || 0;
const productionReturnedToGrid = calculateStatisticsSumGrowth( const productionReturnedToGrid = summedData.total.to_grid ?? null;
this._data.stats,
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
);
let value: number | undefined; let value: number | undefined;

View File

@ -14,8 +14,11 @@ import { getEnergyColor } from "./common/color";
import { formatNumber } from "../../../../common/number/format_number"; import { formatNumber } from "../../../../common/number/format_number";
import "../../../../components/chart/ha-chart-base"; import "../../../../components/chart/ha-chart-base";
import "../../../../components/ha-card"; import "../../../../components/ha-card";
import type { EnergyData } from "../../../../data/energy"; import type { EnergyData, EnergySumData } from "../../../../data/energy";
import { getEnergyDataCollection } from "../../../../data/energy"; import {
getEnergyDataCollection,
getSummedData,
} from "../../../../data/energy";
import type { Statistics, StatisticsMetaData } from "../../../../data/recorder"; import type { Statistics, StatisticsMetaData } from "../../../../data/recorder";
import { getStatisticLabel } from "../../../../data/recorder"; import { getStatisticLabel } from "../../../../data/recorder";
import type { FrontendLocaleData } from "../../../../data/translation"; import type { FrontendLocaleData } from "../../../../data/translation";
@ -279,11 +282,14 @@ export class HuiEnergyUsageGraphCard
this._compareStart = energyData.startCompare; this._compareStart = energyData.startCompare;
this._compareEnd = energyData.endCompare; this._compareEnd = energyData.endCompare;
const { summedData, compareSummedData } = getSummedData(energyData);
if (energyData.statsCompare) { if (energyData.statsCompare) {
datasets.push( datasets.push(
...this._processDataSet( ...this._processDataSet(
energyData.statsCompare, energyData.statsCompare,
energyData.statsMetadata, energyData.statsMetadata,
compareSummedData!,
statIds, statIds,
colorIndices, colorIndices,
computedStyles, computedStyles,
@ -308,6 +314,7 @@ export class HuiEnergyUsageGraphCard
...this._processDataSet( ...this._processDataSet(
energyData.stats, energyData.stats,
energyData.statsMetadata, energyData.statsMetadata,
summedData,
statIds, statIds,
colorIndices, colorIndices,
computedStyles, computedStyles,
@ -325,6 +332,7 @@ export class HuiEnergyUsageGraphCard
private _processDataSet( private _processDataSet(
statistics: Statistics, statistics: Statistics,
statisticsMetaData: Record<string, StatisticsMetaData>, statisticsMetaData: Record<string, StatisticsMetaData>,
summedData: EnergySumData,
statIdsByCat: { statIdsByCat: {
to_grid?: string[] | undefined; to_grid?: string[] | undefined;
from_grid?: string[] | undefined; from_grid?: string[] | undefined;
@ -352,24 +360,11 @@ export class HuiEnergyUsageGraphCard
used_battery?: Record<string, Record<number, number>>; used_battery?: Record<string, Record<number, number>>;
} = {}; } = {};
const summedData: {
to_grid?: Record<number, number>;
from_grid?: Record<number, number>;
to_battery?: Record<number, number>;
from_battery?: Record<number, number>;
solar?: Record<number, number>;
} = {};
Object.entries(statIdsByCat).forEach(([key, statIds]) => { 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 add = !["solar", "from_battery"].includes(key);
const totalStats: Record<number, number> = {}; if (!add) {
return;
}
const sets: Record<string, Record<number, number>> = {}; const sets: Record<string, Record<number, number>> = {};
statIds!.forEach((id) => { statIds!.forEach((id) => {
const stats = statistics[id]; const stats = statistics[id];
@ -383,23 +378,13 @@ export class HuiEnergyUsageGraphCard
return; return;
} }
const val = stat.change; const val = stat.change;
// Get total of solar and to grid to calculate the solar energy used if (!(stat.start in set)) {
if (sum) {
totalStats[stat.start] =
stat.start in totalStats ? totalStats[stat.start] + val : val;
}
if (add && !(stat.start in set)) {
set[stat.start] = val; set[stat.start] = val;
} }
}); });
sets[id] = set; sets[id] = set;
}); });
if (sum) { combinedData[key] = sets;
summedData[key] = totalStats;
}
if (add) {
combinedData[key] = sets;
}
}); });
const grid_to_battery = {}; const grid_to_battery = {};