parent
ca76285bcf
commit
328b79a4af
|
@ -7,6 +7,7 @@ from bthome_ble import BTHomeBluetoothDeviceData, SensorUpdate
|
||||||
from bthome_ble.parser import EncryptionScheme
|
from bthome_ble.parser import EncryptionScheme
|
||||||
|
|
||||||
from homeassistant.components.bluetooth import (
|
from homeassistant.components.bluetooth import (
|
||||||
|
DOMAIN as BLUETOOTH_DOMAIN,
|
||||||
BluetoothScanningMode,
|
BluetoothScanningMode,
|
||||||
BluetoothServiceInfoBleak,
|
BluetoothServiceInfoBleak,
|
||||||
)
|
)
|
||||||
|
@ -16,8 +17,16 @@ from homeassistant.components.bluetooth.passive_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 HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceRegistry, async_get
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import (
|
||||||
|
BTHOME_BLE_EVENT,
|
||||||
|
CONF_BINDKEY,
|
||||||
|
CONF_DISCOVERED_EVENT_CLASSES,
|
||||||
|
DOMAIN,
|
||||||
|
BTHomeBleEvent,
|
||||||
|
)
|
||||||
|
from .models import BTHomeData
|
||||||
|
|
||||||
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
||||||
|
|
||||||
|
@ -29,10 +38,53 @@ def process_service_info(
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
data: BTHomeBluetoothDeviceData,
|
data: BTHomeBluetoothDeviceData,
|
||||||
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 that payload was encrypted and the bindkey was not verified then we need to reauth
|
domain_data: BTHomeData = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
event_class = event.device_key.key
|
||||||
|
event_type = event.event_type
|
||||||
|
|
||||||
|
if event_class not in domain_data.discovered_event_classes:
|
||||||
|
domain_data.discovered_event_classes.add(event_class)
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
entry,
|
||||||
|
data=entry.data
|
||||||
|
| {
|
||||||
|
CONF_DISCOVERED_EVENT_CLASSES: list(
|
||||||
|
domain_data.discovered_event_classes
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.bus.async_fire(
|
||||||
|
BTHOME_BLE_EVENT,
|
||||||
|
dict(
|
||||||
|
BTHomeBleEvent(
|
||||||
|
device_id=device.id,
|
||||||
|
address=address,
|
||||||
|
event_class=event_class, # ie 'button'
|
||||||
|
event_type=event_type, # ie 'press'
|
||||||
|
event_properties=event.event_properties,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# If payload is encrypted and the bindkey is not verified then we need to reauth
|
||||||
if data.encryption_scheme != EncryptionScheme.NONE and not data.bindkey_verified:
|
if data.encryption_scheme != EncryptionScheme.NONE and not data.bindkey_verified:
|
||||||
entry.async_start_reauth(hass, data={"device": data})
|
entry.async_start_reauth(hass, data={"device": data})
|
||||||
|
|
||||||
|
@ -45,10 +97,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
assert address is not None
|
assert address is not None
|
||||||
|
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
if bindkey := entry.data.get("bindkey"):
|
if bindkey := entry.data.get(CONF_BINDKEY):
|
||||||
kwargs["bindkey"] = bytes.fromhex(bindkey)
|
kwargs[CONF_BINDKEY] = bytes.fromhex(bindkey)
|
||||||
data = BTHomeBluetoothDeviceData(**kwargs)
|
data = BTHomeBluetoothDeviceData(**kwargs)
|
||||||
|
|
||||||
|
device_registry = async_get(hass)
|
||||||
coordinator = hass.data.setdefault(DOMAIN, {})[
|
coordinator = hass.data.setdefault(DOMAIN, {})[
|
||||||
entry.entry_id
|
entry.entry_id
|
||||||
] = PassiveBluetoothProcessorCoordinator(
|
] = PassiveBluetoothProcessorCoordinator(
|
||||||
|
@ -57,11 +110,14 @@ 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
|
||||||
),
|
),
|
||||||
connectable=False,
|
connectable=False,
|
||||||
)
|
)
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
domain_data = BTHomeData(set(entry.data.get(CONF_DISCOVERED_EVENT_CLASSES, [])))
|
||||||
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = domain_data
|
||||||
|
|
||||||
entry.async_on_unload(
|
entry.async_on_unload(
|
||||||
coordinator.async_start()
|
coordinator.async_start()
|
||||||
) # only start after all platforms have had a chance to subscribe
|
) # only start after all platforms have had a chance to subscribe
|
||||||
|
|
|
@ -1,3 +1,32 @@
|
||||||
"""Constants for the BTHome Bluetooth integration."""
|
"""Constants for the BTHome Bluetooth integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Final, TypedDict
|
||||||
|
|
||||||
DOMAIN = "bthome"
|
DOMAIN = "bthome"
|
||||||
|
|
||||||
|
CONF_BINDKEY: Final = "bindkey"
|
||||||
|
CONF_DISCOVERED_EVENT_CLASSES: Final = "known_events"
|
||||||
|
CONF_SUBTYPE: Final = "subtype"
|
||||||
|
|
||||||
|
EVENT_TYPE: Final = "event_type"
|
||||||
|
EVENT_CLASS: Final = "event_class"
|
||||||
|
EVENT_PROPERTIES: Final = "event_properties"
|
||||||
|
BTHOME_BLE_EVENT: Final = "bthome_ble_event"
|
||||||
|
|
||||||
|
|
||||||
|
EVENT_CLASS_BUTTON: Final = "button"
|
||||||
|
EVENT_CLASS_DIMMER: Final = "dimmer"
|
||||||
|
|
||||||
|
CONF_EVENT_CLASS: Final = "event_class"
|
||||||
|
CONF_EVENT_PROPERTIES: Final = "event_properties"
|
||||||
|
|
||||||
|
|
||||||
|
class BTHomeBleEvent(TypedDict):
|
||||||
|
"""BTHome BLE event data."""
|
||||||
|
|
||||||
|
device_id: str
|
||||||
|
address: str
|
||||||
|
event_class: str # ie 'button'
|
||||||
|
event_type: str # ie 'press'
|
||||||
|
event_properties: dict[str, str | int | float | None] | None
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
"""Provides device triggers for BTHome BLE."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
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 (
|
||||||
|
BTHOME_BLE_EVENT,
|
||||||
|
CONF_DISCOVERED_EVENT_CLASSES,
|
||||||
|
CONF_SUBTYPE,
|
||||||
|
DOMAIN,
|
||||||
|
EVENT_CLASS,
|
||||||
|
EVENT_CLASS_BUTTON,
|
||||||
|
EVENT_CLASS_DIMMER,
|
||||||
|
EVENT_TYPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
TRIGGERS_BY_EVENT_CLASS = {
|
||||||
|
EVENT_CLASS_BUTTON: {
|
||||||
|
"press",
|
||||||
|
"double_press",
|
||||||
|
"triple_press",
|
||||||
|
"long_press",
|
||||||
|
"long_double_press",
|
||||||
|
"long_triple_press",
|
||||||
|
},
|
||||||
|
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]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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)(
|
||||||
|
config
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_triggers(
|
||||||
|
hass: HomeAssistant, device_id: str
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""Return a list of triggers for BTHome BLE devices."""
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
device = device_registry.async_get(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),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
assert bthome_config_entry is not None
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
# Required fields of TRIGGER_BASE_SCHEMA
|
||||||
|
CONF_PLATFORM: "device",
|
||||||
|
CONF_DEVICE_ID: device_id,
|
||||||
|
CONF_DOMAIN: DOMAIN,
|
||||||
|
# Required fields of TRIGGER_SCHEMA
|
||||||
|
CONF_TYPE: event_class,
|
||||||
|
CONF_SUBTYPE: event_type,
|
||||||
|
}
|
||||||
|
for event_class in bthome_config_entry.data.get(
|
||||||
|
CONF_DISCOVERED_EVENT_CLASSES, []
|
||||||
|
)
|
||||||
|
for event_type in TRIGGERS_BY_EVENT_CLASS.get(event_class, [])
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_attach_trigger(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config: ConfigType,
|
||||||
|
action: TriggerActionType,
|
||||||
|
trigger_info: TriggerInfo,
|
||||||
|
) -> CALLBACK_TYPE:
|
||||||
|
"""Attach a trigger."""
|
||||||
|
return await event_trigger.async_attach_trigger(
|
||||||
|
hass,
|
||||||
|
event_trigger.TRIGGER_SCHEMA(
|
||||||
|
{
|
||||||
|
event_trigger.CONF_PLATFORM: CONF_EVENT,
|
||||||
|
event_trigger.CONF_EVENT_TYPE: BTHOME_BLE_EVENT,
|
||||||
|
event_trigger.CONF_EVENT_DATA: {
|
||||||
|
CONF_DEVICE_ID: config[CONF_DEVICE_ID],
|
||||||
|
EVENT_CLASS: config[CONF_TYPE],
|
||||||
|
EVENT_TYPE: config[CONF_SUBTYPE],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
action,
|
||||||
|
trigger_info,
|
||||||
|
platform_type="device",
|
||||||
|
)
|
|
@ -0,0 +1,11 @@
|
||||||
|
"""The bthome integration models."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class BTHomeData:
|
||||||
|
"""Data for the bthome integration."""
|
||||||
|
|
||||||
|
discovered_event_classes: set[str]
|
|
@ -28,5 +28,21 @@
|
||||||
"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_subtype": {
|
||||||
|
"press": "Press",
|
||||||
|
"double_press": "Double Press",
|
||||||
|
"triple_press": "Triple Press",
|
||||||
|
"long_press": "Long Press",
|
||||||
|
"long_double_press": "Long Double Press",
|
||||||
|
"long_triple_press": "Long Triple Press",
|
||||||
|
"rotate_right": "Rotate Right",
|
||||||
|
"rotate_left": "Rotate Left"
|
||||||
|
},
|
||||||
|
"trigger_type": {
|
||||||
|
"button": "Button \"{subtype}\"",
|
||||||
|
"dimmer": "Dimmer \"{subtype}\""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,282 @@
|
||||||
|
"""Test BTHome BLE events."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components import automation
|
||||||
|
from homeassistant.components.bluetooth.const 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.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers.device_registry import (
|
||||||
|
CONNECTION_NETWORK_MAC,
|
||||||
|
async_get as async_get_dev_reg,
|
||||||
|
)
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from . import make_bthome_v2_adv
|
||||||
|
|
||||||
|
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 bthome_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_bthome_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_long_press(hass: HomeAssistant) -> None:
|
||||||
|
"""Make sure that a long press event is fired."""
|
||||||
|
mac = "A4:C1:38:8D:18:B2"
|
||||||
|
entry = await _async_setup_bthome_device(hass, mac)
|
||||||
|
events = async_capture_events(hass, "bthome_ble_event")
|
||||||
|
|
||||||
|
# Emit long press event
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_bthome_v2_adv(mac, b"\x40\x3A\x04"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for the event
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(events) == 1
|
||||||
|
assert events[0].data["address"] == "A4:C1:38:8D:18:B2"
|
||||||
|
assert events[0].data["event_type"] == "long_press"
|
||||||
|
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_event_rotate_dimmer(hass: HomeAssistant) -> None:
|
||||||
|
"""Make sure that a rotate dimmer event is fired."""
|
||||||
|
mac = "A4:C1:38:8D:18:B2"
|
||||||
|
entry = await _async_setup_bthome_device(hass, mac)
|
||||||
|
events = async_capture_events(hass, "bthome_ble_event")
|
||||||
|
|
||||||
|
# Emit rotate dimmer 3 steps left event
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_bthome_v2_adv(mac, b"\x40\x3C\x01\x03"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait for the event
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(events) == 1
|
||||||
|
assert events[0].data["address"] == "A4:C1:38:8D:18:B2"
|
||||||
|
assert events[0].data["event_type"] == "rotate_left"
|
||||||
|
assert events[0].data["event_properties"] == {"steps": 3}
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_triggers_button(hass: HomeAssistant) -> None:
|
||||||
|
"""Test that we get the expected triggers from a BTHome BLE sensor."""
|
||||||
|
mac = "A4:C1:38:8D:18:B2"
|
||||||
|
entry = await _async_setup_bthome_device(hass, mac)
|
||||||
|
events = async_capture_events(hass, "bthome_ble_event")
|
||||||
|
|
||||||
|
# Emit long press event so it creates the device in the registry
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_bthome_v2_adv(mac, b"\x40\x3A\x04"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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: "button",
|
||||||
|
CONF_SUBTYPE: "long_press",
|
||||||
|
"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_dimmer(hass: HomeAssistant) -> None:
|
||||||
|
"""Test that we get the expected triggers from a BTHome BLE sensor."""
|
||||||
|
mac = "A4:C1:38:8D:18:B2"
|
||||||
|
entry = await _async_setup_bthome_device(hass, mac)
|
||||||
|
events = async_capture_events(hass, "bthome_ble_event")
|
||||||
|
|
||||||
|
# Emit rotate left with 3 steps event so it creates the device in the registry
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_bthome_v2_adv(mac, b"\x40\x3C\x01\x03"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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: "dimmer",
|
||||||
|
CONF_SUBTYPE: "rotate_left",
|
||||||
|
"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_bthome_ble_device(hass: HomeAssistant) -> None:
|
||||||
|
"""Test that we don't get triggers for an invalid device."""
|
||||||
|
mac = "A4:C1:38:8D:18:B2"
|
||||||
|
entry = await _async_setup_bthome_device(hass, mac)
|
||||||
|
events = async_capture_events(hass, "bthome_ble_event")
|
||||||
|
|
||||||
|
# Creates the device in the registry but no events
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_bthome_v2_adv(mac, b"\x40\x02\xca\x09\x03\xbf\x13"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# wait to make sure there are no events
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(events) == 0
|
||||||
|
|
||||||
|
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: HomeAssistant) -> None:
|
||||||
|
"""Test that we don't get triggers when using an invalid device_id."""
|
||||||
|
mac = "DE:70:E8:B2:39:0C"
|
||||||
|
entry = await _async_setup_bthome_device(hass, mac)
|
||||||
|
|
||||||
|
# Emit motion detected event so it creates the device in the registry
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_bthome_v2_adv(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={(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: HomeAssistant, calls) -> None:
|
||||||
|
"""Test for motion event trigger firing."""
|
||||||
|
mac = "DE:70:E8:B2:39:0C"
|
||||||
|
entry = await _async_setup_bthome_device(hass, mac)
|
||||||
|
|
||||||
|
# Emit a button event so it creates the device in the registry
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_bthome_v2_adv(mac, b"\x40\x3A\x03"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# # 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: "button",
|
||||||
|
CONF_SUBTYPE: "long_press",
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"service": "test.automation",
|
||||||
|
"data_template": {"some": "test_trigger_button_long_press"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# Emit long press event
|
||||||
|
inject_bluetooth_service_info_bleak(
|
||||||
|
hass,
|
||||||
|
make_bthome_v2_adv(mac, b"\x40\x3A\x04"),
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(calls) == 1
|
||||||
|
assert calls[0].data["some"] == "test_trigger_button_long_press"
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
Loading…
Reference in New Issue