mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
deCONZ config entry (#13402)
* Try config entries * Testing * Working flow * Config entry text strings * Removed manual inputs for config flow * Support unloading of config entry * Bump requirement to v33 * Fix comments from test * Make sure that only one deCONZ instance can be set up * Hass doesn't support unloading platforms yet * Modify get_api_key to be testable * Fix hound comments * Add test dependency * Add test for no key * Bump requirement to v35 Add pydeconz to list of test components * Don't have a check in async_setup that domain exists in hass.data
This commit is contained in:
parent
5801d78017
commit
931bceefd9
25
homeassistant/components/deconz/.translations/en.json
Normal file
25
homeassistant/components/deconz/.translations/en.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"title": "deCONZ",
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Define deCONZ gateway",
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"port": "Port (default value: '80')"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"no_key": "Couldn't get an API key"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"no_bridges": "No deCONZ bridges discovered",
|
||||||
|
"one_instance_only": "Component only supports one deCONZ instance"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,16 +8,17 @@ import logging
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
from homeassistant.components.discovery import SERVICE_DECONZ
|
from homeassistant.components.discovery import SERVICE_DECONZ
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
|
CONF_API_KEY, CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP)
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers import discovery
|
from homeassistant.helpers import discovery, aiohttp_client
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.util.json import load_json, save_json
|
from homeassistant.util.json import load_json, save_json
|
||||||
|
|
||||||
REQUIREMENTS = ['pydeconz==32']
|
REQUIREMENTS = ['pydeconz==35']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -160,7 +161,8 @@ async def async_request_configuration(hass, config, deconz_config):
|
|||||||
async def async_configuration_callback(data):
|
async def async_configuration_callback(data):
|
||||||
"""Set up actions to do when our configuration callback is called."""
|
"""Set up actions to do when our configuration callback is called."""
|
||||||
from pydeconz.utils import async_get_api_key
|
from pydeconz.utils import async_get_api_key
|
||||||
api_key = await async_get_api_key(hass.loop, **deconz_config)
|
websession = async_get_clientsession(hass)
|
||||||
|
api_key = await async_get_api_key(websession, **deconz_config)
|
||||||
if api_key:
|
if api_key:
|
||||||
deconz_config[CONF_API_KEY] = api_key
|
deconz_config[CONF_API_KEY] = api_key
|
||||||
result = await async_setup_deconz(hass, config, deconz_config)
|
result = await async_setup_deconz(hass, config, deconz_config)
|
||||||
@ -186,3 +188,85 @@ async def async_request_configuration(hass, config, deconz_config):
|
|||||||
entity_picture="/static/images/logo_deconz.jpeg",
|
entity_picture="/static/images/logo_deconz.jpeg",
|
||||||
submit_caption="I have unlocked the gateway",
|
submit_caption="I have unlocked the gateway",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@config_entries.HANDLERS.register(DOMAIN)
|
||||||
|
class DeconzFlowHandler(config_entries.ConfigFlowHandler):
|
||||||
|
"""Handle a deCONZ config flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the deCONZ flow."""
|
||||||
|
self.bridges = []
|
||||||
|
self.deconz_config = {}
|
||||||
|
|
||||||
|
async def async_step_init(self, user_input=None):
|
||||||
|
"""Handle a flow start."""
|
||||||
|
from pydeconz.utils import async_discovery
|
||||||
|
|
||||||
|
if DOMAIN in self.hass.data:
|
||||||
|
return self.async_abort(
|
||||||
|
reason='one_instance_only'
|
||||||
|
)
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
for bridge in self.bridges:
|
||||||
|
if bridge[CONF_HOST] == user_input[CONF_HOST]:
|
||||||
|
self.deconz_config = bridge
|
||||||
|
return await self.async_step_link()
|
||||||
|
|
||||||
|
session = aiohttp_client.async_get_clientsession(self.hass)
|
||||||
|
self.bridges = await async_discovery(session)
|
||||||
|
|
||||||
|
if len(self.bridges) == 1:
|
||||||
|
self.deconz_config = self.bridges[0]
|
||||||
|
return await self.async_step_link()
|
||||||
|
elif len(self.bridges) > 1:
|
||||||
|
hosts = []
|
||||||
|
for bridge in self.bridges:
|
||||||
|
hosts.append(bridge[CONF_HOST])
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id='init',
|
||||||
|
data_schema=vol.Schema({
|
||||||
|
vol.Required(CONF_HOST): vol.In(hosts)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_abort(
|
||||||
|
reason='no_bridges'
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_link(self, user_input=None):
|
||||||
|
"""Attempt to link with the deCONZ bridge."""
|
||||||
|
from pydeconz.utils import async_get_api_key
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
session = aiohttp_client.async_get_clientsession(self.hass)
|
||||||
|
api_key = await async_get_api_key(session, **self.deconz_config)
|
||||||
|
if api_key:
|
||||||
|
self.deconz_config[CONF_API_KEY] = api_key
|
||||||
|
return self.async_create_entry(
|
||||||
|
title='deCONZ',
|
||||||
|
data=self.deconz_config
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
errors['base'] = 'no_key'
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id='link',
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, entry):
|
||||||
|
"""Set up a bridge for a config entry."""
|
||||||
|
if DOMAIN in hass.data:
|
||||||
|
_LOGGER.error(
|
||||||
|
"Config entry failed since one deCONZ instance already exists")
|
||||||
|
return False
|
||||||
|
result = await async_setup_deconz(hass, None, entry.data)
|
||||||
|
if result:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
25
homeassistant/components/deconz/strings.json
Normal file
25
homeassistant/components/deconz/strings.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"title": "deCONZ",
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"title": "Define deCONZ gateway",
|
||||||
|
"data": {
|
||||||
|
"host": "Host",
|
||||||
|
"port": "Port (default value: '80')"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"no_key": "Couldn't get an API key"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"no_bridges": "No deCONZ bridges discovered",
|
||||||
|
"one_instance_only": "Component only supports one deCONZ instance"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -128,6 +128,7 @@ HANDLERS = Registry()
|
|||||||
FLOWS = [
|
FLOWS = [
|
||||||
'config_entry_example',
|
'config_entry_example',
|
||||||
'hue',
|
'hue',
|
||||||
|
'deconz',
|
||||||
]
|
]
|
||||||
|
|
||||||
SOURCE_USER = 'user'
|
SOURCE_USER = 'user'
|
||||||
|
@ -714,7 +714,7 @@ pycsspeechtts==1.0.2
|
|||||||
pydaikin==0.4
|
pydaikin==0.4
|
||||||
|
|
||||||
# homeassistant.components.deconz
|
# homeassistant.components.deconz
|
||||||
pydeconz==32
|
pydeconz==35
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
pydispatcher==2.0.5
|
pydispatcher==2.0.5
|
||||||
|
@ -129,6 +129,9 @@ pushbullet.py==0.11.0
|
|||||||
# homeassistant.components.canary
|
# homeassistant.components.canary
|
||||||
py-canary==0.4.1
|
py-canary==0.4.1
|
||||||
|
|
||||||
|
# homeassistant.components.deconz
|
||||||
|
pydeconz==35
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
pydispatcher==2.0.5
|
pydispatcher==2.0.5
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@ TEST_REQUIREMENTS = (
|
|||||||
'prometheus_client',
|
'prometheus_client',
|
||||||
'pushbullet.py',
|
'pushbullet.py',
|
||||||
'py-canary',
|
'py-canary',
|
||||||
|
'pydeconz',
|
||||||
'pydispatcher',
|
'pydispatcher',
|
||||||
'PyJWT',
|
'PyJWT',
|
||||||
'pylitejet',
|
'pylitejet',
|
||||||
|
97
tests/components/test_deconz.py
Normal file
97
tests/components/test_deconz.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
"""Tests for deCONZ config flow."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
import homeassistant.components.deconz as deconz
|
||||||
|
import pydeconz
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_works(hass, aioclient_mock):
|
||||||
|
"""Test config flow."""
|
||||||
|
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[
|
||||||
|
{'id': 'id', 'internalipaddress': '1.2.3.4', 'internalport': '80'}
|
||||||
|
])
|
||||||
|
aioclient_mock.post('http://1.2.3.4:80/api', json=[
|
||||||
|
{"success": {"username": "1234567890ABCDEF"}}
|
||||||
|
])
|
||||||
|
|
||||||
|
flow = deconz.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
await flow.async_step_init()
|
||||||
|
result = await flow.async_step_link(user_input={})
|
||||||
|
|
||||||
|
assert result['type'] == 'create_entry'
|
||||||
|
assert result['title'] == 'deCONZ'
|
||||||
|
assert result['data'] == {
|
||||||
|
'bridgeid': 'id',
|
||||||
|
'host': '1.2.3.4',
|
||||||
|
'port': '80',
|
||||||
|
'api_key': '1234567890ABCDEF'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_already_registered_bridge(hass, aioclient_mock):
|
||||||
|
"""Test config flow don't allow more than one bridge to be registered."""
|
||||||
|
flow = deconz.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
flow.hass.data[deconz.DOMAIN] = True
|
||||||
|
|
||||||
|
result = await flow.async_step_init()
|
||||||
|
assert result['type'] == 'abort'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_no_discovered_bridges(hass, aioclient_mock):
|
||||||
|
"""Test config flow discovers no bridges."""
|
||||||
|
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[])
|
||||||
|
flow = deconz.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_init()
|
||||||
|
assert result['type'] == 'abort'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_one_bridge_discovered(hass, aioclient_mock):
|
||||||
|
"""Test config flow discovers one bridge."""
|
||||||
|
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[
|
||||||
|
{'id': 'id', 'internalipaddress': '1.2.3.4', 'internalport': '80'}
|
||||||
|
])
|
||||||
|
flow = deconz.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_init()
|
||||||
|
assert result['type'] == 'form'
|
||||||
|
assert result['step_id'] == 'link'
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_two_bridges_discovered(hass, aioclient_mock):
|
||||||
|
"""Test config flow discovers two bridges."""
|
||||||
|
aioclient_mock.get(pydeconz.utils.URL_DISCOVER, json=[
|
||||||
|
{'id': 'id1', 'internalipaddress': '1.2.3.4', 'internalport': '80'},
|
||||||
|
{'id': 'id2', 'internalipaddress': '5.6.7.8', 'internalport': '80'}
|
||||||
|
])
|
||||||
|
flow = deconz.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
|
||||||
|
result = await flow.async_step_init()
|
||||||
|
assert result['type'] == 'form'
|
||||||
|
assert result['step_id'] == 'init'
|
||||||
|
|
||||||
|
with pytest.raises(vol.Invalid):
|
||||||
|
assert result['data_schema']({'host': '0.0.0.0'})
|
||||||
|
|
||||||
|
result['data_schema']({'host': '1.2.3.4'})
|
||||||
|
result['data_schema']({'host': '5.6.7.8'})
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_no_api_key(hass, aioclient_mock):
|
||||||
|
"""Test config flow discovers no bridges."""
|
||||||
|
aioclient_mock.post('http://1.2.3.4:80/api', json=[])
|
||||||
|
flow = deconz.DeconzFlowHandler()
|
||||||
|
flow.hass = hass
|
||||||
|
flow.deconz_config = {'host': '1.2.3.4', 'port': 80}
|
||||||
|
|
||||||
|
result = await flow.async_step_link(user_input={})
|
||||||
|
assert result['type'] == 'form'
|
||||||
|
assert result['step_id'] == 'link'
|
||||||
|
assert result['errors'] == {'base': 'no_key'}
|
Loading…
x
Reference in New Issue
Block a user