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):
"""Track states and offer events for binary sensors."""
component = EntityComponent(
component = hass.data[DOMAIN] = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config)
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
class BinarySensorDevice(Entity):
"""Represent a binary sensor."""

View File

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

View File

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

View File

@ -19,10 +19,12 @@ DEPENDENCIES = ['deconz']
async def async_setup_platform(hass, config, async_add_devices,
discovery_info=None):
"""Set up the deCONZ light."""
if discovery_info is None:
return
"""Old way of setting up deCONZ lights."""
pass
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
groups = hass.data[DATA_DECONZ].groups
entities = []

View File

@ -71,7 +71,7 @@ def activate(hass, entity_id=None):
async def async_setup(hass, config):
"""Set up the scenes."""
logger = logging.getLogger(__name__)
component = EntityComponent(logger, DOMAIN, hass)
component = hass.data[DOMAIN] = EntityComponent(logger, DOMAIN, hass)
await component.async_setup(config)
@ -90,6 +90,11 @@ async def async_setup(hass, config):
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):
"""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,
discovery_info=None):
"""Set up scenes for deCONZ component."""
if discovery_info is None:
return
"""Old way of setting up deCONZ scenes."""
pass
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up scenes for deCONZ component."""
scenes = hass.data[DATA_DECONZ].scenes
entities = []

View File

@ -31,8 +31,13 @@ DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
async def async_setup(hass, config):
"""Track states and offer events for sensors."""
component = EntityComponent(
component = hass.data[DOMAIN] = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
await component.async_setup(config)
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,
discovery_info=None):
"""Set up the deCONZ sensors."""
if discovery_info is None:
return
"""Old way of setting up deCONZ sensors."""
pass
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
sensors = hass.data[DATA_DECONZ].sensors
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."""
from unittest.mock import patch
from unittest.mock import Mock, patch
from homeassistant.setup import async_setup_component
from homeassistant.components import deconz
from tests.common import mock_coro
async def test_config_with_host_passed_to_config_entry(hass):
"""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
# No flow started
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