Home Assistant Unofficial Reference 2024.12.1
event.py
Go to the documentation of this file.
1 """Support for Xiaomi event entities."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import replace
6 
8  EventDeviceClass,
9  EventEntity,
10  EventEntityDescription,
11 )
12 from homeassistant.core import HomeAssistant, callback
13 from homeassistant.helpers import device_registry as dr, entity_registry as er
14 from homeassistant.helpers.dispatcher import async_dispatcher_connect
15 from homeassistant.helpers.entity_platform import AddEntitiesCallback
16 
17 from . import format_discovered_event_class, format_event_dispatcher_name
18 from .const import (
19  DOMAIN,
20  EVENT_CLASS_BUTTON,
21  EVENT_CLASS_CUBE,
22  EVENT_CLASS_DIMMER,
23  EVENT_CLASS_ERROR,
24  EVENT_CLASS_FINGERPRINT,
25  EVENT_CLASS_LOCK,
26  EVENT_CLASS_MOTION,
27  EVENT_PROPERTIES,
28  EVENT_TYPE,
29  XiaomiBleEvent,
30 )
31 from .types import XiaomiBLEConfigEntry
32 
33 DESCRIPTIONS_BY_EVENT_CLASS = {
34  EVENT_CLASS_BUTTON: EventEntityDescription(
35  key=EVENT_CLASS_BUTTON,
36  translation_key="button",
37  event_types=[
38  "press",
39  "double_press",
40  "long_press",
41  ],
42  device_class=EventDeviceClass.BUTTON,
43  ),
44  EVENT_CLASS_CUBE: EventEntityDescription(
45  key=EVENT_CLASS_CUBE,
46  translation_key="cube",
47  event_types=[
48  "rotate_left",
49  "rotate_right",
50  ],
51  ),
52  EVENT_CLASS_DIMMER: EventEntityDescription(
53  key=EVENT_CLASS_DIMMER,
54  translation_key="dimmer",
55  event_types=[
56  "press",
57  "long_press",
58  "rotate_left",
59  "rotate_right",
60  "rotate_left_pressed",
61  "rotate_right_pressed",
62  ],
63  ),
64  EVENT_CLASS_ERROR: EventEntityDescription(
65  key=EVENT_CLASS_ERROR,
66  translation_key="error",
67  event_types=[
68  "frequent_unlocking_with_incorrect_password",
69  "frequent_unlocking_with_wrong_fingerprints",
70  "operation_timeout_password_input_timeout",
71  "lock_picking",
72  "reset_button_is_pressed",
73  "the_wrong_key_is_frequently_unlocked",
74  "foreign_body_in_the_keyhole",
75  "the_key_has_not_been_taken_out",
76  "error_nfc_frequently_unlocks",
77  "timeout_is_not_locked_as_required",
78  "failure_to_unlock_frequently_in_multiple_ways",
79  "unlocking_the_face_frequently_fails",
80  "failure_to_unlock_the_vein_frequently",
81  "hijacking_alarm",
82  "unlock_inside_the_door_after_arming",
83  "palmprints_frequently_fail_to_unlock",
84  "the_safe_was_moved",
85  "the_battery_level_is_less_than_10_percent",
86  "the_battery_is_less_than_5_percent",
87  "the_fingerprint_sensor_is_abnormal",
88  "the_accessory_battery_is_low",
89  "mechanical_failure",
90  "the_lock_sensor_is_faulty",
91  ],
92  ),
93  EVENT_CLASS_FINGERPRINT: EventEntityDescription(
94  key=EVENT_CLASS_FINGERPRINT,
95  translation_key="fingerprint",
96  event_types=[
97  "match_successful",
98  "match_failed",
99  "low_quality_too_light_fuzzy",
100  "insufficient_area",
101  "skin_is_too_dry",
102  "skin_is_too_wet",
103  ],
104  ),
105  EVENT_CLASS_LOCK: EventEntityDescription(
106  key=EVENT_CLASS_LOCK,
107  translation_key="lock",
108  event_types=[
109  "lock_outside_the_door",
110  "unlock_outside_the_door",
111  "lock_inside_the_door",
112  "unlock_inside_the_door",
113  "locked",
114  "turn_on_antilock",
115  "release_the_antilock",
116  "turn_on_child_lock",
117  "turn_off_child_lock",
118  "abnormal",
119  ],
120  ),
121  EVENT_CLASS_MOTION: EventEntityDescription(
122  key=EVENT_CLASS_MOTION,
123  translation_key="motion",
124  event_types=["motion_detected"],
125  device_class=EventDeviceClass.MOTION,
126  ),
127 }
128 
129 
131  """Representation of a Xiaomi event entity."""
132 
133  _attr_should_poll = False
134  _attr_has_entity_name = True
135 
136  def __init__(
137  self,
138  address: str,
139  event_class: str,
140  event: XiaomiBleEvent | None,
141  ) -> None:
142  """Initialise a Xiaomi event entity."""
143  self._update_signal_update_signal = format_event_dispatcher_name(address, event_class)
144  # event_class is something like "button" or "motion"
145  # and it maybe postfixed with "_1", "_2", "_3", etc
146  # If there is only one button then it will be "button"
147  base_event_class, _, postfix = event_class.partition("_")
148  base_description = DESCRIPTIONS_BY_EVENT_CLASS[base_event_class]
149  self.entity_descriptionentity_description = replace(base_description, key=event_class)
150  postfix_name = f" {postfix}" if postfix else ""
151  self._attr_name_attr_name = f"{base_event_class.title()}{postfix_name}"
152  # Matches logic in PassiveBluetoothProcessorEntity
153  self._attr_device_info_attr_device_info = dr.DeviceInfo(
154  identifiers={(DOMAIN, address)},
155  connections={(dr.CONNECTION_BLUETOOTH, address)},
156  )
157  self._attr_unique_id_attr_unique_id = f"{address}-{event_class}"
158  # If the event is provided then we can set the initial state
159  # since the event itself is likely what triggered the creation
160  # of this entity. We have to do this at creation time since
161  # entities are created dynamically and would otherwise miss
162  # the initial state.
163  if event:
164  self._trigger_event_trigger_event(event[EVENT_TYPE], event[EVENT_PROPERTIES])
165 
166  async def async_added_to_hass(self) -> None:
167  """Entity added to hass."""
168  await super().async_added_to_hass()
169  self.async_on_removeasync_on_remove(
171  self.hasshass,
172  self._update_signal_update_signal,
173  self._async_handle_event_async_handle_event,
174  )
175  )
176 
177  @callback
178  def _async_handle_event(self, event: XiaomiBleEvent) -> None:
179  self._trigger_event_trigger_event(event[EVENT_TYPE], event[EVENT_PROPERTIES])
180  self.async_write_ha_stateasync_write_ha_state()
181 
182 
184  hass: HomeAssistant,
185  entry: XiaomiBLEConfigEntry,
186  async_add_entities: AddEntitiesCallback,
187 ) -> None:
188  """Set up Xiaomi event."""
189  coordinator = entry.runtime_data
190  address = coordinator.address
191  ent_reg = er.async_get(hass)
193  # Matches logic in PassiveBluetoothProcessorEntity
194  XiaomiEventEntity(address_event_class[0], address_event_class[2], None)
195  for ent_reg_entry in er.async_entries_for_config_entry(ent_reg, entry.entry_id)
196  if ent_reg_entry.domain == "event"
197  and (address_event_class := ent_reg_entry.unique_id.partition("-"))
198  )
199 
200  @callback
201  def _async_discovered_event_class(event_class: str, event: XiaomiBleEvent) -> None:
202  """Handle a newly discovered event class with or without a postfix."""
203  async_add_entities([XiaomiEventEntity(address, event_class, event)])
204 
205  entry.async_on_unload(
207  hass,
209  _async_discovered_event_class,
210  )
211  )
None _trigger_event(self, str event_type, dict[str, Any]|None event_attributes=None)
Definition: __init__.py:148
None _async_handle_event(self, XiaomiBleEvent event)
Definition: event.py:178
None __init__(self, str address, str event_class, XiaomiBleEvent|None event)
Definition: event.py:141
None async_on_remove(self, CALLBACK_TYPE func)
Definition: entity.py:1331
None async_setup_entry(HomeAssistant hass, XiaomiBLEConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: event.py:187
str format_discovered_event_class(str address)
Definition: __init__.py:117
str format_event_dispatcher_name(str address, str event_class)
Definition: __init__.py:112
Callable[[], None] async_dispatcher_connect(HomeAssistant hass, str signal, Callable[..., Any] target)
Definition: dispatcher.py:103