Fix BTHome validate triggers for device with multiple buttons (#125183)

* Fix BTHome validate triggers for device with multiple buttons

* Remove None default
pull/125246/head
Shay Levy 2024-09-04 18:34:11 +03:00 committed by GitHub
parent 638434c103
commit eaee8d5b78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 158 additions and 22 deletions

View File

@ -7,6 +7,9 @@ from typing import Any
import voluptuous as vol
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
from homeassistant.components.device_automation.exceptions import (
InvalidDeviceAutomationConfig,
)
from homeassistant.components.homeassistant.triggers import event as event_trigger
from homeassistant.const import (
CONF_DEVICE_ID,
@ -43,33 +46,46 @@ TRIGGERS_BY_EVENT_CLASS = {
EVENT_CLASS_DIMMER: {"rotate_left", "rotate_right"},
}
SCHEMA_BY_EVENT_CLASS = {
EVENT_CLASS_BUTTON: DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): vol.In([EVENT_CLASS_BUTTON]),
vol.Required(CONF_SUBTYPE): vol.In(
TRIGGERS_BY_EVENT_CLASS[EVENT_CLASS_BUTTON]
),
}
),
EVENT_CLASS_DIMMER: DEVICE_TRIGGER_BASE_SCHEMA.extend(
{
vol.Required(CONF_TYPE): vol.In([EVENT_CLASS_DIMMER]),
vol.Required(CONF_SUBTYPE): vol.In(
TRIGGERS_BY_EVENT_CLASS[EVENT_CLASS_DIMMER]
),
}
),
}
TRIGGER_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
{vol.Required(CONF_TYPE): str, vol.Required(CONF_SUBTYPE): str}
)
async def async_validate_trigger_config(
hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate trigger config."""
return SCHEMA_BY_EVENT_CLASS.get(config[CONF_TYPE], DEVICE_TRIGGER_BASE_SCHEMA)( # type: ignore[no-any-return]
config
config = TRIGGER_SCHEMA(config)
event_class = config[CONF_TYPE]
event_type = config[CONF_SUBTYPE]
device_registry = dr.async_get(hass)
device = device_registry.async_get(config[CONF_DEVICE_ID])
assert device is not None
config_entries = [
hass.config_entries.async_get_entry(entry_id)
for entry_id in device.config_entries
]
bthome_config_entry = next(
iter(entry for entry in config_entries if entry and entry.domain == DOMAIN)
)
event_classes: list[str] = bthome_config_entry.data.get(
CONF_DISCOVERED_EVENT_CLASSES, []
)
if event_class not in event_classes:
raise InvalidDeviceAutomationConfig(
f"BTHome trigger {event_class} is not valid for device "
f"{device} ({config[CONF_DEVICE_ID]})"
)
if event_type not in TRIGGERS_BY_EVENT_CLASS.get(event_class.split("_")[0], ()):
raise InvalidDeviceAutomationConfig(
f"BTHome trigger {event_type} is not valid for device "
f"{device} ({config[CONF_DEVICE_ID]})"
)
return config
async def async_get_triggers(

View File

@ -1,10 +1,19 @@
"""Test BTHome BLE events."""
import pytest
from homeassistant.components import automation
from homeassistant.components.bluetooth import DOMAIN as BLUETOOTH_DOMAIN
from homeassistant.components.bthome.const import CONF_SUBTYPE, DOMAIN
from homeassistant.components.device_automation import DeviceAutomationType
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_PLATFORM,
CONF_TYPE,
STATE_ON,
STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.setup import async_setup_component
@ -121,6 +130,117 @@ async def test_get_triggers_button(
await hass.async_block_till_done()
async def test_get_triggers_multiple_buttons(
hass: HomeAssistant, device_registry: dr.DeviceRegistry
) -> None:
"""Test that we get the expected triggers for multiple buttons device."""
mac = "A4:C1:38:8D:18:B2"
entry = await _async_setup_bthome_device(hass, mac)
events = async_capture_events(hass, "bthome_ble_event")
# Emit button_1 long press and button_2 press events
# so it creates the device in the registry
inject_bluetooth_service_info_bleak(
hass,
make_bthome_v2_adv(mac, b"\x40\x3a\x04\x3a\x01"),
)
# wait for the event
await hass.async_block_till_done()
assert len(events) == 2
device = device_registry.async_get_device(identifiers={get_device_id(mac)})
assert device
expected_trigger1 = {
CONF_PLATFORM: "device",
CONF_DOMAIN: DOMAIN,
CONF_DEVICE_ID: device.id,
CONF_TYPE: "button_1",
CONF_SUBTYPE: "long_press",
"metadata": {},
}
expected_trigger2 = {
CONF_PLATFORM: "device",
CONF_DOMAIN: DOMAIN,
CONF_DEVICE_ID: device.id,
CONF_TYPE: "button_2",
CONF_SUBTYPE: "press",
"metadata": {},
}
triggers = await async_get_device_automations(
hass, DeviceAutomationType.TRIGGER, device.id
)
assert expected_trigger1 in triggers
assert expected_trigger2 in triggers
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
@pytest.mark.parametrize(
("event_class", "event_type", "expected"),
[
("button_1", "long_press", STATE_ON),
("button_2", "press", STATE_ON),
("button_3", "long_press", STATE_UNAVAILABLE),
("button", "long_press", STATE_UNAVAILABLE),
("button_1", "invalid_press", STATE_UNAVAILABLE),
],
)
async def test_validate_trigger_config(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
event_class: str,
event_type: str,
expected: str,
) -> None:
"""Test unsupported trigger does not return a trigger config."""
mac = "A4:C1:38:8D:18:B2"
entry = await _async_setup_bthome_device(hass, mac)
# Emit button_1 long press and button_2 press events
# so it creates the device in the registry
inject_bluetooth_service_info_bleak(
hass,
make_bthome_v2_adv(mac, b"\x40\x3a\x04\x3a\x01"),
)
# wait for the event
await hass.async_block_till_done()
device = device_registry.async_get_device(identifiers={get_device_id(mac)})
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: [
{
"trigger": {
CONF_PLATFORM: "device",
CONF_DOMAIN: DOMAIN,
CONF_DEVICE_ID: device.id,
CONF_TYPE: event_class,
CONF_SUBTYPE: event_type,
},
"action": {
"service": "test.automation",
"data_template": {"some": "test_trigger_button_long_press"},
},
},
]
},
)
await hass.async_block_till_done()
automations = hass.states.async_entity_ids(automation.DOMAIN)
assert len(automations) == 1
assert hass.states.get(automations[0]).state == expected
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
async def test_get_triggers_dimmer(
hass: HomeAssistant, device_registry: dr.DeviceRegistry
) -> None:
@ -235,7 +355,7 @@ async def test_if_fires_on_motion_detected(
make_bthome_v2_adv(mac, b"\x40\x3a\x03"),
)
# # wait for the event
# wait for the event
await hass.async_block_till_done()
device = device_registry.async_get_device(identifiers={get_device_id(mac)})