diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts index 54d8f709da..8117ac826b 100644 --- a/gallery/src/pages/components/ha-form.ts +++ b/gallery/src/pages/components/ha-form.ts @@ -20,16 +20,22 @@ const ENTITIES = [ }), getEntity("media_player", "livingroom", "playing", { friendly_name: "Livingroom", + media_content_type: "music", + device_class: "tv", }), getEntity("media_player", "lounge", "idle", { friendly_name: "Lounge", supported_features: 444983, + device_class: "speaker", }), getEntity("light", "bedroom", "on", { friendly_name: "Bedroom", + effect: "colorloop", + effect_list: ["colorloop", "random"], }), getEntity("switch", "coffee", "off", { friendly_name: "Coffee", + device_class: "switch", }), ]; @@ -136,16 +142,16 @@ const SCHEMAS: { schema: [ { name: "addon", selector: { addon: {} } }, { name: "entity", selector: { entity: {} } }, - { - name: "State", - selector: { state: { entity_id: "" } }, - context: { filter_entity: "entity" }, - }, { name: "Attribute", selector: { attribute: { entity_id: "" } }, context: { filter_entity: "entity" }, }, + { + name: "State", + selector: { state: { entity_id: "" } }, + context: { filter_entity: "entity", filter_attribute: "Attribute" }, + }, { name: "Device", selector: { device: {} } }, { name: "Duration", selector: { duration: {} } }, { name: "area", selector: { area: {} } }, diff --git a/src/common/entity/get_states.ts b/src/common/entity/get_states.ts index c40ad804d0..7adddf87e1 100644 --- a/src/common/entity/get_states.ts +++ b/src/common/entity/get_states.ts @@ -58,32 +58,220 @@ const FIXED_DOMAIN_STATES = { ], }; -export const getStates = (state: HassEntity): string[] => { +const FIXED_DOMAIN_ATTRIBUTE_STATES = { + alarm_control_panel: { + code_format: ["number", "text"], + }, + binary_sensor: { + device_class: [ + "battery", + "battery_charging", + "co", + "cold", + "connectivity", + "door", + "garage_door", + "gas", + "heat", + "light", + "lock", + "moisture", + "motion", + "moving", + "occupancy", + "opening", + "plug", + "power", + "presence", + "problem", + "running", + "safety", + "smoke", + "sound", + "tamper", + "update", + "vibration", + "window", + ], + }, + button: { + device_class: ["restart", "update"], + }, + camera: { + frontend_stream_type: ["hls", "web_rtc"], + }, + climate: { + hvac_action: ["off", "idle", "heating", "cooling", "drying", "fan"], + }, + cover: { + device_class: [ + "awning", + "blind", + "curtain", + "damper", + "door", + "garage", + "gate", + "shade", + "shutter", + "window", + ], + }, + humidifier: { + device_class: ["humidifier", "dehumidifier"], + }, + media_player: { + device_class: ["tv", "speaker", "receiver"], + media_content_type: [ + "app", + "channel", + "episode", + "game", + "image", + "movie", + "music", + "playlist", + "tvshow", + "url", + "video", + ], + }, + number: { + device_class: ["temperature"], + }, + sensor: { + device_class: [ + "apparent_power", + "aqi", + "battery", + "carbon_dioxide", + "carbon_monoxide", + "current", + "date", + "duration", + "energy", + "frequency", + "gas", + "humidity", + "illuminance", + "monetary", + "nitrogen_dioxide", + "nitrogen_monoxide", + "nitrous_oxide", + "ozone", + "pm1", + "pm10", + "pm25", + "power_factor", + "power", + "pressure", + "reactive_power", + "signal_strength", + "sulphur_dioxide", + "temperature", + "timestamp", + "volatile_organic_compounds", + "voltage", + ], + state_class: ["measurement", "total", "total_increasing"], + }, + switch: { + device_class: ["outlet", "switch"], + }, + update: { + device_class: ["firmware"], + }, + water_heater: { + away_mode: ["on", "off"], + }, +}; + +export const getStates = ( + state: HassEntity, + attribute: string | undefined = undefined +): string[] => { const domain = computeStateDomain(state); const result: string[] = []; - if (domain in FIXED_DOMAIN_STATES) { + if (!attribute && domain in FIXED_DOMAIN_STATES) { result.push(...FIXED_DOMAIN_STATES[domain]); - } else { - // If not fixed, we at least know the current state - result.push(state.state); + } else if ( + attribute && + domain in FIXED_DOMAIN_ATTRIBUTE_STATES && + attribute in FIXED_DOMAIN_ATTRIBUTE_STATES[domain] + ) { + result.push(...FIXED_DOMAIN_ATTRIBUTE_STATES[domain][attribute]); } // Dynamic values based on the entities switch (domain) { case "climate": - result.push(...state.attributes.hvac_modes); + if (!attribute) { + result.push(...state.attributes.hvac_modes); + } else if (attribute === "fan_mode") { + result.push(...state.attributes.fan_modes); + } else if (attribute === "preset_mode") { + result.push(...state.attributes.preset_modes); + } else if (attribute === "swing_mode") { + result.push(...state.attributes.swing_modes); + } + break; + case "device_tracker": + case "person": + if (!attribute) { + result.push("home", "not_home"); + } + break; + case "fan": + if (attribute === "preset_mode") { + result.push(...state.attributes.preset_modes); + } + break; + case "humidifier": + if (attribute === "mode") { + result.push(...state.attributes.available_modes); + } break; case "input_select": case "select": - result.push(...state.attributes.options); + if (!attribute) { + result.push(...state.attributes.options); + } + break; + case "light": + if (attribute === "effect") { + result.push(...state.attributes.effect_list); + } else if (attribute === "color_mode") { + result.push(...state.attributes.color_modes); + } + break; + case "media_player": + if (attribute === "sound_mode") { + result.push(...state.attributes.sound_mode_list); + } else if (attribute === "source") { + result.push(...state.attributes.source_list); + } + break; + case "remote": + if (attribute === "current_activity") { + result.push(...state.attributes.activity_list); + } + break; + case "vacuum": + if (attribute === "fan_speed") { + result.push(...state.attributes.fan_speed_list); + } break; case "water_heater": - result.push(...state.attributes.operation_list); + if (!attribute || attribute === "operation_mode") { + result.push(...state.attributes.operation_list); + } break; } - // All entities can have unavailable states - result.push(...UNAVAILABLE_STATES); + if (!attribute) { + // All entities can have unavailable states + result.push(...UNAVAILABLE_STATES); + } return [...new Set(result)]; }; diff --git a/src/components/entity/ha-entity-state-picker.ts b/src/components/entity/ha-entity-state-picker.ts index 968bdedf46..c9c8965e94 100644 --- a/src/components/entity/ha-entity-state-picker.ts +++ b/src/components/entity/ha-entity-state-picker.ts @@ -16,6 +16,8 @@ class HaEntityStatePicker extends LitElement { @property() public entityId?: string; + @property() public attribute?: string; + @property({ type: Boolean }) public autofocus = false; @property({ type: Boolean }) public disabled = false; @@ -44,14 +46,16 @@ class HaEntityStatePicker extends LitElement { const state = this.entityId ? this.hass.states[this.entityId] : undefined; (this._comboBox as any).items = this.entityId && state - ? getStates(state).map((key) => ({ + ? getStates(state, this.attribute).map((key) => ({ value: key, - label: computeStateDisplay( - this.hass.localize, - state, - this.hass.locale, - key - ), + label: !this.attribute + ? computeStateDisplay( + this.hass.localize, + state, + this.hass.locale, + key + ) + : key, })) : []; } diff --git a/src/components/ha-selector/ha-selector-state.ts b/src/components/ha-selector/ha-selector-state.ts index 4f2a39c540..b72b0349bd 100644 --- a/src/components/ha-selector/ha-selector-state.ts +++ b/src/components/ha-selector/ha-selector-state.ts @@ -22,6 +22,7 @@ export class HaSelectorState extends SubscribeMixin(LitElement) { @property({ type: Boolean }) public required = true; @property() public context?: { + filter_attribute?: string; filter_entity?: string; }; @@ -31,6 +32,8 @@ export class HaSelectorState extends SubscribeMixin(LitElement) { .hass=${this.hass} .entityId=${this.selector.state.entity_id || this.context?.filter_entity} + .attribute=${this.selector.state.attribute || + this.context?.filter_attribute} .value=${this.value} .label=${this.label} .helper=${this.helper} diff --git a/src/data/selector.ts b/src/data/selector.ts index 17474651b1..7514c0fd1b 100644 --- a/src/data/selector.ts +++ b/src/data/selector.ts @@ -195,6 +195,7 @@ export interface SelectSelector { export interface StateSelector { state: { entity_id?: string; + attribute?: string; }; } diff --git a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts index 310dc88d01..0a72d74a86 100644 --- a/src/panels/config/automation/condition/types/ha-automation-condition-state.ts +++ b/src/panels/config/automation/condition/types/ha-automation-condition-state.ts @@ -30,14 +30,17 @@ export class HaStateCondition extends LitElement implements ConditionElement { } private _schema = memoizeOne( - (entityId) => + (entityId, attribute) => [ { name: "entity_id", required: true, selector: { entity: {} } }, { name: "attribute", selector: { attribute: { entity_id: entityId } }, }, - { name: "state", selector: { state: { entity_id: entityId } } }, + { + name: "state", + selector: { state: { entity_id: entityId, attribute: attribute } }, + }, { name: "for", selector: { duration: {} } }, ] as const ); @@ -57,7 +60,10 @@ export class HaStateCondition extends LitElement implements ConditionElement { protected render() { const trgFor = createDurationData(this.condition.for); const data = { ...this.condition, for: trgFor }; - const schema = this._schema(this.condition.entity_id); + const schema = this._schema( + this.condition.entity_id, + this.condition.attribute + ); return html` + (entityId, attribute) => [ { name: "entity_id", @@ -61,13 +61,19 @@ export class HaStateTrigger extends LitElement implements TriggerElement { { name: "from", selector: { - state: { entity_id: entityId ? entityId[0] : undefined }, + state: { + entity_id: entityId ? entityId[0] : undefined, + attribute: attribute, + }, }, }, { name: "to", selector: { - state: { entity_id: entityId ? entityId[0] : undefined }, + state: { + entity_id: entityId ? entityId[0] : undefined, + attribute: attribute, + }, }, }, { name: "for", selector: { duration: {} } }, @@ -111,7 +117,7 @@ export class HaStateTrigger extends LitElement implements TriggerElement { entity_id: ensureArray(this.trigger.entity_id), for: trgFor, }; - const schema = this._schema(this.trigger.entity_id); + const schema = this._schema(this.trigger.entity_id, this.trigger.attribute); return html`