diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index ee2a0ce712d..d72211d5ad1 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -62,6 +62,11 @@ async def async_setup_entry(hass, 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 class BinarySensorDevice(Entity): """Represent a binary sensor.""" diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index d68edac9e59..75414598693 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -7,14 +7,17 @@ https://home-assistant.io/components/deconz/ import voluptuous as vol from homeassistant.const import ( - CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP) -from homeassistant.core import callback + CONF_API_KEY, CONF_EVENT, CONF_HOST, + 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.util import slugify from homeassistant.util.json import load_json # Loading the config flow file will register the flow 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'] @@ -26,6 +29,8 @@ CONFIG_SCHEMA = vol.Schema({ }) }, extra=vol.ALLOW_EXTRA) +SERVICE_DECONZ = 'configure' + SERVICE_FIELD = 'field' SERVICE_ENTITY = 'entity' 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. """ from pydeconz import DeconzSession + from pydeconz.sensor import SWITCH as DECONZ_REMOTE if DOMAIN in hass.data: _LOGGER.error( "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']: hass.async_add_job(hass.config_entries.async_forward_entry_setup( 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() async def async_configure(call): @@ -112,7 +123,7 @@ async def async_setup_entry(hass, config_entry): return await deconz.async_put_state(field, data) hass.services.async_register( - DOMAIN, 'configure', async_configure, schema=SERVICE_SCHEMA) + DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA) @callback 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) 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) diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index c5820c971f6..e6d393c8ee7 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -5,4 +5,5 @@ _LOGGER = logging.getLogger('homeassistant.components.deconz') DOMAIN = 'deconz' CONFIG_FILE = 'deconz.conf' +DATA_DECONZ_EVENT = 'deconz_events' DATA_DECONZ_ID = 'deconz_entities' diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py index a3e3a5b38a7..2394d538f2f 100644 --- a/homeassistant/components/scene/__init__.py +++ b/homeassistant/components/scene/__init__.py @@ -95,6 +95,11 @@ async def async_setup_entry(hass, 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): """A scene is a group of entities and the states we want them to be.""" diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 2887d32b987..bed1850b34d 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -41,3 +41,8 @@ async def async_setup(hass, config): async def async_setup_entry(hass, entry): """Setup a config 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) diff --git a/homeassistant/components/sensor/deconz.py b/homeassistant/components/sensor/deconz.py index dc28a181aa0..69be7f52d6c 100644 --- a/homeassistant/components/sensor/deconz.py +++ b/homeassistant/components/sensor/deconz.py @@ -6,9 +6,8 @@ https://home-assistant.io/components/sensor.deconz/ """ from homeassistant.components.deconz import ( DOMAIN as DATA_DECONZ, DATA_DECONZ_ID) -from homeassistant.const import ( - ATTR_BATTERY_LEVEL, ATTR_VOLTAGE, CONF_EVENT, CONF_ID) -from homeassistant.core import EventOrigin, callback +from homeassistant.const import ATTR_BATTERY_LEVEL, ATTR_VOLTAGE +from homeassistant.core import callback from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level 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(): if sensor and sensor.type in DECONZ_SENSOR: if sensor.type in DECONZ_REMOTE: - DeconzEvent(hass, sensor) if sensor.battery: entities.append(DeconzBattery(sensor)) else: @@ -184,26 +182,3 @@ class DeconzBattery(Entity): ATTR_EVENT_ID: slugify(self._device.name), } 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) diff --git a/tests/components/deconz/test_init.py b/tests/components/deconz/test_init.py index ce231e3d162..b09edf42a87 100644 --- a/tests/components/deconz/test_init.py +++ b/tests/components/deconz/test_init.py @@ -107,3 +107,19 @@ async def test_setup_entry_successful(hass): (entry, 'scene') assert mock_config_entries.async_forward_entry_setup.mock_calls[3][1] == \ (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 diff --git a/tests/components/sensor/test_deconz.py b/tests/components/sensor/test_deconz.py index b70fb396686..d6c026e88bd 100644 --- a/tests/components/sensor/test_deconz.py +++ b/tests/components/sensor/test_deconz.py @@ -51,6 +51,7 @@ async def setup_bridge(hass, data): return_value=mock_coro(data)): await bridge.async_load_parameters() hass.data[deconz.DOMAIN] = bridge + hass.data[deconz.DATA_DECONZ_EVENT] = [] hass.data[deconz.DATA_DECONZ_ID] = {} config_entry = config_entries.ConfigEntry( 1, deconz.DOMAIN, 'Mock Title', {'host': 'mock-host'}, 'test')