2019-09-27 16:57:47 +00:00
|
|
|
"""ZHA device automation trigger tests."""
|
2020-08-10 00:37:07 +00:00
|
|
|
from datetime import timedelta
|
|
|
|
import time
|
2022-06-17 16:41:10 +00:00
|
|
|
from unittest.mock import patch
|
2020-08-10 00:37:07 +00:00
|
|
|
|
2019-09-24 15:54:41 +00:00
|
|
|
import pytest
|
2020-09-29 19:25:05 +00:00
|
|
|
import zigpy.profiles.zha
|
2019-10-21 23:30:56 +00:00
|
|
|
import zigpy.zcl.clusters.general as general
|
2019-09-24 15:54:41 +00:00
|
|
|
|
|
|
|
import homeassistant.components.automation as automation
|
2021-12-21 10:56:00 +00:00
|
|
|
from homeassistant.components.device_automation import DeviceAutomationType
|
2022-06-17 16:41:10 +00:00
|
|
|
from homeassistant.const import Platform
|
2021-03-09 13:25:03 +00:00
|
|
|
from homeassistant.helpers import device_registry as dr
|
2019-09-24 15:54:41 +00:00
|
|
|
from homeassistant.setup import async_setup_component
|
2020-08-10 00:37:07 +00:00
|
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
|
|
|
|
from .common import async_enable_traffic
|
2021-09-06 23:00:06 +00:00
|
|
|
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE
|
2019-09-24 15:54:41 +00:00
|
|
|
|
2020-08-10 00:37:07 +00:00
|
|
|
from tests.common import (
|
|
|
|
async_fire_time_changed,
|
|
|
|
async_get_device_automations,
|
|
|
|
async_mock_service,
|
|
|
|
)
|
2021-03-02 08:02:04 +00:00
|
|
|
from tests.components.blueprint.conftest import stub_blueprint_populate # noqa: F401
|
2019-09-24 15:54:41 +00:00
|
|
|
|
|
|
|
ON = 1
|
|
|
|
OFF = 0
|
|
|
|
SHAKEN = "device_shaken"
|
|
|
|
COMMAND = "command"
|
|
|
|
COMMAND_SHAKE = "shake"
|
|
|
|
COMMAND_HOLD = "hold"
|
|
|
|
COMMAND_SINGLE = "single"
|
|
|
|
COMMAND_DOUBLE = "double"
|
|
|
|
DOUBLE_PRESS = "remote_button_double_press"
|
|
|
|
SHORT_PRESS = "remote_button_short_press"
|
|
|
|
LONG_PRESS = "remote_button_long_press"
|
|
|
|
LONG_RELEASE = "remote_button_long_release"
|
|
|
|
|
|
|
|
|
2022-06-17 16:41:10 +00:00
|
|
|
@pytest.fixture(autouse=True)
|
|
|
|
def sensor_platforms_only():
|
|
|
|
"""Only setup the sensor platform and required base platforms to speed up tests."""
|
|
|
|
with patch("homeassistant.components.zha.PLATFORMS", (Platform.SENSOR,)):
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
2019-09-24 15:54:41 +00:00
|
|
|
def _same_lists(list_a, list_b):
|
|
|
|
if len(list_a) != len(list_b):
|
|
|
|
return False
|
|
|
|
|
|
|
|
for item in list_a:
|
|
|
|
if item not in list_b:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def calls(hass):
|
2020-01-27 18:56:26 +00:00
|
|
|
"""Track calls to a mock service."""
|
2019-09-24 15:54:41 +00:00
|
|
|
return async_mock_service(hass, "test", "automation")
|
|
|
|
|
|
|
|
|
2020-02-12 21:12:14 +00:00
|
|
|
@pytest.fixture
|
|
|
|
async def mock_devices(hass, zigpy_device_mock, zha_device_joined_restored):
|
2020-02-10 02:45:35 +00:00
|
|
|
"""IAS device fixture."""
|
2019-09-24 15:54:41 +00:00
|
|
|
|
2020-02-10 02:45:35 +00:00
|
|
|
zigpy_device = zigpy_device_mock(
|
|
|
|
{
|
|
|
|
1: {
|
2021-09-06 23:00:06 +00:00
|
|
|
SIG_EP_INPUT: [general.Basic.cluster_id],
|
|
|
|
SIG_EP_OUTPUT: [general.OnOff.cluster_id],
|
|
|
|
SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.ON_OFF_SWITCH,
|
|
|
|
SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID,
|
2020-02-10 02:45:35 +00:00
|
|
|
}
|
2020-08-10 00:37:07 +00:00
|
|
|
}
|
2019-09-24 15:54:41 +00:00
|
|
|
)
|
|
|
|
|
2020-02-12 21:12:14 +00:00
|
|
|
zha_device = await zha_device_joined_restored(zigpy_device)
|
2020-02-10 02:45:35 +00:00
|
|
|
zha_device.update_available(True)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
return zigpy_device, zha_device
|
|
|
|
|
|
|
|
|
|
|
|
async def test_triggers(hass, mock_devices):
|
|
|
|
"""Test zha device triggers."""
|
|
|
|
|
|
|
|
zigpy_device, zha_device = mock_devices
|
|
|
|
|
2019-09-24 15:54:41 +00:00
|
|
|
zigpy_device.device_automation_triggers = {
|
|
|
|
(SHAKEN, SHAKEN): {COMMAND: COMMAND_SHAKE},
|
|
|
|
(DOUBLE_PRESS, DOUBLE_PRESS): {COMMAND: COMMAND_DOUBLE},
|
|
|
|
(SHORT_PRESS, SHORT_PRESS): {COMMAND: COMMAND_SINGLE},
|
|
|
|
(LONG_PRESS, LONG_PRESS): {COMMAND: COMMAND_HOLD},
|
|
|
|
(LONG_RELEASE, LONG_RELEASE): {COMMAND: COMMAND_HOLD},
|
|
|
|
}
|
|
|
|
|
|
|
|
ieee_address = str(zha_device.ieee)
|
|
|
|
|
2021-03-09 13:25:03 +00:00
|
|
|
ha_device_registry = dr.async_get(hass)
|
2021-01-07 12:49:45 +00:00
|
|
|
reg_device = ha_device_registry.async_get_device({("zha", ieee_address)})
|
2019-09-24 15:54:41 +00:00
|
|
|
|
2021-12-21 10:56:00 +00:00
|
|
|
triggers = await async_get_device_automations(
|
|
|
|
hass, DeviceAutomationType.TRIGGER, reg_device.id
|
|
|
|
)
|
2019-09-24 15:54:41 +00:00
|
|
|
|
|
|
|
expected_triggers = [
|
2020-08-10 00:37:07 +00:00
|
|
|
{
|
|
|
|
"device_id": reg_device.id,
|
|
|
|
"domain": "zha",
|
|
|
|
"platform": "device",
|
|
|
|
"type": "device_offline",
|
|
|
|
"subtype": "device_offline",
|
2022-04-21 06:01:32 +00:00
|
|
|
"metadata": {},
|
2020-08-10 00:37:07 +00:00
|
|
|
},
|
2019-09-24 15:54:41 +00:00
|
|
|
{
|
|
|
|
"device_id": reg_device.id,
|
|
|
|
"domain": "zha",
|
|
|
|
"platform": "device",
|
|
|
|
"type": SHAKEN,
|
|
|
|
"subtype": SHAKEN,
|
2022-04-21 06:01:32 +00:00
|
|
|
"metadata": {},
|
2019-09-24 15:54:41 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"device_id": reg_device.id,
|
|
|
|
"domain": "zha",
|
|
|
|
"platform": "device",
|
|
|
|
"type": DOUBLE_PRESS,
|
|
|
|
"subtype": DOUBLE_PRESS,
|
2022-04-21 06:01:32 +00:00
|
|
|
"metadata": {},
|
2019-09-24 15:54:41 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"device_id": reg_device.id,
|
|
|
|
"domain": "zha",
|
|
|
|
"platform": "device",
|
|
|
|
"type": SHORT_PRESS,
|
|
|
|
"subtype": SHORT_PRESS,
|
2022-04-21 06:01:32 +00:00
|
|
|
"metadata": {},
|
2019-09-24 15:54:41 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"device_id": reg_device.id,
|
|
|
|
"domain": "zha",
|
|
|
|
"platform": "device",
|
|
|
|
"type": LONG_PRESS,
|
|
|
|
"subtype": LONG_PRESS,
|
2022-04-21 06:01:32 +00:00
|
|
|
"metadata": {},
|
2019-09-24 15:54:41 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"device_id": reg_device.id,
|
|
|
|
"domain": "zha",
|
|
|
|
"platform": "device",
|
|
|
|
"type": LONG_RELEASE,
|
|
|
|
"subtype": LONG_RELEASE,
|
2022-04-21 06:01:32 +00:00
|
|
|
"metadata": {},
|
2019-09-24 15:54:41 +00:00
|
|
|
},
|
|
|
|
]
|
|
|
|
assert _same_lists(triggers, expected_triggers)
|
|
|
|
|
|
|
|
|
2020-02-10 02:45:35 +00:00
|
|
|
async def test_no_triggers(hass, mock_devices):
|
2019-09-24 15:54:41 +00:00
|
|
|
"""Test zha device with no triggers."""
|
|
|
|
|
2020-02-10 02:45:35 +00:00
|
|
|
_, zha_device = mock_devices
|
2019-09-24 15:54:41 +00:00
|
|
|
ieee_address = str(zha_device.ieee)
|
|
|
|
|
2021-03-09 13:25:03 +00:00
|
|
|
ha_device_registry = dr.async_get(hass)
|
2021-01-07 12:49:45 +00:00
|
|
|
reg_device = ha_device_registry.async_get_device({("zha", ieee_address)})
|
2019-09-24 15:54:41 +00:00
|
|
|
|
2021-12-21 10:56:00 +00:00
|
|
|
triggers = await async_get_device_automations(
|
|
|
|
hass, DeviceAutomationType.TRIGGER, reg_device.id
|
|
|
|
)
|
2020-08-10 00:37:07 +00:00
|
|
|
assert triggers == [
|
|
|
|
{
|
|
|
|
"device_id": reg_device.id,
|
|
|
|
"domain": "zha",
|
|
|
|
"platform": "device",
|
|
|
|
"type": "device_offline",
|
|
|
|
"subtype": "device_offline",
|
2022-04-21 06:01:32 +00:00
|
|
|
"metadata": {},
|
2020-08-10 00:37:07 +00:00
|
|
|
}
|
|
|
|
]
|
2019-09-24 15:54:41 +00:00
|
|
|
|
|
|
|
|
2020-02-10 02:45:35 +00:00
|
|
|
async def test_if_fires_on_event(hass, mock_devices, calls):
|
2019-09-24 15:54:41 +00:00
|
|
|
"""Test for remote triggers firing."""
|
|
|
|
|
2020-02-10 02:45:35 +00:00
|
|
|
zigpy_device, zha_device = mock_devices
|
2019-09-24 15:54:41 +00:00
|
|
|
|
|
|
|
zigpy_device.device_automation_triggers = {
|
|
|
|
(SHAKEN, SHAKEN): {COMMAND: COMMAND_SHAKE},
|
|
|
|
(DOUBLE_PRESS, DOUBLE_PRESS): {COMMAND: COMMAND_DOUBLE},
|
|
|
|
(SHORT_PRESS, SHORT_PRESS): {COMMAND: COMMAND_SINGLE},
|
|
|
|
(LONG_PRESS, LONG_PRESS): {COMMAND: COMMAND_HOLD},
|
|
|
|
(LONG_RELEASE, LONG_RELEASE): {COMMAND: COMMAND_HOLD},
|
|
|
|
}
|
|
|
|
|
|
|
|
ieee_address = str(zha_device.ieee)
|
2021-03-09 13:25:03 +00:00
|
|
|
ha_device_registry = dr.async_get(hass)
|
2021-01-07 12:49:45 +00:00
|
|
|
reg_device = ha_device_registry.async_get_device({("zha", ieee_address)})
|
2019-09-24 15:54:41 +00:00
|
|
|
|
|
|
|
assert await async_setup_component(
|
|
|
|
hass,
|
|
|
|
automation.DOMAIN,
|
|
|
|
{
|
|
|
|
automation.DOMAIN: [
|
|
|
|
{
|
|
|
|
"trigger": {
|
|
|
|
"device_id": reg_device.id,
|
|
|
|
"domain": "zha",
|
|
|
|
"platform": "device",
|
|
|
|
"type": SHORT_PRESS,
|
|
|
|
"subtype": SHORT_PRESS,
|
|
|
|
},
|
|
|
|
"action": {
|
|
|
|
"service": "test.automation",
|
|
|
|
"data": {"message": "service called"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
2020-03-21 23:59:32 +00:00
|
|
|
channel = zha_device.channels.pools[0].client_channels["1:0x0006"]
|
2020-02-21 23:06:57 +00:00
|
|
|
channel.zha_send_event(COMMAND_SINGLE, [])
|
2019-09-24 15:54:41 +00:00
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert len(calls) == 1
|
|
|
|
assert calls[0].data["message"] == "service called"
|
|
|
|
|
|
|
|
|
2020-08-10 00:37:07 +00:00
|
|
|
async def test_device_offline_fires(
|
|
|
|
hass, zigpy_device_mock, zha_device_restored, calls
|
|
|
|
):
|
|
|
|
"""Test for device offline triggers firing."""
|
|
|
|
|
|
|
|
zigpy_device = zigpy_device_mock(
|
|
|
|
{
|
|
|
|
1: {
|
|
|
|
"in_clusters": [general.Basic.cluster_id],
|
|
|
|
"out_clusters": [general.OnOff.cluster_id],
|
|
|
|
"device_type": 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
zha_device = await zha_device_restored(zigpy_device, last_seen=time.time())
|
|
|
|
await async_enable_traffic(hass, [zha_device])
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert await async_setup_component(
|
|
|
|
hass,
|
|
|
|
automation.DOMAIN,
|
|
|
|
{
|
|
|
|
automation.DOMAIN: [
|
|
|
|
{
|
|
|
|
"trigger": {
|
|
|
|
"device_id": zha_device.device_id,
|
|
|
|
"domain": "zha",
|
|
|
|
"platform": "device",
|
|
|
|
"type": "device_offline",
|
|
|
|
"subtype": "device_offline",
|
|
|
|
},
|
|
|
|
"action": {
|
|
|
|
"service": "test.automation",
|
|
|
|
"data": {"message": "service called"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert zha_device.available is True
|
|
|
|
|
2021-05-29 23:13:09 +00:00
|
|
|
zigpy_device.last_seen = time.time() - zha_device.consider_unavailable_time - 2
|
2020-08-10 00:37:07 +00:00
|
|
|
|
|
|
|
# there are 3 checkins to perform before marking the device unavailable
|
|
|
|
future = dt_util.utcnow() + timedelta(seconds=90)
|
|
|
|
async_fire_time_changed(hass, future)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
future = dt_util.utcnow() + timedelta(seconds=90)
|
|
|
|
async_fire_time_changed(hass, future)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
future = dt_util.utcnow() + timedelta(
|
2021-05-29 23:13:09 +00:00
|
|
|
seconds=zha_device.consider_unavailable_time + 100
|
2020-08-10 00:37:07 +00:00
|
|
|
)
|
|
|
|
async_fire_time_changed(hass, future)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
|
|
assert zha_device.available is False
|
|
|
|
assert len(calls) == 1
|
|
|
|
assert calls[0].data["message"] == "service called"
|
|
|
|
|
|
|
|
|
2020-02-10 02:45:35 +00:00
|
|
|
async def test_exception_no_triggers(hass, mock_devices, calls, caplog):
|
2019-09-24 15:54:41 +00:00
|
|
|
"""Test for exception on event triggers firing."""
|
|
|
|
|
2020-02-10 02:45:35 +00:00
|
|
|
_, zha_device = mock_devices
|
2019-09-24 15:54:41 +00:00
|
|
|
|
|
|
|
ieee_address = str(zha_device.ieee)
|
2021-03-09 13:25:03 +00:00
|
|
|
ha_device_registry = dr.async_get(hass)
|
2021-01-07 12:49:45 +00:00
|
|
|
reg_device = ha_device_registry.async_get_device({("zha", ieee_address)})
|
2019-09-24 15:54:41 +00:00
|
|
|
|
2019-10-09 19:04:11 +00:00
|
|
|
await async_setup_component(
|
|
|
|
hass,
|
|
|
|
automation.DOMAIN,
|
|
|
|
{
|
|
|
|
automation.DOMAIN: [
|
|
|
|
{
|
|
|
|
"trigger": {
|
|
|
|
"device_id": reg_device.id,
|
|
|
|
"domain": "zha",
|
|
|
|
"platform": "device",
|
|
|
|
"type": "junk",
|
|
|
|
"subtype": "junk",
|
|
|
|
},
|
|
|
|
"action": {
|
|
|
|
"service": "test.automation",
|
|
|
|
"data": {"message": "service called"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert "Invalid config for [automation]" in caplog.text
|
|
|
|
|
|
|
|
|
2020-02-10 02:45:35 +00:00
|
|
|
async def test_exception_bad_trigger(hass, mock_devices, calls, caplog):
|
2019-09-24 15:54:41 +00:00
|
|
|
"""Test for exception on event triggers firing."""
|
|
|
|
|
2020-02-10 02:45:35 +00:00
|
|
|
zigpy_device, zha_device = mock_devices
|
2019-09-24 15:54:41 +00:00
|
|
|
|
|
|
|
zigpy_device.device_automation_triggers = {
|
|
|
|
(SHAKEN, SHAKEN): {COMMAND: COMMAND_SHAKE},
|
|
|
|
(DOUBLE_PRESS, DOUBLE_PRESS): {COMMAND: COMMAND_DOUBLE},
|
|
|
|
(SHORT_PRESS, SHORT_PRESS): {COMMAND: COMMAND_SINGLE},
|
|
|
|
(LONG_PRESS, LONG_PRESS): {COMMAND: COMMAND_HOLD},
|
|
|
|
(LONG_RELEASE, LONG_RELEASE): {COMMAND: COMMAND_HOLD},
|
|
|
|
}
|
|
|
|
|
|
|
|
ieee_address = str(zha_device.ieee)
|
2021-03-09 13:25:03 +00:00
|
|
|
ha_device_registry = dr.async_get(hass)
|
2021-01-07 12:49:45 +00:00
|
|
|
reg_device = ha_device_registry.async_get_device({("zha", ieee_address)})
|
2019-09-24 15:54:41 +00:00
|
|
|
|
2019-10-09 19:04:11 +00:00
|
|
|
await async_setup_component(
|
|
|
|
hass,
|
|
|
|
automation.DOMAIN,
|
|
|
|
{
|
|
|
|
automation.DOMAIN: [
|
|
|
|
{
|
|
|
|
"trigger": {
|
|
|
|
"device_id": reg_device.id,
|
|
|
|
"domain": "zha",
|
|
|
|
"platform": "device",
|
|
|
|
"type": "junk",
|
|
|
|
"subtype": "junk",
|
|
|
|
},
|
|
|
|
"action": {
|
|
|
|
"service": "test.automation",
|
|
|
|
"data": {"message": "service called"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert "Invalid config for [automation]" in caplog.text
|
2022-07-10 17:35:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_exception_no_device(hass, mock_devices, calls, caplog):
|
|
|
|
"""Test for exception on event triggers firing."""
|
|
|
|
|
|
|
|
zigpy_device, zha_device = mock_devices
|
|
|
|
|
|
|
|
zigpy_device.device_automation_triggers = {
|
|
|
|
(SHAKEN, SHAKEN): {COMMAND: COMMAND_SHAKE},
|
|
|
|
(DOUBLE_PRESS, DOUBLE_PRESS): {COMMAND: COMMAND_DOUBLE},
|
|
|
|
(SHORT_PRESS, SHORT_PRESS): {COMMAND: COMMAND_SINGLE},
|
|
|
|
(LONG_PRESS, LONG_PRESS): {COMMAND: COMMAND_HOLD},
|
|
|
|
(LONG_RELEASE, LONG_RELEASE): {COMMAND: COMMAND_HOLD},
|
|
|
|
}
|
|
|
|
|
|
|
|
await async_setup_component(
|
|
|
|
hass,
|
|
|
|
automation.DOMAIN,
|
|
|
|
{
|
|
|
|
automation.DOMAIN: [
|
|
|
|
{
|
|
|
|
"trigger": {
|
|
|
|
"device_id": "no_such_device_id",
|
|
|
|
"domain": "zha",
|
|
|
|
"platform": "device",
|
|
|
|
"type": "junk",
|
|
|
|
"subtype": "junk",
|
|
|
|
},
|
|
|
|
"action": {
|
|
|
|
"service": "test.automation",
|
|
|
|
"data": {"message": "service called"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
assert "Invalid config for [automation]" in caplog.text
|