Use API to discover Hue if no bridges specified (#11909)

* Use API to discover Hue if no bridges specified

* hide config file
pull/11833/head^2
Paulus Schoutsen 2018-01-25 05:55:14 -08:00 committed by Pascal Vizeli
parent 9123dfce6d
commit 3d9ff372fc
3 changed files with 58 additions and 28 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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] == {}