Home Assistant Unofficial Reference 2024.12.1
siren.py
Go to the documentation of this file.
1 """Support for RFXtrx sirens."""
2 
3 from __future__ import annotations
4 
5 from datetime import datetime
6 from typing import Any
7 
8 import RFXtrx as rfxtrxmod
9 
10 from homeassistant.components.siren import ATTR_TONE, SirenEntity, SirenEntityFeature
11 from homeassistant.config_entries import ConfigEntry
12 from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
13 from homeassistant.helpers.entity import Entity
14 from homeassistant.helpers.entity_platform import AddEntitiesCallback
15 from homeassistant.helpers.event import async_call_later
16 
17 from . import DEFAULT_OFF_DELAY, DeviceTuple, async_setup_platform_entry
18 from .const import CONF_OFF_DELAY
19 from .entity import RfxtrxCommandEntity
20 
21 SECURITY_PANIC_ON = "Panic"
22 SECURITY_PANIC_OFF = "End Panic"
23 SECURITY_PANIC_ALL = {SECURITY_PANIC_ON, SECURITY_PANIC_OFF}
24 
25 
26 def supported(event: rfxtrxmod.RFXtrxEvent) -> bool:
27  """Return whether an event supports sirens."""
28  device = event.device
29 
30  if isinstance(device, rfxtrxmod.ChimeDevice):
31  return True
32 
33  if isinstance(device, rfxtrxmod.SecurityDevice) and isinstance(
34  event, rfxtrxmod.SensorEvent
35  ):
36  if event.values["Sensor Status"] in SECURITY_PANIC_ALL:
37  return True
38 
39  return False
40 
41 
42 def get_first_key(data: dict[int, str], entry: str) -> int:
43  """Find a key based on the items value."""
44  return next((key for key, value in data.items() if value == entry))
45 
46 
48  hass: HomeAssistant,
49  config_entry: ConfigEntry,
50  async_add_entities: AddEntitiesCallback,
51 ) -> None:
52  """Set up config entry."""
53 
54  def _constructor(
55  event: rfxtrxmod.RFXtrxEvent,
56  auto: rfxtrxmod.RFXtrxEvent | None,
57  device_id: DeviceTuple,
58  entity_info: dict[str, Any],
59  ) -> list[Entity]:
60  """Construct a entity from an event."""
61  device = event.device
62 
63  if isinstance(device, rfxtrxmod.ChimeDevice):
64  return [
66  event.device,
67  device_id,
68  entity_info.get(CONF_OFF_DELAY, DEFAULT_OFF_DELAY),
69  auto,
70  )
71  ]
72 
73  if isinstance(device, rfxtrxmod.SecurityDevice) and isinstance(
74  event, rfxtrxmod.SensorEvent
75  ):
76  if event.values["Sensor Status"] in SECURITY_PANIC_ALL:
77  return [
79  event.device,
80  device_id,
81  entity_info.get(CONF_OFF_DELAY, DEFAULT_OFF_DELAY),
82  auto,
83  )
84  ]
85  return []
86 
88  hass, config_entry, async_add_entities, supported, _constructor
89  )
90 
91 
92 class RfxtrxOffDelayMixin(Entity): # pylint: disable=hass-enforce-class-module
93  """Mixin to support timeouts on data.
94 
95  Many 433 devices only send data when active. They will
96  repeatedly (every x seconds) send a command to indicate
97  being active and stop sending this command when inactive.
98  This mixin allow us to keep track of the timeout once
99  they go inactive.
100  """
101 
102  _timeout: CALLBACK_TYPE | None = None
103  _off_delay: float | None = None
104 
105  def _setup_timeout(self) -> None:
106  @callback
107  def _done(_: datetime) -> None:
108  self._timeout_timeout = None
109  self.async_write_ha_stateasync_write_ha_state()
110 
111  if self._off_delay:
112  self._timeout_timeout = async_call_later(self.hasshass, self._off_delay, _done)
113 
114  def _cancel_timeout(self) -> None:
115  if self._timeout_timeout:
116  self._timeout_timeout()
117  self._timeout_timeout = None
118 
119  async def async_will_remove_from_hass(self) -> None:
120  """Run when entity will be removed from hass."""
121  self._cancel_timeout_cancel_timeout()
122  return await super().async_will_remove_from_hass()
123 
124 
126  """Representation of a RFXtrx chime."""
127 
128  _attr_supported_features = SirenEntityFeature.TURN_ON | SirenEntityFeature.TONES
129  _device: rfxtrxmod.ChimeDevice
130 
131  def __init__(
132  self,
133  device: rfxtrxmod.RFXtrxDevice,
134  device_id: DeviceTuple,
135  off_delay: float | None = None,
136  event: rfxtrxmod.RFXtrxEvent | None = None,
137  ) -> None:
138  """Initialize the entity."""
139  super().__init__(device, device_id, event)
140  self._attr_available_tones_attr_available_tones = list(self._device_device.COMMANDS.values())
141  self._default_tone_default_tone = next(iter(self._device_device.COMMANDS))
142  self._off_delay_off_delay = off_delay
143 
144  @property
145  def is_on(self) -> bool:
146  """Return true if device is on."""
147  return self._timeout_timeout is not None
148 
149  async def async_turn_on(self, **kwargs: Any) -> None:
150  """Turn the device on."""
151  self._cancel_timeout_cancel_timeout()
152 
153  if tone := kwargs.get(ATTR_TONE):
154  command = get_first_key(self._device_device.COMMANDS, tone)
155  else:
156  command = self._default_tone_default_tone
157 
158  await self._async_send(self._device_device.send_command, command)
159 
160  self._setup_timeout_setup_timeout()
161 
162  self.async_write_ha_stateasync_write_ha_state()
163 
164  def _apply_event(self, event: rfxtrxmod.ControlEvent) -> None:
165  """Apply a received event."""
166  super()._apply_event(event)
167 
168  sound = event.values.get("Sound")
169  if sound is not None:
170  self._cancel_timeout_cancel_timeout()
171  self._setup_timeout_setup_timeout()
172 
173  @callback
175  self, event: rfxtrxmod.RFXtrxEvent, device_id: DeviceTuple
176  ) -> None:
177  """Check if event applies to me and update."""
178  if self._event_applies_event_applies(event, device_id):
179  self._apply_event_apply_event_apply_event(event)
180 
181  self.async_write_ha_stateasync_write_ha_state()
182 
183 
185  """Representation of a security device."""
186 
187  _attr_supported_features = SirenEntityFeature.TURN_ON | SirenEntityFeature.TURN_OFF
188  _device: rfxtrxmod.SecurityDevice
189 
190  def __init__(
191  self,
192  device: rfxtrxmod.RFXtrxDevice,
193  device_id: DeviceTuple,
194  off_delay: float | None = None,
195  event: rfxtrxmod.RFXtrxEvent | None = None,
196  ) -> None:
197  """Initialize the entity."""
198  super().__init__(device, device_id, event)
199  self._on_value_on_value = get_first_key(self._device_device.STATUS, SECURITY_PANIC_ON)
200  self._off_value_off_value = get_first_key(self._device_device.STATUS, SECURITY_PANIC_OFF)
201  self._off_delay_off_delay = off_delay
202 
203  @property
204  def is_on(self) -> bool:
205  """Return true if device is on."""
206  return self._timeout_timeout is not None
207 
208  async def async_turn_on(self, **kwargs: Any) -> None:
209  """Turn the device on."""
210  self._cancel_timeout_cancel_timeout()
211 
212  await self._async_send(self._device_device.send_status, self._on_value_on_value)
213 
214  self._setup_timeout_setup_timeout()
215 
216  self.async_write_ha_stateasync_write_ha_state()
217 
218  async def async_turn_off(self, **kwargs: Any) -> None:
219  """Turn the device off."""
220  self._cancel_timeout_cancel_timeout()
221 
222  await self._async_send(self._device_device.send_status, self._off_value_off_value)
223 
224  self.async_write_ha_stateasync_write_ha_state()
225 
226  def _apply_event(self, event: rfxtrxmod.SensorEvent) -> None:
227  """Apply a received event."""
228  super()._apply_event(event)
229 
230  status = event.values.get("Sensor Status")
231 
232  if status == SECURITY_PANIC_ON:
233  self._cancel_timeout_cancel_timeout()
234  self._setup_timeout_setup_timeout()
235  elif status == SECURITY_PANIC_OFF:
236  self._cancel_timeout_cancel_timeout()
237 
238  @callback
240  self, event: rfxtrxmod.RFXtrxEvent, device_id: DeviceTuple
241  ) -> None:
242  """Check if event applies to me and update."""
243  if self._event_applies_event_applies(event, device_id):
244  self._apply_event_apply_event_apply_event(event)
245 
246  self.async_write_ha_stateasync_write_ha_state()
None _apply_event(self, rfxtrxmod.RFXtrxEvent event)
Definition: entity.py:91
bool _event_applies(self, rfxtrxmod.RFXtrxEvent event, DeviceTuple device_id)
Definition: entity.py:77
None _handle_event(self, rfxtrxmod.RFXtrxEvent event, DeviceTuple device_id)
Definition: siren.py:176
None _apply_event(self, rfxtrxmod.ControlEvent event)
Definition: siren.py:164
None __init__(self, rfxtrxmod.RFXtrxDevice device, DeviceTuple device_id, float|None off_delay=None, rfxtrxmod.RFXtrxEvent|None event=None)
Definition: siren.py:137
None async_turn_on(self, **Any kwargs)
Definition: siren.py:149
None _handle_event(self, rfxtrxmod.RFXtrxEvent event, DeviceTuple device_id)
Definition: siren.py:241
None _apply_event(self, rfxtrxmod.SensorEvent event)
Definition: siren.py:226
None __init__(self, rfxtrxmod.RFXtrxDevice device, DeviceTuple device_id, float|None off_delay=None, rfxtrxmod.RFXtrxEvent|None event=None)
Definition: siren.py:196
bool supported(rfxtrxmod.RFXtrxEvent event)
Definition: siren.py:26
int get_first_key(dict[int, str] data, str entry)
Definition: siren.py:42
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: siren.py:51
None async_setup_platform_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities, Callable[[rfxtrxmod.RFXtrxEvent], bool] supported, Callable[[rfxtrxmod.RFXtrxEvent, rfxtrxmod.RFXtrxEvent|None, DeviceTuple, dict[str, Any],], list[Entity],] constructor)
Definition: __init__.py:308
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