Home Assistant Unofficial Reference 2024.12.1
device_trigger.py
Go to the documentation of this file.
1 """Provides device triggers for Xiaomi BLE."""
2 
3 from __future__ import annotations
4 
5 from dataclasses import dataclass
6 from typing import Any
7 
8 import voluptuous as vol
9 
10 from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
11 from homeassistant.components.homeassistant.triggers import event as event_trigger
12 from homeassistant.const import (
13  CONF_DEVICE_ID,
14  CONF_DOMAIN,
15  CONF_EVENT,
16  CONF_PLATFORM,
17  CONF_TYPE,
18 )
19 from homeassistant.core import CALLBACK_TYPE, HomeAssistant
20 from homeassistant.helpers import device_registry as dr
21 from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
22 from homeassistant.helpers.typing import ConfigType
23 
24 from .const import (
25  BUTTON,
26  BUTTON_PRESS,
27  BUTTON_PRESS_DOUBLE_LONG,
28  BUTTON_PRESS_LONG,
29  CONF_SUBTYPE,
30  CUBE,
31  DIMMER,
32  DOMAIN,
33  DOUBLE_BUTTON,
34  DOUBLE_BUTTON_PRESS_DOUBLE_LONG,
35  ERROR,
36  EVENT_CLASS,
37  EVENT_CLASS_BUTTON,
38  EVENT_CLASS_CUBE,
39  EVENT_CLASS_DIMMER,
40  EVENT_CLASS_ERROR,
41  EVENT_CLASS_FINGERPRINT,
42  EVENT_CLASS_LOCK,
43  EVENT_CLASS_MOTION,
44  EVENT_TYPE,
45  FINGERPRINT,
46  LOCK,
47  LOCK_FINGERPRINT,
48  MOTION,
49  MOTION_DEVICE,
50  REMOTE,
51  REMOTE_BATHROOM,
52  REMOTE_FAN,
53  REMOTE_VENFAN,
54  TRIPPLE_BUTTON,
55  TRIPPLE_BUTTON_PRESS_DOUBLE_LONG,
56  XIAOMI_BLE_EVENT,
57 )
58 
59 TRIGGERS_BY_TYPE = {
60  BUTTON_PRESS: ["press"],
61  BUTTON_PRESS_LONG: ["press", "long_press"],
62  BUTTON_PRESS_DOUBLE_LONG: ["press", "double_press", "long_press"],
63  CUBE: ["rotate_left", "rotate_right"],
64  DIMMER: [
65  "press",
66  "long_press",
67  "rotate_left",
68  "rotate_right",
69  "rotate_left_pressed",
70  "rotate_right_pressed",
71  ],
72  ERROR: [
73  "frequent_unlocking_with_incorrect_password",
74  "frequent_unlocking_with_wrong_fingerprints",
75  "operation_timeout_password_input_timeout",
76  "lock_picking",
77  "reset_button_is_pressed",
78  "the_wrong_key_is_frequently_unlocked",
79  "foreign_body_in_the_keyhole",
80  "the_key_has_not_been_taken_out",
81  "error_nfc_frequently_unlocks",
82  "timeout_is_not_locked_as_required",
83  "failure_to_unlock_frequently_in_multiple_ways",
84  "unlocking_the_face_frequently_fails",
85  "failure_to_unlock_the_vein_frequently",
86  "hijacking_alarm",
87  "unlock_inside_the_door_after_arming",
88  "palmprints_frequently_fail_to_unlock",
89  "the_safe_was_moved",
90  "the_battery_level_is_less_than_10_percent",
91  "the_battery_level_is_less_than_5_percent",
92  "the_fingerprint_sensor_is_abnormal",
93  "the_accessory_battery_is_low",
94  "mechanical_failure",
95  "the_lock_sensor_is_faulty",
96  ],
97  FINGERPRINT: [
98  "match_successful",
99  "match_failed",
100  "low_quality_too_light_fuzzy",
101  "insufficient_area",
102  "skin_is_too_dry",
103  "skin_is_too_wet",
104  ],
105  LOCK: [
106  "lock_outside_the_door",
107  "unlock_outside_the_door",
108  "lock_inside_the_door",
109  "unlock_inside_the_door",
110  "locked",
111  "turn_on_antilock",
112  "release_the_antilock",
113  "turn_on_child_lock",
114  "turn_off_child_lock",
115  "abnormal",
116  ],
117  MOTION_DEVICE: ["motion_detected"],
118 }
119 
120 EVENT_TYPES = {
121  BUTTON: ["button"],
122  CUBE: ["cube"],
123  DIMMER: ["dimmer"],
124  DOUBLE_BUTTON: ["button_left", "button_right"],
125  TRIPPLE_BUTTON: ["button_left", "button_middle", "button_right"],
126  ERROR: ["error"],
127  FINGERPRINT: ["fingerprint"],
128  LOCK: ["lock"],
129  MOTION: ["motion"],
130  REMOTE: [
131  "button_on",
132  "button_off",
133  "button_brightness",
134  "button_plus",
135  "button_min",
136  "button_m",
137  ],
138  REMOTE_BATHROOM: [
139  "button_heat",
140  "button_air_exchange",
141  "button_dry",
142  "button_fan",
143  "button_swing",
144  "button_decrease_speed",
145  "button_increase_speed",
146  "button_stop",
147  "button_light",
148  ],
149  REMOTE_FAN: [
150  "button_fan",
151  "button_light",
152  "button_wind_speed",
153  "button_wind_mode",
154  "button_brightness",
155  "button_color_temperature",
156  ],
157  REMOTE_VENFAN: [
158  "button_swing",
159  "button_power",
160  "button_timer_30_minutes",
161  "button_timer_60_minutes",
162  "button_increase_wind_speed",
163  "button_decrease_wind_speed",
164  ],
165 }
166 
167 
168 @dataclass
170  """Data class for trigger model data."""
171 
172  event_class: str
173  event_types: list[str]
174  triggers: list[str]
175 
176 
177 TRIGGER_MODEL_DATA = {
178  BUTTON_PRESS: TriggerModelData(
179  event_class=EVENT_CLASS_BUTTON,
180  event_types=EVENT_TYPES[BUTTON],
181  triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS],
182  ),
183  BUTTON_PRESS_DOUBLE_LONG: TriggerModelData(
184  event_class=EVENT_CLASS_BUTTON,
185  event_types=EVENT_TYPES[BUTTON],
186  triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_DOUBLE_LONG],
187  ),
188  DOUBLE_BUTTON_PRESS_DOUBLE_LONG: TriggerModelData(
189  event_class=EVENT_CLASS_BUTTON,
190  event_types=EVENT_TYPES[DOUBLE_BUTTON],
191  triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_DOUBLE_LONG],
192  ),
193  CUBE: TriggerModelData(
194  event_class=EVENT_CLASS_CUBE,
195  event_types=EVENT_TYPES[CUBE],
196  triggers=TRIGGERS_BY_TYPE[CUBE],
197  ),
198  DIMMER: TriggerModelData(
199  event_class=EVENT_CLASS_DIMMER,
200  event_types=EVENT_TYPES[DIMMER],
201  triggers=TRIGGERS_BY_TYPE[DIMMER],
202  ),
203  TRIPPLE_BUTTON_PRESS_DOUBLE_LONG: TriggerModelData(
204  event_class=EVENT_CLASS_BUTTON,
205  event_types=EVENT_TYPES[TRIPPLE_BUTTON],
206  triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_DOUBLE_LONG],
207  ),
208  ERROR: TriggerModelData(
209  event_class=EVENT_CLASS_ERROR,
210  event_types=EVENT_TYPES[ERROR],
211  triggers=TRIGGERS_BY_TYPE[ERROR],
212  ),
213  LOCK_FINGERPRINT: TriggerModelData(
214  event_class=EVENT_CLASS_FINGERPRINT,
215  event_types=EVENT_TYPES[LOCK] + EVENT_TYPES[FINGERPRINT],
216  triggers=TRIGGERS_BY_TYPE[LOCK] + TRIGGERS_BY_TYPE[FINGERPRINT],
217  ),
218  LOCK: TriggerModelData(
219  event_class=EVENT_CLASS_LOCK,
220  event_types=EVENT_TYPES[LOCK],
221  triggers=TRIGGERS_BY_TYPE[LOCK],
222  ),
223  MOTION_DEVICE: TriggerModelData(
224  event_class=EVENT_CLASS_MOTION,
225  event_types=EVENT_TYPES[MOTION],
226  triggers=TRIGGERS_BY_TYPE[MOTION_DEVICE],
227  ),
228  REMOTE: TriggerModelData(
229  event_class=EVENT_CLASS_BUTTON,
230  event_types=EVENT_TYPES[REMOTE],
231  triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_LONG],
232  ),
233  REMOTE_BATHROOM: TriggerModelData(
234  event_class=EVENT_CLASS_BUTTON,
235  event_types=EVENT_TYPES[REMOTE_BATHROOM],
236  triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_LONG],
237  ),
238  REMOTE_FAN: TriggerModelData(
239  event_class=EVENT_CLASS_BUTTON,
240  event_types=EVENT_TYPES[REMOTE_FAN],
241  triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_LONG],
242  ),
243  REMOTE_VENFAN: TriggerModelData(
244  event_class=EVENT_CLASS_BUTTON,
245  event_types=EVENT_TYPES[REMOTE_VENFAN],
246  triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_LONG],
247  ),
248 }
249 
250 
251 MODEL_DATA = {
252  "JTYJGD03MI": TRIGGER_MODEL_DATA[BUTTON_PRESS],
253  "MS1BB(MI)": TRIGGER_MODEL_DATA[BUTTON_PRESS],
254  "RTCGQ02LM": TRIGGER_MODEL_DATA[BUTTON_PRESS],
255  "SJWS01LM": TRIGGER_MODEL_DATA[BUTTON_PRESS],
256  "K9BB-1BTN": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
257  "YLAI003": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
258  "XMWXKG01LM": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
259  "PTX_YK1_QMIMB": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
260  "K9B-1BTN": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
261  "XMWXKG01YL": TRIGGER_MODEL_DATA[DOUBLE_BUTTON_PRESS_DOUBLE_LONG],
262  "K9B-2BTN": TRIGGER_MODEL_DATA[DOUBLE_BUTTON_PRESS_DOUBLE_LONG],
263  "K9B-3BTN": TRIGGER_MODEL_DATA[TRIPPLE_BUTTON_PRESS_DOUBLE_LONG],
264  "YLYK01YL": TRIGGER_MODEL_DATA[REMOTE],
265  "YLYK01YL-FANRC": TRIGGER_MODEL_DATA[REMOTE_FAN],
266  "YLYK01YL-VENFAN": TRIGGER_MODEL_DATA[REMOTE_VENFAN],
267  "YLYK01YL-BHFRC": TRIGGER_MODEL_DATA[REMOTE_BATHROOM],
268  "MUE4094RT": TRIGGER_MODEL_DATA[MOTION_DEVICE],
269  "XMMF01JQD": TRIGGER_MODEL_DATA[CUBE],
270  "YLKG07YL/YLKG08YL": TRIGGER_MODEL_DATA[DIMMER],
271  "DSL-C08": TRIGGER_MODEL_DATA[LOCK],
272  "XMZNMS08LM": TRIGGER_MODEL_DATA[LOCK],
273  "Lockin-SV40": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT],
274  "MJZNMSQ01YD": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT],
275  "XMZNMS04LM": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT],
276  "ZNMS16LM": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT],
277  "ZNMS17LM": TRIGGER_MODEL_DATA[LOCK_FINGERPRINT],
278 }
279 
280 
282  hass: HomeAssistant, config: ConfigType
283 ) -> ConfigType:
284  """Validate trigger config."""
285  device_id = config[CONF_DEVICE_ID]
286  if model_data := _async_trigger_model_data(hass, device_id):
287  schema = DEVICE_TRIGGER_BASE_SCHEMA.extend(
288  {
289  vol.Required(CONF_TYPE): vol.In(model_data.event_types),
290  vol.Required(CONF_SUBTYPE): vol.In(model_data.triggers),
291  }
292  )
293  return schema(config) # type: ignore[no-any-return]
294  return config
295 
296 
298  hass: HomeAssistant, device_id: str
299 ) -> list[dict[str, Any]]:
300  """List a list of triggers for Xiaomi BLE devices."""
301 
302  # Check if device is a model supporting device triggers.
303  if not (model_data := _async_trigger_model_data(hass, device_id)):
304  return []
305 
306  event_types = model_data.event_types
307  event_subtypes = model_data.triggers
308  return [
309  {
310  # Required fields of TRIGGER_BASE_SCHEMA
311  CONF_PLATFORM: "device",
312  CONF_DEVICE_ID: device_id,
313  CONF_DOMAIN: DOMAIN,
314  # Required fields of TRIGGER_SCHEMA
315  CONF_TYPE: event_type,
316  CONF_SUBTYPE: event_subtype,
317  }
318  for event_type in event_types
319  for event_subtype in event_subtypes
320  ]
321 
322 
324  hass: HomeAssistant,
325  config: ConfigType,
326  action: TriggerActionType,
327  trigger_info: TriggerInfo,
328 ) -> CALLBACK_TYPE:
329  """Attach a trigger."""
330  return await event_trigger.async_attach_trigger(
331  hass,
332  event_trigger.TRIGGER_SCHEMA(
333  {
334  event_trigger.CONF_PLATFORM: CONF_EVENT,
335  event_trigger.CONF_EVENT_TYPE: XIAOMI_BLE_EVENT,
336  event_trigger.CONF_EVENT_DATA: {
337  CONF_DEVICE_ID: config[CONF_DEVICE_ID],
338  EVENT_CLASS: config[CONF_TYPE],
339  EVENT_TYPE: config[CONF_SUBTYPE],
340  },
341  }
342  ),
343  action,
344  trigger_info,
345  platform_type="device",
346  )
347 
348 
350  hass: HomeAssistant, device_id: str
351 ) -> TriggerModelData | None:
352  """Get available triggers for a given model."""
353  device_registry = dr.async_get(hass)
354  device = device_registry.async_get(device_id)
355  if device and device.model and (model_data := MODEL_DATA.get(device.model)):
356  return model_data
357  return None
list[dict[str, Any]] async_get_triggers(HomeAssistant hass, str device_id)
CALLBACK_TYPE async_attach_trigger(HomeAssistant hass, ConfigType config, TriggerActionType action, TriggerInfo trigger_info)
ConfigType async_validate_trigger_config(HomeAssistant hass, ConfigType config)
TriggerModelData|None _async_trigger_model_data(HomeAssistant hass, str device_id)