deCONZ - retry if setup fails (#17772)
* Make component retry if setup fails * Improve overall test coveragepull/18117/head
parent
145677ed75
commit
a9140dc8f5
|
@ -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
|
||||
|
||||
|
|
|
@ -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)},
|
||||
|
|
|
@ -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)},
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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)},
|
||||
|
|
|
@ -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."""
|
||||
|
|
|
@ -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)},
|
||||
|
|
|
@ -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)},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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=[])
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue