Use API to discover Hue if no bridges specified (#11909)
* Use API to discover Hue if no bridges specified * hide config filepull/11833/head^2
parent
9123dfce6d
commit
3d9ff372fc
|
@ -9,6 +9,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
import requests
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.discovery import SERVICE_HUE
|
from homeassistant.components.discovery import SERVICE_HUE
|
||||||
|
@ -22,6 +23,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = "hue"
|
DOMAIN = "hue"
|
||||||
SERVICE_HUE_SCENE = "hue_activate_scene"
|
SERVICE_HUE_SCENE = "hue_activate_scene"
|
||||||
|
API_NUPNP = 'https://www.meethue.com/api/nupnp'
|
||||||
|
|
||||||
CONF_BRIDGES = "bridges"
|
CONF_BRIDGES = "bridges"
|
||||||
|
|
||||||
|
@ -49,7 +51,7 @@ BRIDGE_CONFIG_SCHEMA = vol.Schema([{
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
DOMAIN: vol.Schema({
|
DOMAIN: vol.Schema({
|
||||||
vol.Optional(CONF_BRIDGES, default=[]): BRIDGE_CONFIG_SCHEMA,
|
vol.Optional(CONF_BRIDGES): BRIDGE_CONFIG_SCHEMA,
|
||||||
}),
|
}),
|
||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
|
@ -69,9 +71,9 @@ Press the button on the bridge to register Philips Hue with Home Assistant.
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Set up the Hue platform."""
|
"""Set up the Hue platform."""
|
||||||
config = config.get(DOMAIN)
|
conf = config.get(DOMAIN)
|
||||||
if config is None:
|
if conf is None:
|
||||||
config = {}
|
conf = {}
|
||||||
|
|
||||||
if DOMAIN not in hass.data:
|
if DOMAIN not in hass.data:
|
||||||
hass.data[DOMAIN] = {}
|
hass.data[DOMAIN] = {}
|
||||||
|
@ -82,7 +84,21 @@ def setup(hass, config):
|
||||||
lambda service, discovery_info:
|
lambda service, discovery_info:
|
||||||
bridge_discovered(hass, service, discovery_info))
|
bridge_discovered(hass, service, discovery_info))
|
||||||
|
|
||||||
bridges = config.get(CONF_BRIDGES, [])
|
# User has configured bridges
|
||||||
|
if CONF_BRIDGES in conf:
|
||||||
|
bridges = conf[CONF_BRIDGES]
|
||||||
|
# Component is part of config but no bridges specified, discover.
|
||||||
|
elif DOMAIN in config:
|
||||||
|
# discover from nupnp
|
||||||
|
hosts = requests.get(API_NUPNP).json()
|
||||||
|
bridges = [{
|
||||||
|
CONF_HOST: entry['internalipaddress'],
|
||||||
|
CONF_FILENAME: '.hue_{}.conf'.format(entry['id']),
|
||||||
|
} for entry in hosts]
|
||||||
|
else:
|
||||||
|
# Component not specified in config, we're loaded via discovery
|
||||||
|
bridges = []
|
||||||
|
|
||||||
for bridge in bridges:
|
for bridge in bridges:
|
||||||
filename = bridge.get(CONF_FILENAME)
|
filename = bridge.get(CONF_FILENAME)
|
||||||
allow_unreachable = bridge.get(CONF_ALLOW_UNREACHABLE)
|
allow_unreachable = bridge.get(CONF_ALLOW_UNREACHABLE)
|
||||||
|
|
|
@ -542,10 +542,8 @@ class MockDependency:
|
||||||
self.root = root
|
self.root = root
|
||||||
self.submodules = args
|
self.submodules = args
|
||||||
|
|
||||||
def __call__(self, func):
|
def __enter__(self):
|
||||||
"""Apply decorator."""
|
"""Start mocking."""
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
def resolve(mock, path):
|
def resolve(mock, path):
|
||||||
"""Resolve a mock."""
|
"""Resolve a mock."""
|
||||||
if not path:
|
if not path:
|
||||||
|
@ -553,16 +551,27 @@ class MockDependency:
|
||||||
|
|
||||||
return resolve(getattr(mock, path[0]), path[1:])
|
return resolve(getattr(mock, path[0]), path[1:])
|
||||||
|
|
||||||
|
base = MagicMock()
|
||||||
|
to_mock = {
|
||||||
|
"{}.{}".format(self.root, tom): resolve(base, tom.split('.'))
|
||||||
|
for tom in self.submodules
|
||||||
|
}
|
||||||
|
to_mock[self.root] = base
|
||||||
|
|
||||||
|
self.patcher = patch.dict('sys.modules', to_mock)
|
||||||
|
self.patcher.start()
|
||||||
|
return base
|
||||||
|
|
||||||
|
def __exit__(self, *exc):
|
||||||
|
"""Stop mocking."""
|
||||||
|
self.patcher.stop()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __call__(self, func):
|
||||||
|
"""Apply decorator."""
|
||||||
def run_mocked(*args, **kwargs):
|
def run_mocked(*args, **kwargs):
|
||||||
"""Run with mocked dependencies."""
|
"""Run with mocked dependencies."""
|
||||||
base = MagicMock()
|
with self as base:
|
||||||
to_mock = {
|
|
||||||
"{}.{}".format(self.root, tom): resolve(base, tom.split('.'))
|
|
||||||
for tom in self.submodules
|
|
||||||
}
|
|
||||||
to_mock[self.root] = base
|
|
||||||
|
|
||||||
with patch.dict('sys.modules', to_mock):
|
|
||||||
args = list(args) + [base]
|
args = list(args) + [base]
|
||||||
func(*args, **kwargs)
|
func(*args, **kwargs)
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
"""Generic Philips Hue component tests."""
|
"""Generic Philips Hue component tests."""
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import call, MagicMock, patch
|
from unittest.mock import call, MagicMock, patch
|
||||||
|
|
||||||
from homeassistant.components import configurator, hue
|
from homeassistant.components import configurator, hue
|
||||||
from homeassistant.const import CONF_FILENAME, CONF_HOST
|
from homeassistant.const import CONF_FILENAME, CONF_HOST
|
||||||
from homeassistant.setup import setup_component
|
from homeassistant.setup import setup_component, async_setup_component
|
||||||
|
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
assert_setup_component, get_test_home_assistant, get_test_config_dir,
|
assert_setup_component, get_test_home_assistant, get_test_config_dir,
|
||||||
|
@ -38,15 +38,6 @@ class TestSetup(unittest.TestCase):
|
||||||
mock_phue.Bridge.assert_not_called()
|
mock_phue.Bridge.assert_not_called()
|
||||||
self.assertEquals({}, self.hass.data[hue.DOMAIN])
|
self.assertEquals({}, self.hass.data[hue.DOMAIN])
|
||||||
|
|
||||||
@MockDependency('phue')
|
|
||||||
def test_setup_no_host(self, mock_phue):
|
|
||||||
"""No host specified in any way."""
|
|
||||||
with assert_setup_component(1):
|
|
||||||
self.assertTrue(setup_component(
|
|
||||||
self.hass, hue.DOMAIN, {hue.DOMAIN: {}}))
|
|
||||||
mock_phue.Bridge.assert_not_called()
|
|
||||||
self.assertEquals({}, self.hass.data[hue.DOMAIN])
|
|
||||||
|
|
||||||
@MockDependency('phue')
|
@MockDependency('phue')
|
||||||
def test_setup_with_host(self, mock_phue):
|
def test_setup_with_host(self, mock_phue):
|
||||||
"""Host specified in the config file."""
|
"""Host specified in the config file."""
|
||||||
|
@ -400,3 +391,17 @@ class TestHueBridge(unittest.TestCase):
|
||||||
{hue.ATTR_GROUP_NAME: 'group', hue.ATTR_SCENE_NAME: 'scene'},
|
{hue.ATTR_GROUP_NAME: 'group', hue.ATTR_SCENE_NAME: 'scene'},
|
||||||
blocking=True)
|
blocking=True)
|
||||||
bridge.bridge.run_scene.assert_called_once_with('group', 'scene')
|
bridge.bridge.run_scene.assert_called_once_with('group', 'scene')
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_setup_no_host(hass, requests_mock):
|
||||||
|
"""No host specified in any way."""
|
||||||
|
requests_mock.get(hue.API_NUPNP, json=[])
|
||||||
|
with MockDependency('phue') as mock_phue:
|
||||||
|
result = yield from async_setup_component(
|
||||||
|
hass, hue.DOMAIN, {hue.DOMAIN: {}})
|
||||||
|
assert result
|
||||||
|
|
||||||
|
mock_phue.Bridge.assert_not_called()
|
||||||
|
|
||||||
|
assert hass.data[hue.DOMAIN] == {}
|
||||||
|
|
Loading…
Reference in New Issue