mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
deCONZ use siren platform (#56397)
* Add siren.py * Working siren platform with 100% test coverage * Also add test file... * Add test to verify that switch platform cleans up legacy entities now that sirens are their own platform * Update homeassistant/components/deconz/siren.py Co-authored-by: jjlawren <jjlawren@users.noreply.github.com>
This commit is contained in:
parent
6b6e26c96d
commit
bf7c2753d5
@ -12,6 +12,7 @@ from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
|
||||
from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.components.siren import DOMAIN as SIREN_DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
|
||||
LOGGER = logging.getLogger(__package__)
|
||||
@ -41,6 +42,7 @@ PLATFORMS = [
|
||||
LOCK_DOMAIN,
|
||||
SCENE_DOMAIN,
|
||||
SENSOR_DOMAIN,
|
||||
SIREN_DOMAIN,
|
||||
SWITCH_DOMAIN,
|
||||
]
|
||||
|
||||
@ -69,10 +71,11 @@ FANS = ["Fan"]
|
||||
# Locks
|
||||
LOCK_TYPES = ["Door Lock", "ZHADoorLock"]
|
||||
|
||||
# Sirens
|
||||
SIRENS = ["Warning device"]
|
||||
|
||||
# Switches
|
||||
POWER_PLUGS = ["On/Off light", "On/Off plug-in unit", "Smart plug"]
|
||||
SIRENS = ["Warning device"]
|
||||
SWITCH_TYPES = POWER_PLUGS + SIRENS
|
||||
|
||||
CONF_ANGLE = "angle"
|
||||
CONF_GESTURE = "gesture"
|
||||
|
@ -34,7 +34,8 @@ from .const import (
|
||||
LOCK_TYPES,
|
||||
NEW_GROUP,
|
||||
NEW_LIGHT,
|
||||
SWITCH_TYPES,
|
||||
POWER_PLUGS,
|
||||
SIRENS,
|
||||
)
|
||||
from .deconz_device import DeconzDevice
|
||||
from .gateway import get_gateway_from_config_entry
|
||||
@ -42,14 +43,16 @@ from .gateway import get_gateway_from_config_entry
|
||||
CONTROLLER = ["Configuration tool"]
|
||||
DECONZ_GROUP = "is_deconz_group"
|
||||
|
||||
OTHER_LIGHT_RESOURCE_TYPES = (
|
||||
CONTROLLER + COVER_TYPES + LOCK_TYPES + POWER_PLUGS + SIRENS
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the deCONZ lights and groups from a config entry."""
|
||||
gateway = get_gateway_from_config_entry(hass, config_entry)
|
||||
gateway.entities[DOMAIN] = set()
|
||||
|
||||
other_light_resource_types = CONTROLLER + COVER_TYPES + LOCK_TYPES + SWITCH_TYPES
|
||||
|
||||
@callback
|
||||
def async_add_light(lights=gateway.api.lights.values()):
|
||||
"""Add light from deCONZ."""
|
||||
@ -57,7 +60,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
|
||||
for light in lights:
|
||||
if (
|
||||
light.type not in other_light_resource_types
|
||||
light.type not in OTHER_LIGHT_RESOURCE_TYPES
|
||||
and light.unique_id not in gateway.entities[DOMAIN]
|
||||
):
|
||||
entities.append(DeconzLight(light, gateway))
|
||||
|
78
homeassistant/components/deconz/siren.py
Normal file
78
homeassistant/components/deconz/siren.py
Normal file
@ -0,0 +1,78 @@
|
||||
"""Support for deCONZ siren."""
|
||||
|
||||
from pydeconz.light import Siren
|
||||
|
||||
from homeassistant.components.siren import (
|
||||
ATTR_DURATION,
|
||||
DOMAIN,
|
||||
SUPPORT_DURATION,
|
||||
SUPPORT_TURN_OFF,
|
||||
SUPPORT_TURN_ON,
|
||||
SirenEntity,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import NEW_LIGHT
|
||||
from .deconz_device import DeconzDevice
|
||||
from .gateway import get_gateway_from_config_entry
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up sirens for deCONZ component."""
|
||||
gateway = get_gateway_from_config_entry(hass, config_entry)
|
||||
gateway.entities[DOMAIN] = set()
|
||||
|
||||
@callback
|
||||
def async_add_siren(lights=gateway.api.lights.values()):
|
||||
"""Add siren from deCONZ."""
|
||||
entities = []
|
||||
|
||||
for light in lights:
|
||||
|
||||
if (
|
||||
isinstance(light, Siren)
|
||||
and light.unique_id not in gateway.entities[DOMAIN]
|
||||
):
|
||||
entities.append(DeconzSiren(light, gateway))
|
||||
|
||||
if entities:
|
||||
async_add_entities(entities)
|
||||
|
||||
config_entry.async_on_unload(
|
||||
async_dispatcher_connect(
|
||||
hass, gateway.async_signal_new_device(NEW_LIGHT), async_add_siren
|
||||
)
|
||||
)
|
||||
|
||||
async_add_siren()
|
||||
|
||||
|
||||
class DeconzSiren(DeconzDevice, SirenEntity):
|
||||
"""Representation of a deCONZ siren."""
|
||||
|
||||
TYPE = DOMAIN
|
||||
|
||||
def __init__(self, device, gateway) -> None:
|
||||
"""Set up siren."""
|
||||
super().__init__(device, gateway)
|
||||
|
||||
self._attr_supported_features = (
|
||||
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_DURATION
|
||||
)
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if siren is on."""
|
||||
return self._device.is_on
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn on siren."""
|
||||
data = {}
|
||||
if (duration := kwargs.get(ATTR_DURATION)) is not None:
|
||||
data["duration"] = duration * 10
|
||||
await self._device.turn_on(**data)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn off siren."""
|
||||
await self._device.turn_off()
|
@ -3,7 +3,7 @@ from homeassistant.components.switch import DOMAIN, SwitchEntity
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import NEW_LIGHT, POWER_PLUGS, SIRENS
|
||||
from .const import DOMAIN as DECONZ_DOMAIN, NEW_LIGHT, POWER_PLUGS, SIRENS
|
||||
from .deconz_device import DeconzDevice
|
||||
from .gateway import get_gateway_from_config_entry
|
||||
|
||||
@ -16,6 +16,17 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
gateway = get_gateway_from_config_entry(hass, config_entry)
|
||||
gateway.entities[DOMAIN] = set()
|
||||
|
||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
||||
|
||||
# Siren platform replacing sirens in switch platform added in 2021.10
|
||||
for light in gateway.api.lights.values():
|
||||
if light.type not in SIRENS:
|
||||
continue
|
||||
if entity_id := entity_registry.async_get_entity_id(
|
||||
DOMAIN, DECONZ_DOMAIN, light.unique_id
|
||||
):
|
||||
entity_registry.async_remove(entity_id)
|
||||
|
||||
@callback
|
||||
def async_add_switch(lights=gateway.api.lights.values()):
|
||||
"""Add switch from deCONZ."""
|
||||
@ -29,11 +40,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
):
|
||||
entities.append(DeconzPowerPlug(light, gateway))
|
||||
|
||||
elif (
|
||||
light.type in SIRENS and light.unique_id not in gateway.entities[DOMAIN]
|
||||
):
|
||||
entities.append(DeconzSiren(light, gateway))
|
||||
|
||||
if entities:
|
||||
async_add_entities(entities)
|
||||
|
||||
@ -63,22 +69,3 @@ class DeconzPowerPlug(DeconzDevice, SwitchEntity):
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn off switch."""
|
||||
await self._device.set_state(on=False)
|
||||
|
||||
|
||||
class DeconzSiren(DeconzDevice, SwitchEntity):
|
||||
"""Representation of a deCONZ siren."""
|
||||
|
||||
TYPE = DOMAIN
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if switch is on."""
|
||||
return self._device.is_on
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn on switch."""
|
||||
await self._device.turn_on()
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn off switch."""
|
||||
await self._device.turn_off()
|
||||
|
@ -25,6 +25,7 @@ from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN
|
||||
from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.components.siren import DOMAIN as SIREN_DOMAIN
|
||||
from homeassistant.components.ssdp import (
|
||||
ATTR_SSDP_LOCATION,
|
||||
ATTR_UPNP_MANUFACTURER_URL,
|
||||
@ -163,7 +164,8 @@ async def test_gateway_setup(hass, aioclient_mock):
|
||||
assert forward_entry_setup.mock_calls[6][1] == (config_entry, LOCK_DOMAIN)
|
||||
assert forward_entry_setup.mock_calls[7][1] == (config_entry, SCENE_DOMAIN)
|
||||
assert forward_entry_setup.mock_calls[8][1] == (config_entry, SENSOR_DOMAIN)
|
||||
assert forward_entry_setup.mock_calls[9][1] == (config_entry, SWITCH_DOMAIN)
|
||||
assert forward_entry_setup.mock_calls[9][1] == (config_entry, SIREN_DOMAIN)
|
||||
assert forward_entry_setup.mock_calls[10][1] == (config_entry, SWITCH_DOMAIN)
|
||||
|
||||
|
||||
async def test_gateway_retry(hass):
|
||||
|
132
tests/components/deconz/test_siren.py
Normal file
132
tests/components/deconz/test_siren.py
Normal file
@ -0,0 +1,132 @@
|
||||
"""deCONZ switch platform tests."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.components.deconz.const import DOMAIN as DECONZ_DOMAIN
|
||||
from homeassistant.components.siren import ATTR_DURATION, DOMAIN as SIREN_DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .test_gateway import (
|
||||
DECONZ_WEB_REQUEST,
|
||||
mock_deconz_put_request,
|
||||
setup_deconz_integration,
|
||||
)
|
||||
|
||||
|
||||
async def test_sirens(hass, aioclient_mock, mock_deconz_websocket):
|
||||
"""Test that siren entities are created."""
|
||||
data = {
|
||||
"lights": {
|
||||
"1": {
|
||||
"name": "Warning device",
|
||||
"type": "Warning device",
|
||||
"state": {"alert": "lselect", "reachable": True},
|
||||
"uniqueid": "00:00:00:00:00:00:00:00-00",
|
||||
},
|
||||
"2": {
|
||||
"name": "Unsupported siren",
|
||||
"type": "Not a siren",
|
||||
"state": {"reachable": True},
|
||||
"uniqueid": "00:00:00:00:00:00:00:01-00",
|
||||
},
|
||||
}
|
||||
}
|
||||
with patch.dict(DECONZ_WEB_REQUEST, data):
|
||||
config_entry = await setup_deconz_integration(hass, aioclient_mock)
|
||||
|
||||
assert len(hass.states.async_all()) == 2
|
||||
assert hass.states.get("siren.warning_device").state == STATE_ON
|
||||
assert not hass.states.get("siren.unsupported_siren")
|
||||
|
||||
event_changed_light = {
|
||||
"t": "event",
|
||||
"e": "changed",
|
||||
"r": "lights",
|
||||
"id": "1",
|
||||
"state": {"alert": None},
|
||||
}
|
||||
await mock_deconz_websocket(data=event_changed_light)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("siren.warning_device").state == STATE_OFF
|
||||
|
||||
# Verify service calls
|
||||
|
||||
mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state")
|
||||
|
||||
# Service turn on siren
|
||||
|
||||
await hass.services.async_call(
|
||||
SIREN_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "siren.warning_device"},
|
||||
blocking=True,
|
||||
)
|
||||
assert aioclient_mock.mock_calls[1][2] == {"alert": "lselect"}
|
||||
|
||||
# Service turn off siren
|
||||
|
||||
await hass.services.async_call(
|
||||
SIREN_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: "siren.warning_device"},
|
||||
blocking=True,
|
||||
)
|
||||
assert aioclient_mock.mock_calls[2][2] == {"alert": "none"}
|
||||
|
||||
# Service turn on siren with duration
|
||||
|
||||
await hass.services.async_call(
|
||||
SIREN_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "siren.warning_device", ATTR_DURATION: 10},
|
||||
blocking=True,
|
||||
)
|
||||
assert aioclient_mock.mock_calls[3][2] == {"alert": "lselect", "ontime": 100}
|
||||
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
|
||||
states = hass.states.async_all()
|
||||
assert len(states) == 2
|
||||
for state in states:
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
await hass.config_entries.async_remove(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
|
||||
async def test_remove_legacy_siren_switch(hass, aioclient_mock):
|
||||
"""Test that switch platform cleans up legacy siren entities."""
|
||||
unique_id = "00:00:00:00:00:00:00:00-00"
|
||||
|
||||
registry = er.async_get(hass)
|
||||
switch_siren_entity = registry.async_get_or_create(
|
||||
SWITCH_DOMAIN, DECONZ_DOMAIN, unique_id
|
||||
)
|
||||
|
||||
assert switch_siren_entity
|
||||
|
||||
data = {
|
||||
"lights": {
|
||||
"1": {
|
||||
"name": "Warning device",
|
||||
"type": "Warning device",
|
||||
"state": {"alert": "lselect", "reachable": True},
|
||||
"uniqueid": unique_id,
|
||||
},
|
||||
}
|
||||
}
|
||||
with patch.dict(DECONZ_WEB_REQUEST, data):
|
||||
await setup_deconz_integration(hass, aioclient_mock)
|
||||
|
||||
assert not registry.async_get(switch_siren_entity.entity_id)
|
@ -107,76 +107,3 @@ async def test_power_plugs(hass, aioclient_mock, mock_deconz_websocket):
|
||||
await hass.config_entries.async_remove(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
|
||||
async def test_sirens(hass, aioclient_mock, mock_deconz_websocket):
|
||||
"""Test that siren entities are created."""
|
||||
data = {
|
||||
"lights": {
|
||||
"1": {
|
||||
"name": "Warning device",
|
||||
"type": "Warning device",
|
||||
"state": {"alert": "lselect", "reachable": True},
|
||||
"uniqueid": "00:00:00:00:00:00:00:00-00",
|
||||
},
|
||||
"2": {
|
||||
"name": "Unsupported switch",
|
||||
"type": "Not a switch",
|
||||
"state": {"reachable": True},
|
||||
"uniqueid": "00:00:00:00:00:00:00:01-00",
|
||||
},
|
||||
}
|
||||
}
|
||||
with patch.dict(DECONZ_WEB_REQUEST, data):
|
||||
config_entry = await setup_deconz_integration(hass, aioclient_mock)
|
||||
|
||||
assert len(hass.states.async_all()) == 2
|
||||
assert hass.states.get("switch.warning_device").state == STATE_ON
|
||||
assert not hass.states.get("switch.unsupported_switch")
|
||||
|
||||
event_changed_light = {
|
||||
"t": "event",
|
||||
"e": "changed",
|
||||
"r": "lights",
|
||||
"id": "1",
|
||||
"state": {"alert": None},
|
||||
}
|
||||
await mock_deconz_websocket(data=event_changed_light)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert hass.states.get("switch.warning_device").state == STATE_OFF
|
||||
|
||||
# Verify service calls
|
||||
|
||||
mock_deconz_put_request(aioclient_mock, config_entry.data, "/lights/1/state")
|
||||
|
||||
# Service turn on siren
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{ATTR_ENTITY_ID: "switch.warning_device"},
|
||||
blocking=True,
|
||||
)
|
||||
assert aioclient_mock.mock_calls[1][2] == {"alert": "lselect"}
|
||||
|
||||
# Service turn off siren
|
||||
|
||||
await hass.services.async_call(
|
||||
SWITCH_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: "switch.warning_device"},
|
||||
blocking=True,
|
||||
)
|
||||
assert aioclient_mock.mock_calls[2][2] == {"alert": "none"}
|
||||
|
||||
await hass.config_entries.async_unload(config_entry.entry_id)
|
||||
|
||||
states = hass.states.async_all()
|
||||
assert len(states) == 2
|
||||
for state in states:
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
await hass.config_entries.async_remove(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
Loading…
x
Reference in New Issue
Block a user