Support multiple deCONZ gateways (#22449)

* Store gateways inside a dict in deconz domain

* Make reachable events gateway specific

* Gateway shall always exist

* Adapt new device signalling to support multiple gateways

* Services follow gateway master

* Working on unload entry

* Make unload and master handover work
Improve tests for init

* Fix config flow

* Fix linting

* Clean up init tests

* Clean up hassio discovery to fit with the rest

* Store gateways inside a dict in deconz domain

* Make reachable events gateway specific

* Gateway shall always exist

* Adapt new device signalling to support multiple gateways

* Services follow gateway master

* Working on unload entry

* Make unload and master handover work
Improve tests for init

* Fix config flow

* Fix linting

* Clean up init tests

* Clean up hassio discovery to fit with the rest

* Add support for services to specify bridgeid
pull/22751/head
Robert Svensson 2019-04-05 02:48:24 +02:00 committed by Jason Hu
parent b9ec623ad9
commit b50afec5f1
22 changed files with 535 additions and 426 deletions

View File

@ -4,12 +4,14 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import (
CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
# Loading the config flow file will register the flow
from .config_flow import configured_hosts
from .const import DEFAULT_PORT, DOMAIN, _LOGGER
from .config_flow import get_master_gateway
from .const import (
CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID,
CONF_MASTER_GATEWAY, DEFAULT_PORT, DOMAIN, _LOGGER)
from .gateway import DeconzGateway
REQUIREMENTS = ['pydeconz==54']
@ -32,26 +34,27 @@ SERVICE_SCHEMA = vol.All(vol.Schema({
vol.Optional(SERVICE_ENTITY): cv.entity_id,
vol.Optional(SERVICE_FIELD): cv.matches_regex('/.*'),
vol.Required(SERVICE_DATA): dict,
vol.Optional(CONF_BRIDGEID): str
}), cv.has_at_least_one_key(SERVICE_ENTITY, SERVICE_FIELD))
SERVICE_DEVICE_REFRESH = 'device_refresh'
SERVICE_DEVICE_REFRESCH_SCHEMA = vol.All(vol.Schema({
vol.Optional(CONF_BRIDGEID): str
}))
async def async_setup(hass, config):
"""Load configuration for deCONZ component.
Discovery has loaded the component if DOMAIN is not present in config.
"""
if DOMAIN in config:
deconz_config = None
if CONF_HOST in config[DOMAIN]:
deconz_config = config[DOMAIN]
if deconz_config and not configured_hosts(hass):
hass.async_add_job(hass.config_entries.flow.async_init(
DOMAIN,
context={'source': config_entries.SOURCE_IMPORT},
data=deconz_config
))
if not hass.config_entries.async_entries(DOMAIN) and DOMAIN in config:
deconz_config = config[DOMAIN]
hass.async_add_job(hass.config_entries.flow.async_init(
DOMAIN, context={'source': config_entries.SOURCE_IMPORT},
data=deconz_config
))
return True
@ -61,26 +64,20 @@ 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.
"""
if DOMAIN in hass.data:
_LOGGER.error(
"Config entry failed since one deCONZ instance already exists")
return False
if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}
if not config_entry.options:
await async_populate_options(hass, config_entry)
gateway = DeconzGateway(hass, config_entry)
if not await gateway.async_setup():
return False
hass.data[DOMAIN] = gateway
hass.data[DOMAIN][gateway.bridgeid] = gateway
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, 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)
await gateway.async_update_device_registry()
async def async_configure(call):
"""Set attribute of device in deCONZ.
@ -100,8 +97,11 @@ 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)
gateway = hass.data[DOMAIN]
data = call.data[SERVICE_DATA]
gateway = get_master_gateway(hass)
if CONF_BRIDGEID in call.data:
gateway = hass.data[DOMAIN][call.data[CONF_BRIDGEID]]
if entity_id:
try:
@ -117,7 +117,9 @@ async def async_setup_entry(hass, config_entry):
async def async_refresh_devices(call):
"""Refresh available devices from deCONZ."""
gateway = hass.data[DOMAIN]
gateway = get_master_gateway(hass)
if CONF_BRIDGEID in call.data:
gateway = hass.data[DOMAIN][call.data[CONF_BRIDGEID]]
groups = set(gateway.api.groups.keys())
lights = set(gateway.api.lights.keys())
@ -151,7 +153,8 @@ async def async_setup_entry(hass, config_entry):
)
hass.services.async_register(
DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices)
DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices,
schema=SERVICE_DEVICE_REFRESCH_SCHEMA)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown)
return True
@ -159,7 +162,34 @@ async def async_setup_entry(hass, config_entry):
async def async_unload_entry(hass, config_entry):
"""Unload deCONZ config entry."""
gateway = hass.data.pop(DOMAIN)
hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
hass.services.async_remove(DOMAIN, SERVICE_DEVICE_REFRESH)
gateway = hass.data[DOMAIN].pop(config_entry.data[CONF_BRIDGEID])
if not hass.data[DOMAIN]:
hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
hass.services.async_remove(DOMAIN, SERVICE_DEVICE_REFRESH)
elif gateway.master:
await async_populate_options(hass, config_entry)
new_master_gateway = next(iter(hass.data[DOMAIN].values()))
await async_populate_options(hass, new_master_gateway.config_entry)
return await gateway.async_reset()
@callback
async def async_populate_options(hass, config_entry):
"""Populate default options for gateway.
Called by setup_entry and unload_entry.
Makes sure there is always one master available.
"""
master = not get_master_gateway(hass)
options = {
CONF_MASTER_GATEWAY: master,
CONF_ALLOW_CLIP_SENSOR: config_entry.data.get(
CONF_ALLOW_CLIP_SENSOR, False),
CONF_ALLOW_DECONZ_GROUPS: config_entry.data.get(
CONF_ALLOW_DECONZ_GROUPS, True)
}
hass.config_entries.async_update_entry(config_entry, options=options)

View File

@ -4,10 +4,9 @@ from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import (
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DECONZ_DOMAIN,
NEW_SENSOR)
from .const import ATTR_DARK, ATTR_ON, NEW_SENSOR
from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz']
@ -24,22 +23,26 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the deCONZ binary sensor."""
gateway = hass.data[DECONZ_DOMAIN]
gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_sensor(sensors):
"""Add binary sensor from deCONZ."""
from pydeconz.sensor import DECONZ_BINARY_SENSOR
entities = []
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
if sensor.type in DECONZ_BINARY_SENSOR and \
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
not (not gateway.allow_clip_sensor and
sensor.type.startswith('CLIP')):
entities.append(DeconzBinarySensor(sensor, gateway))
async_add_entities(entities, True)
gateway.listeners.append(
async_dispatcher_connect(hass, NEW_SENSOR, async_add_sensor))
gateway.listeners.append(async_dispatcher_connect(
hass, gateway.async_event_new_device(NEW_SENSOR), async_add_sensor))
async_add_sensor(gateway.api.sensors.values())

View File

@ -7,10 +7,9 @@ from homeassistant.const import (
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import (
ATTR_OFFSET, ATTR_VALVE, CONF_ALLOW_CLIP_SENSOR,
DOMAIN as DECONZ_DOMAIN, NEW_SENSOR)
from .const import ATTR_OFFSET, ATTR_VALVE, NEW_SENSOR
from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz']
@ -20,22 +19,26 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
Thermostats are based on the same device class as sensors in deCONZ.
"""
gateway = hass.data[DECONZ_DOMAIN]
gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_climate(sensors):
"""Add climate devices from deCONZ."""
from pydeconz.sensor import THERMOSTAT
entities = []
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
if sensor.type in THERMOSTAT and \
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
not (not gateway.allow_clip_sensor and
sensor.type.startswith('CLIP')):
entities.append(DeconzThermostat(sensor, gateway))
async_add_entities(entities, True)
gateway.listeners.append(
async_dispatcher_connect(hass, NEW_SENSOR, async_add_climate))
gateway.listeners.append(async_dispatcher_connect(
hass, gateway.async_event_new_device(NEW_SENSOR), async_add_climate))
async_add_climate(gateway.api.sensors.values())

