core/homeassistant/components/sia/sia_entity_base.py

137 lines
4.4 KiB
Python

"""Module for SIA Base Entity."""
from __future__ import annotations
from abc import abstractmethod
from dataclasses import dataclass
import logging
from pysiaalarm import SIAEvent
from homeassistant.core import CALLBACK_TYPE, State, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo, EntityDescription
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import StateType
from .const import DOMAIN, SIA_EVENT, SIA_HUB_ZONE
from .utils import get_attr_from_sia_event, get_unavailability_interval
_LOGGER = logging.getLogger(__name__)
@dataclass
class SIARequiredKeysMixin:
"""Required keys for SIA entities."""
code_consequences: dict[str, StateType | bool]
@dataclass
class SIAEntityDescription(EntityDescription, SIARequiredKeysMixin):
"""Entity Description for SIA entities."""
class SIABaseEntity(RestoreEntity):
"""Base class for SIA entities."""
entity_description: SIAEntityDescription
def __init__(
self,
port: int,
account: str,
zone: int | None,
ping_interval: int,
entity_description: SIAEntityDescription,
unique_id: str,
name: str,
) -> None:
"""Create SIABaseEntity object."""
self.port = port
self.account = account
self.zone = zone
self.ping_interval = ping_interval
self.entity_description = entity_description
self._attr_unique_id = unique_id
self._attr_name = name
self._attr_device_info = DeviceInfo(
name=name,
identifiers={(DOMAIN, unique_id)},
via_device=(DOMAIN, f"{port}_{account}"),
)
self._cancel_availability_cb: CALLBACK_TYPE | None = None
self._attr_extra_state_attributes = {}
self._attr_should_poll = False
async def async_added_to_hass(self) -> None:
"""Run when entity about to be added to hass.
Overridden from Entity.
1. register the dispatcher and add the callback to on_remove
2. get previous state from storage and pass to entity specific function
3. if available: create availability cb
"""
self.async_on_remove(
async_dispatcher_connect(
self.hass,
SIA_EVENT.format(self.port, self.account),
self.async_handle_event,
)
)
self.handle_last_state(await self.async_get_last_state())
if self._attr_available:
self.async_create_availability_cb()
@abstractmethod
def handle_last_state(self, last_state: State | None) -> None:
"""Handle the last state."""
async def async_will_remove_from_hass(self) -> None:
"""Run when entity will be removed from hass.
Overridden from Entity.
"""
if self._cancel_availability_cb:
self._cancel_availability_cb()
@callback
def async_handle_event(self, sia_event: SIAEvent) -> None:
"""Listen to dispatcher events for this port and account and update state and attributes."""
_LOGGER.debug("Received event: %s", sia_event)
if int(sia_event.ri) not in (self.zone, SIA_HUB_ZONE):
return
self._attr_extra_state_attributes.update(get_attr_from_sia_event(sia_event))
state_changed = self.update_state(sia_event)
if state_changed:
self.async_reset_availability_cb()
self.async_write_ha_state()
@abstractmethod
def update_state(self, sia_event: SIAEvent) -> bool:
"""Do the entity specific state updates."""
@callback
def async_reset_availability_cb(self) -> None:
"""Reset availability cb by cancelling the current and creating a new one."""
self._attr_available = True
if self._cancel_availability_cb:
self._cancel_availability_cb()
self.async_create_availability_cb()
def async_create_availability_cb(self) -> None:
"""Create a availability cb and return the callback."""
self._cancel_availability_cb = async_call_later(
self.hass,
get_unavailability_interval(self.ping_interval),
self.async_set_unavailable,
)
@callback
def async_set_unavailable(self, _) -> None:
"""Set unavailable."""
self._attr_available = False
self.async_write_ha_state()