Home Assistant Unofficial Reference 2024.12.1
deconz_event.py
Go to the documentation of this file.
1 """Representation of a deCONZ remote or keypad."""
2 
3 from __future__ import annotations
4 
5 from typing import Any
6 
7 from pydeconz.models.event import EventType
8 from pydeconz.models.sensor.ancillary_control import (
9  AncillaryControl,
10  AncillaryControlAction,
11 )
12 from pydeconz.models.sensor.presence import Presence, PresenceStatePresenceEvent
13 from pydeconz.models.sensor.relative_rotary import RelativeRotary, RelativeRotaryEvent
14 from pydeconz.models.sensor.switch import Switch
15 
16 from homeassistant.const import (
17  CONF_DEVICE_ID,
18  CONF_EVENT,
19  CONF_ID,
20  CONF_UNIQUE_ID,
21  CONF_XY,
22 )
23 from homeassistant.core import callback
24 from homeassistant.helpers import device_registry as dr
25 from homeassistant.util import slugify
26 
27 from .const import ATTR_DURATION, ATTR_ROTATION, CONF_ANGLE, CONF_GESTURE, LOGGER
28 from .entity import DeconzBase
29 from .hub import DeconzHub
30 
31 CONF_DECONZ_EVENT = "deconz_event"
32 CONF_DECONZ_ALARM_EVENT = "deconz_alarm_event"
33 CONF_DECONZ_PRESENCE_EVENT = "deconz_presence_event"
34 CONF_DECONZ_RELATIVE_ROTARY_EVENT = "deconz_relative_rotary_event"
35 
36 SUPPORTED_DECONZ_ALARM_EVENTS = {
37  AncillaryControlAction.EMERGENCY,
38  AncillaryControlAction.FIRE,
39  AncillaryControlAction.INVALID_CODE,
40  AncillaryControlAction.PANIC,
41 }
42 SUPPORTED_DECONZ_PRESENCE_EVENTS = {
43  PresenceStatePresenceEvent.ENTER,
44  PresenceStatePresenceEvent.LEAVE,
45  PresenceStatePresenceEvent.ENTER_LEFT,
46  PresenceStatePresenceEvent.RIGHT_LEAVE,
47  PresenceStatePresenceEvent.ENTER_RIGHT,
48  PresenceStatePresenceEvent.LEFT_LEAVE,
49  PresenceStatePresenceEvent.APPROACHING,
50  PresenceStatePresenceEvent.ABSENTING,
51 }
52 RELATIVE_ROTARY_DECONZ_TO_EVENT = {
53  RelativeRotaryEvent.NEW: "new",
54  RelativeRotaryEvent.REPEAT: "repeat",
55 }
56 
57 
58 async def async_setup_events(hub: DeconzHub) -> None:
59  """Set up the deCONZ events."""
60 
61  @callback
62  def async_add_sensor(_: EventType, sensor_id: str) -> None:
63  """Create DeconzEvent."""
64  new_event: (
65  DeconzAlarmEvent
66  | DeconzEvent
67  | DeconzPresenceEvent
68  | DeconzRelativeRotaryEvent
69  )
70  sensor = hub.api.sensors[sensor_id]
71 
72  if isinstance(sensor, Switch):
73  new_event = DeconzEvent(sensor, hub)
74 
75  elif isinstance(sensor, AncillaryControl):
76  new_event = DeconzAlarmEvent(sensor, hub)
77 
78  elif isinstance(sensor, Presence):
79  if sensor.presence_event is None:
80  return
81  new_event = DeconzPresenceEvent(sensor, hub)
82 
83  elif isinstance(sensor, RelativeRotary):
84  new_event = DeconzRelativeRotaryEvent(sensor, hub)
85 
86  hub.hass.async_create_task(new_event.async_update_device_registry())
87  hub.events.append(new_event)
88 
89  hub.register_platform_add_device_callback(
90  async_add_sensor,
91  hub.api.sensors.switch,
92  )
93  hub.register_platform_add_device_callback(
94  async_add_sensor,
95  hub.api.sensors.ancillary_control,
96  )
97  hub.register_platform_add_device_callback(
98  async_add_sensor,
99  hub.api.sensors.presence,
100  )
101  hub.register_platform_add_device_callback(
102  async_add_sensor,
103  hub.api.sensors.relative_rotary,
104  )
105 
106 
107 @callback
108 def async_unload_events(hub: DeconzHub) -> None:
109  """Unload all deCONZ events."""
110  for event in hub.events:
111  event.async_will_remove_from_hass()
112 
113  hub.events.clear()
114 
115 
117  """When you want signals instead of entities.
118 
119  Stateless sensors such as remotes are expected to generate an event
120  instead of a sensor entity in hass.
121  """
122 
123  def __init__(
124  self,
125  device: AncillaryControl | Presence | RelativeRotary | Switch,
126  hub: DeconzHub,
127  ) -> None:
128  """Register callback that will be used for signals."""
129  super().__init__(device, hub)
130 
131  self._unsubscribe_unsubscribe = device.subscribe(self.async_update_callbackasync_update_callback)
132 
133  self.devicedevice = device
134  self.device_iddevice_id: str | None = None
135  self.event_idevent_id = slugify(self._device.name)
136  LOGGER.debug("deCONZ event created: %s", self.event_idevent_id)
137 
138  @callback
139  def async_will_remove_from_hass(self) -> None:
140  """Disconnect event object when removed."""
141  self._unsubscribe_unsubscribe()
142 
143  @callback
144  def async_update_callback(self) -> None:
145  """Fire the event if reason is that state is updated."""
146  raise NotImplementedError
147 
148  async def async_update_device_registry(self) -> None:
149  """Update device registry."""
150  if not self.device_info:
151  return
152 
153  device_registry = dr.async_get(self.hub.hass)
154 
155  entry = device_registry.async_get_or_create(
156  config_entry_id=self.hub.config_entry.entry_id, **self.device_info
157  )
158  self.device_iddevice_id = entry.id
159 
160 
162  """When you want signals instead of entities.
163 
164  Stateless sensors such as remotes are expected to generate an event
165  instead of a sensor entity in hass.
166  """
167 
168  _device: Switch
169 
170  @callback
171  def async_update_callback(self) -> None:
172  """Fire the event if reason is that state is updated."""
173  if self.hub.ignore_state_updates or "state" not in self._device.changed_keys:
174  return
175 
176  data: dict[str, Any] = {
177  CONF_ID: self.event_idevent_id,
178  CONF_UNIQUE_ID: self.serial,
179  CONF_EVENT: self._device.button_event,
180  }
181 
182  if self.device_iddevice_id:
183  data[CONF_DEVICE_ID] = self.device_iddevice_id
184 
185  if self._device.gesture is not None:
186  data[CONF_GESTURE] = self._device.gesture
187 
188  if self._device.angle is not None:
189  data[CONF_ANGLE] = self._device.angle
190 
191  if self._device.xy is not None:
192  data[CONF_XY] = self._device.xy
193 
194  self.hub.hass.bus.async_fire(CONF_DECONZ_EVENT, data)
195 
196 
198  """Alarm control panel companion event when user interacts with a keypad."""
199 
200  _device: AncillaryControl
201 
202  @callback
203  def async_update_callback(self) -> None:
204  """Fire the event if reason is new action is updated."""
205  if (
206  self.hub.ignore_state_updates
207  or "action" not in self._device.changed_keys
208  or self._device.action not in SUPPORTED_DECONZ_ALARM_EVENTS
209  ):
210  return
211 
212  data = {
213  CONF_ID: self.event_idevent_id,
214  CONF_UNIQUE_ID: self.serial,
215  CONF_DEVICE_ID: self.device_iddevice_id,
216  CONF_EVENT: self._device.action.value,
217  }
218 
219  self.hub.hass.bus.async_fire(CONF_DECONZ_ALARM_EVENT, data)
220 
221 
223  """Presence event."""
224 
225  _device: Presence
226 
227  @callback
228  def async_update_callback(self) -> None:
229  """Fire the event if reason is new action is updated."""
230  if (
231  self.hub.ignore_state_updates
232  or "presenceevent" not in self._device.changed_keys
233  or self._device.presence_event not in SUPPORTED_DECONZ_PRESENCE_EVENTS
234  ):
235  return
236 
237  data = {
238  CONF_ID: self.event_idevent_id,
239  CONF_UNIQUE_ID: self.serial,
240  CONF_DEVICE_ID: self.device_iddevice_id,
241  CONF_EVENT: self._device.presence_event.value,
242  }
243 
244  self.hub.hass.bus.async_fire(CONF_DECONZ_PRESENCE_EVENT, data)
245 
246 
248  """Relative rotary event."""
249 
250  _device: RelativeRotary
251 
252  @callback
253  def async_update_callback(self) -> None:
254  """Fire the event if reason is new action is updated."""
255  if (
256  self.hub.ignore_state_updates
257  or "rotaryevent" not in self._device.changed_keys
258  ):
259  return
260 
261  data = {
262  CONF_ID: self.event_idevent_id,
263  CONF_UNIQUE_ID: self.serial,
264  CONF_DEVICE_ID: self.device_iddevice_id,
265  CONF_EVENT: RELATIVE_ROTARY_DECONZ_TO_EVENT[self._device.rotary_event],
266  ATTR_ROTATION: self._device.expected_rotation,
267  ATTR_DURATION: self._device.expected_event_duration,
268  }
269 
270  self.hub.hass.bus.async_fire(CONF_DECONZ_RELATIVE_ROTARY_EVENT, data)
None __init__(self, AncillaryControl|Presence|RelativeRotary|Switch device, DeconzHub hub)