Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions homeassistant/components/binary_sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
55 changes: 51 additions & 4 deletions homeassistant/components/deconz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@
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']

Expand All @@ -26,6 +29,8 @@
})
}, extra=vol.ALLOW_EXTRA)

SERVICE_DECONZ = 'configure'

SERVICE_FIELD = 'field'
SERVICE_ENTITY = 'entity'
SERVICE_DATA = 'data'
Expand Down Expand Up @@ -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")
Expand All @@ -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):
Expand Down Expand Up @@ -112,7 +123,7 @@ async def async_configure(call):
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):
Expand All @@ -127,3 +138,39 @@ def deconz_shutdown(event):

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)
1 change: 1 addition & 0 deletions homeassistant/components/deconz/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@

DOMAIN = 'deconz'
CONFIG_FILE = 'deconz.conf'
DATA_DECONZ_EVENT = 'deconz_events'
DATA_DECONZ_ID = 'deconz_entities'
5 changes: 5 additions & 0 deletions homeassistant/components/scene/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
29 changes: 2 additions & 27 deletions homeassistant/components/sensor/deconz.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
"""
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
Expand All @@ -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:
Expand Down Expand Up @@ -184,26 +182,3 @@ def device_state_attributes(self):
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)
16 changes: 16 additions & 0 deletions tests/components/deconz/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions tests/components/sensor/test_deconz.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down