add event and device action for when devices drop (#38701)

This commit is contained in:
David F. Mulcahey 2020-08-09 20:37:07 -04:00 committed by GitHub
parent 3d308e0599
commit 4e56339ba1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 115 additions and 6 deletions

View File

@ -30,6 +30,7 @@ from .const import (
ATTR_CLUSTER_ID, ATTR_CLUSTER_ID,
ATTR_COMMAND, ATTR_COMMAND,
ATTR_COMMAND_TYPE, ATTR_COMMAND_TYPE,
ATTR_DEVICE_IEEE,
ATTR_DEVICE_TYPE, ATTR_DEVICE_TYPE,
ATTR_ENDPOINT_ID, ATTR_ENDPOINT_ID,
ATTR_ENDPOINTS, ATTR_ENDPOINTS,
@ -247,9 +248,14 @@ class ZHADevice(LogMixin):
@property @property
def device_automation_triggers(self): def device_automation_triggers(self):
"""Return the device automation triggers for this device.""" """Return the device automation triggers for this device."""
triggers = {
("device_offline", "device_offline"): {
"device_event_type": "device_offline"
}
}
if hasattr(self._zigpy_device, "device_automation_triggers"): if hasattr(self._zigpy_device, "device_automation_triggers"):
return self._zigpy_device.device_automation_triggers triggers.update(self._zigpy_device.device_automation_triggers)
return None return triggers
@property @property
def available_signal(self): def available_signal(self):
@ -346,6 +352,14 @@ class ZHADevice(LogMixin):
# reinit channels then signal entities # reinit channels then signal entities
self.hass.async_create_task(self._async_became_available()) self.hass.async_create_task(self._async_became_available())
return return
if availability_changed and not available:
self.hass.bus.async_fire(
"zha_event",
{
ATTR_DEVICE_IEEE: str(self.ieee),
"device_event_type": "device_offline",
},
)
async_dispatcher_send(self.hass, f"{self._available_signal}_entity") async_dispatcher_send(self.hass, f"{self._available_signal}_entity")
async def _async_became_available(self) -> None: async def _async_became_available(self) -> None:

View File

@ -51,7 +51,8 @@
"device_tilted": "Device tilted", "device_tilted": "Device tilted",
"device_knocked": "Device knocked \"{subtype}\"", "device_knocked": "Device knocked \"{subtype}\"",
"device_dropped": "Device dropped", "device_dropped": "Device dropped",
"device_flipped": "Device flipped \"{subtype}\"" "device_flipped": "Device flipped \"{subtype}\"",
"device_offline": "Device offline"
}, },
"trigger_subtype": { "trigger_subtype": {
"turn_on": "Turn on", "turn_on": "Turn on",

View File

@ -1,12 +1,23 @@
"""ZHA device automation trigger tests.""" """ZHA device automation trigger tests."""
from datetime import timedelta
import time
import pytest import pytest
import zigpy.zcl.clusters.general as general import zigpy.zcl.clusters.general as general
import homeassistant.components.automation as automation import homeassistant.components.automation as automation
import homeassistant.components.zha.core.device as zha_core_device
from homeassistant.helpers.device_registry import async_get_registry from homeassistant.helpers.device_registry import async_get_registry
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
from tests.common import async_get_device_automations, async_mock_service from .common import async_enable_traffic
from tests.common import (
async_fire_time_changed,
async_get_device_automations,
async_mock_service,
)
ON = 1 ON = 1
OFF = 0 OFF = 0
@ -49,7 +60,7 @@ async def mock_devices(hass, zigpy_device_mock, zha_device_joined_restored):
"out_clusters": [general.OnOff.cluster_id], "out_clusters": [general.OnOff.cluster_id],
"device_type": 0, "device_type": 0,
} }
}, }
) )
zha_device = await zha_device_joined_restored(zigpy_device) zha_device = await zha_device_joined_restored(zigpy_device)
@ -79,6 +90,13 @@ async def test_triggers(hass, mock_devices):
triggers = await async_get_device_automations(hass, "trigger", reg_device.id) triggers = await async_get_device_automations(hass, "trigger", reg_device.id)
expected_triggers = [ expected_triggers = [
{
"device_id": reg_device.id,
"domain": "zha",
"platform": "device",
"type": "device_offline",
"subtype": "device_offline",
},
{ {
"device_id": reg_device.id, "device_id": reg_device.id,
"domain": "zha", "domain": "zha",
@ -128,7 +146,15 @@ async def test_no_triggers(hass, mock_devices):
reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}, set()) reg_device = ha_device_registry.async_get_device({("zha", ieee_address)}, set())
triggers = await async_get_device_automations(hass, "trigger", reg_device.id) triggers = await async_get_device_automations(hass, "trigger", reg_device.id)
assert triggers == [] assert triggers == [
{
"device_id": reg_device.id,
"domain": "zha",
"platform": "device",
"type": "device_offline",
"subtype": "device_offline",
}
]
async def test_if_fires_on_event(hass, mock_devices, calls): async def test_if_fires_on_event(hass, mock_devices, calls):
@ -180,6 +206,74 @@ async def test_if_fires_on_event(hass, mock_devices, calls):
assert calls[0].data["message"] == "service called" assert calls[0].data["message"] == "service called"
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
zigpy_device.last_seen = (
time.time() - zha_core_device.CONSIDER_UNAVAILABLE_BATTERY - 2
)
# 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(
seconds=zha_core_device.CONSIDER_UNAVAILABLE_BATTERY + 100
)
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"
async def test_exception_no_triggers(hass, mock_devices, calls, caplog): async def test_exception_no_triggers(hass, mock_devices, calls, caplog):
"""Test for exception on event triggers firing.""" """Test for exception on event triggers firing."""