View File

@ -9,10 +9,7 @@ from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT
from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client
from .const import (
CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID,
DEFAULT_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_DECONZ_GROUPS, DEFAULT_PORT,
DOMAIN)
from .const import CONF_BRIDGEID, DEFAULT_PORT, DOMAIN
@callback
@ -22,6 +19,14 @@ def configured_hosts(hass):
in hass.config_entries.async_entries(DOMAIN))
@callback
def get_master_gateway(hass):
"""Return a bool telling if this is the master gateway."""
for gateway in hass.data[DOMAIN].values():
if gateway.master:
return gateway
@config_entries.HANDLERS.register(DOMAIN)
class DeconzFlowHandler(config_entries.ConfigFlow):
"""Handle a deCONZ config flow."""
@ -39,16 +44,12 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
async def async_step_user(self, user_input=None):
"""Handle a deCONZ config flow start.
Only allows one instance to be set up.
If only one bridge is found go to link step.
If more than one bridge is found let user choose bridge to link.
If no bridge is found allow user to manually input configuration.
"""
from pydeconz.utils import async_discovery
if configured_hosts(self.hass):
return self.async_abort(reason='one_instance_only')
if user_input is not None:
for bridge in self.bridges:
if bridge[CONF_HOST] == user_input[CONF_HOST]:
@ -99,9 +100,6 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
errors = {}
if user_input is not None:
if configured_hosts(self.hass):
return self.async_abort(reason='one_instance_only')
session = aiohttp_client.async_get_clientsession(self.hass)
try:
@ -114,51 +112,32 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
else:
self.deconz_config[CONF_API_KEY] = api_key
return await self.async_step_options()
return await self._create_entry()
return self.async_show_form(
step_id='link',
errors=errors,
)
async def async_step_options(self, user_input=None):
"""Extra options for deCONZ.
CONF_CLIP_SENSOR -- Allow user to choose if they want clip sensors.
CONF_DECONZ_GROUPS -- Allow user to choose if they want deCONZ groups.
"""
async def _create_entry(self):
"""Create entry for gateway."""
from pydeconz.utils import async_get_bridgeid
if user_input is not None:
self.deconz_config[CONF_ALLOW_CLIP_SENSOR] = \
user_input[CONF_ALLOW_CLIP_SENSOR]
self.deconz_config[CONF_ALLOW_DECONZ_GROUPS] = \
user_input[CONF_ALLOW_DECONZ_GROUPS]
if CONF_BRIDGEID not in self.deconz_config:
session = aiohttp_client.async_get_clientsession(self.hass)
if CONF_BRIDGEID not in self.deconz_config:
session = aiohttp_client.async_get_clientsession(self.hass)
try:
with async_timeout.timeout(10):
self.deconz_config[CONF_BRIDGEID] = \
await async_get_bridgeid(
session, **self.deconz_config)
try:
with async_timeout.timeout(10):
self.deconz_config[CONF_BRIDGEID] = \
await async_get_bridgeid(
session, **self.deconz_config)
except asyncio.TimeoutError:
return self.async_abort(reason='no_bridges')
except asyncio.TimeoutError:
return self.async_abort(reason='no_bridges')
return self.async_create_entry(
title='deCONZ-' + self.deconz_config[CONF_BRIDGEID],
data=self.deconz_config
)
return self.async_show_form(
step_id='options',
data_schema=vol.Schema({
vol.Optional(CONF_ALLOW_CLIP_SENSOR,
default=DEFAULT_ALLOW_CLIP_SENSOR): bool,
vol.Optional(CONF_ALLOW_DECONZ_GROUPS,
default=DEFAULT_ALLOW_DECONZ_GROUPS): bool,
}),
return self.async_create_entry(
title='deCONZ-' + self.deconz_config[CONF_BRIDGEID],
data=self.deconz_config
)
async def async_step_discovery(self, discovery_info):
@ -166,10 +145,14 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
This flow is triggered by the discovery component.
"""
deconz_config = {}
deconz_config[CONF_HOST] = discovery_info.get(CONF_HOST)
deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT)
deconz_config[CONF_BRIDGEID] = discovery_info.get('serial')
deconz_config = {
CONF_HOST: discovery_info[CONF_HOST],
CONF_PORT: discovery_info[CONF_PORT],
CONF_BRIDGEID: discovery_info['serial']
}
if deconz_config[CONF_HOST] in configured_hosts(self.hass):
return self.async_abort(reason='one_instance_only')
return await self.async_step_import(deconz_config)
@ -186,16 +169,11 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
Otherwise we will delegate to `link` step which
will ask user to link the bridge.
"""
if configured_hosts(self.hass):
return self.async_abort(reason='one_instance_only')
self.deconz_config = import_config
if CONF_API_KEY not in import_config:
return await self.async_step_link()
user_input = {CONF_ALLOW_CLIP_SENSOR: True,
CONF_ALLOW_DECONZ_GROUPS: True}
return await self.async_step_options(user_input=user_input)
return await self._create_entry()
async def async_step_hassio(self, user_input=None):
"""Prepare configuration for a Hass.io deCONZ bridge.
@ -212,29 +190,18 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
async def async_step_hassio_confirm(self, user_input=None):
"""Confirm a Hass.io discovery."""
if user_input is not None:
data = self._hassio_discovery
self.deconz_config = {
CONF_HOST: self._hassio_discovery[CONF_HOST],
CONF_PORT: self._hassio_discovery[CONF_PORT],
CONF_BRIDGEID: self._hassio_discovery['serial'],
CONF_API_KEY: self._hassio_discovery[CONF_API_KEY]
}
return self.async_create_entry(
title=data['addon'], data={
CONF_HOST: data[CONF_HOST],
CONF_PORT: data[CONF_PORT],
CONF_BRIDGEID: data['serial'],
CONF_API_KEY: data[CONF_API_KEY],
CONF_ALLOW_CLIP_SENSOR:
user_input[CONF_ALLOW_CLIP_SENSOR],
CONF_ALLOW_DECONZ_GROUPS:
user_input[CONF_ALLOW_DECONZ_GROUPS],
})
return await self._create_entry()
return self.async_show_form(
step_id='hassio_confirm',
description_placeholders={
'addon': self._hassio_discovery['addon']
},
data_schema=vol.Schema({
vol.Optional(CONF_ALLOW_CLIP_SENSOR,
default=DEFAULT_ALLOW_CLIP_SENSOR): bool,
vol.Optional(CONF_ALLOW_DECONZ_GROUPS,
default=DEFAULT_ALLOW_DECONZ_GROUPS): bool,
})
}
)

View File

@ -12,22 +12,21 @@ DEFAULT_ALLOW_DECONZ_GROUPS = False
CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor'
CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups'
CONF_BRIDGEID = 'bridgeid'
CONF_MASTER_GATEWAY = 'master'
SUPPORTED_PLATFORMS = ['binary_sensor', 'climate', 'cover',
'light', 'scene', 'sensor', 'switch']
DECONZ_REACHABLE = 'deconz_reachable'
NEW_GROUP = 'deconz_new_group'
NEW_LIGHT = 'deconz_new_light'
NEW_SCENE = 'deconz_new_scene'
NEW_SENSOR = 'deconz_new_sensor'
NEW_GROUP = 'group'
NEW_LIGHT = 'light'
NEW_SCENE = 'scene'
NEW_SENSOR = 'sensor'
NEW_DEVICE = {
'group': NEW_GROUP,
'light': NEW_LIGHT,
'scene': NEW_SCENE,
'sensor': NEW_SENSOR
NEW_GROUP: 'deconz_new_group_{}',
NEW_LIGHT: 'deconz_new_light_{}',
NEW_SCENE: 'deconz_new_scene_{}',
NEW_SENSOR: 'deconz_new_sensor_{}'
}
ATTR_DARK = 'dark'

View File

@ -5,9 +5,9 @@ from homeassistant.components.cover import (
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import (
COVER_TYPES, DAMPERS, DOMAIN as DECONZ_DOMAIN, NEW_LIGHT, WINDOW_COVERS)
from .const import COVER_TYPES, DAMPERS, NEW_LIGHT, WINDOW_COVERS
from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz']
@ -25,22 +25,26 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
Covers are based on same device class as lights in deCONZ.
"""
gateway = hass.data[DECONZ_DOMAIN]
gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_cover(lights):
"""Add cover from deCONZ."""
entities = []
for light in lights:
if light.type in COVER_TYPES:
if light.modelid in ZIGBEE_SPEC:
entities.append(DeconzCoverZigbeeSpec(light, gateway))
else:
entities.append(DeconzCover(light, gateway))
async_add_entities(entities, True)
gateway.listeners.append(
async_dispatcher_connect(hass, NEW_LIGHT, async_add_cover))
gateway.listeners.append(async_dispatcher_connect(
hass, gateway.async_event_new_device(NEW_LIGHT), async_add_cover))
async_add_cover(gateway.api.lights.values())

View File

@ -4,7 +4,7 @@ from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from .const import DECONZ_REACHABLE, DOMAIN as DECONZ_DOMAIN
from .const import DOMAIN as DECONZ_DOMAIN
class DeconzDevice(Entity):
@ -21,7 +21,8 @@ class DeconzDevice(Entity):
self._device.register_async_callback(self.async_update_callback)
self.gateway.deconz_ids[self.entity_id] = self._device.deconz_id
self.unsub_dispatcher = async_dispatcher_connect(
self.hass, DECONZ_REACHABLE, self.async_update_callback)
self.hass, self.gateway.event_reachable,
self.async_update_callback)
async def async_will_remove_from_hass(self) -> None:
"""Disconnect device object when removed."""

