diff --git a/gallery/src/pages/components/ha-form.ts b/gallery/src/pages/components/ha-form.ts
index 4d8ad42b73..54d8f709da 100644
--- a/gallery/src/pages/components/ha-form.ts
+++ b/gallery/src/pages/components/ha-form.ts
@@ -136,6 +136,11 @@ 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: "" } },
diff --git a/gallery/src/pages/components/ha-selector.ts b/gallery/src/pages/components/ha-selector.ts
index 6ce04e0995..6549c8e30e 100644
--- a/gallery/src/pages/components/ha-selector.ts
+++ b/gallery/src/pages/components/ha-selector.ts
@@ -115,6 +115,10 @@ const SCHEMAS: {
name: "One of each",
input: {
entity: { name: "Entity", selector: { entity: {} } },
+ state: {
+ name: "State",
+ selector: { state: { entity_id: "alarm_control_panel.alarm" } },
+ },
attribute: {
name: "Attribute",
selector: { attribute: { entity_id: "" } },
diff --git a/src/common/entity/get_states.ts b/src/common/entity/get_states.ts
new file mode 100644
index 0000000000..c40ad804d0
--- /dev/null
+++ b/src/common/entity/get_states.ts
@@ -0,0 +1,89 @@
+import { HassEntity } from "home-assistant-js-websocket";
+import { computeStateDomain } from "./compute_state_domain";
+import { UNAVAILABLE_STATES } from "../../data/entity";
+
+const FIXED_DOMAIN_STATES = {
+ alarm_control_panel: [
+ "armed_away",
+ "armed_custom_bypass",
+ "armed_home",
+ "armed_night",
+ "armed_vacation",
+ "arming",
+ "disarmed",
+ "disarming",
+ "pending",
+ "triggered",
+ ],
+ automation: ["on", "off"],
+ binary_sensor: ["on", "off"],
+ button: [],
+ calendar: ["on", "off"],
+ camera: ["idle", "recording", "streaming"],
+ cover: ["closed", "closing", "open", "opening"],
+ device_tracker: ["home", "not_home"],
+ fan: ["on", "off"],
+ humidifier: ["on", "off"],
+ input_boolean: ["on", "off"],
+ input_button: [],
+ light: ["on", "off"],
+ lock: ["jammed", "locked", "locking", "unlocked", "unlocking"],
+ media_player: ["idle", "off", "paused", "playing", "standby"],
+ person: ["home", "not_home"],
+ remote: ["on", "off"],
+ scene: [],
+ schedule: ["on", "off"],
+ script: ["on", "off"],
+ siren: ["on", "off"],
+ sun: ["above_horizon", "below_horizon"],
+ switch: ["on", "off"],
+ update: ["on", "off"],
+ vacuum: ["cleaning", "docked", "error", "idle", "paused", "returning"],
+ weather: [
+ "clear-night",
+ "cloudy",
+ "exceptional",
+ "fog",
+ "hail",
+ "lightning-rainy",
+ "lightning",
+ "partlycloudy",
+ "pouring",
+ "rainy",
+ "snowy-rainy",
+ "snowy",
+ "sunny",
+ "windy-variant",
+ "windy",
+ ],
+};
+
+export const getStates = (state: HassEntity): string[] => {
+ const domain = computeStateDomain(state);
+ const result: string[] = [];
+
+ if (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);
+ }
+
+ // Dynamic values based on the entities
+ switch (domain) {
+ case "climate":
+ result.push(...state.attributes.hvac_modes);
+ break;
+ case "input_select":
+ case "select":
+ result.push(...state.attributes.options);
+ break;
+ case "water_heater":
+ result.push(...state.attributes.operation_list);
+ break;
+ }
+
+ // 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
new file mode 100644
index 0000000000..968bdedf46
--- /dev/null
+++ b/src/components/entity/ha-entity-state-picker.ts
@@ -0,0 +1,107 @@
+import { HassEntity } from "home-assistant-js-websocket";
+import { html, LitElement, PropertyValues, TemplateResult } from "lit";
+import { customElement, property, query } from "lit/decorators";
+import { computeStateDisplay } from "../../common/entity/compute_state_display";
+import { PolymerChangedEvent } from "../../polymer-types";
+import { getStates } from "../../common/entity/get_states";
+import { HomeAssistant } from "../../types";
+import "../ha-combo-box";
+import type { HaComboBox } from "../ha-combo-box";
+
+export type HaEntityPickerEntityFilterFunc = (entityId: HassEntity) => boolean;
+
+@customElement("ha-entity-state-picker")
+class HaEntityStatePicker extends LitElement {
+ @property({ attribute: false }) public hass!: HomeAssistant;
+
+ @property() public entityId?: string;
+
+ @property({ type: Boolean }) public autofocus = false;
+
+ @property({ type: Boolean }) public disabled = false;
+
+ @property({ type: Boolean }) public required = false;
+
+ @property({ type: Boolean, attribute: "allow-custom-value" })
+ public allowCustomValue;
+
+ @property() public label?: string;
+
+ @property() public value?: string;
+
+ @property() public helper?: string;
+
+ @property({ type: Boolean }) private _opened = false;
+
+ @query("ha-combo-box", true) private _comboBox!: HaComboBox;
+
+ protected shouldUpdate(changedProps: PropertyValues) {
+ return !(!changedProps.has("_opened") && this._opened);
+ }
+
+ protected updated(changedProps: PropertyValues) {
+ if (changedProps.has("_opened") && this._opened) {
+ const state = this.entityId ? this.hass.states[this.entityId] : undefined;
+ (this._comboBox as any).items =
+ this.entityId && state
+ ? getStates(state).map((key) => ({
+ value: key,
+ label: computeStateDisplay(
+ this.hass.localize,
+ state,
+ this.hass.locale,
+ key
+ ),
+ }))
+ : [];
+ }
+ }
+
+ protected render(): TemplateResult {
+ if (!this.hass) {
+ return html``;
+ }
+
+ return html`
+
+
+ `;
+ }
+
+ private _openedChanged(ev: PolymerChangedEvent) {
+ this._opened = ev.detail.value;
+ }
+
+ private _valueChanged(ev: PolymerChangedEvent) {
+ this.value = ev.detail.value;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-entity-state-picker": HaEntityStatePicker;
+ }
+}
diff --git a/src/components/ha-selector/ha-selector-state.ts b/src/components/ha-selector/ha-selector-state.ts
new file mode 100644
index 0000000000..4f2a39c540
--- /dev/null
+++ b/src/components/ha-selector/ha-selector-state.ts
@@ -0,0 +1,49 @@
+import { html, LitElement } from "lit";
+import { customElement, property } from "lit/decorators";
+import { StateSelector } from "../../data/selector";
+import { SubscribeMixin } from "../../mixins/subscribe-mixin";
+import { HomeAssistant } from "../../types";
+import "../entity/ha-entity-state-picker";
+
+@customElement("ha-selector-state")
+export class HaSelectorState extends SubscribeMixin(LitElement) {
+ @property() public hass!: HomeAssistant;
+
+ @property() public selector!: StateSelector;
+
+ @property() public value?: any;
+
+ @property() public label?: string;
+
+ @property() public helper?: string;
+
+ @property({ type: Boolean }) public disabled = false;
+
+ @property({ type: Boolean }) public required = true;
+
+ @property() public context?: {
+ filter_entity?: string;
+ };
+
+ protected render() {
+ return html`
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ "ha-selector-state": HaSelectorState;
+ }
+}
diff --git a/src/components/ha-selector/ha-selector.ts b/src/components/ha-selector/ha-selector.ts
index 7d6400b289..cc13845e01 100644
--- a/src/components/ha-selector/ha-selector.ts
+++ b/src/components/ha-selector/ha-selector.ts
@@ -18,6 +18,7 @@ import "./ha-selector-file";
import "./ha-selector-number";
import "./ha-selector-object";
import "./ha-selector-select";
+import "./ha-selector-state";
import "./ha-selector-target";
import "./ha-selector-template";
import "./ha-selector-text";
diff --git a/src/data/selector.ts b/src/data/selector.ts
index 86d6d0eda9..17474651b1 100644
--- a/src/data/selector.ts
+++ b/src/data/selector.ts
@@ -23,6 +23,7 @@ export type Selector =
| NumberSelector
| ObjectSelector
| SelectSelector
+ | StateSelector
| StringSelector
| TargetSelector
| TemplateSelector
@@ -191,6 +192,12 @@ export interface SelectSelector {
};
}
+export interface StateSelector {
+ state: {
+ entity_id?: string;
+ };
+}
+
export interface StringSelector {
text: {
multiline?: boolean;
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 ad0f86c146..310dc88d01 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
@@ -37,7 +37,7 @@ export class HaStateCondition extends LitElement implements ConditionElement {
name: "attribute",
selector: { attribute: { entity_id: entityId } },
},
- { name: "state", selector: { text: {} } },
+ { name: "state", selector: { state: { entity_id: entityId } } },
{ name: "for", selector: { duration: {} } },
] as const
);
diff --git a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts
index 00ded53fac..6a24bf98d5 100644
--- a/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts
+++ b/src/panels/config/automation/trigger/types/ha-automation-trigger-state.ts
@@ -56,8 +56,18 @@ export class HaStateTrigger extends LitElement implements TriggerElement {
name: "attribute",
selector: { attribute: { entity_id: entityId } },
},
- { name: "from", selector: { text: {} } },
- { name: "to", selector: { text: {} } },
+ {
+ name: "from",
+ selector: {
+ state: { entity_id: entityId ? entityId[0] : undefined },
+ },
+ },
+ {
+ name: "to",
+ selector: {
+ state: { entity_id: entityId ? entityId[0] : undefined },
+ },
+ },
{ name: "for", selector: { duration: {} } },
] as const
);
diff --git a/src/translations/en.json b/src/translations/en.json
index 99d5bf27b0..0414bfa152 100755
--- a/src/translations/en.json
+++ b/src/translations/en.json
@@ -400,6 +400,9 @@
"entity-attribute-picker": {
"attribute": "Attribute",
"show_attributes": "Show attributes"
+ },
+ "entity-state-picker": {
+ "state": "State"
}
},
"target-picker": {