From 9e868e144df30ff716c68350eaca958f5074cccb Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 21 Jan 2025 11:37:51 -0500 Subject: [PATCH] Allow storing temperature/humidity entities on an area (#23822) * Allow storing temperature/humidity entities on an area * Update objects after improved types --- gallery/src/pages/components/ha-form.ts | 6 + gallery/src/pages/components/ha-selector.ts | 6 + src/components/ha-area-picker.ts | 8 ++ src/data/area_registry.ts | 18 +-- src/data/sensor.ts | 2 + .../areas/dialog-area-registry-detail.ts | 115 +++++++++++++++--- src/translations/en.json | 6 +- 7 files changed, 139 insertions(+), 22 deletions(-) diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index 2f9b012877..8fc4b7390a 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -124,6 +124,8 @@ const AREAS: AreaRegistryEntry[] = [ picture: null, aliases: [], labels: [], + temperature_entity_id: null, + humidity_entity_id: null, created_at: 0, modified_at: 0, }, @@ -135,6 +137,8 @@ const AREAS: AreaRegistryEntry[] = [ picture: null, aliases: [], labels: [], + temperature_entity_id: null, + humidity_entity_id: null, created_at: 0, modified_at: 0, }, @@ -146,6 +150,8 @@ const AREAS: AreaRegistryEntry[] = [ picture: null, aliases: [], labels: [], + temperature_entity_id: null, + humidity_entity_id: null, created_at: 0, modified_at: 0, }, diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts index f9eb0e6d0e..0601ae66af 100644 --- a/gallery/src/pages/components/ha-selector.ts +++ b/gallery/src/pages/components/ha-selector.ts @@ -123,6 +123,8 @@ const AREAS: AreaRegistryEntry[] = [ picture: null, aliases: [], labels: [], + temperature_entity_id: null, + humidity_entity_id: null, created_at: 0, modified_at: 0, }, @@ -134,6 +136,8 @@ const AREAS: AreaRegistryEntry[] = [ picture: null, aliases: [], labels: [], + temperature_entity_id: null, + humidity_entity_id: null, created_at: 0, modified_at: 0, }, @@ -145,6 +149,8 @@ const AREAS: AreaRegistryEntry[] = [ picture: null, aliases: [], labels: [], + temperature_entity_id: null, + humidity_entity_id: null, created_at: 0, modified_at: 0, }, diff --git a/src/components/ha-area-picker.ts b/src/components/ha-area-picker.ts index 9b0c6cd31f..cbad36bc03 100644 --- a/src/components/ha-area-picker.ts +++ b/src/components/ha-area-picker.ts @@ -276,6 +276,8 @@ export class HaAreaPicker extends LitElement { icon: null, aliases: [], labels: [], + temperature_entity_id: null, + humidity_entity_id: null, created_at: 0, modified_at: 0, }, @@ -294,6 +296,8 @@ export class HaAreaPicker extends LitElement { icon: "mdi:plus", aliases: [], labels: [], + temperature_entity_id: null, + humidity_entity_id: null, created_at: 0, modified_at: 0, }, @@ -378,6 +382,8 @@ export class HaAreaPicker extends LitElement { picture: null, labels: [], aliases: [], + temperature_entity_id: null, + humidity_entity_id: null, created_at: 0, modified_at: 0, }, @@ -396,6 +402,8 @@ export class HaAreaPicker extends LitElement { picture: null, labels: [], aliases: [], + temperature_entity_id: null, + humidity_entity_id: null, created_at: 0, modified_at: 0, }, diff --git a/src/data/area_registry.ts b/src/data/area_registry.ts index e70230b2e1..2fca94fcaa 100644 --- a/src/data/area_registry.ts +++ b/src/data/area_registry.ts @@ -7,13 +7,15 @@ import type { RegistryEntry } from "./registry"; export { subscribeAreaRegistry } from "./ws-area_registry"; export interface AreaRegistryEntry extends RegistryEntry { + aliases: string[]; area_id: string; floor_id: string | null; - name: string; - picture: string | null; + humidity_entity_id: string | null; icon: string | null; labels: string[]; - aliases: string[]; + name: string; + picture: string | null; + temperature_entity_id: string | null; } export type AreaEntityLookup = Record; @@ -21,12 +23,14 @@ export type AreaEntityLookup = Record; export type AreaDeviceLookup = Record; export interface AreaRegistryEntryMutableParams { - name: string; - floor_id?: string | null; - picture?: string | null; - icon?: string | null; aliases?: string[]; + floor_id?: string | null; + humidity_entity_id?: string | null; + icon?: string | null; labels?: string[]; + name: string; + picture?: string | null; + temperature_entity_id?: string | null; } export const createAreaRegistryEntry = ( diff --git a/src/data/sensor.ts b/src/data/sensor.ts index 1d114881f9..a97fcf5115 100644 --- a/src/data/sensor.ts +++ b/src/data/sensor.ts @@ -2,6 +2,8 @@ import type { HomeAssistant } from "../types"; export const SENSOR_DEVICE_CLASS_BATTERY = "battery"; export const SENSOR_DEVICE_CLASS_TIMESTAMP = "timestamp"; +export const SENSOR_DEVICE_CLASS_TEMPERATURE = "temperature"; +export const SENSOR_DEVICE_CLASS_HUMIDITY = "humidity"; export interface SensorDeviceClassUnits { units: string[]; diff --git a/src/panels/config/areas/dialog-area-registry-detail.ts b/src/panels/config/areas/dialog-area-registry-detail.ts index ed4051ba98..26f98283b4 100644 --- a/src/panels/config/areas/dialog-area-registry-detail.ts +++ b/src/panels/config/areas/dialog-area-registry-detail.ts @@ -3,6 +3,7 @@ import "@material/mwc-list/mwc-list"; import type { CSSResultGroup } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { property, state } from "lit/decorators"; +import type { HassEntity } from "home-assistant-js-websocket"; import { fireEvent } from "../../../common/dom/fire_event"; import "../../../components/ha-alert"; import "../../../components/ha-aliases-editor"; @@ -12,6 +13,8 @@ import type { HaPictureUpload } from "../../../components/ha-picture-upload"; import "../../../components/ha-settings-row"; import "../../../components/ha-icon-picker"; import "../../../components/ha-floor-picker"; +import "../../../components/entity/ha-entity-picker"; +import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker"; import "../../../components/ha-textfield"; import "../../../components/ha-labels-picker"; import type { AreaRegistryEntryMutableParams } from "../../../data/area_registry"; @@ -19,6 +22,10 @@ import type { CropOptions } from "../../../dialogs/image-cropper-dialog/show-ima import { haStyleDialog } from "../../../resources/styles"; import type { HomeAssistant, ValueChangedEvent } from "../../../types"; import type { AreaRegistryDetailDialogParams } from "./show-dialog-area-registry-detail"; +import { + SENSOR_DEVICE_CLASS_HUMIDITY, + SENSOR_DEVICE_CLASS_TEMPERATURE, +} from "../../../data/sensor"; const cropOptions: CropOptions = { round: false, @@ -27,6 +34,10 @@ const cropOptions: CropOptions = { aspectRatio: 1.78, }; +const SENSOR_DOMAINS = ["sensor"]; +const TEMPERATURE_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_TEMPERATURE]; +const HUMIDITY_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_HUMIDITY]; + class DialogAreaDetail extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @@ -42,6 +53,10 @@ class DialogAreaDetail extends LitElement { @state() private _floor!: string | null; + @state() private _temperatureEntity!: string | null; + + @state() private _humidityEntity!: string | null; + @state() private _error?: string; @state() private _params?: AreaRegistryDetailDialogParams; @@ -53,14 +68,26 @@ class DialogAreaDetail extends LitElement { ): Promise { this._params = params; this._error = undefined; - this._name = this._params.entry - ? this._params.entry.name - : this._params.suggestedName || ""; - this._aliases = this._params.entry ? this._params.entry.aliases : []; - this._labels = this._params.entry ? this._params.entry.labels : []; - this._picture = this._params.entry?.picture || null; - this._icon = this._params.entry?.icon || null; - this._floor = this._params.entry?.floor_id || null; + if (this._params.entry) { + this._name = this._params.entry.name; + this._aliases = this._params.entry.aliases; + this._labels = this._params.entry.labels; + this._picture = this._params.entry.picture; + this._icon = this._params.entry.icon; + this._floor = this._params.entry.floor_id; + this._temperatureEntity = this._params.entry.temperature_entity_id; + this._humidityEntity = this._params.entry.humidity_entity_id; + } else { + this._name = this._params.suggestedName || ""; + this._aliases = []; + this._labels = []; + this._picture = null; + this._icon = null; + this._floor = null; + this._temperatureEntity = null; + this._humidityEntity = null; + } + await this.updateComplete; } @@ -76,6 +103,7 @@ class DialogAreaDetail extends LitElement { } const entry = this._params.entry; const nameInvalid = !this._isNameValid(); + const isNew = !entry; return html` + + ${!isNew + ? html` + + + + ` + : ""} @@ -183,6 +245,22 @@ class DialogAreaDetail extends LitElement { return this._name.trim() !== ""; } + private _areaEntityFilter = (stateObj: HassEntity): boolean => { + const entityReg = this.hass.entities[stateObj.entity_id]; + if (!entityReg) { + return false; + } + const areaId = this._params!.entry!.area_id; + if (entityReg.area_id === areaId) { + return true; + } + if (!entityReg.device_id) { + return false; + } + const deviceReg = this.hass.devices[entityReg.device_id]; + return deviceReg && deviceReg.area_id === areaId; + }; + private _nameChanged(ev) { this._error = undefined; this._name = ev.target.value; @@ -208,6 +286,16 @@ class DialogAreaDetail extends LitElement { this._picture = (ev.target as HaPictureUpload).value; } + private _aliasesChanged(ev: CustomEvent): void { + this._aliases = ev.detail.value; + } + + private _sensorChanged(ev: CustomEvent): void { + const deviceClass = (ev.target as HaEntityPicker).includeDeviceClasses![0]; + const key = `_${deviceClass}Entity`; + this[key] = ev.detail.value || null; + } + private async _updateEntry() { const create = !this._params!.entry; this._submitting = true; @@ -219,6 +307,8 @@ class DialogAreaDetail extends LitElement { floor_id: this._floor || (create ? undefined : null), labels: this._labels || null, aliases: this._aliases, + temperature_entity_id: this._temperatureEntity, + humidity_entity_id: this._humidityEntity, }; if (create) { await this._params!.createEntry!(values); @@ -235,17 +325,14 @@ class DialogAreaDetail extends LitElement { } } - private _aliasesChanged(ev: CustomEvent): void { - this._aliases = ev.detail.value; - } - static get styles(): CSSResultGroup { return [ haStyleDialog, css` - ha-textfield, - ha-icon-picker, + ha-aliases-editor, + ha-entity-picker, ha-floor-picker, + ha-icon-picker, ha-labels-picker, ha-picture-upload { display: block; diff --git a/src/translations/en.json b/src/translations/en.json index a09048aded..f21285eb73 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2187,7 +2187,11 @@ "aliases_section": "Aliases", "no_aliases": "No configured aliases", "configured_aliases": "{count} configured {count, plural,\n one {alias}\n other {aliases}\n}", - "aliases_description": "Aliases are alternative names used in voice assistants to refer to this area." + "aliases_description": "Aliases are alternative names used in voice assistants to refer to this area.", + "temperature_entity": "Temperature sensor", + "temperature_entity_description": "Sensor that represents the area temperature.", + "humidity_entity": "Humidity sensor", + "humidity_entity_description": "Sensor that represents the area humidity." }, "delete": { "confirmation_title": "Delete {name}?",