View File

@ -6,16 +6,23 @@ from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.const import CONF_EVENT, CONF_HOST, CONF_ID
from homeassistant.core import EventOrigin, callback
from homeassistant.helpers import aiohttp_client
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 .const import (
_LOGGER, DECONZ_REACHABLE, CONF_ALLOW_CLIP_SENSOR, NEW_DEVICE, NEW_SENSOR,
SUPPORTED_PLATFORMS)
_LOGGER, CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID,
CONF_MASTER_GATEWAY, DOMAIN, NEW_DEVICE, NEW_SENSOR, SUPPORTED_PLATFORMS)
from .errors import AuthenticationRequired, CannotConnect
@callback
def get_gateway_from_config_entry(hass, config_entry):
"""Return gateway with a matching bridge id."""
return hass.data[DOMAIN][config_entry.data[CONF_BRIDGEID]]
class DeconzGateway:
"""Manages a single deCONZ gateway."""
@ -30,6 +37,40 @@ class DeconzGateway:
self.events = []
self.listeners = []
@property
def bridgeid(self) -> str:
"""Return the unique identifier of the gateway."""
return self.config_entry.data[CONF_BRIDGEID]
@property
def master(self) -> bool:
"""Gateway which is used with deCONZ services without defining id."""
return self.config_entry.options[CONF_MASTER_GATEWAY]
@property
def allow_clip_sensor(self) -> bool:
"""Allow loading clip sensor from gateway."""
return self.config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
@property
def allow_deconz_groups(self) -> bool:
"""Allow loading deCONZ groups from gateway."""
return self.config_entry.data.get(CONF_ALLOW_DECONZ_GROUPS, True)
async def async_update_device_registry(self):
"""Update device registry."""
device_registry = await \
self.hass.helpers.device_registry.async_get_registry()
device_registry.async_get_or_create(
config_entry_id=self.config_entry.entry_id,
connections={(CONNECTION_NETWORK_MAC, self.api.config.mac)},
identifiers={(DOMAIN, self.api.config.bridgeid)},
manufacturer='Dresden Elektronik',
model=self.api.config.modelid,
name=self.api.config.name,
sw_version=self.api.config.swversion
)
async def async_setup(self):
"""Set up a deCONZ gateway."""
hass = self.hass
@ -52,9 +93,9 @@ class DeconzGateway:
hass.config_entries.async_forward_entry_setup(
self.config_entry, component))
self.listeners.append(
async_dispatcher_connect(
hass, NEW_SENSOR, self.async_add_remote))
self.listeners.append(async_dispatcher_connect(
hass, self.async_event_new_device(NEW_SENSOR),
self.async_add_remote))
self.async_add_remote(self.api.sensors.values())
@ -62,29 +103,39 @@ class DeconzGateway:
return True
@property
def event_reachable(self):
"""Gateway specific event to signal a change in connection status."""
return 'deconz_reachable_{}'.format(self.bridgeid)
@callback
def async_connection_status_callback(self, available):
"""Handle signals of gateway connection status."""
self.available = available
async_dispatcher_send(
self.hass, DECONZ_REACHABLE, {'state': True, 'attr': 'reachable'})
async_dispatcher_send(self.hass, self.event_reachable,
{'state': True, 'attr': 'reachable'})
@callback
def async_event_new_device(self, device_type):
"""Gateway specific event to signal new device."""
return NEW_DEVICE[device_type].format(self.bridgeid)
@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, NEW_DEVICE[device_type], device)
async_dispatcher_send(
self.hass, self.async_event_new_device(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')):
not (not self.allow_clip_sensor and
sensor.type.startswith('CLIP')):
self.events.append(DeconzEvent(self.hass, sensor))
@callback

View File

@ -8,10 +8,9 @@ from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
import homeassistant.util.color as color_util
from .const import (
CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DECONZ_DOMAIN, COVER_TYPES, NEW_GROUP,
NEW_LIGHT, SWITCH_TYPES)
from .const import COVER_TYPES, NEW_GROUP, NEW_LIGHT, SWITCH_TYPES
from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz']
@ -24,32 +23,35 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the deCONZ lights and groups from a config entry."""
gateway = hass.data[DECONZ_DOMAIN]
gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_light(lights):
"""Add light from deCONZ."""
entities = []
for light in lights:
if light.type not in COVER_TYPES + SWITCH_TYPES:
entities.append(DeconzLight(light, gateway))
async_add_entities(entities, True)
gateway.listeners.append(
async_dispatcher_connect(hass, NEW_LIGHT, async_add_light))
gateway.listeners.append(async_dispatcher_connect(
hass, gateway.async_event_new_device(NEW_LIGHT), async_add_light))
@callback
def async_add_group(groups):
"""Add group from deCONZ."""
entities = []
allow_group = config_entry.data.get(CONF_ALLOW_DECONZ_GROUPS, True)
for group in groups:
if group.lights and allow_group:
if group.lights and gateway.allow_deconz_groups:
entities.append(DeconzLight(group, gateway))
async_add_entities(entities, True)
gateway.listeners.append(
async_dispatcher_connect(hass, NEW_GROUP, async_add_group))
gateway.listeners.append(async_dispatcher_connect(
hass, gateway.async_event_new_device(NEW_GROUP), async_add_group))
async_add_light(gateway.api.lights.values())
async_add_group(gateway.api.groups.values())

View File

@ -3,7 +3,8 @@ from homeassistant.components.scene import Scene
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import DOMAIN as DECONZ_DOMAIN, NEW_SCENE
from .const import NEW_SCENE
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz']
@ -16,17 +17,20 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up scenes for deCONZ component."""
gateway = hass.data[DECONZ_DOMAIN]
gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_scene(scenes):
"""Add scene from deCONZ."""
entities = []
for scene in scenes:
entities.append(DeconzScene(scene, gateway))
async_add_entities(entities)
gateway.listeners.append(
async_dispatcher_connect(hass, NEW_SCENE, async_add_scene))
gateway.listeners.append(async_dispatcher_connect(
hass, gateway.async_event_new_device(NEW_SCENE), async_add_scene))
async_add_scene(gateway.api.scenes.values())

View File

@ -5,10 +5,9 @@ from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.util import slugify
from .const import (
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DECONZ_DOMAIN,
NEW_SENSOR)
from .const import ATTR_DARK, ATTR_ON, NEW_SENSOR
from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz']
@ -25,7 +24,7 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the deCONZ sensors."""
gateway = hass.data[DECONZ_DOMAIN]
gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_sensor(sensors):
@ -33,19 +32,24 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
from pydeconz.sensor import (
DECONZ_SENSOR, SWITCH as DECONZ_REMOTE)
entities = []
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
if sensor.type in DECONZ_SENSOR and \
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
not (not gateway.allow_clip_sensor and
sensor.type.startswith('CLIP')):
if sensor.type in DECONZ_REMOTE:
if sensor.battery:
entities.append(DeconzBattery(sensor, gateway))
else:
entities.append(DeconzSensor(sensor, gateway))
async_add_entities(entities, True)
gateway.listeners.append(
async_dispatcher_connect(hass, NEW_SENSOR, async_add_sensor))
gateway.listeners.append(async_dispatcher_connect(
hass, gateway.async_event_new_device(NEW_SENSOR), async_add_sensor))
async_add_sensor(gateway.api.sensors.values())

View File

@ -13,6 +13,12 @@ configure:
data:
description: Data is a json object with what data you want to alter.
example: '{"on": true}'
bridgeid:
description: (Optional) Bridgeid is a string unique for each deCONZ hardware. It can be found as part of the integration name.
example: '00212EFFFF012345'
device_refresh:
description: Refresh device lists from deCONZ.
description: Refresh device lists from deCONZ.
bridgeid:
description: (Optional) Bridgeid is a string unique for each deCONZ hardware. It can be found as part of the integration name.
example: '00212EFFFF012345'

View File

@ -3,8 +3,9 @@ from homeassistant.components.switch import SwitchDevice
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import DOMAIN as DECONZ_DOMAIN, NEW_LIGHT, POWER_PLUGS, SIRENS
from .const import NEW_LIGHT, POWER_PLUGS, SIRENS
from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz']
@ -20,21 +21,25 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
Switches are based same device class as lights in deCONZ.
"""
gateway = hass.data[DECONZ_DOMAIN]
gateway = get_gateway_from_config_entry(hass, config_entry)
@callback
def async_add_switch(lights):
"""Add switch from deCONZ."""
entities = []
for light in lights:
if light.type in POWER_PLUGS:
entities.append(DeconzPowerPlug(light, gateway))
elif light.type in SIRENS:
entities.append(DeconzSiren(light, gateway))
async_add_entities(entities, True)
gateway.listeners.append(
async_dispatcher_connect(hass, NEW_LIGHT, async_add_switch))
gateway.listeners.append(async_dispatcher_connect(
hass, gateway.async_event_new_device(NEW_LIGHT), async_add_switch))
async_add_switch(gateway.api.lights.values())

View File

@ -54,7 +54,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@ -64,6 +64,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
config_entry, 'binary_sensor')
# To flush out the service call to update the group
await hass.async_block_till_done()
return gateway
async def test_platform_manually_configured(hass):
@ -79,56 +80,56 @@ async def test_platform_manually_configured(hass):
async def test_no_binary_sensors(hass):
"""Test that no sensors in deconz results in no sensor entities."""
data = {}
await setup_gateway(hass, data)
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
gateway = await setup_gateway(hass, data)
assert len(hass.data[deconz.DOMAIN][gateway.bridgeid].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_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.DOMAIN].deconz_ids
gateway = await setup_gateway(hass, data)
assert "binary_sensor.sensor_1_name" in gateway.deconz_ids
assert "binary_sensor.sensor_2_name" not in gateway.deconz_ids
assert len(hass.states.async_all()) == 1
hass.data[deconz.DOMAIN].api.sensors['1'].async_update(
hass.data[deconz.DOMAIN][gateway.bridgeid].api.sensors['1'].async_update(
{'state': {'on': False}})
async def test_add_new_sensor(hass):
"""Test successful creation of sensor entities."""
data = {}
await setup_gateway(hass, data)
gateway = 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])
async_dispatcher_send(
hass, gateway.async_event_new_device('sensor'), [sensor])
await hass.async_block_till_done()
assert "binary_sensor.name" in hass.data[deconz.DOMAIN].deconz_ids
assert "binary_sensor.name" in gateway.deconz_ids
async def test_do_not_allow_clip_sensor(hass):
"""Test that clip sensors can be ignored."""
data = {}
await setup_gateway(hass, data, allow_clip_sensor=False)
gateway = 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])
async_dispatcher_send(
hass, gateway.async_event_new_device('sensor'), [sensor])
await hass.async_block_till_done()
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
assert len(gateway.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)
gateway = await setup_gateway(hass, data)
await hass.data[deconz.DOMAIN].async_reset()
await gateway.async_reset()
assert len(hass.states.async_all()) == 0

