mirror of
https://github.com/home-assistant/frontend.git
synced 2025-04-25 05:47:20 +00:00
Update energy dashboard (#9624)
This commit is contained in:
parent
0c0091375c
commit
73b9b87ef3
@ -272,7 +272,7 @@ export default class HaChartBase extends LitElement {
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
margin-right: 4px;
|
||||
margin-right: 6px;
|
||||
width: 16px;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
@ -280,9 +280,10 @@ export default class HaChartBase extends LitElement {
|
||||
.chartTooltip .bullet {
|
||||
align-self: baseline;
|
||||
}
|
||||
:host([rtl]) .chartLegend .bullet,
|
||||
:host([rtl]) .chartTooltip .bullet {
|
||||
margin-right: inherit;
|
||||
margin-left: 4px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.chartTooltip {
|
||||
padding: 8px;
|
||||
@ -314,6 +315,7 @@ export default class HaChartBase extends LitElement {
|
||||
white-space: pre-line;
|
||||
align-items: center;
|
||||
line-height: 16px;
|
||||
padding: 4px 0;
|
||||
}
|
||||
.chartTooltip .title {
|
||||
text-align: center;
|
||||
|
@ -85,7 +85,8 @@ export class EnergyStrategy {
|
||||
// Only include if we have a grid.
|
||||
if (hasGrid) {
|
||||
view.cards!.push({
|
||||
type: "energy-usage",
|
||||
title: "Energy distribution",
|
||||
type: "energy-distribution",
|
||||
prefs: energyPrefs,
|
||||
view_layout: { position: "sidebar" },
|
||||
});
|
||||
|
@ -1,23 +1,23 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { round } from "../../../common/number/round";
|
||||
import { subscribeOne } from "../../../common/util/subscribe-one";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-gauge";
|
||||
import { getConfigEntries } from "../../../data/config_entries";
|
||||
import { energySourcesByType } from "../../../data/energy";
|
||||
import { subscribeEntityRegistry } from "../../../data/entity_registry";
|
||||
import { round } from "../../../../common/number/round";
|
||||
import { subscribeOne } from "../../../../common/util/subscribe-one";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-gauge";
|
||||
import { getConfigEntries } from "../../../../data/config_entries";
|
||||
import { energySourcesByType } from "../../../../data/energy";
|
||||
import { subscribeEntityRegistry } from "../../../../data/entity_registry";
|
||||
import {
|
||||
calculateStatisticsSumGrowth,
|
||||
fetchStatistics,
|
||||
Statistics,
|
||||
} from "../../../data/history";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { createEntityNotFoundWarning } from "../components/hui-warning";
|
||||
import type { LovelaceCard } from "../types";
|
||||
import { severityMap } from "./hui-gauge-card";
|
||||
import type { EnergyCarbonGaugeCardConfig } from "./types";
|
||||
} from "../../../../data/history";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { createEntityNotFoundWarning } from "../../components/hui-warning";
|
||||
import type { LovelaceCard } from "../../types";
|
||||
import { severityMap } from "../hui-gauge-card";
|
||||
import type { EnergyCarbonGaugeCardConfig } from "../types";
|
||||
|
||||
@customElement("hui-energy-carbon-consumed-gauge-card")
|
||||
class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard {
|
||||
@ -103,7 +103,7 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard {
|
||||
(totalSolarProduction || 0) -
|
||||
(totalGridReturned || 0);
|
||||
|
||||
value = round((highCarbonEnergy / totalEnergyConsumed) * 100);
|
||||
value = round((1 - highCarbonEnergy / totalEnergyConsumed) * 100);
|
||||
}
|
||||
|
||||
return html`
|
||||
@ -116,23 +116,23 @@ class HuiEnergyCarbonGaugeCard extends LitElement implements LovelaceCard {
|
||||
.locale=${this.hass!.locale}
|
||||
label="%"
|
||||
style=${styleMap({
|
||||
"--gauge-color": this._computeSeverity(64),
|
||||
"--gauge-color": this._computeSeverity(value),
|
||||
})}
|
||||
></ha-gauge>
|
||||
<div class="name">High-carbon energy consumed</div>`
|
||||
: html`Consumed high-carbon energy couldn't be calculated`}
|
||||
<div class="name">Non-fossil energy consumed</div>`
|
||||
: html`Consumed non-fossil energy couldn't be calculated`}
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _computeSeverity(numberValue: number): string {
|
||||
if (numberValue > 50) {
|
||||
if (numberValue < 10) {
|
||||
return severityMap.red;
|
||||
}
|
||||
if (numberValue > 30) {
|
||||
if (numberValue < 30) {
|
||||
return severityMap.yellow;
|
||||
}
|
||||
if (numberValue < 10) {
|
||||
if (numberValue > 75) {
|
||||
return severityMap.green;
|
||||
}
|
||||
return severityMap.normal;
|
@ -9,23 +9,23 @@ import {
|
||||
unsafeCSS,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import { round } from "../../../common/number/round";
|
||||
import "../../../components/chart/statistics-chart";
|
||||
import "../../../components/ha-card";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import { round } from "../../../../common/number/round";
|
||||
import "../../../../components/chart/statistics-chart";
|
||||
import "../../../../components/ha-card";
|
||||
import {
|
||||
EnergyInfo,
|
||||
getEnergyInfo,
|
||||
GridSourceTypeEnergyPreference,
|
||||
} from "../../../data/energy";
|
||||
} from "../../../../data/energy";
|
||||
import {
|
||||
calculateStatisticSumGrowth,
|
||||
fetchStatistics,
|
||||
Statistics,
|
||||
} from "../../../data/history";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { EnergyDevicesGraphCardConfig } from "./types";
|
||||
} from "../../../../data/history";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergyDevicesGraphCardConfig } from "../types";
|
||||
|
||||
@customElement("hui-energy-costs-table-card")
|
||||
export class HuiEnergyCostsTableCard
|
@ -14,18 +14,18 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { getColorByIndex } from "../../../common/color/colors";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/chart/ha-chart-base";
|
||||
import "../../../components/ha-card";
|
||||
import { getColorByIndex } from "../../../../common/color/colors";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import "../../../../components/ha-card";
|
||||
import {
|
||||
calculateStatisticSumGrowth,
|
||||
fetchStatistics,
|
||||
Statistics,
|
||||
} from "../../../data/history";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { EnergyDevicesGraphCardConfig } from "./types";
|
||||
} from "../../../../data/history";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergyDevicesGraphCardConfig } from "../types";
|
||||
|
||||
@customElement("hui-energy-devices-graph-card")
|
||||
export class HuiEnergyDevicesGraphCard
|
505
src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts
Normal file
505
src/panels/lovelace/cards/energy/hui-energy-distribution-card.ts
Normal file
@ -0,0 +1,505 @@
|
||||
import {
|
||||
mdiArrowLeft,
|
||||
mdiArrowRight,
|
||||
mdiHome,
|
||||
mdiLeaf,
|
||||
mdiSolarPower,
|
||||
mdiTransmissionTower,
|
||||
} from "@mdi/js";
|
||||
import { css, html, LitElement, svg } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { round } from "../../../../common/number/round";
|
||||
import { subscribeOne } from "../../../../common/util/subscribe-one";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { getConfigEntries } from "../../../../data/config_entries";
|
||||
import { energySourcesByType } from "../../../../data/energy";
|
||||
import { subscribeEntityRegistry } from "../../../../data/entity_registry";
|
||||
import {
|
||||
calculateStatisticsSumGrowth,
|
||||
fetchStatistics,
|
||||
Statistics,
|
||||
} from "../../../../data/history";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergyDistributionCardConfig } from "../types";
|
||||
|
||||
const CIRCLE_CIRCUMFERENCE = 238.76104;
|
||||
|
||||
@customElement("hui-energy-distribution-card")
|
||||
class HuiEnergyDistrubutionCard extends LitElement implements LovelaceCard {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _config?: EnergyDistributionCardConfig;
|
||||
|
||||
@state() private _stats?: Statistics;
|
||||
|
||||
@state() private _co2SignalEntity?: string;
|
||||
|
||||
private _fetching = false;
|
||||
|
||||
public setConfig(config: EnergyDistributionCardConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public getCardSize(): Promise<number> | number {
|
||||
return 3;
|
||||
}
|
||||
|
||||
public willUpdate(changedProps) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this._fetching && !this._stats) {
|
||||
this._fetching = true;
|
||||
Promise.all([this._getStatistics(), this._fetchCO2SignalEntity()]).then(
|
||||
() => {
|
||||
this._fetching = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
if (!this._stats) {
|
||||
return html`Loading…`;
|
||||
}
|
||||
|
||||
const prefs = this._config!.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 hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0;
|
||||
|
||||
const totalGridConsumption =
|
||||
calculateStatisticsSumGrowth(
|
||||
this._stats,
|
||||
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
|
||||
) ?? 0;
|
||||
|
||||
let totalSolarProduction: number | null = null;
|
||||
|
||||
if (hasSolarProduction) {
|
||||
totalSolarProduction = calculateStatisticsSumGrowth(
|
||||
this._stats,
|
||||
types.solar!.map((source) => source.stat_energy_from)
|
||||
);
|
||||
}
|
||||
|
||||
let productionReturnedToGrid: number | null = null;
|
||||
|
||||
if (hasReturnToGrid) {
|
||||
productionReturnedToGrid = calculateStatisticsSumGrowth(
|
||||
this._stats,
|
||||
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
|
||||
);
|
||||
}
|
||||
|
||||
// total consumption = consumption_from_grid + solar_production - return_to_grid
|
||||
|
||||
let co2percentage: number | undefined;
|
||||
|
||||
if (this._co2SignalEntity) {
|
||||
const co2State = this.hass.states[this._co2SignalEntity];
|
||||
if (co2State) {
|
||||
co2percentage = Number(co2State.state);
|
||||
if (isNaN(co2percentage)) {
|
||||
co2percentage = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const totalConsumption =
|
||||
totalGridConsumption +
|
||||
(totalSolarProduction || 0) -
|
||||
(productionReturnedToGrid || 0);
|
||||
|
||||
let homeSolarCircumference: number | undefined;
|
||||
if (hasSolarProduction) {
|
||||
const homePctSolar =
|
||||
((totalSolarProduction || 0) - (productionReturnedToGrid || 0)) /
|
||||
totalConsumption;
|
||||
homeSolarCircumference = CIRCLE_CIRCUMFERENCE * homePctSolar;
|
||||
}
|
||||
|
||||
let lowCarbonConsumption: number | undefined;
|
||||
|
||||
let homeLowCarbonCircumference: number | undefined;
|
||||
let homeHighCarbonCircumference: number | undefined;
|
||||
if (co2percentage !== undefined) {
|
||||
const gridPctHighCarbon = co2percentage / 100;
|
||||
|
||||
lowCarbonConsumption =
|
||||
totalGridConsumption - totalGridConsumption * gridPctHighCarbon;
|
||||
|
||||
const homePctGridHighCarbon =
|
||||
(gridPctHighCarbon * totalGridConsumption) / totalConsumption;
|
||||
|
||||
homeHighCarbonCircumference =
|
||||
CIRCLE_CIRCUMFERENCE * homePctGridHighCarbon;
|
||||
|
||||
homeLowCarbonCircumference =
|
||||
CIRCLE_CIRCUMFERENCE -
|
||||
(homeSolarCircumference || 0) -
|
||||
homeHighCarbonCircumference;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-card .header=${this._config.title}>
|
||||
<div class="card-content">
|
||||
${lowCarbonConsumption !== undefined || hasSolarProduction
|
||||
? html`<div class="row">
|
||||
${lowCarbonConsumption === undefined
|
||||
? html`<div class="spacer"></div>`
|
||||
: html`
|
||||
<div class="circle-container low-carbon">
|
||||
<span class="label">Non-fossil</span>
|
||||
<div class="circle">
|
||||
<ha-svg-icon .path="${mdiLeaf}"></ha-svg-icon>
|
||||
${round(lowCarbonConsumption, 1)} kWh
|
||||
</div>
|
||||
<svg width="80" height="30">
|
||||
<line x1="40" y1="0" x2="40" y2="30"></line>
|
||||
</svg>
|
||||
</div>
|
||||
`}
|
||||
${hasSolarProduction
|
||||
? html`<div class="circle-container solar">
|
||||
<span class="label">Solar</span>
|
||||
<div class="circle">
|
||||
<ha-svg-icon .path="${mdiSolarPower}"></ha-svg-icon>
|
||||
${round(totalSolarProduction || 0, 1)} kWh
|
||||
</div>
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="spacer"></div>
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="row">
|
||||
<div class="circle-container grid">
|
||||
<div class="circle">
|
||||
<ha-svg-icon .path="${mdiTransmissionTower}"></ha-svg-icon>
|
||||
<span class="consumption">
|
||||
${hasReturnToGrid
|
||||
? html`<ha-svg-icon
|
||||
class="small"
|
||||
.path=${mdiArrowRight}
|
||||
></ha-svg-icon>`
|
||||
: ""}${round(totalGridConsumption, 1)}
|
||||
kWh
|
||||
</span>
|
||||
${productionReturnedToGrid
|
||||
? html`<span class="return">
|
||||
<ha-svg-icon
|
||||
class="small"
|
||||
.path=${mdiArrowLeft}
|
||||
></ha-svg-icon
|
||||
>${round(productionReturnedToGrid, 1)} kWh
|
||||
</span>`
|
||||
: ""}
|
||||
</div>
|
||||
<span class="label">Grid</span>
|
||||
</div>
|
||||
<div class="circle-container home">
|
||||
<div
|
||||
class="circle ${classMap({
|
||||
border:
|
||||
homeSolarCircumference === undefined &&
|
||||
homeLowCarbonCircumference === undefined,
|
||||
})}"
|
||||
>
|
||||
<ha-svg-icon .path="${mdiHome}"></ha-svg-icon>
|
||||
${round(totalConsumption, 1)} kWh
|
||||
${homeSolarCircumference !== undefined ||
|
||||
homeLowCarbonCircumference !== undefined
|
||||
? html`<svg>
|
||||
${homeSolarCircumference !== undefined
|
||||
? svg`
|
||||
<circle
|
||||
class="solar"
|
||||
cx="40"
|
||||
cy="40"
|
||||
r="38"
|
||||
stroke-dasharray="${homeSolarCircumference} ${
|
||||
CIRCLE_CIRCUMFERENCE - homeSolarCircumference
|
||||
}"
|
||||
shape-rendering="geometricPrecision"
|
||||
stroke-dashoffset="0"
|
||||
/>`
|
||||
: ""}
|
||||
${homeHighCarbonCircumference
|
||||
? svg`
|
||||
<circle
|
||||
class="low-carbon"
|
||||
cx="40"
|
||||
cy="40"
|
||||
r="38"
|
||||
stroke-dasharray="${homeLowCarbonCircumference} ${
|
||||
CIRCLE_CIRCUMFERENCE - homeLowCarbonCircumference!
|
||||
}"
|
||||
stroke-dashoffset="${
|
||||
((homeSolarCircumference || 0) +
|
||||
homeHighCarbonCircumference!) *
|
||||
-1
|
||||
}"
|
||||
shape-rendering="geometricPrecision"
|
||||
/>`
|
||||
: ""}
|
||||
<circle
|
||||
class="grid"
|
||||
cx="40"
|
||||
cy="40"
|
||||
r="38"
|
||||
stroke-dasharray="${homeHighCarbonCircumference ??
|
||||
CIRCLE_CIRCUMFERENCE -
|
||||
homeSolarCircumference!} ${homeHighCarbonCircumference
|
||||
? CIRCLE_CIRCUMFERENCE - homeHighCarbonCircumference
|
||||
: homeSolarCircumference}"
|
||||
stroke-dashoffset="${(homeSolarCircumference || 0) *
|
||||
-1}"
|
||||
shape-rendering="geometricPrecision"
|
||||
/>
|
||||
</svg>`
|
||||
: ""}
|
||||
</div>
|
||||
<span class="label">Home</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lines">
|
||||
<svg
|
||||
viewBox="0 0 100 100"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
${productionReturnedToGrid && hasSolarProduction
|
||||
? svg`<path
|
||||
class="return"
|
||||
d="M50,0 v20 c0,40 -10,35 -65,35 h20"
|
||||
vector-effect="non-scaling-stroke"
|
||||
></path>`
|
||||
: ""}
|
||||
${totalSolarProduction
|
||||
? svg`<path
|
||||
class="solar"
|
||||
d="M50,0 v20 c0,40 10,35 65,35 h20"
|
||||
vector-effect="non-scaling-stroke"
|
||||
></path>`
|
||||
: ""}
|
||||
${totalGridConsumption
|
||||
? svg`<path
|
||||
class="grid"
|
||||
d="M0,55 H100"
|
||||
vector-effect="non-scaling-stroke"
|
||||
></path>`
|
||||
: ""}
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 styles = css`
|
||||
:host {
|
||||
--mdc-icon-size: 24px;
|
||||
}
|
||||
.card-content {
|
||||
position: relative;
|
||||
}
|
||||
.lines {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 146px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0 16px 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.lines svg {
|
||||
width: calc(100% - 160px);
|
||||
height: 100%;
|
||||
max-width: 340px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.circle-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.circle-container.solar {
|
||||
height: 130px;
|
||||
}
|
||||
.spacer {
|
||||
width: 80px;
|
||||
height: 30px;
|
||||
}
|
||||
.circle {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
position: relative;
|
||||
}
|
||||
ha-svg-icon {
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
ha-svg-icon.small {
|
||||
--mdc-icon-size: 12px;
|
||||
}
|
||||
.label {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
line,
|
||||
path {
|
||||
stroke: var(--primary-text-color);
|
||||
stroke-width: 1;
|
||||
fill: none;
|
||||
}
|
||||
.circle svg {
|
||||
position: absolute;
|
||||
fill: none;
|
||||
stroke-width: 4px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.circle svg circle {
|
||||
animation: rotate-in 0.2s ease-in;
|
||||
}
|
||||
.low-carbon line {
|
||||
stroke: #0f9d58;
|
||||
}
|
||||
.low-carbon .circle {
|
||||
border-color: #0f9d58;
|
||||
}
|
||||
.low-carbon ha-svg-icon {
|
||||
color: #0f9d58;
|
||||
}
|
||||
.solar .circle {
|
||||
border-color: #ff9800;
|
||||
}
|
||||
path.solar,
|
||||
circle.solar {
|
||||
stroke: #ff9800;
|
||||
}
|
||||
circle.low-carbon {
|
||||
stroke: #0f9d58;
|
||||
}
|
||||
circle.return,
|
||||
path.return {
|
||||
stroke: #673ab7;
|
||||
}
|
||||
.return {
|
||||
color: #673ab7;
|
||||
}
|
||||
.grid .circle {
|
||||
border-color: #126a9a;
|
||||
}
|
||||
.consumption {
|
||||
color: #126a9a;
|
||||
}
|
||||
circle.grid,
|
||||
path.grid {
|
||||
stroke: #126a9a;
|
||||
}
|
||||
.home .circle {
|
||||
border: none;
|
||||
}
|
||||
.home .circle.border {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
@keyframes rotate-in {
|
||||
from {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-energy-distribution-card": HuiEnergyDistrubutionCard;
|
||||
}
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { round } from "../../../common/number/round";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-gauge";
|
||||
import { energySourcesByType } from "../../../data/energy";
|
||||
import { round } from "../../../../common/number/round";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-gauge";
|
||||
import { energySourcesByType } from "../../../../data/energy";
|
||||
import {
|
||||
calculateStatisticsSumGrowth,
|
||||
fetchStatistics,
|
||||
Statistics,
|
||||
} from "../../../data/history";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCard } from "../types";
|
||||
import { severityMap } from "./hui-gauge-card";
|
||||
import type { EnergySolarGaugeCardConfig } from "./types";
|
||||
} from "../../../../data/history";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { LovelaceCard } from "../../types";
|
||||
import { severityMap } from "../hui-gauge-card";
|
||||
import type { EnergySolarGaugeCardConfig } from "../types";
|
||||
|
||||
@customElement("hui-energy-solar-consumed-gauge-card")
|
||||
class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard {
|
||||
@ -77,7 +77,7 @@ class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard {
|
||||
.locale=${this.hass!.locale}
|
||||
label="%"
|
||||
style=${styleMap({
|
||||
"--gauge-color": this._computeSeverity(64),
|
||||
"--gauge-color": this._computeSeverity(value),
|
||||
})}
|
||||
></ha-gauge>
|
||||
<div class="name">Self consumed solar energy</div>`
|
||||
@ -87,9 +87,12 @@ class HuiEnergySolarGaugeCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
private _computeSeverity(numberValue: number): string {
|
||||
if (numberValue > 50) {
|
||||
if (numberValue > 75) {
|
||||
return severityMap.green;
|
||||
}
|
||||
if (numberValue < 50) {
|
||||
return severityMap.yellow;
|
||||
}
|
||||
return severityMap.normal;
|
||||
}
|
||||
|
@ -8,31 +8,31 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../../components/ha-card";
|
||||
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { EnergySolarGraphCardConfig } from "./types";
|
||||
import { fetchStatistics, Statistics } from "../../../data/history";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergySolarGraphCardConfig } from "../types";
|
||||
import { fetchStatistics, Statistics } from "../../../../data/history";
|
||||
import {
|
||||
hex2rgb,
|
||||
lab2rgb,
|
||||
rgb2hex,
|
||||
rgb2lab,
|
||||
} from "../../../common/color/convert-color";
|
||||
import { labDarken } from "../../../common/color/lab";
|
||||
import { SolarSourceTypeEnergyPreference } from "../../../data/energy";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
} from "../../../../common/color/convert-color";
|
||||
import { labDarken } from "../../../../common/color/lab";
|
||||
import { SolarSourceTypeEnergyPreference } from "../../../../data/energy";
|
||||
import { isComponentLoaded } from "../../../../common/config/is_component_loaded";
|
||||
import {
|
||||
ForecastSolarForecast,
|
||||
getForecastSolarForecasts,
|
||||
} from "../../../data/forecast_solar";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/chart/ha-chart-base";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-formfield";
|
||||
} from "../../../../data/forecast_solar";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-formfield";
|
||||
|
||||
const SOLAR_COLOR = { border: "#FF9800", background: "#ffcb80" };
|
||||
const SOLAR_COLOR = "#FF9800";
|
||||
|
||||
@customElement("hui-energy-solar-graph-card")
|
||||
export class HuiEnergySolarGraphCard
|
||||
@ -123,17 +123,11 @@ export class HuiEnergySolarGraphCard
|
||||
"has-header": !!this._config.title,
|
||||
})}"
|
||||
>
|
||||
<ha-formfield label="Show all forecast data"
|
||||
><ha-switch
|
||||
.checked=${this._showAllForecastData}
|
||||
@change=${this._showAllForecastChanged}
|
||||
></ha-switch
|
||||
></ha-formfield>
|
||||
${this._chartData
|
||||
? html`<ha-chart-base
|
||||
.data=${this._chartData}
|
||||
.options=${this._chartOptions}
|
||||
chart-type="line"
|
||||
chart-type="bar"
|
||||
></ha-chart-base>`
|
||||
: ""}
|
||||
</div>
|
||||
@ -142,12 +136,18 @@ export class HuiEnergySolarGraphCard
|
||||
}
|
||||
|
||||
private _createOptions() {
|
||||
const startDate = new Date();
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
const startTime = startDate.getTime();
|
||||
|
||||
this._chartOptions = {
|
||||
parsing: false,
|
||||
animation: false,
|
||||
scales: {
|
||||
x: {
|
||||
type: "time",
|
||||
suggestedMin: startTime,
|
||||
suggestedMax: startTime + 24 * 60 * 60 * 1000,
|
||||
adapters: {
|
||||
date: {
|
||||
locale: this.hass.locale,
|
||||
@ -168,9 +168,14 @@ export class HuiEnergySolarGraphCard
|
||||
time: {
|
||||
tooltipFormat: "datetimeseconds",
|
||||
},
|
||||
offset: true,
|
||||
},
|
||||
y: {
|
||||
type: "linear",
|
||||
title: {
|
||||
display: true,
|
||||
text: "kWh",
|
||||
},
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
},
|
||||
@ -199,9 +204,10 @@ export class HuiEnergySolarGraphCard
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0.4,
|
||||
tension: 0.3,
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
bar: { borderWidth: 1.5 },
|
||||
point: {
|
||||
hitRadius: 5,
|
||||
},
|
||||
@ -252,7 +258,7 @@ export class HuiEnergySolarGraphCard
|
||||
) as SolarSourceTypeEnergyPreference[];
|
||||
|
||||
const statisticsData = Object.values(this._data!);
|
||||
const datasets: ChartDataset<"line">[] = [];
|
||||
const datasets: ChartDataset<"bar">[] = [];
|
||||
let endTime: Date;
|
||||
|
||||
if (statisticsData.length === 0) {
|
||||
@ -272,22 +278,18 @@ export class HuiEnergySolarGraphCard
|
||||
}
|
||||
|
||||
solarSources.forEach((source, idx) => {
|
||||
const data: ChartDataset<"line">[] = [];
|
||||
const data: ChartDataset<"bar" | "line">[] = [];
|
||||
const entity = this.hass.states[source.stat_energy_from];
|
||||
|
||||
const borderColor =
|
||||
idx > 0
|
||||
? rgb2hex(
|
||||
lab2rgb(labDarken(rgb2lab(hex2rgb(SOLAR_COLOR.border)), idx))
|
||||
)
|
||||
: SOLAR_COLOR.border;
|
||||
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(SOLAR_COLOR)), idx)))
|
||||
: SOLAR_COLOR;
|
||||
|
||||
data.push({
|
||||
label: `Production ${
|
||||
entity ? computeStateName(entity) : source.stat_energy_from
|
||||
}`,
|
||||
fill: true,
|
||||
stepped: false,
|
||||
borderColor: borderColor,
|
||||
backgroundColor: borderColor + "7F",
|
||||
data: [],
|
||||
@ -343,6 +345,7 @@ export class HuiEnergySolarGraphCard
|
||||
|
||||
if (forecastsData) {
|
||||
const forecast: ChartDataset<"line"> = {
|
||||
type: "line",
|
||||
label: `Forecast ${
|
||||
entity ? computeStateName(entity) : source.stat_energy_from
|
||||
}`,
|
||||
@ -382,11 +385,6 @@ export class HuiEnergySolarGraphCard
|
||||
};
|
||||
}
|
||||
|
||||
private _showAllForecastChanged(ev) {
|
||||
this._showAllForecastData = ev.target.checked;
|
||||
this._renderChart();
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-card {
|
@ -1,21 +1,21 @@
|
||||
import { mdiCashMultiple, mdiSolarPower } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import {
|
||||
energySourcesByType,
|
||||
GridSourceTypeEnergyPreference,
|
||||
SolarSourceTypeEnergyPreference,
|
||||
} from "../../../data/energy";
|
||||
} from "../../../../data/energy";
|
||||
import {
|
||||
calculateStatisticSumGrowth,
|
||||
fetchStatistics,
|
||||
Statistics,
|
||||
} from "../../../data/history";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { EnergySummaryCardConfig } from "./types";
|
||||
import "../../../components/ha-card";
|
||||
} from "../../../../data/history";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergySummaryCardConfig } from "../types";
|
||||
import "../../../../components/ha-card";
|
||||
|
||||
const renderSumStatHelper = (
|
||||
data: Statistics,
|
@ -8,33 +8,28 @@ import {
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../../components/ha-card";
|
||||
import { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { EnergySummaryGraphCardConfig } from "./types";
|
||||
import { fetchStatistics, Statistics } from "../../../data/history";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { LovelaceCard } from "../../types";
|
||||
import { EnergySummaryGraphCardConfig } from "../types";
|
||||
import { fetchStatistics, Statistics } from "../../../../data/history";
|
||||
import {
|
||||
hex2rgb,
|
||||
lab2rgb,
|
||||
rgb2hex,
|
||||
rgb2lab,
|
||||
} from "../../../common/color/convert-color";
|
||||
import { labDarken } from "../../../common/color/lab";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/chart/ha-chart-base";
|
||||
import { round } from "../../../common/number/round";
|
||||
} from "../../../../common/color/convert-color";
|
||||
import { labDarken } from "../../../../common/color/lab";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
import { round } from "../../../../common/number/round";
|
||||
|
||||
const NEGATIVE = ["to_grid"];
|
||||
const ORDER = {
|
||||
used_solar: 0,
|
||||
from_grid: 100,
|
||||
to_grid: 200,
|
||||
};
|
||||
const COLORS = {
|
||||
to_grid: { border: "#56d256", background: "#87ceab" },
|
||||
from_grid: { border: "#126A9A", background: "#88b5cd" },
|
||||
used_solar: { border: "#FF9800", background: "#ffcb80" },
|
||||
to_grid: { border: "#673ab7", background: "#b39bdb" },
|
||||
from_grid: { border: "#126A9A", background: "#8ab5cd" },
|
||||
used_solar: { border: "#FF9800", background: "#fecc8e" },
|
||||
};
|
||||
|
||||
@customElement("hui-energy-summary-graph-card")
|
||||
@ -126,7 +121,7 @@ export class HuiEnergySummaryGraphCard
|
||||
? html`<ha-chart-base
|
||||
.data=${this._chartData}
|
||||
.options=${this._chartOptions}
|
||||
chartType="line"
|
||||
chart-type="bar"
|
||||
></ha-chart-base>`
|
||||
: ""}
|
||||
</div>
|
||||
@ -135,12 +130,18 @@ export class HuiEnergySummaryGraphCard
|
||||
}
|
||||
|
||||
private _createOptions() {
|
||||
const startDate = new Date();
|
||||
startDate.setHours(0, 0, 0, 0);
|
||||
const startTime = startDate.getTime();
|
||||
|
||||
this._chartOptions = {
|
||||
parsing: false,
|
||||
animation: false,
|
||||
scales: {
|
||||
x: {
|
||||
type: "time",
|
||||
suggestedMin: startTime,
|
||||
suggestedMax: startTime + 24 * 60 * 60 * 1000,
|
||||
adapters: {
|
||||
date: {
|
||||
locale: this.hass.locale,
|
||||
@ -161,10 +162,15 @@ export class HuiEnergySummaryGraphCard
|
||||
time: {
|
||||
tooltipFormat: "datetimeseconds",
|
||||
},
|
||||
offset: true,
|
||||
},
|
||||
y: {
|
||||
stacked: true,
|
||||
type: "linear",
|
||||
title: {
|
||||
display: true,
|
||||
text: "kWh",
|
||||
},
|
||||
ticks: {
|
||||
beginAtZero: true,
|
||||
callback: (value) => Math.abs(round(value)),
|
||||
@ -193,9 +199,13 @@ export class HuiEnergySummaryGraphCard
|
||||
}
|
||||
}
|
||||
return [
|
||||
`Total consumed: ${totalConsumed.toFixed(2)} kWh`,
|
||||
`Total returned: ${totalReturned.toFixed(2)} kWh`,
|
||||
];
|
||||
totalConsumed
|
||||
? `Total consumed: ${totalConsumed.toFixed(2)} kWh`
|
||||
: "",
|
||||
totalReturned
|
||||
? `Total returned: ${totalReturned.toFixed(2)} kWh`
|
||||
: "",
|
||||
].filter(Boolean);
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -213,10 +223,7 @@ export class HuiEnergySummaryGraphCard
|
||||
mode: "nearest",
|
||||
},
|
||||
elements: {
|
||||
line: {
|
||||
tension: 0.4,
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
bar: { borderWidth: 1.5 },
|
||||
point: {
|
||||
hitRadius: 5,
|
||||
},
|
||||
@ -280,7 +287,7 @@ export class HuiEnergySummaryGraphCard
|
||||
}
|
||||
|
||||
const statisticsData = Object.values(this._data!);
|
||||
const datasets: ChartDataset<"line">[] = [];
|
||||
const datasets: ChartDataset<"bar">[] = [];
|
||||
let endTime: Date;
|
||||
|
||||
if (statisticsData.length === 0) {
|
||||
@ -370,7 +377,7 @@ export class HuiEnergySummaryGraphCard
|
||||
const negative = NEGATIVE.includes(type);
|
||||
|
||||
Object.entries(sources).forEach(([statId, source], idx) => {
|
||||
const data: ChartDataset<"line">[] = [];
|
||||
const data: ChartDataset<"bar">[] = [];
|
||||
const entity = this.hass.states[statId];
|
||||
const color = COLORS[type];
|
||||
|
||||
@ -381,9 +388,6 @@ export class HuiEnergySummaryGraphCard
|
||||
: entity
|
||||
? computeStateName(entity)
|
||||
: statId,
|
||||
fill: true,
|
||||
stepped: false,
|
||||
order: ORDER[type] + idx,
|
||||
borderColor:
|
||||
idx > 0
|
||||
? rgb2hex(lab2rgb(labDarken(rgb2lab(hex2rgb(color.border)), idx)))
|
||||
@ -394,7 +398,7 @@ export class HuiEnergySummaryGraphCard
|
||||
lab2rgb(labDarken(rgb2lab(hex2rgb(color.background)), idx))
|
||||
)
|
||||
: color.background,
|
||||
stack: negative ? "negative" : "positive",
|
||||
stack: "stack",
|
||||
data: [],
|
||||
});
|
||||
|
||||
@ -402,6 +406,7 @@ export class HuiEnergySummaryGraphCard
|
||||
for (const key of uniqueKeys) {
|
||||
const value = key in source ? Math.round(source[key] * 100) / 100 : 0;
|
||||
const date = new Date(key);
|
||||
// @ts-expect-error
|
||||
data[0].data.push({
|
||||
x: date.getTime(),
|
||||
y: value && negative ? -1 * value : value,
|
@ -1,322 +0,0 @@
|
||||
import { mdiHome, mdiLeaf, mdiSolarPower, mdiTransmissionTower } from "@mdi/js";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { subscribeOne } from "../../../common/util/subscribe-one";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { getConfigEntries } from "../../../data/config_entries";
|
||||
import { energySourcesByType } from "../../../data/energy";
|
||||
import { subscribeEntityRegistry } from "../../../data/entity_registry";
|
||||
import {
|
||||
calculateStatisticsSumGrowth,
|
||||
fetchStatistics,
|
||||
Statistics,
|
||||
} from "../../../data/history";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { EnergySummaryCardConfig } from "./types";
|
||||
import "../../../components/ha-card";
|
||||
import { round } from "../../../common/number/round";
|
||||
|
||||
@customElement("hui-energy-usage-card")
|
||||
class HuiEnergyUsageCard extends LitElement implements LovelaceCard {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _config?: EnergySummaryCardConfig;
|
||||
|
||||
@state() private _stats?: Statistics;
|
||||
|
||||
@state() private _co2SignalEntity?: string;
|
||||
|
||||
private _fetching = false;
|
||||
|
||||
public setConfig(config: EnergySummaryCardConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public getCardSize(): Promise<number> | number {
|
||||
return 3;
|
||||
}
|
||||
|
||||
public willUpdate(changedProps) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
if (!this._fetching && !this._stats) {
|
||||
this._fetching = true;
|
||||
Promise.all([this._getStatistics(), this._fetchCO2SignalEntity()]).then(
|
||||
() => {
|
||||
this._fetching = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
if (!this._stats) {
|
||||
return html`Loading…`;
|
||||
}
|
||||
|
||||
const prefs = this._config!.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 hasReturnToGrid = hasConsumption && types.grid![0].flow_to.length > 0;
|
||||
|
||||
const totalGridConsumption =
|
||||
calculateStatisticsSumGrowth(
|
||||
this._stats,
|
||||
types.grid![0].flow_from.map((flow) => flow.stat_energy_from)
|
||||
) ?? 0;
|
||||
|
||||
let totalSolarProduction: number | null = null;
|
||||
|
||||
if (hasSolarProduction) {
|
||||
totalSolarProduction = calculateStatisticsSumGrowth(
|
||||
this._stats,
|
||||
types.solar!.map((source) => source.stat_energy_from)
|
||||
);
|
||||
}
|
||||
|
||||
let productionReturnedToGrid: number | null = null;
|
||||
|
||||
if (hasReturnToGrid) {
|
||||
productionReturnedToGrid = calculateStatisticsSumGrowth(
|
||||
this._stats,
|
||||
types.grid![0].flow_to.map((flow) => flow.stat_energy_to)
|
||||
);
|
||||
}
|
||||
|
||||
// total consumption = consumption_from_grid + solar_production - return_to_grid
|
||||
|
||||
let co2percentage: number | undefined;
|
||||
|
||||
if (this._co2SignalEntity) {
|
||||
const co2State = this.hass.states[this._co2SignalEntity];
|
||||
if (co2State) {
|
||||
co2percentage = Number(co2State.state);
|
||||
if (isNaN(co2percentage)) {
|
||||
co2percentage = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We are calculating low carbon consumption based on what we got from the grid
|
||||
// minus what we gave back because what we gave back is low carbon
|
||||
const relativeGridFlow =
|
||||
totalGridConsumption - (productionReturnedToGrid || 0);
|
||||
|
||||
let lowCarbonConsumption: number | undefined;
|
||||
|
||||
if (co2percentage !== undefined) {
|
||||
if (relativeGridFlow > 0) {
|
||||
lowCarbonConsumption = round(relativeGridFlow * (co2percentage / 100));
|
||||
} else {
|
||||
lowCarbonConsumption = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const totalConsumption =
|
||||
totalGridConsumption +
|
||||
(totalSolarProduction || 0) -
|
||||
(productionReturnedToGrid || 0);
|
||||
|
||||
const gridPctLowCarbon =
|
||||
co2percentage === undefined ? 0 : co2percentage / 100;
|
||||
const gridPctHighCarbon = 1 - gridPctLowCarbon;
|
||||
|
||||
const homePctSolar =
|
||||
((totalSolarProduction || 0) - (productionReturnedToGrid || 0)) /
|
||||
totalConsumption;
|
||||
// When we know the ratio solar-grid, we can adjust the low/high carbon
|
||||
// percentages to reflect that.
|
||||
const homePctGridLowCarbon = gridPctLowCarbon * (1 - homePctSolar);
|
||||
const homePctGridHighCarbon = gridPctHighCarbon * (1 - homePctSolar);
|
||||
|
||||
return html`
|
||||
<ha-card header="Usage">
|
||||
<div class="card-content">
|
||||
<div class="row">
|
||||
${co2percentage === undefined
|
||||
? ""
|
||||
: html`
|
||||
<div class="circle-container">
|
||||
<span class="label">Low-carbon</span>
|
||||
<div class="circle low-carbon">
|
||||
<ha-svg-icon .path="${mdiLeaf}"></ha-svg-icon>
|
||||
${co2percentage}% / ${round(lowCarbonConsumption!)} kWh
|
||||
</div>
|
||||
</div>
|
||||
`}
|
||||
<div class="circle-container">
|
||||
<span class="label">Solar</span>
|
||||
<div class="circle solar">
|
||||
<ha-svg-icon .path="${mdiSolarPower}"></ha-svg-icon>
|
||||
${round(totalSolarProduction || 0)} kWh
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="circle-container">
|
||||
<div class="circle grid">
|
||||
<ha-svg-icon .path="${mdiTransmissionTower}"></ha-svg-icon>
|
||||
${round(totalGridConsumption - (productionReturnedToGrid || 0))}
|
||||
kWh
|
||||
<ul>
|
||||
<li>
|
||||
Grid high carbon: ${round(gridPctHighCarbon * 100, 1)}%
|
||||
</li>
|
||||
<li>Grid low carbon: ${round(gridPctLowCarbon * 100, 1)}%</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span class="label">Grid</span>
|
||||
</div>
|
||||
<div class="circle-container home">
|
||||
<div class="circle home">
|
||||
<ha-svg-icon .path="${mdiHome}"></ha-svg-icon>
|
||||
${round(totalConsumption)} kWh
|
||||
<ul>
|
||||
<li>
|
||||
Grid high carbon: ${round(homePctGridHighCarbon * 100)}%
|
||||
</li>
|
||||
<li>
|
||||
Grid low carbon: ${round(homePctGridLowCarbon * 100)}%
|
||||
</li>
|
||||
<li>Solar: ${round(homePctSolar * 100)}%</li>
|
||||
</ul>
|
||||
</div>
|
||||
<span class="label">Home</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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 styles = css`
|
||||
:host {
|
||||
--mdc-icon-size: 26px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.circle-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-right: 40px;
|
||||
}
|
||||
.circle {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
.label {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
.circle-container:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
.circle ul {
|
||||
display: none;
|
||||
}
|
||||
.low-carbon {
|
||||
border-color: #0da035;
|
||||
}
|
||||
.low-carbon ha-svg-icon {
|
||||
color: #0da035;
|
||||
}
|
||||
.solar {
|
||||
border-color: #ff9800;
|
||||
}
|
||||
.grid {
|
||||
border-color: #134763;
|
||||
}
|
||||
.circle-container.home {
|
||||
margin-left: 120px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-energy-usage-card": HuiEnergyUsageCard;
|
||||
}
|
||||
}
|
@ -92,31 +92,42 @@ export interface ButtonCardConfig extends LovelaceCardConfig {
|
||||
|
||||
export interface EnergySummaryCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-summary";
|
||||
title?: string;
|
||||
prefs: EnergyPreferences;
|
||||
}
|
||||
|
||||
export interface EnergyDistributionCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-distribution";
|
||||
title?: string;
|
||||
prefs: EnergyPreferences;
|
||||
}
|
||||
export interface EnergySummaryGraphCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-summary-graph";
|
||||
title?: string;
|
||||
prefs: EnergyPreferences;
|
||||
}
|
||||
|
||||
export interface EnergySolarGraphCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-solar-graph";
|
||||
title?: string;
|
||||
prefs: EnergyPreferences;
|
||||
}
|
||||
|
||||
export interface EnergyDevicesGraphCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-devices-graph";
|
||||
title?: string;
|
||||
prefs: EnergyPreferences;
|
||||
}
|
||||
|
||||
export interface EnergySolarGaugeCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-solar-consumed-gauge";
|
||||
title?: string;
|
||||
prefs: EnergyPreferences;
|
||||
}
|
||||
|
||||
export interface EnergyCarbonGaugeCardConfig extends LovelaceCardConfig {
|
||||
type: "energy-carbon-consumed-gauge";
|
||||
title?: string;
|
||||
prefs: EnergyPreferences;
|
||||
}
|
||||
|
||||
|
@ -35,18 +35,21 @@ const LAZY_LOAD_TYPES = {
|
||||
"alarm-panel": () => import("../cards/hui-alarm-panel-card"),
|
||||
error: () => import("../cards/hui-error-card"),
|
||||
"empty-state": () => import("../cards/hui-empty-state-card"),
|
||||
"energy-summary": () => import("../cards/hui-energy-summary-card"),
|
||||
"energy-summary": () => import("../cards/energy/hui-energy-summary-card"),
|
||||
"energy-summary-graph": () =>
|
||||
import("../cards/hui-energy-summary-graph-card"),
|
||||
"energy-solar-graph": () => import("../cards/hui-energy-solar-graph-card"),
|
||||
import("../cards/energy/hui-energy-summary-graph-card"),
|
||||
"energy-solar-graph": () =>
|
||||
import("../cards/energy/hui-energy-solar-graph-card"),
|
||||
"energy-devices-graph": () =>
|
||||
import("../cards/hui-energy-devices-graph-card"),
|
||||
"energy-costs-table": () => import("../cards/hui-energy-costs-table-card"),
|
||||
"energy-usage": () => import("../cards/hui-energy-usage-card"),
|
||||
import("../cards/energy/hui-energy-devices-graph-card"),
|
||||
"energy-costs-table": () =>
|
||||
import("../cards/energy/hui-energy-costs-table-card"),
|
||||
"energy-distribution": () =>
|
||||
import("../cards/energy/hui-energy-distribution-card"),
|
||||
"energy-solar-consumed-gauge": () =>
|
||||
import("../cards/hui-energy-solar-consumed-gauge-card"),
|
||||
import("../cards/energy/hui-energy-solar-consumed-gauge-card"),
|
||||
"energy-carbon-consumed-gauge": () =>
|
||||
import("../cards/hui-energy-carbon-consumed-gauge-card"),
|
||||
import("../cards/energy/hui-energy-carbon-consumed-gauge-card"),
|
||||
grid: () => import("../cards/hui-grid-card"),
|
||||
starting: () => import("../cards/hui-starting-card"),
|
||||
"entity-filter": () => import("../cards/hui-entity-filter-card"),
|
||||
|
Loading…
x
Reference in New Issue
Block a user