Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new state property for alarm_control_panel which is using an enum #126283

Merged
merged 33 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
94f6d5e
Alarm state from enum
gjohansson-ST Sep 15, 2024
e00ced9
Fixes
gjohansson-ST Sep 16, 2024
f25bfac
Set final
gjohansson-ST Sep 20, 2024
5690c5b
Fix rebase
gjohansson-ST Oct 8, 2024
148cc34
Test const
gjohansson-ST Oct 8, 2024
4aa230d
Fix breaking version
gjohansson-ST Oct 8, 2024
3f7ca02
Fix other for alarm_control_panel
gjohansson-ST Oct 8, 2024
2a3a62e
Fix integrations
gjohansson-ST Oct 8, 2024
f18c49c
More
gjohansson-ST Oct 8, 2024
a8bcb96
More
gjohansson-ST Oct 8, 2024
84eaf47
More
gjohansson-ST Oct 8, 2024
062fc4b
More
gjohansson-ST Oct 8, 2024
cf95f79
Fix zha
gjohansson-ST Oct 8, 2024
8af36ae
Replace _attr_state
gjohansson-ST Oct 8, 2024
e58ffd1
Fix alarm_control_panel
gjohansson-ST Oct 8, 2024
648ce23
Fix tests
gjohansson-ST Oct 9, 2024
533916d
Fixes
gjohansson-ST Oct 9, 2024
7f51ec5
Mods
gjohansson-ST Oct 13, 2024
d401506
Change some
gjohansson-ST Oct 14, 2024
c3296d8
More
gjohansson-ST Oct 14, 2024
f4d310c
More
gjohansson-ST Oct 14, 2024
c4e3e30
More
gjohansson-ST Oct 14, 2024
c3900bc
Tests
gjohansson-ST Oct 14, 2024
eda81b5
Last tests
gjohansson-ST Oct 14, 2024
6c7143c
Return enum
gjohansson-ST Oct 14, 2024
3ec4649
Fix zha
gjohansson-ST Oct 14, 2024
64c8f44
Remove not needed check
gjohansson-ST Oct 15, 2024
547ad61
Fix wording
gjohansson-ST Oct 17, 2024
425274e
Fix homekit
gjohansson-ST Oct 17, 2024
8b16631
Mod prometheus
gjohansson-ST Oct 17, 2024
bf02939
Fix mypy
gjohansson-ST Oct 17, 2024
5df5970
Fix homekit
gjohansson-ST Oct 17, 2024
ad0726b
Fix ifttt
gjohansson-ST Oct 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions homeassistant/components/abode/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,9 @@
from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntity,
AlarmControlPanelEntityFeature,
AlarmControlPanelState,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_DISARMED,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

Expand Down Expand Up @@ -44,14 +40,14 @@ class AbodeAlarm(AbodeDevice, AlarmControlPanelEntity):
_device: Alarm

@property
def state(self) -> str | None:
def alarm_state(self) -> AlarmControlPanelState | None:
"""Return the state of the device."""
if self._device.is_standby:
return STATE_ALARM_DISARMED
return AlarmControlPanelState.DISARMED
if self._device.is_away:
return STATE_ALARM_ARMED_AWAY
return AlarmControlPanelState.ARMED_AWAY
if self._device.is_home:
return STATE_ALARM_ARMED_HOME
return AlarmControlPanelState.ARMED_HOME
return None

def alarm_disarm(self, code: str | None = None) -> None:
Expand Down
25 changes: 10 additions & 15 deletions homeassistant/components/agent_dvr/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@
from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntity,
AlarmControlPanelEntityFeature,
)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED,
AlarmControlPanelState,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
Expand Down Expand Up @@ -65,37 +60,37 @@ async def async_update(self) -> None:
self._attr_available = self._client.is_available
armed = self._client.is_armed
if armed is None:
self._attr_state = None
self._attr_alarm_state = None
return
if armed:
prof = (await self._client.get_active_profile()).lower()
self._attr_state = STATE_ALARM_ARMED_AWAY
self._attr_alarm_state = AlarmControlPanelState.ARMED_AWAY
if prof == CONF_HOME_MODE_NAME:
self._attr_state = STATE_ALARM_ARMED_HOME
self._attr_alarm_state = AlarmControlPanelState.ARMED_HOME
elif prof == CONF_NIGHT_MODE_NAME:
self._attr_state = STATE_ALARM_ARMED_NIGHT
self._attr_alarm_state = AlarmControlPanelState.ARMED_NIGHT
else:
self._attr_state = STATE_ALARM_DISARMED
self._attr_alarm_state = AlarmControlPanelState.DISARMED

async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
await self._client.disarm()
self._attr_state = STATE_ALARM_DISARMED
self._attr_alarm_state = AlarmControlPanelState.DISARMED

async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command. Uses custom mode."""
await self._client.arm()
await self._client.set_active_profile(CONF_AWAY_MODE_NAME)
self._attr_state = STATE_ALARM_ARMED_AWAY
self._attr_alarm_state = AlarmControlPanelState.ARMED_AWAY

async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command. Uses custom mode."""
await self._client.arm()
await self._client.set_active_profile(CONF_HOME_MODE_NAME)
self._attr_state = STATE_ALARM_ARMED_HOME
self._attr_alarm_state = AlarmControlPanelState.ARMED_HOME

async def async_alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command. Uses custom mode."""
await self._client.arm()
await self._client.set_active_profile(CONF_NIGHT_MODE_NAME)
self._attr_state = STATE_ALARM_ARMED_NIGHT
self._attr_alarm_state = AlarmControlPanelState.ARMED_NIGHT
77 changes: 77 additions & 0 deletions homeassistant/components/alarm_control_panel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import asyncio
from datetime import timedelta
from functools import partial
import logging
Expand Down Expand Up @@ -33,6 +34,7 @@
)
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity_platform import EntityPlatform
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey

Expand All @@ -49,6 +51,7 @@
ATTR_CODE_ARM_REQUIRED,
DOMAIN,
AlarmControlPanelEntityFeature,
AlarmControlPanelState,
CodeFormat,
)

Expand Down Expand Up @@ -142,13 +145,15 @@ class AlarmControlPanelEntityDescription(EntityDescription, frozen_or_thawed=Tru
"changed_by",
"code_arm_required",
"supported_features",
"alarm_state",
}


class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""An abstract class for alarm control entities."""

entity_description: AlarmControlPanelEntityDescription
_attr_alarm_state: AlarmControlPanelState | None = None
_attr_changed_by: str | None = None
_attr_code_arm_required: bool = True
_attr_code_format: CodeFormat | None = None
Expand All @@ -157,6 +162,78 @@ class AlarmControlPanelEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_A
)
_alarm_control_panel_option_default_code: str | None = None

__alarm_legacy_state: bool = False
__alarm_legacy_state_reported: bool = False

def __init_subclass__(cls, **kwargs: Any) -> None:
"""Post initialisation processing."""
super().__init_subclass__(**kwargs)
if any(method in cls.__dict__ for method in ("_attr_state", "state")):
# Integrations should use the 'alarm_state' property instead of
# setting the state directly.
cls.__alarm_legacy_state = True

def __setattr__(self, __name: str, __value: Any) -> None:
"""Set attribute.

Deprecation warning if setting '_attr_state' directly
unless already reported.
"""
if __name == "_attr_state":
if self.__alarm_legacy_state_reported is not True:
self._report_deprecated_alarm_state_handling()
self.__alarm_legacy_state_reported = True
return super().__setattr__(__name, __value)

@callback
def add_to_platform_start(
self,
hass: HomeAssistant,
platform: EntityPlatform,
parallel_updates: asyncio.Semaphore | None,
) -> None:
"""Start adding an entity to a platform."""
super().add_to_platform_start(hass, platform, parallel_updates)
if self.__alarm_legacy_state and not self.__alarm_legacy_state_reported:
self._report_deprecated_alarm_state_handling()

@callback
def _report_deprecated_alarm_state_handling(self) -> None:
"""Report on deprecated handling of alarm state.

Integrations should implement alarm_state instead of using state directly.
"""
self.__alarm_legacy_state_reported = True
if "custom_components" in type(self).__module__:
# Do not report on core integrations as they have been fixed.
report_issue = "report it to the custom integration author."
_LOGGER.warning(
"Entity %s (%s) is setting state directly"
" which will stop working in HA Core 2025.11."
" Entities should implement the 'alarm_state' property and"
" return its state using the AlarmControlPanelState enum, please %s",
self.entity_id,
type(self),
report_issue,
)

