From 30f74bb3caf84883ca2c2e0bfc51131a1e731ffd Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 11 Jun 2016 17:43:13 -0700 Subject: [PATCH] Migrate to generic discovery method (#2271) * Migrate to generic discovery method * Add tests for discovery --- .../alarm_control_panel/__init__.py | 9 +- .../components/binary_sensor/__init__.py | 15 +-- homeassistant/components/bloomsky.py | 14 +-- homeassistant/components/camera/__init__.py | 10 +- .../components/device_tracker/__init__.py | 14 ++- homeassistant/components/discovery.py | 108 +++--------------- homeassistant/components/ecobee.py | 25 +--- .../components/garage_door/__init__.py | 10 +- homeassistant/components/hvac/__init__.py | 8 +- homeassistant/components/insteon_hub.py | 19 +-- homeassistant/components/isy994.py | 24 ++-- homeassistant/components/light/__init__.py | 20 +--- homeassistant/components/lock/__init__.py | 12 +- .../components/media_player/__init__.py | 13 +-- homeassistant/components/mysensors.py | 32 +----- homeassistant/components/netatmo.py | 12 +- homeassistant/components/octoprint.py | 3 +- .../components/rollershutter/__init__.py | 6 +- homeassistant/components/sensor/__init__.py | 20 +--- homeassistant/components/switch/__init__.py | 20 +--- homeassistant/components/tellduslive.py | 24 +--- homeassistant/components/tellstick.py | 24 +--- .../components/thermostat/__init__.py | 9 +- homeassistant/components/vera.py | 51 +++------ homeassistant/components/verisure.py | 23 +--- homeassistant/components/wemo.py | 36 +++--- homeassistant/components/wink.py | 44 ++----- homeassistant/components/zwave.py | 39 ++----- homeassistant/helpers/discovery.py | 86 ++++++++++++++ homeassistant/helpers/entity_component.py | 27 ++--- tests/helpers/test_discovery.py | 90 +++++++++++++++ tests/helpers/test_entity_component.py | 15 +-- 32 files changed, 322 insertions(+), 540 deletions(-) create mode 100644 homeassistant/helpers/discovery.py create mode 100644 tests/helpers/test_discovery.py diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index cf042abbe10..29b2d47fd20 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -9,7 +9,6 @@ import os import voluptuous as vol -from homeassistant.components import verisure from homeassistant.const import ( ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER, SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY) @@ -24,11 +23,6 @@ SCAN_INTERVAL = 30 ENTITY_ID_FORMAT = DOMAIN + '.{}' -# Maps discovered services to their platforms -DISCOVERY_PLATFORMS = { - verisure.DISCOVER_ALARMS: 'verisure' -} - SERVICE_TO_METHOD = { SERVICE_ALARM_DISARM: 'alarm_disarm', SERVICE_ALARM_ARM_HOME: 'alarm_arm_home', @@ -50,8 +44,7 @@ ALARM_SERVICE_SCHEMA = vol.Schema({ def setup(hass, config): """Track states and offer events for sensors.""" component = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, - DISCOVERY_PLATFORMS) + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) component.setup(config) diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 8d941166fdd..18cb8ea0cb2 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -9,8 +9,6 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.const import (STATE_ON, STATE_OFF) -from homeassistant.components import ( - bloomsky, mysensors, zwave, vera, wemo, wink) from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa DOMAIN = 'binary_sensor' @@ -35,22 +33,11 @@ SENSOR_CLASSES = [ 'vibration', # On means vibration detected, Off means no vibration ] -# Maps discovered services to their platforms -DISCOVERY_PLATFORMS = { - bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky', - mysensors.DISCOVER_BINARY_SENSORS: 'mysensors', - zwave.DISCOVER_BINARY_SENSORS: 'zwave', - vera.DISCOVER_BINARY_SENSORS: 'vera', - wemo.DISCOVER_BINARY_SENSORS: 'wemo', - wink.DISCOVER_BINARY_SENSORS: 'wink' -} - def setup(hass, config): """Track states and offer events for binary sensors.""" component = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, - DISCOVERY_PLATFORMS) + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) component.setup(config) diff --git a/homeassistant/components/bloomsky.py b/homeassistant/components/bloomsky.py index de9f4a18b91..b881dcb9526 100644 --- a/homeassistant/components/bloomsky.py +++ b/homeassistant/components/bloomsky.py @@ -9,9 +9,8 @@ from datetime import timedelta import requests -from homeassistant.components import discovery from homeassistant.const import CONF_API_KEY -from homeassistant.helpers import validate_config +from homeassistant.helpers import validate_config, discovery from homeassistant.util import Throttle DOMAIN = "bloomsky" @@ -23,10 +22,6 @@ _LOGGER = logging.getLogger(__name__) # no point in polling the API more frequently MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) -DISCOVER_SENSORS = 'bloomsky.sensors' -DISCOVER_BINARY_SENSORS = 'bloomsky.binary_sensor' -DISCOVER_CAMERAS = 'bloomsky.camera' - # pylint: disable=unused-argument,too-few-public-methods def setup(hass, config): @@ -45,11 +40,8 @@ def setup(hass, config): except RuntimeError: return False - for component, discovery_service in ( - ('camera', DISCOVER_CAMERAS), ('sensor', DISCOVER_SENSORS), - ('binary_sensor', DISCOVER_BINARY_SENSORS)): - discovery.discover(hass, discovery_service, component=component, - hass_config=config) + for component in 'camera', 'binary_sensor', 'sensor': + discovery.load_platform(hass, component, DOMAIN, {}, config) return True diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 649d540a44d..585160f1dac 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -9,7 +9,6 @@ import logging from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.components import bloomsky, netatmo from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa from homeassistant.components.http import HomeAssistantView @@ -18,12 +17,6 @@ DEPENDENCIES = ['http'] SCAN_INTERVAL = 30 ENTITY_ID_FORMAT = DOMAIN + '.{}' -# Maps discovered services to their platforms -DISCOVERY_PLATFORMS = { - bloomsky.DISCOVER_CAMERAS: 'bloomsky', - netatmo.DISCOVER_CAMERAS: 'netatmo', -} - STATE_RECORDING = 'recording' STATE_STREAMING = 'streaming' STATE_IDLE = 'idle' @@ -35,8 +28,7 @@ ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}' def setup(hass, config): """Setup the camera component.""" component = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, - DISCOVERY_PLATFORMS) + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) hass.wsgi.register_view(CameraImageView(hass, component.entities)) hass.wsgi.register_view(CameraMjpegStream(hass, component.entities)) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 55b988fde4c..07b4343803c 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -12,10 +12,11 @@ import os import threading from homeassistant.bootstrap import prepare_setup_platform -from homeassistant.components import discovery, group, zone +from homeassistant.components import group, zone +from homeassistant.components.discovery import SERVICE_NETGEAR from homeassistant.config import load_yaml_config_file from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import config_per_platform +from homeassistant.helpers import config_per_platform, discovery from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv import homeassistant.util as util @@ -62,7 +63,7 @@ ATTR_GPS = 'gps' ATTR_BATTERY = 'battery' DISCOVERY_PLATFORMS = { - discovery.SERVICE_NETGEAR: 'netgear', + SERVICE_NETGEAR: 'netgear', } _LOGGER = logging.getLogger(__name__) @@ -95,8 +96,11 @@ def setup(hass, config): yaml_path = hass.config.path(YAML_DEVICES) conf = config.get(DOMAIN, {}) - if isinstance(conf, list) and len(conf) > 0: - conf = conf[0] + + # Config can be an empty list. In that case, substitute a dict + if isinstance(conf, list): + conf = conf[0] if len(conf) > 0 else {} + consider_home = timedelta( seconds=util.convert(conf.get(CONF_CONSIDER_HOME), int, DEFAULT_CONSIDER_HOME)) diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index fac94aacf69..b4a865abffc 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -9,100 +9,30 @@ loaded before the EVENT_PLATFORM_DISCOVERED is fired. import logging import threading -from homeassistant import bootstrap -from homeassistant.const import ( - ATTR_DISCOVERED, ATTR_SERVICE, EVENT_HOMEASSISTANT_START, - EVENT_PLATFORM_DISCOVERED) +from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.helpers.discovery import load_platform, discover DOMAIN = "discovery" REQUIREMENTS = ['netdisco==0.6.7'] SCAN_INTERVAL = 300 # seconds -LOAD_PLATFORM = 'load_platform' - SERVICE_WEMO = 'belkin_wemo' -SERVICE_HUE = 'philips_hue' -SERVICE_CAST = 'google_cast' SERVICE_NETGEAR = 'netgear_router' -SERVICE_SONOS = 'sonos' -SERVICE_PLEX = 'plex_mediaserver' -SERVICE_SQUEEZEBOX = 'logitech_mediaserver' -SERVICE_PANASONIC_VIERA = 'panasonic_viera' -SERVICE_ROKU = 'roku' SERVICE_HANDLERS = { - SERVICE_WEMO: "wemo", - SERVICE_CAST: "media_player", - SERVICE_HUE: "light", - SERVICE_NETGEAR: 'device_tracker', - SERVICE_SONOS: 'media_player', - SERVICE_PLEX: 'media_player', - SERVICE_SQUEEZEBOX: 'media_player', - SERVICE_PANASONIC_VIERA: 'media_player', - SERVICE_ROKU: 'media_player', + SERVICE_NETGEAR: ('device_tracker', None), + SERVICE_WEMO: ('wemo', None), + 'philips_hue': ('light', 'hue'), + 'google_cast': ('media_player', 'cast'), + 'panasonic_viera': ('media_player', 'panasonic_viera'), + 'plex_mediaserver': ('media_player', 'plex'), + 'roku': ('media_player', 'roku'), + 'sonos': ('media_player', 'sonos'), + 'logitech_mediaserver': ('media_player', 'squeezebox'), } -def listen(hass, service, callback): - """Setup listener for discovery of specific service. - - Service can be a string or a list/tuple. - """ - if isinstance(service, str): - service = (service,) - else: - service = tuple(service) - - def discovery_event_listener(event): - """Listen for discovery events.""" - if ATTR_SERVICE in event.data and event.data[ATTR_SERVICE] in service: - callback(event.data[ATTR_SERVICE], event.data.get(ATTR_DISCOVERED)) - - hass.bus.listen(EVENT_PLATFORM_DISCOVERED, discovery_event_listener) - - -def discover(hass, service, discovered=None, component=None, hass_config=None): - """Fire discovery event. Can ensure a component is loaded.""" - if component is not None: - bootstrap.setup_component(hass, component, hass_config) - - data = { - ATTR_SERVICE: service - } - - if discovered is not None: - data[ATTR_DISCOVERED] = discovered - - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, data) - - -def load_platform(hass, component, platform, info=None, hass_config=None): - """Helper method for generic platform loading. - - This method allows a platform to be loaded dynamically without it being - known at runtime (in the DISCOVERY_PLATFORMS list of the component). - Advantages of using this method: - - Any component & platforms combination can be dynamically added - - A component (i.e. light) does not have to import every component - that can dynamically add a platform (e.g. wemo, wink, insteon_hub) - - Custom user components can take advantage of discovery/loading - - Target components will be loaded and an EVENT_PLATFORM_DISCOVERED will be - fired to load the platform. The event will contain: - { ATTR_SERVICE = LOAD_PLATFORM + '.' + <> - ATTR_DISCOVERED = {LOAD_PLATFORM: <>} } - - * dev note: This listener can be found in entity_component.py - """ - if info is None: - info = {LOAD_PLATFORM: platform} - else: - info[LOAD_PLATFORM] = platform - discover(hass, LOAD_PLATFORM + '.' + component, info, component, - hass_config) - - def setup(hass, config): """Start a discovery service.""" logger = logging.getLogger(__name__) @@ -119,20 +49,18 @@ def setup(hass, config): with lock: logger.info("Found new service: %s %s", service, info) - component = SERVICE_HANDLERS.get(service) + comp_plat = SERVICE_HANDLERS.get(service) # We do not know how to handle this service. - if not component: + if not comp_plat: return - # This component cannot be setup. - if not bootstrap.setup_component(hass, component, config): - return + component, platform = comp_plat - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { - ATTR_SERVICE: service, - ATTR_DISCOVERED: info - }) + if platform is None: + discover(hass, service, info, component, config) + else: + load_platform(hass, component, platform, info, config) # pylint: disable=unused-argument def start_discovery(event): diff --git a/homeassistant/components/ecobee.py b/homeassistant/components/ecobee.py index a1986f8e6eb..3d1b00f60c5 100644 --- a/homeassistant/components/ecobee.py +++ b/homeassistant/components/ecobee.py @@ -8,15 +8,12 @@ import logging import os from datetime import timedelta -from homeassistant import bootstrap -from homeassistant.const import ( - ATTR_DISCOVERED, ATTR_SERVICE, CONF_API_KEY, EVENT_PLATFORM_DISCOVERED) +from homeassistant.helpers import discovery +from homeassistant.const import CONF_API_KEY from homeassistant.loader import get_component from homeassistant.util import Throttle DOMAIN = "ecobee" -DISCOVER_THERMOSTAT = "ecobee.thermostat" -DISCOVER_SENSORS = "ecobee.sensor" NETWORK = None HOLD_TEMP = 'hold_temp' @@ -70,23 +67,11 @@ def setup_ecobee(hass, network, config): configurator = get_component('configurator') configurator.request_done(_CONFIGURING.pop('ecobee')) - # Ensure component is loaded - bootstrap.setup_component(hass, 'thermostat', config) - bootstrap.setup_component(hass, 'sensor', config) - hold_temp = config[DOMAIN].get(HOLD_TEMP, False) - # Fire thermostat discovery event - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { - ATTR_SERVICE: DISCOVER_THERMOSTAT, - ATTR_DISCOVERED: {'hold_temp': hold_temp} - }) - - # Fire sensor discovery event - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { - ATTR_SERVICE: DISCOVER_SENSORS, - ATTR_DISCOVERED: {} - }) + discovery.load_platform(hass, 'thermostat', DOMAIN, + {'hold_temp': hold_temp}, config) + discovery.load_platform(hass, 'sensor', DOMAIN, None, config) # pylint: disable=too-few-public-methods diff --git a/homeassistant/components/garage_door/__init__.py b/homeassistant/components/garage_door/__init__.py index 30d9a5e2e0b..37e422d0f12 100644 --- a/homeassistant/components/garage_door/__init__.py +++ b/homeassistant/components/garage_door/__init__.py @@ -17,7 +17,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.const import ( STATE_CLOSED, STATE_OPEN, STATE_UNKNOWN, SERVICE_CLOSE, SERVICE_OPEN, ATTR_ENTITY_ID) -from homeassistant.components import (group, wink) +from homeassistant.components import group DOMAIN = 'garage_door' SCAN_INTERVAL = 30 @@ -27,11 +27,6 @@ ENTITY_ID_ALL_GARAGE_DOORS = group.ENTITY_ID_FORMAT.format('all_garage_doors') ENTITY_ID_FORMAT = DOMAIN + '.{}' -# Maps discovered services to their platforms -DISCOVERY_PLATFORMS = { - wink.DISCOVER_GARAGE_DOORS: 'wink' -} - GARAGE_DOOR_SERVICE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, }) @@ -60,8 +55,7 @@ def open_door(hass, entity_id=None): def setup(hass, config): """Track states and offer events for garage door.""" component = EntityComponent( - _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS, - GROUP_NAME_ALL_GARAGE_DOORS) + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_GARAGE_DOORS) component.setup(config) def handle_garage_door_service(service): diff --git a/homeassistant/components/hvac/__init__.py b/homeassistant/components/hvac/__init__.py index ca5673aafb8..85d10671a17 100644 --- a/homeassistant/components/hvac/__init__.py +++ b/homeassistant/components/hvac/__init__.py @@ -14,7 +14,6 @@ import homeassistant.util as util from homeassistant.helpers.entity import Entity from homeassistant.helpers.temperature import convert from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa -from homeassistant.components import zwave from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN, TEMP_CELCIUS) @@ -57,10 +56,6 @@ ATTR_SWING_LIST = "swing_list" _LOGGER = logging.getLogger(__name__) -DISCOVERY_PLATFORMS = { - zwave.DISCOVER_HVAC: 'zwave' -} - def set_away_mode(hass, away_mode, entity_id=None): """Turn all or specified hvac away mode on.""" @@ -139,8 +134,7 @@ def set_swing_mode(hass, swing_mode, entity_id=None): # pylint: disable=too-many-branches def setup(hass, config): """Setup hvacs.""" - component = EntityComponent(_LOGGER, DOMAIN, hass, - SCAN_INTERVAL, DISCOVERY_PLATFORMS) + component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) component.setup(config) descriptions = load_yaml_config_file( diff --git a/homeassistant/components/insteon_hub.py b/homeassistant/components/insteon_hub.py index a2688f48dd9..bd12f32b39d 100644 --- a/homeassistant/components/insteon_hub.py +++ b/homeassistant/components/insteon_hub.py @@ -6,17 +6,12 @@ https://home-assistant.io/components/insteon_hub/ """ import logging -import homeassistant.bootstrap as bootstrap -from homeassistant.const import ( - ATTR_DISCOVERED, ATTR_SERVICE, CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME, - EVENT_PLATFORM_DISCOVERED) -from homeassistant.helpers import validate_config -from homeassistant.loader import get_component +from homeassistant.const import CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME +from homeassistant.helpers import validate_config, discovery DOMAIN = "insteon_hub" REQUIREMENTS = ['insteon_hub==0.4.5'] INSTEON = None -DISCOVER_LIGHTS = "insteon_hub.lights" _LOGGER = logging.getLogger(__name__) @@ -44,11 +39,7 @@ def setup(hass, config): _LOGGER.error("Could not connect to Insteon service.") return - comp_name = 'light' - discovery = DISCOVER_LIGHTS - component = get_component(comp_name) - bootstrap.setup_component(hass, component.DOMAIN, config) - hass.bus.fire( - EVENT_PLATFORM_DISCOVERED, - {ATTR_SERVICE: discovery, ATTR_DISCOVERED: {}}) + for component in 'light': + discovery.load_platform(hass, component, DOMAIN, None, config) + return True diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index 09bf62ce849..a657eb14794 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -7,19 +7,15 @@ https://home-assistant.io/components/isy994/ import logging from urllib.parse import urlparse -from homeassistant import bootstrap from homeassistant.const import ( - ATTR_DISCOVERED, ATTR_SERVICE, CONF_HOST, CONF_PASSWORD, CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED) -from homeassistant.helpers import validate_config + CONF_HOST, CONF_PASSWORD, CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP) +from homeassistant.helpers import validate_config, discovery from homeassistant.helpers.entity import ToggleEntity -from homeassistant.loader import get_component DOMAIN = "isy994" REQUIREMENTS = ['PyISY==1.0.6'] -DISCOVER_LIGHTS = "isy994.lights" -DISCOVER_SWITCHES = "isy994.switches" -DISCOVER_SENSORS = "isy994.sensors" + ISY = None SENSOR_STRING = 'Sensor' HIDDEN_STRING = '{HIDE ME}' @@ -76,15 +72,9 @@ def setup(hass, config): # Listen for HA stop to disconnect. hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop) - # Load components for the devices in the ISY controller that we support. - for comp_name, discovery in ((('sensor', DISCOVER_SENSORS), - ('light', DISCOVER_LIGHTS), - ('switch', DISCOVER_SWITCHES))): - component = get_component(comp_name) - bootstrap.setup_component(hass, component.DOMAIN, config) - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, - {ATTR_SERVICE: discovery, - ATTR_DISCOVERED: {}}) + # Load platforms for the devices in the ISY controller that we support. + for component in ('sensor', 'light', 'switch'): + discovery.load_platform(hass, component, DOMAIN, None, config) ISY.auto_update = True return True diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index d1fe0b93f4c..27c68819909 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -10,9 +10,7 @@ import csv import voluptuous as vol -from homeassistant.components import ( - group, discovery, wemo, wink, isy994, - zwave, insteon_hub, mysensors, tellstick, vera) +from homeassistant.components import group from homeassistant.config import load_yaml_config_file from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, @@ -60,19 +58,6 @@ EFFECT_WHITE = "white" LIGHT_PROFILES_FILE = "light_profiles.csv" -# Maps discovered services to their platforms. -DISCOVERY_PLATFORMS = { - wemo.DISCOVER_LIGHTS: 'wemo', - wink.DISCOVER_LIGHTS: 'wink', - insteon_hub.DISCOVER_LIGHTS: 'insteon_hub', - isy994.DISCOVER_LIGHTS: 'isy994', - discovery.SERVICE_HUE: 'hue', - zwave.DISCOVER_LIGHTS: 'zwave', - mysensors.DISCOVER_LIGHTS: 'mysensors', - tellstick.DISCOVER_LIGHTS: 'tellstick', - vera.DISCOVER_LIGHTS: 'vera', -} - PROP_TO_ATTR = { 'brightness': ATTR_BRIGHTNESS, 'color_temp': ATTR_COLOR_TEMP, @@ -172,8 +157,7 @@ def toggle(hass, entity_id=None, transition=None): def setup(hass, config): """Expose light control via statemachine and services.""" component = EntityComponent( - _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS, - GROUP_NAME_ALL_LIGHTS) + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_LIGHTS) component.setup(config) # Load built-in profiles and custom profiles diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py index 8b27929e816..1986da20e94 100644 --- a/homeassistant/components/lock/__init__.py +++ b/homeassistant/components/lock/__init__.py @@ -18,7 +18,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.const import ( ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK) -from homeassistant.components import (group, verisure, wink, zwave) +from homeassistant.components import group DOMAIN = 'lock' SCAN_INTERVAL = 30 @@ -30,13 +30,6 @@ ENTITY_ID_FORMAT = DOMAIN + '.{}' MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) -# Maps discovered services to their platforms -DISCOVERY_PLATFORMS = { - wink.DISCOVER_LOCKS: 'wink', - verisure.DISCOVER_LOCKS: 'verisure', - zwave.DISCOVER_LOCKS: 'zwave', -} - LOCK_SERVICE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Optional(ATTR_CODE): cv.string, @@ -76,8 +69,7 @@ def unlock(hass, entity_id=None, code=None): def setup(hass, config): """Track states and offer events for locks.""" component = EntityComponent( - _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS, - GROUP_NAME_ALL_LOCKS) + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_LOCKS) component.setup(config) def handle_lock_service(service): diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index c5e2eb00e57..25c55f46b7b 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -9,7 +9,6 @@ import os import voluptuous as vol -from homeassistant.components import discovery from homeassistant.config import load_yaml_config_file from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent @@ -30,15 +29,6 @@ SCAN_INTERVAL = 10 ENTITY_ID_FORMAT = DOMAIN + '.{}' -DISCOVERY_PLATFORMS = { - discovery.SERVICE_CAST: 'cast', - discovery.SERVICE_SONOS: 'sonos', - discovery.SERVICE_PLEX: 'plex', - discovery.SERVICE_SQUEEZEBOX: 'squeezebox', - discovery.SERVICE_PANASONIC_VIERA: 'panasonic_viera', - discovery.SERVICE_ROKU: 'roku', -} - SERVICE_PLAY_MEDIA = 'play_media' SERVICE_SELECT_SOURCE = 'select_source' @@ -285,8 +275,7 @@ def select_source(hass, source, entity_id=None): def setup(hass, config): """Track states and offer events for media_players.""" component = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, - DISCOVERY_PLATFORMS) + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) component.setup(config) diff --git a/homeassistant/components/mysensors.py b/homeassistant/components/mysensors.py index 9d42316d382..e87cb2a4411 100644 --- a/homeassistant/components/mysensors.py +++ b/homeassistant/components/mysensors.py @@ -7,14 +7,11 @@ https://home-assistant.io/components/sensor.mysensors/ import logging import socket -import homeassistant.bootstrap as bootstrap -from homeassistant.const import (ATTR_BATTERY_LEVEL, ATTR_DISCOVERED, - ATTR_SERVICE, CONF_OPTIMISTIC, +from homeassistant.const import (ATTR_BATTERY_LEVEL, CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, - EVENT_PLATFORM_DISCOVERED, STATE_OFF, - STATE_ON, TEMP_CELSIUS) -from homeassistant.helpers import validate_config + STATE_OFF, STATE_ON, TEMP_CELSIUS) +from homeassistant.helpers import validate_config, discovery CONF_GATEWAYS = 'gateways' CONF_DEVICE = 'device' @@ -40,19 +37,6 @@ ATTR_DEVICE = 'device' GATEWAYS = None -DISCOVER_SENSORS = 'mysensors.sensors' -DISCOVER_SWITCHES = 'mysensors.switches' -DISCOVER_LIGHTS = 'mysensors.lights' -DISCOVER_BINARY_SENSORS = 'mysensors.binary_sensor' - -# Maps discovered services to their platforms -DISCOVERY_COMPONENTS = [ - ('sensor', DISCOVER_SENSORS), - ('switch', DISCOVER_SWITCHES), - ('light', DISCOVER_LIGHTS), - ('binary_sensor', DISCOVER_BINARY_SENSORS), -] - def setup(hass, config): # pylint: disable=too-many-locals """Setup the MySensors component.""" @@ -124,14 +108,8 @@ def setup(hass, config): # pylint: disable=too-many-locals GATEWAYS[device] = setup_gateway( device, persistence_file, baud_rate, tcp_port) - for (component, discovery_service) in DISCOVERY_COMPONENTS: - # Ensure component is loaded - if not bootstrap.setup_component(hass, component, config): - return False - # Fire discovery event - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { - ATTR_SERVICE: discovery_service, - ATTR_DISCOVERED: {}}) + for component in 'sensor', 'switch', 'light', 'binary_sensor': + discovery.load_platform(hass, component, DOMAIN, None, config) return True diff --git a/homeassistant/components/netatmo.py b/homeassistant/components/netatmo.py index 737819fffbb..4b867831255 100644 --- a/homeassistant/components/netatmo.py +++ b/homeassistant/components/netatmo.py @@ -6,10 +6,9 @@ https://home-assistant.io/components/netatmo/ """ import logging from urllib.error import HTTPError -from homeassistant.components import discovery from homeassistant.const import ( CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME) -from homeassistant.helpers import validate_config +from homeassistant.helpers import validate_config, discovery REQUIREMENTS = [ 'https://github.com/jabesq/netatmo-api-python/archive/' @@ -24,9 +23,6 @@ NETATMO_AUTH = None _LOGGER = logging.getLogger(__name__) -DISCOVER_SENSORS = 'netatmo.sensors' -DISCOVER_CAMERAS = 'netatmo.cameras' - def setup(hass, config): """Setup the Netatmo devices.""" @@ -54,9 +50,7 @@ def setup(hass, config): "Please check your settings for NatAtmo API.") return False - for component, discovery_service in ( - ('camera', DISCOVER_CAMERAS), ('sensor', DISCOVER_SENSORS)): - discovery.discover(hass, discovery_service, component=component, - hass_config=config) + for component in 'camera', 'sensor': + discovery.load_platform(hass, component, DOMAIN, None, config) return True diff --git a/homeassistant/components/octoprint.py b/homeassistant/components/octoprint.py index 76db48b5a07..bd90e67d0df 100644 --- a/homeassistant/components/octoprint.py +++ b/homeassistant/components/octoprint.py @@ -9,9 +9,8 @@ import logging import time import requests -from homeassistant.components import discovery from homeassistant.const import CONF_API_KEY, CONF_HOST -from homeassistant.helpers import validate_config +from homeassistant.helpers import validate_config, discovery DOMAIN = "octoprint" OCTOPRINT = None diff --git a/homeassistant/components/rollershutter/__init__.py b/homeassistant/components/rollershutter/__init__.py index 98bee419802..c5fcb594f31 100644 --- a/homeassistant/components/rollershutter/__init__.py +++ b/homeassistant/components/rollershutter/__init__.py @@ -29,9 +29,6 @@ ENTITY_ID_ALL_ROLLERSHUTTERS = group.ENTITY_ID_FORMAT.format( ENTITY_ID_FORMAT = DOMAIN + '.{}' -# Maps discovered services to their platforms -DISCOVERY_PLATFORMS = {} - _LOGGER = logging.getLogger(__name__) ATTR_CURRENT_POSITION = 'current_position' @@ -68,8 +65,7 @@ def stop(hass, entity_id=None): def setup(hass, config): """Track states and offer events for roller shutters.""" component = EntityComponent( - _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS, - GROUP_NAME_ALL_ROLLERSHUTTERS) + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_ROLLERSHUTTERS) component.setup(config) def handle_rollershutter_service(service): diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 4cd5c50f22e..c018c04cdaf 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -8,35 +8,17 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa -from homeassistant.components import ( - wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors, - bloomsky, vera, netatmo) DOMAIN = 'sensor' SCAN_INTERVAL = 30 ENTITY_ID_FORMAT = DOMAIN + '.{}' -# Maps discovered services to their platforms -DISCOVERY_PLATFORMS = { - bloomsky.DISCOVER_SENSORS: 'bloomsky', - wink.DISCOVER_SENSORS: 'wink', - zwave.DISCOVER_SENSORS: 'zwave', - isy994.DISCOVER_SENSORS: 'isy994', - verisure.DISCOVER_SENSORS: 'verisure', - ecobee.DISCOVER_SENSORS: 'ecobee', - tellduslive.DISCOVER_SENSORS: 'tellduslive', - mysensors.DISCOVER_SENSORS: 'mysensors', - vera.DISCOVER_SENSORS: 'vera', - netatmo.DISCOVER_SENSORS: 'netatmo', -} - def setup(hass, config): """Track states and offer events for sensors.""" component = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, - DISCOVERY_PLATFORMS) + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) component.setup(config) diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 8bd0585ff0c..1f92b458d53 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -18,9 +18,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID) -from homeassistant.components import ( - group, wemo, wink, isy994, verisure, - zwave, tellduslive, tellstick, mysensors, vera) +from homeassistant.components import group DOMAIN = 'switch' SCAN_INTERVAL = 30 @@ -35,19 +33,6 @@ ATTR_CURRENT_POWER_MWH = "current_power_mwh" MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) -# Maps discovered services to their platforms -DISCOVERY_PLATFORMS = { - wemo.DISCOVER_SWITCHES: 'wemo', - wink.DISCOVER_SWITCHES: 'wink', - isy994.DISCOVER_SWITCHES: 'isy994', - verisure.DISCOVER_SWITCHES: 'verisure', - zwave.DISCOVER_SWITCHES: 'zwave', - tellduslive.DISCOVER_SWITCHES: 'tellduslive', - mysensors.DISCOVER_SWITCHES: 'mysensors', - tellstick.DISCOVER_SWITCHES: 'tellstick', - vera.DISCOVER_SWITCHES: 'vera', -} - PROP_TO_ATTR = { 'current_power_mwh': ATTR_CURRENT_POWER_MWH, 'today_power_mw': ATTR_TODAY_MWH, @@ -87,8 +72,7 @@ def toggle(hass, entity_id=None): def setup(hass, config): """Track states and offer events for switches.""" component = EntityComponent( - _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS, - GROUP_NAME_ALL_SWITCHES) + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_SWITCHES) component.setup(config) def handle_switch_service(service): diff --git a/homeassistant/components/tellduslive.py b/homeassistant/components/tellduslive.py index 7adbc2c7a24..ebc62174623 100644 --- a/homeassistant/components/tellduslive.py +++ b/homeassistant/components/tellduslive.py @@ -7,11 +7,7 @@ https://home-assistant.io/components/tellduslive/ import logging from datetime import timedelta -from homeassistant import bootstrap -from homeassistant.const import ( - ATTR_DISCOVERED, ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED) -from homeassistant.helpers import validate_config -from homeassistant.loader import get_component +from homeassistant.helpers import validate_config, discovery from homeassistant.util import Throttle DOMAIN = "tellduslive" @@ -20,12 +16,6 @@ REQUIREMENTS = ['tellive-py==0.5.2'] _LOGGER = logging.getLogger(__name__) -DISCOVER_SENSORS = "tellduslive.sensors" -DISCOVER_SWITCHES = "tellduslive.switches" -DISCOVERY_TYPES = {"sensor": DISCOVER_SENSORS, - "switch": DISCOVER_SWITCHES} - - CONF_PUBLIC_KEY = "public_key" CONF_PRIVATE_KEY = "private_key" CONF_TOKEN = "token" @@ -101,16 +91,8 @@ class TelldusLiveData(object): _LOGGER.info("discovered %d new %s devices", len(found_devices), component_name) - component = get_component(component_name) - bootstrap.setup_component(self._hass, - component.DOMAIN, - self._config) - - discovery_type = DISCOVERY_TYPES[component_name] - - self._hass.bus.fire(EVENT_PLATFORM_DISCOVERED, - {ATTR_SERVICE: discovery_type, - ATTR_DISCOVERED: found_devices}) + discovery.load_platform(self._hass, component_name, DOMAIN, + found_devices, self._config) def request(self, what, **params): """Send a request to the Tellstick Live API.""" diff --git a/homeassistant/components/tellstick.py b/homeassistant/components/tellstick.py index 8bb9d6a53f0..0190f67982c 100644 --- a/homeassistant/components/tellstick.py +++ b/homeassistant/components/tellstick.py @@ -8,11 +8,8 @@ import logging import threading import voluptuous as vol -from homeassistant import bootstrap -from homeassistant.const import ( - ATTR_DISCOVERED, ATTR_SERVICE, - EVENT_PLATFORM_DISCOVERED, EVENT_HOMEASSISTANT_STOP) -from homeassistant.loader import get_component +from homeassistant.helpers import discovery +from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.helpers.entity import Entity DOMAIN = "tellstick" @@ -24,11 +21,6 @@ _LOGGER = logging.getLogger(__name__) ATTR_SIGNAL_REPETITIONS = "signal_repetitions" DEFAULT_SIGNAL_REPETITIONS = 1 -DISCOVER_SWITCHES = "tellstick.switches" -DISCOVER_LIGHTS = "tellstick.lights" -DISCOVERY_TYPES = {"switch": DISCOVER_SWITCHES, - "light": DISCOVER_LIGHTS} - ATTR_DISCOVER_DEVICES = "devices" ATTR_DISCOVER_CONFIG = "config" @@ -57,17 +49,11 @@ def _discover(hass, config, found_devices, component_name): _LOGGER.info("discovered %d new %s devices", len(found_devices), component_name) - component = get_component(component_name) - bootstrap.setup_component(hass, component.DOMAIN, - config) - signal_repetitions = config[DOMAIN].get(ATTR_SIGNAL_REPETITIONS) - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, - {ATTR_SERVICE: DISCOVERY_TYPES[component_name], - ATTR_DISCOVERED: {ATTR_DISCOVER_DEVICES: found_devices, - ATTR_DISCOVER_CONFIG: - signal_repetitions}}) + discovery.load_platform(hass, component_name, DOMAIN, { + ATTR_DISCOVER_DEVICES: found_devices, + ATTR_DISCOVER_CONFIG: signal_repetitions}, config) def setup(hass, config): diff --git a/homeassistant/components/thermostat/__init__.py b/homeassistant/components/thermostat/__init__.py index 0004156aecf..fa90813500d 100644 --- a/homeassistant/components/thermostat/__init__.py +++ b/homeassistant/components/thermostat/__init__.py @@ -15,7 +15,6 @@ from homeassistant.config import load_yaml_config_file from homeassistant.helpers.entity import Entity from homeassistant.helpers.temperature import convert from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa -from homeassistant.components import (ecobee, zwave) import homeassistant.helpers.config_validation as cv from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, STATE_ON, STATE_OFF, STATE_UNKNOWN, @@ -45,11 +44,6 @@ ATTR_OPERATION = "current_operation" _LOGGER = logging.getLogger(__name__) -DISCOVERY_PLATFORMS = { - ecobee.DISCOVER_THERMOSTAT: 'ecobee', - zwave.DISCOVER_THERMOSTATS: 'zwave' -} - SET_AWAY_MODE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_AWAY_MODE): cv.boolean, @@ -101,8 +95,7 @@ def set_fan_mode(hass, fan_mode, entity_id=None): # pylint: disable=too-many-branches def setup(hass, config): """Setup thermostats.""" - component = EntityComponent(_LOGGER, DOMAIN, hass, - SCAN_INTERVAL, DISCOVERY_PLATFORMS) + component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) component.setup(config) descriptions = load_yaml_config_file( diff --git a/homeassistant/components/vera.py b/homeassistant/components/vera.py index 96b95f0bba0..11c8c6a0f1e 100644 --- a/homeassistant/components/vera.py +++ b/homeassistant/components/vera.py @@ -5,16 +5,13 @@ For more details about this component, please refer to the documentation at https://home-assistant.io/components/vera/ """ import logging - from collections import defaultdict + from requests.exceptions import RequestException -from homeassistant import bootstrap -from homeassistant.const import ( - ATTR_SERVICE, ATTR_DISCOVERED, - EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED) +from homeassistant.helpers import discovery +from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.helpers.entity import Entity -from homeassistant.loader import get_component REQUIREMENTS = ['pyvera==0.2.10'] @@ -27,28 +24,18 @@ VERA_CONTROLLER = None CONF_EXCLUDE = 'exclude' CONF_LIGHTS = 'lights' -BINARY_SENSOR = 'binary_sensor' -SENSOR = 'sensor' -LIGHT = 'light' -SWITCH = 'switch' - DEVICE_CATEGORIES = { - 'Sensor': BINARY_SENSOR, - 'Temperature Sensor': SENSOR, - 'Light Sensor': SENSOR, - 'Humidity Sensor': SENSOR, - 'Dimmable Switch': LIGHT, - 'Switch': SWITCH, - 'Armable Sensor': SWITCH, - 'On/Off Switch': SWITCH, + 'Sensor': 'binary_sensor', + 'Temperature Sensor': 'sensor', + 'Light Sensor': 'sensor', + 'Humidity Sensor': 'sensor', + 'Dimmable Switch': 'light', + 'Switch': 'switch', + 'Armable Sensor': 'switch', + 'On/Off Switch': 'switch', # 'Window Covering': NOT SUPPORTED YET } -DISCOVER_BINARY_SENSORS = 'vera.binary_sensors' -DISCOVER_SENSORS = 'vera.sensors' -DISCOVER_LIGHTS = 'vera.lights' -DISCOVER_SWITCHES = 'vera.switchs' - VERA_DEVICES = defaultdict(list) @@ -100,19 +87,13 @@ def setup(hass, base_config): dev_type = DEVICE_CATEGORIES.get(device.category) if dev_type is None: continue - if dev_type == SWITCH and device.device_id in lights_ids: - dev_type = LIGHT + if dev_type == 'switch' and device.device_id in lights_ids: + dev_type = 'light' VERA_DEVICES[dev_type].append(device) - for comp_name, discovery in (((BINARY_SENSOR, DISCOVER_BINARY_SENSORS), - (SENSOR, DISCOVER_SENSORS), - (LIGHT, DISCOVER_LIGHTS), - (SWITCH, DISCOVER_SWITCHES))): - component = get_component(comp_name) - bootstrap.setup_component(hass, component.DOMAIN, base_config) - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, - {ATTR_SERVICE: discovery, - ATTR_DISCOVERED: {}}) + for component in 'binary_sensor', 'sensor', 'light', 'switch': + discovery.load_platform(hass, component, DOMAIN, None, base_config) + return True diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py index a445eaaa405..9841aa3af9a 100644 --- a/homeassistant/components/verisure.py +++ b/homeassistant/components/verisure.py @@ -9,19 +9,11 @@ import threading import time from datetime import timedelta -from homeassistant import bootstrap -from homeassistant.const import ( - ATTR_DISCOVERED, ATTR_SERVICE, CONF_PASSWORD, CONF_USERNAME, - EVENT_PLATFORM_DISCOVERED) -from homeassistant.helpers import validate_config -from homeassistant.loader import get_component +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.helpers import validate_config, discovery from homeassistant.util import Throttle DOMAIN = "verisure" -DISCOVER_SENSORS = 'verisure.sensors' -DISCOVER_SWITCHES = 'verisure.switches' -DISCOVER_ALARMS = 'verisure.alarm_control_panel' -DISCOVER_LOCKS = 'verisure.lock' REQUIREMENTS = ['vsure==0.8.1'] @@ -43,15 +35,8 @@ def setup(hass, config): if not HUB.login(): return False - for comp_name, discovery in ((('sensor', DISCOVER_SENSORS), - ('switch', DISCOVER_SWITCHES), - ('alarm_control_panel', DISCOVER_ALARMS), - ('lock', DISCOVER_LOCKS))): - component = get_component(comp_name) - bootstrap.setup_component(hass, component.DOMAIN, config) - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, - {ATTR_SERVICE: discovery, - ATTR_DISCOVERED: {}}) + for component in ('sensor', 'switch', 'alarm_control_panel', 'lock'): + discovery.load_platform(hass, component, DOMAIN, None, config) return True diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py index 0c1433574bf..1a32d4c361e 100644 --- a/homeassistant/components/wemo.py +++ b/homeassistant/components/wemo.py @@ -6,29 +6,22 @@ https://home-assistant.io/components/wemo/ """ import logging -from homeassistant.components import discovery +from homeassistant.components.discovery import SERVICE_WEMO +from homeassistant.helpers import discovery from homeassistant.const import EVENT_HOMEASSISTANT_STOP REQUIREMENTS = ['pywemo==0.4.3'] DOMAIN = 'wemo' -DISCOVER_LIGHTS = 'wemo.light' -DISCOVER_BINARY_SENSORS = 'wemo.binary_sensor' -DISCOVER_SWITCHES = 'wemo.switch' -# Mapping from Wemo model_name to service. +# Mapping from Wemo model_name to component. WEMO_MODEL_DISPATCH = { - 'Bridge': DISCOVER_LIGHTS, - 'Insight': DISCOVER_SWITCHES, - 'Maker': DISCOVER_SWITCHES, - 'Sensor': DISCOVER_BINARY_SENSORS, - 'Socket': DISCOVER_SWITCHES, - 'LightSwitch': DISCOVER_SWITCHES -} -WEMO_SERVICE_DISPATCH = { - DISCOVER_LIGHTS: 'light', - DISCOVER_BINARY_SENSORS: 'binary_sensor', - DISCOVER_SWITCHES: 'switch', + 'Bridge': 'light', + 'Insight': 'switch', + 'Maker': 'switch', + 'Sensor': 'binary_sensor', + 'Socket': 'switch', + 'LightSwitch': 'switch' } SUBSCRIPTION_REGISTRY = None @@ -64,13 +57,12 @@ def setup(hass, config): _LOGGER.debug('Discovered unique device %s', serial) KNOWN_DEVICES.append(serial) - service = WEMO_MODEL_DISPATCH.get(model_name) or DISCOVER_SWITCHES - component = WEMO_SERVICE_DISPATCH.get(service) + component = WEMO_MODEL_DISPATCH.get(model_name, 'switch') - discovery.discover(hass, service, discovery_info, - component, config) + discovery.load_platform(hass, component, DOMAIN, discovery_info, + config) - discovery.listen(hass, discovery.SERVICE_WEMO, discovery_dispatch) + discovery.listen(hass, SERVICE_WEMO, discovery_dispatch) _LOGGER.info("Scanning for WeMo devices.") devices = [(device.host, device) for device in pywemo.discover_devices()] @@ -92,5 +84,5 @@ def setup(hass, config): discovery_info = (device.name, device.model_name, url, device.mac, device.serialnumber) - discovery.discover(hass, discovery.SERVICE_WEMO, discovery_info) + discovery.discover(hass, SERVICE_WEMO, discovery_info) return True diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index 74939bd47cb..186e8a0afe2 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -6,24 +6,13 @@ https://home-assistant.io/components/wink/ """ import logging -from homeassistant import bootstrap -from homeassistant.const import ( - ATTR_DISCOVERED, ATTR_SERVICE, CONF_ACCESS_TOKEN, - EVENT_PLATFORM_DISCOVERED, ATTR_BATTERY_LEVEL) -from homeassistant.helpers import validate_config +from homeassistant.const import CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL +from homeassistant.helpers import validate_config, discovery from homeassistant.helpers.entity import ToggleEntity -from homeassistant.loader import get_component DOMAIN = "wink" REQUIREMENTS = ['python-wink==0.7.6'] -DISCOVER_LIGHTS = "wink.lights" -DISCOVER_SWITCHES = "wink.switches" -DISCOVER_SENSORS = "wink.sensors" -DISCOVER_BINARY_SENSORS = "wink.binary_sensors" -DISCOVER_LOCKS = "wink.locks" -DISCOVER_GARAGE_DOORS = "wink.garage_doors" - def setup(hass, config): """Setup the Wink component.""" @@ -36,28 +25,17 @@ def setup(hass, config): pywink.set_bearer_token(config[DOMAIN][CONF_ACCESS_TOKEN]) # Load components for the devices in the Wink that we support - for component_name, func_exists, discovery_type in ( - ('light', pywink.get_bulbs, DISCOVER_LIGHTS), - ('switch', lambda: pywink.get_switches or - pywink.get_sirens or - pywink.get_powerstrip_outlets, DISCOVER_SWITCHES), - ('binary_sensor', pywink.get_sensors, DISCOVER_BINARY_SENSORS), - ('sensor', lambda: pywink.get_sensors or - pywink.get_eggtrays, DISCOVER_SENSORS), - ('lock', pywink.get_locks, DISCOVER_LOCKS), - ('garage_door', pywink.get_garage_doors, DISCOVER_GARAGE_DOORS)): + for component_name, func_exists in ( + ('light', pywink.get_bulbs), + ('switch', lambda: pywink.get_switches or pywink.get_sirens or + pywink.get_powerstrip_outlets), + ('binary_sensor', pywink.get_sensors), + ('sensor', lambda: pywink.get_sensors or pywink.get_eggtrays), + ('lock', pywink.get_locks), + ('garage_door', pywink.get_garage_doors)): if func_exists(): - component = get_component(component_name) - - # Ensure component is loaded - bootstrap.setup_component(hass, component.DOMAIN, config) - - # Fire discovery event - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { - ATTR_SERVICE: discovery_type, - ATTR_DISCOVERED: {} - }) + discovery.load_platform(hass, component_name, DOMAIN, None, config) return True diff --git a/homeassistant/components/zwave.py b/homeassistant/components/zwave.py index 3f396e2ded4..b2dd036074c 100644 --- a/homeassistant/components/zwave.py +++ b/homeassistant/components/zwave.py @@ -9,11 +9,11 @@ import os.path import time from pprint import pprint -from homeassistant import bootstrap +from homeassistant.helpers import discovery from homeassistant.const import ( - ATTR_BATTERY_LEVEL, ATTR_DISCOVERED, ATTR_ENTITY_ID, ATTR_LOCATION, - ATTR_SERVICE, CONF_CUSTOMIZE, EVENT_HOMEASSISTANT_START, - EVENT_HOMEASSISTANT_STOP, EVENT_PLATFORM_DISCOVERED) + ATTR_BATTERY_LEVEL, ATTR_ENTITY_ID, ATTR_LOCATION, + CONF_CUSTOMIZE, EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers.event import track_time_change from homeassistant.util import convert, slugify @@ -37,14 +37,6 @@ SERVICE_HEAL_NETWORK = "heal_network" SERVICE_SOFT_RESET = "soft_reset" SERVICE_TEST_NETWORK = "test_network" -DISCOVER_SENSORS = "zwave.sensors" -DISCOVER_SWITCHES = "zwave.switch" -DISCOVER_LIGHTS = "zwave.light" -DISCOVER_BINARY_SENSORS = 'zwave.binary_sensor' -DISCOVER_THERMOSTATS = 'zwave.thermostat' -DISCOVER_HVAC = 'zwave.hvac' -DISCOVER_LOCKS = 'zwave.lock' - EVENT_SCENE_ACTIVATED = "zwave.scene_activated" COMMAND_CLASS_SWITCH_MULTILEVEL = 38 @@ -71,39 +63,32 @@ TYPE_DECIMAL = "Decimal" # value type). DISCOVERY_COMPONENTS = [ ('sensor', - DISCOVER_SENSORS, [COMMAND_CLASS_SENSOR_MULTILEVEL, COMMAND_CLASS_METER, COMMAND_CLASS_ALARM], TYPE_WHATEVER, GENRE_USER), ('light', - DISCOVER_LIGHTS, [COMMAND_CLASS_SWITCH_MULTILEVEL], TYPE_BYTE, GENRE_USER), ('switch', - DISCOVER_SWITCHES, [COMMAND_CLASS_SWITCH_BINARY], TYPE_BOOL, GENRE_USER), ('binary_sensor', - DISCOVER_BINARY_SENSORS, [COMMAND_CLASS_SENSOR_BINARY], TYPE_BOOL, GENRE_USER), ('thermostat', - DISCOVER_THERMOSTATS, [COMMAND_CLASS_THERMOSTAT_SETPOINT], TYPE_WHATEVER, GENRE_WHATEVER), ('hvac', - DISCOVER_HVAC, [COMMAND_CLASS_THERMOSTAT_FAN_MODE], TYPE_WHATEVER, GENRE_WHATEVER), ('lock', - DISCOVER_LOCKS, [COMMAND_CLASS_DOOR_LOCK], TYPE_BOOL, GENRE_USER), @@ -235,7 +220,6 @@ def setup(hass, config): def value_added(node, value): """Called when a value is added to a node on the network.""" for (component, - discovery_service, command_ids, value_type, value_genre) in DISCOVERY_COMPONENTS: @@ -247,9 +231,6 @@ def setup(hass, config): if value_genre is not None and value_genre != value.genre: continue - # Ensure component is loaded - bootstrap.setup_component(hass, component, config) - # Configure node name = "{}.{}".format(component, _object_id(value)) @@ -261,14 +242,10 @@ def setup(hass, config): else: value.disable_poll() - # Fire discovery event - hass.bus.fire(EVENT_PLATFORM_DISCOVERED, { - ATTR_SERVICE: discovery_service, - ATTR_DISCOVERED: { - ATTR_NODE_ID: node.node_id, - ATTR_VALUE_ID: value.value_id, - } - }) + discovery.load_platform(hass, component, DOMAIN, { + ATTR_NODE_ID: node.node_id, + ATTR_VALUE_ID: value.value_id, + }, config) def scene_activated(node, scene_id): """Called when a scene is activated on any node in the network.""" diff --git a/homeassistant/helpers/discovery.py b/homeassistant/helpers/discovery.py new file mode 100644 index 00000000000..480c786d31f --- /dev/null +++ b/homeassistant/helpers/discovery.py @@ -0,0 +1,86 @@ +"""Helper methods to help with platform discovery.""" + +from homeassistant import bootstrap +from homeassistant.const import ( + ATTR_DISCOVERED, ATTR_SERVICE, EVENT_PLATFORM_DISCOVERED) + +EVENT_LOAD_PLATFORM = 'load_platform.{}' +ATTR_PLATFORM = 'platform' + + +def listen(hass, service, callback): + """Setup listener for discovery of specific service. + + Service can be a string or a list/tuple. + """ + if isinstance(service, str): + service = (service,) + else: + service = tuple(service) + + def discovery_event_listener(event): + """Listen for discovery events.""" + if ATTR_SERVICE in event.data and event.data[ATTR_SERVICE] in service: + callback(event.data[ATTR_SERVICE], event.data.get(ATTR_DISCOVERED)) + + hass.bus.listen(EVENT_PLATFORM_DISCOVERED, discovery_event_listener) + + +def discover(hass, service, discovered=None, component=None, hass_config=None): + """Fire discovery event. Can ensure a component is loaded.""" + if component is not None: + bootstrap.setup_component(hass, component, hass_config) + + data = { + ATTR_SERVICE: service + } + + if discovered is not None: + data[ATTR_DISCOVERED] = discovered + + hass.bus.fire(EVENT_PLATFORM_DISCOVERED, data) + + +def listen_platform(hass, component, callback): + """Register a platform loader listener.""" + service = EVENT_LOAD_PLATFORM.format(component) + + def discovery_platform_listener(event): + """Listen for platform discovery events.""" + if event.data.get(ATTR_SERVICE) != service: + return + + platform = event.data.get(ATTR_PLATFORM) + + if not platform: + return + + callback(platform, event.data.get(ATTR_DISCOVERED)) + + hass.bus.listen(EVENT_PLATFORM_DISCOVERED, discovery_platform_listener) + + +def load_platform(hass, component, platform, discovered=None, + hass_config=None): + """Load a component and platform dynamically. + + Target components will be loaded and an EVENT_PLATFORM_DISCOVERED will be + fired to load the platform. The event will contain: + { ATTR_SERVICE = LOAD_PLATFORM + '.' + <> + ATTR_PLATFORM = <> + ATTR_DISCOVERED = <> } + + Use `listen_platform` to register a callback for these events. + """ + if component is not None: + bootstrap.setup_component(hass, component, hass_config) + + data = { + ATTR_SERVICE: EVENT_LOAD_PLATFORM.format(component), + ATTR_PLATFORM: platform, + } + + if discovered is not None: + data[ATTR_DISCOVERED] = discovered + + hass.bus.fire(EVENT_PLATFORM_DISCOVERED, data) diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py index 2a99b57da55..898a445c788 100644 --- a/homeassistant/helpers/entity_component.py +++ b/homeassistant/helpers/entity_component.py @@ -2,11 +2,11 @@ from threading import Lock from homeassistant.bootstrap import prepare_setup_platform -from homeassistant.components import discovery, group +from homeassistant.components import group from homeassistant.const import ( ATTR_ENTITY_ID, CONF_SCAN_INTERVAL, CONF_ENTITY_NAMESPACE, DEVICE_DEFAULT_NAME) -from homeassistant.helpers import config_per_platform +from homeassistant.helpers import config_per_platform, discovery from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.event import track_utc_time_change from homeassistant.helpers.service import extract_entity_ids @@ -20,8 +20,7 @@ class EntityComponent(object): # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-arguments def __init__(self, logger, domain, hass, - scan_interval=DEFAULT_SCAN_INTERVAL, - discovery_platforms=None, group_name=None): + scan_interval=DEFAULT_SCAN_INTERVAL, group_name=None): """Initialize an entity component.""" self.logger = logger self.hass = hass @@ -29,7 +28,6 @@ class EntityComponent(object): self.domain = domain self.entity_id_format = domain + '.{}' self.scan_interval = scan_interval - self.discovery_platforms = discovery_platforms self.group_name = group_name self.entities = {} @@ -54,23 +52,14 @@ class EntityComponent(object): for p_type, p_config in config_per_platform(config, self.domain): self._setup_platform(p_type, p_config) - if self.discovery_platforms: - # Discovery listener for all items in discovery_platforms array - # passed from a component's setup method (e.g. light/__init__.py) - discovery.listen( - self.hass, self.discovery_platforms.keys(), - lambda service, info: - self._setup_platform(self.discovery_platforms[service], {}, - info)) - # Generic discovery listener for loading platform dynamically # Refer to: homeassistant.components.discovery.load_platform() - def load_platform_callback(service, info): + def component_platform_discovered(platform, info): """Callback to load a platform.""" - platform = info.pop(discovery.LOAD_PLATFORM) - self._setup_platform(platform, {}, info if info else None) - discovery.listen(self.hass, discovery.LOAD_PLATFORM + '.' + - self.domain, load_platform_callback) + self._setup_platform(platform, {}, info) + + discovery.listen_platform(self.hass, self.domain, + component_platform_discovered) def extract_from_service(self, service): """Extract all known entities from a service call. diff --git a/tests/helpers/test_discovery.py b/tests/helpers/test_discovery.py new file mode 100644 index 00000000000..bdc6e2ed119 --- /dev/null +++ b/tests/helpers/test_discovery.py @@ -0,0 +1,90 @@ +"""Test discovery helpers.""" + +from unittest.mock import patch + +from homeassistant.helpers import discovery + +from tests.common import get_test_home_assistant + + +class TestHelpersDiscovery: + """Tests for discovery helper methods.""" + + def setup_method(self, method): + """Setup things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + def teardown_method(self, method): + """Stop everything that was started.""" + self.hass.stop() + + @patch('homeassistant.bootstrap.setup_component') + def test_listen(self, mock_setup_component): + """Test discovery listen/discover combo.""" + calls_single = [] + calls_multi = [] + + def callback_single(service, info): + """Service discovered callback.""" + calls_single.append((service, info)) + + def callback_multi(service, info): + """Service discovered callback.""" + calls_multi.append((service, info)) + + discovery.listen(self.hass, 'test service', callback_single) + discovery.listen(self.hass, ['test service', 'another service'], + callback_multi) + + discovery.discover(self.hass, 'test service', 'discovery info', + 'test_component') + self.hass.pool.block_till_done() + + discovery.discover(self.hass, 'another service', 'discovery info', + 'test_component') + self.hass.pool.block_till_done() + + assert mock_setup_component.called + assert mock_setup_component.call_args[0] == \ + (self.hass, 'test_component', None) + assert len(calls_single) == 1 + assert calls_single[0] == ('test service', 'discovery info') + + assert len(calls_single) == 1 + assert len(calls_multi) == 2 + assert ['test service', 'another service'] == [info[0] for info + in calls_multi] + + @patch('homeassistant.bootstrap.setup_component') + def test_platform(self, mock_setup_component): + """Test discover platform method.""" + calls = [] + + def platform_callback(platform, info): + """Platform callback method.""" + calls.append((platform, info)) + + discovery.listen_platform(self.hass, 'test_component', + platform_callback) + + discovery.load_platform(self.hass, 'test_component', 'test_platform', + 'discovery info') + assert mock_setup_component.called + assert mock_setup_component.call_args[0] == \ + (self.hass, 'test_component', None) + self.hass.pool.block_till_done() + + discovery.load_platform(self.hass, 'test_component_2', 'test_platform', + 'discovery info') + self.hass.pool.block_till_done() + + assert len(calls) == 1 + assert calls[0] == ('test_platform', 'discovery info') + + self.hass.bus.fire(discovery.EVENT_PLATFORM_DISCOVERED, { + discovery.ATTR_SERVICE: + discovery.EVENT_LOAD_PLATFORM.format('test_component') + }) + self.hass.pool.block_till_done() + + assert len(calls) == 1 diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 0b4b852d397..a9a6310eb79 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -9,7 +9,7 @@ import homeassistant.core as ha import homeassistant.loader as loader from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent -from homeassistant.components import discovery +from homeassistant.helpers import discovery import homeassistant.util.dt as dt_util from tests.common import ( @@ -228,22 +228,17 @@ class TestHelpersEntityComponent(unittest.TestCase): '._setup_platform') def test_setup_does_discovery(self, mock_setup): """Test setup for discovery.""" - component = EntityComponent( - _LOGGER, DOMAIN, self.hass, discovery_platforms={ - 'discovery.test': 'platform_test', - }) + component = EntityComponent(_LOGGER, DOMAIN, self.hass) component.setup({}) - self.hass.bus.fire(discovery.EVENT_PLATFORM_DISCOVERED, { - discovery.ATTR_SERVICE: 'discovery.test', - discovery.ATTR_DISCOVERED: 'discovery_info', - }) + discovery.load_platform(self.hass, DOMAIN, 'platform_test', + {'msg': 'discovery_info'}) self.hass.pool.block_till_done() assert mock_setup.called - assert ('platform_test', {}, 'discovery_info') == \ + assert ('platform_test', {}, {'msg': 'discovery_info'}) == \ mock_setup.call_args[0] @patch('homeassistant.helpers.entity_component.track_utc_time_change')