Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deCONZ - retry if setup fails #17772

Merged
merged 22 commits into from Oct 31, 2018
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 4 additions & 1 deletion .coveragerc
Expand Up @@ -77,7 +77,10 @@ omit =
homeassistant/components/*/daikin.py

homeassistant/components/deconz/*
homeassistant/components/*/deconz.py
homeassistant/components/cover/deconz.py
homeassistant/components/light/deconz.py
homeassistant/components/scene/deconz.py
homeassistant/components/sensor/deconz.py

homeassistant/components/digital_ocean.py
homeassistant/components/*/digital_ocean.py
Expand Down
11 changes: 6 additions & 5 deletions homeassistant/components/binary_sensor/deconz.py
Expand Up @@ -7,7 +7,7 @@
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.deconz.const import (
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ,
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DECONZ_DOMAIN)
DECONZ_DOMAIN)
from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.core import callback
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
Expand Down Expand Up @@ -36,10 +36,10 @@ def async_add_sensor(sensors):
entities.append(DeconzBinarySensor(sensor))
async_add_entities(entities, True)

hass.data[DATA_DECONZ_UNSUB].append(
hass.data[DATA_DECONZ].listeners.append(
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor))

async_add_sensor(hass.data[DATA_DECONZ].sensors.values())
async_add_sensor(hass.data[DATA_DECONZ].api.sensors.values())


class DeconzBinarySensor(BinarySensorDevice):
Expand All @@ -52,7 +52,8 @@ def __init__(self, sensor):
async def async_added_to_hass(self):
"""Subscribe sensors events."""
self._sensor.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \
self._sensor.deconz_id

async def async_will_remove_from_hass(self) -> None:
"""Disconnect sensor object when removed."""
Expand Down Expand Up @@ -127,7 +128,7 @@ def device_info(self):
self._sensor.uniqueid.count(':') != 7):
return None
serial = self._sensor.uniqueid.split('-', 1)[0]
bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid
bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid
return {
'connections': {(CONNECTION_ZIGBEE, serial)},
'identifiers': {(DECONZ_DOMAIN, serial)},
Expand Down
12 changes: 6 additions & 6 deletions homeassistant/components/cover/deconz.py
Expand Up @@ -5,8 +5,7 @@
https://home-assistant.io/components/cover.deconz/
"""
from homeassistant.components.deconz.const import (
COVER_TYPES, DAMPERS, DOMAIN as DATA_DECONZ, DATA_DECONZ_ID,
DATA_DECONZ_UNSUB, DECONZ_DOMAIN, WINDOW_COVERS)
COVER_TYPES, DAMPERS, DOMAIN as DATA_DECONZ, DECONZ_DOMAIN, WINDOW_COVERS)
from homeassistant.components.cover import (
ATTR_POSITION, CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP,
SUPPORT_SET_POSITION)
Expand Down Expand Up @@ -42,10 +41,10 @@ def async_add_cover(lights):
entities.append(DeconzCover(light))
async_add_entities(entities, True)

hass.data[DATA_DECONZ_UNSUB].append(
hass.data[DATA_DECONZ].listeners.append(
async_dispatcher_connect(hass, 'deconz_new_light', async_add_cover))

async_add_cover(hass.data[DATA_DECONZ].lights.values())
async_add_cover(hass.data[DATA_DECONZ].api.lights.values())


class DeconzCover(CoverDevice):
Expand All @@ -62,7 +61,8 @@ def __init__(self, cover):
async def async_added_to_hass(self):
"""Subscribe to covers events."""
self._cover.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._cover.deconz_id
self.hass.data[DATA_DECONZ].deconz_ids[self.entity_id] = \
self._cover.deconz_id

async def async_will_remove_from_hass(self) -> None:
"""Disconnect cover object when removed."""
Expand Down Expand Up @@ -151,7 +151,7 @@ def device_info(self):
self._cover.uniqueid.count(':') != 7):
return None
serial = self._cover.uniqueid.split('-', 1)[0]
bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid
bridgeid = self.hass.data[DATA_DECONZ].api.config.bridgeid
return {
'connections': {(CONNECTION_ZIGBEE, serial)},
'identifiers': {(DECONZ_DOMAIN, serial)},
Expand Down
149 changes: 30 additions & 119 deletions homeassistant/components/deconz/__init__.py
Expand Up @@ -23,6 +23,7 @@
from .const import (
CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
from .gateway import DeconzGateway

REQUIREMENTS = ['pydeconz==47']

Expand Down Expand Up @@ -80,61 +81,26 @@ async def async_setup_entry(hass, config_entry):
Load config, group, light and sensor data for server information.
Start websocket for push notification of state changes from deCONZ.
"""
from pydeconz import DeconzSession
if DOMAIN in hass.data:
_LOGGER.error(
"Config entry failed since one deCONZ instance already exists")
return False

@callback
def async_add_device_callback(device_type, device):
"""Handle event of new device creation in deCONZ."""
if not isinstance(device, list):
device = [device]
async_dispatcher_send(
hass, 'deconz_new_{}'.format(device_type), device)
gateway = DeconzGateway(hass, config_entry)

session = aiohttp_client.async_get_clientsession(hass)
deconz = DeconzSession(hass.loop, session, **config_entry.data,
async_add_device=async_add_device_callback)
result = await deconz.async_load_parameters()
hass.data[DOMAIN] = gateway

if result is False:
if not await gateway.async_setup():
return False

hass.data[DOMAIN] = deconz
hass.data[DATA_DECONZ_ID] = {}
hass.data[DATA_DECONZ_EVENT] = []
hass.data[DATA_DECONZ_UNSUB] = []

for component in SUPPORTED_PLATFORMS:
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
config_entry, component))

@callback
def async_add_remote(sensors):
"""Set up remote from deCONZ."""
from pydeconz.sensor import SWITCH as DECONZ_REMOTE
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
if sensor.type in DECONZ_REMOTE and \
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
hass.data[DATA_DECONZ_EVENT].append(DeconzEvent(hass, sensor))
hass.data[DATA_DECONZ_UNSUB].append(
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_remote))

async_add_remote(deconz.sensors.values())

deconz.start()

device_registry = await \
hass.helpers.device_registry.async_get_registry()
device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id,
connections={(CONNECTION_NETWORK_MAC, deconz.config.mac)},
identifiers={(DOMAIN, deconz.config.bridgeid)},
manufacturer='Dresden Elektronik', model=deconz.config.modelid,
name=deconz.config.name, sw_version=deconz.config.swversion)
connections={(CONNECTION_NETWORK_MAC, gateway.api.config.mac)},
identifiers={(DOMAIN, gateway.api.config.bridgeid)},
manufacturer='Dresden Elektronik', model=gateway.api.config.modelid,
name=gateway.api.config.name, sw_version=gateway.api.config.swversion)

async def async_configure(call):
"""Set attribute of device in deCONZ.
Expand All @@ -155,121 +121,66 @@ async def async_configure(call):
field = call.data.get(SERVICE_FIELD, '')
entity_id = call.data.get(SERVICE_ENTITY)
data = call.data.get(SERVICE_DATA)
deconz = hass.data[DOMAIN]
gateway = hass.data[DOMAIN]

if entity_id:
try:
field = hass.data[DATA_DECONZ_ID][entity_id] + field
field = gateway.deconz_ids.get(entity_id) + field
except KeyError:
_LOGGER.error('Could not find the entity %s', entity_id)
return

await deconz.async_put_state(field, data)
await gateway.api.async_put_state(field, data)

hass.services.async_register(
DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA)

async def async_refresh_devices(call):
"""Refresh available devices from deCONZ."""
deconz = hass.data[DOMAIN]
gateway = hass.data[DOMAIN]

groups = list(deconz.groups.keys())
lights = list(deconz.lights.keys())
scenes = list(deconz.scenes.keys())
sensors = list(deconz.sensors.keys())
groups = set(gateway.api.groups.keys())
lights = set(gateway.api.lights.keys())
scenes = set(gateway.api.scenes.keys())
sensors = set(gateway.api.sensors.keys())

if not await deconz.async_load_parameters():
if not await gateway.api.async_load_parameters():
return

async_add_device_callback(
gateway.async_add_device_callback(
'group', [group
for group_id, group in deconz.groups.items()
for group_id, group in gateway.api.groups.items()
if group_id not in groups]
)

async_add_device_callback(
gateway.async_add_device_callback(
'light', [light
for light_id, light in deconz.lights.items()
for light_id, light in gateway.api.lights.items()
if light_id not in lights]
)

async_add_device_callback(
gateway.async_add_device_callback(
'scene', [scene
for scene_id, scene in deconz.scenes.items()
for scene_id, scene in gateway.api.scenes.items()
if scene_id not in scenes]
)

async_add_device_callback(
gateway.async_add_device_callback(
'sensor', [sensor
for sensor_id, sensor in deconz.sensors.items()
for sensor_id, sensor in gateway.api.sensors.items()
if sensor_id not in sensors]
)

hass.services.async_register(
DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices)

@callback
def deconz_shutdown(event):
"""
Wrap the call to deconz.close.

Used as an argument to EventBus.async_listen_once - EventBus calls
this method with the event as the first argument, which should not
be passed on to deconz.close.
"""
deconz.close()

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, deconz_shutdown)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, gateway.shutdown)
return True


async def async_unload_entry(hass, config_entry):
"""Unload deCONZ config entry."""
deconz = hass.data.pop(DOMAIN)
gateway = hass.data.pop(DOMAIN)
hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
deconz.close()

for component in SUPPORTED_PLATFORMS:
await hass.config_entries.async_forward_entry_unload(
config_entry, component)

dispatchers = hass.data[DATA_DECONZ_UNSUB]
for unsub_dispatcher in dispatchers:
unsub_dispatcher()
hass.data[DATA_DECONZ_UNSUB] = []

for event in hass.data[DATA_DECONZ_EVENT]:
event.async_will_remove_from_hass()
hass.data[DATA_DECONZ_EVENT].remove(event)

hass.data[DATA_DECONZ_ID] = []

return True


class DeconzEvent:
"""When you want signals instead of entities.

Stateless sensors such as remotes are expected to generate an event
instead of a sensor entity in hass.
"""

def __init__(self, hass, device):
"""Register callback that will be used for signals."""
self._hass = hass
self._device = device
self._device.register_async_callback(self.async_update_callback)
self._event = 'deconz_{}'.format(CONF_EVENT)
self._id = slugify(self._device.name)

@callback
def async_will_remove_from_hass(self) -> None:
"""Disconnect event object when removed."""
self._device.remove_callback(self.async_update_callback)
self._device = None

@callback
def async_update_callback(self, reason):
"""Fire the event if reason is that state is updated."""
if reason['state']:
data = {CONF_ID: self._id, CONF_EVENT: self._device.state}
self._hass.bus.async_fire(self._event, data, EventOrigin.remote)
hass.services.async_remove(DOMAIN, SERVICE_DEVICE_REFRESH)
return await gateway.async_reset()
3 changes: 3 additions & 0 deletions homeassistant/components/deconz/const.py
Expand Up @@ -13,6 +13,9 @@
CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor'
CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups'

SUPPORTED_PLATFORMS = ['binary_sensor', 'cover',
'light', 'scene', 'sensor', 'switch']

ATTR_DARK = 'dark'
ATTR_ON = 'on'

Expand Down