deCONZ use forward entry setup (#13990)

* Use forward entry setup with light platform

* Move sensor to forward entry setup

* Use forward entry setup with binary sensors

* Use forward entry setup with scene platform

* Remove import of unused functionality

* Move deconz setup in to setup entry
Create initial negative tests for setup entry

* Fix hound comment

* Improved tests

* Add test for scene platform

* Add test for binary sensor platform

* Add test for light platform

* Add test for light platform

* Add test for sensor platform

* Fix hound comment

* More asserts on sensor types
This commit is contained in:
Kane610 2018-04-23 18:00:16 +02:00 committed by Paulus Schoutsen
parent 5fe4053021
commit 8a10fcd985
13 changed files with 358 additions and 36 deletions

View File

@ -50,13 +50,18 @@ DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
async def async_setup(hass, config): async def async_setup(hass, config):
"""Track states and offer events for binary sensors.""" """Track states and offer events for binary sensors."""
component = EntityComponent( component = hass.data[DOMAIN] = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config) await component.async_setup(config)
return True return True
async def async_setup_entry(hass, entry):
"""Setup a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
# pylint: disable=no-self-use # pylint: disable=no-self-use
class BinarySensorDevice(Entity): class BinarySensorDevice(Entity):
"""Represent a binary sensor.""" """Represent a binary sensor."""

View File

@ -15,10 +15,12 @@ DEPENDENCIES = ['deconz']
async def async_setup_platform(hass, config, async_add_devices, async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None): discovery_info=None):
"""Set up the deCONZ binary sensor.""" """Old way of setting up deCONZ binary sensors."""
if discovery_info is None: pass
return
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the deCONZ binary sensor."""
from pydeconz.sensor import DECONZ_BINARY_SENSOR from pydeconz.sensor import DECONZ_BINARY_SENSOR
sensors = hass.data[DATA_DECONZ].sensors sensors = hass.data[DATA_DECONZ].sensors
entities = [] entities = []

View File

@ -9,8 +9,7 @@ import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP) CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import ( from homeassistant.helpers import aiohttp_client, config_validation as cv
aiohttp_client, discovery, config_validation as cv)
from homeassistant.util.json import load_json from homeassistant.util.json import load_json
# Loading the config flow file will register the flow # Loading the config flow file will register the flow
@ -58,28 +57,20 @@ async def async_setup(hass, config):
return True return True
async def async_setup_entry(hass, entry): async def async_setup_entry(hass, config_entry):
"""Set up a deCONZ bridge for a config entry.""" """Set up a deCONZ bridge for a config entry.
if DOMAIN in hass.data:
_LOGGER.error(
"Config entry failed since one deCONZ instance already exists")
return False
result = await async_setup_deconz(hass, None, entry.data)
if result:
return True
return False
async def async_setup_deconz(hass, config, deconz_config):
"""Set up a deCONZ session.
Load config, group, light and sensor data for server information. Load config, group, light and sensor data for server information.
Start websocket for push notification of state changes from deCONZ. Start websocket for push notification of state changes from deCONZ.
""" """
_LOGGER.debug("deCONZ config %s", deconz_config)
from pydeconz import DeconzSession from pydeconz import DeconzSession
if DOMAIN in hass.data:
_LOGGER.error(
"Config entry failed since one deCONZ instance already exists")
return False
session = aiohttp_client.async_get_clientsession(hass) session = aiohttp_client.async_get_clientsession(hass)
deconz = DeconzSession(hass.loop, session, **deconz_config) deconz = DeconzSession(hass.loop, session, **config_entry.data)
result = await deconz.async_load_parameters() result = await deconz.async_load_parameters()
if result is False: if result is False:
_LOGGER.error("Failed to communicate with deCONZ") _LOGGER.error("Failed to communicate with deCONZ")
@ -89,8 +80,8 @@ async def async_setup_deconz(hass, config, deconz_config):
hass.data[DATA_DECONZ_ID] = {} hass.data[DATA_DECONZ_ID] = {}
for component in ['binary_sensor', 'light', 'scene', 'sensor']: for component in ['binary_sensor', 'light', 'scene', 'sensor']:
hass.async_add_job(discovery.async_load_platform( hass.async_add_job(hass.config_entries.async_forward_entry_setup(
hass, component, DOMAIN, {}, config)) config_entry, component))
deconz.start() deconz.start()
async def async_configure(call): async def async_configure(call):

View File

