Add self-sufficiency gauge card (#15704)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
Till 2023-06-19 13:59:20 +02:00 committed by GitHub
parent cdd29c8bf7
commit fa75b18a6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 281 additions and 4 deletions

View File

@ -141,6 +141,11 @@ export class EnergyStrategy {
view_layout: { position: "sidebar" },
collection_key: "energy_dashboard",
});
view.cards!.push({
type: "energy-self-sufficiency-gauge",
view_layout: { position: "sidebar" },
collection_key: "energy_dashboard",
});
}
// Only include if we have a grid

View File

@ -0,0 +1,257 @@
import "@lrnwebcomponents/simple-tooltip/simple-tooltip";
import { mdiInformation } from "@mdi/js";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import "../../../../components/ha-card";
import "../../../../components/ha-gauge";
import "../../../../components/ha-svg-icon";
import {
EnergyData,
energySourcesByType,
getEnergyDataCollection,
} from "../../../../data/energy";
import { calculateStatisticsSumGrowth } from "../../../../data/recorder";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import type { HomeAssistant } from "../../../../types";
import type { LovelaceCard } from "../../types";
import { severityMap } from "../hui-gauge-card";
import type { EnergySelfSufficiencyGaugeCardConfig } from "../types";
@customElement("hui-energy-self-sufficiency-gauge-card")
class HuiEnergySelfSufficiencyGaugeCard
extends SubscribeMixin(LitElement)
implements LovelaceCard
{
@property({ attribute: false }) public hass?: HomeAssistant;
@state() private _config?: EnergySelfSufficiencyGaugeCardConfig;
@state() private _data?: EnergyData;
protected hassSubscribeRequiredHostProps = ["_config"];
public hassSubscribe(): UnsubscribeFunc[] {
return [
getEnergyDataCollection(this.hass!, {
key: this._config?.collection_key,
}).subscribe((data) => {
this._data = data;
}),
];
}
public getCardSize(): number {
return 4;
}
public setConfig(config: EnergySelfSufficiencyGaugeCardConfig): void {
this._config = config;
}
protected render() {
if (!this._config || !this.hass) {
return nothing;
}
if (!this._data) {
return html`${this.hass.localize(
"ui.panel.lovelace.cards.energy.loading"
)}`;
}
const prefs = this._data.prefs;
const types = energySourcesByType(prefs);
// The strategy only includes this card if we have a grid.
const hasConsumption = true;
const hasSolarProduction = types.solar !== undefined;
const hasBattery = types.battery !== undefined;
const hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0;
const totalFromGrid =
calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
) ?? 0;
let totalSolarProduction: number | null = null;
if (hasSolarProduction) {
totalSolarProduction =
calculateStatisticsSumGrowth(
this._data.stats,
types.solar!.map((source) => source.stat_energy_from)
) || 0;
}
let totalBatteryIn: number | null = null;
let totalBatteryOut: number | null = null;
if (hasBattery) {
totalBatteryIn =
calculateStatisticsSumGrowth(
this._data.stats,
types.battery!.map((source) => source.stat_energy_to)
) || 0;
totalBatteryOut =
calculateStatisticsSumGrowth(
this._data.stats,
types.battery!.map((source) => source.stat_energy_from)
) || 0;
}
let returnedToGrid: number | null = null;
if (hasReturnToGrid) {
returnedToGrid =
calculateStatisticsSumGrowth(
this._data.stats,
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
) || 0;
}
let solarConsumption: number | null = null;
if (hasSolarProduction) {
solarConsumption =
(totalSolarProduction || 0) -
(returnedToGrid || 0) -
(totalBatteryIn || 0);
}
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 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 value: number | undefined;
if (
totalFromGrid !== null &&
totalHomeConsumption !== null &&
totalHomeConsumption > 0
) {
value = (1 - totalFromGrid / totalHomeConsumption) * 100;
}
return html`
<ha-card>
${value !== undefined
? html`
<ha-svg-icon id="info" .path=${mdiInformation}></ha-svg-icon>
<simple-tooltip animation-delay="0" for="info" position="left">
<span>
${this.hass.localize(
"ui.panel.lovelace.cards.energy.self_sufficiency_gauge.card_indicates_self_sufficiency_quota"
)}
</span>
</simple-tooltip>
<ha-gauge
min="0"
max="100"
.value=${value}
.locale=${this.hass.locale}
label="%"
style=${styleMap({
"--gauge-color": this._computeSeverity(value),
})}
></ha-gauge>
<div class="name">
${this.hass.localize(
"ui.panel.lovelace.cards.energy.self_sufficiency_gauge.self_sufficiency_quota"
)}
</div>
`
: this.hass.localize(
"ui.panel.lovelace.cards.energy.self_sufficiency_gauge.self_sufficiency_could_not_calc"
)}
</ha-card>
`;
}
private _computeSeverity(numberValue: number): string {
if (numberValue > 75) {
return severityMap.green;
}
if (numberValue < 50) {
return severityMap.yellow;
}
return severityMap.normal;
}
static get styles(): CSSResultGroup {
return css`
ha-card {
height: 100%;
overflow: hidden;
padding: 16px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
box-sizing: border-box;
}
ha-gauge {
width: 100%;
max-width: 250px;
direction: ltr;
}
.name {
text-align: center;
line-height: initial;
color: var(--primary-text-color);
width: 100%;
font-size: 15px;
margin-top: 8px;
}
ha-svg-icon {
position: absolute;
right: 4px;
top: 4px;
color: var(--secondary-text-color);
}
simple-tooltip > span {
font-size: 12px;
line-height: 12px;
}
simple-tooltip {
width: 80%;
max-width: 250px;
top: 8px !important;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-energy-self-sufficiency-gauge-card": HuiEnergySelfSufficiencyGaugeCard;
}
}

View File

@ -68,10 +68,11 @@ class HuiEnergySolarGaugeCard
return nothing;
}
const totalSolarProduction = calculateStatisticsSumGrowth(
this._data.stats,
types.solar.map((source) => source.stat_energy_from)
);
const totalSolarProduction =
calculateStatisticsSumGrowth(
this._data.stats,
types.solar.map((source) => source.stat_energy_from)
) || 0;
const productionReturnedToGrid = calculateStatisticsSumGrowth(
this._data.stats,

View File

@ -159,6 +159,13 @@ export interface EnergySolarGaugeCardConfig extends LovelaceCardConfig {
collection_key?: string;
}
export interface EnergySelfSufficiencyGaugeCardConfig
extends LovelaceCardConfig {
type: "energy-self-sufficiency-gauge";
title?: string;
collection_key?: string;
}
export interface EnergyGridGaugeCardConfig extends LovelaceCardConfig {
type: "energy-grid-result-gauge";
title?: string;

View File

@ -53,6 +53,8 @@ const LAZY_LOAD_TYPES = {
import("../cards/energy/hui-energy-grid-neutrality-gauge-card"),
"energy-solar-consumed-gauge": () =>
import("../cards/energy/hui-energy-solar-consumed-gauge-card"),
"energy-self-sufficiency-gauge": () =>
import("../cards/energy/hui-energy-self-sufficiency-gauge-card"),
"energy-solar-graph": () =>
import("../cards/energy/hui-energy-solar-graph-card"),
"energy-sources-table": () =>

5
src/translations/en.json Executable file → Normal file
View File

@ -4219,6 +4219,11 @@
"not_produced_solar_energy": "You have not produced any solar energy",
"self_consumed_solar_could_not_calc": "Self-consumed solar energy couldn't be calculated"
},
"self_sufficiency_gauge": {
"card_indicates_self_sufficiency_quota": "This card indicates how self-sufficient your home is.",
"self_sufficiency_quota": "Self-sufficiency quota",
"self_sufficiency_could_not_calc": "Self-sufficiency quota couldn't be calculated"
},
"grid_neutrality_gauge": {
"energy_dependency": "This card indicates your net energy usage.",
"color_explain": "If the needle is in the purple, you returned more energy to the grid than you consumed from it. If it's in the blue, you consumed more energy from the grid than you returned.",