mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 20:57:21 +00:00
deCONZ - support for power plugs (#15752)
* 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
This commit is contained in:
parent
623f6c841b
commit
f8a478946e
@ -22,7 +22,7 @@ from .const import (
|
|||||||
CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
|
CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
|
||||||
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
|
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
|
||||||
|
|
||||||
REQUIREMENTS = ['pydeconz==42']
|
REQUIREMENTS = ['pydeconz==43']
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
DOMAIN: vol.Schema({
|
DOMAIN: vol.Schema({
|
||||||
@ -96,7 +96,7 @@ async def async_setup_entry(hass, config_entry):
|
|||||||
hass.data[DATA_DECONZ_EVENT] = []
|
hass.data[DATA_DECONZ_EVENT] = []
|
||||||
hass.data[DATA_DECONZ_UNSUB] = []
|
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(
|
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
|
||||||
config_entry, component))
|
config_entry, component))
|
||||||
|
|
||||||
|
@ -14,3 +14,5 @@ CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups'
|
|||||||
|
|
||||||
ATTR_DARK = 'dark'
|
ATTR_DARK = 'dark'
|
||||||
ATTR_ON = 'on'
|
ATTR_ON = 'on'
|
||||||
|
|
||||||
|
SWITCH_TYPES = ["On/Off plug-in unit", "Smart plug"]
|
||||||
|
@ -4,9 +4,9 @@ Support for deCONZ light.
|
|||||||
For more details about this component, please refer to the documentation at
|
For more details about this component, please refer to the documentation at
|
||||||
https://home-assistant.io/components/light.deconz/
|
https://home-assistant.io/components/light.deconz/
|
||||||
"""
|
"""
|
||||||
from homeassistant.components.deconz import (
|
from homeassistant.components.deconz.const import (
|
||||||
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB)
|
CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DATA_DECONZ,
|
||||||
from homeassistant.components.deconz.const import CONF_ALLOW_DECONZ_GROUPS
|
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, SWITCH_TYPES)
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR,
|
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR,
|
||||||
ATTR_TRANSITION, EFFECT_COLORLOOP, FLASH_LONG, FLASH_SHORT,
|
ATTR_TRANSITION, EFFECT_COLORLOOP, FLASH_LONG, FLASH_SHORT,
|
||||||
@ -32,7 +32,8 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
|
|||||||
"""Add light from deCONZ."""
|
"""Add light from deCONZ."""
|
||||||
entities = []
|
entities = []
|
||||||
for light in lights:
|
for light in lights:
|
||||||
entities.append(DeconzLight(light))
|
if light.type not in SWITCH_TYPES:
|
||||||
|
entities.append(DeconzLight(light))
|
||||||
async_add_devices(entities, True)
|
async_add_devices(entities, True)
|
||||||
|
|
||||||
hass.data[DATA_DECONZ_UNSUB].append(
|
hass.data[DATA_DECONZ_UNSUB].append(
|
||||||
@ -186,3 +187,12 @@ class DeconzLight(Light):
|
|||||||
del data['on']
|
del data['on']
|
||||||
|
|
||||||
await self._light.async_set_state(data)
|
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
homeassistant/components/switch/deconz.py
Normal file
82
homeassistant/components/switch/deconz.py
Normal file
@ -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)
|
@ -793,7 +793,7 @@ pycsspeechtts==1.0.2
|
|||||||
pydaikin==0.4
|
pydaikin==0.4
|
||||||
|
|
||||||
# homeassistant.components.deconz
|
# homeassistant.components.deconz
|
||||||
pydeconz==42
|
pydeconz==43
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
pydispatcher==2.0.5
|
pydispatcher==2.0.5
|
||||||
|
@ -136,7 +136,7 @@ py-canary==0.5.0
|
|||||||
pyblackbird==0.5
|
pyblackbird==0.5
|
||||||
|
|
||||||
# homeassistant.components.deconz
|
# homeassistant.components.deconz
|
||||||
pydeconz==42
|
pydeconz==43
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
pydispatcher==2.0.5
|
pydispatcher==2.0.5
|
||||||
|
@ -99,8 +99,8 @@ async def test_setup_entry_successful(hass):
|
|||||||
assert hass.data[deconz.DOMAIN]
|
assert hass.data[deconz.DOMAIN]
|
||||||
assert hass.data[deconz.DATA_DECONZ_ID] == {}
|
assert hass.data[deconz.DATA_DECONZ_ID] == {}
|
||||||
assert len(hass.data[deconz.DATA_DECONZ_UNSUB]) == 1
|
assert len(hass.data[deconz.DATA_DECONZ_UNSUB]) == 1
|
||||||
assert len(mock_add_job.mock_calls) == 4
|
assert len(mock_add_job.mock_calls) == 5
|
||||||
assert len(mock_config_entries.async_forward_entry_setup.mock_calls) == 4
|
assert len(mock_config_entries.async_forward_entry_setup.mock_calls) == 5
|
||||||
assert mock_config_entries.async_forward_entry_setup.mock_calls[0][1] == \
|
assert mock_config_entries.async_forward_entry_setup.mock_calls[0][1] == \
|
||||||
(entry, 'binary_sensor')
|
(entry, 'binary_sensor')
|
||||||
assert mock_config_entries.async_forward_entry_setup.mock_calls[1][1] == \
|
assert mock_config_entries.async_forward_entry_setup.mock_calls[1][1] == \
|
||||||
@ -109,6 +109,8 @@ async def test_setup_entry_successful(hass):
|
|||||||
(entry, 'scene')
|
(entry, 'scene')
|
||||||
assert mock_config_entries.async_forward_entry_setup.mock_calls[3][1] == \
|
assert mock_config_entries.async_forward_entry_setup.mock_calls[3][1] == \
|
||||||
(entry, 'sensor')
|
(entry, 'sensor')
|
||||||
|
assert mock_config_entries.async_forward_entry_setup.mock_calls[4][1] == \
|
||||||
|
(entry, 'switch')
|
||||||
|
|
||||||
|
|
||||||
async def test_unload_entry(hass):
|
async def test_unload_entry(hass):
|
||||||
|
@ -37,6 +37,15 @@ GROUP = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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):
|
async def setup_bridge(hass, data, allow_deconz_groups=True):
|
||||||
"""Load the deCONZ light platform."""
|
"""Load the deCONZ light platform."""
|
||||||
@ -112,3 +121,10 @@ async def test_do_not_add_deconz_groups(hass):
|
|||||||
async_dispatcher_send(hass, 'deconz_new_group', [group])
|
async_dispatcher_send(hass, 'deconz_new_group', [group])
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
|
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
tests/components/switch/test_deconz.py
Normal file
90
tests/components/switch/test_deconz.py
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user