View File

@ -65,7 +65,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(hass.loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@ -75,6 +75,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
config_entry, 'climate')
# To flush out the service call to update the group
await hass.async_block_till_done()
return gateway
async def test_platform_manually_configured(hass):
@ -89,26 +90,26 @@ async def test_platform_manually_configured(hass):
async def test_no_sensors(hass):
"""Test that no sensors in deconz results in no climate entities."""
await setup_gateway(hass, {})
assert not hass.data[deconz.DOMAIN].deconz_ids
gateway = await setup_gateway(hass, {})
assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert not hass.states.async_all()
async def test_climate_devices(hass):
"""Test successful creation of sensor entities."""
await setup_gateway(hass, {"sensors": SENSOR})
assert "climate.climate_1_name" in hass.data[deconz.DOMAIN].deconz_ids
assert "sensor.sensor_2_name" not in hass.data[deconz.DOMAIN].deconz_ids
gateway = await setup_gateway(hass, {"sensors": SENSOR})
assert "climate.climate_1_name" in gateway.deconz_ids
assert "sensor.sensor_2_name" not in gateway.deconz_ids
assert len(hass.states.async_all()) == 1
hass.data[deconz.DOMAIN].api.sensors['1'].async_update(
gateway.api.sensors['1'].async_update(
{'state': {'on': False}})
await hass.services.async_call(
'climate', 'turn_on', {'entity_id': 'climate.climate_1_name'},
blocking=True
)
hass.data[deconz.DOMAIN].api.session.put.assert_called_with(
gateway.api.session.put.assert_called_with(
'http://1.2.3.4:80/api/ABCDEF/sensors/1/config',
data='{"mode": "auto"}'
)
@ -117,7 +118,7 @@ async def test_climate_devices(hass):
'climate', 'turn_off', {'entity_id': 'climate.climate_1_name'},
blocking=True
)
hass.data[deconz.DOMAIN].api.session.put.assert_called_with(
gateway.api.session.put.assert_called_with(
'http://1.2.3.4:80/api/ABCDEF/sensors/1/config',
data='{"mode": "off"}'
)
@ -127,18 +128,18 @@ async def test_climate_devices(hass):
{'entity_id': 'climate.climate_1_name', 'temperature': 20},
blocking=True
)
hass.data[deconz.DOMAIN].api.session.put.assert_called_with(
gateway.api.session.put.assert_called_with(
'http://1.2.3.4:80/api/ABCDEF/sensors/1/config',
data='{"heatsetpoint": 2000.0}'
)
assert len(hass.data[deconz.DOMAIN].api.session.put.mock_calls) == 3
assert len(gateway.api.session.put.mock_calls) == 3
async def test_verify_state_update(hass):
"""Test that state update properly."""
await setup_gateway(hass, {"sensors": SENSOR})
assert "climate.climate_1_name" in hass.data[deconz.DOMAIN].deconz_ids
gateway = await setup_gateway(hass, {"sensors": SENSOR})
assert "climate.climate_1_name" in gateway.deconz_ids
thermostat = hass.states.get('climate.climate_1_name')
assert thermostat.state == 'on'
@ -150,7 +151,7 @@ async def test_verify_state_update(hass):
"id": "1",
"config": {"on": False}
}
hass.data[deconz.DOMAIN].api.async_event_handler(state_update)
gateway.api.async_event_handler(state_update)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
@ -161,32 +162,34 @@ async def test_verify_state_update(hass):
async def test_add_new_climate_device(hass):
"""Test successful creation of climate entities."""
await setup_gateway(hass, {})
gateway = await setup_gateway(hass, {})
sensor = Mock()
sensor.name = 'name'
sensor.type = 'ZHAThermostat'
sensor.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
async_dispatcher_send(
hass, gateway.async_event_new_device('sensor'), [sensor])
await hass.async_block_till_done()
assert "climate.name" in hass.data[deconz.DOMAIN].deconz_ids
assert "climate.name" in gateway.deconz_ids
async def test_do_not_allow_clipsensor(hass):
"""Test that clip sensors can be ignored."""
await setup_gateway(hass, {}, allow_clip_sensor=False)
gateway = await setup_gateway(hass, {}, allow_clip_sensor=False)
sensor = Mock()
sensor.name = 'name'
sensor.type = 'CLIPThermostat'
sensor.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
async_dispatcher_send(
hass, gateway.async_event_new_device('sensor'), [sensor])
await hass.async_block_till_done()
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
assert len(gateway.deconz_ids) == 0
async def test_unload_sensor(hass):
"""Test that it works to unload sensor entities."""
await setup_gateway(hass, {"sensors": SENSOR})
gateway = await setup_gateway(hass, {"sensors": SENSOR})
await hass.data[deconz.DOMAIN].async_reset()
await gateway.async_reset()
assert len(hass.states.async_all()) == 0

View File

@ -22,10 +22,7 @@ async def test_flow_works(hass, aioclient_mock):
flow.hass = hass
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})
result = await flow.async_step_link(user_input={})
assert result['type'] == 'create_entry'
assert result['title'] == 'deCONZ-id'
@ -33,25 +30,10 @@ async def test_flow_works(hass, aioclient_mock):
'bridgeid': 'id',
'host': '1.2.3.4',
'port': 80,
'api_key': '1234567890ABCDEF',
'allow_clip_sensor': True,
'allow_deconz_groups': True
'api_key': '1234567890ABCDEF'
}
async def test_flow_already_registered_bridge(hass):
"""Test config flow don't allow more than one bridge to be registered."""
MockConfigEntry(domain='deconz', data={
'host': '1.2.3.4'
}).add_to_hass(hass)
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
result = await flow.async_step_user()
assert result['type'] == 'abort'
async def test_flow_bridge_discovery_fails(hass, aioclient_mock):
"""Test config flow works when discovery fails."""
flow = config_flow.DeconzFlowHandler()
@ -153,24 +135,6 @@ async def test_link_no_api_key(hass):
assert result['errors'] == {'base': 'no_key'}
async def test_link_already_registered_bridge(hass):
"""Test that link verifies to only allow one config entry to complete.
This is possible with discovery which will allow the user to complete
a second config entry and then complete the discovered config entry.
"""
MockConfigEntry(domain='deconz', data={
'host': '1.2.3.4'
}).add_to_hass(hass)
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
flow.deconz_config = {'host': '1.2.3.4', 'port': 80}
result = await flow.async_step_link(user_input={})
assert result['type'] == 'abort'
async def test_bridge_discovery(hass):
"""Test a bridge being discovered."""
flow = config_flow.DeconzFlowHandler()
@ -197,6 +161,7 @@ async def test_bridge_discovery_already_configured(hass):
result = await flow.async_step_discovery({
'host': '1.2.3.4',
'port': 80,
'serial': 'id'
})
@ -234,14 +199,12 @@ async def test_import_with_api_key(hass):
'bridgeid': 'id',
'host': '1.2.3.4',
'port': 80,
'api_key': '1234567890ABCDEF',
'allow_clip_sensor': True,
'allow_deconz_groups': True
'api_key': '1234567890ABCDEF'
}
async def test_options(hass, aioclient_mock):
"""Test that options work and that bridgeid can be requested."""
async def test_create_entry(hass, aioclient_mock):
"""Test that _create_entry work and that bridgeid can be requested."""
aioclient_mock.get('http://1.2.3.4:80/api/1234567890ABCDEF/config',
json={"bridgeid": "id"},
headers={'content-type': 'application/json'})
@ -252,8 +215,7 @@ async def test_options(hass, aioclient_mock):
'port': 80,
'api_key': '1234567890ABCDEF'}
result = await flow.async_step_options(
user_input={'allow_clip_sensor': False, 'allow_deconz_groups': False})
result = await flow._create_entry()
assert result['type'] == 'create_entry'
assert result['title'] == 'deCONZ-id'
@ -261,9 +223,7 @@ async def test_options(hass, aioclient_mock):
'bridgeid': 'id',
'host': '1.2.3.4',
'port': 80,
'api_key': '1234567890ABCDEF',
'allow_clip_sensor': False,
'allow_deconz_groups': False
'api_key': '1234567890ABCDEF'
}
@ -286,8 +246,8 @@ async def test_hassio_confirm(hass):
data={
'addon': 'Mock Addon',
'host': 'mock-deconz',
'port': 8080,
'serial': 'aa:bb',
'port': 80,
'serial': 'id',
'api_key': '1234567890ABCDEF',
},
context={'source': 'hassio'}
@ -299,18 +259,13 @@ async def test_hassio_confirm(hass):
}
result = await hass.config_entries.flow.async_configure(
result['flow_id'], {
'allow_clip_sensor': True,
'allow_deconz_groups': True,
}
result['flow_id'], user_input={}
)
assert result['type'] == 'create_entry'
assert result['result'].data == {
'host': 'mock-deconz',
'port': 8080,
'bridgeid': 'aa:bb',
'api_key': '1234567890ABCDEF',
'allow_clip_sensor': True,
'allow_deconz_groups': True,
'port': 80,
'bridgeid': 'id',
'api_key': '1234567890ABCDEF'
}

View File

@ -61,7 +61,7 @@ async def setup_gateway(hass, data):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@ -70,6 +70,7 @@ async def setup_gateway(hass, data):
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()
return gateway
async def test_platform_manually_configured(hass):
@ -84,8 +85,8 @@ async def test_platform_manually_configured(hass):
async def test_no_covers(hass):
"""Test that no cover entities are created."""
await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
gateway = await setup_gateway(hass, {})
assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0
@ -93,8 +94,8 @@ async def test_cover(hass):
"""Test that all supported cover entities are created."""
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
gateway = await setup_gateway(hass, {"lights": SUPPORTED_COVERS})
assert "cover.cover_1_name" in gateway.deconz_ids
assert len(SUPPORTED_COVERS) == len(COVER_TYPES)
assert len(hass.states.async_all()) == 3
@ -102,7 +103,7 @@ async def test_cover(hass):
assert cover_1 is not None
assert cover_1.state == 'closed'
hass.data[deconz.DOMAIN].api.lights['1'].async_update({})
gateway.api.lights['1'].async_update({})
await hass.services.async_call('cover', 'open_cover', {
'entity_id': 'cover.cover_1_name'
@ -122,14 +123,15 @@ async def test_cover(hass):
async def test_add_new_cover(hass):
"""Test successful creation of cover entity."""
data = {}
await setup_gateway(hass, data)
gateway = 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])
async_dispatcher_send(
hass, gateway.async_event_new_device('light'), [cover])
await hass.async_block_till_done()
assert "cover.name" in hass.data[deconz.DOMAIN].deconz_ids
assert "cover.name" in gateway.deconz_ids
async def test_unsupported_cover(hass):
@ -140,8 +142,8 @@ async def test_unsupported_cover(hass):
async def test_unload_cover(hass):
"""Test that it works to unload switch entities."""
await setup_gateway(hass, {"lights": SUPPORTED_COVERS})
gateway = await setup_gateway(hass, {"lights": SUPPORTED_COVERS})
await hass.data[deconz.DOMAIN].async_reset()
await gateway.async_reset()
assert len(hass.states.async_all()) == 1

