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
This commit is contained in:
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 import config_entries
from homeassistant.const import ( from homeassistant.const import (
CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP) CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv 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 # Loading the config flow file will register the flow
from .config_flow import configured_hosts from .config_flow import get_master_gateway
from .const import DEFAULT_PORT, DOMAIN, _LOGGER from .const import (
CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID,
CONF_MASTER_GATEWAY, DEFAULT_PORT, DOMAIN, _LOGGER)
from .gateway import DeconzGateway from .gateway import DeconzGateway
REQUIREMENTS = ['pydeconz==54'] REQUIREMENTS = ['pydeconz==54']
@ -32,24 +34,25 @@ SERVICE_SCHEMA = vol.All(vol.Schema({
vol.Optional(SERVICE_ENTITY): cv.entity_id, vol.Optional(SERVICE_ENTITY): cv.entity_id,
vol.Optional(SERVICE_FIELD): cv.matches_regex('/.*'), vol.Optional(SERVICE_FIELD): cv.matches_regex('/.*'),
vol.Required(SERVICE_DATA): dict, vol.Required(SERVICE_DATA): dict,
vol.Optional(CONF_BRIDGEID): str
}), cv.has_at_least_one_key(SERVICE_ENTITY, SERVICE_FIELD)) }), cv.has_at_least_one_key(SERVICE_ENTITY, SERVICE_FIELD))
SERVICE_DEVICE_REFRESH = 'device_refresh' SERVICE_DEVICE_REFRESH = 'device_refresh'
SERVICE_DEVICE_REFRESCH_SCHEMA = vol.All(vol.Schema({
vol.Optional(CONF_BRIDGEID): str
}))
async def async_setup(hass, config): async def async_setup(hass, config):
"""Load configuration for deCONZ component. """Load configuration for deCONZ component.
Discovery has loaded the component if DOMAIN is not present in config. Discovery has loaded the component if DOMAIN is not present in config.
""" """
if DOMAIN in config: if not hass.config_entries.async_entries(DOMAIN) and DOMAIN in config:
deconz_config = None
if CONF_HOST in config[DOMAIN]:
deconz_config = config[DOMAIN] deconz_config = config[DOMAIN]
if deconz_config and not configured_hosts(hass):
hass.async_add_job(hass.config_entries.flow.async_init( hass.async_add_job(hass.config_entries.flow.async_init(
DOMAIN, DOMAIN, context={'source': config_entries.SOURCE_IMPORT},
context={'source': config_entries.SOURCE_IMPORT},
data=deconz_config data=deconz_config
)) ))
return True return True
@ -61,26 +64,20 @@ async def async_setup_entry(hass, config_entry):
Load config, group, light and sensor data for server information. Load config, group, light and sensor data for server information.
Start websocket for push notification of state changes from deCONZ. Start websocket for push notification of state changes from deCONZ.
""" """
if DOMAIN in hass.data: if DOMAIN not in hass.data:
_LOGGER.error( hass.data[DOMAIN] = {}
"Config entry failed since one deCONZ instance already exists")
return False if not config_entry.options:
await async_populate_options(hass, config_entry)
gateway = DeconzGateway(hass, config_entry) gateway = DeconzGateway(hass, config_entry)
if not await gateway.async_setup(): if not await gateway.async_setup():
return False return False
hass.data[DOMAIN] = gateway hass.data[DOMAIN][gateway.bridgeid] = gateway
device_registry = await \ await gateway.async_update_device_registry()
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)
async def async_configure(call): async def async_configure(call):
"""Set attribute of device in deCONZ. """Set attribute of device in deCONZ.
@ -100,8 +97,11 @@ async def async_setup_entry(hass, config_entry):
""" """
field = call.data.get(SERVICE_FIELD, '') field = call.data.get(SERVICE_FIELD, '')
entity_id = call.data.get(SERVICE_ENTITY) entity_id = call.data.get(SERVICE_ENTITY)
data = call.data.get(SERVICE_DATA) data = call.data[SERVICE_DATA]
gateway = hass.data[DOMAIN]
gateway = get_master_gateway(hass)
if CONF_BRIDGEID in call.data:
gateway = hass.data[DOMAIN][call.data[CONF_BRIDGEID]]
if entity_id: if entity_id:
try: try:
@ -117,7 +117,9 @@ async def async_setup_entry(hass, config_entry):
async def async_refresh_devices(call): async def async_refresh_devices(call):
"""Refresh available devices from deCONZ.""" """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()) groups = set(gateway.api.groups.keys())
lights = set(gateway.api.lights.keys()) lights = set(gateway.api.lights.keys())
@ -151,7 +153,8 @@ async def async_setup_entry(hass, config_entry):
) )
hass.services.async_register( 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) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown)
return True return True
@ -159,7 +162,34 @@ async def async_setup_entry(hass, config_entry):
async def async_unload_entry(hass, config_entry): async def async_unload_entry(hass, config_entry):
"""Unload deCONZ config entry.""" """Unload deCONZ config entry."""
gateway = hass.data.pop(DOMAIN) 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_DECONZ)
hass.services.async_remove(DOMAIN, SERVICE_DEVICE_REFRESH) 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() 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.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import ( from .const import ATTR_DARK, ATTR_ON, NEW_SENSOR
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DECONZ_DOMAIN,
NEW_SENSOR)
from .deconz_device import DeconzDevice from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz'] DEPENDENCIES = ['deconz']
@ -24,22 +23,26 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the deCONZ binary sensor.""" """Set up the deCONZ binary sensor."""
gateway = hass.data[DECONZ_DOMAIN] gateway = get_gateway_from_config_entry(hass, config_entry)
@callback @callback
def async_add_sensor(sensors): def async_add_sensor(sensors):
"""Add binary sensor from deCONZ.""" """Add binary sensor from deCONZ."""
from pydeconz.sensor import DECONZ_BINARY_SENSOR from pydeconz.sensor import DECONZ_BINARY_SENSOR
entities = [] entities = []
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors: for sensor in sensors:
if sensor.type in DECONZ_BINARY_SENSOR and \ 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)) entities.append(DeconzBinarySensor(sensor, gateway))
async_add_entities(entities, True) async_add_entities(entities, True)
gateway.listeners.append( gateway.listeners.append(async_dispatcher_connect(
async_dispatcher_connect(hass, NEW_SENSOR, async_add_sensor)) hass, gateway.async_event_new_device(NEW_SENSOR), async_add_sensor))
async_add_sensor(gateway.api.sensors.values()) async_add_sensor(gateway.api.sensors.values())

View file