@ -19,10 +19,12 @@ DEPENDENCIES = ['deconz']
async def async_setup_platform(hass, config, async_add_devices, async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None): discovery_info=None):
"""Set up the deCONZ light.""" """Old way of setting up deCONZ lights."""
if discovery_info is None: pass
return
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the deCONZ lights from a config entry."""
lights = hass.data[DATA_DECONZ].lights lights = hass.data[DATA_DECONZ].lights
groups = hass.data[DATA_DECONZ].groups groups = hass.data[DATA_DECONZ].groups
entities = [] entities = []

View File

@ -71,7 +71,7 @@ def activate(hass, entity_id=None):
async def async_setup(hass, config): async def async_setup(hass, config):
"""Set up the scenes.""" """Set up the scenes."""
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
component = EntityComponent(logger, DOMAIN, hass) component = hass.data[DOMAIN] = EntityComponent(logger, DOMAIN, hass)
await component.async_setup(config) await component.async_setup(config)
@ -90,6 +90,11 @@ async def async_setup(hass, config):
return True return True
async def async_setup_entry(hass, entry):
"""Setup a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)
class Scene(Entity): class Scene(Entity):
"""A scene is a group of entities and the states we want them to be.""" """A scene is a group of entities and the states we want them to be."""

View File

@ -13,10 +13,12 @@ DEPENDENCIES = ['deconz']
async def async_setup_platform(hass, config, async_add_devices, async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None): discovery_info=None):
"""Set up scenes for deCONZ component.""" """Old way of setting up deCONZ scenes."""
if discovery_info is None: pass
return
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up scenes for deCONZ component."""
scenes = hass.data[DATA_DECONZ].scenes scenes = hass.data[DATA_DECONZ].scenes
entities = [] entities = []

View File

@ -31,8 +31,13 @@ DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
async def async_setup(hass, config): async def async_setup(hass, config):
"""Track states and offer events for sensors.""" """Track states and offer events for sensors."""
component = EntityComponent( component = hass.data[DOMAIN] = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL) _LOGGER, DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config) await component.async_setup(config)
return True return True
async def async_setup_entry(hass, entry):
"""Setup a config entry."""
return await hass.data[DOMAIN].async_setup_entry(entry)

View File

@ -22,10 +22,12 @@ ATTR_EVENT_ID = 'event_id'
async def async_setup_platform(hass, config, async_add_devices, async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None): discovery_info=None):
"""Set up the deCONZ sensors.""" """Old way of setting up deCONZ sensors."""
if discovery_info is None: pass
return
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the deCONZ sensors."""
from pydeconz.sensor import DECONZ_SENSOR, SWITCH as DECONZ_REMOTE from pydeconz.sensor import DECONZ_SENSOR, SWITCH as DECONZ_REMOTE
sensors = hass.data[DATA_DECONZ].sensors sensors = hass.data[DATA_DECONZ].sensors
entities = [] entities = []

View File

@ -0,0 +1,55 @@
"""deCONZ binary sensor platform tests."""
from unittest.mock import Mock, patch
from homeassistant import config_entries
from homeassistant.components import deconz
from tests.common import mock_coro
SENSOR = {
"1": {
"id": "Sensor 1 id",
"name": "Sensor 1 name",
"type": "ZHAPresence",
"state": {"presence": False},
"config": {}
}
}
async def setup_bridge(hass, data):
"""Load the deCONZ binary sensor 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_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, 'binary_sensor')
# To flush out the service call to update the group
await hass.async_block_till_done()
async def test_no_binary_sensors(hass):
"""Test the update_lights function with some lights."""
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_binary_sensors(hass):
"""Test the update_lights function with some lights."""
data = {"sensors": SENSOR}
await setup_bridge(hass, data)
assert "binary_sensor.sensor_1_name" in hass.data[deconz.DATA_DECONZ_ID]
assert len(hass.states.async_all()) == 1

View File

@ -1,9 +1,11 @@
"""Test deCONZ component setup process.""" """Test deCONZ component setup process."""
from unittest.mock import patch from unittest.mock import Mock, patch
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.components import deconz from homeassistant.components import deconz
from tests.common import mock_coro
async def test_config_with_host_passed_to_config_entry(hass): async def test_config_with_host_passed_to_config_entry(hass):
"""Test that configured options for a host are loaded via config entry.""" """Test that configured options for a host are loaded via config entry."""
@ -67,3 +69,41 @@ async def test_config_discovery(hass):
assert await async_setup_component(hass, deconz.DOMAIN, {}) is True assert await async_setup_component(hass, deconz.DOMAIN, {}) is True
# No flow started # No flow started
assert len(mock_config_entries.flow.mock_calls) == 0 assert len(mock_config_entries.flow.mock_calls) == 0
async def test_setup_entry_already_registered_bridge(hass):
"""Test setup entry doesn't allow more than one instance of deCONZ."""
hass.data[deconz.DOMAIN] = True
assert await deconz.async_setup_entry(hass, {}) is False
async def test_setup_entry_no_available_bridge(hass):
"""Test setup entry fails if deCONZ is not available."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
with patch('pydeconz.DeconzSession.async_load_parameters',
return_value=mock_coro(False)):
assert await deconz.async_setup_entry(hass, entry) is False
async def test_setup_entry_successful(hass):
"""Test setup entry is successful."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
with patch.object(hass, 'async_add_job') as mock_add_job, \
patch.object(hass, 'config_entries') as mock_config_entries, \
patch('pydeconz.DeconzSession.async_load_parameters',
return_value=mock_coro(True)):
assert await deconz.async_setup_entry(hass, entry) is True
assert hass.data[deconz.DOMAIN]
assert hass.data[deconz.DATA_DECONZ_ID] == {}
assert len(mock_add_job.mock_calls) == 4
assert len(mock_config_entries.async_forward_entry_setup.mock_calls) == 4
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] == \
(entry, 'light')
assert mock_config_entries.async_forward_entry_setup.mock_calls[2][1] == \
(entry, 'scene')
assert mock_config_entries.async_forward_entry_setup.mock_calls[3][1] == \
(entry, 'sensor')

