Skip to content

Commit

Permalink
deCONZ - support for power plugs (home-assistant#15752)
Browse files Browse the repository at this point in the history
* Initial commit for deCONZ switch support

* Fix hound comment

* Fix martins comment; platforms shouldn't depend on another platform

* Fix existing tests

* New tests

* Clean up unnecessary methods

* Bump requirement to v43

* Added device state attributes to light
  • Loading branch information
Kane610 authored and Jacob Mansfield committed Sep 4, 2018
1 parent e5c7d64 commit 057a272
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 10 deletions.
4 changes: 2 additions & 2 deletions homeassistant/components/deconz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)

REQUIREMENTS = ['pydeconz==42']
REQUIREMENTS = ['pydeconz==43']

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
Expand Down Expand Up @@ -96,7 +96,7 @@ def async_add_device_callback(device_type, device):
hass.data[DATA_DECONZ_EVENT] = []
hass.data[DATA_DECONZ_UNSUB] = []

for component in ['binary_sensor', 'light', 'scene', 'sensor']:
for component in ['binary_sensor', 'light', 'scene', 'sensor', 'switch']:
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
config_entry, component))

Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/deconz/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@

ATTR_DARK = 'dark'
ATTR_ON = 'on'

SWITCH_TYPES = ["On/Off plug-in unit", "Smart plug"]
18 changes: 14 additions & 4 deletions homeassistant/components/light/deconz.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/light.deconz/
"""
from homeassistant.components.deconz import (
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB)
from homeassistant.components.deconz.const import CONF_ALLOW_DECONZ_GROUPS
from homeassistant.components.deconz.const import (
CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DATA_DECONZ,
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, SWITCH_TYPES)
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR,
ATTR_TRANSITION, EFFECT_COLORLOOP, FLASH_LONG, FLASH_SHORT,
Expand All @@ -32,7 +32,8 @@ def async_add_light(lights):
"""Add light from deCONZ."""
entities = []
for light in lights:
entities.append(DeconzLight(light))
if light.type not in SWITCH_TYPES:
entities.append(DeconzLight(light))
async_add_devices(entities, True)

hass.data[DATA_DECONZ_UNSUB].append(
Expand Down Expand Up @@ -186,3 +187,12 @@ async def async_turn_off(self, **kwargs):
del data['on']

await self._light.async_set_state(data)

@property
def device_state_attributes(self):
"""Return the device state attributes."""
attributes = {}
attributes['is_deconz_group'] = self._light.type == 'LightGroup'
if self._light.type == 'LightGroup':
attributes['all_on'] = self._light.all_on
return attributes
82 changes: 82 additions & 0 deletions homeassistant/components/switch/deconz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""
Support for deCONZ switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.deconz/
"""
from homeassistant.components.deconz.const import (
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB, SWITCH_TYPES)
from homeassistant.components.switch import SwitchDevice
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect

DEPENDENCIES = ['deconz']


async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Old way of setting up deCONZ switches."""
pass


async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up switches for deCONZ component.
Switches are based same device class as lights in deCONZ.
"""
@callback
def async_add_switch(lights):
"""Add switch from deCONZ."""
entities = []
for light in lights:
if light.type in SWITCH_TYPES:
entities.append(DeconzSwitch(light))
async_add_devices(entities, True)

hass.data[DATA_DECONZ_UNSUB].append(
async_dispatcher_connect(hass, 'deconz_new_light', async_add_switch))

async_add_switch(hass.data[DATA_DECONZ].lights.values())


class DeconzSwitch(SwitchDevice):
"""Representation of a deCONZ switch."""

def __init__(self, switch):
"""Set up switch and add update callback to get data from websocket."""
self._switch = switch

async def async_added_to_hass(self):
"""Subscribe to switches events."""
self._switch.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._switch.deconz_id

@callback
def async_update_callback(self, reason):
"""Update the switch's state."""
self.async_schedule_update_ha_state()

@property
def is_on(self):
"""Return true if switch is on."""
return self._switch.state

@property
def name(self):
"""Return the name of the switch."""
return self._switch.name

@property
def unique_id(self):
"""Return a unique identifier for this switch."""
return self._switch.uniqueid

async def async_turn_on(self, **kwargs):
"""Turn on switch."""
data = {'on': True}
await self._switch.async_set_state(data)

async def async_turn_off(self, **kwargs):
"""Turn off switch."""
data = {'on': False}
await self._switch.async_set_state(data)
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,7 @@ pycsspeechtts==1.0.2
pydaikin==0.4

# homeassistant.components.deconz
pydeconz==42
pydeconz==43

# homeassistant.components.zwave
pydispatcher==2.0.5
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ py-canary==0.5.0
pyblackbird==0.5

# homeassistant.components.deconz
pydeconz==42
pydeconz==43

