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
10 changes: 7 additions & 3 deletions homeassistant/components/binary_sensor/deconz.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"""
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.deconz import (
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB)
CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ, DATA_DECONZ_ID,
DATA_DECONZ_UNSUB)
from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
Expand All @@ -27,10 +28,13 @@ def async_add_sensor(sensors):
"""Add binary sensor from deCONZ."""
from pydeconz.sensor import DECONZ_BINARY_SENSOR
entities = []
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
if sensor.type in DECONZ_BINARY_SENSOR:
if sensor.type in DECONZ_BINARY_SENSOR and \
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
entities.append(DeconzBinarySensor(sensor))
async_add_devices(entities, True)

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

Expand Down Expand Up @@ -103,6 +107,6 @@ def device_state_attributes(self):
attr = {}
if self._sensor.battery:
attr[ATTR_BATTERY_LEVEL] = self._sensor.battery
if self._sensor.type in PRESENCE and self._sensor.dark:
if self._sensor.type in PRESENCE and self._sensor.dark is not None:
attr['dark'] = self._sensor.dark
return attr
8 changes: 7 additions & 1 deletion homeassistant/components/deconz/.translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@
"link": {
"description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ system settings\n2. Press \"Unlock Gateway\" button",
"title": "Link with deCONZ"
},
"options": {
"title": "Extra configuration options for deCONZ",
"data": {
"allow_clip_sensor": "Allow importing virtual sensors"
}
}
},
"title": "deCONZ"
"title": "deCONZ Zigbee gateway"
}
}
8 changes: 5 additions & 3 deletions homeassistant/components/deconz/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
# Loading the config flow file will register the flow
from .config_flow import configured_hosts
from .const import (
CONFIG_FILE, DATA_DECONZ_EVENT, DATA_DECONZ_ID,
DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)
CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT,
DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER)

REQUIREMENTS = ['pydeconz==38']

Expand Down Expand Up @@ -104,8 +104,10 @@ def async_add_device_callback(device_type, device):
def async_add_remote(sensors):
"""Setup 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:
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))
Expand Down
69 changes: 47 additions & 22 deletions homeassistant/components/deconz/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
from homeassistant.helpers import aiohttp_client
from homeassistant.util.json import load_json

from .const import CONFIG_FILE, DOMAIN
from .const import CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DOMAIN

CONF_BRIDGEID = 'bridgeid'


