Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deCONZ - support for power plugs #15752

Merged
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
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 @@ -790,7 +790,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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't really testing the production code, just that we cover all cases in the tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Later on there is gonna be more types of switches than power plugs so I want to make sure the lists are correct

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