mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
add event and device action for when devices drop (#38701)
This commit is contained in:
parent
3d308e0599
commit
4e56339ba1
@ -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:
|
||||||
|
@ -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",
|
||||||
|
@ -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."""
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user