mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Add events for xiaomi-ble (#85139)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
90fc8dd860
commit
886d2fc3a1
@ -8,6 +8,7 @@ from xiaomi_ble.parser import EncryptionScheme
|
|||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.bluetooth import (
|
from homeassistant.components.bluetooth import (
|
||||||
|
DOMAIN as BLUETOOTH_DOMAIN,
|
||||||
BluetoothScanningMode,
|
BluetoothScanningMode,
|
||||||
BluetoothServiceInfoBleak,
|
BluetoothServiceInfoBleak,
|
||||||
async_ble_device_from_address,
|
async_ble_device_from_address,
|
||||||
@ -18,8 +19,9 @@ from homeassistant.components.bluetooth.active_update_processor import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import CoreState, HomeAssistant
|
from homeassistant.core import CoreState, HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceRegistry, async_get
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN, XIAOMI_BLE_EVENT, XiaomiBleEvent
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||||
|
|
||||||
@ -31,9 +33,35 @@ def process_service_info(
|
|||||||
entry: config_entries.ConfigEntry,
|
entry: config_entries.ConfigEntry,
|
||||||
data: XiaomiBluetoothDeviceData,
|
data: XiaomiBluetoothDeviceData,
|
||||||
service_info: BluetoothServiceInfoBleak,
|
service_info: BluetoothServiceInfoBleak,
|
||||||
|
device_registry: DeviceRegistry,
|
||||||
) -> SensorUpdate:
|
) -> SensorUpdate:
|
||||||
"""Process a BluetoothServiceInfoBleak, running side effects and returning sensor data."""
|
"""Process a BluetoothServiceInfoBleak, running side effects and returning sensor data."""
|
||||||
update = data.update(service_info)
|
update = data.update(service_info)
|
||||||
|
if update.events:
|
||||||
|
address = service_info.device.address
|
||||||
|
for device_key, event in update.events.items():
|
||||||
|
sensor_device_info = update.devices[device_key.device_id]
|
||||||
|
device = device_registry.async_get_or_create(
|
||||||
|
config_entry_id=entry.entry_id,
|
||||||
|
identifiers={(BLUETOOTH_DOMAIN, address)},
|
||||||
|
manufacturer=sensor_device_info.manufacturer,
|
||||||
|
model=sensor_device_info.model,
|
||||||
|
name=sensor_device_info.name,
|
||||||
|
sw_version=sensor_device_info.sw_version,
|
||||||
|
hw_version=sensor_device_info.hw_version,
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.bus.async_fire(
|
||||||
|
XIAOMI_BLE_EVENT,
|
||||||
|
dict(
|
||||||
|
XiaomiBleEvent(
|
||||||
|
device_id=device.id,
|
||||||
|
address=address,
|
||||||
|
event_type=event.event_type,
|
||||||
|
event_properties=event.event_properties,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# If device isn't pending we know it has seen at least one broadcast with a payload
|
# If device isn't pending we know it has seen at least one broadcast with a payload
|
||||||
# If that payload was encrypted and the bindkey was not verified then we need to reauth
|
# If that payload was encrypted and the bindkey was not verified then we need to reauth
|
||||||
@ -91,6 +119,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
)
|
)
|
||||||
return await data.async_poll(connectable_device)
|
return await data.async_poll(connectable_device)
|
||||||
|
|
||||||
|
device_registry = async_get(hass)
|
||||||
coordinator = hass.data.setdefault(DOMAIN, {})[
|
coordinator = hass.data.setdefault(DOMAIN, {})[
|
||||||
entry.entry_id
|
entry.entry_id
|
||||||
] = ActiveBluetoothProcessorCoordinator(
|
] = ActiveBluetoothProcessorCoordinator(
|
||||||
@ -99,7 +128,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
address=address,
|
address=address,
|
||||||
mode=BluetoothScanningMode.PASSIVE,
|
mode=BluetoothScanningMode.PASSIVE,
|
||||||
update_method=lambda service_info: process_service_info(
|
update_method=lambda service_info: process_service_info(
|
||||||
hass, entry, data, service_info
|
hass, entry, data, service_info, device_registry
|
||||||
),
|
),
|
||||||
needs_poll_method=_needs_poll,
|
needs_poll_method=_needs_poll,
|
||||||
poll_method=_async_poll,
|
poll_method=_async_poll,
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
"""Constants for the Xiaomi Bluetooth integration."""
|
"""Constants for the Xiaomi Bluetooth integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Final, TypedDict
|
||||||
|
|
||||||
DOMAIN = "xiaomi_ble"
|
DOMAIN = "xiaomi_ble"
|
||||||
|
|
||||||
|
|
||||||
|
CONF_EVENT_PROPERTIES: Final = "event_properties"
|
||||||
|
EVENT_PROPERTIES: Final = "event_properties"
|
||||||
|
EVENT_TYPE: Final = "event_type"
|
||||||
|
XIAOMI_BLE_EVENT: Final = "xiaomi_ble_event"
|
||||||
|
|
||||||
|
|
||||||
|
class XiaomiBleEvent(TypedDict):
|
||||||
|
"""Xiaomi BLE event data."""
|
||||||
|
|
||||||
|
device_id: str
|
||||||
|
address: str
|
||||||
|
event_type: str
|
||||||
|
event_properties: dict[str, str | int | float | None] | None
|
||||||
|
127
homeassistant/components/xiaomi_ble/device_trigger.py
Normal file
127
homeassistant/components/xiaomi_ble/device_trigger.py
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
"""Provides device triggers for Xiaomi BLE."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.components.device_automation import DEVICE_TRIGGER_BASE_SCHEMA
|
||||||
|
from homeassistant.components.homeassistant.triggers import event as event_trigger
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_DEVICE_ID,
|
||||||
|
CONF_DOMAIN,
|
||||||
|
CONF_EVENT,
|
||||||
|
CONF_PLATFORM,
|
||||||
|
CONF_TYPE,
|
||||||
|
)
|
||||||
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
|
||||||
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
|
from .const import (
|
||||||
|
CONF_EVENT_PROPERTIES,
|
||||||
|
DOMAIN,
|
||||||
|
EVENT_PROPERTIES,
|
||||||
|
EVENT_TYPE,
|
||||||
|
XIAOMI_BLE_EVENT,
|
||||||
|
)
|
||||||
|
|
||||||
|
MOTION_DEVICE_TRIGGERS = [
|
||||||
|
{CONF_TYPE: "motion_detected", CONF_EVENT_PROPERTIES: None},
|
||||||
|
]
|
||||||
|
|
||||||
|
MOTION_DEVICE_SCHEMA = DEVICE_TRIGGER_BASE_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_TYPE): vol.In(
|
||||||
|
[trigger[CONF_TYPE] for trigger in MOTION_DEVICE_TRIGGERS]
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_EVENT_PROPERTIES): vol.In(
|
||||||
|
[trigger[CONF_EVENT_PROPERTIES] for trigger in MOTION_DEVICE_TRIGGERS]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TriggerModelData:
|
||||||
|
"""Data class for trigger model data."""
|
||||||
|
|
||||||
|
triggers: list[dict[str, Any]]
|
||||||
|
schema: vol.Schema
|
||||||
|
|
||||||
|
|
||||||
|
MODEL_DATA = {
|
||||||
|
"MUE4094RT": TriggerModelData(
|
||||||
|
triggers=MOTION_DEVICE_TRIGGERS, schema=MOTION_DEVICE_SCHEMA
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_validate_trigger_config(
|
||||||
|
hass: HomeAssistant, config: ConfigType
|
||||||
|
) -> ConfigType:
|
||||||
|
"""Validate trigger config."""
|
||||||
|
device_id = config[CONF_DEVICE_ID]
|
||||||
|
if model_data := _async_trigger_model_data(hass, device_id):
|
||||||
|
return model_data.schema(config)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_triggers(
|
||||||
|
hass: HomeAssistant, device_id: str
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""List a list of triggers for Xiaomi BLE devices."""
|
||||||
|
|
||||||
|
# Check if device is a model supporting device triggers.
|
||||||
|
if not (model_data := _async_trigger_model_data(hass, device_id)):
|
||||||
|
return []
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
CONF_PLATFORM: "device",
|
||||||
|
CONF_DOMAIN: DOMAIN,
|
||||||
|
CONF_DEVICE_ID: device_id,
|
||||||
|
**trigger,
|
||||||
|
}
|
||||||
|
for trigger in model_data.triggers
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_attach_trigger(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config: ConfigType,
|
||||||
|
action: TriggerActionType,
|
||||||
|
trigger_info: TriggerInfo,
|
||||||
|
) -> CALLBACK_TYPE:
|
||||||
|
"""Attach a trigger."""
|
||||||
|
|
||||||
|
event_data = {
|
||||||
|
CONF_DEVICE_ID: config[CONF_DEVICE_ID],
|
||||||
|
EVENT_TYPE: config[CONF_TYPE],
|
||||||
|
EVENT_PROPERTIES: config[CONF_EVENT_PROPERTIES],
|
||||||
|
}
|
||||||
|
return await event_trigger.async_attach_trigger(
|
||||||
|
hass,
|
||||||
|
event_trigger.TRIGGER_SCHEMA(
|
||||||
|
{
|
||||||
|
event_trigger.CONF_PLATFORM: CONF_EVENT,
|
||||||
|
event_trigger.CONF_EVENT_TYPE: XIAOMI_BLE_EVENT,
|
||||||
|
event_trigger.CONF_EVENT_DATA: event_data,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
action,
|
||||||
|
trigger_info,
|
||||||
|
platform_type="device",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _async_trigger_model_data(
|
||||||
|
hass: HomeAssistant, device_id: str
|
||||||
|
) -> TriggerModelData | None:
|
||||||
|
"""Get available triggers for a given model."""
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
device = device_registry.async_get(device_id)
|
||||||
|
if device and device.model and (model_data := MODEL_DATA.get(device.model)):
|
||||||
|
return model_data
|
||||||
|
return None
|
@ -13,8 +13,8 @@
|
|||||||
"service_data_uuid": "0000fe95-0000-1000-8000-00805f9b34fb"
|
"service_data_uuid": "0000fe95-0000-1000-8000-00805f9b34fb"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"requirements": ["xiaomi-ble==0.14.3"],
|
|
||||||
"dependencies": ["bluetooth_adapters"],
|
"dependencies": ["bluetooth_adapters"],
|
||||||
|
"requirements": ["xiaomi-ble==0.15.0"],
|
||||||
"codeowners": ["@Jc2k", "@Ernst79"],
|
"codeowners": ["@Jc2k", "@Ernst79"],
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
}
|
}
|
||||||
|
@ -38,5 +38,10 @@
|
|||||||
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"device_automation": {
|
||||||
|
"trigger_type": {
|
||||||
|
"motion_detected": "Motion detected"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,5 +38,10 @@
|
|||||||
"description": "Choose a device to set up"
|
"description": "Choose a device to set up"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"device_automation": {
|
||||||
|
"trigger_type": {
|
||||||
|
"motion_detected": "Motion detected"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2631,7 +2631,7 @@ xbox-webapi==2.0.11
|
|||||||
xboxapi==2.0.1
|
xboxapi==2.0.1
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_ble
|
# homeassistant.components.xiaomi_ble
|
||||||
xiaomi-ble==0.14.3
|
xiaomi-ble==0.15.0
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
xknx==2.3.0
|
xknx==2.3.0
|
||||||
|
@ -1856,7 +1856,7 @@ wolf_smartset==0.1.11
|
|||||||
xbox-webapi==2.0.11
|
xbox-webapi==2.0.11
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_ble
|
# homeassistant.components.xiaomi_ble
|
||||||
xiaomi-ble==0.14.3
|
xiaomi-ble==0.15.0
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
xknx==2.3.0
|
xknx==2.3.0
|
||||||
|
383
tests/components/xiaomi_ble/test_device_trigger.py
Normal file
383
tests/components/xiaomi_ble/test_device_trigger.py
Normal file
@ -0,0 +1,383 @@
|
|||||||
|
"""Test Xiaomi BLE events."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components import automation
|
||||||
|
from homeassistant.components.bluetooth.const import DOMAIN as BLUETOOTH_DOMAIN
|
||||||
|
from homeassistant.components.device_automation import DeviceAutomationType
|
||||||
|
from homeassistant.components.xiaomi_ble.const import (
|
||||||
|
CONF_EVENT_PROPERTIES,
|
||||||
|
DOMAIN,
|
||||||
|
EVENT_PROPERTIES,
|
||||||
|
EVENT_TYPE,
|
||||||
|
XIAOMI_BLE_EVENT,
|
||||||
|
)
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_ADDRESS,
|
||||||
|
CONF_DEVICE_ID,
|
||||||
|
CONF_DOMAIN,
|
||||||
|
CONF_PLATFORM,
|
||||||
|
CONF_TYPE,
|
||||||
|
)
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers import device_registry
|
||||||
|
from homeassistant.helpers.device_registry import async_get as async_get_dev_reg
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from . import make_advertisement
|
||||||
|
|
||||||
|
from tests.common import (
|
||||||
|
MockConfigEntry,
|
||||||
|
async_capture_events,
|
||||||
|
async_get_device_automations,
|
||||||
|
async_mock_service,
|
||||||
|
)
|
||||||
|
from tests.components.bluetooth import inject_bluetooth_service_info_bleak
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def get_device_id(mac: str) -> tuple[str, str]:
|
||||||
|
"""Get device registry identifier for xiaomi_ble."""
|
||||||
|
return (BLUETOOTH_DOMAIN, mac)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def calls(hass):
|
||||||
|
"""Track calls to a mock service."""
|
||||||
|
return async_mock_service(hass, "test", "automation")
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_setup_xiaomi_device(hass, mac: str):
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id=mac,
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
return config_entry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_event_motion_detected(hass):
|
||||||
|
"""Make sure that a motion detected event is fired."""
|
||||||
|
mac = "DE:70:E8:B2:39:0C"
|
||||||
|
entry = await _async_setup_xiaomi_device(hass, mac)
|
||||||
|
events = async_capture_events(hass, "xiaomi_ble_event")
|
||||||
|
|
||||||
|
# Emit motion detected event
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_advertisement(mac, b"@0\xdd\x03$\x03\x00\x01\x01"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for the event
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(events) == 1
|
||||||
|
assert events[0].data["address"] == "DE:70:E8:B2:39:0C"
|
||||||
|
assert events[0].data["event_type"] == "motion_detected"
|
||||||
|
assert events[0].data["event_properties"] is None
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_triggers(hass):
|
||||||
|
"""Test that we get the expected triggers from a Xiaomi BLE motion sensor."""
|
||||||
|
mac = "DE:70:E8:B2:39:0C"
|
||||||
|
entry = await _async_setup_xiaomi_device(hass, mac)
|
||||||
|
events = async_capture_events(hass, "xiaomi_ble_event")
|
||||||
|
|
||||||
|
# Emit motion detected event so it creates the device in the registry
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_advertisement(mac, b"@0\xdd\x03$\x03\x00\x01\x01"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for the event
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(events) == 1
|
||||||
|
|
||||||
|
dev_reg = async_get_dev_reg(hass)
|
||||||
|
device = dev_reg.async_get_device({get_device_id(mac)})
|
||||||
|
assert device
|
||||||
|
expected_trigger = {
|
||||||
|
CONF_PLATFORM: "device",
|
||||||
|
CONF_DOMAIN: DOMAIN,
|
||||||
|
CONF_DEVICE_ID: device.id,
|
||||||
|
CONF_TYPE: "motion_detected",
|
||||||
|
CONF_EVENT_PROPERTIES: None,
|
||||||
|
"metadata": {},
|
||||||
|
}
|
||||||
|
triggers = await async_get_device_automations(
|
||||||
|
hass, DeviceAutomationType.TRIGGER, device.id
|
||||||
|
)
|
||||||
|
assert expected_trigger in triggers
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_triggers_for_invalid_xiami_ble_device(hass):
|
||||||
|
"""Test that we don't get triggers for an invalid device."""
|
||||||
|
mac = "DE:70:E8:B2:39:0C"
|
||||||
|
entry = await _async_setup_xiaomi_device(hass, mac)
|
||||||
|
events = async_capture_events(hass, "xiaomi_ble_event")
|
||||||
|
|
||||||
|
# Emit motion detected event so it creates the device in the registry
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_advertisement(mac, b"@0\xdd\x03$\x03\x00\x01\x01"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for the event
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(events) == 1
|
||||||
|
|
||||||
|
dev_reg = async_get_dev_reg(hass)
|
||||||
|
invalid_device = dev_reg.async_get_or_create(
|
||||||
|
config_entry_id=entry.entry_id,
|
||||||
|
identifiers={(DOMAIN, "invdevmac")},
|
||||||
|
)
|
||||||
|
|
||||||
|
triggers = await async_get_device_automations(
|
||||||
|
hass, DeviceAutomationType.TRIGGER, invalid_device.id
|
||||||
|
)
|
||||||
|
assert triggers == []
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_triggers_for_invalid_device_id(hass):
|
||||||
|
"""Test that we don't get triggers when using an invalid device_id."""
|
||||||
|
mac = "DE:70:E8:B2:39:0C"
|
||||||
|
entry = await _async_setup_xiaomi_device(hass, mac)
|
||||||
|
|
||||||
|
# Emit motion detected event so it creates the device in the registry
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_advertisement(mac, b"@0\xdd\x03$\x03\x00\x01\x01"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for the event
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
dev_reg = async_get_dev_reg(hass)
|
||||||
|
|
||||||
|
invalid_device = dev_reg.async_get_or_create(
|
||||||
|
config_entry_id=entry.entry_id,
|
||||||
|
connections={(device_registry.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||||
|
)
|
||||||
|
assert invalid_device
|
||||||
|
triggers = await async_get_device_automations(
|
||||||
|
hass, DeviceAutomationType.TRIGGER, invalid_device.id
|
||||||
|
)
|
||||||
|
assert triggers == []
|
||||||
|
|
||||||
|
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, calls):
|
||||||
|
"""Test for motion event trigger firing."""
|
||||||
|
mac = "DE:70:E8:B2:39:0C"
|
||||||
|
entry = await _async_setup_xiaomi_device(hass, mac)
|
||||||
|
|
||||||
|
# Emit motion detected event so it creates the device in the registry
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_advertisement(mac, b"@0\xdd\x03$\x03\x00\x01\x01"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for the event
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
dev_reg = async_get_dev_reg(hass)
|
||||||
|
device = dev_reg.async_get_device({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: "motion_detected",
|
||||||
|
CONF_EVENT_PROPERTIES: None,
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"service": "test.automation",
|
||||||
|
"data_template": {"some": "test_trigger_motion_detected"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
message = {
|
||||||
|
CONF_DEVICE_ID: device_id,
|
||||||
|
CONF_ADDRESS: "DE:70:E8:B2:39:0C",
|
||||||
|
EVENT_TYPE: "motion_detected",
|
||||||
|
EVENT_PROPERTIES: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
hass.bus.async_fire(XIAOMI_BLE_EVENT, message)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(calls) == 1
|
||||||
|
assert calls[0].data["some"] == "test_trigger_motion_detected"
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_automation_with_invalid_trigger_type(hass, caplog):
|
||||||
|
"""Test for automation with invalid trigger type."""
|
||||||
|
mac = "DE:70:E8:B2:39:0C"
|
||||||
|
entry = await _async_setup_xiaomi_device(hass, mac)
|
||||||
|
|
||||||
|
# Emit motion detected event so it creates the device in the registry
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_advertisement(mac, b"@0\xdd\x03$\x03\x00\x01\x01"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for the event
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
dev_reg = async_get_dev_reg(hass)
|
||||||
|
device = dev_reg.async_get_device({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: "invalid",
|
||||||
|
CONF_EVENT_PROPERTIES: None,
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"service": "test.automation",
|
||||||
|
"data_template": {"some": "test_trigger_motion_detected"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# Logs should return message to make sure event type is of one ["motion_detected"]
|
||||||
|
assert "motion_detected" in caplog.text
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_automation_with_invalid_trigger_event_property(hass, caplog):
|
||||||
|
"""Test for automation with invalid trigger event property."""
|
||||||
|
mac = "DE:70:E8:B2:39:0C"
|
||||||
|
entry = await _async_setup_xiaomi_device(hass, mac)
|
||||||
|
|
||||||
|
# Emit motion detected event so it creates the device in the registry
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_advertisement(mac, b"@0\xdd\x03$\x03\x00\x01\x01"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for the event
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
dev_reg = async_get_dev_reg(hass)
|
||||||
|
device = dev_reg.async_get_device({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: "motion_detected",
|
||||||
|
CONF_EVENT_PROPERTIES: "invalid_property",
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"service": "test.automation",
|
||||||
|
"data_template": {"some": "test_trigger_motion_detected"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
# Logs should return message to make sure event property is of one [None] for motion event
|
||||||
|
assert str([None]) in caplog.text
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_triggers_for_invalid__model(hass, calls):
|
||||||
|
"""Test invalid model doesn't return triggers."""
|
||||||
|
mac = "DE:70:E8:B2:39:0C"
|
||||||
|
entry = await _async_setup_xiaomi_device(hass, mac)
|
||||||
|
|
||||||
|
# Emit motion detected event so it creates the device in the registry
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_advertisement(mac, b"@0\xdd\x03$\x03\x00\x01\x01"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for the event
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
dev_reg = async_get_dev_reg(hass)
|
||||||
|
# modify model to invalid model
|
||||||
|
invalid_model = dev_reg.async_get_or_create(
|
||||||
|
config_entry_id=entry.entry_id,
|
||||||
|
identifiers={(DOMAIN, mac)},
|
||||||
|
model="invalid model",
|
||||||
|
)
|
||||||
|
invalid_model_id = invalid_model.id
|
||||||
|
|
||||||
|
# setup automation to validate trigger config
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
automation.DOMAIN,
|
||||||
|
{
|
||||||
|
automation.DOMAIN: [
|
||||||
|
{
|
||||||
|
"trigger": {
|
||||||
|
CONF_PLATFORM: "device",
|
||||||
|
CONF_DOMAIN: DOMAIN,
|
||||||
|
CONF_DEVICE_ID: invalid_model_id,
|
||||||
|
CONF_TYPE: "motion_detected",
|
||||||
|
CONF_EVENT_PROPERTIES: None,
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"service": "test.automation",
|
||||||
|
"data_template": {"some": "test_trigger_motion_detected"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
triggers = await async_get_device_automations(
|
||||||
|
hass, DeviceAutomationType.TRIGGER, invalid_model_id
|
||||||
|
)
|
||||||
|
assert triggers == []
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
@ -1,5 +1,4 @@
|
|||||||
"""Test the Xiaomi config flow."""
|
"""Test Xiaomi BLE sensors."""
|
||||||
|
|
||||||
|
|
||||||
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
from homeassistant.components.sensor import ATTR_STATE_CLASS
|
||||||
from homeassistant.components.xiaomi_ble.const import DOMAIN
|
from homeassistant.components.xiaomi_ble.const import DOMAIN
|
||||||
|
Loading…
x
Reference in New Issue
Block a user