deCONZ - retry if setup fails (#17772)

* Make component retry if setup fails
* Improve overall test coverage
pull/18117/head
Robert Svensson 2018-10-31 22:38:04 +01:00 committed by GitHub
parent 145677ed75
commit a9140dc8f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 823 additions and 475 deletions

View File

@ -76,9 +76,6 @@ omit =
homeassistant/components/daikin.py
homeassistant/components/*/daikin.py
homeassistant/components/deconz/*
homeassistant/components/*/deconz.py
homeassistant/components/digital_ocean.py
homeassistant/components/*/digital_ocean.py

View File

@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.deconz/
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.deconz.const import (
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ,
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DECONZ_DOMAIN)
DECONZ_DOMAIN)
from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.core import callback
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
@ -36,10 +36,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities.append(DeconzBinarySensor(sensor))
async_add_entities(entities, True)
hass.data[DATA_DECONZ_UNSUB].append(
hass.data[DATA_DECONZ].listeners.append(
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor))
async_add_sensor(hass.data[DATA_DECONZ].sensors.values())
async_add_sensor(hass.data[DATA_DECONZ].api.sensors.values())
class DeconzBinarySensor(BinarySensorDevice):
@ -52,7 +52,8 @@ class DeconzBinarySensor(BinarySensorDevice):
async def async_added_to_hass(self):
"""Subscribe sensors events."""
self._sensor.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \
self._sensor.deconz_id
async def async_will_remove_from_hass(self) -> None:
"""Disconnect sensor object when removed."""
@ -127,7 +128,7 @@ class DeconzBinarySensor(BinarySensorDevice):
self._sensor.uniqueid.count(':') != 7):
return None
serial = self._sensor.uniqueid.split('-', 1)[0]
bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid
bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid
return {
'connections': {(CONNECTION_ZIGBEE, serial)},
'identifiers': {(DECONZ_DOMAIN, serial)},

View File

@ -5,8 +5,7 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.deconz/
"""
from homeassistant.components.deconz.const import (
COVER_TYPES, DAMPERS, DOMAIN as DATA_DECONZ, DATA_DECONZ_ID,
DATA_DECONZ_UNSUB, DECONZ_DOMAIN, WINDOW_COVERS)
COVER_TYPES, DAMPERS, DOMAIN as DATA_DECONZ, DECONZ_DOMAIN, WINDOW_COVERS)
from homeassistant.components.cover import (
ATTR_POSITION, CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP,
SUPPORT_SET_POSITION)
@ -42,10 +41,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities.append(DeconzCover(light))
async_add_entities(entities, True)
hass.data[DATA_DECONZ_UNSUB].append(
hass.data[DATA_DECONZ].listeners.append(
async_dispatcher_connect(hass, 'deconz_new_light', async_add_cover))
async_add_cover(hass.data[DATA_DECONZ].lights.values())
async_add_cover(hass.data[DATA_DECONZ].api.lights.values())
class DeconzCover(CoverDevice):
@ -62,7 +61,8 @@ class DeconzCover(CoverDevice):
async def async_added_to_hass(self):
"""Subscribe to covers events."""
self._cover.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._cover.deconz_id
self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \
self._cover.deconz_id
async def async_will_remove_from_hass(self) -> None:
"""Disconnect cover object when removed."""
@ -103,7 +103,6 @@ class DeconzCover(CoverDevice):
return 'damper'
if self._cover.type in WINDOW_COVERS:
return 'window'
return None
@property
def supported_features(self):
@ -151,7 +150,7 @@ class DeconzCover(CoverDevice):
self._cover.uniqueid.count(':') != 7):
return None
serial = self._cover.uniqueid.split('-', 1)[0]
bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid
bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid
return {
'connections': {(CONNECTION_ZIGBEE, serial)},
'identifiers': {(DECONZ_DOMAIN, serial)},

View File

@ -8,21 +8,15 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import (
CONF_API_KEY, CONF_EVENT, CONF_HOST,
CONF_ID, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import EventOrigin, callback
from homeassistant.helpers import aiohttp_client, config_validation as cv
CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
from homeassistant.util import slugify
from homeassistant.util.json import load_json
# Loading the config flow file will register the flow
from .config_flow import configured_hosts
from .const import (
CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
from .const import CONFIG_FILE, DOMAIN, _LOGGER
from .gateway import DeconzGateway
REQUIREMENTS = ['pydeconz==47']
@ -80,61 +74,26 @@ async def async_setup_entry(hass, config_entry):
Load config, group, light and sensor data for server information.
Start websocket for push notification of state changes from deCONZ.
"""
from pydeconz import DeconzSession
if DOMAIN in hass.data:
_LOGGER.error(
"Config entry failed since one deCONZ instance already exists")
return False
@callback
def async_add_device_callback(device_type, device):
"""Handle event of new device creation in deCONZ."""
if not isinstance(device, list):
device = [device]
async_dispatcher_send(
hass, 'deconz_new_{}'.format(device_type), device)
gateway = DeconzGateway(hass, config_entry)
session = aiohttp_client.async_get_clientsession(hass)
deconz = DeconzSession(hass.loop, session, **config_entry.data,
async_add_device=async_add_device_callback)
result = await deconz.async_load_parameters()
hass.data[DOMAIN] = gateway
if result is False:
if not await gateway.async_setup():
return False
hass.data[DOMAIN] = deconz
hass.data[DATA_DECONZ_ID] = {}
hass.data[DATA_DECONZ_EVENT] = []
hass.data[DATA_DECONZ_UNSUB] = []
for component in SUPPORTED_PLATFORMS:
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
config_entry, component))
@callback
def async_add_remote(sensors):
"""Set up remote from deCONZ."""
from pydeconz.sensor import SWITCH as DECONZ_REMOTE
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
if sensor.type in DECONZ_REMOTE and \
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
hass.data[DATA_DECONZ_EVENT].append(DeconzEvent(hass, sensor))
hass.data[DATA_DECONZ_UNSUB].append(
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_remote))
async_add_remote(deconz.sensors.values())
deconz.start()
device_registry = await \
hass.helpers.device_registry.async_get_registry()
device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(CONNECTION_NETWORK_MAC, deconz.config.mac)},
identifiers={(DOMAIN, deconz.config.bridgeid)},
manufacturer='Dresden Elektronik', model=deconz.config.modelid,
name=deconz.config.name, sw_version=deconz.config.swversion)
connections={(CONNECTION_NETWORK_MAC, gateway.api.config.mac)},
identifiers={(DOMAIN, gateway.api.config.bridgeid)},
manufacturer='Dresden Elektronik', model=gateway.api.config.modelid,
name=gateway.api.config.name, sw_version=gateway.api.config.swversion)
async def async_configure(call):
"""Set attribute of device in deCONZ.
@ -155,121 +114,66 @@ async def async_setup_entry(hass, config_entry):
field = call.data.get(SERVICE_FIELD, '')
entity_id = call.data.get(SERVICE_ENTITY)
data = call.data.get(SERVICE_DATA)
deconz = hass.data[DOMAIN]
gateway = hass.data[DOMAIN]
if entity_id:
try:
field = hass.data[DATA_DECONZ_ID][entity_id] + field
field = gateway.deconz_ids[entity_id] + field
except KeyError:
_LOGGER.error('Could not find the entity %s', entity_id)
return
await deconz.async_put_state(field, data)
await gateway.api.async_put_state(field, data)
hass.services.async_register(
DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA)
async def async_refresh_devices(call):
"""Refresh available devices from deCONZ."""
deconz = hass.data[DOMAIN]
gateway = hass.data[DOMAIN]
groups = list(deconz.groups.keys())
lights = list(deconz.lights.keys())
scenes = list(deconz.scenes.keys())
sensors = list(deconz.sensors.keys())
groups = set(gateway.api.groups.keys())
lights = set(gateway.api.lights.keys())
scenes = set(gateway.api.scenes.keys())
sensors = set(gateway.api.sensors.keys())
if not await deconz.async_load_parameters():
if not await gateway.api.async_load_parameters():
return
async_add_device_callback(
gateway.async_add_device_callback(
'group', [group
for group_id, group in deconz.groups.items()
for group_id, group in gateway.api.groups.items()
if group_id not in groups]
)
async_add_device_callback(
gateway.async_add_device_callback(
'light', [light
for light_id, light in deconz.lights.items()
for light_id, light in gateway.api.lights.items()
if light_id not in lights]
)
async_add_device_callback(
gateway.async_add_device_callback(
'scene', [scene
for scene_id, scene in deconz.scenes.items()
for scene_id, scene in gateway.api.scenes.items()
if scene_id not in scenes]
)
async_add_device_callback(
gateway.async_add_device_callback(
'sensor', [sensor
for sensor_id, sensor in deconz.sensors.items()
for sensor_id, sensor in gateway.api.sensors.items()
if sensor_id not in sensors]
)
hass.services.async_register(
DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices)
@callback
def deconz_shutdown(event):
"""
Wrap the call to deconz.close.
Used as an argument to EventBus.async_listen_once - EventBus calls
this method with the event as the first argument, which should not
be passed on to deconz.close.
"""
deconz.close()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, deconz_shutdown)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown)
return True
async def async_unload_entry(hass, config_entry):
"""Unload deCONZ config entry."""
deconz = hass.data.pop(DOMAIN)
gateway = hass.data.pop(DOMAIN)
hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
deconz.close()
for component in SUPPORTED_PLATFORMS:
await hass.config_entries.async_forward_entry_unload(
config_entry, component)
dispatchers = hass.data[DATA_DECONZ_UNSUB]
for unsub_dispatcher in dispatchers:
unsub_dispatcher()
hass.data[DATA_DECONZ_UNSUB] = []
for event in hass.data[DATA_DECONZ_EVENT]:
event.async_will_remove_from_hass()
hass.data[DATA_DECONZ_EVENT].remove(event)
hass.data[DATA_DECONZ_ID] = []
return True
class DeconzEvent:
"""When you want signals instead of entities.
Stateless sensors such as remotes are expected to generate an event
instead of a sensor entity in hass.
"""
def __init__(self, hass, device):
"""Register callback that will be used for signals."""
self._hass = hass
self._device = device
self._device.register_async_callback(self.async_update_callback)
self._event = 'deconz_{}'.format(CONF_EVENT)
self._id = slugify(self._device.name)
@callback
def async_will_remove_from_hass(self) -> None:
"""Disconnect event object when removed."""
self._device.remove_callback(self.async_update_callback)
self._device = None
@callback
def async_update_callback(self, reason):
"""Fire the event if reason is that state is updated."""
if reason['state']:
data = {CONF_ID: self._id, CONF_EVENT: self._device.state}
self._hass.bus.async_fire(self._event, data, EventOrigin.remote)
hass.services.async_remove(DOMAIN, SERVICE_DEVICE_REFRESH)
return await gateway.async_reset()

View File

@ -35,10 +35,6 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
self.deconz_config = {}
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
return await self.async_step_init(user_input)
async def async_step_init(self, user_input=None):
"""Handle a deCONZ config flow start.
Only allows one instance to be set up.
@ -67,7 +63,7 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
for bridge in self.bridges:
hosts.append(bridge[CONF_HOST])
return self.async_show_form(
step_id='init',
step_id='user',
data_schema=vol.Schema({
vol.Required(CONF_HOST): vol.In(hosts)
})

View File

@ -13,6 +13,9 @@ DECONZ_DOMAIN = 'deconz'
CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor'
CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups'
SUPPORTED_PLATFORMS = ['binary_sensor', 'cover',
'light', 'scene', 'sensor', 'switch']
ATTR_DARK = 'dark'
ATTR_ON = 'on'

View File

@ -0,0 +1,165 @@
"""Representation of a deCONZ gateway."""
from homeassistant import config_entries
from homeassistant.const import CONF_EVENT, CONF_ID
from homeassistant.core import EventOrigin, callback
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send)
from homeassistant.util import slugify
from .const import (
_LOGGER, CONF_ALLOW_CLIP_SENSOR, SUPPORTED_PLATFORMS)
class DeconzGateway:
"""Manages a single deCONZ gateway."""
def __init__(self, hass, config_entry):
"""Initialize the system."""
self.hass = hass
self.config_entry = config_entry
self.api = None
self._cancel_retry_setup = None
self.deconz_ids = {}
self.events = []
self.listeners = []
async def async_setup(self, tries=0):
"""Set up a deCONZ gateway."""
hass = self.hass
self.api = await get_gateway(
hass, self.config_entry.data, self.async_add_device_callback
)
if self.api is False:
retry_delay = 2 ** (tries + 1)
_LOGGER.error(
"Error connecting to deCONZ gateway. Retrying in %d seconds",
retry_delay)
async def retry_setup(_now):
"""Retry setup."""
if await self.async_setup(tries + 1):
# This feels hacky, we should find a better way to do this
self.config_entry.state = config_entries.ENTRY_STATE_LOADED
self._cancel_retry_setup = hass.helpers.event.async_call_later(
retry_delay, retry_setup)
return False
for component in SUPPORTED_PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(
self.config_entry, component))
self.listeners.append(
async_dispatcher_connect(
hass, 'deconz_new_sensor', self.async_add_remote))
self.async_add_remote(self.api.sensors.values())
self.api.start()
return True
@callback
def async_add_device_callback(self, device_type, device):
"""Handle event of new device creation in deCONZ."""
if not isinstance(device, list):
device = [device]
async_dispatcher_send(
self.hass, 'deconz_new_{}'.format(device_type), device)
@callback
def async_add_remote(self, sensors):
"""Set up remote from deCONZ."""
from pydeconz.sensor import SWITCH as DECONZ_REMOTE
allow_clip_sensor = self.config_entry.data.get(
CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
if sensor.type in DECONZ_REMOTE and \
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
self.events.append(DeconzEvent(self.hass, sensor))
@callback
def shutdown(self, event):
"""Wrap the call to deconz.close.
Used as an argument to EventBus.async_listen_once.
"""
self.api.close()
async def async_reset(self):
"""Reset this gateway to default state.
Will cancel any scheduled setup retry and will unload
the config entry.
"""
# If we have a retry scheduled, we were never setup.
if self._cancel_retry_setup is not None:
self._cancel_retry_setup()
self._cancel_retry_setup = None
return True
self.api.close()
for component in SUPPORTED_PLATFORMS:
await self.hass.config_entries.async_forward_entry_unload(
self.config_entry, component)
for unsub_dispatcher in self.listeners:
unsub_dispatcher()
self.listeners = []
for event in self.events:
event.async_will_remove_from_hass()
self.events.remove(event)
self.deconz_ids = {}
return True
async def get_gateway(hass, config, async_add_device_callback):
"""Create a gateway object and verify configuration."""
from pydeconz import DeconzSession
session = aiohttp_client.async_get_clientsession(hass)
deconz = DeconzSession(hass.loop, session, **config,
async_add_device=async_add_device_callback)
result = await deconz.async_load_parameters()
if result:
return deconz
return result
class DeconzEvent:
"""When you want signals instead of entities.
Stateless sensors such as remotes are expected to generate an event
instead of a sensor entity in hass.
"""
def __init__(self, hass, device):
"""Register callback that will be used for signals."""
self._hass = hass
self._device = device
self._device.register_async_callback(self.async_update_callback)
self._event = 'deconz_{}'.format(CONF_EVENT)
self._id = slugify(self._device.name)
@callback
def async_will_remove_from_hass(self) -> None:
"""Disconnect event object when removed."""
self._device.remove_callback(self.async_update_callback)
self._device = None
@callback
def async_update_callback(self, reason):
"""Fire the event if reason is that state is updated."""
if reason['state']:
data = {CONF_ID: self._id, CONF_EVENT: self._device.state}
self._hass.bus.async_fire(self._event, data, EventOrigin.remote)

View File

@ -5,8 +5,7 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/light.deconz/
"""
from homeassistant.components.deconz.const import (
CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DATA_DECONZ,
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DECONZ_DOMAIN,
CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DATA_DECONZ, DECONZ_DOMAIN,
COVER_TYPES, SWITCH_TYPES)
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR,
@ -38,7 +37,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities.append(DeconzLight(light))
async_add_entities(entities, True)
hass.data[DATA_DECONZ_UNSUB].append(
hass.data[DATA_DECONZ].listeners.append(
async_dispatcher_connect(hass, 'deconz_new_light', async_add_light))
@callback
@ -51,11 +50,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities.append(DeconzLight(group))
async_add_entities(entities, True)
hass.data[DATA_DECONZ_UNSUB].append(
hass.data[DATA_DECONZ].listeners.append(
async_dispatcher_connect(hass, 'deconz_new_group', async_add_group))
async_add_light(hass.data[DATA_DECONZ].lights.values())
async_add_group(hass.data[DATA_DECONZ].groups.values())
async_add_light(hass.data[DATA_DECONZ].api.lights.values())
async_add_group(hass.data[DATA_DECONZ].api.groups.values())
class DeconzLight(Light):
@ -81,7 +80,8 @@ class DeconzLight(Light):
async def async_added_to_hass(self):
"""Subscribe to lights events."""
self._light.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._light.deconz_id
self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \
self._light.deconz_id
async def async_will_remove_from_hass(self) -> None:
"""Disconnect light object when removed."""
@ -214,7 +214,7 @@ class DeconzLight(Light):
self._light.uniqueid.count(':') != 7):
return None
serial = self._light.uniqueid.split('-', 1)[0]
bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid
bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid
return {
'connections': {(CONNECTION_ZIGBEE, serial)},
'identifiers': {(DECONZ_DOMAIN, serial)},

View File

@ -4,8 +4,7 @@ Support for deCONZ scenes.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/scene.deconz/
"""
from homeassistant.components.deconz import (
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB)
from homeassistant.components.deconz import DOMAIN as DATA_DECONZ
from homeassistant.components.scene import Scene
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@ -28,10 +27,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
for scene in scenes:
entities.append(DeconzScene(scene))
async_add_entities(entities)
hass.data[DATA_DECONZ_UNSUB].append(
hass.data[DATA_DECONZ].listeners.append(
async_dispatcher_connect(hass, 'deconz_new_scene', async_add_scene))
async_add_scene(hass.data[DATA_DECONZ].scenes.values())
async_add_scene(hass.data[DATA_DECONZ].api.scenes.values())
class DeconzScene(Scene):
@ -43,7 +42,8 @@ class DeconzScene(Scene):
async def async_added_to_hass(self):
"""Subscribe to sensors events."""
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._scene.deconz_id
self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \
self._scene.deconz_id
async def async_will_remove_from_hass(self) -> None:
"""Disconnect scene object when removed."""

View File

@ -6,7 +6,7 @@ https://home-assistant.io/components/sensor.deconz/
"""
from homeassistant.components.deconz.const import (
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ,
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DECONZ_DOMAIN)
DECONZ_DOMAIN)
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_VOLTAGE, DEVICE_CLASS_BATTERY)
from homeassistant.core import callback
@ -46,10 +46,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities.append(DeconzSensor(sensor))
async_add_entities(entities, True)
hass.data[DATA_DECONZ_UNSUB].append(
hass.data[DATA_DECONZ].listeners.append(
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor))
async_add_sensor(hass.data[DATA_DECONZ].sensors.values())
async_add_sensor(hass.data[DATA_DECONZ].api.sensors.values())
class DeconzSensor(Entity):
@ -62,7 +62,8 @@ class DeconzSensor(Entity):
async def async_added_to_hass(self):
"""Subscribe to sensors events."""
self._sensor.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \
self._sensor.deconz_id
async def async_will_remove_from_hass(self) -> None:
"""Disconnect sensor object when removed."""
@ -147,7 +148,7 @@ class DeconzSensor(Entity):
self._sensor.uniqueid.count(':') != 7):
return None
serial = self._sensor.uniqueid.split('-', 1)[0]
bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid
bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid
return {
'connections': {(CONNECTION_ZIGBEE, serial)},
'identifiers': {(DECONZ_DOMAIN, serial)},
@ -171,7 +172,8 @@ class DeconzBattery(Entity):
async def async_added_to_hass(self):
"""Subscribe to sensors events."""
self._sensor.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \
self._sensor.deconz_id
async def async_will_remove_from_hass(self) -> None:
"""Disconnect sensor object when removed."""
@ -181,7 +183,7 @@ class DeconzBattery(Entity):
@callback
def async_update_callback(self, reason):
"""Update the battery's state, if needed."""
if 'battery' in reason['attr']:
if 'reachable' in reason['attr'] or 'battery' in reason['attr']:
self.async_schedule_update_ha_state()
@property
@ -229,7 +231,7 @@ class DeconzBattery(Entity):
self._sensor.uniqueid.count(':') != 7):
return None
serial = self._sensor.uniqueid.split('-', 1)[0]
bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid
bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid
return {
'connections': {(CONNECTION_ZIGBEE, serial)},
'identifiers': {(DECONZ_DOMAIN, serial)},

View File

@ -5,8 +5,7 @@ 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,
DECONZ_DOMAIN, POWER_PLUGS, SIRENS)
DOMAIN as DATA_DECONZ, DECONZ_DOMAIN, POWER_PLUGS, SIRENS)
from homeassistant.components.switch import SwitchDevice
from homeassistant.core import callback
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
@ -37,10 +36,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities.append(DeconzSiren(light))
async_add_entities(entities, True)
hass.data[DATA_DECONZ_UNSUB].append(
hass.data[DATA_DECONZ].listeners.append(
async_dispatcher_connect(hass, 'deconz_new_light', async_add_switch))
async_add_switch(hass.data[DATA_DECONZ].lights.values())
async_add_switch(hass.data[DATA_DECONZ].api.lights.values())
class DeconzSwitch(SwitchDevice):
@ -53,7 +52,8 @@ class DeconzSwitch(SwitchDevice):
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
self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \
self._switch.deconz_id
async def async_will_remove_from_hass(self) -> None:
"""Disconnect switch object when removed."""
@ -92,7 +92,7 @@ class DeconzSwitch(SwitchDevice):
self._switch.uniqueid.count(':') != 7):
return None
serial = self._switch.uniqueid.split('-', 1)[0]
bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid
bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid
return {
'connections': {(CONNECTION_ZIGBEE, serial)},
'identifiers': {(DECONZ_DOMAIN, serial)},

View File

@ -4,6 +4,9 @@ from unittest.mock import Mock, patch
from homeassistant import config_entries
from homeassistant.components import deconz
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.setup import async_setup_component
import homeassistant.components.binary_sensor as binary_sensor
from tests.common import mock_coro
@ -14,7 +17,8 @@ SENSOR = {
"name": "Sensor 1 name",
"type": "ZHAPresence",
"state": {"presence": False},
"config": {}
"config": {},
"uniqueid": "00:00:00:00:00:00:00:00-00"
},
"2": {
"id": "Sensor 2 id",
@ -26,70 +30,105 @@ SENSOR = {
}
async def setup_bridge(hass, data, allow_clip_sensor=True):
ENTRY_CONFIG = {
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
deconz.config_flow.CONF_API_KEY: "ABCDEF",
deconz.config_flow.CONF_BRIDGEID: "0123456789",
deconz.config_flow.CONF_HOST: "1.2.3.4",
deconz.config_flow.CONF_PORT: 80
}
async def setup_gateway(hass, data, allow_clip_sensor=True):
"""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)
bridge.config = Mock()
ENTRY_CONFIG[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor
config_entry = config_entries.ConfigEntry(
1, deconz.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
config_entries.CONN_CLASS_LOCAL_PUSH)
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
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', 'allow_clip_sensor': allow_clip_sensor}, 'test',
config_entries.CONN_CLASS_LOCAL_PUSH)
await gateway.api.async_load_parameters()
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_platform_manually_configured(hass):
"""Test that we do not discover anything or try to set up a gateway."""
assert await async_setup_component(hass, binary_sensor.DOMAIN, {
'binary_sensor': {
'platform': deconz.DOMAIN
}
}) is True
assert deconz.DOMAIN not in hass.data
async def test_no_binary_sensors(hass):
"""Test that no sensors in deconz results in no sensor entities."""
data = {}
await setup_bridge(hass, data)
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
await setup_gateway(hass, data)
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
assert len(hass.states.async_all()) == 0
async def test_binary_sensors(hass):
"""Test successful creation of binary sensor entities."""
data = {"sensors": SENSOR}
await setup_bridge(hass, data)
assert "binary_sensor.sensor_1_name" in hass.data[deconz.DATA_DECONZ_ID]
await setup_gateway(hass, data)
assert "binary_sensor.sensor_1_name" in \
hass.data[deconz.DOMAIN].deconz_ids
assert "binary_sensor.sensor_2_name" not in \
hass.data[deconz.DATA_DECONZ_ID]
hass.data[deconz.DOMAIN].deconz_ids
assert len(hass.states.async_all()) == 1
hass.data[deconz.DOMAIN].api.sensors['1'].async_update(
{'state': {'on': False}})
async def test_add_new_sensor(hass):
"""Test successful creation of sensor entities."""
data = {}
await setup_bridge(hass, data)
await setup_gateway(hass, data)
sensor = Mock()
sensor.name = 'name'
sensor.type = 'ZHAPresence'
sensor.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
await hass.async_block_till_done()
assert "binary_sensor.name" in hass.data[deconz.DATA_DECONZ_ID]
assert "binary_sensor.name" in hass.data[deconz.DOMAIN].deconz_ids
async def test_do_not_allow_clip_sensor(hass):
"""Test that clip sensors can be ignored."""
data = {}
await setup_bridge(hass, data, allow_clip_sensor=False)
await setup_gateway(hass, data, allow_clip_sensor=False)
sensor = Mock()
sensor.name = 'name'
sensor.type = 'CLIPPresence'
sensor.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
await hass.async_block_till_done()
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
async def test_unload_switch(hass):
"""Test that it works to unload switch entities."""
data = {"sensors": SENSOR}
await setup_gateway(hass, data)
await hass.data[deconz.DOMAIN].async_reset()
assert len(hass.states.async_all()) == 0

View File

@ -5,6 +5,9 @@ from homeassistant import config_entries
from homeassistant.components import deconz
from homeassistant.components.deconz.const import COVER_TYPES
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.setup import async_setup_component
import homeassistant.components.cover as cover
from tests.common import mock_coro
@ -13,14 +16,15 @@ SUPPORTED_COVERS = {
"id": "Cover 1 id",
"name": "Cover 1 name",
"type": "Level controllable output",
"state": {},
"modelid": "Not zigbee spec"
"state": {"bri": 255, "reachable": True},
"modelid": "Not zigbee spec",
"uniqueid": "00:00:00:00:00:00:00:00-00"
},
"2": {
"id": "Cover 2 id",
"name": "Cover 2 name",
"type": "Window covering device",
"state": {},
"state": {"bri": 255, "reachable": True},
"modelid": "lumi.curtain"
}
}
@ -35,58 +39,109 @@ UNSUPPORTED_COVER = {
}
async def setup_bridge(hass, data):
ENTRY_CONFIG = {
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
deconz.config_flow.CONF_API_KEY: "ABCDEF",
deconz.config_flow.CONF_BRIDGEID: "0123456789",
deconz.config_flow.CONF_HOST: "1.2.3.4",
deconz.config_flow.CONF_PORT: 80
}
async def setup_gateway(hass, data):
"""Load the deCONZ cover 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)
config_entry = config_entries.ConfigEntry(
1, deconz.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
config_entries.CONN_CLASS_LOCAL_PUSH)
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
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',
config_entries.CONN_CLASS_LOCAL_PUSH)
await gateway.api.async_load_parameters()
await hass.config_entries.async_forward_entry_setup(config_entry, 'cover')
# To flush out the service call to update the group
await hass.async_block_till_done()
async def test_no_switches(hass):
async def test_platform_manually_configured(hass):
"""Test that we do not discover anything or try to set up a gateway."""
assert await async_setup_component(hass, cover.DOMAIN, {
'cover': {
'platform': deconz.DOMAIN
}
}) is True
assert deconz.DOMAIN not in hass.data
async def test_no_covers(hass):
"""Test that no cover entities are created."""
data = {}
await setup_bridge(hass, data)
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
assert len(hass.states.async_all()) == 0
async def test_cover(hass):
"""Test that all supported cover entities are created."""
await setup_bridge(hass, {"lights": SUPPORTED_COVERS})
assert "cover.cover_1_name" in hass.data[deconz.DATA_DECONZ_ID]
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
await setup_gateway(hass, {"lights": SUPPORTED_COVERS})
assert "cover.cover_1_name" in hass.data[deconz.DOMAIN].deconz_ids
assert len(SUPPORTED_COVERS) == len(COVER_TYPES)
assert len(hass.states.async_all()) == 3
cover_1 = hass.states.get('cover.cover_1_name')
assert cover_1 is not None
assert cover_1.state == 'closed'
hass.data[deconz.DOMAIN].api.lights['1'].async_update({})
await hass.services.async_call('cover', 'open_cover', {
'entity_id': 'cover.cover_1_name'
}, blocking=True)
await hass.services.async_call('cover', 'close_cover', {
'entity_id': 'cover.cover_1_name'
}, blocking=True)
await hass.services.async_call('cover', 'stop_cover', {
'entity_id': 'cover.cover_1_name'
}, blocking=True)
await hass.services.async_call('cover', 'close_cover', {
'entity_id': 'cover.cover_2_name'
}, blocking=True)
async def test_add_new_cover(hass):
"""Test successful creation of cover entity."""
data = {}
await setup_bridge(hass, data)
await setup_gateway(hass, data)
cover = Mock()
cover.name = 'name'
cover.type = "Level controllable output"
cover.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_light', [cover])
await hass.async_block_till_done()
assert "cover.name" in hass.data[deconz.DATA_DECONZ_ID]
assert "cover.name" in hass.data[deconz.DOMAIN].deconz_ids
async def test_unsupported_cover(hass):
"""Test that unsupported covers are not created."""
await setup_bridge(hass, {"lights": UNSUPPORTED_COVER})
await setup_gateway(hass, {"lights": UNSUPPORTED_COVER})
assert len(hass.states.async_all()) == 0
async def test_unload_cover(hass):
"""Test that it works to unload switch entities."""
await setup_gateway(hass, {"lights": SUPPORTED_COVERS})
await hass.data[deconz.DOMAIN].async_reset()
assert len(hass.states.async_all()) == 1

View File

@ -20,7 +20,7 @@ async def test_flow_works(hass, aioclient_mock):
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
await flow.async_step_init()
await flow.async_step_user()
await flow.async_step_link(user_input={})
result = await flow.async_step_options(
user_input={'allow_clip_sensor': True, 'allow_deconz_groups': True})
@ -45,7 +45,7 @@ async def test_flow_already_registered_bridge(hass):
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
result = await flow.async_step_init()
result = await flow.async_step_user()
assert result['type'] == 'abort'
@ -55,7 +55,7 @@ async def test_flow_no_discovered_bridges(hass, aioclient_mock):
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
result = await flow.async_step_init()
result = await flow.async_step_user()
assert result['type'] == 'abort'
@ -67,7 +67,7 @@ async def test_flow_one_bridge_discovered(hass, aioclient_mock):
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
result = await flow.async_step_init()
result = await flow.async_step_user()
assert result['type'] == 'form'
assert result['step_id'] == 'link'
@ -81,9 +81,9 @@ async def test_flow_two_bridges_discovered(hass, aioclient_mock):
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
result = await flow.async_step_init()
result = await flow.async_step_user()
assert result['type'] == 'form'
assert result['step_id'] == 'init'
assert result['step_id'] == 'user'
with pytest.raises(vol.Invalid):
assert result['data_schema']({'host': '0.0.0.0'})
@ -92,6 +92,21 @@ async def test_flow_two_bridges_discovered(hass, aioclient_mock):
result['data_schema']({'host': '5.6.7.8'})
async def test_flow_two_bridges_selection(hass, aioclient_mock):
"""Test config flow selection of one of two bridges."""
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
flow.bridges = [
{'bridgeid': 'id1', 'host': '1.2.3.4', 'port': 80},
{'bridgeid': 'id2', 'host': '5.6.7.8', 'port': 80}
]
result = await flow.async_step_user(user_input={'host': '1.2.3.4'})
assert result['type'] == 'form'
assert result['step_id'] == 'link'
assert flow.deconz_config['host'] == '1.2.3.4'
async def test_link_no_api_key(hass, aioclient_mock):
"""Test config flow should abort if no API key was possible to retrieve."""
aioclient_mock.post('http://1.2.3.4:80/api', json=[])

View File

@ -1,11 +1,11 @@
"""Test deCONZ component setup process."""
from unittest.mock import Mock, patch
from homeassistant.components import deconz
from homeassistant.components.deconz import DATA_DECONZ_ID
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.setup import async_setup_component
from tests.common import mock_coro
from homeassistant.components import deconz
from tests.common import mock_coro, MockConfigEntry
CONFIG = {
"config": {
@ -99,173 +99,113 @@ async def test_setup_entry_no_available_bridge(hass):
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_create_task') as mock_add_job, \
patch.object(hass, 'config_entries') as mock_config_entries, \
patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(CONFIG)), \
patch('pydeconz.DeconzSession.start', return_value=True), \
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
})
entry.add_to_hass(hass)
mock_registry = Mock()
with patch.object(deconz, 'DeconzGateway') as mock_gateway, \
patch('homeassistant.helpers.device_registry.async_get_registry',
return_value=mock_coro(Mock())):
return_value=mock_coro(mock_registry)):
mock_gateway.return_value.async_setup.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(hass.data[deconz.DATA_DECONZ_UNSUB]) == 1
assert len(mock_add_job.mock_calls) == \
len(deconz.SUPPORTED_PLATFORMS)
assert len(mock_config_entries.async_forward_entry_setup.mock_calls) == \
len(deconz.SUPPORTED_PLATFORMS)
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, 'cover')
assert mock_config_entries.async_forward_entry_setup.mock_calls[2][1] == \
(entry, 'light')
assert mock_config_entries.async_forward_entry_setup.mock_calls[3][1] == \
(entry, 'scene')
assert mock_config_entries.async_forward_entry_setup.mock_calls[4][1] == \
(entry, 'sensor')
assert mock_config_entries.async_forward_entry_setup.mock_calls[5][1] == \
(entry, 'switch')
async def test_unload_entry(hass):
"""Test being able to unload an entry."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
entry.async_unload.return_value = mock_coro(True)
deconzmock = Mock()
deconzmock.async_load_parameters.return_value = mock_coro(True)
deconzmock.sensors = {}
with patch('pydeconz.DeconzSession', return_value=deconzmock):
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
})
entry.add_to_hass(hass)
mock_registry = Mock()
with patch.object(deconz, 'DeconzGateway') as mock_gateway, \
patch('homeassistant.helpers.device_registry.async_get_registry',
return_value=mock_coro(mock_registry)):
mock_gateway.return_value.async_setup.return_value = mock_coro(True)
assert await deconz.async_setup_entry(hass, entry) is True
assert deconz.DATA_DECONZ_EVENT in hass.data
hass.data[deconz.DATA_DECONZ_EVENT].append(Mock())
hass.data[deconz.DATA_DECONZ_ID] = {'id': 'deconzid'}
mock_gateway.return_value.async_reset.return_value = mock_coro(True)
assert await deconz.async_unload_entry(hass, entry)
assert deconz.DOMAIN not in hass.data
assert len(hass.data[deconz.DATA_DECONZ_UNSUB]) == 0
assert len(hass.data[deconz.DATA_DECONZ_EVENT]) == 0
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
async def test_add_new_device(hass):
"""Test adding a new device generates a signal for platforms."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80,
'api_key': '1234567890ABCDEF', 'allow_clip_sensor': False}
new_event = {
"t": "event",
"e": "added",
"r": "sensors",
"id": "1",
"sensor": {
"config": {
"on": "True",
"reachable": "True"
},
"name": "event",
"state": {},
"type": "ZHASwitch"
}
}
with patch.object(deconz, 'async_dispatcher_send') as mock_dispatch_send, \
patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(CONFIG)), \
patch('pydeconz.DeconzSession.start', return_value=True):
assert await deconz.async_setup_entry(hass, entry) is True
hass.data[deconz.DOMAIN].async_event_handler(new_event)
await hass.async_block_till_done()
assert len(mock_dispatch_send.mock_calls) == 1
assert len(mock_dispatch_send.mock_calls[0]) == 3
async def test_add_new_remote(hass):
"""Test new added device creates a new remote."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80,
'api_key': '1234567890ABCDEF', 'allow_clip_sensor': False}
remote = Mock()
remote.name = 'name'
remote.type = 'ZHASwitch'
remote.register_async_callback = Mock()
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(CONFIG)), \
patch('pydeconz.DeconzSession.start', return_value=True):
assert await deconz.async_setup_entry(hass, entry) is True
async_dispatcher_send(hass, 'deconz_new_sensor', [remote])
await hass.async_block_till_done()
assert len(hass.data[deconz.DATA_DECONZ_EVENT]) == 1
async def test_do_not_allow_clip_sensor(hass):
"""Test that clip sensors can be ignored."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80,
'api_key': '1234567890ABCDEF', 'allow_clip_sensor': False}
remote = Mock()
remote.name = 'name'
remote.type = 'CLIPSwitch'
remote.register_async_callback = Mock()
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(CONFIG)), \
patch('pydeconz.DeconzSession.start', return_value=True):
assert await deconz.async_setup_entry(hass, entry) is True
async_dispatcher_send(hass, 'deconz_new_sensor', [remote])
await hass.async_block_till_done()
assert len(hass.data[deconz.DATA_DECONZ_EVENT]) == 0
async def test_service_configure(hass):
"""Test that service invokes pydeconz with the correct path and data."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(CONFIG)), \
patch('pydeconz.DeconzSession.start', return_value=True), \
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
})
entry.add_to_hass(hass)
mock_registry = Mock()
with patch.object(deconz, 'DeconzGateway') as mock_gateway, \
patch('homeassistant.helpers.device_registry.async_get_registry',
return_value=mock_coro(Mock())):
return_value=mock_coro(mock_registry)):
mock_gateway.return_value.async_setup.return_value = mock_coro(True)
assert await deconz.async_setup_entry(hass, entry) is True
hass.data[DATA_DECONZ_ID] = {
hass.data[deconz.DOMAIN].deconz_ids = {
'light.test': '/light/1'
}
data = {'on': True, 'attr1': 10, 'attr2': 20}
# only field
with patch('pydeconz.DeconzSession.async_put_state') as async_put_state:
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
await hass.services.async_call('deconz', 'configure', service_data={
'field': '/light/42', 'data': data
})
await hass.async_block_till_done()
async_put_state.assert_called_with('/light/42', data)
# only entity
with patch('pydeconz.DeconzSession.async_put_state') as async_put_state:
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
await hass.services.async_call('deconz', 'configure', service_data={
'entity': 'light.test', 'data': data
})
await hass.async_block_till_done()
async_put_state.assert_called_with('/light/1', data)
# entity + field
with patch('pydeconz.DeconzSession.async_put_state') as async_put_state:
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
await hass.services.async_call('deconz', 'configure', service_data={
'entity': 'light.test', 'field': '/state', 'data': data})
await hass.async_block_till_done()
async_put_state.assert_called_with('/light/1/state', data)
# non-existing entity (or not from deCONZ)
with patch('pydeconz.DeconzSession.async_put_state') as async_put_state:
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
await hass.services.async_call('deconz', 'configure', service_data={
'entity': 'light.nonexisting', 'field': '/state', 'data': data})
await hass.async_block_till_done()
async_put_state.assert_not_called()
# field does not start with /
with patch('pydeconz.DeconzSession.async_put_state') as async_put_state:
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
await hass.services.async_call('deconz', 'configure', service_data={
'entity': 'light.test', 'field': 'state', 'data': data})
await hass.async_block_till_done()
async_put_state.assert_not_called()
async def test_service_refresh_devices(hass):
"""Test that service can refresh devices."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
})
entry.add_to_hass(hass)
mock_registry = Mock()
with patch.object(deconz, 'DeconzGateway') as mock_gateway, \
patch('homeassistant.helpers.device_registry.async_get_registry',
return_value=mock_coro(mock_registry)):
mock_gateway.return_value.async_setup.return_value = mock_coro(True)
assert await deconz.async_setup_entry(hass, entry) is True
with patch.object(hass.data[deconz.DOMAIN].api, 'async_load_parameters',
return_value=mock_coro(True)):
await hass.services.async_call(
'deconz', 'device_refresh', service_data={})
await hass.async_block_till_done()
with patch.object(hass.data[deconz.DOMAIN].api, 'async_load_parameters',
return_value=mock_coro(False)):
await hass.services.async_call(
'deconz', 'device_refresh', service_data={})
await hass.async_block_till_done()

View File

@ -4,6 +4,9 @@ from unittest.mock import Mock, patch
from homeassistant import config_entries
from homeassistant.components import deconz
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.setup import async_setup_component
import homeassistant.components.light as light
from tests.common import mock_coro
@ -12,7 +15,18 @@ LIGHT = {
"1": {
"id": "Light 1 id",
"name": "Light 1 name",
"state": {}
"state": {
"on": True, "bri": 255, "colormode": "xy", "xy": (500, 500),
"reachable": True
},
"uniqueid": "00:00:00:00:00:00:00:00-00"
},
"2": {
"id": "Light 2 id",
"name": "Light 2 name",
"state": {
"on": True, "colormode": "ct", "ct": 2500, "reachable": True
}
}
}
@ -20,6 +34,7 @@ GROUP = {
"1": {
"id": "Group 1 id",
"name": "Group 1 name",
"type": "LightGroup",
"state": {},
"action": {},
"scenes": [],
@ -47,85 +62,152 @@ SWITCH = {
}
async def setup_bridge(hass, data, allow_deconz_groups=True):
ENTRY_CONFIG = {
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
deconz.config_flow.CONF_API_KEY: "ABCDEF",
deconz.config_flow.CONF_BRIDGEID: "0123456789",
deconz.config_flow.CONF_HOST: "1.2.3.4",
deconz.config_flow.CONF_PORT: 80
}
async def setup_gateway(hass, data, allow_deconz_groups=True):
"""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)
bridge.config = Mock()
ENTRY_CONFIG[deconz.const.CONF_ALLOW_DECONZ_GROUPS] = allow_deconz_groups
config_entry = config_entries.ConfigEntry(
1, deconz.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
config_entries.CONN_CLASS_LOCAL_PUSH)
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
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', 'allow_deconz_groups': allow_deconz_groups},
'test', config_entries.CONN_CLASS_LOCAL_PUSH)
await gateway.api.async_load_parameters()
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_platform_manually_configured(hass):
"""Test that we do not discover anything or try to set up a gateway."""
assert await async_setup_component(hass, light.DOMAIN, {
'light': {
'platform': deconz.DOMAIN
}
}) is True
assert deconz.DOMAIN not in hass.data
async def test_no_lights_or_groups(hass):
"""Test that no lights or groups entities are created."""
data = {}
await setup_bridge(hass, data)
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
assert len(hass.states.async_all()) == 0
async def test_lights_and_groups(hass):
"""Test that lights or groups entities are created."""
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
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
await setup_gateway(hass, {"lights": LIGHT, "groups": GROUP})
assert "light.light_1_name" in hass.data[deconz.DOMAIN].deconz_ids
assert "light.light_2_name" in hass.data[deconz.DOMAIN].deconz_ids
assert "light.group_1_name" in hass.data[deconz.DOMAIN].deconz_ids
assert "light.group_2_name" not in hass.data[deconz.DOMAIN].deconz_ids
assert len(hass.states.async_all()) == 4
lamp_1 = hass.states.get('light.light_1_name')
assert lamp_1 is not None
assert lamp_1.state == 'on'
assert lamp_1.attributes['brightness'] == 255
assert lamp_1.attributes['hs_color'] == (224.235, 100.0)
light_2 = hass.states.get('light.light_2_name')
assert light_2 is not None
assert light_2.state == 'on'
assert light_2.attributes['color_temp'] == 2500
hass.data[deconz.DOMAIN].api.lights['1'].async_update({})
await hass.services.async_call('light', 'turn_on', {
'entity_id': 'light.light_1_name',
'color_temp': 2500,
'brightness': 200,
'transition': 5,
'flash': 'short',
'effect': 'colorloop'
}, blocking=True)
await hass.services.async_call('light', 'turn_on', {
'entity_id': 'light.light_1_name',
'hs_color': (20, 30),
'flash': 'long',
'effect': 'None'
}, blocking=True)
await hass.services.async_call('light', 'turn_off', {
'entity_id': 'light.light_1_name',
'transition': 5,
'flash': 'short'
}, blocking=True)
await hass.services.async_call('light', 'turn_off', {
'entity_id': 'light.light_1_name',
'flash': 'long'
}, blocking=True)
async def test_add_new_light(hass):
"""Test successful creation of light entities."""
data = {}
await setup_bridge(hass, data)
await setup_gateway(hass, {})
light = Mock()
light.name = 'name'
light.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_light', [light])
await hass.async_block_till_done()
assert "light.name" in hass.data[deconz.DATA_DECONZ_ID]
assert "light.name" in hass.data[deconz.DOMAIN].deconz_ids
async def test_add_new_group(hass):
"""Test successful creation of group entities."""
data = {}
await setup_bridge(hass, data)
await setup_gateway(hass, {})
group = Mock()
group.name = 'name'
group.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_group', [group])
await hass.async_block_till_done()
assert "light.name" in hass.data[deconz.DATA_DECONZ_ID]
assert "light.name" in hass.data[deconz.DOMAIN].deconz_ids
async def test_do_not_add_deconz_groups(hass):
"""Test that clip sensors can be ignored."""
data = {}
await setup_bridge(hass, data, allow_deconz_groups=False)
await setup_gateway(hass, {}, allow_deconz_groups=False)
group = Mock()
group.name = 'name'
group.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_group', [group])
await hass.async_block_till_done()
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 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
await setup_gateway(hass, {"lights": SWITCH})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
assert len(hass.states.async_all()) == 0
async def test_unload_light(hass):
"""Test that it works to unload switch entities."""
await setup_gateway(hass, {"lights": LIGHT, "groups": GROUP})
await hass.data[deconz.DOMAIN].async_reset()
# Group.all_lights will not be removed
assert len(hass.states.async_all()) == 1

View File

@ -1,8 +1,11 @@
"""deCONZ scenes platform tests."""
"""deCONZ scene platform tests."""
from unittest.mock import Mock, patch
from homeassistant import config_entries
from homeassistant.components import deconz
from homeassistant.setup import async_setup_component
import homeassistant.components.scene as scene
from tests.common import mock_coro
@ -21,39 +24,73 @@ GROUP = {
}
async def setup_bridge(hass, data):
ENTRY_CONFIG = {
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
deconz.config_flow.CONF_API_KEY: "ABCDEF",
deconz.config_flow.CONF_BRIDGEID: "0123456789",
deconz.config_flow.CONF_HOST: "1.2.3.4",
deconz.config_flow.CONF_PORT: 80
}
async def setup_gateway(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)
config_entry = config_entries.ConfigEntry(
1, deconz.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
config_entries.CONN_CLASS_LOCAL_PUSH)
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
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',
config_entries.CONN_CLASS_LOCAL_PUSH)
await gateway.api.async_load_parameters()
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_platform_manually_configured(hass):
"""Test that we do not discover anything or try to set up a gateway."""
assert await async_setup_component(hass, scene.DOMAIN, {
'scene': {
'platform': deconz.DOMAIN
}
}) is True
assert deconz.DOMAIN not in hass.data
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
"""Test that scenes can be loaded without scenes being available."""
await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 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]
"""Test that scenes works."""
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
await setup_gateway(hass, {"groups": GROUP})
assert "scene.group_1_name_scene_1" in hass.data[deconz.DOMAIN].deconz_ids
assert len(hass.states.async_all()) == 1
await hass.services.async_call('scene', 'turn_on', {
'entity_id': 'scene.group_1_name_scene_1'
}, blocking=True)
async def test_unload_scene(hass):
"""Test that it works to unload scene entities."""
await setup_gateway(hass, {"groups": GROUP})
await hass.data[deconz.DOMAIN].async_reset()
assert len(hass.states.async_all()) == 0

View File

@ -1,10 +1,12 @@
"""deCONZ sensor platform tests."""
from unittest.mock import Mock, patch
from homeassistant import config_entries
from homeassistant.components import deconz
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.setup import async_setup_component
import homeassistant.components.sensor as sensor
from tests.common import mock_coro
@ -13,9 +15,10 @@ SENSOR = {
"1": {
"id": "Sensor 1 id",
"name": "Sensor 1 name",
"type": "ZHATemperature",
"state": {"temperature": False},
"config": {}
"type": "ZHALightLevel",
"state": {"lightlevel": 30000, "dark": False},
"config": {"reachable": True},
"uniqueid": "00:00:00:00:00:00:00:00-00"
},
"2": {
"id": "Sensor 2 id",
@ -36,80 +39,134 @@ SENSOR = {
"name": "Sensor 4 name",
"type": "ZHASwitch",
"state": {"buttonevent": 1000},
"config": {"battery": 100}
"config": {"battery": 100},
"uniqueid": "00:00:00:00:00:00:00:01-00"
},
"5": {
"id": "Sensor 5 id",
"name": "Sensor 5 name",
"type": "ZHASwitch",
"state": {"buttonevent": 1000},
"config": {"battery": 100},
"uniqueid": "00:00:00:00:00:00:00:02:00-00"
},
"6": {
"id": "Sensor 6 id",
"name": "Sensor 6 name",
"type": "Daylight",
"state": {"daylight": True},
"config": {}
},
"7": {
"id": "Sensor 7 id",
"name": "Sensor 7 name",
"type": "ZHAPower",
"state": {"current": 2, "power": 6, "voltage": 3},
"config": {"reachable": True}
}
}
async def setup_bridge(hass, data, allow_clip_sensor=True):
ENTRY_CONFIG = {
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
deconz.config_flow.CONF_API_KEY: "ABCDEF",
deconz.config_flow.CONF_BRIDGEID: "0123456789",
deconz.config_flow.CONF_HOST: "1.2.3.4",
deconz.config_flow.CONF_PORT: 80
}
async def setup_gateway(hass, data, allow_clip_sensor=True):
"""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)
bridge.config = Mock()
ENTRY_CONFIG[deconz.const.CONF_ALLOW_CLIP_SENSOR] = allow_clip_sensor
config_entry = config_entries.ConfigEntry(
1, deconz.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
config_entries.CONN_CLASS_LOCAL_PUSH)
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
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_EVENT] = []
hass.data[deconz.DATA_DECONZ_ID] = {}
config_entry = config_entries.ConfigEntry(
1, deconz.DOMAIN, 'Mock Title',
{'host': 'mock-host', 'allow_clip_sensor': allow_clip_sensor}, 'test',
config_entries.CONN_CLASS_LOCAL_PUSH)
await hass.config_entries.async_forward_entry_setup(config_entry, 'sensor')
await gateway.api.async_load_parameters()
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_platform_manually_configured(hass):
"""Test that we do not discover anything or try to set up a gateway."""
assert await async_setup_component(hass, sensor.DOMAIN, {
'sensor': {
'platform': deconz.DOMAIN
}
}) is True
assert deconz.DOMAIN not in hass.data
async def test_no_sensors(hass):
"""Test that no sensors in deconz results in no sensor entities."""
data = {}
await setup_bridge(hass, data)
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
assert len(hass.states.async_all()) == 0
async def test_sensors(hass):
"""Test successful creation of sensor entities."""
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]
await setup_gateway(hass, {"sensors": SENSOR})
assert "sensor.sensor_1_name" in hass.data[deconz.DOMAIN].deconz_ids
assert "sensor.sensor_2_name" not in hass.data[deconz.DOMAIN].deconz_ids
assert "sensor.sensor_3_name" not in hass.data[deconz.DOMAIN].deconz_ids
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]
hass.data[deconz.DOMAIN].deconz_ids
assert "sensor.sensor_4_name" not in hass.data[deconz.DOMAIN].deconz_ids
assert "sensor.sensor_4_name_battery_level" in \
hass.data[deconz.DATA_DECONZ_ID]
assert len(hass.states.async_all()) == 2
hass.data[deconz.DOMAIN].deconz_ids
assert len(hass.states.async_all()) == 5
hass.data[deconz.DOMAIN].api.sensors['1'].async_update(
{'state': {'on': False}})
hass.data[deconz.DOMAIN].api.sensors['4'].async_update(
{'config': {'battery': 75}})
async def test_add_new_sensor(hass):
"""Test successful creation of sensor entities."""
data = {}
await setup_bridge(hass, data)
await setup_gateway(hass, {})
sensor = Mock()
sensor.name = 'name'
sensor.type = 'ZHATemperature'
sensor.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
await hass.async_block_till_done()
assert "sensor.name" in hass.data[deconz.DATA_DECONZ_ID]
assert "sensor.name" in hass.data[deconz.DOMAIN].deconz_ids
async def test_do_not_allow_clipsensor(hass):
"""Test that clip sensors can be ignored."""
data = {}
await setup_bridge(hass, data, allow_clip_sensor=False)
await setup_gateway(hass, {}, allow_clip_sensor=False)
sensor = Mock()
sensor.name = 'name'
sensor.type = 'CLIPTemperature'
sensor.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
await hass.async_block_till_done()
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
async def test_unload_sensor(hass):
"""Test that it works to unload sensor entities."""
await setup_gateway(hass, {"sensors": SENSOR})
await hass.data[deconz.DOMAIN].async_reset()
assert len(hass.states.async_all()) == 0

View File

@ -5,6 +5,9 @@ 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 homeassistant.setup import async_setup_component
import homeassistant.components.switch as switch
from tests.common import mock_coro
@ -13,19 +16,20 @@ SUPPORTED_SWITCHES = {
"id": "Switch 1 id",
"name": "Switch 1 name",
"type": "On/Off plug-in unit",
"state": {}
"state": {"on": True, "reachable": True},
"uniqueid": "00:00:00:00:00:00:00:00-00"
},
"2": {
"id": "Switch 2 id",
"name": "Switch 2 name",
"type": "Smart plug",
"state": {}
"state": {"on": True, "reachable": True}
},
"3": {
"id": "Switch 3 id",
"name": "Switch 3 name",
"type": "Warning device",
"state": {}
"state": {"alert": "lselect", "reachable": True}
}
}
@ -39,61 +43,113 @@ UNSUPPORTED_SWITCH = {
}
async def setup_bridge(hass, data):
ENTRY_CONFIG = {
deconz.const.CONF_ALLOW_CLIP_SENSOR: True,
deconz.const.CONF_ALLOW_DECONZ_GROUPS: True,
deconz.config_flow.CONF_API_KEY: "ABCDEF",
deconz.config_flow.CONF_BRIDGEID: "0123456789",
deconz.config_flow.CONF_HOST: "1.2.3.4",
deconz.config_flow.CONF_PORT: 80
}
async def setup_gateway(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)
bridge.config = Mock()
config_entry = config_entries.ConfigEntry(
1, deconz.DOMAIN, 'Mock Title', ENTRY_CONFIG, 'test',
config_entries.CONN_CLASS_LOCAL_PUSH)
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
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',
config_entries.CONN_CLASS_LOCAL_PUSH)
await gateway.api.async_load_parameters()
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_platform_manually_configured(hass):
"""Test that we do not discover anything or try to set up a gateway."""
assert await async_setup_component(hass, switch.DOMAIN, {
'switch': {
'platform': deconz.DOMAIN
}
}) is True
assert deconz.DOMAIN not in hass.data
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
await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
assert len(hass.states.async_all()) == 0
async def test_switch(hass):
async def test_switches(hass):
"""Test that all supported switch entities 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 "switch.switch_3_name" in hass.data[deconz.DATA_DECONZ_ID]
with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)):
await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES})
assert "switch.switch_1_name" in hass.data[deconz.DOMAIN].deconz_ids
assert "switch.switch_2_name" in hass.data[deconz.DOMAIN].deconz_ids
assert "switch.switch_3_name" in hass.data[deconz.DOMAIN].deconz_ids
assert len(SUPPORTED_SWITCHES) == len(SWITCH_TYPES)
assert len(hass.states.async_all()) == 4
switch_1 = hass.states.get('switch.switch_1_name')
assert switch_1 is not None
assert switch_1.state == 'on'
switch_3 = hass.states.get('switch.switch_3_name')
assert switch_3 is not None
assert switch_3.state == 'on'
hass.data[deconz.DOMAIN].api.lights['1'].async_update({})
await hass.services.async_call('switch', 'turn_on', {
'entity_id': 'switch.switch_1_name'
}, blocking=True)
await hass.services.async_call('switch', 'turn_off', {
'entity_id': 'switch.switch_1_name'
}, blocking=True)
await hass.services.async_call('switch', 'turn_on', {
'entity_id': 'switch.switch_3_name'
}, blocking=True)
await hass.services.async_call('switch', 'turn_off', {
'entity_id': 'switch.switch_3_name'
}, blocking=True)
async def test_add_new_switch(hass):
"""Test successful creation of switch entity."""
data = {}
await setup_bridge(hass, data)
await setup_gateway(hass, {})
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]
assert "switch.name" in hass.data[deconz.DOMAIN].deconz_ids
async def test_unsupported_switch(hass):
"""Test that unsupported switches are not created."""
await setup_bridge(hass, {"lights": UNSUPPORTED_SWITCH})
await setup_gateway(hass, {"lights": UNSUPPORTED_SWITCH})
assert len(hass.states.async_all()) == 0
async def test_unload_switch(hass):
"""Test that it works to unload switch entities."""
await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES})
await hass.data[deconz.DOMAIN].async_reset()
assert len(hass.states.async_all()) == 1