# homeassistant.components.zwave
pydispatcher==2.0.5
Expand Down
6 changes: 4 additions & 2 deletions tests/components/deconz/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ async def test_setup_entry_successful(hass):
assert hass.data[deconz.DOMAIN]
assert hass.data[deconz.DATA_DECONZ_ID] == {}
assert len(hass.data[deconz.DATA_DECONZ_UNSUB]) == 1
assert len(mock_add_job.mock_calls) == 4
assert len(mock_config_entries.async_forward_entry_setup.mock_calls) == 4
assert len(mock_add_job.mock_calls) == 5
assert len(mock_config_entries.async_forward_entry_setup.mock_calls) == 5
assert mock_config_entries.async_forward_entry_setup.mock_calls[0][1] == \
(entry, 'binary_sensor')
assert mock_config_entries.async_forward_entry_setup.mock_calls[1][1] == \
Expand All @@ -109,6 +109,8 @@ async def test_setup_entry_successful(hass):
(entry, 'scene')
assert mock_config_entries.async_forward_entry_setup.mock_calls[3][1] == \
(entry, 'sensor')
assert mock_config_entries.async_forward_entry_setup.mock_calls[4][1] == \
(entry, 'switch')


async def test_unload_entry(hass):
Expand Down
16 changes: 16 additions & 0 deletions tests/components/light/test_deconz.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@
},
}

SWITCH = {
"1": {
"id": "Switch 1 id",
"name": "Switch 1 name",
"type": "On/Off plug-in unit",
"state": {}
}
}


async def setup_bridge(hass, data, allow_deconz_groups=True):
"""Load the deCONZ light platform."""
Expand Down Expand Up @@ -112,3 +121,10 @@ async def test_do_not_add_deconz_groups(hass):
async_dispatcher_send(hass, 'deconz_new_group', [group])
await hass.async_block_till_done()
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0


async def test_no_switch(hass):
"""Test that a switch doesn't get created as a light entity."""
await setup_bridge(hass, {"lights": SWITCH})
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
assert len(hass.states.async_all()) == 0
90 changes: 90 additions & 0 deletions tests/components/switch/test_deconz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""deCONZ switch platform tests."""
from unittest.mock import Mock, patch

from homeassistant import config_entries
from homeassistant.components import deconz
from homeassistant.components.deconz.const import SWITCH_TYPES
from homeassistant.helpers.dispatcher import async_dispatcher_send

from tests.common import mock_coro

SUPPORTED_SWITCHES = {
"1": {
"id": "Switch 1 id",
"name": "Switch 1 name",
"type": "On/Off plug-in unit",
"state": {}
},
"2": {
"id": "Switch 2 id",
"name": "Switch 2 name",
"type": "Smart plug",
"state": {}
}
}

UNSUPPORTED_SWITCH = {
"1": {
"id": "Switch id",
"name": "Unsupported switch",
"type": "Not a smart plug",
"state": {}
}
}


async def setup_bridge(hass, data):
"""Load the deCONZ switch platform."""
from pydeconz import DeconzSession
loop = Mock()
session = Mock()
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
bridge = DeconzSession(loop, session, **entry.data)
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
await bridge.async_load_parameters()
hass.data[deconz.DOMAIN] = bridge
hass.data[deconz.DATA_DECONZ_UNSUB] = []
hass.data[deconz.DATA_DECONZ_ID] = {}
config_entry = config_entries.ConfigEntry(
1, deconz.DOMAIN, 'Mock Title', {'host': 'mock-host'}, 'test')
await hass.config_entries.async_forward_entry_setup(config_entry, 'switch')
# To flush out the service call to update the group
await hass.async_block_till_done()


async def test_no_switches(hass):
"""Test that no switch entities are created."""
data = {}
await setup_bridge(hass, data)
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
assert len(hass.states.async_all()) == 0


async def test_switch(hass):
"""Test that all supported switch entities and switch group are created."""
await setup_bridge(hass, {"lights": SUPPORTED_SWITCHES})
assert "switch.switch_1_name" in hass.data[deconz.DATA_DECONZ_ID]
assert "switch.switch_2_name" in hass.data[deconz.DATA_DECONZ_ID]
assert len(SUPPORTED_SWITCHES) == len(SWITCH_TYPES)
assert len(hass.states.async_all()) == 3


async def test_add_new_switch(hass):
"""Test successful creation of switch entity."""
data = {}
await setup_bridge(hass, data)
switch = Mock()
switch.name = 'name'
switch.type = "Smart plug"
switch.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_light', [switch])
await hass.async_block_till_done()
assert "switch.name" in hass.data[deconz.DATA_DECONZ_ID]


async def test_unsupported_switch(hass):
"""Test that unsupported switches are not created."""
await setup_bridge(hass, {"lights": UNSUPPORTED_SWITCH})
assert len(hass.states.async_all()) == 0

0 comments on commit 057a272

Please sign in to comment.