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:
parent
b9ec623ad9
commit
b50afec5f1
22 changed files with 535 additions and 426 deletions
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
@ -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,
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
@ -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."""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
@ -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'
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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={})
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue