mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-18 00:24:50 +00:00
Compare commits
1 Commits
dev
...
feature/po
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ca5550e02 |
@@ -1,7 +1,7 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
@@ -14,11 +14,6 @@ export class PowerViewStrategy extends ReactiveElement {
|
||||
_config: LovelaceStrategyConfig,
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceViewConfig> {
|
||||
const view: LovelaceViewConfig = {
|
||||
type: "sections",
|
||||
sections: [{ type: "grid", cards: [] }],
|
||||
};
|
||||
|
||||
const collectionKey =
|
||||
_config.collection_key || DEFAULT_ENERGY_COLLECTION_KEY;
|
||||
|
||||
@@ -40,15 +35,26 @@ export class PowerViewStrategy extends ReactiveElement {
|
||||
(device) => device.stat_rate
|
||||
);
|
||||
|
||||
const cards: LovelaceCardConfig[] = [];
|
||||
|
||||
const view: LovelaceViewConfig = {
|
||||
type: "sections",
|
||||
sections: [{ type: "grid", cards }],
|
||||
};
|
||||
|
||||
// No power sources configured
|
||||
if (!prefs || (!hasPowerSources && !hasPowerDevices)) {
|
||||
return view;
|
||||
}
|
||||
|
||||
const section = view.sections![0] as LovelaceSectionConfig;
|
||||
cards.push({
|
||||
type: "power-total",
|
||||
collection_key: collectionKey,
|
||||
grid_options: { columns: 12 },
|
||||
});
|
||||
|
||||
if (hasPowerSources) {
|
||||
section.cards!.push({
|
||||
cards.push({
|
||||
title: hass.localize("ui.panel.energy.cards.power_sources_graph_title"),
|
||||
type: "power-sources-graph",
|
||||
collection_key: collectionKey,
|
||||
@@ -64,7 +70,7 @@ export class PowerViewStrategy extends ReactiveElement {
|
||||
hass,
|
||||
(d) => d.stat_rate
|
||||
);
|
||||
section.cards!.push({
|
||||
cards.push({
|
||||
title: hass.localize("ui.panel.energy.cards.power_sankey_title"),
|
||||
type: "power-sankey",
|
||||
collection_key: collectionKey,
|
||||
|
||||
185
src/panels/lovelace/cards/energy/hui-power-total-card.ts
Normal file
185
src/panels/lovelace/cards/energy/hui-power-total-card.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
import { mdiHomeLightningBolt } from "@mdi/js";
|
||||
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { formatNumber } from "../../../../common/number/format_number";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/tile/ha-tile-container";
|
||||
import "../../../../components/tile/ha-tile-icon";
|
||||
import "../../../../components/tile/ha-tile-info";
|
||||
import type { EnergyData, EnergyPreferences } from "../../../../data/energy";
|
||||
import {
|
||||
getEnergyDataCollection,
|
||||
getPowerFromState,
|
||||
} from "../../../../data/energy";
|
||||
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { LovelaceCard, LovelaceGridOptions } from "../../types";
|
||||
import { tileCardStyle } from "../tile/tile-card-style";
|
||||
import type { PowerTotalCardConfig } from "../types";
|
||||
|
||||
@customElement("hui-power-total-card")
|
||||
export class HuiPowerTotalCard
|
||||
extends SubscribeMixin(LitElement)
|
||||
implements LovelaceCard
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _config?: PowerTotalCardConfig;
|
||||
|
||||
@state() private _data?: EnergyData;
|
||||
|
||||
private _entities = new Set<string>();
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public setConfig(config: PowerTotalCardConfig): void {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
public hassSubscribe(): UnsubscribeFunc[] {
|
||||
return [
|
||||
getEnergyDataCollection(this.hass, {
|
||||
key: this._config?.collection_key,
|
||||
}).subscribe((data) => {
|
||||
this._data = data;
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
public getCardSize(): Promise<number> | number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
getGridOptions(): LovelaceGridOptions {
|
||||
return {
|
||||
columns: 12,
|
||||
min_columns: 6,
|
||||
rows: 1,
|
||||
min_rows: 1,
|
||||
};
|
||||
}
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
if (changedProps.has("_config") || changedProps.has("_data")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if any of the tracked entity states have changed
|
||||
if (changedProps.has("hass")) {
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (!oldHass || !this._entities.size) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only update if one of our tracked entities changed
|
||||
for (const entityId of this._entities) {
|
||||
if (oldHass.states[entityId] !== this.hass.states[entityId]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private _getCurrentPower(entityId: string): number {
|
||||
this._entities.add(entityId);
|
||||
return getPowerFromState(this.hass.states[entityId]) ?? 0;
|
||||
}
|
||||
|
||||
private _computeTotalPower(prefs: EnergyPreferences): number {
|
||||
this._entities.clear();
|
||||
|
||||
let solar = 0;
|
||||
let from_grid = 0;
|
||||
let to_grid = 0;
|
||||
let from_battery = 0;
|
||||
let to_battery = 0;
|
||||
|
||||
prefs.energy_sources.forEach((source) => {
|
||||
if (source.type === "solar" && source.stat_rate) {
|
||||
const value = this._getCurrentPower(source.stat_rate);
|
||||
if (value > 0) solar += value;
|
||||
} else if (source.type === "grid" && source.stat_rate) {
|
||||
const value = this._getCurrentPower(source.stat_rate);
|
||||
if (value > 0) from_grid += value;
|
||||
else if (value < 0) to_grid += Math.abs(value);
|
||||
} else if (source.type === "battery" && source.stat_rate) {
|
||||
const value = this._getCurrentPower(source.stat_rate);
|
||||
if (value > 0) from_battery += value;
|
||||
else if (value < 0) to_battery += Math.abs(value);
|
||||
}
|
||||
});
|
||||
|
||||
const used_total = from_grid + solar + from_battery - to_grid - to_battery;
|
||||
return Math.max(0, used_total);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config || !this.hass || !this._data) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const power = this._computeTotalPower(this._data.prefs);
|
||||
|
||||
let displayValue = "";
|
||||
if (power >= 1000) {
|
||||
displayValue = `${formatNumber(power / 1000, this.hass.locale, {
|
||||
maximumFractionDigits: 1,
|
||||
})} kW`;
|
||||
} else {
|
||||
displayValue = `${formatNumber(power, this.hass.locale, {
|
||||
maximumFractionDigits: 0,
|
||||
})} W`;
|
||||
}
|
||||
|
||||
const name =
|
||||
this._config.title ||
|
||||
this.hass.localize("ui.panel.energy.cards.power_total_title");
|
||||
|
||||
// We try to mock a basic tile card appearance
|
||||
const style = {
|
||||
"--tile-color": "var(--state-sensor-color, var(--state-icon-color))",
|
||||
};
|
||||
|
||||
return html`
|
||||
<ha-card style=${styleMap(style)}>
|
||||
<ha-tile-container .interactive=${false}>
|
||||
<ha-tile-icon slot="icon" data-domain="sensor" data-state="active">
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${mdiHomeLightningBolt}
|
||||
></ha-svg-icon>
|
||||
</ha-tile-icon>
|
||||
<ha-tile-info slot="info">
|
||||
<span slot="primary" class="primary">${name}</span>
|
||||
<span slot="secondary" class="secondary">${displayValue}</span>
|
||||
</ha-tile-info>
|
||||
</ha-tile-container>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
tileCardStyle,
|
||||
css`
|
||||
ha-card {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
ha-tile-container {
|
||||
--tile-icon-color: var(--tile-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-power-total-card": HuiPowerTotalCard;
|
||||
}
|
||||
}
|
||||
@@ -257,6 +257,11 @@ export interface PowerSourcesGraphCardConfig extends EnergyCardBaseConfig {
|
||||
show_legend?: boolean;
|
||||
}
|
||||
|
||||
export interface PowerTotalCardConfig extends EnergyCardBaseConfig {
|
||||
type: "power-total";
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface PowerSankeyCardConfig extends EnergyCardBaseConfig {
|
||||
type: "power-sankey";
|
||||
title?: string;
|
||||
|
||||
@@ -69,6 +69,7 @@ const LAZY_LOAD_TYPES = {
|
||||
"water-sankey": () => import("../cards/water/hui-water-sankey-card"),
|
||||
"power-sources-graph": () =>
|
||||
import("../cards/energy/hui-power-sources-graph-card"),
|
||||
"power-total": () => import("../cards/energy/hui-power-total-card"),
|
||||
"power-sankey": () => import("../cards/energy/hui-power-sankey-card"),
|
||||
"entity-filter": () => import("../cards/hui-entity-filter-card"),
|
||||
error: () => import("../cards/hui-error-card"),
|
||||
|
||||
@@ -10255,7 +10255,8 @@
|
||||
"water_sankey_title": "Water flow",
|
||||
"energy_top_consumers_title": "Top consumers",
|
||||
"power_sankey_title": "Current power flow",
|
||||
"power_sources_graph_title": "Power sources"
|
||||
"power_sources_graph_title": "Power sources",
|
||||
"power_total_title": "Power consumption"
|
||||
}
|
||||
},
|
||||
"history": {
|
||||
|
||||
Reference in New Issue
Block a user