Skip to content

Commit

Permalink
Use API to discover Hue if no bridges specified (#11909)
Browse files Browse the repository at this point in the history
* Use API to discover Hue if no bridges specified

* hide config file
  • Loading branch information
balloob authored and pvizeli committed Jan 25, 2018
1 parent 9123dfc commit 3d9ff37
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 28 deletions.
26 changes: 21 additions & 5 deletions homeassistant/components/hue.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import os
import socket

import requests
import voluptuous as vol

from homeassistant.components.discovery import SERVICE_HUE
Expand All @@ -22,6 +23,7 @@

DOMAIN = "hue"
SERVICE_HUE_SCENE = "hue_activate_scene"
API_NUPNP = 'https://www.meethue.com/api/nupnp'

CONF_BRIDGES = "bridges"

Expand Down Expand Up @@ -49,7 +51,7 @@

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_BRIDGES, default=[]): BRIDGE_CONFIG_SCHEMA,
vol.Optional(CONF_BRIDGES): BRIDGE_CONFIG_SCHEMA,
}),
}, extra=vol.ALLOW_EXTRA)

Expand All @@ -69,9 +71,9 @@

def setup(hass, config):
"""Set up the Hue platform."""
config = config.get(DOMAIN)
if config is None:
config = {}
conf = config.get(DOMAIN)
if conf is None:
conf = {}

if DOMAIN not in hass.data:
hass.data[DOMAIN] = {}
Expand All @@ -82,7 +84,21 @@ def setup(hass, config):
lambda 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:
filename = bridge.get(CONF_FILENAME)
allow_unreachable = bridge.get(CONF_ALLOW_UNREACHABLE)
Expand Down
33 changes: 21 additions & 12 deletions tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -542,27 +542,36 @@ def __init__(self, root, *args):
self.root = root
self.submodules = args

def __call__(self, func):
"""Apply decorator."""
from unittest.mock import MagicMock, patch

def __enter__(self):
"""Start mocking."""
def resolve(mock, path):
"""Resolve a mock."""
if not path:
return mock

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):
"""Run with mocked dependencies."""
base = MagicMock()
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):
with self as base:
args = list(args) + [base]
func(*args, **kwargs)

Expand Down
27 changes: 16 additions & 11 deletions tests/components/test_hue.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""Generic Philips Hue component tests."""

import asyncio
import logging
import unittest
from unittest.mock import call, MagicMock, patch

from homeassistant.components import configurator, hue
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 (
assert_setup_component, get_test_home_assistant, get_test_config_dir,
Expand Down Expand Up @@ -38,15 +38,6 @@ def test_setup_no_domain(self, mock_phue):
mock_phue.Bridge.assert_not_called()
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')
def test_setup_with_host(self, mock_phue):
"""Host specified in the config file."""
Expand Down Expand Up @@ -400,3 +391,17 @@ def test_hue_activate_scene(self, mock_phue):
{hue.ATTR_GROUP_NAME: 'group', hue.ATTR_SCENE_NAME: 'scene'},
blocking=True)
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] == {}

0 comments on commit 3d9ff37

Please sign in to comment.