From 91e5fcacd53e719ae46964a261dfbbbaaf0b378b Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 29 May 2024 11:05:46 +0200 Subject: [PATCH] Add default code to alarm_control_panel (#20062) Co-authored-by: Paul Bottein --- src/data/alarm_control_panel.ts | 51 +++++++++++++++- src/data/entity_registry.ts | 6 ++ .../controls/more-info-alarm_control_panel.ts | 30 ++++------ .../entity-registry-settings-editor.ts | 27 +++++++++ .../hui-alarm-modes-card-feature.ts | 34 +---------- .../lovelace/cards/hui-alarm-panel-card.ts | 58 ++++++++++++++++++- ...state-control-alarm_control_panel-modes.ts | 34 +---------- 7 files changed, 152 insertions(+), 88 deletions(-) diff --git a/src/data/alarm_control_panel.ts b/src/data/alarm_control_panel.ts index 64a8d53fee..acde378a13 100644 --- a/src/data/alarm_control_panel.ts +++ b/src/data/alarm_control_panel.ts @@ -10,8 +10,10 @@ import { HassEntityAttributeBase, HassEntityBase, } from "home-assistant-js-websocket"; -import { HomeAssistant } from "../types"; import { supportsFeature } from "../common/entity/supports-feature"; +import { showEnterCodeDialog } from "../dialogs/enter-code/show-enter-code-dialog"; +import { HomeAssistant } from "../types"; +import { getExtendedEntityRegistryEntry } from "./entity_registry"; export const FORMAT_TEXT = "text"; export const FORMAT_NUMBER = "number"; @@ -103,3 +105,50 @@ export const supportedAlarmModes = (stateObj: AlarmControlPanelEntity) => const feature = ALARM_MODES[mode].feature; return !feature || supportsFeature(stateObj, feature); }); + +export const setProtectedAlarmControlPanelMode = async ( + element: HTMLElement, + hass: HomeAssistant, + stateObj: AlarmControlPanelEntity, + mode: AlarmMode +) => { + const { service } = ALARM_MODES[mode]; + + let code: string | undefined; + + if ( + (mode !== "disarmed" && + stateObj.attributes.code_arm_required && + stateObj.attributes.code_format) || + (mode === "disarmed" && stateObj.attributes.code_format) + ) { + const entry = await getExtendedEntityRegistryEntry( + hass, + stateObj.entity_id + ).catch(() => undefined); + const defaultCode = entry?.options?.alarm_control_panel?.default_code; + + if (!defaultCode) { + const disarm = mode === "disarmed"; + + const response = await showEnterCodeDialog(element, { + codeFormat: stateObj.attributes.code_format, + title: hass.localize( + `ui.card.alarm_control_panel.${disarm ? "disarm" : "arm"}` + ), + submitText: hass.localize( + `ui.card.alarm_control_panel.${disarm ? "disarm" : "arm"}` + ), + }); + if (response == null) { + throw new Error("Code dialog closed"); + } + code = response; + } + } + + await hass.callService("alarm_control_panel", service, { + entity_id: stateObj.entity_id, + code, + }); +}; diff --git a/src/data/entity_registry.ts b/src/data/entity_registry.ts index 2ebfd15778..02b72bf477 100644 --- a/src/data/entity_registry.ts +++ b/src/data/entity_registry.ts @@ -96,6 +96,10 @@ export interface LockEntityOptions { default_code?: string | null; } +export interface AlarmControlPanelEntityOptions { + default_code?: string | null; +} + export interface WeatherEntityOptions { precipitation_unit?: string | null; pressure_unit?: string | null; @@ -112,6 +116,7 @@ export interface SwitchAsXEntityOptions { export interface EntityRegistryOptions { number?: NumberEntityOptions; sensor?: SensorEntityOptions; + alarm_control_panel?: AlarmControlPanelEntityOptions; lock?: LockEntityOptions; weather?: WeatherEntityOptions; light?: LightEntityOptions; @@ -134,6 +139,7 @@ export interface EntityRegistryEntryUpdateParams { | SensorEntityOptions | NumberEntityOptions | LockEntityOptions + | AlarmControlPanelEntityOptions | WeatherEntityOptions | LightEntityOptions; aliases?: string[]; diff --git a/src/dialogs/more-info/controls/more-info-alarm_control_panel.ts b/src/dialogs/more-info/controls/more-info-alarm_control_panel.ts index 3a28b4f4b7..430565433f 100644 --- a/src/dialogs/more-info/controls/more-info-alarm_control_panel.ts +++ b/src/dialogs/more-info/controls/more-info-alarm_control_panel.ts @@ -4,10 +4,12 @@ import { styleMap } from "lit/directives/style-map"; import { stateColorCss } from "../../../common/entity/state_color"; import "../../../components/ha-control-button"; import "../../../components/ha-state-icon"; -import { AlarmControlPanelEntity } from "../../../data/alarm_control_panel"; +import { + AlarmControlPanelEntity, + setProtectedAlarmControlPanelMode, +} from "../../../data/alarm_control_panel"; import "../../../state-control/alarm_control_panel/ha-state-control-alarm_control_panel-modes"; import type { HomeAssistant } from "../../../types"; -import { showEnterCodeDialog } from "../../enter-code/show-enter-code-dialog"; import "../components/ha-more-info-state-header"; import { moreInfoControlStyle } from "../components/more-info-control-style"; @@ -18,24 +20,12 @@ class MoreInfoAlarmControlPanel extends LitElement { @property({ attribute: false }) public stateObj?: AlarmControlPanelEntity; private async _disarm() { - let code: string | undefined; - - if (this.stateObj!.attributes.code_format) { - const response = await showEnterCodeDialog(this, { - codeFormat: this.stateObj!.attributes.code_format, - title: this.hass.localize("ui.card.alarm_control_panel.disarm"), - submitText: this.hass.localize("ui.card.alarm_control_panel.disarm"), - }); - if (response == null) { - return; - } - code = response; - } - - this.hass.callService("alarm_control_panel", "alarm_disarm", { - entity_id: this.stateObj!.entity_id, - code, - }); + setProtectedAlarmControlPanelMode( + this, + this.hass, + this.stateObj!, + "disarmed" + ); } protected render() { diff --git a/src/panels/config/entities/entity-registry-settings-editor.ts b/src/panels/config/entities/entity-registry-settings-editor.ts index b4e8edd885..a63850941a 100644 --- a/src/panels/config/entities/entity-registry-settings-editor.ts +++ b/src/panels/config/entities/entity-registry-settings-editor.ts @@ -59,6 +59,7 @@ import { updateDeviceRegistryEntry, } from "../../../data/device_registry"; import { + AlarmControlPanelEntityOptions, EntityRegistryEntry, EntityRegistryEntryUpdateParams, ExtEntityRegistryEntry, @@ -257,6 +258,10 @@ export class EntityRegistrySettingsEditor extends LitElement { this._defaultCode = this.entry.options?.lock?.default_code; } + if (domain === "alarm_control_panel") { + this._defaultCode = this.entry.options?.alarm_control_panel?.default_code; + } + if (domain === "weather") { const stateObj: HassEntity | undefined = this.hass.states[this.entry.entity_id]; @@ -583,6 +588,19 @@ export class EntityRegistrySettingsEditor extends LitElement { > ` : ""} + ${domain === "alarm_control_panel" + ? html` + + ` + : ""} ${domain === "sensor" && this._deviceClass && stateObj?.attributes.unit_of_measurement && @@ -1071,6 +1089,15 @@ export class EntityRegistrySettingsEditor extends LitElement { params.options = this.entry.options?.[domain] || {}; (params.options as LockEntityOptions).default_code = this._defaultCode; } + if ( + domain === "alarm_control_panel" && + this.entry.options?.[domain]?.default_code !== this._defaultCode + ) { + params.options_domain = domain; + params.options = this.entry.options?.[domain] || {}; + (params.options as AlarmControlPanelEntityOptions).default_code = + this._defaultCode; + } if ( domain === "weather" && (stateObj?.attributes?.precipitation_unit !== this._precipitation_unit || diff --git a/src/panels/lovelace/card-features/hui-alarm-modes-card-feature.ts b/src/panels/lovelace/card-features/hui-alarm-modes-card-feature.ts index 926fdcea25..9b8ef39c99 100644 --- a/src/panels/lovelace/card-features/hui-alarm-modes-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-alarm-modes-card-feature.ts @@ -16,10 +16,10 @@ import { ALARM_MODES, AlarmControlPanelEntity, AlarmMode, + setProtectedAlarmControlPanelMode, supportedAlarmModes, } from "../../../data/alarm_control_panel"; import { UNAVAILABLE } from "../../../data/entity"; -import { showEnterCodeDialog } from "../../../dialogs/enter-code/show-enter-code-dialog"; import { HomeAssistant } from "../../../types"; import { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { filterModes } from "./common/filter-modes"; @@ -115,37 +115,7 @@ class HuiAlarmModeCardFeature } private async _setMode(mode: AlarmMode) { - const { service } = ALARM_MODES[mode]; - - let code: string | undefined; - - if ( - (mode !== "disarmed" && - this.stateObj!.attributes.code_arm_required && - this.stateObj!.attributes.code_format) || - (mode === "disarmed" && this.stateObj!.attributes.code_format) - ) { - const disarm = mode === "disarmed"; - - const response = await showEnterCodeDialog(this, { - codeFormat: this.stateObj!.attributes.code_format, - title: this.hass!.localize( - `ui.card.alarm_control_panel.${disarm ? "disarm" : "arm"}` - ), - submitText: this.hass!.localize( - `ui.card.alarm_control_panel.${disarm ? "disarm" : "arm"}` - ), - }); - if (response == null) { - throw new Error("cancel"); - } - code = response; - } - - await this.hass!.callService("alarm_control_panel", service, { - entity_id: this.stateObj!.entity_id, - code, - }); + setProtectedAlarmControlPanelMode(this, this.hass!, this.stateObj!, mode); } protected render(): TemplateResult | null { diff --git a/src/panels/lovelace/cards/hui-alarm-panel-card.ts b/src/panels/lovelace/cards/hui-alarm-panel-card.ts index ecfce5fac2..225f30cea6 100644 --- a/src/panels/lovelace/cards/hui-alarm-panel-card.ts +++ b/src/panels/lovelace/cards/hui-alarm-panel-card.ts @@ -1,4 +1,4 @@ -import { HassEntity } from "home-assistant-js-websocket"; +import { HassEntity, UnsubscribeFunc } from "home-assistant-js-websocket"; import { CSSResultGroup, LitElement, @@ -30,6 +30,11 @@ import type { HomeAssistant } from "../../../types"; import { findEntities } from "../common/find-entities"; import { createEntityNotFoundWarning } from "../components/hui-warning"; import type { LovelaceCard } from "../types"; +import { + ExtEntityRegistryEntry, + getExtendedEntityRegistryEntry, + subscribeEntityRegistry, +} from "../../../data/entity_registry"; import { AlarmPanelCardConfig, AlarmPanelCardConfigState } from "./types"; const BUTTONS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "", "0", "clear"]; @@ -99,8 +104,22 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { @state() private _config?: AlarmPanelCardConfig; + @state() private _entry?: ExtEntityRegistryEntry | null; + @query("#alarmCode") private _input?: HaTextField; + private _unsubEntityRegistry?: UnsubscribeFunc; + + public connectedCallback() { + super.connectedCallback(); + this._subscribeEntityEntry(); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this._unsubscribeEntityRegistry(); + } + public async getCardSize(): Promise { if (!this._config || !this.hass) { return 9; @@ -123,6 +142,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { } this._config = { ...config }; + this._subscribeEntityEntry(); } protected updated(changedProps: PropertyValues): void { @@ -165,6 +185,36 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { ); } + private async _unsubscribeEntityRegistry() { + if (this._unsubEntityRegistry) { + this._unsubEntityRegistry(); + this._unsubEntityRegistry = undefined; + } + } + + private async _subscribeEntityEntry() { + if (!this._config?.entity) { + return; + } + try { + this._unsubEntityRegistry = subscribeEntityRegistry( + this.hass!.connection, + async (entries) => { + if ( + entries.some((entry) => entry.entity_id === this._config!.entity) + ) { + this._entry = await getExtendedEntityRegistryEntry( + this.hass!, + this._config!.entity + ); + } + } + ); + } catch (e) { + this._entry = null; + } + } + protected render() { if (!this._config || !this.hass) { return nothing; @@ -184,6 +234,8 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { const stateLabel = this._stateDisplay(stateObj.state); + const defaultCode = this._entry?.options?.alarm_control_panel?.default_code; + return html`

@@ -222,7 +274,7 @@ class HuiAlarmPanelCard extends LitElement implements LovelaceCard { ` )} - ${!stateObj.attributes.code_format + ${!stateObj.attributes.code_format || defaultCode ? nothing : html` `} - ${stateObj.attributes.code_format !== FORMAT_NUMBER + ${stateObj.attributes.code_format !== FORMAT_NUMBER || defaultCode ? nothing : html`
diff --git a/src/state-control/alarm_control_panel/ha-state-control-alarm_control_panel-modes.ts b/src/state-control/alarm_control_panel/ha-state-control-alarm_control_panel-modes.ts index 585d14d9ca..c93ac8b013 100644 --- a/src/state-control/alarm_control_panel/ha-state-control-alarm_control_panel-modes.ts +++ b/src/state-control/alarm_control_panel/ha-state-control-alarm_control_panel-modes.ts @@ -11,9 +11,9 @@ import { ALARM_MODES, AlarmControlPanelEntity, AlarmMode, + setProtectedAlarmControlPanelMode, } from "../../data/alarm_control_panel"; import { UNAVAILABLE } from "../../data/entity"; -import { showEnterCodeDialog } from "../../dialogs/enter-code/show-enter-code-dialog"; import { HomeAssistant } from "../../types"; @customElement("ha-state-control-alarm_control_panel-modes") @@ -44,37 +44,7 @@ export class HaStateControlAlarmControlPanelModes extends LitElement { } private async _setMode(mode: AlarmMode) { - const { service } = ALARM_MODES[mode]; - - let code: string | undefined; - - if ( - (mode !== "disarmed" && - this.stateObj!.attributes.code_arm_required && - this.stateObj!.attributes.code_format) || - (mode === "disarmed" && this.stateObj!.attributes.code_format) - ) { - const disarm = mode === "disarmed"; - - const response = await showEnterCodeDialog(this, { - codeFormat: this.stateObj!.attributes.code_format, - title: this.hass!.localize( - `ui.card.alarm_control_panel.${disarm ? "disarm" : "arm"}` - ), - submitText: this.hass!.localize( - `ui.card.alarm_control_panel.${disarm ? "disarmn" : "arm"}` - ), - }); - if (response == null) { - throw new Error("cancel"); - } - code = response; - } - - await this.hass!.callService("alarm_control_panel", service, { - entity_id: this.stateObj!.entity_id, - code, - }); + setProtectedAlarmControlPanelMode(this, this.hass!, this.stateObj!, mode); } private async _valueChanged(ev: CustomEvent) {