diff --git a/src/panels/lovelace/cards/hui-alarm-panel-card.ts b/src/panels/lovelace/cards/hui-alarm-panel-card.ts index b35235bd2d..34585865c6 100644 --- a/src/panels/lovelace/cards/hui-alarm-panel-card.ts +++ b/src/panels/lovelace/cards/hui-alarm-panel-card.ts @@ -9,6 +9,7 @@ import { import { customElement, property, query, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { styleMap } from "lit/directives/style-map"; +import { HassEntity } from "home-assistant-js-websocket"; import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element"; import { fireEvent } from "../../../common/dom/fire_event"; import { alarmPanelIcon } from "../../../common/entity/alarm_panel_icon"; @@ -20,16 +21,48 @@ import type { HaTextField } from "../../../components/ha-textfield"; import { callAlarmAction, FORMAT_NUMBER, + ALARM_MODES, + AlarmMode, } from "../../../data/alarm_control_panel"; import { UNAVAILABLE } from "../../../data/entity"; import type { HomeAssistant } from "../../../types"; import { findEntities } from "../common/find-entities"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import type { LovelaceCard } from "../types"; -import { AlarmPanelCardConfig } from "./types"; +import { AlarmPanelCardConfig, AlarmPanelCardConfigState } from "./types"; +import { supportsFeature } from "../../../common/entity/supports-feature"; const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"]; +export const DEFAULT_STATES = [ + "arm_home", + "arm_away", +] as AlarmPanelCardConfigState[]; + +export const ALARM_MODE_STATE_MAP: Record< + AlarmPanelCardConfigState, + AlarmMode +> = { + arm_home: "armed_home", + arm_away: "armed_away", + arm_night: "armed_night", + arm_vacation: "armed_vacation", + arm_custom_bypass: "armed_custom_bypass", +}; + +export const filterSupportedAlarmStates = ( + stateObj: HassEntity | undefined, + states: AlarmPanelCardConfigState[] +): AlarmPanelCardConfigState[] => + states.filter( + (s) => + stateObj && + supportsFeature( + stateObj, + ALARM_MODES[ALARM_MODE_STATE_MAP[s]].feature || 0 + ) + ); + @customElement("hui-alarm-panel-card") class HuiAlarmPanelCard extends LitElement implements LovelaceCard { public static async getConfigElement() { @@ -52,10 +85,13 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { includeDomains ); + const entity = foundEntities[0] || ""; + const stateObj = hass.states[entity]; + return { type: "alarm-panel", - states: ["arm_home", "arm_away"], - entity: foundEntities[0] || "", + states: filterSupportedAlarmStates(stateObj, DEFAULT_STATES), + entity, }; } @@ -86,11 +122,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { throw new Error("Invalid configuration"); } - const defaults = { - states: ["arm_away", "arm_home"] as const, - }; - - this._config = { ...defaults, ...config }; + this._config = { ...config }; } protected updated(changedProps: PropertyValues): void { @@ -138,6 +170,9 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { return nothing; } const stateObj = this.hass.states[this._config.entity]; + const states = + this._config.states || + filterSupportedAlarmStates(stateObj, DEFAULT_STATES); if (!stateObj) { return html` @@ -170,7 +205,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard {
${(stateObj.state === "disarmed" - ? this._config.states! + ? states : (["disarm"] as const) ).map( (stateAction) => html` diff --git a/src/panels/lovelace/cards/types.ts b/src/panels/lovelace/cards/types.ts index 0a7c8ee412..7ddeef08a3 100644 --- a/src/panels/lovelace/cards/types.ts +++ b/src/panels/lovelace/cards/types.ts @@ -14,10 +14,17 @@ import { HaDurationData } from "../../../components/ha-duration-input"; import { LovelaceTileFeatureConfig } from "../tile-features/types"; import { ForecastType } from "../../../data/weather"; +export type AlarmPanelCardConfigState = + | "arm_away" + | "arm_home" + | "arm_night" + | "arm_vacation" + | "arm_custom_bypass"; + export interface AlarmPanelCardConfig extends LovelaceCardConfig { entity: string; name?: string; - states?: readonly (keyof TranslationDict["ui"]["card"]["alarm_control_panel"])[]; + states?: AlarmPanelCardConfigState[]; theme?: string; } diff --git a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts index b70e346018..74ff40fc22 100644 --- a/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-alarm-panel-card-editor.ts @@ -2,14 +2,25 @@ import { html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { array, assert, assign, object, optional, string } from "superstruct"; +import { HassEntity } from "home-assistant-js-websocket"; import { fireEvent } from "../../../../common/dom/fire_event"; import type { LocalizeFunc } from "../../../../common/translations/localize"; import "../../../../components/ha-form/ha-form"; import type { SchemaUnion } from "../../../../components/ha-form/types"; import type { HomeAssistant } from "../../../../types"; -import type { AlarmPanelCardConfig } from "../../cards/types"; +import type { + AlarmPanelCardConfig, + AlarmPanelCardConfigState, +} from "../../cards/types"; import type { LovelaceCardEditor } from "../../types"; import { baseLovelaceCardConfig } from "../structs/base-card-struct"; +import { + DEFAULT_STATES, + ALARM_MODE_STATE_MAP, + filterSupportedAlarmStates, +} from "../../cards/hui-alarm-panel-card"; +import { supportsFeature } from "../../../../common/entity/supports-feature"; +import { ALARM_MODES } from "../../../../data/alarm_control_panel"; const cardConfigStruct = assign( baseLovelaceCardConfig, @@ -21,13 +32,7 @@ const cardConfigStruct = assign( }) ); -const states = [ - "arm_home", - "arm_away", - "arm_night", - "arm_vacation", - "arm_custom_bypass", -] as const; +const states = Object.keys(ALARM_MODE_STATE_MAP) as AlarmPanelCardConfigState[]; @customElement("hui-alarm-panel-card-editor") export class HuiAlarmPanelCardEditor @@ -44,7 +49,11 @@ export class HuiAlarmPanelCardEditor } private _schema = memoizeOne( - (localize: LocalizeFunc) => + ( + localize: LocalizeFunc, + stateObj: HassEntity | undefined, + config_states: AlarmPanelCardConfigState[] + ) => [ { name: "entity", @@ -60,12 +69,24 @@ export class HuiAlarmPanelCardEditor ], }, { - type: "multi_select", name: "states", - options: states.map((s) => [ - s, - localize(`ui.card.alarm_control_panel.${s}`), - ]) as [string, string][], + selector: { + select: { + multiple: true, + mode: "list", + options: states.map((s) => ({ + value: s, + label: localize(`ui.card.alarm_control_panel.${s}`), + disabled: + !config_states.includes(s) && + (!stateObj || + !supportsFeature( + stateObj, + ALARM_MODES[ALARM_MODE_STATE_MAP[s]].feature || 0 + )), + })), + }, + }, }, ] as const ); @@ -75,11 +96,18 @@ export class HuiAlarmPanelCardEditor return nothing; } + const stateObj = this.hass.states[this._config.entity]; + const defaultFilteredStates = filterSupportedAlarmStates( + stateObj, + DEFAULT_STATES + ); + const config = { states: defaultFilteredStates, ...this._config }; + return html` @@ -87,7 +115,26 @@ export class HuiAlarmPanelCardEditor } private _valueChanged(ev: CustomEvent): void { - fireEvent(this, "config-changed", { config: ev.detail.value }); + const newConfig = ev.detail.value; + + // Sort states in a consistent order + if (newConfig.states) { + const sortStates = states.filter((s) => newConfig.states.includes(s)); + newConfig.states = sortStates; + } + + // When changing entities, clear any states that the new entity does not support + if (newConfig.states && newConfig.entity !== this._config?.entity) { + const newStateObj = this.hass?.states[newConfig.entity]; + if (newStateObj) { + newConfig.states = filterSupportedAlarmStates( + newStateObj, + newConfig.states + ); + } + } + + fireEvent(this, "config-changed", { config: newConfig }); } private _computeLabelCallback = (