Add battery to energy dashboard (#9757)
parent
44548fdc33
commit
19e4c0657a
|
@ -44,6 +44,11 @@ export const mockEnergy = (hass: MockHomeAssistant) => {
|
||||||
stat_energy_from: "sensor.solar_production",
|
stat_energy_from: "sensor.solar_production",
|
||||||
config_entry_solar_forecast: ["solar_forecast"],
|
config_entry_solar_forecast: ["solar_forecast"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: "battery",
|
||||||
|
stat_energy_from: "sensor.battery_output",
|
||||||
|
stat_energy_to: "sensor.battery_input",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
device_consumption: [
|
device_consumption: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -18,6 +18,24 @@ export const energyEntities = () =>
|
||||||
unit_of_measurement: "kWh",
|
unit_of_measurement: "kWh",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"sensor.battery_input": {
|
||||||
|
entity_id: "sensor.battery_input",
|
||||||
|
state: "4",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Battery Input",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"sensor.battery_output": {
|
||||||
|
entity_id: "sensor.battery_output",
|
||||||
|
state: "3",
|
||||||
|
attributes: {
|
||||||
|
last_reset: "1970-01-01T00:00:00:00+00",
|
||||||
|
friendly_name: "Battery Output",
|
||||||
|
unit_of_measurement: "kWh",
|
||||||
|
},
|
||||||
|
},
|
||||||
"sensor.energy_consumption_tarif_1": {
|
"sensor.energy_consumption_tarif_1": {
|
||||||
entity_id: "sensor.energy_consumption_tarif_1 ",
|
entity_id: "sensor.energy_consumption_tarif_1 ",
|
||||||
state: "88.6",
|
state: "88.6",
|
||||||
|
|
|
@ -47,6 +47,13 @@ export const emptySolarEnergyPreference =
|
||||||
config_entry_solar_forecast: null,
|
config_entry_solar_forecast: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const emptyBatteryEnergyPreference =
|
||||||
|
(): BatterySourceTypeEnergyPreference => ({
|
||||||
|
type: "battery",
|
||||||
|
stat_energy_from: "",
|
||||||
|
stat_energy_to: "",
|
||||||
|
});
|
||||||
|
|
||||||
export interface DeviceConsumptionEnergyPreference {
|
export interface DeviceConsumptionEnergyPreference {
|
||||||
// This is an ever increasing value
|
// This is an ever increasing value
|
||||||
stat_consumption: string;
|
stat_consumption: string;
|
||||||
|
@ -94,9 +101,16 @@ export interface SolarSourceTypeEnergyPreference {
|
||||||
config_entry_solar_forecast: string[] | null;
|
config_entry_solar_forecast: string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BatterySourceTypeEnergyPreference {
|
||||||
|
type: "battery";
|
||||||
|
stat_energy_from: string;
|
||||||
|
stat_energy_to: string;
|
||||||
|
}
|
||||||
|
|
||||||
type EnergySource =
|
type EnergySource =
|
||||||
| SolarSourceTypeEnergyPreference
|
| SolarSourceTypeEnergyPreference
|
||||||
| GridSourceTypeEnergyPreference;
|
| GridSourceTypeEnergyPreference
|
||||||
|
| BatterySourceTypeEnergyPreference;
|
||||||
|
|
||||||
export interface EnergyPreferences {
|
export interface EnergyPreferences {
|
||||||
energy_sources: EnergySource[];
|
energy_sources: EnergySource[];
|
||||||
|
@ -132,6 +146,7 @@ export const saveEnergyPreferences = async (
|
||||||
interface EnergySourceByType {
|
interface EnergySourceByType {
|
||||||
grid?: GridSourceTypeEnergyPreference[];
|
grid?: GridSourceTypeEnergyPreference[];
|
||||||
solar?: SolarSourceTypeEnergyPreference[];
|
solar?: SolarSourceTypeEnergyPreference[];
|
||||||
|
battery?: BatterySourceTypeEnergyPreference[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const energySourcesByType = (prefs: EnergyPreferences) => {
|
export const energySourcesByType = (prefs: EnergyPreferences) => {
|
||||||
|
@ -203,6 +218,12 @@ const getEnergyData = async (
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (source.type === "battery") {
|
||||||
|
statIDs.push(source.stat_energy_from);
|
||||||
|
statIDs.push(source.stat_energy_to);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// grid source
|
// grid source
|
||||||
for (const flowFrom of source.flow_from) {
|
for (const flowFrom of source.flow_from) {
|
||||||
statIDs.push(flowFrom.stat_energy_from);
|
statIDs.push(flowFrom.stat_energy_from);
|
||||||
|
|
|
@ -0,0 +1,177 @@
|
||||||
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import { mdiBatteryHigh, mdiDelete, mdiPencil } from "@mdi/js";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||||
|
import "../../../../components/entity/ha-statistic-picker";
|
||||||
|
import "../../../../components/ha-card";
|
||||||
|
import "../../../../components/ha-settings-row";
|
||||||
|
import {
|
||||||
|
BatterySourceTypeEnergyPreference,
|
||||||
|
EnergyPreferences,
|
||||||
|
energySourcesByType,
|
||||||
|
saveEnergyPreferences,
|
||||||
|
} from "../../../../data/energy";
|
||||||
|
import {
|
||||||
|
showConfirmationDialog,
|
||||||
|
showAlertDialog,
|
||||||
|
} from "../../../../dialogs/generic/show-dialog-box";
|
||||||
|
import { haStyle } from "../../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import { documentationUrl } from "../../../../util/documentation-url";
|
||||||
|
import { showEnergySettingsBatteryDialog } from "../dialogs/show-dialogs-energy";
|
||||||
|
import { energyCardStyles } from "./styles";
|
||||||
|
|
||||||
|
@customElement("ha-energy-battery-settings")
|
||||||
|
export class EnergyBatterySettings extends LitElement {
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public preferences!: EnergyPreferences;
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
const types = energySourcesByType(this.preferences);
|
||||||
|
|
||||||
|
const batterySources = types.battery || [];
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-card>
|
||||||
|
<h1 class="card-header">
|
||||||
|
<ha-svg-icon .path=${mdiBatteryHigh}></ha-svg-icon>
|
||||||
|
${this.hass.localize("ui.panel.config.energy.battery.title")}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<p>
|
||||||
|
${this.hass.localize("ui.panel.config.energy.battery.sub")}
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
href="${documentationUrl(this.hass, "/docs/energy/battery/")}"
|
||||||
|
>${this.hass.localize(
|
||||||
|
"ui.panel.config.energy.battery.learn_more"
|
||||||
|
)}</a
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
<h3>Battery systems</h3>
|
||||||
|
${batterySources.map((source) => {
|
||||||
|
const fromEntityState = this.hass.states[source.stat_energy_from];
|
||||||
|
const toEntityState = this.hass.states[source.stat_energy_to];
|
||||||
|
return html`
|
||||||
|
<div class="row" .source=${source}>
|
||||||
|
${toEntityState?.attributes.icon
|
||||||
|
? html`<ha-icon
|
||||||
|
.icon=${toEntityState.attributes.icon}
|
||||||
|
></ha-icon>`
|
||||||
|
: html`<ha-svg-icon .path=${mdiBatteryHigh}></ha-svg-icon>`}
|
||||||
|
<div class="content">
|
||||||
|
<span
|
||||||
|
>${toEntityState
|
||||||
|
? computeStateName(toEntityState)
|
||||||
|
: source.stat_energy_from}</span
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
>${fromEntityState
|
||||||
|
? computeStateName(fromEntityState)
|
||||||
|
: source.stat_energy_to}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<mwc-icon-button @click=${this._editSource}>
|
||||||
|
<ha-svg-icon .path=${mdiPencil}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
<mwc-icon-button @click=${this._deleteSource}>
|
||||||
|
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
|
||||||
|
</mwc-icon-button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
<div class="row border-bottom">
|
||||||
|
<ha-svg-icon .path=${mdiBatteryHigh}></ha-svg-icon>
|
||||||
|
<mwc-button @click=${this._addSource}
|
||||||
|
>Add battery system</mwc-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ha-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _addSource() {
|
||||||
|
showEnergySettingsBatteryDialog(this, {
|
||||||
|
saveCallback: async (source) => {
|
||||||
|
await this._savePreferences({
|
||||||
|
...this.preferences,
|
||||||
|
energy_sources: this.preferences.energy_sources.concat(source),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _editSource(ev) {
|
||||||
|
const origSource: BatterySourceTypeEnergyPreference =
|
||||||
|
ev.currentTarget.closest(".row").source;
|
||||||
|
showEnergySettingsBatteryDialog(this, {
|
||||||
|
source: { ...origSource },
|
||||||
|
saveCallback: async (newSource) => {
|
||||||
|
await this._savePreferences({
|
||||||
|
...this.preferences,
|
||||||
|
energy_sources: this.preferences.energy_sources.map((src) =>
|
||||||
|
src === origSource ? newSource : src
|
||||||
|
),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _deleteSource(ev) {
|
||||||
|
const sourceToDelete: BatterySourceTypeEnergyPreference =
|
||||||
|
ev.currentTarget.closest(".row").source;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(await showConfirmationDialog(this, {
|
||||||
|
title: "Are you sure you want to delete this source?",
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this._savePreferences({
|
||||||
|
...this.preferences,
|
||||||
|
energy_sources: this.preferences.energy_sources.filter(
|
||||||
|
(source) => source !== sourceToDelete
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
showAlertDialog(this, { title: `Failed to save config: ${err.message}` });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _savePreferences(preferences: EnergyPreferences) {
|
||||||
|
const result = await saveEnergyPreferences(this.hass, preferences);
|
||||||
|
fireEvent(this, "value-changed", { value: result });
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
energyCardStyles,
|
||||||
|
css`
|
||||||
|
.row {
|
||||||
|
height: 58px;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"ha-energy-battery-settings": EnergyBatterySettings;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
import { mdiBatteryHigh } from "@mdi/js";
|
||||||
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import "../../../../components/ha-dialog";
|
||||||
|
import {
|
||||||
|
BatterySourceTypeEnergyPreference,
|
||||||
|
emptyBatteryEnergyPreference,
|
||||||
|
} from "../../../../data/energy";
|
||||||
|
import { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||||
|
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||||
|
import { HomeAssistant } from "../../../../types";
|
||||||
|
import { EnergySettingsBatteryDialogParams } from "./show-dialogs-energy";
|
||||||
|
import "@material/mwc-button/mwc-button";
|
||||||
|
import "../../../../components/entity/ha-statistic-picker";
|
||||||
|
|
||||||
|
const energyUnits = ["kWh"];
|
||||||
|
|
||||||
|
@customElement("dialog-energy-battery-settings")
|
||||||
|
export class DialogEnergyBatterySettings
|
||||||
|
extends LitElement
|
||||||
|
implements HassDialog<EnergySettingsBatteryDialogParams>
|
||||||
|
{
|
||||||
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
@state() private _params?: EnergySettingsBatteryDialogParams;
|
||||||
|
|
||||||
|
@state() private _source?: BatterySourceTypeEnergyPreference;
|
||||||
|
|
||||||
|
@state() private _error?: string;
|
||||||
|
|
||||||
|
public async showDialog(
|
||||||
|
params: EnergySettingsBatteryDialogParams
|
||||||
|
): Promise<void> {
|
||||||
|
this._params = params;
|
||||||
|
this._source = params.source
|
||||||
|
? { ...params.source }
|
||||||
|
: (this._source = emptyBatteryEnergyPreference());
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog(): void {
|
||||||
|
this._params = undefined;
|
||||||
|
this._source = undefined;
|
||||||
|
this._error = undefined;
|
||||||
|
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||||
|
}
|
||||||
|
|
||||||
|
protected render(): TemplateResult {
|
||||||
|
if (!this._params || !this._source) {
|
||||||
|
return html``;
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<ha-dialog
|
||||||
|
open
|
||||||
|
.heading=${html`<ha-svg-icon
|
||||||
|
.path=${mdiBatteryHigh}
|
||||||
|
style="--mdc-icon-size: 32px;"
|
||||||
|
></ha-svg-icon>
|
||||||
|
Configure battery system`}
|
||||||
|
@closed=${this.closeDialog}
|
||||||
|
>
|
||||||
|
${this._error ? html`<p class="error">${this._error}</p>` : ""}
|
||||||
|
|
||||||
|
<ha-statistic-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.includeUnitOfMeasurement=${energyUnits}
|
||||||
|
.value=${this._source.stat_energy_to}
|
||||||
|
.label=${`Energy going in to the battery (kWh)`}
|
||||||
|
entities-only
|
||||||
|
@value-changed=${this._statisticToChanged}
|
||||||
|
></ha-statistic-picker>
|
||||||
|
|
||||||
|
<ha-statistic-picker
|
||||||
|
.hass=${this.hass}
|
||||||
|
.includeUnitOfMeasurement=${energyUnits}
|
||||||
|
.value=${this._source.stat_energy_from}
|
||||||
|
.label=${`Energy coming out of the battery (kWh)`}
|
||||||
|
entities-only
|
||||||
|
@value-changed=${this._statisticFromChanged}
|
||||||
|
></ha-statistic-picker>
|
||||||
|
|
||||||
|
<mwc-button @click=${this.closeDialog} slot="secondaryAction">
|
||||||
|
${this.hass.localize("ui.common.cancel")}
|
||||||
|
</mwc-button>
|
||||||
|
<mwc-button
|
||||||
|
@click=${this._save}
|
||||||
|
.disabled=${!this._source.stat_energy_from ||
|
||||||
|
!this._source.stat_energy_to}
|
||||||
|
slot="primaryAction"
|
||||||
|
>
|
||||||
|
${this.hass.localize("ui.common.save")}
|
||||||
|
</mwc-button>
|
||||||
|
</ha-dialog>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _statisticToChanged(ev: CustomEvent<{ value: string }>) {
|
||||||
|
this._source = { ...this._source!, stat_energy_to: ev.detail.value };
|
||||||
|
}
|
||||||
|
|
||||||
|
private _statisticFromChanged(ev: CustomEvent<{ value: string }>) {
|
||||||
|
this._source = { ...this._source!, stat_energy_from: ev.detail.value };
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _save() {
|
||||||
|
try {
|
||||||
|
await this._params!.saveCallback(this._source!);
|
||||||
|
this.closeDialog();
|
||||||
|
} catch (e) {
|
||||||
|
this._error = e.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles(): CSSResultGroup {
|
||||||
|
return [
|
||||||
|
haStyle,
|
||||||
|
haStyleDialog,
|
||||||
|
css`
|
||||||
|
ha-dialog {
|
||||||
|
--mdc-dialog-max-width: 430px;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
"dialog-energy-battery-settings": DialogEnergyBatterySettings;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,6 @@ import "../../../../components/ha-radio";
|
||||||
import "../../../../components/ha-checkbox";
|
import "../../../../components/ha-checkbox";
|
||||||
import type { HaCheckbox } from "../../../../components/ha-checkbox";
|
import type { HaCheckbox } from "../../../../components/ha-checkbox";
|
||||||
import "../../../../components/ha-formfield";
|
import "../../../../components/ha-formfield";
|
||||||
import "../../../../components/entity/ha-entity-picker";
|
|
||||||
import type { HaRadio } from "../../../../components/ha-radio";
|
import type { HaRadio } from "../../../../components/ha-radio";
|
||||||
import { showConfigFlowDialog } from "../../../../dialogs/config-flow/show-dialog-config-flow";
|
import { showConfigFlowDialog } from "../../../../dialogs/config-flow/show-dialog-config-flow";
|
||||||
import { ConfigEntry, getConfigEntries } from "../../../../data/config_entries";
|
import { ConfigEntry, getConfigEntries } from "../../../../data/config_entries";
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import {
|
import {
|
||||||
|
BatterySourceTypeEnergyPreference,
|
||||||
DeviceConsumptionEnergyPreference,
|
DeviceConsumptionEnergyPreference,
|
||||||
FlowFromGridSourceEnergyPreference,
|
FlowFromGridSourceEnergyPreference,
|
||||||
FlowToGridSourceEnergyPreference,
|
FlowToGridSourceEnergyPreference,
|
||||||
|
@ -33,6 +34,11 @@ export interface EnergySettingsSolarDialogParams {
|
||||||
saveCallback: (source: SolarSourceTypeEnergyPreference) => Promise<void>;
|
saveCallback: (source: SolarSourceTypeEnergyPreference) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EnergySettingsBatteryDialogParams {
|
||||||
|
source?: BatterySourceTypeEnergyPreference;
|
||||||
|
saveCallback: (source: BatterySourceTypeEnergyPreference) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface EnergySettingsDeviceDialogParams {
|
export interface EnergySettingsDeviceDialogParams {
|
||||||
saveCallback: (device: DeviceConsumptionEnergyPreference) => Promise<void>;
|
saveCallback: (device: DeviceConsumptionEnergyPreference) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -48,6 +54,17 @@ export const showEnergySettingsDeviceDialog = (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const showEnergySettingsBatteryDialog = (
|
||||||
|
element: HTMLElement,
|
||||||
|
dialogParams: EnergySettingsBatteryDialogParams
|
||||||
|
): void => {
|
||||||
|
fireEvent(element, "show-dialog", {
|
||||||
|
dialogTag: "dialog-energy-battery-settings",
|
||||||
|
dialogImport: () => import("./dialog-energy-battery-settings"),
|
||||||
|
dialogParams: dialogParams,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export const showEnergySettingsSolarDialog = (
|
export const showEnergySettingsSolarDialog = (
|
||||||
element: HTMLElement,
|
element: HTMLElement,
|
||||||
dialogParams: EnergySettingsSolarDialogParams
|
dialogParams: EnergySettingsSolarDialogParams
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { configSections } from "../ha-panel-config";
|
||||||
import "./components/ha-energy-device-settings";
|
import "./components/ha-energy-device-settings";
|
||||||
import "./components/ha-energy-grid-settings";
|
import "./components/ha-energy-grid-settings";
|
||||||
import "./components/ha-energy-solar-settings";
|
import "./components/ha-energy-solar-settings";
|
||||||
|
import "./components/ha-energy-battery-settings";
|
||||||
|
|
||||||
const INITIAL_CONFIG: EnergyPreferences = {
|
const INITIAL_CONFIG: EnergyPreferences = {
|
||||||
energy_sources: [],
|
energy_sources: [],
|
||||||
|
@ -81,6 +82,11 @@ class HaConfigEnergy extends LitElement {
|
||||||
.preferences=${this._preferences!}
|
.preferences=${this._preferences!}
|
||||||
@value-changed=${this._prefsChanged}
|
@value-changed=${this._prefsChanged}
|
||||||
></ha-energy-solar-settings>
|
></ha-energy-solar-settings>
|
||||||
|
<ha-energy-battery-settings
|
||||||
|
.hass=${this.hass}
|
||||||
|
.preferences=${this._preferences!}
|
||||||
|
@value-changed=${this._prefsChanged}
|
||||||
|
></ha-energy-battery-settings>
|
||||||
<ha-energy-device-settings
|
<ha-energy-device-settings
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.preferences=${this._preferences!}
|
.preferences=${this._preferences!}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { LovelaceCard, Lovelace } from "../../lovelace/types";
|
||||||
import "@material/mwc-button/mwc-button";
|
import "@material/mwc-button/mwc-button";
|
||||||
import "../../config/energy/components/ha-energy-grid-settings";
|
import "../../config/energy/components/ha-energy-grid-settings";
|
||||||
import "../../config/energy/components/ha-energy-solar-settings";
|
import "../../config/energy/components/ha-energy-solar-settings";
|
||||||
|
import "../../config/energy/components/ha-energy-battery-settings";
|
||||||
import "../../config/energy/components/ha-energy-device-settings";
|
import "../../config/energy/components/ha-energy-device-settings";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||||
|
@ -43,18 +44,24 @@ export class EnergySetupWizard extends LitElement implements LovelaceCard {
|
||||||
return html`
|
return html`
|
||||||
<p>Step ${this._step + 1} of 3</p>
|
<p>Step ${this._step + 1} of 3</p>
|
||||||
${this._step === 0
|
${this._step === 0
|
||||||
? html` <ha-energy-grid-settings
|
? html`<ha-energy-grid-settings
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.preferences=${this._preferences}
|
.preferences=${this._preferences}
|
||||||
@value-changed=${this._prefsChanged}
|
@value-changed=${this._prefsChanged}
|
||||||
></ha-energy-grid-settings>`
|
></ha-energy-grid-settings>`
|
||||||
: this._step === 1
|
: this._step === 1
|
||||||
? html` <ha-energy-solar-settings
|
? html`<ha-energy-solar-settings
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.preferences=${this._preferences}
|
.preferences=${this._preferences}
|
||||||
@value-changed=${this._prefsChanged}
|
@value-changed=${this._prefsChanged}
|
||||||
></ha-energy-solar-settings>`
|
></ha-energy-solar-settings>`
|
||||||
: html` <ha-energy-device-settings
|
: this._step === 2
|
||||||
|
? html`<ha-energy-battery-settings
|
||||||
|
.hass=${this.hass}
|
||||||
|
.preferences=${this._preferences}
|
||||||
|
@value-changed=${this._prefsChanged}
|
||||||
|
></ha-energy-battery-settings>`
|
||||||
|
: html`<ha-energy-device-settings
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
.preferences=${this._preferences}
|
.preferences=${this._preferences}
|
||||||
@value-changed=${this._prefsChanged}
|
@value-changed=${this._prefsChanged}
|
||||||
|
@ -65,7 +72,7 @@ export class EnergySetupWizard extends LitElement implements LovelaceCard {
|
||||||
>${this.hass.localize("ui.panel.energy.setup.back")}</mwc-button
|
>${this.hass.localize("ui.panel.energy.setup.back")}</mwc-button
|
||||||
>`
|
>`
|
||||||
: html`<div></div>`}
|
: html`<div></div>`}
|
||||||
${this._step < 2
|
${this._step < 3
|
||||||
? html`<mwc-button unelevated @click=${this._next}
|
? html`<mwc-button unelevated @click=${this._next}
|
||||||
>${this.hass.localize("ui.panel.energy.setup.next")}</mwc-button
|
>${this.hass.localize("ui.panel.energy.setup.next")}</mwc-button
|
||||||
>`
|
>`
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import {
|
import {
|
||||||
|
mdiArrowDown,
|
||||||
mdiArrowLeft,
|
mdiArrowLeft,
|
||||||
mdiArrowRight,
|
mdiArrowRight,
|
||||||
|
mdiArrowUp,
|
||||||
|
mdiBatteryHigh,
|
||||||
mdiHome,
|
mdiHome,
|
||||||
mdiLeaf,
|
mdiLeaf,
|
||||||
mdiSolarPower,
|
mdiSolarPower,
|
||||||
|
@ -75,9 +78,10 @@ class HuiEnergyDistrubutionCard
|
||||||
const hasConsumption = true;
|
const hasConsumption = true;
|
||||||
|
|
||||||
const hasSolarProduction = types.solar !== undefined;
|
const hasSolarProduction = types.solar !== undefined;
|
||||||
|
const hasBattery = types.battery !== undefined;
|
||||||
const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0;
|
const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0;
|
||||||
|
|
||||||
const totalGridConsumption =
|
const totalFromGrid =
|
||||||
calculateStatisticsSumGrowth(
|
calculateStatisticsSumGrowth(
|
||||||
this._data.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)
|
||||||
|
@ -93,30 +97,97 @@ class HuiEnergyDistrubutionCard
|
||||||
) || 0;
|
) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let productionReturnedToGrid: number | null = null;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
let returnedToGrid: number | null = null;
|
||||||
|
|
||||||
if (hasReturnToGrid) {
|
if (hasReturnToGrid) {
|
||||||
productionReturnedToGrid =
|
returnedToGrid =
|
||||||
calculateStatisticsSumGrowth(
|
calculateStatisticsSumGrowth(
|
||||||
this._data.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
const solarConsumption = Math.max(
|
let solarConsumption: number | null = null;
|
||||||
0,
|
if (hasSolarProduction) {
|
||||||
(totalSolarProduction || 0) - (productionReturnedToGrid || 0)
|
solarConsumption =
|
||||||
);
|
(totalSolarProduction || 0) -
|
||||||
|
(returnedToGrid || 0) -
|
||||||
|
(totalBatteryIn || 0);
|
||||||
|
}
|
||||||
|
|
||||||
const totalHomeConsumption = totalGridConsumption + solarConsumption;
|
let batteryFromGrid: null | number = null;
|
||||||
|
let batteryToGrid: null | number = null;
|
||||||
|
if (solarConsumption !== null && solarConsumption < 0) {
|
||||||
|
// What we returned to the grid and what went in to the battery is more than produced,
|
||||||
|
// so we have used grid energy to fill the battery
|
||||||
|
// or returned battery energy to the grid
|
||||||
|
if (hasBattery) {
|
||||||
|
batteryFromGrid = solarConsumption * -1;
|
||||||
|
if (batteryFromGrid > totalFromGrid) {
|
||||||
|
batteryToGrid = Math.min(0, batteryFromGrid - totalFromGrid);
|
||||||
|
batteryFromGrid = totalFromGrid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
solarConsumption = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let solarToBattery: null | number = null;
|
||||||
|
if (hasSolarProduction && hasBattery) {
|
||||||
|
if (!batteryToGrid) {
|
||||||
|
batteryToGrid = Math.max(
|
||||||
|
0,
|
||||||
|
(returnedToGrid || 0) -
|
||||||
|
(totalSolarProduction || 0) -
|
||||||
|
(totalBatteryIn || 0) -
|
||||||
|
(batteryFromGrid || 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
solarToBattery = totalBatteryIn! - (batteryFromGrid || 0);
|
||||||
|
} else if (!hasSolarProduction && hasBattery) {
|
||||||
|
batteryToGrid = returnedToGrid;
|
||||||
|
}
|
||||||
|
|
||||||
|
let batteryConsumption: number | null = null;
|
||||||
|
if (hasBattery) {
|
||||||
|
batteryConsumption = (totalBatteryOut || 0) - (batteryToGrid || 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gridConsumption = Math.max(0, totalFromGrid - (batteryFromGrid || 0));
|
||||||
|
|
||||||
|
const totalHomeConsumption = Math.max(
|
||||||
|
0,
|
||||||
|
gridConsumption + (solarConsumption || 0) + (batteryConsumption || 0)
|
||||||
|
);
|
||||||
|
|
||||||
let homeSolarCircumference: number | undefined;
|
let homeSolarCircumference: number | undefined;
|
||||||
if (hasSolarProduction) {
|
if (hasSolarProduction) {
|
||||||
homeSolarCircumference =
|
homeSolarCircumference =
|
||||||
CIRCLE_CIRCUMFERENCE * (solarConsumption / totalHomeConsumption);
|
CIRCLE_CIRCUMFERENCE * (solarConsumption! / totalHomeConsumption);
|
||||||
}
|
}
|
||||||
|
|
||||||
let lowCarbonConsumption: number | undefined;
|
let homeBatteryCircumference: number | undefined;
|
||||||
|
if (batteryConsumption) {
|
||||||
|
homeBatteryCircumference =
|
||||||
|
CIRCLE_CIRCUMFERENCE * (batteryConsumption / totalHomeConsumption);
|
||||||
|
}
|
||||||
|
|
||||||
|
let lowCarbonEnergy: number | undefined;
|
||||||
|
|
||||||
let homeLowCarbonCircumference: number | undefined;
|
let homeLowCarbonCircumference: number | undefined;
|
||||||
let homeHighCarbonCircumference: number | undefined;
|
let homeHighCarbonCircumference: number | undefined;
|
||||||
|
@ -129,7 +200,7 @@ class HuiEnergyDistrubutionCard
|
||||||
this._data.co2SignalEntity in this._data.stats
|
this._data.co2SignalEntity in this._data.stats
|
||||||
) {
|
) {
|
||||||
// Calculate high carbon consumption
|
// Calculate high carbon consumption
|
||||||
const highCarbonConsumption = calculateStatisticsSumGrowthWithPercentage(
|
const highCarbonEnergy = calculateStatisticsSumGrowthWithPercentage(
|
||||||
this._data.stats[this._data.co2SignalEntity],
|
this._data.stats[this._data.co2SignalEntity],
|
||||||
types
|
types
|
||||||
.grid![0].flow_from.map(
|
.grid![0].flow_from.map(
|
||||||
|
@ -144,8 +215,17 @@ class HuiEnergyDistrubutionCard
|
||||||
electricityMapUrl += `/zone/${co2State.attributes.country_code}`;
|
electricityMapUrl += `/zone/${co2State.attributes.country_code}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (highCarbonConsumption !== null) {
|
if (highCarbonEnergy !== null) {
|
||||||
lowCarbonConsumption = totalGridConsumption - highCarbonConsumption;
|
lowCarbonEnergy = totalFromGrid - highCarbonEnergy;
|
||||||
|
|
||||||
|
let highCarbonConsumption: number;
|
||||||
|
if (gridConsumption !== totalFromGrid) {
|
||||||
|
// Only get the part that was used for consumption and not the battery
|
||||||
|
highCarbonConsumption =
|
||||||
|
highCarbonEnergy * (gridConsumption / totalFromGrid);
|
||||||
|
} else {
|
||||||
|
highCarbonConsumption = highCarbonEnergy;
|
||||||
|
}
|
||||||
|
|
||||||
homeHighCarbonCircumference =
|
homeHighCarbonCircumference =
|
||||||
CIRCLE_CIRCUMFERENCE * (highCarbonConsumption / totalHomeConsumption);
|
CIRCLE_CIRCUMFERENCE * (highCarbonConsumption / totalHomeConsumption);
|
||||||
|
@ -153,16 +233,26 @@ class HuiEnergyDistrubutionCard
|
||||||
homeLowCarbonCircumference =
|
homeLowCarbonCircumference =
|
||||||
CIRCLE_CIRCUMFERENCE -
|
CIRCLE_CIRCUMFERENCE -
|
||||||
(homeSolarCircumference || 0) -
|
(homeSolarCircumference || 0) -
|
||||||
|
(homeBatteryCircumference || 0) -
|
||||||
homeHighCarbonCircumference;
|
homeHighCarbonCircumference;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalLines =
|
||||||
|
gridConsumption +
|
||||||
|
(solarConsumption || 0) +
|
||||||
|
(returnedToGrid ? returnedToGrid - (batteryToGrid || 0) : 0) +
|
||||||
|
(solarToBattery || 0) +
|
||||||
|
(batteryConsumption || 0) +
|
||||||
|
(batteryFromGrid || 0) +
|
||||||
|
(batteryToGrid || 0);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card .header=${this._config.title}>
|
<ha-card .header=${this._config.title}>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
${lowCarbonConsumption !== undefined || hasSolarProduction
|
${lowCarbonEnergy !== undefined || hasSolarProduction
|
||||||
? html`<div class="row">
|
? html`<div class="row">
|
||||||
${lowCarbonConsumption === undefined
|
${lowCarbonEnergy === undefined
|
||||||
? html`<div class="spacer"></div>`
|
? html`<div class="spacer"></div>`
|
||||||
: html`<div class="circle-container low-carbon">
|
: html`<div class="circle-container low-carbon">
|
||||||
<span class="label">Non-fossil</span>
|
<span class="label">Non-fossil</span>
|
||||||
|
@ -173,12 +263,10 @@ class HuiEnergyDistrubutionCard
|
||||||
rel="noopener no referrer"
|
rel="noopener no referrer"
|
||||||
>
|
>
|
||||||
<ha-svg-icon .path="${mdiLeaf}"></ha-svg-icon>
|
<ha-svg-icon .path="${mdiLeaf}"></ha-svg-icon>
|
||||||
${lowCarbonConsumption
|
${lowCarbonEnergy
|
||||||
? formatNumber(
|
? formatNumber(lowCarbonEnergy, this.hass.locale, {
|
||||||
lowCarbonConsumption,
|
maximumFractionDigits: 1,
|
||||||
this.hass.locale,
|
})
|
||||||
{ maximumFractionDigits: 1 }
|
|
||||||
)
|
|
||||||
: "-"}
|
: "-"}
|
||||||
kWh
|
kWh
|
||||||
</a>
|
</a>
|
||||||
|
@ -207,33 +295,29 @@ class HuiEnergyDistrubutionCard
|
||||||
<div class="circle-container grid">
|
<div class="circle-container grid">
|
||||||
<div class="circle">
|
<div class="circle">
|
||||||
<ha-svg-icon .path="${mdiTransmissionTower}"></ha-svg-icon>
|
<ha-svg-icon .path="${mdiTransmissionTower}"></ha-svg-icon>
|
||||||
|
${returnedToGrid !== null
|
||||||
|
? html`<span class="return">
|
||||||
|
<ha-svg-icon
|
||||||
|
class="small"
|
||||||
|
.path=${mdiArrowLeft}
|
||||||
|
></ha-svg-icon
|
||||||
|
>${formatNumber(returnedToGrid, this.hass.locale, {
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
})}
|
||||||
|
kWh
|
||||||
|
</span>`
|
||||||
|
: ""}
|
||||||
<span class="consumption">
|
<span class="consumption">
|
||||||
${hasReturnToGrid
|
${hasReturnToGrid
|
||||||
? html`<ha-svg-icon
|
? html`<ha-svg-icon
|
||||||
class="small"
|
class="small"
|
||||||
.path=${mdiArrowRight}
|
.path=${mdiArrowRight}
|
||||||
></ha-svg-icon>`
|
></ha-svg-icon>`
|
||||||
: ""}${formatNumber(
|
: ""}${formatNumber(totalFromGrid, this.hass.locale, {
|
||||||
totalGridConsumption,
|
maximumFractionDigits: 1,
|
||||||
this.hass.locale,
|
})}
|
||||||
{ maximumFractionDigits: 1 }
|
|
||||||
)}
|
|
||||||
kWh
|
kWh
|
||||||
</span>
|
</span>
|
||||||
${productionReturnedToGrid !== null
|
|
||||||
? html`<span class="return">
|
|
||||||
<ha-svg-icon
|
|
||||||
class="small"
|
|
||||||
.path=${mdiArrowLeft}
|
|
||||||
></ha-svg-icon
|
|
||||||
>${formatNumber(
|
|
||||||
productionReturnedToGrid,
|
|
||||||
this.hass.locale,
|
|
||||||
{ maximumFractionDigits: 1 }
|
|
||||||
)}
|
|
||||||
kWh
|
|
||||||
</span>`
|
|
||||||
: ""}
|
|
||||||
</div>
|
</div>
|
||||||
<span class="label">Grid</span>
|
<span class="label">Grid</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -268,6 +352,23 @@ class HuiEnergyDistrubutionCard
|
||||||
}"
|
}"
|
||||||
/>`
|
/>`
|
||||||
: ""}
|
: ""}
|
||||||
|
${homeBatteryCircumference
|
||||||
|
? svg`<circle
|
||||||
|
class="battery"
|
||||||
|
cx="40"
|
||||||
|
cy="40"
|
||||||
|
r="38"
|
||||||
|
stroke-dasharray="${homeBatteryCircumference} ${
|
||||||
|
CIRCLE_CIRCUMFERENCE - homeBatteryCircumference
|
||||||
|
}"
|
||||||
|
stroke-dashoffset="-${
|
||||||
|
CIRCLE_CIRCUMFERENCE -
|
||||||
|
homeBatteryCircumference -
|
||||||
|
(homeSolarCircumference || 0)
|
||||||
|
}"
|
||||||
|
shape-rendering="geometricPrecision"
|
||||||
|
/>`
|
||||||
|
: ""}
|
||||||
${homeLowCarbonCircumference
|
${homeLowCarbonCircumference
|
||||||
? svg`<circle
|
? svg`<circle
|
||||||
class="low-carbon"
|
class="low-carbon"
|
||||||
|
@ -280,6 +381,7 @@ class HuiEnergyDistrubutionCard
|
||||||
stroke-dashoffset="-${
|
stroke-dashoffset="-${
|
||||||
CIRCLE_CIRCUMFERENCE -
|
CIRCLE_CIRCUMFERENCE -
|
||||||
homeLowCarbonCircumference -
|
homeLowCarbonCircumference -
|
||||||
|
(homeBatteryCircumference || 0) -
|
||||||
(homeSolarCircumference || 0)
|
(homeSolarCircumference || 0)
|
||||||
}"
|
}"
|
||||||
shape-rendering="geometricPrecision"
|
shape-rendering="geometricPrecision"
|
||||||
|
@ -292,10 +394,12 @@ class HuiEnergyDistrubutionCard
|
||||||
r="38"
|
r="38"
|
||||||
stroke-dasharray="${homeHighCarbonCircumference ??
|
stroke-dasharray="${homeHighCarbonCircumference ??
|
||||||
CIRCLE_CIRCUMFERENCE -
|
CIRCLE_CIRCUMFERENCE -
|
||||||
homeSolarCircumference!} ${homeHighCarbonCircumference !==
|
homeSolarCircumference! -
|
||||||
undefined
|
(homeBatteryCircumference ||
|
||||||
|
0)} ${homeHighCarbonCircumference !== undefined
|
||||||
? CIRCLE_CIRCUMFERENCE - homeHighCarbonCircumference
|
? CIRCLE_CIRCUMFERENCE - homeHighCarbonCircumference
|
||||||
: homeSolarCircumference}"
|
: homeSolarCircumference! +
|
||||||
|
(homeBatteryCircumference || 0)}"
|
||||||
stroke-dashoffset="0"
|
stroke-dashoffset="0"
|
||||||
shape-rendering="geometricPrecision"
|
shape-rendering="geometricPrecision"
|
||||||
/>
|
/>
|
||||||
|
@ -305,7 +409,39 @@ class HuiEnergyDistrubutionCard
|
||||||
<span class="label">Home</span>
|
<span class="label">Home</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lines">
|
${hasBattery
|
||||||
|
? html`<div class="row">
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<div class="circle-container battery">
|
||||||
|
<div class="circle">
|
||||||
|
<ha-svg-icon .path="${mdiBatteryHigh}"></ha-svg-icon>
|
||||||
|
<span class="battery-in">
|
||||||
|
<ha-svg-icon
|
||||||
|
class="small"
|
||||||
|
.path=${mdiArrowDown}
|
||||||
|
></ha-svg-icon
|
||||||
|
>${formatNumber(totalBatteryIn || 0, this.hass.locale, {
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
})}
|
||||||
|
kWh</span
|
||||||
|
>
|
||||||
|
<span class="battery-out">
|
||||||
|
<ha-svg-icon
|
||||||
|
class="small"
|
||||||
|
.path=${mdiArrowUp}
|
||||||
|
></ha-svg-icon>
|
||||||
|
${formatNumber(totalBatteryOut || 0, this.hass.locale, {
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
})}
|
||||||
|
kWh</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<span class="label">Battery</span>
|
||||||
|
</div>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
</div>`
|
||||||
|
: ""}
|
||||||
|
<div class="lines ${classMap({ battery: hasBattery })}">
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 100 100"
|
viewBox="0 0 100 100"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
@ -315,7 +451,9 @@ class HuiEnergyDistrubutionCard
|
||||||
? svg`<path
|
? svg`<path
|
||||||
id="return"
|
id="return"
|
||||||
class="return"
|
class="return"
|
||||||
d="M47,0 v15 c0,40 -10,35 -30,35 h-20"
|
d="M${hasBattery ? 45 : 47},0 v15 c0,${
|
||||||
|
hasBattery ? "35 -10,30 -30,30" : "40 -10,35 -30,35"
|
||||||
|
} h-20"
|
||||||
vector-effect="non-scaling-stroke"
|
vector-effect="non-scaling-stroke"
|
||||||
></path> `
|
></path> `
|
||||||
: ""}
|
: ""}
|
||||||
|
@ -323,19 +461,45 @@ class HuiEnergyDistrubutionCard
|
||||||
? svg`<path
|
? svg`<path
|
||||||
id="solar"
|
id="solar"
|
||||||
class="solar"
|
class="solar"
|
||||||
d="M${
|
d="M${hasBattery ? 55 : 53},0 v15 c0,${
|
||||||
hasReturnToGrid ? 53 : 50
|
hasBattery ? "35 10,30 30,30" : "40 10,35 30,35"
|
||||||
},0 v15 c0,40 10,35 30,35 h20"
|
} h20"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
></path>`
|
||||||
|
: ""}
|
||||||
|
${hasBattery
|
||||||
|
? svg`<path
|
||||||
|
id="battery-house"
|
||||||
|
class="battery-house"
|
||||||
|
d="M55,100 v-15 c0,-35 10,-30 30,-30 h20"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
id="battery-grid"
|
||||||
|
class=${classMap({
|
||||||
|
"battery-from-grid": Boolean(batteryFromGrid),
|
||||||
|
"battery-to-grid": Boolean(batteryToGrid),
|
||||||
|
})}
|
||||||
|
d="M45,100 v-15 c0,-35 -10,-30 -30,-30 h-20"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
></path>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
${hasBattery && hasSolarProduction
|
||||||
|
? svg`<path
|
||||||
|
id="battery-solar"
|
||||||
|
class="battery-solar"
|
||||||
|
d="M50,0 V100"
|
||||||
vector-effect="non-scaling-stroke"
|
vector-effect="non-scaling-stroke"
|
||||||
></path>`
|
></path>`
|
||||||
: ""}
|
: ""}
|
||||||
<path
|
<path
|
||||||
class="grid"
|
class="grid"
|
||||||
id="grid"
|
id="grid"
|
||||||
d="M0,${hasSolarProduction ? 56 : 53} H100"
|
d="M0,${hasBattery ? 50 : hasSolarProduction ? 56 : 53} H100"
|
||||||
vector-effect="non-scaling-stroke"
|
vector-effect="non-scaling-stroke"
|
||||||
></path>
|
></path>
|
||||||
${productionReturnedToGrid && hasSolarProduction
|
${returnedToGrid && hasSolarProduction
|
||||||
? svg`<circle
|
? svg`<circle
|
||||||
r="1"
|
r="1"
|
||||||
class="return"
|
class="return"
|
||||||
|
@ -344,61 +508,107 @@ class HuiEnergyDistrubutionCard
|
||||||
<animateMotion
|
<animateMotion
|
||||||
dur="${
|
dur="${
|
||||||
6 -
|
6 -
|
||||||
(productionReturnedToGrid /
|
((returnedToGrid - (batteryToGrid || 0)) / totalLines) *
|
||||||
(totalGridConsumption +
|
6
|
||||||
(totalSolarProduction || 0))) *
|
|
||||||
5
|
|
||||||
}s"
|
}s"
|
||||||
repeatCount="indefinite"
|
repeatCount="indefinite"
|
||||||
rotate="auto"
|
calcMode="linear"
|
||||||
>
|
>
|
||||||
<mpath xlink:href="#return" />
|
<mpath xlink:href="#return" />
|
||||||
</animateMotion>
|
</animateMotion>
|
||||||
</circle>`
|
</circle>`
|
||||||
: ""}
|
: ""}
|
||||||
${totalSolarProduction
|
${solarConsumption
|
||||||
? svg`<circle
|
? svg`<circle
|
||||||
r="1"
|
r="1"
|
||||||
class="solar"
|
class="solar"
|
||||||
vector-effect="non-scaling-stroke"
|
vector-effect="non-scaling-stroke"
|
||||||
>
|
>
|
||||||
<animateMotion
|
<animateMotion
|
||||||
dur="${
|
dur="${6 - (solarConsumption / totalLines) * 5}s"
|
||||||
6 -
|
|
||||||
((totalSolarProduction -
|
|
||||||
(productionReturnedToGrid || 0)) /
|
|
||||||
(totalGridConsumption +
|
|
||||||
(totalSolarProduction || 0))) *
|
|
||||||
5
|
|
||||||
}s"
|
|
||||||
repeatCount="indefinite"
|
repeatCount="indefinite"
|
||||||
rotate="auto"
|
calcMode="linear"
|
||||||
>
|
>
|
||||||
<mpath xlink:href="#solar" />
|
<mpath xlink:href="#solar" />
|
||||||
</animateMotion>
|
</animateMotion>
|
||||||
</circle>`
|
</circle>`
|
||||||
: ""}
|
: ""}
|
||||||
${totalGridConsumption
|
${gridConsumption
|
||||||
? svg`<circle
|
? svg`<circle
|
||||||
r="1"
|
r="1"
|
||||||
class="grid"
|
class="grid"
|
||||||
vector-effect="non-scaling-stroke"
|
vector-effect="non-scaling-stroke"
|
||||||
>
|
>
|
||||||
<animateMotion
|
<animateMotion
|
||||||
dur="${
|
dur="${6 - (gridConsumption / totalLines) * 5}s"
|
||||||
6 -
|
|
||||||
(totalGridConsumption /
|
|
||||||
(totalGridConsumption +
|
|
||||||
(totalSolarProduction || 0))) *
|
|
||||||
5
|
|
||||||
}s"
|
|
||||||
repeatCount="indefinite"
|
repeatCount="indefinite"
|
||||||
rotate="auto"
|
calcMode="linear"
|
||||||
>
|
>
|
||||||
<mpath xlink:href="#grid" />
|
<mpath xlink:href="#grid" />
|
||||||
</animateMotion>
|
</animateMotion>
|
||||||
</circle>`
|
</circle>`
|
||||||
: ""}
|
: ""}
|
||||||
|
${solarToBattery
|
||||||
|
? svg`<circle
|
||||||
|
r="1"
|
||||||
|
class="battery-solar"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
>
|
||||||
|
<animateMotion
|
||||||
|
dur="${6 - (solarToBattery / totalLines) * 5}s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
calcMode="linear"
|
||||||
|
>
|
||||||
|
<mpath xlink:href="#battery-solar" />
|
||||||
|
</animateMotion>
|
||||||
|
</circle>`
|
||||||
|
: ""}
|
||||||
|
${batteryConsumption
|
||||||
|
? svg`<circle
|
||||||
|
r="1"
|
||||||
|
class="battery-house"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
>
|
||||||
|
<animateMotion
|
||||||
|
dur="${6 - (batteryConsumption / totalLines) * 5}s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
calcMode="linear"
|
||||||
|
>
|
||||||
|
<mpath xlink:href="#battery-house" />
|
||||||
|
</animateMotion>
|
||||||
|
</circle>`
|
||||||
|
: ""}
|
||||||
|
${batteryFromGrid
|
||||||
|
? svg`<circle
|
||||||
|
r="1"
|
||||||
|
class="battery-from-grid"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
>
|
||||||
|
<animateMotion
|
||||||
|
dur="${6 - (batteryFromGrid / totalLines) * 5}s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
keyPoints="1;0" keyTimes="0;1"
|
||||||
|
calcMode="linear"
|
||||||
|
>
|
||||||
|
<mpath xlink:href="#battery-grid" />
|
||||||
|
</animateMotion>
|
||||||
|
</circle>`
|
||||||
|
: ""}
|
||||||
|
${batteryToGrid
|
||||||
|
? svg`<circle
|
||||||
|
r="1"
|
||||||
|
class="battery-to-grid"
|
||||||
|
vector-effect="non-scaling-stroke"
|
||||||
|
>
|
||||||
|
<animateMotion
|
||||||
|
dur="${6 - (batteryToGrid / totalLines) * 5}s"
|
||||||
|
repeatCount="indefinite"
|
||||||
|
calcMode="linear"
|
||||||
|
>
|
||||||
|
<mpath xlink:href="#battery-grid" />
|
||||||
|
</animateMotion>
|
||||||
|
</circle>`
|
||||||
|
: ""}
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -433,6 +643,10 @@ class HuiEnergyDistrubutionCard
|
||||||
padding: 0 16px 16px;
|
padding: 0 16px 16px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
.lines.battery {
|
||||||
|
bottom: 100px;
|
||||||
|
height: 156px;
|
||||||
|
}
|
||||||
.lines svg {
|
.lines svg {
|
||||||
width: calc(100% - 160px);
|
width: calc(100% - 160px);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -456,6 +670,10 @@ class HuiEnergyDistrubutionCard
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
height: 130px;
|
height: 130px;
|
||||||
}
|
}
|
||||||
|
.circle-container.battery {
|
||||||
|
height: 110px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
.spacer {
|
.spacer {
|
||||||
width: 84px;
|
width: 84px;
|
||||||
}
|
}
|
||||||
|
@ -523,11 +741,48 @@ class HuiEnergyDistrubutionCard
|
||||||
stroke-width: 4;
|
stroke-width: 4;
|
||||||
fill: var(--energy-solar-color);
|
fill: var(--energy-solar-color);
|
||||||
}
|
}
|
||||||
path.return,
|
.battery .circle {
|
||||||
circle.return {
|
border-color: var(--energy-battery-in-color);
|
||||||
|
}
|
||||||
|
circle.battery,
|
||||||
|
path.battery {
|
||||||
|
stroke: var(--energy-battery-out-color);
|
||||||
|
}
|
||||||
|
path.battery-house,
|
||||||
|
circle.battery-house {
|
||||||
|
stroke: var(--energy-battery-out-color);
|
||||||
|
}
|
||||||
|
circle.battery-house {
|
||||||
|
stroke-width: 4;
|
||||||
|
fill: var(--energy-battery-out-color);
|
||||||
|
}
|
||||||
|
path.battery-solar,
|
||||||
|
circle.battery-solar {
|
||||||
|
stroke: var(--energy-battery-in-color);
|
||||||
|
}
|
||||||
|
circle.battery-solar {
|
||||||
|
stroke-width: 4;
|
||||||
|
fill: var(--energy-battery-in-color);
|
||||||
|
}
|
||||||
|
.battery-in {
|
||||||
|
color: var(--energy-battery-in-color);
|
||||||
|
}
|
||||||
|
.battery-out {
|
||||||
|
color: var(--energy-battery-out-color);
|
||||||
|
}
|
||||||
|
path.battery-from-grid {
|
||||||
|
stroke: var(--energy-grid-consumption-color);
|
||||||
|
}
|
||||||
|
path.battery-to-grid {
|
||||||
stroke: var(--energy-grid-return-color);
|
stroke: var(--energy-grid-return-color);
|
||||||
}
|
}
|
||||||
circle.return {
|
path.return,
|
||||||
|
circle.return,
|
||||||
|
circle.battery-to-grid {
|
||||||
|
stroke: var(--energy-grid-return-color);
|
||||||
|
}
|
||||||
|
circle.return,
|
||||||
|
circle.battery-to-grid {
|
||||||
stroke-width: 4;
|
stroke-width: 4;
|
||||||
fill: var(--energy-grid-return-color);
|
fill: var(--energy-grid-return-color);
|
||||||
}
|
}
|
||||||
|
@ -541,10 +796,12 @@ class HuiEnergyDistrubutionCard
|
||||||
color: var(--energy-grid-consumption-color);
|
color: var(--energy-grid-consumption-color);
|
||||||
}
|
}
|
||||||
circle.grid,
|
circle.grid,
|
||||||
|
circle.battery-from-grid,
|
||||||
path.grid {
|
path.grid {
|
||||||
stroke: var(--energy-grid-consumption-color);
|
stroke: var(--energy-grid-consumption-color);
|
||||||
}
|
}
|
||||||
circle.grid {
|
circle.grid,
|
||||||
|
circle.battery-from-grid {
|
||||||
stroke-width: 4;
|
stroke-width: 4;
|
||||||
fill: var(--energy-grid-consumption-color);
|
fill: var(--energy-grid-consumption-color);
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ export class HuiEnergySourcesTableCard
|
||||||
|
|
||||||
let totalGrid = 0;
|
let totalGrid = 0;
|
||||||
let totalSolar = 0;
|
let totalSolar = 0;
|
||||||
|
let totalBattery = 0;
|
||||||
let totalCost = 0;
|
let totalCost = 0;
|
||||||
|
|
||||||
const types = energySourcesByType(this._data.prefs);
|
const types = energySourcesByType(this._data.prefs);
|
||||||
|
@ -81,6 +82,12 @@ export class HuiEnergySourcesTableCard
|
||||||
const solarColor = computedStyles
|
const solarColor = computedStyles
|
||||||
.getPropertyValue("--energy-solar-color")
|
.getPropertyValue("--energy-solar-color")
|
||||||
.trim();
|
.trim();
|
||||||
|
const batteryFromColor = computedStyles
|
||||||
|
.getPropertyValue("--energy-battery-out-color")
|
||||||
|
.trim();
|
||||||
|
const batteryToColor = computedStyles
|
||||||
|
.getPropertyValue("--energy-battery-in-color")
|
||||||
|
.trim();
|
||||||
const returnColor = computedStyles
|
const returnColor = computedStyles
|
||||||
.getPropertyValue("--energy-grid-return-color")
|
.getPropertyValue("--energy-grid-return-color")
|
||||||
.trim();
|
.trim();
|
||||||
|
@ -190,6 +197,99 @@ export class HuiEnergySourcesTableCard
|
||||||
: ""}
|
: ""}
|
||||||
</tr>`
|
</tr>`
|
||||||
: ""}
|
: ""}
|
||||||
|
${types.battery?.map((source, idx) => {
|
||||||
|
const entityFrom = this.hass.states[source.stat_energy_from];
|
||||||
|
const entityTo = this.hass.states[source.stat_energy_to];
|
||||||
|
const energyFrom =
|
||||||
|
calculateStatisticSumGrowth(
|
||||||
|
this._data!.stats[source.stat_energy_from]
|
||||||
|
) || 0;
|
||||||
|
const energyTo =
|
||||||
|
calculateStatisticSumGrowth(
|
||||||
|
this._data!.stats[source.stat_energy_to]
|
||||||
|
) || 0;
|
||||||
|
totalBattery += energyFrom - energyTo;
|
||||||
|
const fromColor =
|
||||||
|
idx > 0
|
||||||
|
? rgb2hex(
|
||||||
|
lab2rgb(
|
||||||
|
labDarken(rgb2lab(hex2rgb(batteryFromColor)), idx)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: batteryFromColor;
|
||||||
|
const toColor =
|
||||||
|
idx > 0
|
||||||
|
? rgb2hex(
|
||||||
|
lab2rgb(
|
||||||
|
labDarken(rgb2lab(hex2rgb(batteryToColor)), idx)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
: batteryToColor;
|
||||||
|
return html`<tr class="mdc-data-table__row">
|
||||||
|
<td class="mdc-data-table__cell cell-bullet">
|
||||||
|
<div
|
||||||
|
class="bullet"
|
||||||
|
style=${styleMap({
|
||||||
|
borderColor: fromColor,
|
||||||
|
backgroundColor: fromColor + "7F",
|
||||||
|
})}
|
||||||
|
></div>
|
||||||
|
</td>
|
||||||
|
<th class="mdc-data-table__cell" scope="row">
|
||||||
|
${entityFrom
|
||||||
|
? computeStateName(entityFrom)
|
||||||
|
: source.stat_energy_from}
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||||
|
>
|
||||||
|
${formatNumber(energyFrom, this.hass.locale)} kWh
|
||||||
|
</td>
|
||||||
|
${showCosts
|
||||||
|
? html`<td class="mdc-data-table__cell"></td>`
|
||||||
|
: ""}
|
||||||
|
</tr>
|
||||||
|
<tr class="mdc-data-table__row">
|
||||||
|
<td class="mdc-data-table__cell cell-bullet">
|
||||||
|
<div
|
||||||
|
class="bullet"
|
||||||
|
style=${styleMap({
|
||||||
|
borderColor: toColor,
|
||||||
|
backgroundColor: toColor + "7F",
|
||||||
|
})}
|
||||||
|
></div>
|
||||||
|
</td>
|
||||||
|
<th class="mdc-data-table__cell" scope="row">
|
||||||
|
${entityTo
|
||||||
|
? computeStateName(entityTo)
|
||||||
|
: source.stat_energy_from}
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||||
|
>
|
||||||
|
${formatNumber(energyTo * -1, this.hass.locale)} kWh
|
||||||
|
</td>
|
||||||
|
${showCosts
|
||||||
|
? html`<td class="mdc-data-table__cell"></td>`
|
||||||
|
: ""}
|
||||||
|
</tr>`;
|
||||||
|
})}
|
||||||
|
${types.battery
|
||||||
|
? html`<tr class="mdc-data-table__row total">
|
||||||
|
<td class="mdc-data-table__cell"></td>
|
||||||
|
<th class="mdc-data-table__cell" scope="row">
|
||||||
|
Battery total
|
||||||
|
</th>
|
||||||
|
<td
|
||||||
|
class="mdc-data-table__cell mdc-data-table__cell--numeric"
|
||||||
|
>
|
||||||
|
${formatNumber(totalBattery, this.hass.locale)} kWh
|
||||||
|
</td>
|
||||||
|
${showCosts
|
||||||
|
? html`<td class="mdc-data-table__cell"></td>`
|
||||||
|
: ""}
|
||||||
|
</tr>`
|
||||||
|
: ""}
|
||||||
${types.grid?.map(
|
${types.grid?.map(
|
||||||
(source) => html`${source.flow_from.map((flow, idx) => {
|
(source) => html`${source.flow_from.map((flow, idx) => {
|
||||||
const entity = this.hass.states[flow.stat_energy_from];
|
const entity = this.hass.states[flow.stat_energy_from];
|
||||||
|
|
|
@ -244,6 +244,8 @@ export class HuiEnergyUsageGraphCard
|
||||||
to_grid?: string[];
|
to_grid?: string[];
|
||||||
from_grid?: string[];
|
from_grid?: string[];
|
||||||
solar?: string[];
|
solar?: string[];
|
||||||
|
to_battery?: string[];
|
||||||
|
from_battery?: string[];
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
for (const source of energyData.prefs.energy_sources) {
|
for (const source of energyData.prefs.energy_sources) {
|
||||||
|
@ -256,6 +258,17 @@ export class HuiEnergyUsageGraphCard
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (source.type === "battery") {
|
||||||
|
if (statistics.to_battery) {
|
||||||
|
statistics.to_battery.push(source.stat_energy_to);
|
||||||
|
statistics.from_battery!.push(source.stat_energy_from);
|
||||||
|
} else {
|
||||||
|
statistics.to_battery = [source.stat_energy_to];
|
||||||
|
statistics.from_battery = [source.stat_energy_from];
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// grid source
|
// grid source
|
||||||
for (const flowFrom of source.flow_from) {
|
for (const flowFrom of source.flow_from) {
|
||||||
if (statistics.from_grid) {
|
if (statistics.from_grid) {
|
||||||
|
@ -306,21 +319,47 @@ export class HuiEnergyUsageGraphCard
|
||||||
}
|
}
|
||||||
|
|
||||||
const combinedData: {
|
const combinedData: {
|
||||||
[key: string]: { [statId: string]: { [start: string]: number } };
|
to_grid?: { [statId: string]: { [start: string]: number } };
|
||||||
|
to_battery?: { [statId: string]: { [start: string]: number } };
|
||||||
|
from_grid?: { [statId: string]: { [start: string]: number } };
|
||||||
|
used_grid?: { [statId: string]: { [start: string]: number } };
|
||||||
|
used_solar?: { [statId: string]: { [start: string]: number } };
|
||||||
|
used_battery?: { [statId: string]: { [start: string]: number } };
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
const summedData: {
|
||||||
|
to_grid?: { [start: string]: number };
|
||||||
|
from_grid?: { [start: string]: number };
|
||||||
|
to_battery?: { [start: string]: number };
|
||||||
|
from_battery?: { [start: string]: number };
|
||||||
|
solar?: { [start: string]: number };
|
||||||
} = {};
|
} = {};
|
||||||
const summedData: { [key: string]: { [start: string]: number } } = {};
|
|
||||||
|
|
||||||
const computedStyles = getComputedStyle(this);
|
const computedStyles = getComputedStyle(this);
|
||||||
const colors = {
|
const colors = {
|
||||||
to_grid: computedStyles
|
to_grid: computedStyles
|
||||||
.getPropertyValue("--energy-grid-return-color")
|
.getPropertyValue("--energy-grid-return-color")
|
||||||
.trim(),
|
.trim(),
|
||||||
|
to_battery: computedStyles
|
||||||
|
.getPropertyValue("--energy-battery-in-color")
|
||||||
|
.trim(),
|
||||||
from_grid: computedStyles
|
from_grid: computedStyles
|
||||||
.getPropertyValue("--energy-grid-consumption-color")
|
.getPropertyValue("--energy-grid-consumption-color")
|
||||||
.trim(),
|
.trim(),
|
||||||
|
used_grid: computedStyles
|
||||||
|
.getPropertyValue("--energy-grid-consumption-color")
|
||||||
|
.trim(),
|
||||||
used_solar: computedStyles
|
used_solar: computedStyles
|
||||||
.getPropertyValue("--energy-solar-color")
|
.getPropertyValue("--energy-solar-color")
|
||||||
.trim(),
|
.trim(),
|
||||||
|
used_battery: computedStyles
|
||||||
|
.getPropertyValue("--energy-battery-out-color")
|
||||||
|
.trim(),
|
||||||
|
};
|
||||||
|
const labels = {
|
||||||
|
used_grid: "Combined from grid",
|
||||||
|
used_solar: "Consumed solar",
|
||||||
|
used_battery: "Consumed battery",
|
||||||
};
|
};
|
||||||
|
|
||||||
const backgroundColor = computedStyles
|
const backgroundColor = computedStyles
|
||||||
|
@ -328,8 +367,14 @@ export class HuiEnergyUsageGraphCard
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
Object.entries(statistics).forEach(([key, statIds]) => {
|
Object.entries(statistics).forEach(([key, statIds]) => {
|
||||||
const sum = ["solar", "to_grid"].includes(key);
|
const sum = [
|
||||||
const add = key !== "solar";
|
"solar",
|
||||||
|
"to_grid",
|
||||||
|
"from_grid",
|
||||||
|
"to_battery",
|
||||||
|
"from_battery",
|
||||||
|
].includes(key);
|
||||||
|
const add = !["solar", "from_battery"].includes(key);
|
||||||
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) => {
|
||||||
|
@ -374,15 +419,72 @@ export class HuiEnergyUsageGraphCard
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (summedData.to_grid && summedData.solar) {
|
const grid_to_battery = {};
|
||||||
|
const battery_to_grid = {};
|
||||||
|
if ((summedData.to_grid || summedData.to_battery) && summedData.solar) {
|
||||||
const used_solar = {};
|
const used_solar = {};
|
||||||
for (const start of Object.keys(summedData.solar)) {
|
for (const start of Object.keys(summedData.solar)) {
|
||||||
used_solar[start] = Math.max(
|
used_solar[start] =
|
||||||
(summedData.solar[start] || 0) - (summedData.to_grid[start] || 0),
|
(summedData.solar[start] || 0) -
|
||||||
0
|
(summedData.to_grid?.[start] || 0) -
|
||||||
);
|
(summedData.to_battery?.[start] || 0);
|
||||||
|
if (used_solar[start] < 0) {
|
||||||
|
if (summedData.to_battery) {
|
||||||
|
grid_to_battery[start] = used_solar[start] * -1;
|
||||||
|
if (grid_to_battery[start] > (summedData.from_grid?.[start] || 0)) {
|
||||||
|
battery_to_grid[start] = Math.min(
|
||||||
|
0,
|
||||||
|
grid_to_battery[start] - (summedData.from_grid?.[start] || 0)
|
||||||
|
);
|
||||||
|
grid_to_battery[start] = summedData.from_grid?.[start];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
used_solar[start] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
combinedData.used_solar = { used_solar: used_solar };
|
combinedData.used_solar = { used_solar };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summedData.from_battery) {
|
||||||
|
if (summedData.to_grid) {
|
||||||
|
const used_battery = {};
|
||||||
|
for (const start of Object.keys(summedData.from_battery)) {
|
||||||
|
used_battery[start] =
|
||||||
|
(summedData.from_battery![start] || 0) -
|
||||||
|
(battery_to_grid[start] || 0);
|
||||||
|
}
|
||||||
|
combinedData.used_battery = { used_battery };
|
||||||
|
} else {
|
||||||
|
combinedData.used_battery = { used_battery: summedData.from_battery };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (combinedData.from_grid && summedData.to_battery) {
|
||||||
|
const used_grid = {};
|
||||||
|
for (const start of Object.keys(grid_to_battery)) {
|
||||||
|
let noOfSources = 0;
|
||||||
|
let source: string;
|
||||||
|
for (const [key, stats] of Object.entries(combinedData.from_grid)) {
|
||||||
|
if (stats[start]) {
|
||||||
|
source = key;
|
||||||
|
noOfSources++;
|
||||||
|
}
|
||||||
|
if (noOfSources > 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (noOfSources === 1) {
|
||||||
|
combinedData.from_grid[source!][start] -= grid_to_battery[start] || 0;
|
||||||
|
} else {
|
||||||
|
let total_from_grid = 0;
|
||||||
|
Object.values(combinedData.from_grid).forEach((stats) => {
|
||||||
|
total_from_grid += stats[start] || 0;
|
||||||
|
delete stats[start];
|
||||||
|
});
|
||||||
|
used_grid[start] = total_from_grid - (grid_to_battery[start] || 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
combinedData.used_grid = { used_grid };
|
||||||
}
|
}
|
||||||
|
|
||||||
let allKeys: string[] = [];
|
let allKeys: string[] = [];
|
||||||
|
@ -406,12 +508,17 @@ export class HuiEnergyUsageGraphCard
|
||||||
|
|
||||||
data.push({
|
data.push({
|
||||||
label:
|
label:
|
||||||
type === "used_solar"
|
type in labels
|
||||||
? "Consumed solar"
|
? labels[type]
|
||||||
: entity
|
: entity
|
||||||
? computeStateName(entity)
|
? computeStateName(entity)
|
||||||
: statId,
|
: statId,
|
||||||
order: type === "used_solar" ? 0 : idx + 1,
|
order:
|
||||||
|
type === "used_solar"
|
||||||
|
? 0
|
||||||
|
: type === "to_battery"
|
||||||
|
? Object.keys(combinedData).length
|
||||||
|
: idx + 1,
|
||||||
borderColor,
|
borderColor,
|
||||||
backgroundColor: hexBlend(borderColor, backgroundColor, 50),
|
backgroundColor: hexBlend(borderColor, backgroundColor, 50),
|
||||||
stack: "stack",
|
stack: "stack",
|
||||||
|
@ -425,7 +532,10 @@ export class HuiEnergyUsageGraphCard
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
data[0].data.push({
|
data[0].data.push({
|
||||||
x: date.getTime(),
|
x: date.getTime(),
|
||||||
y: value && type === "to_grid" ? -1 * value : value,
|
y:
|
||||||
|
value && ["to_grid", "to_battery"].includes(type)
|
||||||
|
? -1 * value
|
||||||
|
: value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,8 @@ documentContainer.innerHTML = `<custom-style>
|
||||||
--energy-grid-return-color: #673ab7;
|
--energy-grid-return-color: #673ab7;
|
||||||
--energy-solar-color: #ff9800;
|
--energy-solar-color: #ff9800;
|
||||||
--energy-non-fossil-color: #0f9d58;
|
--energy-non-fossil-color: #0f9d58;
|
||||||
|
--energy-battery-out-color: #4db6ac;
|
||||||
|
--energy-battery-in-color: #f06292;
|
||||||
|
|
||||||
/* opacity for dark text on a light background */
|
/* opacity for dark text on a light background */
|
||||||
--dark-divider-opacity: 0.12;
|
--dark-divider-opacity: 0.12;
|
||||||
|
|
|
@ -1046,6 +1046,11 @@
|
||||||
"stat_return_to_grid": "Solar energy returned to the grid",
|
"stat_return_to_grid": "Solar energy returned to the grid",
|
||||||
"stat_predicted_production": "Prediction of your solar energy production"
|
"stat_predicted_production": "Prediction of your solar energy production"
|
||||||
},
|
},
|
||||||
|
"battery": {
|
||||||
|
"title": "Home Battery Storage",
|
||||||
|
"sub": "If you have a battery system, you can configure it to monitor how much energy was stored and used from your battery.",
|
||||||
|
"learn_more": "More information on how to get started."
|
||||||
|
},
|
||||||
"device_consumption": {
|
"device_consumption": {
|
||||||
"title": "Individual devices",
|
"title": "Individual devices",
|
||||||
"sub": "Tracking the energy usage of individual devices allows Home Assistant to break down your energy usage by device.",
|
"sub": "Tracking the energy usage of individual devices allows Home Assistant to break down your energy usage by device.",
|
||||||
|
|
Loading…
Reference in New Issue