Discovery fix (#6321)

* Fix incorrect import

* Create own discovery service

* Fix tests

* Fix hdmi_cec bad import
pull/6339/head
Paulus Schoutsen 2017-03-01 07:38:49 -08:00
parent 52b1e13aca
commit 6736534a52
4 changed files with 134 additions and 102 deletions

View File

@ -6,20 +6,23 @@ Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered.
Knows which components handle certain types, will make sure they are
loaded before the EVENT_PLATFORM_DISCOVERED is fired.
"""
import asyncio
from datetime import timedelta
import logging
import threading
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_START
from homeassistant.helpers.discovery import load_platform, discover
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.helpers.discovery import async_load_platform, async_discover
import homeassistant.util.dt as dt_util
REQUIREMENTS = ['netdisco==0.8.3']
REQUIREMENTS = ['netdisco==0.9.0']
DOMAIN = 'discovery'
SCAN_INTERVAL = 300 # seconds
SCAN_INTERVAL = timedelta(seconds=300)
SERVICE_NETGEAR = 'netgear_router'
SERVICE_WEMO = 'belkin_wemo'
SERVICE_HASS_IOS_APP = 'hass_ios'
@ -48,18 +51,20 @@ SERVICE_HANDLERS = {
CONF_IGNORE = 'ignore'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(DOMAIN): vol.Schema({
vol.Optional(CONF_IGNORE, default=[]):
vol.All(cv.ensure_list, [vol.In(SERVICE_HANDLERS)])
}),
}, extra=vol.ALLOW_EXTRA)
def setup(hass, config):
@asyncio.coroutine
def async_setup(hass, config):
"""Start a discovery service."""
logger = logging.getLogger(__name__)
from netdisco.discovery import NetworkDiscovery
from netdisco.service import DiscoveryService
logger = logging.getLogger(__name__)
netdisco = NetworkDiscovery()
# Disable zeroconf logging, it spams
logging.getLogger('zeroconf').setLevel(logging.CRITICAL)
@ -67,37 +72,56 @@ def setup(hass, config):
# Platforms ignore by config
ignored_platforms = config[DOMAIN][CONF_IGNORE]
lock = threading.Lock()
def new_service_listener(service, info):
@asyncio.coroutine
def new_service_found(service, info):
"""Called when a new service is found."""
if service in ignored_platforms:
logger.info("Ignoring service: %s %s", service, info)
return
with lock:
logger.info("Found new service: %s %s", service, info)
logger.info("Found new service: %s %s", service, info)
comp_plat = SERVICE_HANDLERS.get(service)
comp_plat = SERVICE_HANDLERS.get(service)
# We do not know how to handle this service.
if not comp_plat:
return
# We do not know how to handle this service.
if not comp_plat:
return
component, platform = comp_plat
component, platform = comp_plat
if platform is None:
discover(hass, service, info, component, config)
else:
load_platform(hass, component, platform, info, config)
if platform is None:
yield from async_discover(hass, service, info, component, config)
else:
yield from async_load_platform(
hass, component, platform, info, config)
# pylint: disable=unused-argument
def start_discovery(event):
"""Start discovering."""
netdisco = DiscoveryService(SCAN_INTERVAL)
netdisco.add_listener(new_service_listener)
netdisco.start()
@asyncio.coroutine
def scan_devices(_):
"""Scan for devices."""
results = yield from hass.loop.run_in_executor(
None, _discover, netdisco)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_discovery)
for result in results:
hass.async_add_job(new_service_found(*result))
async_track_point_in_utc_time(hass, scan_devices,
dt_util.utcnow() + SCAN_INTERVAL)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, scan_devices)
return True
def _discover(netdisco):
"""Discover devices."""
results = []
try:
netdisco.scan()
for disc in netdisco.discover():
for service in netdisco.get_info(disc):
results.append((disc, service))
finally:
netdisco.stop()
return results

View File

@ -13,7 +13,7 @@ from functools import reduce
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components import discovery
from homeassistant.helpers import discovery
from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER
from homeassistant.components.switch import DOMAIN as SWITCH
from homeassistant.config import load_yaml_config_file

View File

@ -350,7 +350,7 @@ mutagen==1.36.2
myusps==1.0.3
# homeassistant.components.discovery
netdisco==0.8.3
netdisco==0.9.0
# homeassistant.components.sensor.neurio_energy
neurio==0.3.1

View File

@ -1,14 +1,13 @@
"""The tests for the discovery component."""
import unittest
import asyncio
from unittest import mock
from unittest.mock import patch
from homeassistant.bootstrap import setup_component
from homeassistant.bootstrap import async_setup_component
from homeassistant.components import discovery
from homeassistant.const import EVENT_HOMEASSISTANT_START
from tests.common import get_test_home_assistant
from tests.common import mock_coro
# One might consider to "mock" services, but it's easy enough to just use
# what is already available.
@ -34,87 +33,96 @@ IGNORE_CONFIG = {
}
@patch('netdisco.service.DiscoveryService')
@patch('homeassistant.components.discovery.load_platform')
@patch('homeassistant.components.discovery.discover')
class DiscoveryTest(unittest.TestCase):
"""Test the discovery component."""
@asyncio.coroutine
def test_unknown_service(hass):
"""Test that unknown service is ignored."""
result = yield from async_setup_component(hass, 'discovery', {
'discovery': {},
})
assert result
def setUp(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.netdisco = mock.Mock()
def discover(netdisco):
"""Fake discovery."""
return [('this_service_will_never_be_supported', {'info': 'some'})]
def tearDown(self):
"""Stop everything that was started."""
self.hass.stop()
with patch.object(discovery, '_discover', discover), \
patch('homeassistant.components.discovery.async_discover',
return_value=mock_coro()) as mock_discover, \
patch('homeassistant.components.discovery.async_load_platform',
return_value=mock_coro()) as mock_platform:
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
yield from hass.async_block_till_done()
def setup_discovery_component(self, discovery_service, config):
"""Setup the discovery component with mocked netdisco."""
discovery_service.return_value = self.netdisco
assert not mock_discover.called
assert not mock_platform.called
setup_component(self.hass, discovery.DOMAIN, config)
self.hass.bus.fire(EVENT_HOMEASSISTANT_START)
self.hass.block_till_done()
@asyncio.coroutine
def test_load_platform(hass):
"""Test load a platform."""
result = yield from async_setup_component(hass, 'discovery', BASE_CONFIG)
assert result
def discover_service(self, discovery_service, name):
"""Simulate that netdisco discovered a new service."""
self.assertTrue(self.netdisco.add_listener.called)
def discover(netdisco):
"""Fake discovery."""
return [(SERVICE, SERVICE_INFO)]
# Extract a refernce to the service listener
args, _ = self.netdisco.add_listener.call_args
listener = args[0]
with patch.object(discovery, '_discover', discover), \
patch('homeassistant.components.discovery.async_discover',
return_value=mock_coro()) as mock_discover, \
patch('homeassistant.components.discovery.async_load_platform',
return_value=mock_coro()) as mock_platform:
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
yield from hass.async_block_till_done()
# Call the listener (just like netdisco does)
listener(name, SERVICE_INFO)
assert not mock_discover.called
assert mock_platform.called
mock_platform.assert_called_with(
hass, SERVICE_COMPONENT, SERVICE, SERVICE_INFO, BASE_CONFIG)
def test_netdisco_is_started(
self, discover, load_platform, discovery_service):
"""Test that netdisco is started."""
self.setup_discovery_component(discovery_service, BASE_CONFIG)
self.assertTrue(self.netdisco.start.called)
def test_unknown_service(
self, discover, load_platform, discovery_service):
"""Test that unknown service is ignored."""
self.setup_discovery_component(discovery_service, BASE_CONFIG)
self.discover_service(discovery_service, UNKNOWN_SERVICE)
@asyncio.coroutine
def test_load_component(hass):
"""Test load a component."""
result = yield from async_setup_component(hass, 'discovery', BASE_CONFIG)
assert result
self.assertFalse(load_platform.called)
self.assertFalse(discover.called)
def discover(netdisco):
"""Fake discovery."""
return [(SERVICE_NO_PLATFORM, SERVICE_INFO)]
def test_load_platform(
self, discover, load_platform, discovery_service):
"""Test load a supported platform."""
self.setup_discovery_component(discovery_service, BASE_CONFIG)
self.discover_service(discovery_service, SERVICE)
with patch.object(discovery, '_discover', discover), \
patch('homeassistant.components.discovery.async_discover',
return_value=mock_coro()) as mock_discover, \
patch('homeassistant.components.discovery.async_load_platform',
return_value=mock_coro()) as mock_platform:
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
yield from hass.async_block_till_done()
load_platform.assert_called_with(self.hass,
SERVICE_COMPONENT,
SERVICE,
SERVICE_INFO,
BASE_CONFIG)
assert mock_discover.called
assert not mock_platform.called
mock_discover.assert_called_with(
hass, SERVICE_NO_PLATFORM, SERVICE_INFO,
SERVICE_NO_PLATFORM_COMPONENT, BASE_CONFIG)
def test_discover_platform(
self, discover, load_platform, discovery_service):
"""Test discover a supported platform."""
self.setup_discovery_component(discovery_service, BASE_CONFIG)
self.discover_service(discovery_service, SERVICE_NO_PLATFORM)
discover.assert_called_with(self.hass,
SERVICE_NO_PLATFORM,
SERVICE_INFO,
SERVICE_NO_PLATFORM_COMPONENT,
BASE_CONFIG)
@asyncio.coroutine
def test_ignore_service(hass):
"""Test ignore service."""
result = yield from async_setup_component(hass, 'discovery', IGNORE_CONFIG)
assert result
def test_ignore_platforms(
self, discover, load_platform, discovery_service):
"""Test that ignored platforms are not setup."""
self.setup_discovery_component(discovery_service, IGNORE_CONFIG)
def discover(netdisco):
"""Fake discovery."""
return [(SERVICE_NO_PLATFORM, SERVICE_INFO)]
self.discover_service(discovery_service, SERVICE_NO_PLATFORM)
self.assertFalse(discover.called)
with patch.object(discovery, '_discover', discover), \
patch('homeassistant.components.discovery.async_discover',
return_value=mock_coro()) as mock_discover, \
patch('homeassistant.components.discovery.async_load_platform',
return_value=mock_coro()) as mock_platform:
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
yield from hass.async_block_till_done()
self.discover_service(discovery_service, SERVICE)
self.assertTrue(load_platform.called)
assert not mock_discover.called
assert not mock_platform.called