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 =
"/docs/energy/faq/#troubleshooting-missing-entities";
interface EnergySumData {
export interface EnergySumData {
to_grid?: Record<number, number>;
from_grid?: Record<number, number>;
to_battery?: Record<number, number>;
from_battery?: Record<number, number>;
solar?: Record<number, number>;
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<number, number> = {};
const sets: Record<string, Record<number, number>> = {};
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;

View File

@ -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
</hui-warning>`;
}
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 +

View File

@ -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;

View File

@ -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) {

View File

@ -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",

View File

@ -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;

View File

@ -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;

View File

@ -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<string, StatisticsMetaData>,
summedData: EnergySumData,
statIdsByCat: {
to_grid?: string[] | undefined;
from_grid?: string[] | undefined;
@ -352,24 +360,11 @@ export class HuiEnergyUsageGraphCard
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]) => {
const sum = [
"solar",
"to_grid",
"from_grid",
"to_battery",
"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>> = {};
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 = {};