@final
@property
def state(self) -> str | None:
"""Return the current state."""
if (alarm_state := self.alarm_state) is None:
return None
return alarm_state

@cached_property
def alarm_state(self) -> AlarmControlPanelState | None:
"""Return the current alarm control panel entity state.

Integrations should overwrite this or use the '_attr_alarm_state'
attribute to set the alarm status using the 'AlarmControlPanelState' enum.
"""
return self._attr_alarm_state

@final
@callback
def code_or_default_code(self, code: str | None) -> str | None:
Expand Down
15 changes: 15 additions & 0 deletions homeassistant/components/alarm_control_panel/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@
ATTR_CODE_ARM_REQUIRED: Final = "code_arm_required"


class AlarmControlPanelState(StrEnum):
"""Alarm control panel entity states."""

DISARMED = "disarmed"
ARMED_HOME = "armed_home"
ARMED_AWAY = "armed_away"
ARMED_NIGHT = "armed_night"
ARMED_VACATION = "armed_vacation"
ARMED_CUSTOM_BYPASS = "armed_custom_bypass"
PENDING = "pending"
ARMING = "arming"
DISARMING = "disarming"
TRIGGERED = "triggered"


class CodeFormat(StrEnum):
"""Code formats for the Alarm Control Panel."""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,6 @@
CONF_DOMAIN,
CONF_ENTITY_ID,
CONF_TYPE,
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import (
Expand All @@ -31,7 +24,7 @@
from homeassistant.helpers.entity import get_supported_features
from homeassistant.helpers.typing import ConfigType, TemplateVarsType

from . import DOMAIN
from . import DOMAIN, AlarmControlPanelState
from .const import (
CONDITION_ARMED_AWAY,
CONDITION_ARMED_CUSTOM_BYPASS,
Expand Down Expand Up @@ -109,19 +102,19 @@ def async_condition_from_config(
) -> condition.ConditionCheckerType:
"""Create a function to test a device condition."""
if config[CONF_TYPE] == CONDITION_TRIGGERED:
state = STATE_ALARM_TRIGGERED
state = AlarmControlPanelState.TRIGGERED
elif config[CONF_TYPE] == CONDITION_DISARMED:
state = STATE_ALARM_DISARMED
state = AlarmControlPanelState.DISARMED
elif config[CONF_TYPE] == CONDITION_ARMED_HOME:
state = STATE_ALARM_ARMED_HOME
state = AlarmControlPanelState.ARMED_HOME
elif config[CONF_TYPE] == CONDITION_ARMED_AWAY:
state = STATE_ALARM_ARMED_AWAY
state = AlarmControlPanelState.ARMED_AWAY
elif config[CONF_TYPE] == CONDITION_ARMED_NIGHT:
state = STATE_ALARM_ARMED_NIGHT
state = AlarmControlPanelState.ARMED_NIGHT
elif config[CONF_TYPE] == CONDITION_ARMED_VACATION:
state = STATE_ALARM_ARMED_VACATION
state = AlarmControlPanelState.ARMED_VACATION
elif config[CONF_TYPE] == CONDITION_ARMED_CUSTOM_BYPASS:
state = STATE_ALARM_ARMED_CUSTOM_BYPASS
state = AlarmControlPanelState.ARMED_CUSTOM_BYPASS

registry = er.async_get(hass)
entity_id = er.async_resolve_entity_id(registry, config[ATTR_ENTITY_ID])
Expand Down
23 changes: 8 additions & 15 deletions homeassistant/components/alarm_control_panel/device_trigger.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,14 @@
CONF_FOR,
CONF_PLATFORM,
CONF_TYPE,
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_ARMING,
STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED,
)
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.entity import get_supported_features
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
from homeassistant.helpers.typing import ConfigType

from . import DOMAIN
from . import DOMAIN, AlarmControlPanelState
from .const import AlarmControlPanelEntityFeature

BASIC_TRIGGER_TYPES: Final[set[str]] = {"triggered", "disarmed", "arming"}
Expand Down Expand Up @@ -129,19 +122,19 @@ async def async_attach_trigger(
) -> CALLBACK_TYPE:
"""Attach a trigger."""
if config[CONF_TYPE] == "triggered":
to_state = STATE_ALARM_TRIGGERED
to_state = AlarmControlPanelState.TRIGGERED
elif config[CONF_TYPE] == "disarmed":
to_state = STATE_ALARM_DISARMED
to_state = AlarmControlPanelState.DISARMED
elif config[CONF_TYPE] == "arming":
to_state = STATE_ALARM_ARMING
to_state = AlarmControlPanelState.ARMING
elif config[CONF_TYPE] == "armed_home":
to_state = STATE_ALARM_ARMED_HOME
to_state = AlarmControlPanelState.ARMED_HOME
elif config[CONF_TYPE] == "armed_away":
to_state = STATE_ALARM_ARMED_AWAY
to_state = AlarmControlPanelState.ARMED_AWAY
elif config[CONF_TYPE] == "armed_night":
to_state = STATE_ALARM_ARMED_NIGHT
to_state = AlarmControlPanelState.ARMED_NIGHT
elif config[CONF_TYPE] == "armed_vacation":
to_state = STATE_ALARM_ARMED_VACATION
to_state = AlarmControlPanelState.ARMED_VACATION

state_config = {
state_trigger.CONF_PLATFORM: "state",
Expand Down
37 changes: 15 additions & 22 deletions homeassistant/components/alarm_control_panel/reproduce_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,21 @@
SERVICE_ALARM_ARM_VACATION,
SERVICE_ALARM_DISARM,
SERVICE_ALARM_TRIGGER,
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED,
)
from homeassistant.core import Context, HomeAssistant, State

from . import DOMAIN
from . import DOMAIN, AlarmControlPanelState

_LOGGER: Final = logging.getLogger(__name__)

VALID_STATES: Final[set[str]] = {
STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_CUSTOM_BYPASS,
STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_ARMED_VACATION,
STATE_ALARM_DISARMED,
STATE_ALARM_TRIGGERED,
AlarmControlPanelState.ARMED_AWAY,
AlarmControlPanelState.ARMED_CUSTOM_BYPASS,
AlarmControlPanelState.ARMED_HOME,
AlarmControlPanelState.ARMED_NIGHT,
AlarmControlPanelState.ARMED_VACATION,
AlarmControlPanelState.DISARMED,
AlarmControlPanelState.TRIGGERED,
}


Expand Down Expand Up @@ -65,19 +58,19 @@ async def _async_reproduce_state(

service_data = {ATTR_ENTITY_ID: state.entity_id}

if state.state == STATE_ALARM_ARMED_AWAY:
if state.state == AlarmControlPanelState.ARMED_AWAY:
service = SERVICE_ALARM_ARM_AWAY
elif state.state == STATE_ALARM_ARMED_CUSTOM_BYPASS:
elif state.state == AlarmControlPanelState.ARMED_CUSTOM_BYPASS:
service = SERVICE_ALARM_ARM_CUSTOM_BYPASS
elif state.state == STATE_ALARM_ARMED_HOME:
elif state.state == AlarmControlPanelState.ARMED_HOME:
service = SERVICE_ALARM_ARM_HOME
elif state.state == STATE_ALARM_ARMED_NIGHT:
elif state.state == AlarmControlPanelState.ARMED_NIGHT:
service = SERVICE_ALARM_ARM_NIGHT
elif state.state == STATE_ALARM_ARMED_VACATION:
elif state.state == AlarmControlPanelState.ARMED_VACATION:
service = SERVICE_ALARM_ARM_VACATION
elif state.state == STATE_ALARM_DISARMED:
elif state.state == AlarmControlPanelState.DISARMED:
service = SERVICE_ALARM_DISARM
elif state.state == STATE_ALARM_TRIGGERED:
elif state.state == AlarmControlPanelState.TRIGGERED:
service = SERVICE_ALARM_TRIGGER

await hass.services.async_call(
Expand Down
Loading
Loading