@callback
def configured_hosts(hass):
"""Return a set of the configured hosts."""
return set(entry.data['host'] for entry
return set(entry.data[CONF_HOST] for entry
in hass.config_entries.async_entries(DOMAIN))


Expand All @@ -30,7 +32,12 @@ def __init__(self):
self.deconz_config = {}

async def async_step_init(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 more than one bridge is found let user choose bridge to link.
"""
from pydeconz.utils import async_discovery

if configured_hosts(self.hass):
Expand Down Expand Up @@ -65,7 +72,7 @@ async def async_step_init(self, user_input=None):

async def async_step_link(self, user_input=None):
"""Attempt to link with the deCONZ bridge."""
from pydeconz.utils import async_get_api_key, async_get_bridgeid
from pydeconz.utils import async_get_api_key
errors = {}

if user_input is not None:
Expand All @@ -75,20 +82,42 @@ async def async_step_link(self, user_input=None):
api_key = await async_get_api_key(session, **self.deconz_config)
if api_key:
self.deconz_config[CONF_API_KEY] = api_key
if 'bridgeid' not in self.deconz_config:
self.deconz_config['bridgeid'] = await async_get_bridgeid(
session, **self.deconz_config)
return self.async_create_entry(
title='deCONZ-' + self.deconz_config['bridgeid'],
data=self.deconz_config
)
return await self.async_step_options()
errors['base'] = 'no_key'

return self.async_show_form(
step_id='link',
errors=errors,
)

async def async_step_options(self, user_input=None):
"""Extra options for deCONZ.

CONF_CLIP_SENSOR -- Allow user to choose if they want clip sensors.
"""
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]

if CONF_BRIDGEID not in self.deconz_config:
session = aiohttp_client.async_get_clientsession(self.hass)
self.deconz_config[CONF_BRIDGEID] = await async_get_bridgeid(
session, **self.deconz_config)

return self.async_create_entry(
title='deCONZ-' + self.deconz_config[CONF_BRIDGEID],
data=self.deconz_config
)

return self.async_show_form(
step_id='options',
data_schema=vol.Schema({
vol.Optional(CONF_ALLOW_CLIP_SENSOR): bool,
}),
)

async def async_step_discovery(self, discovery_info):
"""Prepare configuration for a discovered deCONZ bridge.

Expand All @@ -97,7 +126,7 @@ async def async_step_discovery(self, discovery_info):
deconz_config = {}
deconz_config[CONF_HOST] = discovery_info.get(CONF_HOST)
deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT)
deconz_config['bridgeid'] = discovery_info.get('serial')
deconz_config[CONF_BRIDGEID] = discovery_info.get('serial')

config_file = await self.hass.async_add_job(
load_json, self.hass.config.path(CONFIG_FILE))
Expand All @@ -121,19 +150,15 @@ async def async_step_import(self, import_config):
Otherwise we will delegate to `link` step which
will ask user to link the bridge.
"""
from pydeconz.utils import async_get_bridgeid

if configured_hosts(self.hass):
return self.async_abort(reason='one_instance_only')
elif CONF_API_KEY not in import_config:
self.deconz_config = import_config

self.deconz_config = import_config
if CONF_API_KEY not in import_config:
return await self.async_step_link()

if 'bridgeid' not in import_config:
session = aiohttp_client.async_get_clientsession(self.hass)
import_config['bridgeid'] = await async_get_bridgeid(
session, **import_config)
self.deconz_config[CONF_ALLOW_CLIP_SENSOR] = True
return self.async_create_entry(
title='deCONZ-' + import_config['bridgeid'],
data=import_config
title='deCONZ-' + self.deconz_config[CONF_BRIDGEID],
data=self.deconz_config
)
2 changes: 2 additions & 0 deletions homeassistant/components/deconz/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
DATA_DECONZ_EVENT = 'deconz_events'
DATA_DECONZ_ID = 'deconz_entities'
DATA_DECONZ_UNSUB = 'deconz_dispatchers'

CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor'
8 changes: 7 additions & 1 deletion homeassistant/components/deconz/strings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"config": {
"title": "deCONZ",
"title": "deCONZ Zigbee gateway",
"step": {
"init": {
"title": "Define deCONZ gateway",
Expand All @@ -12,6 +12,12 @@
"link": {
"title": "Link with deCONZ",
"description": "Unlock your deCONZ gateway to register with Home Assistant.\n\n1. Go to deCONZ system settings\n2. Press \"Unlock Gateway\" button"
},
"options": {
"title": "Extra configuration options for deCONZ",
"data":{
"allow_clip_sensor": "Allow importing virtual sensors"
}
}
},
"error": {
Expand Down
11 changes: 9 additions & 2 deletions homeassistant/components/sensor/deconz.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
https://home-assistant.io/components/sensor.deconz/
"""
from homeassistant.components.deconz import (
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB)
CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ, DATA_DECONZ_ID,
DATA_DECONZ_UNSUB)
from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_VOLTAGE, DEVICE_CLASS_BATTERY)
from homeassistant.core import callback
Expand Down Expand Up @@ -33,14 +34,17 @@ def async_add_sensor(sensors):
"""Add sensors from deCONZ."""
from pydeconz.sensor import DECONZ_SENSOR, SWITCH as DECONZ_REMOTE
entities = []
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
for sensor in sensors:
if sensor.type in DECONZ_SENSOR:
if sensor.type in DECONZ_SENSOR and \
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
if sensor.type in DECONZ_REMOTE:
if sensor.battery:
entities.append(DeconzBattery(sensor))
else:
entities.append(DeconzSensor(sensor))
async_add_devices(entities, True)

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

Expand Down Expand Up @@ -114,9 +118,12 @@ def should_poll(self):
@property
def device_state_attributes(self):
"""Return the state attributes of the sensor."""
from pydeconz.sensor import LIGHTLEVEL
attr = {}
if self._sensor.battery:
attr[ATTR_BATTERY_LEVEL] = self._sensor.battery
if self._sensor.type in LIGHTLEVEL and self._sensor.dark is not None:
attr['dark'] = self._sensor.dark
if self.unit_of_measurement == 'Watts':
attr[ATTR_CURRENT] = self._sensor.current
attr[ATTR_VOLTAGE] = self._sensor.voltage
Expand Down
18 changes: 16 additions & 2 deletions tests/components/binary_sensor/test_deconz.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
}


async def setup_bridge(hass, data):
async def setup_bridge(hass, data, allow_clip_sensor=True):
"""Load the deCONZ binary sensor platform."""
from pydeconz import DeconzSession
loop = Mock()
Expand All @@ -41,7 +41,8 @@ async def setup_bridge(hass, data):
hass.data[deconz.DATA_DECONZ_UNSUB] = []
hass.data[deconz.DATA_DECONZ_ID] = {}
config_entry = config_entries.ConfigEntry(
1, deconz.DOMAIN, 'Mock Title', {'host': 'mock-host'}, 'test')
1, deconz.DOMAIN, 'Mock Title',
{'host': 'mock-host', 'allow_clip_sensor': allow_clip_sensor}, 'test')
await hass.config_entries.async_forward_entry_setup(
config_entry, 'binary_sensor')
# To flush out the service call to update the group
Expand Down Expand Up @@ -77,3 +78,16 @@ async def test_add_new_sensor(hass):
async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
await hass.async_block_till_done()
assert "binary_sensor.name" in hass.data[deconz.DATA_DECONZ_ID]


async def test_do_not_allow_clip_sensor(hass):
"""Test that clip sensors can be ignored."""
data = {}
await setup_bridge(hass, data, allow_clip_sensor=False)
sensor = Mock()
sensor.name = 'name'
sensor.type = 'CLIPPresence'
sensor.register_async_callback = Mock()
async_dispatcher_send(hass, 'deconz_new_sensor', [sensor])
await hass.async_block_till_done()
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0
35 changes: 30 additions & 5 deletions tests/components/deconz/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@ async def test_flow_works(hass, aioclient_mock):
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
await flow.async_step_init()
result = await flow.async_step_link(user_input={})
await flow.async_step_link(user_input={})
result = await flow.async_step_options(
user_input={'allow_clip_sensor': True})

assert result['type'] == 'create_entry'
assert result['title'] == 'deCONZ-id'
assert result['data'] == {
'bridgeid': 'id',
'host': '1.2.3.4',
'port': 80,
'api_key': '1234567890ABCDEF'
'api_key': '1234567890ABCDEF',
'allow_clip_sensor': True
}


Expand Down Expand Up @@ -146,14 +149,14 @@ async def test_bridge_discovery_config_file(hass):
'port': 80,
'serial': 'id'
})

assert result['type'] == 'create_entry'
assert result['title'] == 'deCONZ-id'
assert result['data'] == {
'bridgeid': 'id',
'host': '1.2.3.4',
'port': 80,
'api_key': '1234567890ABCDEF'
'api_key': '1234567890ABCDEF',
'allow_clip_sensor': True
}


Expand Down Expand Up @@ -214,12 +217,34 @@ async def test_import_with_api_key(hass):
'port': 80,
'api_key': '1234567890ABCDEF'
})
assert result['type'] == 'create_entry'
assert result['title'] == 'deCONZ-id'
assert result['data'] == {
'bridgeid': 'id',
'host': '1.2.3.4',
'port': 80,
'api_key': '1234567890ABCDEF',
'allow_clip_sensor': True
}


async def test_options(hass, aioclient_mock):
"""Test that options work and that bridgeid can be requested."""
aioclient_mock.get('http://1.2.3.4:80/api/1234567890ABCDEF/config',
json={"bridgeid": "id"})
flow = config_flow.DeconzFlowHandler()
flow.hass = hass
flow.deconz_config = {'host': '1.2.3.4',
'port': 80,
'api_key': '1234567890ABCDEF'}
result = await flow.async_step_options(
user_input={'allow_clip_sensor': False})
assert result['type'] == 'create_entry'
assert result['title'] == 'deCONZ-id'
assert result['data'] == {
'bridgeid': 'id',
'host': '1.2.3.4',
'port': 80,
'api_key': '1234567890ABCDEF'
'api_key': '1234567890ABCDEF',
'allow_clip_sensor': False
}
18 changes: 18 additions & 0 deletions tests/components/deconz/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,21 @@ async def test_add_new_remote(hass):
async_dispatcher_send(hass, 'deconz_new_sensor', [remote])
await hass.async_block_till_done()
assert len(hass.data[deconz.DATA_DECONZ_EVENT]) == 1


async def test_do_not_allow_clip_sensor(hass):
"""Test that clip sensors can be ignored."""
entry = Mock()
entry.data = {'host': '1.2.3.4', 'port': 80,
'api_key': '1234567890ABCDEF', 'allow_clip_sensor': False}
remote = Mock()
remote.name = 'name'
remote.type = 'CLIPSwitch'
remote.register_async_callback = Mock()
with patch('pydeconz.DeconzSession.async_load_parameters',
return_value=mock_coro(True)):
assert await deconz.async_setup_entry(hass, entry) is True

async_dispatcher_send(hass, 'deconz_new_sensor', [remote])
await hass.async_block_till_done()
assert len(hass.data[deconz.DATA_DECONZ_EVENT]) == 0
Loading