@ -7,10 +7,9 @@ from homeassistant.const import (
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import ( from .const import ATTR_OFFSET, ATTR_VALVE, NEW_SENSOR
ATTR_OFFSET, ATTR_VALVE, CONF_ALLOW_CLIP_SENSOR,
DOMAIN as DECONZ_DOMAIN, NEW_SENSOR)
from .deconz_device import DeconzDevice from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz'] 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. 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 @callback
def async_add_climate(sensors): def async_add_climate(sensors):
"""Add climate devices from deCONZ.""" """Add climate devices from deCONZ."""
from pydeconz.sensor import THERMOSTAT from pydeconz.sensor import THERMOSTAT
entities = [] entities = []
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors: for sensor in sensors:
if sensor.type in THERMOSTAT and \ 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)) entities.append(DeconzThermostat(sensor, gateway))
async_add_entities(entities, True) async_add_entities(entities, True)
gateway.listeners.append( gateway.listeners.append(async_dispatcher_connect(
async_dispatcher_connect(hass, NEW_SENSOR, async_add_climate)) hass, gateway.async_event_new_device(NEW_SENSOR), async_add_climate))
async_add_climate(gateway.api.sensors.values()) 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.core import callback
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from .const import ( from .const import CONF_BRIDGEID, DEFAULT_PORT, DOMAIN
CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID,
DEFAULT_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_DECONZ_GROUPS, DEFAULT_PORT,
DOMAIN)
@callback @callback
@ -22,6 +19,14 @@ def configured_hosts(hass):
in hass.config_entries.async_entries(DOMAIN)) 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) @config_entries.HANDLERS.register(DOMAIN)
class DeconzFlowHandler(config_entries.ConfigFlow): class DeconzFlowHandler(config_entries.ConfigFlow):
"""Handle a deCONZ config flow.""" """Handle a deCONZ config flow."""
@ -39,16 +44,12 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
"""Handle a deCONZ config flow start. """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 only one bridge is found go to link step.
If more than one bridge is found let user choose bridge to link. If more than one bridge is found let user choose bridge to link.
If no bridge is found allow user to manually input configuration. If no bridge is found allow user to manually input configuration.
""" """
from pydeconz.utils import async_discovery 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: if user_input is not None:
for bridge in self.bridges: for bridge in self.bridges:
if bridge[CONF_HOST] == user_input[CONF_HOST]: if bridge[CONF_HOST] == user_input[CONF_HOST]:
@ -99,9 +100,6 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
errors = {} errors = {}
if user_input is not None: 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) session = aiohttp_client.async_get_clientsession(self.hass)
try: try:
@ -114,29 +112,20 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
else: else:
self.deconz_config[CONF_API_KEY] = api_key self.deconz_config[CONF_API_KEY] = api_key
return await self.async_step_options() return await self._create_entry()
return self.async_show_form( return self.async_show_form(
step_id='link', step_id='link',
errors=errors, errors=errors,
) )
async def async_step_options(self, user_input=None): async def _create_entry(self):
"""Extra options for deCONZ. """Create entry for gateway."""
CONF_CLIP_SENSOR -- Allow user to choose if they want clip sensors.
CONF_DECONZ_GROUPS -- Allow user to choose if they want deCONZ groups.
"""
from pydeconz.utils import async_get_bridgeid 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: if CONF_BRIDGEID not in self.deconz_config:
session = aiohttp_client.async_get_clientsession(self.hass) session = aiohttp_client.async_get_clientsession(self.hass)
try: try:
with async_timeout.timeout(10): with async_timeout.timeout(10):
self.deconz_config[CONF_BRIDGEID] = \ self.deconz_config[CONF_BRIDGEID] = \
@ -151,25 +140,19 @@ class DeconzFlowHandler(config_entries.ConfigFlow):
data=self.deconz_config 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,
}),
)
async def async_step_discovery(self, discovery_info): async def async_step_discovery(self, discovery_info):
"""Prepare configuration for a discovered deCONZ bridge. """Prepare configuration for a discovered deCONZ bridge.
This flow is triggered by the discovery component. This flow is triggered by the discovery component.
""" """
deconz_config = {} deconz_config = {
deconz_config[CONF_HOST] = discovery_info.get(CONF_HOST) CONF_HOST: discovery_info[CONF_HOST],
deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT) CONF_PORT: discovery_info[CONF_PORT],
deconz_config[CONF_BRIDGEID] = discovery_info.get('serial') 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) 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 Otherwise we will delegate to `link` step which
will ask user to link the bridge. 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 self.deconz_config = import_config
if CONF_API_KEY not in import_config: if CONF_API_KEY not in import_config:
return await self.async_step_link() return await self.async_step_link()
user_input = {CONF_ALLOW_CLIP_SENSOR: True, return await self._create_entry()
CONF_ALLOW_DECONZ_GROUPS: True}
return await self.async_step_options(user_input=user_input)
async def async_step_hassio(self, user_input=None): async def async_step_hassio(self, user_input=None):
"""Prepare configuration for a Hass.io deCONZ bridge. """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): async def async_step_hassio_confirm(self, user_input=None):
"""Confirm a Hass.io discovery.""" """Confirm a Hass.io discovery."""
if user_input is not None: 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( return await self._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 self.async_show_form( return self.async_show_form(
step_id='hassio_confirm', step_id='hassio_confirm',
description_placeholders={ description_placeholders={
'addon': self._hassio_discovery['addon'] '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_CLIP_SENSOR = 'allow_clip_sensor'
CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups' CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups'
CONF_BRIDGEID = 'bridgeid' CONF_BRIDGEID = 'bridgeid'
CONF_MASTER_GATEWAY = 'master'
SUPPORTED_PLATFORMS = ['binary_sensor', 'climate', 'cover', SUPPORTED_PLATFORMS = ['binary_sensor', 'climate', 'cover',
'light', 'scene', 'sensor', 'switch'] 'light', 'scene', 'sensor', 'switch']
DECONZ_REACHABLE = 'deconz_reachable' NEW_GROUP = 'group'
NEW_LIGHT = 'light'
NEW_GROUP = 'deconz_new_group' NEW_SCENE = 'scene'
NEW_LIGHT = 'deconz_new_light' NEW_SENSOR = 'sensor'
NEW_SCENE = 'deconz_new_scene'
NEW_SENSOR = 'deconz_new_sensor'
NEW_DEVICE = { NEW_DEVICE = {
'group': NEW_GROUP, NEW_GROUP: 'deconz_new_group_{}',
'light': NEW_LIGHT, NEW_LIGHT: 'deconz_new_light_{}',
'scene': NEW_SCENE, NEW_SCENE: 'deconz_new_scene_{}',
'sensor': NEW_SENSOR NEW_SENSOR: 'deconz_new_sensor_{}'
} }
ATTR_DARK = 'dark' ATTR_DARK = 'dark'

View file

@ -5,9 +5,9 @@ from homeassistant.components.cover import (
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from .const import ( from .const import COVER_TYPES, DAMPERS, NEW_LIGHT, WINDOW_COVERS
COVER_TYPES, DAMPERS, DOMAIN as DECONZ_DOMAIN, NEW_LIGHT, WINDOW_COVERS)
from .deconz_device import DeconzDevice from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz'] 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. 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 @callback
def async_add_cover(lights): def async_add_cover(lights):
"""Add cover from deCONZ.""" """Add cover from deCONZ."""
entities = [] entities = []
for light in lights: for light in lights:
if light.type in COVER_TYPES: if light.type in COVER_TYPES:
if light.modelid in ZIGBEE_SPEC: if light.modelid in ZIGBEE_SPEC:
entities.append(DeconzCoverZigbeeSpec(light, gateway)) entities.append(DeconzCoverZigbeeSpec(light, gateway))
else: else:
entities.append(DeconzCover(light, gateway)) entities.append(DeconzCover(light, gateway))
async_add_entities(entities, True) async_add_entities(entities, True)
gateway.listeners.append( gateway.listeners.append(async_dispatcher_connect(
async_dispatcher_connect(hass, NEW_LIGHT, async_add_cover)) hass, gateway.async_event_new_device(NEW_LIGHT), async_add_cover))
async_add_cover(gateway.api.lights.values()) 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.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity 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): class DeconzDevice(Entity):
@ -21,7 +21,8 @@ class DeconzDevice(Entity):
self._device.register_async_callback(self.async_update_callback) self._device.register_async_callback(self.async_update_callback)
self.gateway.deconz_ids[self.entity_id] = self._device.deconz_id self.gateway.deconz_ids[self.entity_id] = self._device.deconz_id
self.unsub_dispatcher = async_dispatcher_connect( 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: async def async_will_remove_from_hass(self) -> None:
"""Disconnect device object when removed.""" """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.const import CONF_EVENT, CONF_HOST, CONF_ID
from homeassistant.core import EventOrigin, callback from homeassistant.core import EventOrigin, callback
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_send) async_dispatcher_connect, async_dispatcher_send)
from homeassistant.util import slugify from homeassistant.util import slugify
from .const import ( from .const import (
_LOGGER, DECONZ_REACHABLE, CONF_ALLOW_CLIP_SENSOR, NEW_DEVICE, NEW_SENSOR, _LOGGER, CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, CONF_BRIDGEID,
SUPPORTED_PLATFORMS) CONF_MASTER_GATEWAY, DOMAIN, NEW_DEVICE, NEW_SENSOR, SUPPORTED_PLATFORMS)
from .errors import AuthenticationRequired, CannotConnect 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: class DeconzGateway:
"""Manages a single deCONZ gateway.""" """Manages a single deCONZ gateway."""
@ -30,6 +37,40 @@ class DeconzGateway:
self.events = [] self.events = []
self.listeners = [] 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): async def async_setup(self):
"""Set up a deCONZ gateway.""" """Set up a deCONZ gateway."""
hass = self.hass hass = self.hass
@ -52,9 +93,9 @@ class DeconzGateway:
hass.config_entries.async_forward_entry_setup( hass.config_entries.async_forward_entry_setup(
self.config_entry, component)) self.config_entry, component))
self.listeners.append( self.listeners.append(async_dispatcher_connect(
async_dispatcher_connect( hass, self.async_event_new_device(NEW_SENSOR),
hass, NEW_SENSOR, self.async_add_remote)) self.async_add_remote))
self.async_add_remote(self.api.sensors.values()) self.async_add_remote(self.api.sensors.values())
@ -62,29 +103,39 @@ class DeconzGateway:
return True return True
@property
def event_reachable(self):
"""Gateway specific event to signal a change in connection status."""
return 'deconz_reachable_{}'.format(self.bridgeid)
@callback @callback
def async_connection_status_callback(self, available): def async_connection_status_callback(self, available):
"""Handle signals of gateway connection status.""" """Handle signals of gateway connection status."""
self.available = available self.available = available
async_dispatcher_send( async_dispatcher_send(self.hass, self.event_reachable,
self.hass, DECONZ_REACHABLE, {'state': True, 'attr': '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 @callback
def async_add_device_callback(self, device_type, device): def async_add_device_callback(self, device_type, device):
"""Handle event of new device creation in deCONZ.""" """Handle event of new device creation in deCONZ."""
if not isinstance(device, list): if not isinstance(device, list):
device = [device] 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 @callback
def async_add_remote(self, sensors): def async_add_remote(self, sensors):
"""Set up remote from deCONZ.""" """Set up remote from deCONZ."""
from pydeconz.sensor import SWITCH as DECONZ_REMOTE 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: for sensor in sensors:
if sensor.type in DECONZ_REMOTE and \ 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)) self.events.append(DeconzEvent(self.hass, sensor))
@callback @callback

View file

@ -8,10 +8,9 @@ from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
from .const import ( from .const import COVER_TYPES, NEW_GROUP, NEW_LIGHT, SWITCH_TYPES
CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DECONZ_DOMAIN, COVER_TYPES, NEW_GROUP,
NEW_LIGHT, SWITCH_TYPES)
from .deconz_device import DeconzDevice from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz'] DEPENDENCIES = ['deconz']
@ -24,32 +23,35 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the deCONZ lights and groups from a config entry.""" """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 @callback
def async_add_light(lights): def async_add_light(lights):
"""Add light from deCONZ.""" """Add light from deCONZ."""
entities = [] entities = []
for light in lights: for light in lights:
if light.type not in COVER_TYPES + SWITCH_TYPES: if light.type not in COVER_TYPES + SWITCH_TYPES:
entities.append(DeconzLight(light, gateway)) entities.append(DeconzLight(light, gateway))
async_add_entities(entities, True) async_add_entities(entities, True)
gateway.listeners.append( gateway.listeners.append(async_dispatcher_connect(
async_dispatcher_connect(hass, NEW_LIGHT, async_add_light)) hass, gateway.async_event_new_device(NEW_LIGHT), async_add_light))
@callback @callback
def async_add_group(groups): def async_add_group(groups):
"""Add group from deCONZ.""" """Add group from deCONZ."""
entities = [] entities = []
allow_group = config_entry.data.get(CONF_ALLOW_DECONZ_GROUPS, True)
for group in groups: for group in groups:
if group.lights and allow_group: if group.lights and gateway.allow_deconz_groups:
entities.append(DeconzLight(group, gateway)) entities.append(DeconzLight(group, gateway))
async_add_entities(entities, True) async_add_entities(entities, True)
gateway.listeners.append( gateway.listeners.append(async_dispatcher_connect(
async_dispatcher_connect(hass, NEW_GROUP, async_add_group)) hass, gateway.async_event_new_device(NEW_GROUP), async_add_group))
async_add_light(gateway.api.lights.values()) async_add_light(gateway.api.lights.values())
async_add_group(gateway.api.groups.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.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect 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'] DEPENDENCIES = ['deconz']
@ -16,17 +17,20 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up scenes for deCONZ component.""" """Set up scenes for deCONZ component."""
gateway = hass.data[DECONZ_DOMAIN] gateway = get_gateway_from_config_entry(hass, config_entry)
@callback @callback
def async_add_scene(scenes): def async_add_scene(scenes):
"""Add scene from deCONZ.""" """Add scene from deCONZ."""
entities = [] entities = []
for scene in scenes: for scene in scenes:
entities.append(DeconzScene(scene, gateway)) entities.append(DeconzScene(scene, gateway))
async_add_entities(entities) 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()) 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.helpers.dispatcher import async_dispatcher_connect
from homeassistant.util import slugify from homeassistant.util import slugify
from .const import ( from .const import ATTR_DARK, ATTR_ON, NEW_SENSOR
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DECONZ_DOMAIN,
NEW_SENSOR)
from .deconz_device import DeconzDevice from .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz'] DEPENDENCIES = ['deconz']
@ -25,7 +24,7 @@ async def async_setup_platform(
async def async_setup_entry(hass, config_entry, async_add_entities): async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the deCONZ sensors.""" """Set up the deCONZ sensors."""
gateway = hass.data[DECONZ_DOMAIN] gateway = get_gateway_from_config_entry(hass, config_entry)
@callback @callback
def async_add_sensor(sensors): def async_add_sensor(sensors):
@ -33,19 +32,24 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
from pydeconz.sensor import ( from pydeconz.sensor import (
DECONZ_SENSOR, SWITCH as DECONZ_REMOTE) DECONZ_SENSOR, SWITCH as DECONZ_REMOTE)
entities = [] entities = []
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors: for sensor in sensors:
if sensor.type in DECONZ_SENSOR and \ 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.type in DECONZ_REMOTE:
if sensor.battery: if sensor.battery:
entities.append(DeconzBattery(sensor, gateway)) entities.append(DeconzBattery(sensor, gateway))
else: else:
entities.append(DeconzSensor(sensor, gateway)) entities.append(DeconzSensor(sensor, gateway))
async_add_entities(entities, True) async_add_entities(entities, True)
gateway.listeners.append( gateway.listeners.append(async_dispatcher_connect(
async_dispatcher_connect(hass, NEW_SENSOR, async_add_sensor)) hass, gateway.async_event_new_device(NEW_SENSOR), async_add_sensor))
async_add_sensor(gateway.api.sensors.values()) async_add_sensor(gateway.api.sensors.values())

View file

@ -13,6 +13,12 @@ configure:
data: data:
description: Data is a json object with what data you want to alter. description: Data is a json object with what data you want to alter.
example: '{"on": true}' 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: 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.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect 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 .deconz_device import DeconzDevice
from .gateway import get_gateway_from_config_entry
DEPENDENCIES = ['deconz'] 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. 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 @callback
def async_add_switch(lights): def async_add_switch(lights):
"""Add switch from deCONZ.""" """Add switch from deCONZ."""
entities = [] entities = []
for light in lights: for light in lights:
if light.type in POWER_PLUGS: if light.type in POWER_PLUGS:
entities.append(DeconzPowerPlug(light, gateway)) entities.append(DeconzPowerPlug(light, gateway))
elif light.type in SIRENS: elif light.type in SIRENS:
entities.append(DeconzSiren(light, gateway)) entities.append(DeconzSiren(light, gateway))
async_add_entities(entities, True) async_add_entities(entities, True)
gateway.listeners.append( gateway.listeners.append(async_dispatcher_connect(
async_dispatcher_connect(hass, NEW_LIGHT, async_add_switch)) hass, gateway.async_event_new_device(NEW_LIGHT), async_add_switch))
async_add_switch(gateway.api.lights.values()) 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 = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data) gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock() gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state', with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)): return_value=mock_coro(data)):
@ -64,6 +64,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
config_entry, 'binary_sensor') config_entry, 'binary_sensor')
# To flush out the service call to update the group # To flush out the service call to update the group
await hass.async_block_till_done() await hass.async_block_till_done()
return gateway
async def test_platform_manually_configured(hass): 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): async def test_no_binary_sensors(hass):
"""Test that no sensors in deconz results in no sensor entities.""" """Test that no sensors in deconz results in no sensor entities."""
data = {} data = {}
await setup_gateway(hass, data) gateway = await setup_gateway(hass, data)
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert len(hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids) == 0
assert len(hass.states.async_all()) == 0 assert len(hass.states.async_all()) == 0
async def test_binary_sensors(hass): async def test_binary_sensors(hass):
"""Test successful creation of binary sensor entities.""" """Test successful creation of binary sensor entities."""
data = {"sensors": SENSOR} data = {"sensors": SENSOR}
await setup_gateway(hass, data) gateway = await setup_gateway(hass, data)
assert "binary_sensor.sensor_1_name" in \ assert "binary_sensor.sensor_1_name" in gateway.deconz_ids
hass.data[deconz.DOMAIN].deconz_ids assert "binary_sensor.sensor_2_name" not in gateway.deconz_ids
assert "binary_sensor.sensor_2_name" not in \
hass.data[deconz.DOMAIN].deconz_ids
assert len(hass.states.async_all()) == 1 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}}) {'state': {'on': False}})
async def test_add_new_sensor(hass): async def test_add_new_sensor(hass):
"""Test successful creation of sensor entities.""" """Test successful creation of sensor entities."""
data = {} data = {}
await setup_gateway(hass, data) gateway = await setup_gateway(hass, data)
sensor = Mock() sensor = Mock()
sensor.name = 'name' sensor.name = 'name'
sensor.type = 'ZHAPresence' sensor.type = 'ZHAPresence'
sensor.register_async_callback = Mock() 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() 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): async def test_do_not_allow_clip_sensor(hass):
"""Test that clip sensors can be ignored.""" """Test that clip sensors can be ignored."""
data = {} data = {}
await setup_gateway(hass, data, allow_clip_sensor=False) gateway = await setup_gateway(hass, data, allow_clip_sensor=False)
sensor = Mock() sensor = Mock()
sensor.name = 'name' sensor.name = 'name'
sensor.type = 'CLIPPresence' sensor.type = 'CLIPPresence'
sensor.register_async_callback = Mock() 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() 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): async def test_unload_switch(hass):
"""Test that it works to unload switch entities.""" """Test that it works to unload switch entities."""
data = {"sensors": SENSOR} 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 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 = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(hass.loop, session, **config_entry.data) gateway.api = DeconzSession(hass.loop, session, **config_entry.data)
gateway.api.config = Mock() gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state', with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)): return_value=mock_coro(data)):
@ -75,6 +75,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
config_entry, 'climate') config_entry, 'climate')
# To flush out the service call to update the group # To flush out the service call to update the group
await hass.async_block_till_done() await hass.async_block_till_done()
return gateway
async def test_platform_manually_configured(hass): async def test_platform_manually_configured(hass):
@ -89,26 +90,26 @@ async def test_platform_manually_configured(hass):
async def test_no_sensors(hass): async def test_no_sensors(hass):
"""Test that no sensors in deconz results in no climate entities.""" """Test that no sensors in deconz results in no climate entities."""
await setup_gateway(hass, {}) gateway = await setup_gateway(hass, {})
assert not hass.data[deconz.DOMAIN].deconz_ids assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert not hass.states.async_all() assert not hass.states.async_all()
async def test_climate_devices(hass): async def test_climate_devices(hass):
"""Test successful creation of sensor entities.""" """Test successful creation of sensor entities."""
await setup_gateway(hass, {"sensors": SENSOR}) gateway = await setup_gateway(hass, {"sensors": SENSOR})
assert "climate.climate_1_name" in hass.data[deconz.DOMAIN].deconz_ids assert "climate.climate_1_name" in gateway.deconz_ids
assert "sensor.sensor_2_name" not in hass.data[deconz.DOMAIN].deconz_ids assert "sensor.sensor_2_name" not in gateway.deconz_ids
assert len(hass.states.async_all()) == 1 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}}) {'state': {'on': False}})
await hass.services.async_call( await hass.services.async_call(
'climate', 'turn_on', {'entity_id': 'climate.climate_1_name'}, 'climate', 'turn_on', {'entity_id': 'climate.climate_1_name'},
blocking=True 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', 'http://1.2.3.4:80/api/ABCDEF/sensors/1/config',
data='{"mode": "auto"}' data='{"mode": "auto"}'
) )
@ -117,7 +118,7 @@ async def test_climate_devices(hass):
'climate', 'turn_off', {'entity_id': 'climate.climate_1_name'}, 'climate', 'turn_off', {'entity_id': 'climate.climate_1_name'},
blocking=True 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', 'http://1.2.3.4:80/api/ABCDEF/sensors/1/config',
data='{"mode": "off"}' data='{"mode": "off"}'
) )
@ -127,18 +128,18 @@ async def test_climate_devices(hass):
{'entity_id': 'climate.climate_1_name', 'temperature': 20}, {'entity_id': 'climate.climate_1_name', 'temperature': 20},
blocking=True 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', 'http://1.2.3.4:80/api/ABCDEF/sensors/1/config',
data='{"heatsetpoint": 2000.0}' 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): async def test_verify_state_update(hass):
"""Test that state update properly.""" """Test that state update properly."""
await setup_gateway(hass, {"sensors": SENSOR}) gateway = await setup_gateway(hass, {"sensors": SENSOR})
assert "climate.climate_1_name" in hass.data[deconz.DOMAIN].deconz_ids assert "climate.climate_1_name" in gateway.deconz_ids
thermostat = hass.states.get('climate.climate_1_name') thermostat = hass.states.get('climate.climate_1_name')
assert thermostat.state == 'on' assert thermostat.state == 'on'
@ -150,7 +151,7 @@ async def test_verify_state_update(hass):
"id": "1", "id": "1",
"config": {"on": False} "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() await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1 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): async def test_add_new_climate_device(hass):
"""Test successful creation of climate entities.""" """Test successful creation of climate entities."""
await setup_gateway(hass, {}) gateway = await setup_gateway(hass, {})
sensor = Mock() sensor = Mock()
sensor.name = 'name' sensor.name = 'name'
sensor.type = 'ZHAThermostat' sensor.type = 'ZHAThermostat'
sensor.register_async_callback = Mock() 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() 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): async def test_do_not_allow_clipsensor(hass):
"""Test that clip sensors can be ignored.""" """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 = Mock()
sensor.name = 'name' sensor.name = 'name'
sensor.type = 'CLIPThermostat' sensor.type = 'CLIPThermostat'
sensor.register_async_callback = Mock() 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() 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): async def test_unload_sensor(hass):
"""Test that it works to unload sensor entities.""" """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 assert len(hass.states.async_all()) == 0

View file

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

View file

@ -61,7 +61,7 @@ async def setup_gateway(hass, data):
gateway = deconz.DeconzGateway(hass, config_entry) gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data) gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock() gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state', with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)): 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') await hass.config_entries.async_forward_entry_setup(config_entry, 'cover')
# To flush out the service call to update the group # To flush out the service call to update the group
await hass.async_block_till_done() await hass.async_block_till_done()
return gateway
async def test_platform_manually_configured(hass): async def test_platform_manually_configured(hass):
@ -84,8 +85,8 @@ async def test_platform_manually_configured(hass):
async def test_no_covers(hass): async def test_no_covers(hass):
"""Test that no cover entities are created.""" """Test that no cover entities are created."""
await setup_gateway(hass, {}) gateway = await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0 assert len(hass.states.async_all()) == 0
@ -93,8 +94,8 @@ async def test_cover(hass):
"""Test that all supported cover entities are created.""" """Test that all supported cover entities are created."""
with patch('pydeconz.DeconzSession.async_put_state', with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)): return_value=mock_coro(True)):
await setup_gateway(hass, {"lights": SUPPORTED_COVERS}) gateway = await setup_gateway(hass, {"lights": SUPPORTED_COVERS})
assert "cover.cover_1_name" in hass.data[deconz.DOMAIN].deconz_ids assert "cover.cover_1_name" in gateway.deconz_ids
assert len(SUPPORTED_COVERS) == len(COVER_TYPES) assert len(SUPPORTED_COVERS) == len(COVER_TYPES)
assert len(hass.states.async_all()) == 3 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 is not None
assert cover_1.state == 'closed' 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', { await hass.services.async_call('cover', 'open_cover', {
'entity_id': 'cover.cover_1_name' 'entity_id': 'cover.cover_1_name'
@ -122,14 +123,15 @@ async def test_cover(hass):
async def test_add_new_cover(hass): async def test_add_new_cover(hass):
"""Test successful creation of cover entity.""" """Test successful creation of cover entity."""
data = {} data = {}
await setup_gateway(hass, data) gateway = await setup_gateway(hass, data)
cover = Mock() cover = Mock()
cover.name = 'name' cover.name = 'name'
cover.type = "Level controllable output" cover.type = "Level controllable output"
cover.register_async_callback = Mock() 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() 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): async def test_unsupported_cover(hass):
@ -140,8 +142,8 @@ async def test_unsupported_cover(hass):
async def test_unload_cover(hass): async def test_unload_cover(hass):
"""Test that it works to unload switch entities.""" """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 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 from tests.common import mock_coro, MockConfigEntry
ENTRY1_HOST = '1.2.3.4'
ENTRY1_PORT = 80
ENTRY1_API_KEY = '1234567890ABCDEF'
ENTRY1_BRIDGEID = '12345ABC'
CONFIG = { ENTRY2_HOST = '2.3.4.5'
"config": { ENTRY2_PORT = 80
"bridgeid": "0123456789ABCDEF", ENTRY2_API_KEY = '1234567890ABCDEF'
"mac": "12:34:56:78:90:ab", ENTRY2_BRIDGEID = '23456DEF'
"modelid": "deCONZ",
"name": "Phoscon",
"swversion": "2.05.35" 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): async def test_config_with_host_passed_to_config_entry(hass):
"""Test that configured options for a host are loaded via config entry.""" """Test that configured options for a host are loaded via config entry."""
with patch.object(hass, 'config_entries') as mock_config_entries, \ with patch.object(hass.config_entries, 'flow') as mock_config_flow:
patch.object(deconz, 'configured_hosts', return_value=[]):
assert await async_setup_component(hass, deconz.DOMAIN, { assert await async_setup_component(hass, deconz.DOMAIN, {
deconz.DOMAIN: { deconz.DOMAIN: {
deconz.CONF_HOST: '1.2.3.4', deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: 80 deconz.CONF_PORT: ENTRY1_PORT
} }
}) is True }) is True
# Import flow started # 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): async def test_config_without_host_not_passed_to_config_entry(hass):
"""Test that a configuration without a host does not initiate an import.""" """Test that a configuration without a host does not initiate an import."""
with patch.object(hass, 'config_entries') as mock_config_entries, \ MockConfigEntry(domain=deconz.DOMAIN, data={}).add_to_hass(hass)
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, { assert await async_setup_component(hass, deconz.DOMAIN, {
deconz.DOMAIN: {} deconz.DOMAIN: {}
}) is True }) is True
# No flow started # 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.""" """Test that an already registered host does not initiate an import."""
with patch.object(hass, 'config_entries') as mock_config_entries, \ MockConfigEntry(domain=deconz.DOMAIN, data={}).add_to_hass(hass)
patch.object(deconz, 'configured_hosts', with patch.object(hass.config_entries, 'flow') as mock_config_flow:
return_value=['1.2.3.4']):
assert await async_setup_component(hass, deconz.DOMAIN, { assert await async_setup_component(hass, deconz.DOMAIN, {
deconz.DOMAIN: { deconz.DOMAIN: {
deconz.CONF_HOST: '1.2.3.4', deconz.CONF_HOST: ENTRY1_HOST,
deconz.CONF_PORT: 80 deconz.CONF_PORT: ENTRY1_PORT
} }
}) is True }) is True
# No flow started # 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): 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 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): async def test_setup_entry_fails(hass):
"""Test setup entry fails if deCONZ is not available.""" """Test setup entry fails if deCONZ is not available."""
entry = Mock() 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', with patch('pydeconz.DeconzSession.async_load_parameters',
side_effect=Exception): side_effect=Exception):
await deconz.async_setup_entry(hass, entry) 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): async def test_setup_entry_no_available_bridge(hass):
"""Test setup entry fails if deCONZ is not available.""" """Test setup entry fails if deCONZ is not available."""
entry = Mock() entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} entry.data = {
with patch( deconz.CONF_HOST: ENTRY1_HOST,
'pydeconz.DeconzSession.async_load_parameters', deconz.CONF_PORT: ENTRY1_PORT,
side_effect=asyncio.TimeoutError deconz.CONF_API_KEY: ENTRY1_API_KEY
), pytest.raises(ConfigEntryNotReady): }
with patch('pydeconz.DeconzSession.async_load_parameters',
side_effect=asyncio.TimeoutError),\
pytest.raises(ConfigEntryNotReady):
await deconz.async_setup_entry(hass, entry) await deconz.async_setup_entry(hass, entry)
async def test_setup_entry_successful(hass): async def test_setup_entry_successful(hass):
"""Test setup entry is successful.""" """Test setup entry is successful."""
entry = MockConfigEntry(domain=deconz.DOMAIN, 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) entry.add_to_hass(hass)
mock_registry = Mock()
with patch.object(deconz, 'DeconzGateway') as mock_gateway, \ await setup_entry(hass, entry)
patch('homeassistant.helpers.device_registry.async_get_registry',
return_value=mock_coro(mock_registry)): assert ENTRY1_BRIDGEID in hass.data[deconz.DOMAIN]
mock_gateway.return_value.async_setup.return_value = mock_coro(True) assert hass.data[deconz.DOMAIN][ENTRY1_BRIDGEID].master
assert await deconz.async_setup_entry(hass, entry) is True
assert hass.data[deconz.DOMAIN]
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): async def test_unload_entry(hass):
"""Test being able to unload an entry.""" """Test being able to unload an entry."""
entry = MockConfigEntry(domain=deconz.DOMAIN, 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) 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) 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 await deconz.async_unload_entry(hass, entry)
assert deconz.DOMAIN not in hass.data
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): async def test_service_configure(hass):
"""Test that service invokes pydeconz with the correct path and data.""" """Test that service invokes pydeconz with the correct path and data."""
entry = MockConfigEntry(domain=deconz.DOMAIN, 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) 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' 'light.test': '/light/1'
} }
data = {'on': True, 'attr1': 10, 'attr2': 20} data = {'on': True, 'attr1': 10, 'attr2': 20}
@ -191,24 +255,22 @@ async def test_service_configure(hass):
async def test_service_refresh_devices(hass): async def test_service_refresh_devices(hass):
"""Test that service can refresh devices.""" """Test that service can refresh devices."""
entry = MockConfigEntry(domain=deconz.DOMAIN, 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) entry.add_to_hass(hass)
mock_registry = Mock()
with patch.object(deconz, 'DeconzGateway') as mock_gateway, \ await setup_entry(hass, entry)
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', with patch('pydeconz.DeconzSession.async_load_parameters',
return_value=mock_coro(True)): return_value=mock_coro(True)):
await hass.services.async_call( await hass.services.async_call(
'deconz', 'device_refresh', service_data={}) 'deconz', 'device_refresh', service_data={})
await hass.async_block_till_done() await hass.async_block_till_done()
with patch.object(hass.data[deconz.DOMAIN].api, 'async_load_parameters', with patch('pydeconz.DeconzSession.async_load_parameters',
return_value=mock_coro(False)): return_value=mock_coro(False)):
await hass.services.async_call( await hass.services.async_call(
'deconz', 'device_refresh', service_data={}) 'deconz', 'device_refresh', service_data={})

View file

@ -87,7 +87,7 @@ async def setup_gateway(hass, data, allow_deconz_groups=True):
gateway = deconz.DeconzGateway(hass, config_entry) gateway = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data) gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock() gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state', with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)): 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') await hass.config_entries.async_forward_entry_setup(config_entry, 'light')
# To flush out the service call to update the group # To flush out the service call to update the group
await hass.async_block_till_done() await hass.async_block_till_done()
return gateway
async def test_platform_manually_configured(hass): 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): async def test_no_lights_or_groups(hass):
"""Test that no lights or groups entities are created.""" """Test that no lights or groups entities are created."""
await setup_gateway(hass, {}) gateway = await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0 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.""" """Test that lights or groups entities are created."""
with patch('pydeconz.DeconzSession.async_put_state', with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)): return_value=mock_coro(True)):
await setup_gateway(hass, {"lights": LIGHT, "groups": GROUP}) gateway = await setup_gateway(
assert "light.light_1_name" in hass.data[deconz.DOMAIN].deconz_ids hass, {"lights": LIGHT, "groups": GROUP})
assert "light.light_2_name" in hass.data[deconz.DOMAIN].deconz_ids assert "light.light_1_name" in gateway.deconz_ids
assert "light.group_1_name" in hass.data[deconz.DOMAIN].deconz_ids assert "light.light_2_name" in gateway.deconz_ids
assert "light.group_2_name" not in hass.data[deconz.DOMAIN].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 assert len(hass.states.async_all()) == 4
lamp_1 = hass.states.get('light.light_1_name') 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.state == 'on'
assert light_2.attributes['color_temp'] == 2500 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', { await hass.services.async_call('light', 'turn_on', {
'entity_id': 'light.light_1_name', 'entity_id': 'light.light_1_name',
@ -166,49 +168,52 @@ async def test_lights_and_groups(hass):
async def test_add_new_light(hass): async def test_add_new_light(hass):
"""Test successful creation of light entities.""" """Test successful creation of light entities."""
await setup_gateway(hass, {}) gateway = await setup_gateway(hass, {})
light = Mock() light = Mock()
light.name = 'name' light.name = 'name'
light.register_async_callback = Mock() 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() 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): async def test_add_new_group(hass):
"""Test successful creation of group entities.""" """Test successful creation of group entities."""
await setup_gateway(hass, {}) gateway = await setup_gateway(hass, {})
group = Mock() group = Mock()
group.name = 'name' group.name = 'name'
group.register_async_callback = Mock() 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() 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): async def test_do_not_add_deconz_groups(hass):
"""Test that clip sensors can be ignored.""" """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 = Mock()
group.name = 'name' group.name = 'name'
group.register_async_callback = Mock() 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() 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): async def test_no_switch(hass):
"""Test that a switch doesn't get created as a light entity.""" """Test that a switch doesn't get created as a light entity."""
await setup_gateway(hass, {"lights": SWITCH}) gateway = await setup_gateway(hass, {"lights": SWITCH})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert len(gateway.deconz_ids) == 0
assert len(hass.states.async_all()) == 0 assert len(hass.states.async_all()) == 0
async def test_unload_light(hass): async def test_unload_light(hass):
"""Test that it works to unload switch entities.""" """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 # Group.all_lights will not be removed
assert len(hass.states.async_all()) == 1 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 = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data) gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock() gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state', with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)): 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') await hass.config_entries.async_forward_entry_setup(config_entry, 'scene')
# To flush out the service call to update the group # To flush out the service call to update the group
await hass.async_block_till_done() await hass.async_block_till_done()
return gateway
async def test_platform_manually_configured(hass): async def test_platform_manually_configured(hass):
@ -70,8 +71,8 @@ async def test_platform_manually_configured(hass):
async def test_no_scenes(hass): async def test_no_scenes(hass):
"""Test that scenes can be loaded without scenes being available.""" """Test that scenes can be loaded without scenes being available."""
await setup_gateway(hass, {}) gateway = await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0 assert len(hass.states.async_all()) == 0
@ -79,8 +80,8 @@ async def test_scenes(hass):
"""Test that scenes works.""" """Test that scenes works."""
with patch('pydeconz.DeconzSession.async_put_state', with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)): return_value=mock_coro(True)):
await setup_gateway(hass, {"groups": GROUP}) gateway = await setup_gateway(hass, {"groups": GROUP})
assert "scene.group_1_name_scene_1" in hass.data[deconz.DOMAIN].deconz_ids assert "scene.group_1_name_scene_1" in gateway.deconz_ids
assert len(hass.states.async_all()) == 1 assert len(hass.states.async_all()) == 1
await hass.services.async_call('scene', 'turn_on', { await hass.services.async_call('scene', 'turn_on', {
@ -90,8 +91,8 @@ async def test_scenes(hass):
async def test_unload_scene(hass): async def test_unload_scene(hass):
"""Test that it works to unload scene entities.""" """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 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 = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data) gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock() gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state', with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)): return_value=mock_coro(data)):
@ -101,6 +101,7 @@ async def setup_gateway(hass, data, allow_clip_sensor=True):
config_entry, 'sensor') config_entry, 'sensor')
# To flush out the service call to update the group # To flush out the service call to update the group
await hass.async_block_till_done() await hass.async_block_till_done()
return gateway
async def test_platform_manually_configured(hass): async def test_platform_manually_configured(hass):
@ -115,58 +116,56 @@ async def test_platform_manually_configured(hass):
async def test_no_sensors(hass): async def test_no_sensors(hass):
"""Test that no sensors in deconz results in no sensor entities.""" """Test that no sensors in deconz results in no sensor entities."""
await setup_gateway(hass, {}) gateway = await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0 assert len(hass.states.async_all()) == 0
async def test_sensors(hass): async def test_sensors(hass):
"""Test successful creation of sensor entities.""" """Test successful creation of sensor entities."""
await setup_gateway(hass, {"sensors": SENSOR}) gateway = await setup_gateway(hass, {"sensors": SENSOR})
assert "sensor.sensor_1_name" in hass.data[deconz.DOMAIN].deconz_ids assert "sensor.sensor_1_name" in gateway.deconz_ids
assert "sensor.sensor_2_name" not in hass.data[deconz.DOMAIN].deconz_ids assert "sensor.sensor_2_name" not in gateway.deconz_ids
assert "sensor.sensor_3_name" not in hass.data[deconz.DOMAIN].deconz_ids assert "sensor.sensor_3_name" not in gateway.deconz_ids
assert "sensor.sensor_3_name_battery_level" not in \ assert "sensor.sensor_3_name_battery_level" not in gateway.deconz_ids
hass.data[deconz.DOMAIN].deconz_ids assert "sensor.sensor_4_name" not in gateway.deconz_ids
assert "sensor.sensor_4_name" not in hass.data[deconz.DOMAIN].deconz_ids assert "sensor.sensor_4_name_battery_level" in gateway.deconz_ids
assert "sensor.sensor_4_name_battery_level" in \
hass.data[deconz.DOMAIN].deconz_ids
assert len(hass.states.async_all()) == 5 assert len(hass.states.async_all()) == 5
hass.data[deconz.DOMAIN].api.sensors['1'].async_update( gateway.api.sensors['1'].async_update({'state': {'on': False}})
{'state': {'on': False}}) gateway.api.sensors['4'].async_update({'config': {'battery': 75}})
hass.data[deconz.DOMAIN].api.sensors['4'].async_update(
{'config': {'battery': 75}})
async def test_add_new_sensor(hass): async def test_add_new_sensor(hass):
"""Test successful creation of sensor entities.""" """Test successful creation of sensor entities."""
await setup_gateway(hass, {}) gateway = await setup_gateway(hass, {})
sensor = Mock() sensor = Mock()
sensor.name = 'name' sensor.name = 'name'
sensor.type = 'ZHATemperature' sensor.type = 'ZHATemperature'
sensor.register_async_callback = Mock() 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() 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): async def test_do_not_allow_clipsensor(hass):
"""Test that clip sensors can be ignored.""" """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 = Mock()
sensor.name = 'name' sensor.name = 'name'
sensor.type = 'CLIPTemperature' sensor.type = 'CLIPTemperature'
sensor.register_async_callback = Mock() 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() 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): async def test_unload_sensor(hass):
"""Test that it works to unload sensor entities.""" """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 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 = deconz.DeconzGateway(hass, config_entry)
gateway.api = DeconzSession(loop, session, **config_entry.data) gateway.api = DeconzSession(loop, session, **config_entry.data)
gateway.api.config = Mock() gateway.api.config = Mock()
hass.data[deconz.DOMAIN] = gateway hass.data[deconz.DOMAIN] = {gateway.bridgeid: gateway}
with patch('pydeconz.DeconzSession.async_get_state', with patch('pydeconz.DeconzSession.async_get_state',
return_value=mock_coro(data)): 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') await hass.config_entries.async_forward_entry_setup(config_entry, 'switch')
# To flush out the service call to update the group # To flush out the service call to update the group
await hass.async_block_till_done() await hass.async_block_till_done()
return gateway
async def test_platform_manually_configured(hass): async def test_platform_manually_configured(hass):
@ -88,8 +89,8 @@ async def test_platform_manually_configured(hass):
async def test_no_switches(hass): async def test_no_switches(hass):
"""Test that no switch entities are created.""" """Test that no switch entities are created."""
await setup_gateway(hass, {}) gateway = await setup_gateway(hass, {})
assert len(hass.data[deconz.DOMAIN].deconz_ids) == 0 assert not hass.data[deconz.DOMAIN][gateway.bridgeid].deconz_ids
assert len(hass.states.async_all()) == 0 assert len(hass.states.async_all()) == 0
@ -97,10 +98,10 @@ async def test_switches(hass):
"""Test that all supported switch entities are created.""" """Test that all supported switch entities are created."""
with patch('pydeconz.DeconzSession.async_put_state', with patch('pydeconz.DeconzSession.async_put_state',
return_value=mock_coro(True)): return_value=mock_coro(True)):
await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES}) gateway = await setup_gateway(hass, {"lights": SUPPORTED_SWITCHES})
assert "switch.switch_1_name" in hass.data[deconz.DOMAIN].deconz_ids assert "switch.switch_1_name" in gateway.deconz_ids
assert "switch.switch_2_name" in hass.data[deconz.DOMAIN].deconz_ids assert "switch.switch_2_name" in gateway.deconz_ids
assert "switch.switch_3_name" in hass.data[deconz.DOMAIN].deconz_ids assert "switch.switch_3_name" in gateway.deconz_ids
assert len(SUPPORTED_SWITCHES) == len(SWITCH_TYPES) assert len(SUPPORTED_SWITCHES) == len(SWITCH_TYPES)
assert len(hass.states.async_all()) == 4 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 is not None
assert switch_3.state == 'on' 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', { await hass.services.async_call('switch', 'turn_on', {
'entity_id': 'switch.switch_1_name' 'entity_id': 'switch.switch_1_name'
@ -130,14 +131,15 @@ async def test_switches(hass):
async def test_add_new_switch(hass): async def test_add_new_switch(hass):
"""Test successful creation of switch entity.""" """Test successful creation of switch entity."""
await setup_gateway(hass, {}) gateway = await setup_gateway(hass, {})
switch = Mock() switch = Mock()
switch.name = 'name' switch.name = 'name'
switch.type = "Smart plug" switch.type = "Smart plug"
switch.register_async_callback = Mock() 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() 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): async def test_unsupported_switch(hass):
@ -148,8 +150,8 @@ async def test_unsupported_switch(hass):
async def test_unload_switch(hass): async def test_unload_switch(hass):
"""Test that it works to unload switch entities.""" """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 assert len(hass.states.async_all()) == 1