Home Assistant Unofficial Reference 2024.12.1
entity.py
Go to the documentation of this file.
1 """Module for SIA Base Entity."""
2 
3 from __future__ import annotations
4 
5 from abc import abstractmethod
6 from dataclasses import dataclass
7 import logging
8 
9 from pysiaalarm import SIAEvent
10 
11 from homeassistant.components.alarm_control_panel import AlarmControlPanelState
12 from homeassistant.config_entries import ConfigEntry
13 from homeassistant.const import CONF_PORT
14 from homeassistant.core import CALLBACK_TYPE, State, callback
15 from homeassistant.helpers.device_registry import DeviceInfo
16 from homeassistant.helpers.dispatcher import async_dispatcher_connect
17 from homeassistant.helpers.entity import EntityDescription
18 from homeassistant.helpers.event import async_call_later
19 from homeassistant.helpers.restore_state import RestoreEntity
20 from homeassistant.helpers.typing import StateType
21 
22 from .const import (
23  AVAILABILITY_EVENT_CODE,
24  CONF_ACCOUNT,
25  CONF_ACCOUNTS,
26  CONF_PING_INTERVAL,
27  DOMAIN,
28  SIA_EVENT,
29  SIA_HUB_ZONE,
30 )
31 from .utils import (
32  get_attr_from_sia_event,
33  get_unavailability_interval,
34  get_unique_id_and_name,
35 )
36 
37 _LOGGER = logging.getLogger(__name__)
38 
39 
40 @dataclass(frozen=True)
42  """Required keys for SIA entities."""
43 
44  code_consequences: dict[str, StateType | bool | AlarmControlPanelState]
45 
46 
47 @dataclass(frozen=True)
49  """Entity Description for SIA entities."""
50 
51 
52 class SIABaseEntity(RestoreEntity):
53  """Base class for SIA entities."""
54 
55  entity_description: SIAEntityDescription
56 
57  def __init__(
58  self,
59  entry: ConfigEntry,
60  account: str,
61  zone: int,
62  entity_description: SIAEntityDescription,
63  ) -> None:
64  """Create SIABaseEntity object."""
65  self.portport = entry.data[CONF_PORT]
66  self.accountaccount = account
67  self.zonezone = zone
68  self.entity_descriptionentity_description = entity_description
69 
70  self.ping_interval: int = next(
71  acc[CONF_PING_INTERVAL]
72  for acc in entry.data[CONF_ACCOUNTS]
73  if acc[CONF_ACCOUNT] == account
74  )
75  self._attr_unique_id, self._attr_name_attr_name = get_unique_id_and_name(
76  entry.entry_id, entry.data[CONF_PORT], account, zone, entity_description.key
77  )
78  self._attr_device_info_attr_device_info = DeviceInfo(
79  name=self._attr_name_attr_name,
80  identifiers={(DOMAIN, self._attr_unique_id)},
81  via_device=(DOMAIN, f"{entry.data[CONF_PORT]}_{account}"),
82  )
83 
84  self._post_interval_update_cb_canceller_post_interval_update_cb_canceller: CALLBACK_TYPE | None = None
85  self._attr_extra_state_attributes_attr_extra_state_attributes = {}
86  self._attr_should_poll_attr_should_poll = False
87 
88  async def async_added_to_hass(self) -> None:
89  """Run when entity about to be added to hass.
90 
91  Overridden from Entity.
92 
93  1. register the dispatcher and add the callback to on_remove
94  2. get previous state from storage and pass to entity specific function
95  3. if available: create availability cb
96  """
97  self.async_on_removeasync_on_remove(
99  self.hasshass,
100  SIA_EVENT.format(self.portport, self.accountaccount),
101  self.async_handle_eventasync_handle_event,
102  )
103  )
104  self.handle_last_statehandle_last_state(await self.async_get_last_stateasync_get_last_state())
105  if self._attr_available_attr_available:
106  self.async_create_post_interval_update_cbasync_create_post_interval_update_cb()
107 
108  @abstractmethod
109  def handle_last_state(self, last_state: State | None) -> None:
110  """Handle the last state."""
111 
112  async def async_will_remove_from_hass(self) -> None:
113  """Run when entity will be removed from hass.
114 
115  Overridden from Entity.
116  """
117  self._cancel_post_interval_update_cb_cancel_post_interval_update_cb()
118 
119  @callback
120  def async_handle_event(self, sia_event: SIAEvent) -> None:
121  """Listen to dispatcher events for this port and account and update state and attributes.
122 
123  If the event is for either the zone or the 0 zone (hub zone),
124  then handle it further.
125 
126  If the event had a code that was relevant for the entity,
127  then update the attributes.
128  If the event had a code that was relevant or it was a availability event
129  then update the availability and schedule the next unavailability check.
130  """
131  _LOGGER.debug("Received event: %s", sia_event)
132  if (int(sia_event.ri) if sia_event.ri else 0) not in (self.zonezone, SIA_HUB_ZONE):
133  return
134 
135  relevant_event = self.update_stateupdate_state(sia_event)
136 
137  if relevant_event:
138  self._attr_extra_state_attributes_attr_extra_state_attributes.update(get_attr_from_sia_event(sia_event))
139 
140  if relevant_event or sia_event.code == AVAILABILITY_EVENT_CODE:
141  self._attr_available_attr_available = True
142  self._cancel_post_interval_update_cb_cancel_post_interval_update_cb()
143  self.async_create_post_interval_update_cbasync_create_post_interval_update_cb()
144 
145  self.async_write_ha_stateasync_write_ha_state()
146 
147  @abstractmethod
148  def update_state(self, sia_event: SIAEvent) -> bool:
149  """Do the entity specific state updates.
150 
151  Return True if the event was relevant for this entity.
152  """
153 
154  @callback
156  """Create a port interval update cb and store the callback."""
157  self._post_interval_update_cb_canceller_post_interval_update_cb_canceller = async_call_later(
158  self.hasshass,
159  get_unavailability_interval(self.ping_interval),
160  self.async_post_interval_updateasync_post_interval_update,
161  )
162 
163  @callback
164  def async_post_interval_update(self, _) -> None:
165  """Set unavailable after a ping interval."""
166  self._attr_available_attr_available = False
167  self.async_write_ha_stateasync_write_ha_state()
168 
169  @callback
171  """Cancel the callback."""
172  if self._post_interval_update_cb_canceller_post_interval_update_cb_canceller:
173  self._post_interval_update_cb_canceller_post_interval_update_cb_canceller()
174  self._post_interval_update_cb_canceller_post_interval_update_cb_canceller = None
None handle_last_state(self, State|None last_state)
Definition: entity.py:109
bool update_state(self, SIAEvent sia_event)
Definition: entity.py:148
None __init__(self, ConfigEntry entry, str account, int zone, SIAEntityDescription entity_description)
Definition: entity.py:63
None async_handle_event(self, SIAEvent sia_event)
Definition: entity.py:120
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
dict[str, Any] get_attr_from_sia_event(SIAEvent event)
Definition: utils.py:53
tuple[str, str] get_unique_id_and_name(str entry_id, int port, str account, int zone, str entity_key)
Definition: utils.py:32
float get_unavailability_interval(int ping)
Definition: utils.py:48
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)
Definition: event.py:1597