mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-02 08:33:31 +01:00
Add power, water and gas current flow rate tile cards (#29788)
This commit is contained in:
@@ -1401,6 +1401,80 @@ export const calculateSolarConsumedGauge = (
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Conversion factors from each flow rate unit to L/min.
|
||||
* All HA-supported UnitOfVolumeFlowRate values are covered.
|
||||
*
|
||||
* m³/h → 1000/60 = 16.6667 L/min
|
||||
* m³/min → 1000 L/min
|
||||
* m³/s → 60000 L/min
|
||||
* ft³/min→ 28.3168 L/min
|
||||
* L/h → 1/60 L/min
|
||||
* L/min → 1 L/min
|
||||
* L/s → 60 L/min
|
||||
* gal/h → 3.78541/60 L/min
|
||||
* gal/min→ 3.78541 L/min
|
||||
* gal/d → 3.78541/1440 L/min
|
||||
* mL/s → 0.06 L/min
|
||||
*/
|
||||
|
||||
/** Exact number of liters in one US gallon */
|
||||
const LITERS_PER_GALLON = 3.785411784;
|
||||
|
||||
const FLOW_RATE_TO_LMIN: Record<string, number> = {
|
||||
"m³/h": 1000 / 60,
|
||||
"m³/min": 1000,
|
||||
"m³/s": 60000,
|
||||
"ft³/min": 28.316846592,
|
||||
"L/h": 1 / 60,
|
||||
"L/min": 1,
|
||||
"L/s": 60,
|
||||
"gal/h": LITERS_PER_GALLON / 60,
|
||||
"gal/min": LITERS_PER_GALLON,
|
||||
"gal/d": LITERS_PER_GALLON / 1440,
|
||||
"mL/s": 60 / 1000,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current flow rate from an entity state, converted to L/min.
|
||||
* @returns Flow rate in L/min, or undefined if unavailable/invalid.
|
||||
*/
|
||||
export const getFlowRateFromState = (
|
||||
stateObj?: HassEntity
|
||||
): number | undefined => {
|
||||
if (!stateObj) {
|
||||
return undefined;
|
||||
}
|
||||
const value = parseFloat(stateObj.state);
|
||||
if (isNaN(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const unit = stateObj.attributes.unit_of_measurement;
|
||||
const factor = unit ? FLOW_RATE_TO_LMIN[unit] : undefined;
|
||||
if (factor === undefined) {
|
||||
// Unknown unit – return raw value as-is (best effort)
|
||||
return value;
|
||||
}
|
||||
return value * factor;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format a flow rate value (in L/min) to a human-readable string using
|
||||
* the preferred unit system: metric → L/min, imperial → gal/min.
|
||||
*/
|
||||
export const formatFlowRateShort = (
|
||||
hassLocale: HomeAssistant["locale"],
|
||||
lengthUnitSystem: string,
|
||||
litersPerMin: number
|
||||
): string => {
|
||||
const isMetric = lengthUnitSystem === "km";
|
||||
if (isMetric) {
|
||||
return `${formatNumber(litersPerMin, hassLocale, { maximumFractionDigits: 1 })} L/min`;
|
||||
}
|
||||
const galPerMin = litersPerMin / LITERS_PER_GALLON;
|
||||
return `${formatNumber(galPerMin, hassLocale, { maximumFractionDigits: 1 })} gal/min`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current power value from entity state, normalized to watts (W)
|
||||
* @param stateObj - The entity state object to get power value from
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { ReactiveElement } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { getEnergyDataCollection } from "../../../data/energy";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
|
||||
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
|
||||
import { shouldShowFloorsAndAreas } from "./show-floors-and-areas";
|
||||
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
|
||||
import {
|
||||
LARGE_SCREEN_CONDITION,
|
||||
SMALL_SCREEN_CONDITION,
|
||||
} from "../../lovelace/strategies/helpers/screen-conditions";
|
||||
import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
|
||||
|
||||
@customElement("power-view-strategy")
|
||||
export class PowerViewStrategy extends ReactiveElement {
|
||||
@@ -14,11 +19,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;
|
||||
|
||||
@@ -39,16 +39,50 @@ export class PowerViewStrategy extends ReactiveElement {
|
||||
const hasPowerDevices = prefs?.device_consumption.some(
|
||||
(device) => device.stat_rate
|
||||
);
|
||||
const hasWaterSources = prefs?.energy_sources.some(
|
||||
(source) => source.type === "water" && source.stat_rate
|
||||
);
|
||||
const hasGasSources = prefs?.energy_sources.some(
|
||||
(source) => source.type === "gas" && source.stat_rate
|
||||
);
|
||||
|
||||
const tileSection: LovelaceSectionConfig = {
|
||||
type: "grid",
|
||||
cards: [],
|
||||
column_span: 2,
|
||||
};
|
||||
const chartsSection: LovelaceSectionConfig = {
|
||||
type: "grid",
|
||||
cards: [],
|
||||
column_span: 2,
|
||||
};
|
||||
const tiles: LovelaceCardConfig[] = [];
|
||||
|
||||
const view: LovelaceViewConfig = {
|
||||
type: "sections",
|
||||
sections: [tileSection, chartsSection],
|
||||
max_columns: 2,
|
||||
};
|
||||
|
||||
// No power sources configured
|
||||
if (!prefs || (!hasPowerSources && !hasPowerDevices)) {
|
||||
if (
|
||||
!prefs ||
|
||||
(!hasPowerSources &&
|
||||
!hasPowerDevices &&
|
||||
!hasWaterSources &&
|
||||
!hasGasSources)
|
||||
) {
|
||||
return view;
|
||||
}
|
||||
|
||||
const section = view.sections![0] as LovelaceSectionConfig;
|
||||
|
||||
if (hasPowerSources) {
|
||||
section.cards!.push({
|
||||
const card = {
|
||||
type: "power-total",
|
||||
collection_key: collectionKey,
|
||||
};
|
||||
tiles.push(card);
|
||||
|
||||
chartsSection.cards!.push({
|
||||
title: hass.localize("ui.panel.energy.cards.power_sources_graph_title"),
|
||||
type: "power-sources-graph",
|
||||
collection_key: collectionKey,
|
||||
@@ -58,13 +92,29 @@ export class PowerViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
if (hasGasSources) {
|
||||
const card = {
|
||||
type: "gas-total",
|
||||
collection_key: collectionKey,
|
||||
};
|
||||
tiles.push({ ...card });
|
||||
}
|
||||
|
||||
if (hasWaterSources) {
|
||||
const card = {
|
||||
type: "water-total",
|
||||
collection_key: collectionKey,
|
||||
};
|
||||
tiles.push({ ...card });
|
||||
}
|
||||
|
||||
if (hasPowerDevices) {
|
||||
const showFloorsAndAreas = shouldShowFloorsAndAreas(
|
||||
prefs.device_consumption,
|
||||
hass,
|
||||
(d) => d.stat_rate
|
||||
);
|
||||
section.cards!.push({
|
||||
chartsSection.cards!.push({
|
||||
title: hass.localize("ui.panel.energy.cards.power_sankey_title"),
|
||||
type: "power-sankey",
|
||||
collection_key: collectionKey,
|
||||
@@ -76,6 +126,23 @@ export class PowerViewStrategy extends ReactiveElement {
|
||||
});
|
||||
}
|
||||
|
||||
tiles.forEach((card) => {
|
||||
tileSection.cards!.push({
|
||||
...card,
|
||||
grid_options: { columns: 24 / tiles.length },
|
||||
});
|
||||
});
|
||||
|
||||
if (tiles.length > 2) {
|
||||
// On small screens with 3 tiles, show them in 1 column
|
||||
tileSection.visibility = [LARGE_SCREEN_CONDITION];
|
||||
view.sections!.unshift({
|
||||
type: "grid",
|
||||
cards: tiles,
|
||||
visibility: [SMALL_SCREEN_CONDITION],
|
||||
});
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
153
src/panels/lovelace/cards/energy/hui-gas-total-card.ts
Normal file
153
src/panels/lovelace/cards/energy/hui-gas-total-card.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import { mdiFire } 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 "../../../../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 {
|
||||
formatFlowRateShort,
|
||||
getEnergyDataCollection,
|
||||
getFlowRateFromState,
|
||||
} 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 { GasTotalCardConfig } from "../types";
|
||||
|
||||
@customElement("hui-gas-total-card")
|
||||
export class HuiGasTotalCard
|
||||
extends SubscribeMixin(LitElement)
|
||||
implements LovelaceCard
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _config?: GasTotalCardConfig;
|
||||
|
||||
@state() private _data?: EnergyData;
|
||||
|
||||
private _entities = new Set<string>();
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public setConfig(config: GasTotalCardConfig): 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 _getCurrentFlowRate(entityId: string): number {
|
||||
this._entities.add(entityId);
|
||||
return getFlowRateFromState(this.hass.states[entityId]) ?? 0;
|
||||
}
|
||||
|
||||
private _computeTotalFlowRate(prefs: EnergyPreferences): number {
|
||||
this._entities.clear();
|
||||
|
||||
let totalFlow = 0;
|
||||
|
||||
prefs.energy_sources.forEach((source) => {
|
||||
if (source.type === "gas" && source.stat_rate) {
|
||||
const value = this._getCurrentFlowRate(source.stat_rate);
|
||||
if (value > 0) totalFlow += value;
|
||||
}
|
||||
});
|
||||
|
||||
return Math.max(0, totalFlow);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config || !this._data) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const flowRate = this._computeTotalFlowRate(this._data.prefs);
|
||||
const displayValue = formatFlowRateShort(
|
||||
this.hass.locale,
|
||||
this.hass.config.unit_system.length,
|
||||
flowRate
|
||||
);
|
||||
|
||||
const name =
|
||||
this._config.title ||
|
||||
this.hass.localize("ui.panel.lovelace.cards.energy.gas_total_title");
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
<ha-tile-container .interactive=${false}>
|
||||
<ha-tile-icon slot="icon" data-domain="sensor" data-state="active">
|
||||
<ha-svg-icon slot="icon" .path=${mdiFire}></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`
|
||||
:host {
|
||||
--tile-color: var(--energy-gas-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-gas-total-card": HuiGasTotalCard;
|
||||
}
|
||||
}
|
||||
175
src/panels/lovelace/cards/energy/hui-power-total-card.ts
Normal file
175
src/panels/lovelace/cards/energy/hui-power-total-card.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
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 { 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._data) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const power = this._computeTotalPower(this._data.prefs);
|
||||
|
||||
let displayValue = "";
|
||||
if (power >= 1000) {
|
||||
displayValue = `${formatNumber(power / 1000, this.hass.locale, {
|
||||
maximumFractionDigits: 2,
|
||||
})} kW`;
|
||||
} else {
|
||||
displayValue = `${formatNumber(power, this.hass.locale, {
|
||||
maximumFractionDigits: 0,
|
||||
})} W`;
|
||||
}
|
||||
|
||||
const name =
|
||||
this._config.title ||
|
||||
this.hass.localize("ui.panel.lovelace.cards.energy.power_total_title");
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
<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`
|
||||
:host {
|
||||
--tile-color: var(--primary-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-power-total-card": HuiPowerTotalCard;
|
||||
}
|
||||
}
|
||||
153
src/panels/lovelace/cards/energy/hui-water-total-card.ts
Normal file
153
src/panels/lovelace/cards/energy/hui-water-total-card.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import { mdiWater } 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 "../../../../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 {
|
||||
formatFlowRateShort,
|
||||
getEnergyDataCollection,
|
||||
getFlowRateFromState,
|
||||
} 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 { WaterTotalCardConfig } from "../types";
|
||||
|
||||
@customElement("hui-water-total-card")
|
||||
export class HuiWaterTotalCard
|
||||
extends SubscribeMixin(LitElement)
|
||||
implements LovelaceCard
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _config?: WaterTotalCardConfig;
|
||||
|
||||
@state() private _data?: EnergyData;
|
||||
|
||||
private _entities = new Set<string>();
|
||||
|
||||
protected hassSubscribeRequiredHostProps = ["_config"];
|
||||
|
||||
public setConfig(config: WaterTotalCardConfig): 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 _getCurrentFlowRate(entityId: string): number {
|
||||
this._entities.add(entityId);
|
||||
return getFlowRateFromState(this.hass.states[entityId]) ?? 0;
|
||||
}
|
||||
|
||||
private _computeTotalFlowRate(prefs: EnergyPreferences): number {
|
||||
this._entities.clear();
|
||||
|
||||
let totalFlow = 0;
|
||||
|
||||
prefs.energy_sources.forEach((source) => {
|
||||
if (source.type === "water" && source.stat_rate) {
|
||||
const value = this._getCurrentFlowRate(source.stat_rate);
|
||||
if (value > 0) totalFlow += value;
|
||||
}
|
||||
});
|
||||
|
||||
return Math.max(0, totalFlow);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._config || !this._data) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const flowRate = this._computeTotalFlowRate(this._data.prefs);
|
||||
const displayValue = formatFlowRateShort(
|
||||
this.hass.locale,
|
||||
this.hass.config.unit_system.length,
|
||||
flowRate
|
||||
);
|
||||
|
||||
const name =
|
||||
this._config.title ||
|
||||
this.hass.localize("ui.panel.lovelace.cards.energy.water_total_title");
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
<ha-tile-container .interactive=${false}>
|
||||
<ha-tile-icon slot="icon" data-domain="sensor" data-state="active">
|
||||
<ha-svg-icon slot="icon" .path=${mdiWater}></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`
|
||||
:host {
|
||||
--tile-color: var(--energy-water-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"hui-water-total-card": HuiWaterTotalCard;
|
||||
}
|
||||
}
|
||||
@@ -257,6 +257,21 @@ export interface PowerSourcesGraphCardConfig extends EnergyCardBaseConfig {
|
||||
show_legend?: boolean;
|
||||
}
|
||||
|
||||
export interface PowerTotalCardConfig extends EnergyCardBaseConfig {
|
||||
type: "power-total";
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface WaterTotalCardConfig extends EnergyCardBaseConfig {
|
||||
type: "water-total";
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface GasTotalCardConfig extends EnergyCardBaseConfig {
|
||||
type: "gas-total";
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface PowerSankeyCardConfig extends EnergyCardBaseConfig {
|
||||
type: "power-sankey";
|
||||
title?: string;
|
||||
|
||||
@@ -69,6 +69,9 @@ 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"),
|
||||
"water-total": () => import("../cards/energy/hui-water-total-card"),
|
||||
"gas-total": () => import("../cards/energy/hui-gas-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"),
|
||||
|
||||
@@ -8260,7 +8260,10 @@
|
||||
"info": "You are comparing the period {start} with the period {end}",
|
||||
"compare_previous_year": "Compare previous year",
|
||||
"compare_previous_period": "Compare previous period"
|
||||
}
|
||||
},
|
||||
"power_total_title": "Power usage",
|
||||
"water_total_title": "[%key:ui::panel::config::energy::water::dialog::water_flow_rate%]",
|
||||
"gas_total_title": "[%key:ui::panel::config::energy::gas::dialog::gas_flow_rate%]"
|
||||
},
|
||||
"distribution": {
|
||||
"no_entities": "No entities specified",
|
||||
|
||||
Reference in New Issue
Block a user