deCONZ allow unloading of config entry (#14115)
* Working but incomplete * Remove events on unload * Add unload test * Fix failing sensor test * Improve unload test * Move DeconzEvent to init * Fix visual under-indentation
This commit is contained in:
parent
ef48a7ca2c
commit
3fd4987baf
8 changed files with 86 additions and 31 deletions
|
@ -62,6 +62,11 @@ async def async_setup_entry(hass, entry):
|
||||||
return await hass.data[DOMAIN].async_setup_entry(entry)
|
return await hass.data[DOMAIN].async_setup_entry(entry)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, entry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return await hass.data[DOMAIN].async_unload_entry(entry)
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
class BinarySensorDevice(Entity):
|
class BinarySensorDevice(Entity):
|
||||||
"""Represent a binary sensor."""
|
"""Represent a binary sensor."""
|
||||||
|
|
|
@ -7,14 +7,17 @@ https://home-assistant.io/components/deconz/
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
|
CONF_API_KEY, CONF_EVENT, CONF_HOST,
|
||||||
from homeassistant.core import callback
|
CONF_ID, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
|
||||||
|
from homeassistant.core import EventOrigin, callback
|
||||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||||
|
from homeassistant.util import slugify
|
||||||
from homeassistant.util.json import load_json
|
from homeassistant.util.json import load_json
|
||||||
|
|
||||||
# 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 configured_hosts
|
||||||
from .const import CONFIG_FILE, DATA_DECONZ_ID, DOMAIN, _LOGGER
|
from .const import (
|
||||||
|
CONFIG_FILE, DATA_DECONZ_EVENT, DATA_DECONZ_ID, DOMAIN, _LOGGER)
|
||||||
|
|
||||||
REQUIREMENTS = ['pydeconz==36']
|
REQUIREMENTS = ['pydeconz==36']
|
||||||
|
|
||||||
|
@ -26,6 +29,8 @@ CONFIG_SCHEMA = vol.Schema({
|
||||||
})
|
})
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
SERVICE_DECONZ = 'configure'
|
||||||
|
|
||||||
SERVICE_FIELD = 'field'
|
SERVICE_FIELD = 'field'
|
||||||
SERVICE_ENTITY = 'entity'
|
SERVICE_ENTITY = 'entity'
|
||||||
SERVICE_DATA = 'data'
|
SERVICE_DATA = 'data'
|
||||||
|
@ -64,6 +69,7 @@ async def async_setup_entry(hass, config_entry):
|
||||||
Start websocket for push notification of state changes from deCONZ.
|
Start websocket for push notification of state changes from deCONZ.
|
||||||
"""
|
"""
|
||||||
from pydeconz import DeconzSession
|
from pydeconz import DeconzSession
|
||||||
|
from pydeconz.sensor import SWITCH as DECONZ_REMOTE
|
||||||
if DOMAIN in hass.data:
|
if DOMAIN in hass.data:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Config entry failed since one deCONZ instance already exists")
|
"Config entry failed since one deCONZ instance already exists")
|
||||||
|
@ -82,6 +88,11 @@ async def async_setup_entry(hass, config_entry):
|
||||||
for component in ['binary_sensor', 'light', 'scene', 'sensor']:
|
for component in ['binary_sensor', 'light', 'scene', 'sensor']:
|
||||||
hass.async_add_job(hass.config_entries.async_forward_entry_setup(
|
hass.async_add_job(hass.config_entries.async_forward_entry_setup(
|
||||||
config_entry, component))
|
config_entry, component))
|
||||||
|
|
||||||
|
hass.data[DATA_DECONZ_EVENT] = [DeconzEvent(
|
||||||
|
hass, sensor) for sensor in deconz.sensors.values()
|
||||||
|
if sensor.type in DECONZ_REMOTE]
|
||||||
|
|
||||||
deconz.start()
|
deconz.start()
|
||||||
|
|
||||||
async def async_configure(call):
|
async def async_configure(call):
|
||||||
|
@ -112,7 +123,7 @@ async def async_setup_entry(hass, config_entry):
|
||||||
return
|
return
|
||||||
await deconz.async_put_state(field, data)
|
await deconz.async_put_state(field, data)
|
||||||
hass.services.async_register(
|
hass.services.async_register(
|
||||||
DOMAIN, 'configure', async_configure, schema=SERVICE_SCHEMA)
|
DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def deconz_shutdown(event):
|
def deconz_shutdown(event):
|
||||||
|
@ -127,3 +138,39 @@ async def async_setup_entry(hass, config_entry):
|
||||||
|
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, deconz_shutdown)
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, deconz_shutdown)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, config_entry):
|
||||||
|
"""Unload deCONZ config entry."""
|
||||||
|
deconz = hass.data.pop(DOMAIN)
|
||||||
|
hass.services.async_remove(DOMAIN, SERVICE_DECONZ)
|
||||||
|
deconz.close()
|
||||||
|
for component in ['binary_sensor', 'light', 'scene', 'sensor']:
|
||||||
|
await hass.config_entries.async_forward_entry_unload(
|
||||||
|
config_entry, component)
|
||||||
|
hass.data[DATA_DECONZ_EVENT] = []
|
||||||
|
hass.data[DATA_DECONZ_ID] = []
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class DeconzEvent(object):
|
||||||
|
"""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_update_callback(self, reason):
|
||||||
|
"""Fire the event if reason is that state is updated."""
|
||||||
|
if reason['state']:
|
||||||
|
data = {CONF_ID: self._id, CONF_EVENT: self._device.state}
|
||||||
|
self._hass.bus.async_fire(self._event, data, EventOrigin.remote)
|
||||||
|
|
|
@ -5,4 +5,5 @@ _LOGGER = logging.getLogger('homeassistant.components.deconz')
|
||||||
|
|
||||||
DOMAIN = 'deconz'
|
DOMAIN = 'deconz'
|
||||||
CONFIG_FILE = 'deconz.conf'
|
CONFIG_FILE = 'deconz.conf'
|
||||||
|
DATA_DECONZ_EVENT = 'deconz_events'
|
||||||
DATA_DECONZ_ID = 'deconz_entities'
|
DATA_DECONZ_ID = 'deconz_entities'
|
||||||
|
|
|
@ -95,6 +95,11 @@ async def async_setup_entry(hass, entry):
|
||||||
return await hass.data[DOMAIN].async_setup_entry(entry)
|
return await hass.data[DOMAIN].async_setup_entry(entry)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, entry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return await hass.data[DOMAIN].async_unload_entry(entry)
|
||||||
|
|
||||||
|
|
||||||
class Scene(Entity):
|
class Scene(Entity):
|
||||||
"""A scene is a group of entities and the states we want them to be."""
|
"""A scene is a group of entities and the states we want them to be."""
|
||||||
|
|
||||||
|
|
|
@ -41,3 +41,8 @@ async def async_setup(hass, config):
|
||||||
async def async_setup_entry(hass, entry):
|
async def async_setup_entry(hass, entry):
|
||||||
"""Setup a config entry."""
|
"""Setup a config entry."""
|
||||||
return await hass.data[DOMAIN].async_setup_entry(entry)
|
return await hass.data[DOMAIN].async_setup_entry(entry)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, entry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return await hass.data[DOMAIN].async_unload_entry(entry)
|
||||||
|
|
|
@ -6,9 +6,8 @@ https://home-assistant.io/components/sensor.deconz/
|
||||||
"""
|
"""
|
||||||
from homeassistant.components.deconz import (
|
from homeassistant.components.deconz import (
|
||||||
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID)
|
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_VOLTAGE
|
||||||
ATTR_BATTERY_LEVEL, ATTR_VOLTAGE, CONF_EVENT, CONF_ID)
|
from homeassistant.core import callback
|
||||||
from homeassistant.core import EventOrigin, callback
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.icon import icon_for_battery_level
|
from homeassistant.helpers.icon import icon_for_battery_level
|
||||||
from homeassistant.util import slugify
|
from homeassistant.util import slugify
|
||||||
|
@ -35,7 +34,6 @@ async def async_setup_entry(hass, config_entry, async_add_devices):
|
||||||
for sensor in sensors.values():
|
for sensor in sensors.values():
|
||||||
if sensor and sensor.type in DECONZ_SENSOR:
|
if sensor and sensor.type in DECONZ_SENSOR:
|
||||||
if sensor.type in DECONZ_REMOTE:
|
if sensor.type in DECONZ_REMOTE:
|
||||||
DeconzEvent(hass, sensor)
|
|
||||||
if sensor.battery:
|
if sensor.battery:
|
||||||
entities.append(DeconzBattery(sensor))
|
entities.append(DeconzBattery(sensor))
|
||||||
else:
|
else:
|
||||||
|
@ -184,26 +182,3 @@ class DeconzBattery(Entity):
|
||||||
ATTR_EVENT_ID: slugify(self._device.name),
|
ATTR_EVENT_ID: slugify(self._device.name),
|
||||||
}
|
}
|
||||||
return attr
|
return attr
|
||||||
|
|
||||||
|
|
||||||
class DeconzEvent(object):
|
|
||||||
"""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_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)
|
|
||||||
|
|
|
@ -107,3 +107,19 @@ async def test_setup_entry_successful(hass):
|
||||||
(entry, 'scene')
|
(entry, 'scene')
|
||||||
assert mock_config_entries.async_forward_entry_setup.mock_calls[3][1] == \
|
assert mock_config_entries.async_forward_entry_setup.mock_calls[3][1] == \
|
||||||
(entry, 'sensor')
|
(entry, 'sensor')
|
||||||
|
|
||||||
|
|
||||||
|
async def test_unload_entry(hass):
|
||||||
|
"""Test being able to unload an entry."""
|
||||||
|
entry = Mock()
|
||||||
|
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'}
|
||||||
|
with patch('pydeconz.DeconzSession.async_load_parameters',
|
||||||
|
return_value=mock_coro(True)):
|
||||||
|
assert await deconz.async_setup_entry(hass, entry) is True
|
||||||
|
assert deconz.DATA_DECONZ_EVENT in hass.data
|
||||||
|
hass.data[deconz.DATA_DECONZ_EVENT].append(Mock())
|
||||||
|
hass.data[deconz.DATA_DECONZ_ID] = {'id': 'deconzid'}
|
||||||
|
assert await deconz.async_unload_entry(hass, entry)
|
||||||
|
assert deconz.DOMAIN not in hass.data
|
||||||
|
assert len(hass.data[deconz.DATA_DECONZ_EVENT]) == 0
|
||||||
|
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
|
||||||
|
|
|
@ -51,6 +51,7 @@ async def setup_bridge(hass, data):
|
||||||
return_value=mock_coro(data)):
|
return_value=mock_coro(data)):
|
||||||
await bridge.async_load_parameters()
|
await bridge.async_load_parameters()
|
||||||
hass.data[deconz.DOMAIN] = bridge
|
hass.data[deconz.DOMAIN] = bridge
|
||||||
|
hass.data[deconz.DATA_DECONZ_EVENT] = []
|
||||||
hass.data[deconz.DATA_DECONZ_ID] = {}
|
hass.data[deconz.DATA_DECONZ_ID] = {}
|
||||||
config_entry = config_entries.ConfigEntry(
|
config_entry = config_entries.ConfigEntry(
|
||||||
1, deconz.DOMAIN, 'Mock Title', {'host': 'mock-host'}, 'test')
|
1, deconz.DOMAIN, 'Mock Title', {'host': 'mock-host'}, 'test')
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue