Fix Xiaomi-ble automations for multiple button devices (#109251)

pull/109294/head
Ernst Klamer 2024-02-01 09:04:02 +01:00 committed by GitHub
parent e11e54fd50
commit 697d4987c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 112 additions and 29 deletions

View File

@ -21,8 +21,15 @@ XIAOMI_BLE_EVENT: Final = "xiaomi_ble_event"
EVENT_CLASS_BUTTON: Final = "button"
EVENT_CLASS_MOTION: Final = "motion"
BUTTON: Final = "button"
DOUBLE_BUTTON: Final = "double_button"
TRIPPLE_BUTTON: Final = "tripple_button"
MOTION: Final = "motion"
BUTTON_PRESS: Final = "button_press"
BUTTON_PRESS_DOUBLE_LONG: Final = "button_press_double_long"
DOUBLE_BUTTON_PRESS_DOUBLE_LONG: Final = "double_button_press_double_long"
TRIPPLE_BUTTON_PRESS_DOUBLE_LONG: Final = "tripple_button_press_double_long"
MOTION_DEVICE: Final = "motion_device"

View File

@ -21,15 +21,21 @@ from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
from homeassistant.helpers.typing import ConfigType
from .const import (
BUTTON,
BUTTON_PRESS,
BUTTON_PRESS_DOUBLE_LONG,
CONF_SUBTYPE,
DOMAIN,
DOUBLE_BUTTON,
DOUBLE_BUTTON_PRESS_DOUBLE_LONG,
EVENT_CLASS,
EVENT_CLASS_BUTTON,
EVENT_CLASS_MOTION,
EVENT_TYPE,
MOTION,
MOTION_DEVICE,
TRIPPLE_BUTTON,
TRIPPLE_BUTTON_PRESS_DOUBLE_LONG,
XIAOMI_BLE_EVENT,
)
@ -39,47 +45,47 @@ TRIGGERS_BY_TYPE = {
MOTION_DEVICE: ["motion_detected"],
}
EVENT_TYPES = {
BUTTON: ["button"],
DOUBLE_BUTTON: ["button_left", "button_right"],
TRIPPLE_BUTTON: ["button_left", "button_middle", "button_right"],
MOTION: ["motion"],
}
@dataclass
class TriggerModelData:
"""Data class for trigger model data."""
schema: vol.Schema
event_class: str
event_types: list[str]
triggers: list[str]
TRIGGER_MODEL_DATA = {
BUTTON_PRESS: TriggerModelData(
schema=DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): vol.In([EVENT_CLASS_BUTTON]),
vol.Required(CONF_SUBTYPE): vol.In(TRIGGERS_BY_TYPE[BUTTON_PRESS]),
}
),
event_class=EVENT_CLASS_BUTTON,
event_types=EVENT_TYPES[BUTTON],
triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS],
),
BUTTON_PRESS_DOUBLE_LONG: TriggerModelData(
schema=DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): vol.In([EVENT_CLASS_BUTTON]),
vol.Required(CONF_SUBTYPE): vol.In(
TRIGGERS_BY_TYPE[BUTTON_PRESS_DOUBLE_LONG]
),
}
),
event_class=EVENT_CLASS_BUTTON,
event_types=EVENT_TYPES[BUTTON],
triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_DOUBLE_LONG],
),
DOUBLE_BUTTON_PRESS_DOUBLE_LONG: TriggerModelData(
event_class=EVENT_CLASS_BUTTON,
event_types=EVENT_TYPES[DOUBLE_BUTTON],
triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_DOUBLE_LONG],
),
TRIPPLE_BUTTON_PRESS_DOUBLE_LONG: TriggerModelData(
event_class=EVENT_CLASS_BUTTON,
event_types=EVENT_TYPES[TRIPPLE_BUTTON],
triggers=TRIGGERS_BY_TYPE[BUTTON_PRESS_DOUBLE_LONG],
),
MOTION_DEVICE: TriggerModelData(
schema=DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): vol.In([EVENT_CLASS_MOTION]),
vol.Required(CONF_SUBTYPE): vol.In(TRIGGERS_BY_TYPE[MOTION_DEVICE]),
}
),
event_class=EVENT_CLASS_MOTION,
event_types=EVENT_TYPES[MOTION],
triggers=TRIGGERS_BY_TYPE[MOTION_DEVICE],
),
}
@ -90,13 +96,13 @@ MODEL_DATA = {
"MS1BB(MI)": TRIGGER_MODEL_DATA[BUTTON_PRESS],
"RTCGQ02LM": TRIGGER_MODEL_DATA[BUTTON_PRESS],
"SJWS01LM": TRIGGER_MODEL_DATA[BUTTON_PRESS],
"K9B-1BTN": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
"K9B-2BTN": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
"K9B-3BTN": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
"K9BB-1BTN": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
"YLAI003": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
"XMWXKG01LM": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
"XMWXKG01YL": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
"K9B-1BTN": TRIGGER_MODEL_DATA[BUTTON_PRESS_DOUBLE_LONG],
"XMWXKG01YL": TRIGGER_MODEL_DATA[DOUBLE_BUTTON_PRESS_DOUBLE_LONG],
"K9B-2BTN": TRIGGER_MODEL_DATA[DOUBLE_BUTTON_PRESS_DOUBLE_LONG],
"K9B-3BTN": TRIGGER_MODEL_DATA[TRIPPLE_BUTTON_PRESS_DOUBLE_LONG],
"MUE4094RT": TRIGGER_MODEL_DATA[MOTION_DEVICE],
}
@ -107,7 +113,13 @@ async def async_validate_trigger_config(
"""Validate trigger config."""
device_id = config[CONF_DEVICE_ID]
if model_data := _async_trigger_model_data(hass, device_id):
return model_data.schema(config) # type: ignore[no-any-return]
schema = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): vol.In(model_data.event_types),
vol.Required(CONF_SUBTYPE): vol.In(model_data.triggers),
}
)
return schema(config) # type: ignore[no-any-return]
return config
@ -120,7 +132,7 @@ async def async_get_triggers(
if not (model_data := _async_trigger_model_data(hass, device_id)):
return []
event_type = model_data.event_class
event_types = model_data.event_types
event_subtypes = model_data.triggers
return [
{
@ -132,6 +144,7 @@ async def async_get_triggers(
CONF_TYPE: event_type,
CONF_SUBTYPE: event_subtype,
}
for event_type in event_types
for event_subtype in event_subtypes
]

View File

@ -48,6 +48,9 @@
},
"trigger_type": {
"button": "Button \"{subtype}\"",
"button_left": "Button Left \"{subtype}\"",
"button_middle": "Button Middle \"{subtype}\"",
"button_right": "Button Right \"{subtype}\"",
"motion": "{subtype}"
}
},

View File

@ -164,8 +164,8 @@ async def test_get_triggers_double_button(hass: HomeAssistant) -> None:
CONF_PLATFORM: "device",
CONF_DOMAIN: DOMAIN,
CONF_DEVICE_ID: device.id,
CONF_TYPE: "button",
CONF_SUBTYPE: "press",
CONF_TYPE: "button_right",
CONF_SUBTYPE: "long_press",
"metadata": {},
}
triggers = await async_get_device_automations(
@ -334,6 +334,66 @@ async def test_if_fires_on_button_press(hass: HomeAssistant, calls) -> None:
await hass.async_block_till_done()
async def test_if_fires_on_double_button_long_press(hass: HomeAssistant, calls) -> None:
"""Test for button press event trigger firing."""
mac = "DC:ED:83:87:12:73"
data = {"bindkey": "b93eb3787eabda352edd94b667f5d5a9"}
entry = await _async_setup_xiaomi_device(hass, mac, data)
# Emit left button press event so it creates the device in the registry
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
mac,
b"XYI\x19Ks\x12\x87\x83\xed\xdc!\xad\xb4\xcd\x02\x00\x00,\xf3\xd9\x83",
),
)
# wait for the device being created
await hass.async_block_till_done()
dev_reg = async_get_dev_reg(hass)
device = dev_reg.async_get_device(identifiers={get_device_id(mac)})
device_id = device.id
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
CONF_PLATFORM: "device",
CONF_DOMAIN: DOMAIN,
CONF_DEVICE_ID: device_id,
CONF_TYPE: "button_right",
CONF_SUBTYPE: "press",
},
"action": {
"service": "test.automation",
"data_template": {"some": "test_trigger_right_button_press"},
},
},
]
},
)
# Emit right button press event
inject_bluetooth_service_info_bleak(
hass,
make_advertisement(
mac,
b"XYI\x19Ps\x12\x87\x83\xed\xdc\x13~~\xbe\x02\x00\x00\xf0\\;4",
),
)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].data["some"] == "test_trigger_right_button_press"
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
async def test_if_fires_on_motion_detected(hass: HomeAssistant, calls) -> None:
"""Test for motion event trigger firing."""
mac = "DE:70:E8:B2:39:0C"