View File

@ -11,56 +11,62 @@ from homeassistant.components import deconz
from tests.common import mock_coro, MockConfigEntry
ENTRY1_HOST = '1.2.3.4'
ENTRY1_PORT = 80
ENTRY1_API_KEY = '1234567890ABCDEF'
ENTRY1_BRIDGEID = '12345ABC'
CONFIG = {
"config": {
"bridgeid": "0123456789ABCDEF",
"mac": "12:34:56:78:90:ab",
"modelid": "deCONZ",
"name": "Phoscon",
"swversion": "2.05.35"
}
}
ENTRY2_HOST = '2.3.4.5'
ENTRY2_PORT = 80
ENTRY2_API_KEY = '1234567890ABCDEF'
ENTRY2_BRIDGEID = '23456DEF'
async def setup_entry(hass, entry):
"""Test that setup entry works."""
with patch.object(deconz.DeconzGateway, 'async_setup',
return_value=mock_coro(True)), \
patch.object(deconz.DeconzGateway, 'async_update_device_registry',
return_value=mock_coro(True)):
assert await deconz.async_setup_entry(hass, entry) is True
async def test_config_with_host_passed_to_config_entry(hass):
"""Test that configured options for a host are loaded via config entry."""
with patch.object(hass, 'config_entries') as mock_config_entries, \
patch.object(deconz, 'configured_hosts', return_value=[]):
with patch.object(hass.config_entries, 'flow') as mock_config_flow:
assert await async_setup_component(hass, deconz.DOMAIN, {
deconz.DOMAIN: {
deconz.CONF_HOST: '1.2.3.4',
deconz.CONF_PORT: 80
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT
}
}) is True
# Import flow started
assert len(mock_config_entries.flow.mock_calls) == 2
assert len(mock_config_flow.mock_calls) == 2
async def test_config_without_host_not_passed_to_config_entry(hass):
"""Test that a configuration without a host does not initiate an import."""
with patch.object(hass, 'config_entries') as mock_config_entries, \
patch.object(deconz, 'configured_hosts', return_value=[]):
MockConfigEntry(domain=deconz.DOMAIN, data={}).add_to_hass(hass)
with patch.object(hass.config_entries, 'flow') as mock_config_flow:
assert await async_setup_component(hass, deconz.DOMAIN, {
deconz.DOMAIN: {}
}) is True
# No flow started
assert len(mock_config_entries.flow.mock_calls) == 0
assert len(mock_config_flow.mock_calls) == 0
async def test_config_already_registered_not_passed_to_config_entry(hass):
async def test_config_import_entry_fails_when_entries_exist(hass):
"""Test that an already registered host does not initiate an import."""
with patch.object(hass, 'config_entries') as mock_config_entries, \
patch.object(deconz, 'configured_hosts',
return_value=['1.2.3.4']):
MockConfigEntry(domain=deconz.DOMAIN, data={}).add_to_hass(hass)
with patch.object(hass.config_entries, 'flow') as mock_config_flow:
assert await async_setup_component(hass, deconz.DOMAIN, {
deconz.DOMAIN: {
deconz.CONF_HOST: '1.2.3.4',
deconz.CONF_PORT: 80
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT
}
}) is True
# No flow started
assert len(mock_config_entries.flow.mock_calls) == 0
assert len(mock_config_flow.mock_calls) == 0
async def test_config_discovery(hass):
@ -71,16 +77,14 @@ async def test_config_discovery(hass):
assert len(mock_config_entries.flow.mock_calls) == 0
async def test_setup_entry_already_registered_bridge(hass):
"""Test setup entry doesn't allow more than one instance of deCONZ."""
hass.data[deconz.DOMAIN] = True
assert await deconz.async_setup_entry(hass, {}) is False
async def test_setup_entry_fails(hass):
"""Test setup entry fails if deCONZ is not available."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
entry.data = {
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY
}
with patch('pydeconz.DeconzSession.async_load_parameters',
side_effect=Exception):
await deconz.async_setup_entry(hass, entry)
@ -89,61 +93,121 @@ async def test_setup_entry_fails(hass):
async def test_setup_entry_no_available_bridge(hass):
"""Test setup entry fails if deCONZ is not available."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
with patch(
'pydeconz.DeconzSession.async_load_parameters',
side_effect=asyncio.TimeoutError
), pytest.raises(ConfigEntryNotReady):
entry.data = {
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY
}
with patch('pydeconz.DeconzSession.async_load_parameters',
side_effect=asyncio.TimeoutError),\
pytest.raises(ConfigEntryNotReady):
await deconz.async_setup_entry(hass, entry)
async def test_setup_entry_successful(hass):
"""Test setup entry is successful."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
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 hass.data[deconz.DOMAIN]
await setup_entry(hass, entry)
assert ENTRY1_BRIDGEID in hass.data[deconz.DOMAIN]
assert hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].master
async def test_setup_entry_multiple_gateways(hass):
"""Test setup entry is successful with multiple gateways."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
entry.add_to_hass(hass)
entry2 = MockConfigEntry(domain=deconz.DOMAIN, data={
deconz.CONF_HOST: ENTRY2_HOST,
deconz.CONF_PORT: ENTRY2_PORT,
deconz.CONF_API_KEY: ENTRY2_API_KEY,
deconz.CONF_BRIDGEID: ENTRY2_BRIDGEID
})
entry2.add_to_hass(hass)
await setup_entry(hass, entry)
await setup_entry(hass, entry2)
assert ENTRY1_BRIDGEID in hass.data[deconz.DOMAIN]
assert hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].master
assert ENTRY2_BRIDGEID in hass.data[deconz.DOMAIN]
assert not hass.data[deconz.DOMAIN][ENTRY2_BRIDGEID].master
async def test_unload_entry(hass):
"""Test being able to unload an entry."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
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
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
await setup_entry(hass, entry)
with patch.object(deconz.DeconzGateway, 'async_reset',
return_value=mock_coro(True)):
assert await deconz.async_unload_entry(hass, entry)
assert not hass.data[deconz.DOMAIN]
async def test_unload_entry_multiple_gateways(hass):
"""Test being able to unload an entry and master gateway gets moved."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
entry.add_to_hass(hass)
entry2 = MockConfigEntry(domain=deconz.DOMAIN, data={
deconz.CONF_HOST: ENTRY2_HOST,
deconz.CONF_PORT: ENTRY2_PORT,
deconz.CONF_API_KEY: ENTRY2_API_KEY,
deconz.CONF_BRIDGEID: ENTRY2_BRIDGEID
})
entry2.add_to_hass(hass)
await setup_entry(hass, entry)
await setup_entry(hass, entry2)
with patch.object(deconz.DeconzGateway, 'async_reset',
return_value=mock_coro(True)):
assert await deconz.async_unload_entry(hass, entry)
assert ENTRY2_BRIDGEID in hass.data[deconz.DOMAIN]
assert hass.data[deconz.DOMAIN][ENTRY2_BRIDGEID].master
async def test_service_configure(hass):
"""Test that service invokes pydeconz with the correct path and data."""
entry = MockConfigEntry(domain=deconz.DOMAIN, data={
'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
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
hass.data[deconz.DOMAIN].deconz_ids = {
await setup_entry(hass, entry)
hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].deconz_ids = {
'light.test': '/light/1'
}
data = {'on': True, 'attr1': 10, 'attr2': 20}
@ -191,25 +255,23 @@ async def test_service_configure(hass):
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'
deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: ENTRY1_PORT,
deconz.CONF_API_KEY: ENTRY1_API_KEY,
deconz.CONF_BRIDGEID: ENTRY1_BRIDGEID
})
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
await setup_entry(hass, entry)
with patch.object(hass.data[deconz.DOMAIN].api, 'async_load_parameters',
return_value=mock_coro(True)):
with patch('pydeconz.DeconzSession.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)):
with patch('pydeconz.DeconzSession.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

@ -87,7 +87,7 @@ async def setup_gateway(hass, data, allow_deconz_groups=True):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@ -96,6 +96,7 @@ async def setup_gateway(hass, data, allow_deconz_groups=True):
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()
return gateway
async def test_platform_manually_configured(hass):
@ -110,8 +111,8 @@ async def test_platform_manually_configured(hass):
async def test_no_lights_or_groups(hass):
"""Test that no lights or groups entities are created."""
await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
gateway = await setup_gateway(hass, {})
assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0
@ -119,11 +120,12 @@ async def test_lights_and_groups(hass):
"""Test that lights or groups entities are created."""
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
gateway = await setup_gateway(
hass, {"lights": LIGHT, "groups": GROUP})
assert "light.light_1_name" in gateway.deconz_ids
assert "light.light_2_name" in gateway.deconz_ids
assert "light.group_1_name" in gateway.deconz_ids
assert "light.group_2_name" not in gateway.deconz_ids
assert len(hass.states.async_all()) == 4
lamp_1 = hass.states.get('light.light_1_name')
@ -137,7 +139,7 @@ async def test_lights_and_groups(hass):
assert light_2.state == 'on'
assert light_2.attributes['color_temp'] == 2500
hass.data[deconz.DOMAIN].api.lights['1'].async_update({})
gateway.api.lights['1'].async_update({})
await hass.services.async_call('light', 'turn_on', {
'entity_id': 'light.light_1_name',
@ -166,49 +168,52 @@ async def test_lights_and_groups(hass):
async def test_add_new_light(hass):
"""Test successful creation of light entities."""
await setup_gateway(hass, {})
gateway = await setup_gateway(hass, {})
light = Mock()
light.name = 'name'
light.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_light', [light])
async_dispatcher_send(
hass, gateway.async_event_new_device('light'), [light])
await hass.async_block_till_done()
assert "light.name" in hass.data[deconz.DOMAIN].deconz_ids
assert "light.name" in gateway.deconz_ids
async def test_add_new_group(hass):
"""Test successful creation of group entities."""
await setup_gateway(hass, {})
gateway = await setup_gateway(hass, {})
group = Mock()
group.name = 'name'
group.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_group', [group])
async_dispatcher_send(
hass, gateway.async_event_new_device('group'), [group])
await hass.async_block_till_done()
assert "light.name" in hass.data[deconz.DOMAIN].deconz_ids
assert "light.name" in gateway.deconz_ids
async def test_do_not_add_deconz_groups(hass):
"""Test that clip sensors can be ignored."""
await setup_gateway(hass, {}, allow_deconz_groups=False)
gateway = 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])
async_dispatcher_send(
hass, gateway.async_event_new_device('group'), [group])
await hass.async_block_till_done()
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
assert len(gateway.deconz_ids) == 0
async def test_no_switch(hass):
"""Test that a switch doesn't get created as a light entity."""
await setup_gateway(hass, {"lights": SWITCH})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
gateway = await setup_gateway(hass, {"lights": SWITCH})
assert len(gateway.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})
gateway = await setup_gateway(hass, {"lights": LIGHT, "groups": GROUP})
await hass.data[deconz.DOMAIN].async_reset()
await gateway.async_reset()
# Group.all_lights will not be removed
assert len(hass.states.async_all()) == 1

View File

@ -47,7 +47,7 @@ async def setup_gateway(hass, data):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@ -56,6 +56,7 @@ async def setup_gateway(hass, data):
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()
return gateway
async def test_platform_manually_configured(hass):
@ -70,8 +71,8 @@ async def test_platform_manually_configured(hass):
async def test_no_scenes(hass):
"""Test that scenes can be loaded without scenes being available."""
await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
gateway = await setup_gateway(hass, {})
assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0
@ -79,8 +80,8 @@ async def test_scenes(hass):
"""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
gateway = await setup_gateway(hass, {"groups": GROUP})
assert "scene.group_1_name_scene_1" in gateway.deconz_ids
assert len(hass.states.async_all()) == 1
await hass.services.async_call('scene', 'turn_on', {
@ -90,8 +91,8 @@ async def test_scenes(hass):
async def test_unload_scene(hass):
"""Test that it works to unload scene entities."""
await setup_gateway(hass, {"groups": GROUP})
gateway = await setup_gateway(hass, {"groups": GROUP})
await hass.data[deconz.DOMAIN].async_reset()
await gateway.async_reset()
assert len(hass.states.async_all()) == 0

View File

@ -91,7 +91,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@ -101,6 +101,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
config_entry, 'sensor')
# To flush out the service call to update the group
await hass.async_block_till_done()
return gateway
async def test_platform_manually_configured(hass):
@ -115,58 +116,56 @@ async def test_platform_manually_configured(hass):
async def test_no_sensors(hass):
"""Test that no sensors in deconz results in no sensor entities."""
await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
gateway = await setup_gateway(hass, {})
assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0
async def test_sensors(hass):
"""Test successful creation of sensor entities."""
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.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.DOMAIN].deconz_ids
gateway = await setup_gateway(hass, {"sensors": SENSOR})
assert "sensor.sensor_1_name" in gateway.deconz_ids
assert "sensor.sensor_2_name" not in gateway.deconz_ids
assert "sensor.sensor_3_name" not in gateway.deconz_ids
assert "sensor.sensor_3_name_battery_level" not in gateway.deconz_ids
assert "sensor.sensor_4_name" not in gateway.deconz_ids
assert "sensor.sensor_4_name_battery_level" in gateway.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}})
gateway.api.sensors['1'].async_update({'state': {'on': False}})
gateway.api.sensors['4'].async_update({'config': {'battery': 75}})
async def test_add_new_sensor(hass):
"""Test successful creation of sensor entities."""
await setup_gateway(hass, {})
gateway = 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])
async_dispatcher_send(
hass, gateway.async_event_new_device('sensor'), [sensor])
await hass.async_block_till_done()
assert "sensor.name" in hass.data[deconz.DOMAIN].deconz_ids
assert "sensor.name" in gateway.deconz_ids
async def test_do_not_allow_clipsensor(hass):
"""Test that clip sensors can be ignored."""
await setup_gateway(hass, {}, allow_clip_sensor=False)
gateway = 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])
async_dispatcher_send(
hass, gateway.async_event_new_device('sensor'), [sensor])
await hass.async_block_till_done()
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
assert len(gateway.deconz_ids) == 0
async def test_unload_sensor(hass):
"""Test that it works to unload sensor entities."""
await setup_gateway(hass, {"sensors": SENSOR})
gateway = await setup_gateway(hass, {"sensors": SENSOR})
await hass.data[deconz.DOMAIN].async_reset()
await gateway.async_reset()
assert len(hass.states.async_all()) == 0

View File

@ -65,7 +65,7 @@ async def setup_gateway(hass, data):
gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway
hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)):
@ -74,6 +74,7 @@ async def setup_gateway(hass, data):
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()
return gateway
async def test_platform_manually_configured(hass):
@ -88,8 +89,8 @@ async def test_platform_manually_configured(hass):
async def test_no_switches(hass):
"""Test that no switch entities are created."""
await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0
gateway = await setup_gateway(hass, {})
assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0
@ -97,10 +98,10 @@ async def test_switches(hass):
"""Test that all supported switch entities are created."""
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
gateway = await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES})
assert "switch.switch_1_name" in gateway.deconz_ids
assert "switch.switch_2_name" in gateway.deconz_ids
assert "switch.switch_3_name" in gateway.deconz_ids
assert len(SUPPORTED_SWITCHES) == len(SWITCH_TYPES)
assert len(hass.states.async_all()) == 4
@ -111,7 +112,7 @@ async def test_switches(hass):
assert switch_3 is not None
assert switch_3.state == 'on'
hass.data[deconz.DOMAIN].api.lights['1'].async_update({})
gateway.api.lights['1'].async_update({})
await hass.services.async_call('switch', 'turn_on', {
'entity_id': 'switch.switch_1_name'
@ -130,14 +131,15 @@ async def test_switches(hass):
async def test_add_new_switch(hass):
"""Test successful creation of switch entity."""
await setup_gateway(hass, {})
gateway = 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])
async_dispatcher_send(
hass, gateway.async_event_new_device('light'), [switch])
await hass.async_block_till_done()
assert "switch.name" in hass.data[deconz.DOMAIN].deconz_ids
assert "switch.name" in gateway.deconz_ids
async def test_unsupported_switch(hass):
@ -148,8 +150,8 @@ async def test_unsupported_switch(hass):
async def test_unload_switch(hass):
"""Test that it works to unload switch entities."""
await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES})
gateway = await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES})
await hass.data[deconz.DOMAIN].async_reset()
await gateway.async_reset()
assert len(hass.states.async_all()) == 1