mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-23 09:16:38 +00:00
Speed up data loading and allow embedding individual energy cards (#9660)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
539d2b880c
commit
0f16ba9325
@ -1,4 +1,9 @@
|
|||||||
|
import { Collection, getCollection } from "home-assistant-js-websocket";
|
||||||
|
import { subscribeOne } from "../common/util/subscribe-one";
|
||||||
import { HomeAssistant } from "../types";
|
import { HomeAssistant } from "../types";
|
||||||
|
import { ConfigEntry, getConfigEntries } from "./config_entries";
|
||||||
|
import { subscribeEntityRegistry } from "./entity_registry";
|
||||||
|
import { fetchStatistics, Statistics } from "./history";
|
||||||
|
|
||||||
export const emptyFlowFromGridSourceEnergyPreference =
|
export const emptyFlowFromGridSourceEnergyPreference =
|
||||||
(): FlowFromGridSourceEnergyPreference => ({
|
(): FlowFromGridSourceEnergyPreference => ({
|
||||||
@ -128,3 +133,137 @@ export const energySourcesByType = (prefs: EnergyPreferences) => {
|
|||||||
}
|
}
|
||||||
return types;
|
return types;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface EnergyData {
|
||||||
|
start: Date;
|
||||||
|
end?: Date;
|
||||||
|
prefs: EnergyPreferences;
|
||||||
|
info: EnergyInfo;
|
||||||
|
stats: Statistics;
|
||||||
|
co2SignalConfigEntry?: ConfigEntry;
|
||||||
|
co2SignalEntity?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getEnergyData = async (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
prefs: EnergyPreferences,
|
||||||
|
start: Date,
|
||||||
|
end?: Date
|
||||||
|
): Promise<EnergyData> => {
|
||||||
|
const [configEntries, entityRegistryEntries, info] = await Promise.all([
|
||||||
|
getConfigEntries(hass),
|
||||||
|
subscribeOne(hass.connection, subscribeEntityRegistry),
|
||||||
|
getEnergyInfo(hass),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const co2SignalConfigEntry = configEntries.find(
|
||||||
|
(entry) => entry.domain === "co2signal"
|
||||||
|
);
|
||||||
|
|
||||||
|
let co2SignalEntity: string | undefined;
|
||||||
|
|
||||||
|
if (co2SignalConfigEntry) {
|
||||||
|
for (const entry of entityRegistryEntries) {
|
||||||
|
if (entry.config_entry_id !== co2SignalConfigEntry.entry_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The integration offers 2 entities. We want the % one.
|
||||||
|
const co2State = hass.states[entry.entity_id];
|
||||||
|
if (!co2State || co2State.attributes.unit_of_measurement !== "%") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
co2SignalEntity = co2State.entity_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const statIDs: string[] = [];
|
||||||
|
|
||||||
|
if (co2SignalEntity !== undefined) {
|
||||||
|
statIDs.push(co2SignalEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const source of prefs.energy_sources) {
|
||||||
|
if (source.type === "solar") {
|
||||||
|
statIDs.push(source.stat_energy_from);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// grid source
|
||||||
|
for (const flowFrom of source.flow_from) {
|
||||||
|
statIDs.push(flowFrom.stat_energy_from);
|
||||||
|
}
|
||||||
|
for (const flowTo of source.flow_to) {
|
||||||
|
statIDs.push(flowTo.stat_energy_to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = await fetchStatistics(hass!, start, end, statIDs);
|
||||||
|
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
info,
|
||||||
|
prefs,
|
||||||
|
stats,
|
||||||
|
co2SignalConfigEntry,
|
||||||
|
co2SignalEntity,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface EnergyCollection extends Collection<EnergyData> {
|
||||||
|
start: Date;
|
||||||
|
end?: Date;
|
||||||
|
prefs?: EnergyPreferences;
|
||||||
|
clearPrefs(): void;
|
||||||
|
setPeriod(newStart: Date, newEnd?: Date): void;
|
||||||
|
getDeviceStatIds(): string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getEnergyDataCollection = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
prefs?: EnergyPreferences
|
||||||
|
): EnergyCollection => {
|
||||||
|
if ((hass.connection as any)._energy) {
|
||||||
|
return (hass.connection as any)._energy;
|
||||||
|
}
|
||||||
|
|
||||||
|
const collection = getCollection<EnergyData>(
|
||||||
|
hass.connection,
|
||||||
|
"_energy",
|
||||||
|
async () => {
|
||||||
|
if (!collection.prefs) {
|
||||||
|
// This will raise if not found.
|
||||||
|
// Detect by checking `e.code === "not_found"
|
||||||
|
collection.prefs = await getEnergyPreferences(hass);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getEnergyData(
|
||||||
|
hass,
|
||||||
|
collection.prefs,
|
||||||
|
collection.start,
|
||||||
|
collection.end
|
||||||
|
);
|
||||||
|
}
|
||||||
|
) as EnergyCollection;
|
||||||
|
|
||||||
|
collection.prefs = prefs;
|
||||||
|
collection.start = new Date();
|
||||||
|
collection.start.setHours(0, 0, 0, 0);
|
||||||
|
collection.start.setTime(collection.start.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
|
||||||
|
|
||||||
|
collection.clearPrefs = () => {
|
||||||
|
collection.prefs = undefined;
|
||||||
|
};
|
||||||
|
collection.setPeriod = (newStart: Date, newEnd?: Date) => {
|
||||||
|
collection.start = newStart;
|
||||||
|
collection.end = newEnd;
|
||||||
|
};
|
||||||
|
collection.getDeviceStatIds = () =>
|
||||||
|
collection.state.prefs.device_consumption.map(
|
||||||
|
(device) => device.stat_consumption
|
||||||
|
);
|
||||||
|
return collection;
|
||||||
|
};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
EnergyPreferences,
|
EnergyPreferences,
|
||||||
|
getEnergyDataCollection,
|
||||||
getEnergyPreferences,
|
getEnergyPreferences,
|
||||||
GridSourceTypeEnergyPreference,
|
GridSourceTypeEnergyPreference,
|
||||||
} from "../../../data/energy";
|
} from "../../../data/energy";
|
||||||
@ -26,10 +27,10 @@ export class EnergyStrategy {
|
|||||||
|
|
||||||
const view: LovelaceViewConfig = { cards: [] };
|
const view: LovelaceViewConfig = { cards: [] };
|
||||||
|
|
||||||
let energyPrefs: EnergyPreferences;
|
let prefs: EnergyPreferences;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
energyPrefs = await getEnergyPreferences(hass);
|
prefs = await getEnergyPreferences(hass);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.code === "not_found") {
|
if (e.code === "not_found") {
|
||||||
return setupWizard();
|
return setupWizard();
|
||||||
@ -43,20 +44,21 @@ export class EnergyStrategy {
|
|||||||
|
|
||||||
view.type = "sidebar";
|
view.type = "sidebar";
|
||||||
|
|
||||||
const hasGrid = energyPrefs.energy_sources.find(
|
const hasGrid = prefs.energy_sources.find(
|
||||||
(source) => source.type === "grid"
|
(source) => source.type === "grid"
|
||||||
) as GridSourceTypeEnergyPreference;
|
) as GridSourceTypeEnergyPreference;
|
||||||
const hasReturn = hasGrid && hasGrid.flow_to.length;
|
const hasReturn = hasGrid && hasGrid.flow_to.length;
|
||||||
const hasSolar = energyPrefs.energy_sources.some(
|
const hasSolar = prefs.energy_sources.some(
|
||||||
(source) => source.type === "solar"
|
(source) => source.type === "solar"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
getEnergyDataCollection(hass, prefs);
|
||||||
|
|
||||||
// Only include if we have a grid source.
|
// Only include if we have a grid source.
|
||||||
if (hasGrid) {
|
if (hasGrid) {
|
||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
title: "Energy usage",
|
title: "Energy usage",
|
||||||
type: "energy-usage-graph",
|
type: "energy-usage-graph",
|
||||||
prefs: energyPrefs,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +67,6 @@ export class EnergyStrategy {
|
|||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
title: "Solar production",
|
title: "Solar production",
|
||||||
type: "energy-solar-graph",
|
type: "energy-solar-graph",
|
||||||
prefs: energyPrefs,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +75,6 @@ export class EnergyStrategy {
|
|||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
title: "Energy distribution",
|
title: "Energy distribution",
|
||||||
type: "energy-distribution",
|
type: "energy-distribution",
|
||||||
prefs: energyPrefs,
|
|
||||||
view_layout: { position: "sidebar" },
|
view_layout: { position: "sidebar" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -83,16 +83,6 @@ export class EnergyStrategy {
|
|||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
title: "Sources",
|
title: "Sources",
|
||||||
type: "energy-sources-table",
|
type: "energy-sources-table",
|
||||||
prefs: energyPrefs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only include if we have a solar source.
|
|
||||||
if (hasSolar) {
|
|
||||||
view.cards!.push({
|
|
||||||
type: "energy-solar-consumed-gauge",
|
|
||||||
prefs: energyPrefs,
|
|
||||||
view_layout: { position: "sidebar" },
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,7 +90,14 @@ export class EnergyStrategy {
|
|||||||
if (hasReturn) {
|
if (hasReturn) {
|
||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
type: "energy-grid-neutrality-gauge",
|
type: "energy-grid-neutrality-gauge",
|
||||||
prefs: energyPrefs,
|
view_layout: { position: "sidebar" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only include if we have a solar source.
|
||||||
|
if (hasSolar && hasReturn) {
|
||||||
|
view.cards!.push({
|
||||||
|
type: "energy-solar-consumed-gauge",
|
||||||
view_layout: { position: "sidebar" },
|
view_layout: { position: "sidebar" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -109,17 +106,15 @@ export class EnergyStrategy {
|
|||||||
if (hasGrid) {
|
if (hasGrid) {
|
||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
type: "energy-carbon-consumed-gauge",
|
type: "energy-carbon-consumed-gauge",
|
||||||
prefs: energyPrefs,
|
|
||||||
view_layout: { position: "sidebar" },
|
view_layout: { position: "sidebar" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only include if we have at least 1 device in the config.
|
// Only include if we have at least 1 device in the config.
|
||||||
if (energyPrefs.device_consumption.length) {
|
if (prefs.device_consumption.length) {
|
||||||
view.cards!.push({
|
view.cards!.push({
|
||||||
title: "Monitor individual devices",
|
title: "Monitor individual devices",
|
||||||
type: "energy-devices-graph",
|
type: "energy-devices-graph",
|
||||||
prefs: energyPrefs,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import { round } from "../../../../common/number/round";
|
import { round } from "../../../../common/number/round";
|
||||||
import { subscribeOne } from "../../../../common/util/subscribe-one";
|
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import "../../../../components/ha-gauge";
|
import "../../../../components/ha-gauge";
|
||||||
import { getConfigEntries } from "../../../../data/config_entries";
|
import {
|
||||||
import { energySourcesByType } from "../../../../data/energy";
|
EnergyData,
|
||||||
import { subscribeEntityRegistry } from "../../../../data/entity_registry";
|
energySourcesByType,
|
||||||
|
getEnergyDataCollection,
|
||||||
|
} from "../../../../data/energy";
|
||||||
import {
|
import {
|
||||||
calculateStatisticsSumGrowth,
|
calculateStatisticsSumGrowth,
|
||||||
calculateStatisticsSumGrowthWithPercentage,
|
calculateStatisticsSumGrowthWithPercentage,
|
||||||
fetchStatistics,
|
|
||||||
Statistics,
|
|
||||||
} from "../../../../data/history";
|
} from "../../../../data/history";
|
||||||
|
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";
|
||||||
import type { LovelaceCard } from "../../types";
|
import type { LovelaceCard } from "../../types";
|
||||||
@ -21,14 +22,15 @@ import { severityMap } from "../hui-gauge-card";
|
|||||||
import type { EnergyCarbonGaugeCardConfig } from "../types";
|
import type { EnergyCarbonGaugeCardConfig } from "../types";
|
||||||
|
|
||||||
@customElement("hui-energy-carbon-consumed-gauge-card")
|
@customElement("hui-energy-carbon-consumed-gauge-card")
|
||||||
class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard {
|
class HuiEnergyCarbonGaugeCard
|
||||||
|
extends SubscribeMixin(LitElement)
|
||||||
|
implements LovelaceCard
|
||||||
|
{
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _config?: EnergyCarbonGaugeCardConfig;
|
@state() private _config?: EnergyCarbonGaugeCardConfig;
|
||||||
|
|
||||||
@state() private _stats?: Statistics;
|
@state() private _data?: EnergyData;
|
||||||
|
|
||||||
@state() private _co2SignalEntity?: string | null;
|
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return 4;
|
return 4;
|
||||||
@ -38,12 +40,12 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard {
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps) {
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
super.willUpdate(changedProps);
|
return [
|
||||||
|
getEnergyDataCollection(this.hass).subscribe((data) => {
|
||||||
if (!this.hasUpdated) {
|
this._data = data;
|
||||||
this._getStatistics();
|
}),
|
||||||
}
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
@ -51,52 +53,55 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard {
|
|||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._co2SignalEntity === null) {
|
if (!this._data) {
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._stats || !this._co2SignalEntity) {
|
|
||||||
return html`Loading...`;
|
return html`Loading...`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const co2State = this.hass.states[this._co2SignalEntity];
|
if (!this._data.co2SignalEntity) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
const co2State = this.hass.states[this._data.co2SignalEntity];
|
||||||
|
|
||||||
if (!co2State) {
|
if (!co2State) {
|
||||||
return html`<hui-warning>
|
return html`<hui-warning>
|
||||||
${createEntityNotFoundWarning(this.hass, this._co2SignalEntity)}
|
${createEntityNotFoundWarning(this.hass, this._data.co2SignalEntity)}
|
||||||
</hui-warning>`;
|
</hui-warning>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefs = this._config!.prefs;
|
const prefs = this._data.prefs;
|
||||||
const types = energySourcesByType(prefs);
|
const types = energySourcesByType(prefs);
|
||||||
|
|
||||||
const totalGridConsumption = calculateStatisticsSumGrowth(
|
const totalGridConsumption = calculateStatisticsSumGrowth(
|
||||||
this._stats,
|
this._data.stats,
|
||||||
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
|
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
|
||||||
);
|
);
|
||||||
|
|
||||||
let value: number | undefined;
|
let value: number | undefined;
|
||||||
|
|
||||||
if (this._co2SignalEntity in this._stats && totalGridConsumption) {
|
if (
|
||||||
|
this._data.co2SignalEntity in this._data.stats &&
|
||||||
|
totalGridConsumption
|
||||||
|
) {
|
||||||
const highCarbonEnergy =
|
const highCarbonEnergy =
|
||||||
calculateStatisticsSumGrowthWithPercentage(
|
calculateStatisticsSumGrowthWithPercentage(
|
||||||
this._stats[this._co2SignalEntity],
|
this._data.stats[this._data.co2SignalEntity],
|
||||||
types
|
types
|
||||||
.grid![0].flow_from.map(
|
.grid![0].flow_from.map(
|
||||||
(flow) => this._stats![flow.stat_energy_from]
|
(flow) => this._data!.stats![flow.stat_energy_from]
|
||||||
)
|
)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
) || 0;
|
) || 0;
|
||||||
|
|
||||||
const totalSolarProduction = types.solar
|
const totalSolarProduction = types.solar
|
||||||
? calculateStatisticsSumGrowth(
|
? calculateStatisticsSumGrowth(
|
||||||
this._stats,
|
this._data.stats,
|
||||||
types.solar.map((source) => source.stat_energy_from)
|
types.solar.map((source) => source.stat_energy_from)
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const totalGridReturned = calculateStatisticsSumGrowth(
|
const totalGridReturned = calculateStatisticsSumGrowth(
|
||||||
this._stats,
|
this._data.stats,
|
||||||
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
|
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -139,78 +144,6 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard {
|
|||||||
return severityMap.normal;
|
return severityMap.normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchCO2SignalEntity() {
|
|
||||||
const [configEntries, entityRegistryEntries] = await Promise.all([
|
|
||||||
getConfigEntries(this.hass),
|
|
||||||
subscribeOne(this.hass.connection, subscribeEntityRegistry),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const co2ConfigEntry = configEntries.find(
|
|
||||||
(entry) => entry.domain === "co2signal"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!co2ConfigEntry) {
|
|
||||||
this._co2SignalEntity = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const entry of entityRegistryEntries) {
|
|
||||||
if (entry.config_entry_id !== co2ConfigEntry.entry_id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The integration offers 2 entities. We want the % one.
|
|
||||||
const co2State = this.hass.states[entry.entity_id];
|
|
||||||
if (!co2State || co2State.attributes.unit_of_measurement !== "%") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._co2SignalEntity = co2State.entity_id;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._co2SignalEntity = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _getStatistics(): Promise<void> {
|
|
||||||
await this._fetchCO2SignalEntity();
|
|
||||||
|
|
||||||
if (this._co2SignalEntity === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const startDate = new Date();
|
|
||||||
startDate.setHours(0, 0, 0, 0);
|
|
||||||
startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
|
|
||||||
|
|
||||||
const statistics: string[] = [];
|
|
||||||
const prefs = this._config!.prefs;
|
|
||||||
for (const source of prefs.energy_sources) {
|
|
||||||
if (source.type === "solar") {
|
|
||||||
statistics.push(source.stat_energy_from);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// grid source
|
|
||||||
for (const flowFrom of source.flow_from) {
|
|
||||||
statistics.push(flowFrom.stat_energy_from);
|
|
||||||
}
|
|
||||||
for (const flowTo of source.flow_to) {
|
|
||||||
statistics.push(flowTo.stat_energy_to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._co2SignalEntity) {
|
|
||||||
statistics.push(this._co2SignalEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._stats = await fetchStatistics(
|
|
||||||
this.hass!,
|
|
||||||
startDate,
|
|
||||||
undefined,
|
|
||||||
statistics
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
ha-card {
|
||||||
|
@ -4,16 +4,11 @@ import {
|
|||||||
ChartOptions,
|
ChartOptions,
|
||||||
ParsedDataType,
|
ParsedDataType,
|
||||||
} from "chart.js";
|
} from "chart.js";
|
||||||
import {
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
css,
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { getColorByIndex } from "../../../../common/color/colors";
|
import { getColorByIndex } from "../../../../common/color/colors";
|
||||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||||
import {
|
import {
|
||||||
@ -22,18 +17,21 @@ import {
|
|||||||
} from "../../../../common/string/format_number";
|
} from "../../../../common/string/format_number";
|
||||||
import "../../../../components/chart/ha-chart-base";
|
import "../../../../components/chart/ha-chart-base";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
|
import { EnergyData, getEnergyDataCollection } from "../../../../data/energy";
|
||||||
import {
|
import {
|
||||||
calculateStatisticSumGrowth,
|
calculateStatisticSumGrowth,
|
||||||
fetchStatistics,
|
fetchStatistics,
|
||||||
Statistics,
|
Statistics,
|
||||||
} from "../../../../data/history";
|
} from "../../../../data/history";
|
||||||
|
import { FrontendLocaleData } from "../../../../data/translation";
|
||||||
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergyDevicesGraphCardConfig } from "../types";
|
import { EnergyDevicesGraphCardConfig } from "../types";
|
||||||
|
|
||||||
@customElement("hui-energy-devices-graph-card")
|
@customElement("hui-energy-devices-graph-card")
|
||||||
export class HuiEnergyDevicesGraphCard
|
export class HuiEnergyDevicesGraphCard
|
||||||
extends LitElement
|
extends SubscribeMixin(LitElement)
|
||||||
implements LovelaceCard
|
implements LovelaceCard
|
||||||
{
|
{
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -44,32 +42,12 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
|
|
||||||
@state() private _chartData?: ChartData;
|
@state() private _chartData?: ChartData;
|
||||||
|
|
||||||
@state() private _chartOptions?: ChartOptions;
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
|
return [
|
||||||
private _fetching = false;
|
getEnergyDataCollection(this.hass).subscribe((data) =>
|
||||||
|
this._getStatistics(data)
|
||||||
private _interval?: number;
|
),
|
||||||
|
];
|
||||||
public disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
if (this._interval) {
|
|
||||||
clearInterval(this._interval);
|
|
||||||
this._interval = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._getStatistics();
|
|
||||||
// statistics are created every hour
|
|
||||||
clearInterval(this._interval);
|
|
||||||
this._interval = window.setInterval(
|
|
||||||
() => this._getStatistics(),
|
|
||||||
1000 * 60 * 60
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCardSize(): Promise<number> | number {
|
public getCardSize(): Promise<number> | number {
|
||||||
@ -80,30 +58,6 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues) {
|
|
||||||
super.willUpdate(changedProps);
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
this._createOptions();
|
|
||||||
}
|
|
||||||
if (!this._config || !changedProps.has("_config")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldConfig = changedProps.get("_config") as
|
|
||||||
| EnergyDevicesGraphCardConfig
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
if (oldConfig !== this._config) {
|
|
||||||
this._getStatistics();
|
|
||||||
// statistics are created every hour
|
|
||||||
clearInterval(this._interval);
|
|
||||||
this._interval = window.setInterval(
|
|
||||||
() => this._getStatistics(),
|
|
||||||
1000 * 60 * 60
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config) {
|
||||||
return html``;
|
return html``;
|
||||||
@ -122,7 +76,7 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
${this._chartData
|
${this._chartData
|
||||||
? html`<ha-chart-base
|
? html`<ha-chart-base
|
||||||
.data=${this._chartData}
|
.data=${this._chartData}
|
||||||
.options=${this._chartOptions}
|
.options=${this._createOptions(this.hass.locale)}
|
||||||
chart-type="bar"
|
chart-type="bar"
|
||||||
></ha-chart-base>`
|
></ha-chart-base>`
|
||||||
: ""}
|
: ""}
|
||||||
@ -131,8 +85,8 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createOptions() {
|
private _createOptions = memoizeOne(
|
||||||
this._chartOptions = {
|
(locale: FrontendLocaleData): ChartOptions => ({
|
||||||
parsing: false,
|
parsing: false,
|
||||||
animation: false,
|
animation: false,
|
||||||
responsive: true,
|
responsive: true,
|
||||||
@ -153,37 +107,24 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
label: (context) =>
|
label: (context) =>
|
||||||
`${context.dataset.label}: ${formatNumber(
|
`${context.dataset.label}: ${formatNumber(
|
||||||
context.parsed.x,
|
context.parsed.x,
|
||||||
this.hass.locale
|
locale
|
||||||
)} kWh`,
|
)} kWh`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
locale: numberFormatToLocale(this.hass.locale),
|
locale: numberFormatToLocale(this.hass.locale),
|
||||||
};
|
})
|
||||||
}
|
);
|
||||||
|
|
||||||
private async _getStatistics(): Promise<void> {
|
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
||||||
if (this._fetching) {
|
const energyCollection = getEnergyDataCollection(this.hass);
|
||||||
return;
|
this._data = await fetchStatistics(
|
||||||
}
|
this.hass,
|
||||||
const startDate = new Date();
|
energyCollection.start,
|
||||||
startDate.setHours(0, 0, 0, 0);
|
energyCollection.end,
|
||||||
startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
|
energyCollection.getDeviceStatIds()
|
||||||
|
|
||||||
this._fetching = true;
|
|
||||||
const prefs = this._config!.prefs;
|
|
||||||
|
|
||||||
try {
|
|
||||||
this._data = await fetchStatistics(
|
|
||||||
this.hass!,
|
|
||||||
startDate,
|
|
||||||
undefined,
|
|
||||||
prefs.device_consumption.map((device) => device.stat_consumption)
|
|
||||||
);
|
);
|
||||||
} finally {
|
|
||||||
this._fetching = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const statisticsData = Object.values(this._data!);
|
const statisticsData = Object.values(this._data!);
|
||||||
let endTime: Date;
|
let endTime: Date;
|
||||||
@ -213,8 +154,8 @@ export class HuiEnergyDevicesGraphCard
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
for (let idx = 0; idx < prefs.device_consumption.length; idx++) {
|
for (let idx = 0; idx < energyData.prefs.device_consumption.length; idx++) {
|
||||||
const device = prefs.device_consumption[idx];
|
const device = energyData.prefs.device_consumption[idx];
|
||||||
const entity = this.hass.states[device.stat_consumption];
|
const entity = this.hass.states[device.stat_consumption];
|
||||||
const label = entity ? computeStateName(entity) : device.stat_consumption;
|
const label = entity ? computeStateName(entity) : device.stat_consumption;
|
||||||
|
|
||||||
|
@ -6,23 +6,24 @@ import {
|
|||||||
mdiSolarPower,
|
mdiSolarPower,
|
||||||
mdiTransmissionTower,
|
mdiTransmissionTower,
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, html, LitElement, svg } from "lit";
|
import { css, html, LitElement, svg } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import { ifDefined } from "lit/directives/if-defined";
|
import { ifDefined } from "lit/directives/if-defined";
|
||||||
import { formatNumber } from "../../../../common/string/format_number";
|
import { formatNumber } from "../../../../common/string/format_number";
|
||||||
import { subscribeOne } from "../../../../common/util/subscribe-one";
|
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
import { getConfigEntries } from "../../../../data/config_entries";
|
import {
|
||||||
import { energySourcesByType } from "../../../../data/energy";
|
EnergyData,
|
||||||
import { subscribeEntityRegistry } from "../../../../data/entity_registry";
|
energySourcesByType,
|
||||||
|
getEnergyDataCollection,
|
||||||
|
} from "../../../../data/energy";
|
||||||
import {
|
import {
|
||||||
calculateStatisticsSumGrowth,
|
calculateStatisticsSumGrowth,
|
||||||
calculateStatisticsSumGrowthWithPercentage,
|
calculateStatisticsSumGrowthWithPercentage,
|
||||||
fetchStatistics,
|
|
||||||
Statistics,
|
|
||||||
} from "../../../../data/history";
|
} from "../../../../data/history";
|
||||||
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergyDistributionCardConfig } from "../types";
|
import { EnergyDistributionCardConfig } from "../types";
|
||||||
@ -30,46 +31,42 @@ import { EnergyDistributionCardConfig } from "../types";
|
|||||||
const CIRCLE_CIRCUMFERENCE = 238.76104;
|
const CIRCLE_CIRCUMFERENCE = 238.76104;
|
||||||
|
|
||||||
@customElement("hui-energy-distribution-card")
|
@customElement("hui-energy-distribution-card")
|
||||||
class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
class HuiEnergyDistrubutionCard
|
||||||
|
extends SubscribeMixin(LitElement)
|
||||||
|
implements LovelaceCard
|
||||||
|
{
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _config?: EnergyDistributionCardConfig;
|
@state() private _config?: EnergyDistributionCardConfig;
|
||||||
|
|
||||||
@state() private _stats?: Statistics;
|
@state() private _data?: EnergyData;
|
||||||
|
|
||||||
@state() private _co2SignalEntity?: string;
|
|
||||||
|
|
||||||
private _fetching = false;
|
|
||||||
|
|
||||||
public setConfig(config: EnergyDistributionCardConfig): void {
|
public setConfig(config: EnergyDistributionCardConfig): void {
|
||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
|
return [
|
||||||
|
getEnergyDataCollection(this.hass).subscribe((data) => {
|
||||||
|
this._data = data;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public getCardSize(): Promise<number> | number {
|
public getCardSize(): Promise<number> | number {
|
||||||
return 3;
|
return 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps) {
|
|
||||||
super.willUpdate(changedProps);
|
|
||||||
|
|
||||||
if (!this._fetching && !this._stats) {
|
|
||||||
this._fetching = true;
|
|
||||||
this._getStatistics().then(() => {
|
|
||||||
this._fetching = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._stats) {
|
if (!this._data) {
|
||||||
return html`Loading…`;
|
return html`Loading…`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefs = this._config!.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.
|
// The strategy only includes this card if we have a grid.
|
||||||
@ -80,7 +77,7 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
const totalGridConsumption =
|
const totalGridConsumption =
|
||||||
calculateStatisticsSumGrowth(
|
calculateStatisticsSumGrowth(
|
||||||
this._stats,
|
this._data.stats,
|
||||||
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
|
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
|
||||||
) ?? 0;
|
) ?? 0;
|
||||||
|
|
||||||
@ -89,7 +86,7 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
|||||||
if (hasSolarProduction) {
|
if (hasSolarProduction) {
|
||||||
totalSolarProduction =
|
totalSolarProduction =
|
||||||
calculateStatisticsSumGrowth(
|
calculateStatisticsSumGrowth(
|
||||||
this._stats,
|
this._data.stats,
|
||||||
types.solar!.map((source) => source.stat_energy_from)
|
types.solar!.map((source) => source.stat_energy_from)
|
||||||
) || 0;
|
) || 0;
|
||||||
}
|
}
|
||||||
@ -99,7 +96,7 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
|||||||
if (hasReturnToGrid) {
|
if (hasReturnToGrid) {
|
||||||
productionReturnedToGrid =
|
productionReturnedToGrid =
|
||||||
calculateStatisticsSumGrowth(
|
calculateStatisticsSumGrowth(
|
||||||
this._stats,
|
this._data.stats,
|
||||||
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
|
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
|
||||||
) || 0;
|
) || 0;
|
||||||
}
|
}
|
||||||
@ -124,16 +121,21 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
let electricityMapUrl: string | undefined;
|
let electricityMapUrl: string | undefined;
|
||||||
|
|
||||||
if (this._co2SignalEntity && this._co2SignalEntity in this._stats) {
|
if (
|
||||||
|
this._data.co2SignalEntity &&
|
||||||
|
this._data.co2SignalEntity in this._data.stats
|
||||||
|
) {
|
||||||
// Calculate high carbon consumption
|
// Calculate high carbon consumption
|
||||||
const highCarbonConsumption = calculateStatisticsSumGrowthWithPercentage(
|
const highCarbonConsumption = calculateStatisticsSumGrowthWithPercentage(
|
||||||
this._stats[this._co2SignalEntity],
|
this._data.stats[this._data.co2SignalEntity],
|
||||||
types
|
types
|
||||||
.grid![0].flow_from.map((flow) => this._stats![flow.stat_energy_from])
|
.grid![0].flow_from.map(
|
||||||
|
(flow) => this._data!.stats[flow.stat_energy_from]
|
||||||
|
)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
);
|
);
|
||||||
|
|
||||||
const co2State = this.hass.states[this._co2SignalEntity];
|
const co2State = this.hass.states[this._data.co2SignalEntity];
|
||||||
|
|
||||||
if (co2State) {
|
if (co2State) {
|
||||||
electricityMapUrl = `https://www.electricitymap.org/zone/${co2State.attributes.country_code}`;
|
electricityMapUrl = `https://www.electricitymap.org/zone/${co2State.attributes.country_code}`;
|
||||||
@ -401,69 +403,6 @@ class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getStatistics(): Promise<void> {
|
|
||||||
const [configEntries, entityRegistryEntries] = await Promise.all([
|
|
||||||
getConfigEntries(this.hass),
|
|
||||||
subscribeOne(this.hass.connection, subscribeEntityRegistry),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const co2ConfigEntry = configEntries.find(
|
|
||||||
(entry) => entry.domain === "co2signal"
|
|
||||||
);
|
|
||||||
|
|
||||||
this._co2SignalEntity = undefined;
|
|
||||||
|
|
||||||
if (co2ConfigEntry) {
|
|
||||||
for (const entry of entityRegistryEntries) {
|
|
||||||
if (entry.config_entry_id !== co2ConfigEntry.entry_id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The integration offers 2 entities. We want the % one.
|
|
||||||
const co2State = this.hass.states[entry.entity_id];
|
|
||||||
if (!co2State || co2State.attributes.unit_of_measurement !== "%") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._co2SignalEntity = co2State.entity_id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const startDate = new Date();
|
|
||||||
startDate.setHours(0, 0, 0, 0);
|
|
||||||
startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
|
|
||||||
|
|
||||||
const statistics: string[] = [];
|
|
||||||
|
|
||||||
if (this._co2SignalEntity !== undefined) {
|
|
||||||
statistics.push(this._co2SignalEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
const prefs = this._config!.prefs;
|
|
||||||
for (const source of prefs.energy_sources) {
|
|
||||||
if (source.type === "solar") {
|
|
||||||
statistics.push(source.stat_energy_from);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// grid source
|
|
||||||
for (const flowFrom of source.flow_from) {
|
|
||||||
statistics.push(flowFrom.stat_energy_from);
|
|
||||||
}
|
|
||||||
for (const flowTo of source.flow_to) {
|
|
||||||
statistics.push(flowTo.stat_energy_to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._stats = await fetchStatistics(
|
|
||||||
this.hass!,
|
|
||||||
startDate,
|
|
||||||
undefined,
|
|
||||||
statistics
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
:host {
|
:host {
|
||||||
--mdc-icon-size: 24px;
|
--mdc-icon-size: 24px;
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { formatNumber } from "../../../../common/string/format_number";
|
import { formatNumber } from "../../../../common/string/format_number";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import "../../../../components/ha-gauge";
|
import "../../../../components/ha-gauge";
|
||||||
import type { LevelDefinition } from "../../../../components/ha-gauge";
|
import type { LevelDefinition } from "../../../../components/ha-gauge";
|
||||||
import { GridSourceTypeEnergyPreference } from "../../../../data/energy";
|
|
||||||
import {
|
import {
|
||||||
calculateStatisticsSumGrowth,
|
EnergyData,
|
||||||
fetchStatistics,
|
getEnergyDataCollection,
|
||||||
Statistics,
|
GridSourceTypeEnergyPreference,
|
||||||
} from "../../../../data/history";
|
} from "../../../../data/energy";
|
||||||
|
import { calculateStatisticsSumGrowth } from "../../../../data/history";
|
||||||
|
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";
|
||||||
import type { EnergyGridGaugeCardConfig } from "../types";
|
import type { EnergyGridGaugeCardConfig } from "../types";
|
||||||
@ -21,12 +23,23 @@ const LEVELS: LevelDefinition[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
@customElement("hui-energy-grid-neutrality-gauge-card")
|
@customElement("hui-energy-grid-neutrality-gauge-card")
|
||||||
class HuiEnergyGridGaugeCard extends LitElement implements LovelaceCard {
|
class HuiEnergyGridGaugeCard
|
||||||
|
extends SubscribeMixin(LitElement)
|
||||||
|
implements LovelaceCard
|
||||||
|
{
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
@state() private _config?: EnergyGridGaugeCardConfig;
|
@state() private _config?: EnergyGridGaugeCardConfig;
|
||||||
|
|
||||||
@state() private _stats?: Statistics;
|
@state() private _data?: EnergyData;
|
||||||
|
|
||||||
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
|
return [
|
||||||
|
getEnergyDataCollection(this.hass!).subscribe((data) => {
|
||||||
|
this._data = data;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return 4;
|
return 4;
|
||||||
@ -36,24 +49,16 @@ class HuiEnergyGridGaugeCard extends LitElement implements LovelaceCard {
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps) {
|
|
||||||
super.willUpdate(changedProps);
|
|
||||||
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
this._getStatistics();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this._config || !this.hass) {
|
if (!this._config || !this.hass) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._stats) {
|
if (!this._data) {
|
||||||
return html`Loading...`;
|
return html`Loading...`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefs = this._config!.prefs;
|
const prefs = this._data.prefs;
|
||||||
const gridSource = prefs.energy_sources.find(
|
const gridSource = prefs.energy_sources.find(
|
||||||
(src) => src.type === "grid"
|
(src) => src.type === "grid"
|
||||||
) as GridSourceTypeEnergyPreference | undefined;
|
) as GridSourceTypeEnergyPreference | undefined;
|
||||||
@ -65,12 +70,12 @@ class HuiEnergyGridGaugeCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const consumedFromGrid = calculateStatisticsSumGrowth(
|
const consumedFromGrid = calculateStatisticsSumGrowth(
|
||||||
this._stats,
|
this._data.stats,
|
||||||
gridSource.flow_from.map((flow) => flow.stat_energy_from)
|
gridSource.flow_from.map((flow) => flow.stat_energy_from)
|
||||||
);
|
);
|
||||||
|
|
||||||
const returnedToGrid = calculateStatisticsSumGrowth(
|
const returnedToGrid = calculateStatisticsSumGrowth(
|
||||||
this._stats,
|
this._data.stats,
|
||||||
gridSource.flow_to.map((flow) => flow.stat_energy_to)
|
gridSource.flow_to.map((flow) => flow.stat_energy_to)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -111,35 +116,6 @@ class HuiEnergyGridGaugeCard extends LitElement implements LovelaceCard {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getStatistics(): Promise<void> {
|
|
||||||
const startDate = new Date();
|
|
||||||
startDate.setHours(0, 0, 0, 0);
|
|
||||||
startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
|
|
||||||
|
|
||||||
const statistics: string[] = [];
|
|
||||||
const prefs = this._config!.prefs;
|
|
||||||
for (const source of prefs.energy_sources) {
|
|
||||||
if (source.type === "solar") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// grid source
|
|
||||||
for (const flowFrom of source.flow_from) {
|
|
||||||
statistics.push(flowFrom.stat_energy_from);
|
|
||||||
}
|
|
||||||
for (const flowTo of source.flow_to) {
|
|
||||||
statistics.push(flowTo.stat_energy_to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._stats = await fetchStatistics(
|
|
||||||
this.hass!,
|
|
||||||
startDate,
|
|
||||||
undefined,
|
|
||||||
statistics
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
ha-card {
|
||||||
|
@ -1,26 +1,39 @@
|
|||||||
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { styleMap } from "lit/directives/style-map";
|
import { styleMap } from "lit/directives/style-map";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import "../../../../components/ha-gauge";
|
import "../../../../components/ha-gauge";
|
||||||
import { energySourcesByType } from "../../../../data/energy";
|
|
||||||
import {
|
import {
|
||||||
calculateStatisticsSumGrowth,
|
EnergyData,
|
||||||
fetchStatistics,
|
energySourcesByType,
|
||||||
Statistics,
|
getEnergyDataCollection,
|
||||||
} from "../../../../data/history";
|
} from "../../../../data/energy";
|
||||||
|
import { calculateStatisticsSumGrowth } from "../../../../data/history";
|
||||||
|
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";
|
||||||
import { severityMap } from "../hui-gauge-card";
|
import { severityMap } from "../hui-gauge-card";
|
||||||
import type { EnergySolarGaugeCardConfig } from "../types";
|
import type { EnergySolarGaugeCardConfig } from "../types";
|
||||||
|
|
||||||
@customElement("hui-energy-solar-consumed-gauge-card")
|
@customElement("hui-energy-solar-consumed-gauge-card")
|
||||||
class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard {
|
class HuiEnergySolarGaugeCard
|
||||||
|
extends SubscribeMixin(LitElement)
|
||||||
|
implements LovelaceCard
|
||||||
|
{
|
||||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||||
|
|
||||||
@state() private _config?: EnergySolarGaugeCardConfig;
|
@state() private _config?: EnergySolarGaugeCardConfig;
|
||||||
|
|
||||||
@state() private _stats?: Statistics;
|
@state() private _data?: EnergyData;
|
||||||
|
|
||||||
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
|
return [
|
||||||
|
getEnergyDataCollection(this.hass!).subscribe((data) => {
|
||||||
|
this._data = data;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public getCardSize(): number {
|
public getCardSize(): number {
|
||||||
return 4;
|
return 4;
|
||||||
@ -30,33 +43,25 @@ class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard {
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps) {
|
|
||||||
super.willUpdate(changedProps);
|
|
||||||
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
this._getStatistics();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this._config || !this.hass) {
|
if (!this._config || !this.hass) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._stats) {
|
if (!this._data) {
|
||||||
return html`Loading...`;
|
return html`Loading...`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefs = this._config!.prefs;
|
const prefs = this._data.prefs;
|
||||||
const types = energySourcesByType(prefs);
|
const types = energySourcesByType(prefs);
|
||||||
|
|
||||||
const totalSolarProduction = calculateStatisticsSumGrowth(
|
const totalSolarProduction = calculateStatisticsSumGrowth(
|
||||||
this._stats,
|
this._data.stats,
|
||||||
types.solar!.map((source) => source.stat_energy_from)
|
types.solar!.map((source) => source.stat_energy_from)
|
||||||
);
|
);
|
||||||
|
|
||||||
const productionReturnedToGrid = calculateStatisticsSumGrowth(
|
const productionReturnedToGrid = calculateStatisticsSumGrowth(
|
||||||
this._stats,
|
this._data.stats,
|
||||||
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
|
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -101,36 +106,6 @@ class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard {
|
|||||||
return severityMap.normal;
|
return severityMap.normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getStatistics(): Promise<void> {
|
|
||||||
const startDate = new Date();
|
|
||||||
startDate.setHours(0, 0, 0, 0);
|
|
||||||
startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
|
|
||||||
|
|
||||||
const statistics: string[] = [];
|
|
||||||
const prefs = this._config!.prefs;
|
|
||||||
for (const source of prefs.energy_sources) {
|
|
||||||
if (source.type === "solar") {
|
|
||||||
statistics.push(source.stat_energy_from);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// grid source
|
|
||||||
for (const flowFrom of source.flow_from) {
|
|
||||||
statistics.push(flowFrom.stat_energy_from);
|
|
||||||
}
|
|
||||||
for (const flowTo of source.flow_to) {
|
|
||||||
statistics.push(flowTo.stat_energy_to);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._stats = await fetchStatistics(
|
|
||||||
this.hass!,
|
|
||||||
startDate,
|
|
||||||
undefined,
|
|
||||||
statistics
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
ha-card {
|
ha-card {
|
||||||
|
@ -1,19 +1,13 @@
|
|||||||
import {
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
css,
|
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergySolarGraphCardConfig } from "../types";
|
import { EnergySolarGraphCardConfig } from "../types";
|
||||||
import { fetchStatistics, Statistics } from "../../../../data/history";
|
|
||||||
import {
|
import {
|
||||||
hex2rgb,
|
hex2rgb,
|
||||||
lab2rgb,
|
lab2rgb,
|
||||||
@ -21,7 +15,12 @@ import {
|
|||||||
rgb2lab,
|
rgb2lab,
|
||||||
} from "../../../../common/color/convert-color";
|
} from "../../../../common/color/convert-color";
|
||||||
import { labDarken } from "../../../../common/color/lab";
|
import { labDarken } from "../../../../common/color/lab";
|
||||||
import { SolarSourceTypeEnergyPreference } from "../../../../data/energy";
|
import {
|
||||||
|
EnergyCollection,
|
||||||
|
EnergyData,
|
||||||
|
getEnergyDataCollection,
|
||||||
|
SolarSourceTypeEnergyPreference,
|
||||||
|
} from "../../../../data/energy";
|
||||||
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
||||||
import {
|
import {
|
||||||
ForecastSolarForecast,
|
ForecastSolarForecast,
|
||||||
@ -35,52 +34,32 @@ import {
|
|||||||
formatNumber,
|
formatNumber,
|
||||||
numberFormatToLocale,
|
numberFormatToLocale,
|
||||||
} from "../../../../common/string/format_number";
|
} from "../../../../common/string/format_number";
|
||||||
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
|
import { FrontendLocaleData } from "../../../../data/translation";
|
||||||
|
|
||||||
@customElement("hui-energy-solar-graph-card")
|
@customElement("hui-energy-solar-graph-card")
|
||||||
export class HuiEnergySolarGraphCard
|
export class HuiEnergySolarGraphCard
|
||||||
extends LitElement
|
extends SubscribeMixin(LitElement)
|
||||||
implements LovelaceCard
|
implements LovelaceCard
|
||||||
{
|
{
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _config?: EnergySolarGraphCardConfig;
|
@state() private _config?: EnergySolarGraphCardConfig;
|
||||||
|
|
||||||
@state() private _data?: Statistics;
|
|
||||||
|
|
||||||
@state() private _chartData: ChartData = {
|
@state() private _chartData: ChartData = {
|
||||||
datasets: [],
|
datasets: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@state() private _forecasts?: Record<string, ForecastSolarForecast>;
|
@state() private _forecasts?: Record<string, ForecastSolarForecast>;
|
||||||
|
|
||||||
@state() private _chartOptions?: ChartOptions;
|
|
||||||
|
|
||||||
@state() private _showAllForecastData = false;
|
@state() private _showAllForecastData = false;
|
||||||
|
|
||||||
private _fetching = false;
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
|
return [
|
||||||
private _interval?: number;
|
getEnergyDataCollection(this.hass).subscribe((data) =>
|
||||||
|
this._getStatistics(data)
|
||||||
public disconnectedCallback() {
|
),
|
||||||
super.disconnectedCallback();
|
];
|
||||||
if (this._interval) {
|
|
||||||
clearInterval(this._interval);
|
|
||||||
this._interval = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._getStatistics();
|
|
||||||
// statistics are created every hour
|
|
||||||
clearInterval(this._interval);
|
|
||||||
this._interval = window.setInterval(
|
|
||||||
() => this._getStatistics(),
|
|
||||||
1000 * 60 * 60
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCardSize(): Promise<number> | number {
|
public getCardSize(): Promise<number> | number {
|
||||||
@ -91,30 +70,6 @@ export class HuiEnergySolarGraphCard
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues) {
|
|
||||||
super.willUpdate(changedProps);
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
this._createOptions();
|
|
||||||
}
|
|
||||||
if (!this._config || !changedProps.has("_config")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldConfig = changedProps.get("_config") as
|
|
||||||
| EnergySolarGraphCardConfig
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
if (oldConfig !== this._config) {
|
|
||||||
this._getStatistics();
|
|
||||||
// statistics are created every hour
|
|
||||||
clearInterval(this._interval);
|
|
||||||
this._interval = window.setInterval(
|
|
||||||
() => this._getStatistics(),
|
|
||||||
1000 * 60 * 60
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config) {
|
||||||
return html``;
|
return html``;
|
||||||
@ -132,7 +87,10 @@ export class HuiEnergySolarGraphCard
|
|||||||
>
|
>
|
||||||
<ha-chart-base
|
<ha-chart-base
|
||||||
.data=${this._chartData}
|
.data=${this._chartData}
|
||||||
.options=${this._chartOptions}
|
.options=${this._createOptions(
|
||||||
|
getEnergyDataCollection(this.hass),
|
||||||
|
this.hass.locale
|
||||||
|
)}
|
||||||
chart-type="bar"
|
chart-type="bar"
|
||||||
></ha-chart-base>
|
></ha-chart-base>
|
||||||
</div>
|
</div>
|
||||||
@ -140,12 +98,14 @@ export class HuiEnergySolarGraphCard
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createOptions() {
|
private _createOptions = memoizeOne(
|
||||||
const startDate = new Date();
|
(
|
||||||
startDate.setHours(0, 0, 0, 0);
|
energyCollection: EnergyCollection,
|
||||||
const startTime = startDate.getTime();
|
locale: FrontendLocaleData
|
||||||
|
): ChartOptions => {
|
||||||
|
const startTime = energyCollection.start.getTime();
|
||||||
|
|
||||||
this._chartOptions = {
|
return {
|
||||||
parsing: false,
|
parsing: false,
|
||||||
animation: false,
|
animation: false,
|
||||||
scales: {
|
scales: {
|
||||||
@ -155,7 +115,7 @@ export class HuiEnergySolarGraphCard
|
|||||||
suggestedMax: startTime + 24 * 60 * 60 * 1000,
|
suggestedMax: startTime + 24 * 60 * 60 * 1000,
|
||||||
adapters: {
|
adapters: {
|
||||||
date: {
|
date: {
|
||||||
locale: this.hass.locale,
|
locale: locale,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
@ -193,7 +153,7 @@ export class HuiEnergySolarGraphCard
|
|||||||
label: (context) =>
|
label: (context) =>
|
||||||
`${context.dataset.label}: ${formatNumber(
|
`${context.dataset.label}: ${formatNumber(
|
||||||
context.parsed.y,
|
context.parsed.y,
|
||||||
this.hass.locale
|
locale
|
||||||
)} kWh`,
|
)} kWh`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -221,37 +181,17 @@ export class HuiEnergySolarGraphCard
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
locale: numberFormatToLocale(this.hass.locale),
|
locale: numberFormatToLocale(locale),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
private async _getStatistics(): Promise<void> {
|
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
||||||
if (this._fetching) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const startDate = new Date();
|
|
||||||
startDate.setHours(0, 0, 0, 0);
|
|
||||||
startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
|
|
||||||
|
|
||||||
this._fetching = true;
|
|
||||||
|
|
||||||
const solarSources: SolarSourceTypeEnergyPreference[] =
|
const solarSources: SolarSourceTypeEnergyPreference[] =
|
||||||
this._config!.prefs.energy_sources.filter(
|
energyData.prefs.energy_sources.filter(
|
||||||
(source) => source.type === "solar"
|
(source) => source.type === "solar"
|
||||||
) as SolarSourceTypeEnergyPreference[];
|
) as SolarSourceTypeEnergyPreference[];
|
||||||
|
|
||||||
try {
|
|
||||||
this._data = await fetchStatistics(
|
|
||||||
this.hass!,
|
|
||||||
startDate,
|
|
||||||
undefined,
|
|
||||||
solarSources.map((source) => source.stat_energy_from)
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
this._fetching = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isComponentLoaded(this.hass, "forecast_solar") &&
|
isComponentLoaded(this.hass, "forecast_solar") &&
|
||||||
solarSources.some((source) => source.config_entry_solar_forecast)
|
solarSources.some((source) => source.config_entry_solar_forecast)
|
||||||
@ -259,16 +199,7 @@ export class HuiEnergySolarGraphCard
|
|||||||
this._forecasts = await getForecastSolarForecasts(this.hass);
|
this._forecasts = await getForecastSolarForecasts(this.hass);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._renderChart();
|
const statisticsData = Object.values(energyData.stats);
|
||||||
}
|
|
||||||
|
|
||||||
private _renderChart() {
|
|
||||||
const solarSources: SolarSourceTypeEnergyPreference[] =
|
|
||||||
this._config!.prefs.energy_sources.filter(
|
|
||||||
(source) => source.type === "solar"
|
|
||||||
) as SolarSourceTypeEnergyPreference[];
|
|
||||||
|
|
||||||
const statisticsData = Object.values(this._data!);
|
|
||||||
const datasets: ChartDataset<"bar">[] = [];
|
const datasets: ChartDataset<"bar">[] = [];
|
||||||
let endTime: Date;
|
let endTime: Date;
|
||||||
|
|
||||||
@ -311,8 +242,8 @@ export class HuiEnergySolarGraphCard
|
|||||||
let prevStart: string | null = null;
|
let prevStart: string | null = null;
|
||||||
|
|
||||||
// Process solar production data.
|
// Process solar production data.
|
||||||
if (this._data![source.stat_energy_from]) {
|
if (energyData.stats[source.stat_energy_from]) {
|
||||||
for (const point of this._data![source.stat_energy_from]) {
|
for (const point of energyData.stats[source.stat_energy_from]) {
|
||||||
if (!point.sum) {
|
if (!point.sum) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import dataTableStyles from "@material/data-table/dist/mdc.data-table.min.css";
|
import dataTableStyles from "@material/data-table/dist/mdc.data-table.min.css";
|
||||||
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import {
|
import {
|
||||||
css,
|
css,
|
||||||
CSSResultGroup,
|
CSSResultGroup,
|
||||||
@ -22,31 +23,34 @@ import { formatNumber } from "../../../../common/string/format_number";
|
|||||||
import "../../../../components/chart/statistics-chart";
|
import "../../../../components/chart/statistics-chart";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import {
|
import {
|
||||||
EnergyInfo,
|
EnergyData,
|
||||||
energySourcesByType,
|
energySourcesByType,
|
||||||
getEnergyInfo,
|
getEnergyDataCollection,
|
||||||
} from "../../../../data/energy";
|
} from "../../../../data/energy";
|
||||||
import {
|
import { calculateStatisticSumGrowth } from "../../../../data/history";
|
||||||
calculateStatisticSumGrowth,
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
fetchStatistics,
|
|
||||||
Statistics,
|
|
||||||
} from "../../../../data/history";
|
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergySourcesTableCardConfig } from "../types";
|
import { EnergySourcesTableCardConfig } from "../types";
|
||||||
|
|
||||||
@customElement("hui-energy-sources-table-card")
|
@customElement("hui-energy-sources-table-card")
|
||||||
export class HuiEnergySourcesTableCard
|
export class HuiEnergySourcesTableCard
|
||||||
extends LitElement
|
extends SubscribeMixin(LitElement)
|
||||||
implements LovelaceCard
|
implements LovelaceCard
|
||||||
{
|
{
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _config?: EnergySourcesTableCardConfig;
|
@state() private _config?: EnergySourcesTableCardConfig;
|
||||||
|
|
||||||
@state() private _stats?: Statistics;
|
@state() private _data?: EnergyData;
|
||||||
|
|
||||||
@state() private _energyInfo?: EnergyInfo;
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
|
return [
|
||||||
|
getEnergyDataCollection(this.hass).subscribe((data) => {
|
||||||
|
this._data = data;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public getCardSize(): Promise<number> | number {
|
public getCardSize(): Promise<number> | number {
|
||||||
return 3;
|
return 3;
|
||||||
@ -56,18 +60,12 @@ export class HuiEnergySourcesTableCard
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate() {
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
this._getEnergyInfo().then(() => this._getStatistics());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._stats) {
|
if (!this._data) {
|
||||||
return html`Loading...`;
|
return html`Loading...`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +73,7 @@ export class HuiEnergySourcesTableCard
|
|||||||
let totalSolar = 0;
|
let totalSolar = 0;
|
||||||
let totalCost = 0;
|
let totalCost = 0;
|
||||||
|
|
||||||
const types = energySourcesByType(this._config.prefs);
|
const types = energySourcesByType(this._data.prefs);
|
||||||
|
|
||||||
const computedStyles = getComputedStyle(this);
|
const computedStyles = getComputedStyle(this);
|
||||||
const solarColor = computedStyles
|
const solarColor = computedStyles
|
||||||
@ -140,7 +138,7 @@ export class HuiEnergySourcesTableCard
|
|||||||
const entity = this.hass.states[source.stat_energy_from];
|
const entity = this.hass.states[source.stat_energy_from];
|
||||||
const energy =
|
const energy =
|
||||||
calculateStatisticSumGrowth(
|
calculateStatisticSumGrowth(
|
||||||
this._stats![source.stat_energy_from]
|
this._data!.stats[source.stat_energy_from]
|
||||||
) || 0;
|
) || 0;
|
||||||
totalSolar += energy;
|
totalSolar += energy;
|
||||||
const color =
|
const color =
|
||||||
@ -195,14 +193,16 @@ export class HuiEnergySourcesTableCard
|
|||||||
const entity = this.hass.states[flow.stat_energy_from];
|
const entity = this.hass.states[flow.stat_energy_from];
|
||||||
const energy =
|
const energy =
|
||||||
calculateStatisticSumGrowth(
|
calculateStatisticSumGrowth(
|
||||||
this._stats![flow.stat_energy_from]
|
this._data!.stats[flow.stat_energy_from]
|
||||||
) || 0;
|
) || 0;
|
||||||
totalGrid += energy;
|
totalGrid += energy;
|
||||||
const cost_stat =
|
const cost_stat =
|
||||||
flow.stat_cost ||
|
flow.stat_cost ||
|
||||||
this._energyInfo!.cost_sensors[flow.stat_energy_from];
|
this._data!.info.cost_sensors[flow.stat_energy_from];
|
||||||
const cost = cost_stat
|
const cost = cost_stat
|
||||||
? calculateStatisticSumGrowth(this._stats![cost_stat]) || 0
|
? calculateStatisticSumGrowth(
|
||||||
|
this._data!.stats[cost_stat]
|
||||||
|
) || 0
|
||||||
: null;
|
: null;
|
||||||
if (cost !== null) {
|
if (cost !== null) {
|
||||||
totalCost += cost;
|
totalCost += cost;
|
||||||
@ -253,15 +253,16 @@ export class HuiEnergySourcesTableCard
|
|||||||
const entity = this.hass.states[flow.stat_energy_to];
|
const entity = this.hass.states[flow.stat_energy_to];
|
||||||
const energy =
|
const energy =
|
||||||
(calculateStatisticSumGrowth(
|
(calculateStatisticSumGrowth(
|
||||||
this._stats![flow.stat_energy_to]
|
this._data!.stats[flow.stat_energy_to]
|
||||||
) || 0) * -1;
|
) || 0) * -1;
|
||||||
totalGrid += energy;
|
totalGrid += energy;
|
||||||
const cost_stat =
|
const cost_stat =
|
||||||
flow.stat_compensation ||
|
flow.stat_compensation ||
|
||||||
this._energyInfo!.cost_sensors[flow.stat_energy_to];
|
this._data!.info.cost_sensors[flow.stat_energy_to];
|
||||||
const cost = cost_stat
|
const cost = cost_stat
|
||||||
? (calculateStatisticSumGrowth(this._stats![cost_stat]) ||
|
? (calculateStatisticSumGrowth(
|
||||||
0) * -1
|
this._data!.stats[cost_stat]
|
||||||
|
) || 0) * -1
|
||||||
: null;
|
: null;
|
||||||
if (cost !== null) {
|
if (cost !== null) {
|
||||||
totalCost += cost;
|
totalCost += cost;
|
||||||
@ -333,45 +334,6 @@ export class HuiEnergySourcesTableCard
|
|||||||
</ha-card>`;
|
</ha-card>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getEnergyInfo() {
|
|
||||||
this._energyInfo = await getEnergyInfo(this.hass);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _getStatistics(): Promise<void> {
|
|
||||||
const startDate = new Date();
|
|
||||||
startDate.setHours(0, 0, 0, 0);
|
|
||||||
startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
|
|
||||||
|
|
||||||
const statistics: string[] = Object.values(this._energyInfo!.cost_sensors);
|
|
||||||
const prefs = this._config!.prefs;
|
|
||||||
for (const source of prefs.energy_sources) {
|
|
||||||
if (source.type === "solar") {
|
|
||||||
statistics.push(source.stat_energy_from);
|
|
||||||
} else {
|
|
||||||
// grid source
|
|
||||||
for (const flowFrom of source.flow_from) {
|
|
||||||
statistics.push(flowFrom.stat_energy_from);
|
|
||||||
if (flowFrom.stat_cost) {
|
|
||||||
statistics.push(flowFrom.stat_cost);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const flowTo of source.flow_to) {
|
|
||||||
statistics.push(flowTo.stat_energy_to);
|
|
||||||
if (flowTo.stat_compensation) {
|
|
||||||
statistics.push(flowTo.stat_compensation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._stats = await fetchStatistics(
|
|
||||||
this.hass!,
|
|
||||||
startDate,
|
|
||||||
undefined,
|
|
||||||
statistics
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
${unsafeCSS(dataTableStyles)}
|
${unsafeCSS(dataTableStyles)}
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||||
import {
|
import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
css,
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
CSSResultGroup,
|
|
||||||
html,
|
|
||||||
LitElement,
|
|
||||||
PropertyValues,
|
|
||||||
TemplateResult,
|
|
||||||
} from "lit";
|
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import {
|
import {
|
||||||
hex2rgb,
|
hex2rgb,
|
||||||
lab2rgb,
|
lab2rgb,
|
||||||
@ -24,52 +19,36 @@ import {
|
|||||||
} from "../../../../common/string/format_number";
|
} from "../../../../common/string/format_number";
|
||||||
import "../../../../components/chart/ha-chart-base";
|
import "../../../../components/chart/ha-chart-base";
|
||||||
import "../../../../components/ha-card";
|
import "../../../../components/ha-card";
|
||||||
import { fetchStatistics, Statistics } from "../../../../data/history";
|
import {
|
||||||
|
EnergyCollection,
|
||||||
|
EnergyData,
|
||||||
|
getEnergyDataCollection,
|
||||||
|
} from "../../../../data/energy";
|
||||||
|
import { FrontendLocaleData } from "../../../../data/translation";
|
||||||
|
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { LovelaceCard } from "../../types";
|
import { LovelaceCard } from "../../types";
|
||||||
import { EnergyUsageGraphCardConfig } from "../types";
|
import { EnergyUsageGraphCardConfig } from "../types";
|
||||||
|
|
||||||
@customElement("hui-energy-usage-graph-card")
|
@customElement("hui-energy-usage-graph-card")
|
||||||
export class HuiEnergyUsageGraphCard
|
export class HuiEnergyUsageGraphCard
|
||||||
extends LitElement
|
extends SubscribeMixin(LitElement)
|
||||||
implements LovelaceCard
|
implements LovelaceCard
|
||||||
{
|
{
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@state() private _config?: EnergyUsageGraphCardConfig;
|
@state() private _config?: EnergyUsageGraphCardConfig;
|
||||||
|
|
||||||
@state() private _data?: Statistics;
|
|
||||||
|
|
||||||
@state() private _chartData: ChartData = {
|
@state() private _chartData: ChartData = {
|
||||||
datasets: [],
|
datasets: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@state() private _chartOptions?: ChartOptions;
|
public hassSubscribe(): UnsubscribeFunc[] {
|
||||||
|
return [
|
||||||
private _fetching = false;
|
getEnergyDataCollection(this.hass).subscribe((data) =>
|
||||||
|
this._getStatistics(data)
|
||||||
private _interval?: number;
|
),
|
||||||
|
];
|
||||||
public disconnectedCallback() {
|
|
||||||
super.disconnectedCallback();
|
|
||||||
if (this._interval) {
|
|
||||||
clearInterval(this._interval);
|
|
||||||
this._interval = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._getStatistics();
|
|
||||||
// statistics are created every hour
|
|
||||||
clearInterval(this._interval);
|
|
||||||
this._interval = window.setInterval(
|
|
||||||
() => this._getStatistics(),
|
|
||||||
1000 * 60 * 60
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCardSize(): Promise<number> | number {
|
public getCardSize(): Promise<number> | number {
|
||||||
@ -80,30 +59,6 @@ export class HuiEnergyUsageGraphCard
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public willUpdate(changedProps: PropertyValues) {
|
|
||||||
super.willUpdate(changedProps);
|
|
||||||
if (!this.hasUpdated) {
|
|
||||||
this._createOptions();
|
|
||||||
}
|
|
||||||
if (!this._config || !changedProps.has("_config")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldConfig = changedProps.get("_config") as
|
|
||||||
| EnergyUsageGraphCardConfig
|
|
||||||
| undefined;
|
|
||||||
|
|
||||||
if (oldConfig !== this._config) {
|
|
||||||
this._getStatistics();
|
|
||||||
// statistics are created every hour
|
|
||||||
clearInterval(this._interval);
|
|
||||||
this._interval = window.setInterval(
|
|
||||||
() => this._getStatistics(),
|
|
||||||
1000 * 60 * 60
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.hass || !this._config) {
|
if (!this.hass || !this._config) {
|
||||||
return html``;
|
return html``;
|
||||||
@ -121,7 +76,10 @@ export class HuiEnergyUsageGraphCard
|
|||||||
>
|
>
|
||||||
<ha-chart-base
|
<ha-chart-base
|
||||||
.data=${this._chartData}
|
.data=${this._chartData}
|
||||||
.options=${this._chartOptions}
|
.options=${this._createOptions(
|
||||||
|
getEnergyDataCollection(this.hass),
|
||||||
|
this.hass.locale
|
||||||
|
)}
|
||||||
chart-type="bar"
|
chart-type="bar"
|
||||||
></ha-chart-base>
|
></ha-chart-base>
|
||||||
</div>
|
</div>
|
||||||
@ -129,12 +87,14 @@ export class HuiEnergyUsageGraphCard
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _createOptions() {
|
private _createOptions = memoizeOne(
|
||||||
const startDate = new Date();
|
(
|
||||||
startDate.setHours(0, 0, 0, 0);
|
energyCollection: EnergyCollection,
|
||||||
const startTime = startDate.getTime();
|
locale: FrontendLocaleData
|
||||||
|
): ChartOptions => {
|
||||||
|
const startTime = energyCollection.start.getTime();
|
||||||
|
|
||||||
this._chartOptions = {
|
return {
|
||||||
parsing: false,
|
parsing: false,
|
||||||
animation: false,
|
animation: false,
|
||||||
scales: {
|
scales: {
|
||||||
@ -144,7 +104,7 @@ export class HuiEnergyUsageGraphCard
|
|||||||
suggestedMax: startTime + 24 * 60 * 60 * 1000,
|
suggestedMax: startTime + 24 * 60 * 60 * 1000,
|
||||||
adapters: {
|
adapters: {
|
||||||
date: {
|
date: {
|
||||||
locale: this.hass.locale,
|
locale: locale,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
@ -173,8 +133,7 @@ export class HuiEnergyUsageGraphCard
|
|||||||
},
|
},
|
||||||
ticks: {
|
ticks: {
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
callback: (value) =>
|
callback: (value) => formatNumber(Math.abs(value), locale),
|
||||||
formatNumber(Math.abs(value), this.hass.locale),
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -188,7 +147,7 @@ export class HuiEnergyUsageGraphCard
|
|||||||
label: (context) =>
|
label: (context) =>
|
||||||
`${context.dataset.label}: ${formatNumber(
|
`${context.dataset.label}: ${formatNumber(
|
||||||
Math.abs(context.parsed.y),
|
Math.abs(context.parsed.y),
|
||||||
this.hass.locale
|
locale
|
||||||
)} kWh`,
|
)} kWh`,
|
||||||
footer: (contexts) => {
|
footer: (contexts) => {
|
||||||
let totalConsumed = 0;
|
let totalConsumed = 0;
|
||||||
@ -206,13 +165,13 @@ export class HuiEnergyUsageGraphCard
|
|||||||
totalConsumed
|
totalConsumed
|
||||||
? `Total consumed: ${formatNumber(
|
? `Total consumed: ${formatNumber(
|
||||||
totalConsumed,
|
totalConsumed,
|
||||||
this.hass.locale
|
locale
|
||||||
)} kWh`
|
)} kWh`
|
||||||
: "",
|
: "",
|
||||||
totalReturned
|
totalReturned
|
||||||
? `Total returned: ${formatNumber(
|
? `Total returned: ${formatNumber(
|
||||||
totalReturned,
|
totalReturned,
|
||||||
this.hass.locale
|
locale
|
||||||
)} kWh`
|
)} kWh`
|
||||||
: "",
|
: "",
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
@ -239,27 +198,19 @@ export class HuiEnergyUsageGraphCard
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
locale: numberFormatToLocale(this.hass.locale),
|
locale: numberFormatToLocale(locale),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
private async _getStatistics(): Promise<void> {
|
private async _getStatistics(energyData: EnergyData): Promise<void> {
|
||||||
if (this._fetching) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const startDate = new Date();
|
|
||||||
startDate.setHours(0, 0, 0, 0);
|
|
||||||
startDate.setTime(startDate.getTime() - 1000 * 60 * 60); // subtract 1 hour to get a startpoint
|
|
||||||
|
|
||||||
this._fetching = true;
|
|
||||||
const prefs = this._config!.prefs;
|
|
||||||
const statistics: {
|
const statistics: {
|
||||||
to_grid?: string[];
|
to_grid?: string[];
|
||||||
from_grid?: string[];
|
from_grid?: string[];
|
||||||
solar?: string[];
|
solar?: string[];
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
for (const source of prefs.energy_sources) {
|
for (const source of energyData.prefs.energy_sources) {
|
||||||
if (source.type === "solar") {
|
if (source.type === "solar") {
|
||||||
if (statistics.solar) {
|
if (statistics.solar) {
|
||||||
statistics.solar.push(source.stat_energy_from);
|
statistics.solar.push(source.stat_energy_from);
|
||||||
@ -286,19 +237,7 @@ export class HuiEnergyUsageGraphCard
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const statisticsData = Object.values(energyData.stats);
|
||||||
this._data = await fetchStatistics(
|
|
||||||
this.hass!,
|
|
||||||
startDate,
|
|
||||||
undefined,
|
|
||||||
// Array.flat()
|
|
||||||
([] as string[]).concat(...Object.values(statistics))
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
this._fetching = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const statisticsData = Object.values(this._data!);
|
|
||||||
const datasets: ChartDataset<"bar">[] = [];
|
const datasets: ChartDataset<"bar">[] = [];
|
||||||
let endTime: Date;
|
let endTime: Date;
|
||||||
|
|
||||||
@ -346,7 +285,7 @@ export class HuiEnergyUsageGraphCard
|
|||||||
const totalStats: { [start: string]: number } = {};
|
const totalStats: { [start: string]: number } = {};
|
||||||
const sets: { [statId: string]: { [start: string]: number } } = {};
|
const sets: { [statId: string]: { [start: string]: number } } = {};
|
||||||
statIds!.forEach((id) => {
|
statIds!.forEach((id) => {
|
||||||
const stats = this._data![id];
|
const stats = energyData.stats[id];
|
||||||
if (!stats) {
|
if (!stats) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { EnergyPreferences } from "../../../data/energy";
|
|
||||||
import { StatisticType } from "../../../data/history";
|
import { StatisticType } from "../../../data/history";
|
||||||
import { ActionConfig, LovelaceCardConfig } from "../../../data/lovelace";
|
import { ActionConfig, LovelaceCardConfig } from "../../../data/lovelace";
|
||||||
import { FullCalendarView } from "../../../types";
|
import { FullCalendarView } from "../../../types";
|
||||||
@ -93,54 +92,45 @@ export interface ButtonCardConfig extends LovelaceCardConfig {
|
|||||||
export interface EnergySummaryCardConfig extends LovelaceCardConfig {
|
export interface EnergySummaryCardConfig extends LovelaceCardConfig {
|
||||||
type: "energy-summary";
|
type: "energy-summary";
|
||||||
title?: string;
|
title?: string;
|
||||||
prefs: EnergyPreferences;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnergyDistributionCardConfig extends LovelaceCardConfig {
|
export interface EnergyDistributionCardConfig extends LovelaceCardConfig {
|
||||||
type: "energy-distribution";
|
type: "energy-distribution";
|
||||||
title?: string;
|
title?: string;
|
||||||
prefs: EnergyPreferences;
|
|
||||||
}
|
}
|
||||||
export interface EnergyUsageGraphCardConfig extends LovelaceCardConfig {
|
export interface EnergyUsageGraphCardConfig extends LovelaceCardConfig {
|
||||||
type: "energy-summary-graph";
|
type: "energy-summary-graph";
|
||||||
title?: string;
|
title?: string;
|
||||||
prefs: EnergyPreferences;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnergySolarGraphCardConfig extends LovelaceCardConfig {
|
export interface EnergySolarGraphCardConfig extends LovelaceCardConfig {
|
||||||
type: "energy-solar-graph";
|
type: "energy-solar-graph";
|
||||||
title?: string;
|
title?: string;
|
||||||
prefs: EnergyPreferences;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnergyDevicesGraphCardConfig extends LovelaceCardConfig {
|
export interface EnergyDevicesGraphCardConfig extends LovelaceCardConfig {
|
||||||
type: "energy-devices-graph";
|
type: "energy-devices-graph";
|
||||||
title?: string;
|
title?: string;
|
||||||
prefs: EnergyPreferences;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnergySourcesTableCardConfig extends LovelaceCardConfig {
|
export interface EnergySourcesTableCardConfig extends LovelaceCardConfig {
|
||||||
type: "energy-sources-table";
|
type: "energy-sources-table";
|
||||||
title?: string;
|
title?: string;
|
||||||
prefs: EnergyPreferences;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnergySolarGaugeCardConfig extends LovelaceCardConfig {
|
export interface EnergySolarGaugeCardConfig extends LovelaceCardConfig {
|
||||||
type: "energy-solar-consumed-gauge";
|
type: "energy-solar-consumed-gauge";
|
||||||
title?: string;
|
title?: string;
|
||||||
prefs: EnergyPreferences;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnergyGridGaugeCardConfig extends LovelaceCardConfig {
|
export interface EnergyGridGaugeCardConfig extends LovelaceCardConfig {
|
||||||
type: "energy-grid-result-gauge";
|
type: "energy-grid-result-gauge";
|
||||||
title?: string;
|
title?: string;
|
||||||
prefs: EnergyPreferences;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnergyCarbonGaugeCardConfig extends LovelaceCardConfig {
|
export interface EnergyCarbonGaugeCardConfig extends LovelaceCardConfig {
|
||||||
type: "energy-carbon-consumed-gauge";
|
type: "energy-carbon-consumed-gauge";
|
||||||
title?: string;
|
title?: string;
|
||||||
prefs: EnergyPreferences;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityFilterCardConfig extends LovelaceCardConfig {
|
export interface EntityFilterCardConfig extends LovelaceCardConfig {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user