1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-17 07:34:21 +01:00

Add flow rate picker to gas, water, and water device energy dialogs (#29693)

* Add flow rate picker to gas, water, and water device energy dialogs

Add optional flow rate (stat_rate) picker to gas source, water source,
and water device configuration dialogs, matching the pattern used for
power tracking in grid/solar/battery sources and energy devices.

- Add stat_rate to GasSourceTypeEnergyPreference and WaterSourceTypeEnergyPreference
- Collect gas/water stat_rate in getReferencedStatisticIdsPower()
- Add flow rate ha-statistic-picker filtered to volume_flow_rate device class
- Move entity help text to picker helper props for cleaner layout

* Apply suggestions from code review

Co-authored-by: Norbert Rittel <norbert@rittel.de>

---------

Co-authored-by: Norbert Rittel <norbert@rittel.de>
This commit is contained in:
Petar Petrov
2026-02-24 06:55:35 +01:00
committed by GitHub
parent 3a48e1996f
commit b849fecf0b
5 changed files with 150 additions and 28 deletions

View File

@@ -159,6 +159,9 @@ export interface GasSourceTypeEnergyPreference {
// kWh/volume meter
stat_energy_from: string;
// Flow rate (m³/h, L/min, etc.)
stat_rate?: string;
// $ meter
stat_cost: string | null;
@@ -174,6 +177,9 @@ export interface WaterSourceTypeEnergyPreference {
// volume meter
stat_energy_from: string;
// Flow rate (L/min, gal/min, m³/h, etc.)
stat_rate?: string;
// $ meter
stat_cost: string | null;
@@ -368,6 +374,9 @@ export const getReferencedStatisticIdsPower = (
for (const source of prefs.energy_sources) {
if (source.type === "gas" || source.type === "water") {
if (source.stat_rate) {
statIDs.push(source.stat_rate);
}
continue;
}
@@ -389,6 +398,7 @@ export const getReferencedStatisticIdsPower = (
}
}
statIDs.push(...prefs.device_consumption.map((d) => d.stat_rate));
statIDs.push(...prefs.device_consumption_water.map((d) => d.stat_rate));
return statIDs.filter(Boolean) as string[];
};

View File

@@ -20,6 +20,7 @@ import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
import type { EnergySettingsDeviceWaterDialogParams } from "./show-dialogs-energy";
const volumeUnitClasses = ["volume"];
const flowRateUnitClasses = ["volume_flow_rate"];
@customElement("dialog-energy-device-settings-water")
export class DialogEnergyDeviceSettingsWater
@@ -36,10 +37,14 @@ export class DialogEnergyDeviceSettingsWater
@state() private _volume_units?: string[];
@state() private _flow_rate_units?: string[];
@state() private _error?: string;
private _excludeList?: string[];
private _excludeListFlowRate?: string[];
private _possibleParents: DeviceConsumptionEnergyPreference[] = [];
public async showDialog(
@@ -51,9 +56,15 @@ export class DialogEnergyDeviceSettingsWater
this._volume_units = (
await getSensorDeviceClassConvertibleUnits(this.hass, "water")
).units;
this._flow_rate_units = (
await getSensorDeviceClassConvertibleUnits(this.hass, "volume_flow_rate")
).units;
this._excludeList = this._params.device_consumptions
.map((entry) => entry.stat_consumption)
.filter((id) => id !== this._device?.stat_consumption);
this._excludeListFlowRate = this._params.device_consumptions
.map((entry) => entry.stat_rate)
.filter((id) => id && id !== this._device?.stat_rate) as string[];
this._open = true;
}
@@ -92,6 +103,7 @@ export class DialogEnergyDeviceSettingsWater
this._device = undefined;
this._error = undefined;
this._excludeList = undefined;
this._excludeListFlowRate = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -134,12 +146,6 @@ export class DialogEnergyDeviceSettingsWater
@closed=${this._dialogClosed}
>
${this._error ? html`<p class="error">${this._error}</p>` : ""}
<div>
${this.hass.localize(
"ui.panel.config.energy.device_consumption_water.dialog.selected_stat_intro",
{ unit: pickableUnit }
)}
</div>
<ha-statistic-picker
.hass=${this.hass}
@@ -151,9 +157,28 @@ export class DialogEnergyDeviceSettingsWater
)}
.excludeStatistics=${this._excludeList}
@value-changed=${this._statisticChanged}
.helper=${this.hass.localize(
"ui.panel.config.energy.device_consumption_water.dialog.selected_stat_intro",
{ unit: pickableUnit }
)}
autofocus
></ha-statistic-picker>
<ha-statistic-picker
.hass=${this.hass}
.includeUnitClass=${flowRateUnitClasses}
.value=${this._device?.stat_rate}
.label=${this.hass.localize(
"ui.panel.config.energy.device_consumption_water.dialog.device_consumption_water_flow_rate"
)}
.excludeStatistics=${this._excludeListFlowRate}
@value-changed=${this._flowRateStatisticChanged}
.helper=${this.hass.localize(
"ui.panel.config.energy.device_consumption_water.dialog.selected_stat_intro",
{ unit: this._flow_rate_units?.join(", ") || "" }
)}
></ha-statistic-picker>
<ha-textfield
.label=${this.hass.localize(
"ui.panel.config.energy.device_consumption_water.dialog.display_name"
@@ -216,6 +241,20 @@ export class DialogEnergyDeviceSettingsWater
this._computePossibleParents();
}
private _flowRateStatisticChanged(ev: ValueChangedEvent<string>) {
if (!this._device) {
return;
}
const newDevice = {
...this._device,
stat_rate: ev.detail.value,
} as DeviceConsumptionEnergyPreference;
if (!newDevice.stat_rate) {
delete newDevice.stat_rate;
}
this._device = newDevice;
}
private _nameChanged(ev) {
const newDevice = {
...this._device!,
@@ -252,7 +291,9 @@ export class DialogEnergyDeviceSettingsWater
haStyleDialog,
css`
ha-statistic-picker {
display: block;
width: 100%;
margin-bottom: var(--ha-space-4);
}
ha-select {
display: block;

View File

@@ -30,6 +30,7 @@ import type { EnergySettingsGasDialogParams } from "./show-dialogs-energy";
const gasDeviceClasses = ["gas", "energy"];
const gasUnitClasses = ["volume", "energy"];
const flowRateUnitClasses = ["volume_flow_rate"];
@customElement("dialog-energy-gas-settings")
export class DialogEnergyGasSettings
@@ -52,10 +53,14 @@ export class DialogEnergyGasSettings
@state() private _gas_units?: string[];
@state() private _flow_rate_units?: string[];
@state() private _error?: string;
private _excludeList?: string[];
private _excludeListFlowRate?: string[];
public async showDialog(
params: EnergySettingsGasDialogParams
): Promise<void> {
@@ -81,9 +86,15 @@ export class DialogEnergyGasSettings
this._gas_units = (
await getSensorDeviceClassConvertibleUnits(this.hass, "gas")
).units;
this._flow_rate_units = (
await getSensorDeviceClassConvertibleUnits(this.hass, "volume_flow_rate")
).units;
this._excludeList = this._params.gas_sources
.map((entry) => entry.stat_energy_from)
.filter((id) => id !== this._source?.stat_energy_from);
this._excludeListFlowRate = this._params.gas_sources
.map((entry) => entry.stat_rate)
.filter((id) => id && id !== this._source?.stat_rate) as string[];
this._open = true;
}
@@ -99,6 +110,7 @@ export class DialogEnergyGasSettings
this._pickedDisplayUnit = undefined;
this._error = undefined;
this._excludeList = undefined;
this._excludeListFlowRate = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -146,12 +158,6 @@ export class DialogEnergyGasSettings
<p>
${this.hass.localize("ui.panel.config.energy.gas.dialog.paragraph")}
</p>
<p>
${this.hass.localize(
"ui.panel.config.energy.gas.dialog.entity_para",
{ unit: pickableUnit }
)}
</p>
<p>
${this.hass.localize("ui.panel.config.energy.gas.dialog.note_para")}
</p>
@@ -169,9 +175,28 @@ export class DialogEnergyGasSettings
)}
.excludeStatistics=${this._excludeList}
@value-changed=${this._statisticChanged}
.helper=${this.hass.localize(
"ui.panel.config.energy.gas.dialog.entity_para",
{ unit: pickableUnit }
)}
autofocus
></ha-statistic-picker>
<ha-statistic-picker
.hass=${this.hass}
.includeUnitClass=${flowRateUnitClasses}
.value=${this._source.stat_rate}
.label=${this.hass.localize(
"ui.panel.config.energy.gas.dialog.gas_flow_rate"
)}
.excludeStatistics=${this._excludeListFlowRate}
@value-changed=${this._flowRateStatisticChanged}
.helper=${this.hass.localize(
"ui.panel.config.energy.gas.dialog.flow_rate_para",
{ unit: this._flow_rate_units?.join(", ") || "" }
)}
></ha-statistic-picker>
<p>
${this.hass.localize("ui.panel.config.energy.gas.dialog.cost_para")}
</p>
@@ -341,6 +366,13 @@ export class DialogEnergyGasSettings
};
}
private _flowRateStatisticChanged(ev: ValueChangedEvent<string>) {
this._source = {
...this._source!,
stat_rate: ev.detail.value || undefined,
};
}
private async _statisticChanged(ev: ValueChangedEvent<string>) {
if (ev.detail.value) {
const metadata = await getStatisticMetadata(this.hass, [ev.detail.value]);
@@ -380,6 +412,10 @@ export class DialogEnergyGasSettings
haStyle,
haStyleDialog,
css`
ha-statistic-picker {
display: block;
margin-bottom: var(--ha-space-4);
}
ha-formfield {
display: block;
}

View File

@@ -24,6 +24,8 @@ import { haStyle, haStyleDialog } from "../../../../resources/styles";
import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
import type { EnergySettingsWaterDialogParams } from "./show-dialogs-energy";
const flowRateUnitClasses = ["volume_flow_rate"];
@customElement("dialog-energy-water-settings")
export class DialogEnergyWaterSettings
extends LitElement
@@ -41,10 +43,14 @@ export class DialogEnergyWaterSettings
@state() private _water_units?: string[];
@state() private _flow_rate_units?: string[];
@state() private _error?: string;
private _excludeList?: string[];
private _excludeListFlowRate?: string[];
public async showDialog(
params: EnergySettingsWaterDialogParams
): Promise<void> {
@@ -62,9 +68,15 @@ export class DialogEnergyWaterSettings
this._water_units = (
await getSensorDeviceClassConvertibleUnits(this.hass, "water")
).units;
this._flow_rate_units = (
await getSensorDeviceClassConvertibleUnits(this.hass, "volume_flow_rate")
).units;
this._excludeList = this._params.water_sources
.map((entry) => entry.stat_energy_from)
.filter((id) => id !== this._source?.stat_energy_from);
this._excludeListFlowRate = this._params.water_sources
.map((entry) => entry.stat_rate)
.filter((id) => id && id !== this._source?.stat_rate) as string[];
this._open = true;
}
@@ -79,6 +91,7 @@ export class DialogEnergyWaterSettings
this._source = undefined;
this._error = undefined;
this._excludeList = undefined;
this._excludeListFlowRate = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -108,19 +121,6 @@ export class DialogEnergyWaterSettings
@closed=${this._dialogClosed}
>
${this._error ? html`<p class="error">${this._error}</p>` : ""}
<div>
<p>
${this.hass.localize(
"ui.panel.config.energy.water.dialog.paragraph"
)}
</p>
<p>
${this.hass.localize(
"ui.panel.config.energy.water.dialog.entity_para",
{ unit: pickableUnit }
)}
</p>
</div>
<ha-statistic-picker
.hass=${this.hass}
@@ -133,9 +133,28 @@ export class DialogEnergyWaterSettings
)}
.excludeStatistics=${this._excludeList}
@value-changed=${this._statisticChanged}
.helper=${this.hass.localize(
"ui.panel.config.energy.water.dialog.entity_para",
{ unit: pickableUnit }
)}
autofocus
></ha-statistic-picker>
<ha-statistic-picker
.hass=${this.hass}
.includeUnitClass=${flowRateUnitClasses}
.value=${this._source.stat_rate}
.label=${this.hass.localize(
"ui.panel.config.energy.water.dialog.water_flow_rate"
)}
.excludeStatistics=${this._excludeListFlowRate}
@value-changed=${this._flowRateStatisticChanged}
.helper=${this.hass.localize(
"ui.panel.config.energy.water.dialog.flow_rate_para",
{ unit: this._flow_rate_units?.join(", ") || "" }
)}
></ha-statistic-picker>
<p>
${this.hass.localize("ui.panel.config.energy.water.dialog.cost_para")}
</p>
@@ -287,6 +306,13 @@ export class DialogEnergyWaterSettings
};
}
private _flowRateStatisticChanged(ev: ValueChangedEvent<string>) {
this._source = {
...this._source!,
stat_rate: ev.detail.value || undefined,
};
}
private async _statisticChanged(ev: ValueChangedEvent<string>) {
if (
ev.detail.value &&
@@ -320,6 +346,10 @@ export class DialogEnergyWaterSettings
haStyle,
haStyleDialog,
css`
ha-statistic-picker {
display: block;
margin-bottom: var(--ha-space-4);
}
ha-formfield {
display: block;
}

View File

@@ -3936,7 +3936,9 @@
"cost_entity_helper_volume": "volume",
"cost_number": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_number%]",
"cost_number_input": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_number%]",
"gas_usage": "Gas usage"
"gas_usage": "Gas consumption",
"gas_flow_rate": "Gas flow rate",
"flow_rate_para": "Optionally pick a sensor which measures the gas flow rate in either of {unit}."
}
},
"water": {
@@ -3960,7 +3962,9 @@
"cost_entity_helper": "Any entity with a unit of `{currency}/(valid water unit)` (e.g. `{currency}/gal` or `{currency}/m³`) may be used and will be automatically converted.",
"cost_number": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_number%]",
"cost_number_input": "[%key:ui::panel::config::energy::grid::flow_dialog::from::cost_number%]",
"water_usage": "Water usage"
"water_usage": "Water consumption",
"water_flow_rate": "Water flow rate",
"flow_rate_para": "Optionally pick a sensor which measures the water flow rate in either of {unit}."
}
},
"device_consumption": {
@@ -3992,7 +3996,8 @@
"header": "Add a water device",
"display_name": "Display name",
"device_consumption_water": "Device water consumption",
"selected_stat_intro": "Select the water sensor that measures the device's water usage in either of {unit}.",
"device_consumption_water_flow_rate": "Device water flow rate",
"selected_stat_intro": "Select the water sensor that measures the device's water consumption in either of {unit}.",
"included_in_device": "Upstream device",
"included_in_device_helper": "If this device is already counted by another device (such as a water meter measured by the main water supply), selecting the upstream device prevents duplicate water tracking.",
"no_upstream_devices": "No eligible upstream devices"