Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions homeassistant/components/deconz/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"climate",
"cover",
"light",
"lock",
"scene",
"sensor",
"switch",
Expand All @@ -38,10 +39,16 @@
ATTR_ON = "on"
ATTR_VALVE = "valve"

# Covers
DAMPERS = ["Level controllable output"]
WINDOW_COVERS = ["Window covering device", "Window covering controller"]
COVER_TYPES = DAMPERS + WINDOW_COVERS

# Locks
LOCKS = ["Door Lock"]
LOCK_TYPES = LOCKS

# Switches
POWER_PLUGS = ["On/Off light", "On/Off plug-in unit", "Smart plug"]
SIRENS = ["Warning device"]
SWITCH_TYPES = POWER_PLUGS + SIRENS
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/deconz/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
CONF_GROUP_ID_BASE,
COVER_TYPES,
DOMAIN as DECONZ_DOMAIN,
LOCK_TYPES,
NEW_GROUP,
NEW_LIGHT,
SWITCH_TYPES,
Expand All @@ -50,7 +51,7 @@ def async_add_light(lights):

for light in lights:
if (
light.type not in COVER_TYPES + SWITCH_TYPES
light.type not in COVER_TYPES + LOCK_TYPES + SWITCH_TYPES
and light.uniqueid not in gateway.entities[DOMAIN]
):
entities.append(DeconzLight(light, gateway))
Expand Down
59 changes: 59 additions & 0 deletions homeassistant/components/deconz/lock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Support for deCONZ locks."""
from homeassistant.components.lock import DOMAIN, LockEntity
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect

from .const import LOCKS, 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 locks for deCONZ component.

Locks are based on the same device class as lights in deCONZ.
"""
gateway = get_gateway_from_config_entry(hass, config_entry)
gateway.entities[DOMAIN] = set()

@callback
def async_add_lock(lights):
"""Add lock from deCONZ."""
entities = []

for light in lights:

if light.type in LOCKS and light.uniqueid not in gateway.entities[DOMAIN]:
entities.append(DeconzLock(light, gateway))

if entities:
async_add_entities(entities, True)

gateway.listeners.append(
async_dispatcher_connect(
hass, gateway.async_signal_new_device(NEW_LIGHT), async_add_lock
)
)

async_add_lock(gateway.api.lights.values())


class DeconzLock(DeconzDevice, LockEntity):
"""Representation of a deCONZ lock."""

TYPE = DOMAIN

@property
def is_locked(self):
"""Return true if lock is on."""
return self._device.state

async def async_lock(self, **kwargs):
"""Lock the lock."""
data = {"on": True}
await self._device.async_set_state(data)

async def async_unlock(self, **kwargs):
"""Unlock the lock."""
data = {"on": False}
await self._device.async_set_state(data)
7 changes: 4 additions & 3 deletions tests/components/deconz/test_gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ async def test_gateway_setup(hass):
assert forward_entry_setup.mock_calls[1][1] == (entry, "climate")
assert forward_entry_setup.mock_calls[2][1] == (entry, "cover")
assert forward_entry_setup.mock_calls[3][1] == (entry, "light")
assert forward_entry_setup.mock_calls[4][1] == (entry, "scene")
assert forward_entry_setup.mock_calls[5][1] == (entry, "sensor")
assert forward_entry_setup.mock_calls[6][1] == (entry, "switch")
assert forward_entry_setup.mock_calls[4][1] == (entry, "lock")
assert forward_entry_setup.mock_calls[5][1] == (entry, "scene")
assert forward_entry_setup.mock_calls[6][1] == (entry, "sensor")
assert forward_entry_setup.mock_calls[7][1] == (entry, "switch")


async def test_gateway_retry(hass):
Expand Down
96 changes: 96 additions & 0 deletions tests/components/deconz/test_lock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""deCONZ lock platform tests."""
from copy import deepcopy

from homeassistant.components import deconz
import homeassistant.components.lock as lock
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
from homeassistant.setup import async_setup_component

from .test_gateway import DECONZ_WEB_REQUEST, setup_deconz_integration

from tests.async_mock import patch

LOCKS = {
"1": {
"etag": "5c2ec06cde4bd654aef3a555fcd8ad12",
"hascolor": False,
"lastannounced": None,
"lastseen": "2020-08-22T15:29:03Z",
"manufacturername": "Danalock",
"modelid": "V3-BTZB",
"name": "Door lock",
"state": {"alert": "none", "on": False, "reachable": True},
"swversion": "19042019",
"type": "Door Lock",
"uniqueid": "00:00:00:00:00:00:00:00-00",
}
}


async def test_platform_manually_configured(hass):
"""Test that we do not discover anything or try to set up a gateway."""
assert (
await async_setup_component(
hass, lock.DOMAIN, {"lock": {"platform": deconz.DOMAIN}}
)
is True
)
assert deconz.DOMAIN not in hass.data


async def test_no_locks(hass):
"""Test that no lock entities are created."""
gateway = await setup_deconz_integration(hass)
assert len(gateway.deconz_ids) == 0
Comment thread
Kane610 marked this conversation as resolved.
assert len(hass.states.async_all()) == 0


async def test_locks(hass):
"""Test that all supported lock entities are created."""
data = deepcopy(DECONZ_WEB_REQUEST)
data["lights"] = deepcopy(LOCKS)
gateway = await setup_deconz_integration(hass, get_state_response=data)
assert "lock.door_lock" in gateway.deconz_ids
assert len(hass.states.async_all()) == 1

door_lock = hass.states.get("lock.door_lock")
assert door_lock.state == STATE_UNLOCKED

state_changed_event = {
"t": "event",
"e": "changed",
"r": "lights",
"id": "1",
"state": {"on": True},
}
gateway.api.event_handler(state_changed_event)
await hass.async_block_till_done()

door_lock = hass.states.get("lock.door_lock")
assert door_lock.state == STATE_LOCKED

door_lock_device = gateway.api.lights["1"]

with patch.object(door_lock_device, "_request", return_value=True) as set_callback:
await hass.services.async_call(
lock.DOMAIN,
lock.SERVICE_LOCK,
{"entity_id": "lock.door_lock"},
blocking=True,
)
await hass.async_block_till_done()
set_callback.assert_called_with("put", "/lights/1/state", json={"on": True})

with patch.object(door_lock_device, "_request", return_value=True) as set_callback:
await hass.services.async_call(
lock.DOMAIN,
lock.SERVICE_UNLOCK,
{"entity_id": "lock.door_lock"},
blocking=True,
)
await hass.async_block_till_done()
set_callback.assert_called_with("put", "/lights/1/state", json={"on": False})

await gateway.async_reset()

assert len(hass.states.async_all()) == 0