Skip to content
110 changes: 78 additions & 32 deletions zha/application/platforms/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

from abc import ABC, abstractmethod
from dataclasses import dataclass
import functools
import logging
Expand Down Expand Up @@ -53,12 +54,84 @@ class AlarmControlPanelEntityInfo(BaseEntityInfo):
translation_key: str


class BaseAlarmControlPanel(PlatformEntity, ABC):
"""Abstract base class for ZHA alarm control panel entities."""

PLATFORM = Platform.ALARM_CONTROL_PANEL

@property
def state(self) -> dict[str, Any]:
"""Get the state of the alarm control panel."""
response = super().state
response["state"] = self.alarm_state
return response

@property
@abstractmethod
def alarm_state(self) -> AlarmState:
"""Return the current alarm state."""

@property
@abstractmethod
def code_arm_required(self) -> bool:
"""Whether the code is required for arm actions."""

_attr_code_format: CodeFormat
_attr_supported_features: int

@property
def code_format(self) -> CodeFormat:
"""Code format or None if no code is required."""
return self._attr_code_format

@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return self._attr_supported_features

@functools.cached_property
def info_object(self) -> AlarmControlPanelEntityInfo:
"""Return a representation of the alarm control panel."""
return AlarmControlPanelEntityInfo(
**super().info_object.__dict__,
code_arm_required=self.code_arm_required,
code_format=self.code_format,
supported_features=self.supported_features,
)

@abstractmethod
async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""

@abstractmethod
async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""

@abstractmethod
async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""

@abstractmethod
async def async_alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command."""

@abstractmethod
async def async_alarm_trigger(self, code: str | None = None) -> None:
"""Send alarm trigger command."""


@register_entity(IasAce.cluster_id)
class AlarmControlPanel(PlatformEntity):
class AlarmControlPanel(BaseAlarmControlPanel):
"""Entity for ZHA alarm control devices."""

_attr_translation_key: str = "alarm_control_panel"
PLATFORM = Platform.ALARM_CONTROL_PANEL
_attr_code_format = CodeFormat.NUMBER
_attr_supported_features = (
SUPPORT_ALARM_ARM_HOME
| SUPPORT_ALARM_ARM_AWAY
| SUPPORT_ALARM_ARM_NIGHT
| SUPPORT_ALARM_TRIGGER
)

_cluster_handler_match = ClusterHandlerMatch(
client_cluster_handlers=frozenset({CLUSTER_HANDLER_IAS_ACE}),
Expand Down Expand Up @@ -107,45 +180,18 @@ def on_add(self) -> None:
)
)

@functools.cached_property
def info_object(self) -> AlarmControlPanelEntityInfo:
"""Return a representation of the alarm control panel."""
return AlarmControlPanelEntityInfo(
**super().info_object.__dict__,
code_arm_required=self.code_arm_required,
code_format=self.code_format,
supported_features=self.supported_features,
)

@property
def state(self) -> dict[str, Any]:
"""Get the state of the alarm control panel."""
response = super().state
response["state"] = IAS_ACE_STATE_MAP.get(
def alarm_state(self) -> AlarmState:
"""Return the current alarm state."""
return IAS_ACE_STATE_MAP.get(
self._cluster_handler.armed_state, AlarmState.UNKNOWN
)
return response

@property
def code_arm_required(self) -> bool:
"""Whether the code is required for arm actions."""
return self._cluster_handler.code_required_arm_actions

@functools.cached_property
def code_format(self) -> CodeFormat:
"""Code format or None if no code is required."""
return CodeFormat.NUMBER

@functools.cached_property
def supported_features(self) -> int:
"""Return the list of supported features."""
return (
SUPPORT_ALARM_ARM_HOME
| SUPPORT_ALARM_ARM_AWAY
| SUPPORT_ALARM_ARM_NIGHT
| SUPPORT_ALARM_TRIGGER
)

def handle_cluster_handler_state_changed(
self,
event: ClusterHandlerStateChangedEvent, # pylint: disable=unused-argument
Expand Down
29 changes: 20 additions & 9 deletions zha/application/platforms/binary_sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Callable
from dataclasses import dataclass
import functools
Expand Down Expand Up @@ -62,13 +63,30 @@ class BinarySensorEntityInfo(BaseEntityInfo):
device_class: BinarySensorDeviceClass | None


class BinarySensor(PlatformEntity):
class BaseBinarySensor(PlatformEntity, ABC):
"""Abstract base class for ZHA binary sensors."""

PLATFORM: Platform = Platform.BINARY_SENSOR

@property
def state(self) -> dict:
"""Return the state of the binary sensor."""
response = super().state
response["state"] = self.is_on
return response

@property
@abstractmethod
def is_on(self) -> bool:
"""Return True if the binary sensor is on."""


class BinarySensor(BaseBinarySensor):
"""ZHA BinarySensor."""

_attr_device_class: BinarySensorDeviceClass | None
_attribute_name: str
_attribute_converter: Callable[[Any], Any] | None = None
PLATFORM: Platform = Platform.BINARY_SENSOR

def __init__(
self,
Expand Down Expand Up @@ -115,13 +133,6 @@ def info_object(self) -> BinarySensorEntityInfo:
attribute_name=self._attribute_name,
)

@property
def state(self) -> dict:
"""Return the state of the binary sensor."""
response = super().state
response["state"] = self.is_on
return response

@property
def is_on(self) -> bool:
"""Return True if the switch is on based on the state machine."""
Expand Down
31 changes: 24 additions & 7 deletions zha/application/platforms/button/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass
import functools
import logging
Expand Down Expand Up @@ -36,7 +37,12 @@


@dataclass(frozen=True, kw_only=True)
class CommandButtonEntityInfo(BaseEntityInfo):
class ButtonEntityInfo(BaseEntityInfo):
"""Button entity info."""


@dataclass(frozen=True, kw_only=True)
class CommandButtonEntityInfo(ButtonEntityInfo):
"""Command button entity info."""

command: str
Expand All @@ -45,18 +51,31 @@ class CommandButtonEntityInfo(BaseEntityInfo):


@dataclass(frozen=True, kw_only=True)
class WriteAttributeButtonEntityInfo(BaseEntityInfo):
class WriteAttributeButtonEntityInfo(ButtonEntityInfo):
"""Write attribute button entity info."""

attribute_name: str
attribute_value: Any


class Button(PlatformEntity):
"""Defines a ZHA button."""
class BaseButton(PlatformEntity, ABC):
"""Base representation of a ZHA button."""

PLATFORM = Platform.BUTTON

@functools.cached_property
def info_object(self) -> ButtonEntityInfo:
"""Return a representation of the button."""
return ButtonEntityInfo(**super().info_object.__dict__)

@abstractmethod
async def async_press(self) -> None:
"""Send out a press command."""


class Button(BaseButton):
"""Defines a ZHA button."""

_command_name: str
_args: list[Any]
_kwargs: dict[str, Any]
Expand Down Expand Up @@ -131,11 +150,9 @@ def is_supported_in_list(self, entities: list[BaseEntity]) -> bool:
return not any(type(entity) is cls for entity in entities)


class WriteAttributeButton(PlatformEntity):
class WriteAttributeButton(BaseButton):
"""Defines a ZHA button, which writes a value to an attribute."""

PLATFORM = Platform.BUTTON

_attribute_name: str
_attribute_value: Any = None

Expand Down
Loading
Loading