View File

@ -0,0 +1,74 @@
"""deCONZ light platform tests."""
from unittest.mock import Mock, patch
from homeassistant import config_entries
from homeassistant.components import deconz
from tests.common import mock_coro
LIGHT = {
"1": {
"id": "Light 1 id",
"name": "Light 1 name",
"state": {}
}
}
GROUP = {
"1": {
"id": "Group 1 id",
"name": "Group 1 name",
"state": {},
"action": {},
"scenes": [],
"lights": [
"1",
"2"
]
},
"2": {
"id": "Group 2 id",
"name": "Group 2 name",
"state": {},
"action": {},
"scenes": []
},
}
async def setup_bridge(hass, data):
"""Load the deCONZ light 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_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, 'light')
# To flush out the service call to update the group
await hass.async_block_till_done()
async def test_no_lights_or_groups(hass):
"""Test the update_lights function with some lights."""
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_lights_and_groups(hass):
"""Test the update_lights function with some lights."""
await setup_bridge(hass, {"lights": LIGHT, "groups": GROUP})
assert "light.light_1_name" in hass.data[deconz.DATA_DECONZ_ID]
assert "light.group_1_name" in hass.data[deconz.DATA_DECONZ_ID]
assert "light.group_2_name" not in hass.data[deconz.DATA_DECONZ_ID]
assert len(hass.states.async_all()) == 3

View File

@ -0,0 +1,57 @@
"""deCONZ scenes platform tests."""
from unittest.mock import Mock, patch
from homeassistant import config_entries
from homeassistant.components import deconz
from tests.common import mock_coro
GROUP = {
"1": {
"id": "Group 1 id",
"name": "Group 1 name",
"state": {},
"action": {},
"scenes": [{
"id": "1",
"name": "Scene 1"
}],
}
}
async def setup_bridge(hass, data):
"""Load the deCONZ scene 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_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, 'scene')
# To flush out the service call to update the group
await hass.async_block_till_done()
async def test_no_scenes(hass):
"""Test the update_lights function with some lights."""
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_scenes(hass):
"""Test the update_lights function with some lights."""
data = {"groups": GROUP}
await setup_bridge(hass, data)
assert "scene.group_1_name_scene_1" in hass.data[deconz.DATA_DECONZ_ID]
assert len(hass.states.async_all()) == 1

View File

@ -0,0 +1,82 @@
"""deCONZ sensor platform tests."""
from unittest.mock import Mock, patch
from homeassistant import config_entries
from homeassistant.components import deconz
from tests.common import mock_coro
SENSOR = {
"1": {
"id": "Sensor 1 id",
"name": "Sensor 1 name",
"type": "ZHATemperature",
"state": {"temperature": False},
"config": {}
},
"2": {
"id": "Sensor 2 id",
"name": "Sensor 2 name",
"type": "ZHAPresence",
"state": {"presence": False},
"config": {}
},
"3": {
"id": "Sensor 3 id",
"name": "Sensor 3 name",
"type": "ZHASwitch",
"state": {"buttonevent": 1000},
"config": {}
},
"4": {
"id": "Sensor 4 id",
"name": "Sensor 4 name",
"type": "ZHASwitch",
"state": {"buttonevent": 1000},
"config": {"battery": 100}
}
}
async def setup_bridge(hass, data):
"""Load the deCONZ sensor 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_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, 'sensor')
# To flush out the service call to update the group
await hass.async_block_till_done()
async def test_no_sensors(hass):
"""Test the update_lights function with some lights."""
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_binary_sensors(hass):
"""Test the update_lights function with some lights."""
data = {"sensors": SENSOR}
await setup_bridge(hass, data)
assert "sensor.sensor_1_name" in hass.data[deconz.DATA_DECONZ_ID]
assert "sensor.sensor_2_name" not in hass.data[deconz.DATA_DECONZ_ID]
assert "sensor.sensor_3_name" not in hass.data[deconz.DATA_DECONZ_ID]
assert "sensor.sensor_3_name_battery_level" not in \
hass.data[deconz.DATA_DECONZ_ID]
assert "sensor.sensor_4_name" not in hass.data[deconz.DATA_DECONZ_ID]
assert "sensor.sensor_4_name_battery_level" in \
hass.data[deconz.DATA_DECONZ_ID]
assert len(hass.states.async_all()) == 2