From 89100d14c8dd5b7fcbf99a3bd3f78e7ae852817e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 1 Mar 2015 01:35:58 -0800 Subject: [PATCH] Refactored device components --- homeassistant/components/light/__init__.py | 66 ++--------- homeassistant/components/sensor/__init__.py | 53 ++------- homeassistant/components/sensor/demo.py | 20 +--- homeassistant/components/sensor/wink.py | 31 ++--- homeassistant/components/sensor/zwave.py | 34 +++--- homeassistant/components/switch/__init__.py | 56 ++------- homeassistant/components/switch/demo.py | 17 +-- homeassistant/components/switch/tellstick.py | 9 +- homeassistant/components/switch/wemo.py | 54 ++++----- homeassistant/components/switch/wink.py | 31 ++--- .../components/thermostat/__init__.py | 53 ++------- homeassistant/components/thermostat/demo.py | 10 +- .../components/thermostat/heat_control.py | 6 +- homeassistant/components/thermostat/nest.py | 14 ++- .../{helpers.py => helpers/__init__.py} | 0 homeassistant/helpers/device_component.py | 107 ++++++++++++++++++ tests/config/custom_components/light/test.py | 5 - tests/config/custom_components/switch/test.py | 6 +- tests/test_component_light.py | 4 +- tests/test_component_switch.py | 2 +- 20 files changed, 239 insertions(+), 339 deletions(-) rename homeassistant/{helpers.py => helpers/__init__.py} (100%) create mode 100644 homeassistant/helpers/device_component.py diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 89abc1d2682..c0448d96be6 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -52,17 +52,18 @@ import logging import os import csv -from homeassistant.loader import get_component +from homeassistant.helpers.device_component import DeviceComponent + import homeassistant.util as util from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) -from homeassistant.helpers import ( - generate_entity_id, extract_entity_ids, config_per_platform) +from homeassistant.helpers import extract_entity_ids from homeassistant.components import group, discovery, wink DOMAIN = "light" DEPENDENCIES = [] +SCAN_INTERVAL = 30 GROUP_NAME_ALL_LIGHTS = 'all lights' ENTITY_ID_ALL_LIGHTS = group.ENTITY_ID_FORMAT.format('all_lights') @@ -140,6 +141,13 @@ def turn_off(hass, entity_id=None, transition=None): def setup(hass, config): """ Exposes light control via statemachine and services. """ + component = DeviceComponent( + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS, + GROUP_NAME_ALL_LIGHTS) + component.setup(config) + + lights = component.devices + # Load built-in profiles and custom profiles profile_paths = [os.path.join(os.path.dirname(__file__), LIGHT_PROFILES_FILE), @@ -168,55 +176,6 @@ def setup(hass, config): return False - # Dict to track entity_id -> lights - lights = {} - - # Track all lights in a group - light_group = group.Group(hass, GROUP_NAME_ALL_LIGHTS, user_defined=False) - - def add_lights(new_lights): - """ Add lights to the component to track. """ - for light in new_lights: - if light is not None and light not in lights.values(): - light.hass = hass - - light.entity_id = generate_entity_id( - ENTITY_ID_FORMAT, light.name, lights.keys()) - - lights[light.entity_id] = light - - light.update_ha_state() - - light_group.update_tracked_entity_ids(lights.keys()) - - for p_type, p_config in config_per_platform(config, DOMAIN, _LOGGER): - platform = get_component(ENTITY_ID_FORMAT.format(p_type)) - - if platform is None: - _LOGGER.error("Unknown type specified: %s", p_type) - - platform.setup_platform(hass, p_config, add_lights) - - def update_lights_state(now): - """ Update the states of all the lights. """ - if lights: - _LOGGER.info("Updating light states") - - for light in lights.values(): - if light.should_poll: - light.update_ha_state(True) - - update_lights_state(None) - - def light_discovered(service, info): - """ Called when a light is discovered. """ - platform = get_component( - ENTITY_ID_FORMAT.format(DISCOVERY_PLATFORMS[service])) - - platform.setup_platform(hass, {}, add_lights, info) - - discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), light_discovered) - def handle_light_service(service): """ Hande a turn light on or off service call. """ # Get and validate data @@ -303,9 +262,6 @@ def setup(hass, config): for light in target_lights: light.update_ha_state(True) - # Update light state every 30 seconds - hass.track_time_change(update_lights_state, second=[0, 30]) - # Listen for light on and light off service calls hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_light_service) diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 439f2fa06f0..1aebf7f59ad 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -4,68 +4,29 @@ homeassistant.components.sensor Component to interface with various sensors that can be monitored. """ import logging -from datetime import timedelta -from homeassistant.loader import get_component -import homeassistant.util as util -from homeassistant.helpers import ( - platform_devices_from_config, generate_entity_id) -from homeassistant.components import discovery, wink, zwave +from homeassistant.helpers.device_component import DeviceComponent +from homeassistant.components import wink, zwave DOMAIN = 'sensor' DEPENDENCIES = [] +SCAN_INTERVAL = 30 ENTITY_ID_FORMAT = DOMAIN + '.{}' -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=1) - # Maps discovered services to their platforms DISCOVERY_PLATFORMS = { wink.DISCOVER_SENSORS: 'wink', zwave.DISCOVER_SENSORS: 'zwave', } -_LOGGER = logging.getLogger(__name__) - def setup(hass, config): """ Track states and offer events for sensors. """ - logger = logging.getLogger(__name__) + component = DeviceComponent( + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL, + DISCOVERY_PLATFORMS) - sensors = platform_devices_from_config( - config, DOMAIN, hass, ENTITY_ID_FORMAT, logger) - - @util.Throttle(MIN_TIME_BETWEEN_SCANS) - def update_sensor_states(now): - """ Update states of all sensors. """ - if sensors: - for sensor in sensors.values(): - if sensor.should_poll: - sensor.update_ha_state(True) - - update_sensor_states(None) - - def sensor_discovered(service, info): - """ Called when a sensor is discovered. """ - platform = get_component("{}.{}".format( - DOMAIN, DISCOVERY_PLATFORMS[service])) - - discovered = platform.devices_discovered(hass, config, info) - - for sensor in discovered: - if sensor is not None and sensor not in sensors.values(): - sensor.hass = hass - - sensor.entity_id = generate_entity_id( - ENTITY_ID_FORMAT, sensor.name, sensors.keys()) - - sensors[sensor.entity_id] = sensor - - sensor.update_ha_state() - - discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), sensor_discovered) - - # Fire every 3 seconds - hass.track_time_change(update_sensor_states, second=range(0, 60, 3)) + component.setup(config) return True diff --git a/homeassistant/components/sensor/demo.py b/homeassistant/components/sensor/demo.py index a9acbd22116..bbfef927488 100644 --- a/homeassistant/components/sensor/demo.py +++ b/homeassistant/components/sensor/demo.py @@ -4,23 +4,13 @@ from homeassistant.const import ( TEMP_CELCIUS, ATTR_UNIT_OF_MEASUREMENT, ATTR_FRIENDLY_NAME) -def get_devices(hass, config): - """ Find and return Wink sensors. """ - - return get_sensors() - - -def devices_discovered(hass, config, info): - """ Called when a device is discovered. """ - return get_sensors() - - -def get_sensors(): - """ Returns the Wink sensors. """ - return [ +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Demo sensors. """ + add_devices([ DemoSensor('Outside Temperature', 15.6, TEMP_CELCIUS), DemoSensor('Outside Humidity', 54, '%'), - ] + ]) class DemoSensor(Device): diff --git a/homeassistant/components/sensor/wink.py b/homeassistant/components/sensor/wink.py index 6dcb6b3d9bd..984e218e967 100644 --- a/homeassistant/components/sensor/wink.py +++ b/homeassistant/components/sensor/wink.py @@ -8,26 +8,17 @@ from homeassistant.components.wink import WinkSensorDevice from homeassistant.const import CONF_ACCESS_TOKEN -def get_devices(hass, config): - """ Find and return Wink sensors. """ - token = config.get(CONF_ACCESS_TOKEN) +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Wink platform. """ + if discovery_info is None: + token = config.get(CONF_ACCESS_TOKEN) - if token is None: - logging.getLogger(__name__).error( - "Missing wink access_token - " - "get one at https://winkbearertoken.appspot.com/") - return [] + if token is None: + logging.getLogger(__name__).error( + "Missing wink access_token - " + "get one at https://winkbearertoken.appspot.com/") + return - pywink.set_bearer_token(token) + pywink.set_bearer_token(token) - return get_sensors() - - -def devices_discovered(hass, config, info): - """ Called when a device is discovered. """ - return get_sensors() - - -def get_sensors(): - """ Returns the Wink sensors. """ - return [WinkSensorDevice(sensor) for sensor in pywink.get_sensors()] + add_devices(WinkSensorDevice(sensor) for sensor in pywink.get_sensors()) diff --git a/homeassistant/components/sensor/zwave.py b/homeassistant/components/sensor/zwave.py index fc41ae53621..ca343466633 100644 --- a/homeassistant/components/sensor/zwave.py +++ b/homeassistant/components/sensor/zwave.py @@ -15,6 +15,23 @@ from homeassistant.const import ( TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_LOCATION) +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up Z-Wave sensors. """ + node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]] + value = node.values[discovery_info[zwave.ATTR_VALUE_ID]] + + value.set_change_verified(False) + + if zwave.NETWORK.controller.node_id not in node.groups[1].associations: + node.groups[1].add_association(zwave.NETWORK.controller.node_id) + + if value.command_class == zwave.COMMAND_CLASS_SENSOR_BINARY: + return [ZWaveBinarySensor(value)] + + elif value.command_class == zwave.COMMAND_CLASS_SENSOR_MULTILEVEL: + return [ZWaveMultilevelSensor(value)] + + class ZWaveSensor(Device): """ Represents a Z-Wave sensor. """ def __init__(self, sensor_value): @@ -116,20 +133,3 @@ class ZWaveMultilevelSensor(ZWaveSensor): return TEMP_FAHRENHEIT else: return unit - - -def devices_discovered(hass, config, info): - """ Called when a device is discovered. """ - node = zwave.NETWORK.nodes[info[zwave.ATTR_NODE_ID]] - value = node.values[info[zwave.ATTR_VALUE_ID]] - - value.set_change_verified(False) - - if zwave.NETWORK.controller.node_id not in node.groups[1].associations: - node.groups[1].add_association(zwave.NETWORK.controller.node_id) - - if value.command_class == zwave.COMMAND_CLASS_SENSOR_BINARY: - return [ZWaveBinarySensor(value)] - - elif value.command_class == zwave.COMMAND_CLASS_SENSOR_MULTILEVEL: - return [ZWaveMultilevelSensor(value)] diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 97a1ba94c99..bb42c1d89da 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -6,16 +6,16 @@ Component to interface with various switches that can be controlled remotely. import logging from datetime import timedelta -from homeassistant.loader import get_component -import homeassistant.util as util +from homeassistant.helpers.device_component import DeviceComponent + from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) -from homeassistant.helpers import ( - generate_entity_id, extract_entity_ids, platform_devices_from_config) +from homeassistant.helpers import extract_entity_ids from homeassistant.components import group, discovery, wink DOMAIN = 'switch' DEPENDENCIES = [] +SCAN_INTERVAL = 30 GROUP_NAME_ALL_SWITCHES = 'all switches' ENTITY_ID_ALL_SWITCHES = group.ENTITY_ID_FORMAT.format('all_switches') @@ -59,47 +59,12 @@ def turn_off(hass, entity_id=None): def setup(hass, config): """ Track states and offer events for switches. """ - logger = logging.getLogger(__name__) + component = DeviceComponent( + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DISCOVERY_PLATFORMS, + GROUP_NAME_ALL_SWITCHES) + component.setup(config) - switches = platform_devices_from_config( - config, DOMAIN, hass, ENTITY_ID_FORMAT, logger) - - @util.Throttle(MIN_TIME_BETWEEN_SCANS) - def update_states(now): - """ Update states of all switches. """ - if switches: - logger.info("Updating switch states") - - for switch in switches.values(): - switch.update_ha_state(True) - - update_states(None) - - # Track all switches in a group - switch_group = group.Group( - hass, GROUP_NAME_ALL_SWITCHES, switches.keys(), False) - - def switch_discovered(service, info): - """ Called when a switch is discovered. """ - platform = get_component("{}.{}".format( - DOMAIN, DISCOVERY_PLATFORMS[service])) - - discovered = platform.devices_discovered(hass, config, info) - - for switch in discovered: - if switch is not None and switch not in switches.values(): - switch.hass = hass - - switch.entity_id = generate_entity_id( - ENTITY_ID_FORMAT, switch.name, switches.keys()) - - switches[switch.entity_id] = switch - - switch.update_ha_state() - - switch_group.update_tracked_entity_ids(switches.keys()) - - discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), switch_discovered) + switches = component.devices def handle_switch_service(service): """ Handles calls to the switch services. """ @@ -118,9 +83,6 @@ def setup(hass, config): switch.update_ha_state(True) - # Update state every 30 seconds - hass.track_time_change(update_states, second=[0, 30]) - hass.services.register(DOMAIN, SERVICE_TURN_OFF, handle_switch_service) hass.services.register(DOMAIN, SERVICE_TURN_ON, handle_switch_service) diff --git a/homeassistant/components/switch/demo.py b/homeassistant/components/switch/demo.py index 17233e0c46d..e18e12c520a 100644 --- a/homeassistant/components/switch/demo.py +++ b/homeassistant/components/switch/demo.py @@ -3,22 +3,13 @@ from homeassistant.helpers import ToggleDevice from homeassistant.const import STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME -def get_devices(hass, config): +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Find and return demo switches. """ - return get_switches() - - -def devices_discovered(hass, config, info): - """ Called when a device is discovered. """ - return get_switches() - - -def get_switches(): - """ Returns the Wink switches. """ - return [ + add_devices_callback([ DemoSwitch('Ceiling', STATE_ON), DemoSwitch('AC', STATE_OFF) - ] + ]) class DemoSwitch(ToggleDevice): diff --git a/homeassistant/components/switch/tellstick.py b/homeassistant/components/switch/tellstick.py index 29c2a2e1403..e82213a3d0f 100644 --- a/homeassistant/components/switch/tellstick.py +++ b/homeassistant/components/switch/tellstick.py @@ -7,14 +7,15 @@ from homeassistant.helpers import ToggleDevice import tellcore.constants as tellcore_constants -def get_devices(hass, config): - """ Find and return Tellstick switches. """ +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Find and return tellstick switches. """ try: import tellcore.telldus as telldus except ImportError: logging.getLogger(__name__).exception( "Failed to import tellcore") - return [] + return core = telldus.TelldusCore() switches_and_lights = core.devices() @@ -25,7 +26,7 @@ def get_devices(hass, config): if not switch.methods(tellcore_constants.TELLSTICK_DIM): switches.append(TellstickSwitchDevice(switch)) - return switches + add_devices_callback(switches) class TellstickSwitchDevice(ToggleDevice): diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index ffec5eaf8c7..5fd5ce4b9f7 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -6,50 +6,36 @@ from homeassistant.components.switch import ( ATTR_TODAY_MWH, ATTR_CURRENT_POWER_MWH) -def get_devices(hass, config): - """ Find and return WeMo switches. """ - - pywemo, _ = get_pywemo() - - if pywemo is None: - return [] - - logging.getLogger(__name__).info("Scanning for WeMo devices") - switches = pywemo.discover_devices() - - # Filter out the switches and wrap in WemoSwitch object - return [WemoSwitch(switch) for switch in switches - if isinstance(switch, pywemo.Switch)] - - -def devices_discovered(hass, config, info): - """ Called when a device is discovered. """ - _, discovery = get_pywemo() - - if discovery is None: - return [] - - device = discovery.device_from_description(info) - - return [] if device is None else [WemoSwitch(device)] - - -def get_pywemo(): - """ Tries to import PyWemo. """ +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Find and return wemo switches. """ try: # pylint: disable=no-name-in-module, import-error import homeassistant.external.pywemo.pywemo as pywemo import homeassistant.external.pywemo.pywemo.discovery as discovery - - return pywemo, discovery - except ImportError: logging.getLogger(__name__).exception(( "Failed to import pywemo. " "Did you maybe not run `git submodule init` " "and `git submodule update`?")) - return None, None + return + + if discovery_info is not None: + device = discovery.device_from_description(discovery_info) + + if device: + add_devices_callback([device]) + + return + + logging.getLogger(__name__).info("Scanning for WeMo devices") + switches = pywemo.discover_devices() + + # Filter out the switches and wrap in WemoSwitch object + add_devices_callback( + [WemoSwitch(switch) for switch in switches + if isinstance(switch, pywemo.Switch)]) class WemoSwitch(ToggleDevice): diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/switch/wink.py index cc325454376..f3d38d87c29 100644 --- a/homeassistant/components/switch/wink.py +++ b/homeassistant/components/switch/wink.py @@ -8,26 +8,17 @@ from homeassistant.components.wink import WinkToggleDevice from homeassistant.const import CONF_ACCESS_TOKEN -def get_devices(hass, config): - """ Find and return Wink switches. """ - token = config.get(CONF_ACCESS_TOKEN) +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Wink platform. """ + if discovery_info is None: + token = config.get(CONF_ACCESS_TOKEN) - if token is None: - logging.getLogger(__name__).error( - "Missing wink access_token - " - "get one at https://winkbearertoken.appspot.com/") - return [] + if token is None: + logging.getLogger(__name__).error( + "Missing wink access_token - " + "get one at https://winkbearertoken.appspot.com/") + return - pywink.set_bearer_token(token) + pywink.set_bearer_token(token) - return get_switches() - - -def devices_discovered(hass, config, info): - """ Called when a device is discovered. """ - return get_switches() - - -def get_switches(): - """ Returns the Wink switches. """ - return [WinkToggleDevice(switch) for switch in pywink.get_switches()] + add_devices(WinkToggleDevice(switch) for switch in pywink.get_switches()) diff --git a/homeassistant/components/thermostat/__init__.py b/homeassistant/components/thermostat/__init__.py index 47ca6a5d4dc..90cc2b2b94e 100644 --- a/homeassistant/components/thermostat/__init__.py +++ b/homeassistant/components/thermostat/__init__.py @@ -5,23 +5,21 @@ homeassistant.components.thermostat Provides functionality to interact with thermostats. """ import logging -from datetime import timedelta -from homeassistant.helpers import ( - extract_entity_ids, platform_devices_from_config) +from homeassistant.helpers.device_component import DeviceComponent + import homeassistant.util as util -from homeassistant.helpers import Device +from homeassistant.helpers import Device, extract_entity_ids from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF) DOMAIN = "thermostat" -ENTITY_ID_FORMAT = DOMAIN + ".{}" - -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) - DEPENDENCIES = [] +ENTITY_ID_FORMAT = DOMAIN + ".{}" +SCAN_INTERVAL = 60 + SERVICE_SET_AWAY_MODE = "set_away_mode" SERVICE_SET_TEMPERATURE = "set_temperature" @@ -31,22 +29,10 @@ ATTR_AWAY_MODE = "away_mode" _LOGGER = logging.getLogger(__name__) -def turn_away_mode_on(hass, entity_id=None): +def set_away_mode(hass, away_mode, entity_id=None): """ Turn all or specified thermostat away mode on. """ data = { - ATTR_AWAY_MODE: True - } - - if entity_id: - data[ATTR_ENTITY_ID] = entity_id - - hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data) - - -def turn_away_mode_off(hass, entity_id=None): - """ Turn all or specified thermostat away mode off. """ - data = { - ATTR_AWAY_MODE: False + ATTR_AWAY_MODE: away_mode } if entity_id: @@ -67,27 +53,10 @@ def set_temperature(hass, temperature, entity_id=None): def setup(hass, config): """ Setup thermostats. """ + component = DeviceComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) + component.setup(config) - logger = logging.getLogger(__name__) - - thermostats = platform_devices_from_config( - config, DOMAIN, hass, ENTITY_ID_FORMAT, _LOGGER) - - if not thermostats: - return False - - @util.Throttle(MIN_TIME_BETWEEN_SCANS) - def update_state(now): - """ Update thermostat state. """ - logger.info("Updating thermostat state") - - for thermostat in thermostats.values(): - if thermostat.should_poll: - thermostat.update_ha_state(True) - - # Update state every minute - hass.track_time_change(update_state, second=[0]) - update_state(None) + thermostats = component.devices def thermostat_service(service): """ Handles calls to the services. """ diff --git a/homeassistant/components/thermostat/demo.py b/homeassistant/components/thermostat/demo.py index 570883ef075..2a558ed442a 100644 --- a/homeassistant/components/thermostat/demo.py +++ b/homeassistant/components/thermostat/demo.py @@ -6,14 +6,12 @@ from homeassistant.components.thermostat import ThermostatDevice from homeassistant.const import TEMP_CELCIUS, TEMP_FAHRENHEIT -# pylint: disable=unused-argument -def get_devices(hass, config): - """ Gets thermostats. """ - - return [ +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the Demo thermostats. """ + add_devices([ DemoThermostat("Nest", 21, TEMP_CELCIUS, False, 19), DemoThermostat("Thermostat", 68, TEMP_FAHRENHEIT, True, 77), - ] + ]) # pylint: disable=too-many-arguments diff --git a/homeassistant/components/thermostat/heat_control.py b/homeassistant/components/thermostat/heat_control.py index 90fa62031fa..d21245dae3a 100644 --- a/homeassistant/components/thermostat/heat_control.py +++ b/homeassistant/components/thermostat/heat_control.py @@ -69,11 +69,11 @@ TOL_TEMP = 0.3 # pylint: disable=unused-argument -def get_devices(hass, config): - """ Gets thermostats. """ +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the heat control thermostat. """ logger = logging.getLogger(__name__) - return [HeatControl(hass, config, logger)] + add_devices([HeatControl(hass, config, logger)]) # pylint: disable=too-many-instance-attributes diff --git a/homeassistant/components/thermostat/nest.py b/homeassistant/components/thermostat/nest.py index cac2d53d040..06adb49a708 100644 --- a/homeassistant/components/thermostat/nest.py +++ b/homeassistant/components/thermostat/nest.py @@ -7,8 +7,9 @@ from homeassistant.components.thermostat import ThermostatDevice from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, TEMP_CELCIUS) -def get_devices(hass, config): - """ Gets Nest thermostats. """ +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """ Sets up the nest thermostat. """ logger = logging.getLogger(__name__) username = config.get(CONF_USERNAME) @@ -17,7 +18,7 @@ def get_devices(hass, config): if username is None or password is None: logger.error("Missing required configuration items %s or %s", CONF_USERNAME, CONF_PASSWORD) - return [] + return try: import nest @@ -26,14 +27,15 @@ def get_devices(hass, config): "Error while importing dependency nest. " "Did you maybe not install the python-nest dependency?") - return [] + return napi = nest.Nest(username, password) - return [ + add_devices([ NestThermostat(structure, device) for structure in napi.structures - for device in structure.devices] + for device in structure.devices + ]) class NestThermostat(ThermostatDevice): diff --git a/homeassistant/helpers.py b/homeassistant/helpers/__init__.py similarity index 100% rename from homeassistant/helpers.py rename to homeassistant/helpers/__init__.py diff --git a/homeassistant/helpers/device_component.py b/homeassistant/helpers/device_component.py new file mode 100644 index 00000000000..7ddb7118824 --- /dev/null +++ b/homeassistant/helpers/device_component.py @@ -0,0 +1,107 @@ +""" +Provides helpers for components that handle devices. +""" +from homeassistant.loader import get_component +from homeassistant.helpers import generate_entity_id, config_per_platform +from homeassistant.components import group, discovery + + +class DeviceComponent(object): + # pylint: disable=too-many-instance-attributes + # pylint: disable=too-many-arguments,too-few-public-methods + """ + Helper class that will help a device component manage its devices. + """ + def __init__(self, logger, domain, hass, scan_interval, + discovery_platforms=None, group_name=None): + self.logger = logger + self.hass = hass + + self.domain = domain + self.entity_id_format = domain + '.{}' + self.scan_interval = scan_interval + self.discovery_platforms = discovery_platforms + self.group_name = group_name + + self.devices = {} + self.group = None + + def setup(self, config): + """ + Sets up a full device component: + - Loads the platforms from the config + - Will update devices on an interval + - Will listen for supported discovered platforms + """ + + # only setup group if name is given + if self.group_name is None: + self.group = None + else: + self.group = group.Group(self.hass, self.group_name, + user_defined=False) + + # Look in config for Domain, Domain 2, Domain 3 etc and load them + for p_type, p_config in \ + config_per_platform(config, self.domain, self.logger): + + self._setup_platform(p_type, p_config) + + self.hass.track_time_change(self._update_device_states, + second=range(0, 60, self.scan_interval)) + + if self.discovery_platforms: + discovery.listen(self.hass, self.discovery_platforms.keys(), + self._device_discovered) + + def _update_device_states(self, now): + """ Update the states of all the lights. """ + self.logger.info("Updating %s states", self.domain) + + for device in self.devices.values(): + if device.should_poll: + device.update_ha_state(True) + + def _device_discovered(self, service, info): + """ Called when a device is discovered. """ + if service not in self.discovery_platforms: + return + + self._setup_platform(self.discovery_platforms[service], {}, info) + + def _add_devices(self, new_devices): + """ + Takes in a list of new devices. For each device will see if it already + exists. If not, will add it, set it up and push the first state. + """ + for device in new_devices: + if device is not None and device not in self.devices.values(): + device.hass = self.hass + + device.entity_id = generate_entity_id( + self.entity_id_format, device.name, self.devices.keys()) + + self.devices[device.entity_id] = device + + device.update_ha_state() + + if self.group is not None: + self.group.update_tracked_entity_ids(self.devices.keys()) + + def _setup_platform(self, platform_type, config, discovery_info=None): + """ Tries to setup a platform for this component. """ + platform_name = '{}.{}'.format(self.domain, platform_type) + platform = get_component(platform_name) + + if platform is None: + self.logger.error('Unable to find platform %s', platform_type) + return + + try: + platform.setup_platform( + self.hass, config, self._add_devices, discovery_info) + except (AttributeError, TypeError): + # AttributeError if setup_platform does not exist + # TypeError if wrong number of argumnets for setup_platform + self.logger.exception( + "Error setting up %s", platform_type) diff --git a/tests/config/custom_components/light/test.py b/tests/config/custom_components/light/test.py index c557fc8575a..f7f355c4b30 100644 --- a/tests/config/custom_components/light/test.py +++ b/tests/config/custom_components/light/test.py @@ -27,8 +27,3 @@ def init(empty=False): def setup_platform(hass, config, add_devices_callback, discovery_info=None): """ Returns mock devices. """ add_devices_callback(DEVICES) - - -def get_lights(): - """ Helper method to get current light objects. """ - return DEVICES diff --git a/tests/config/custom_components/switch/test.py b/tests/config/custom_components/switch/test.py index 35e544fa0cb..178faf7fcdc 100644 --- a/tests/config/custom_components/switch/test.py +++ b/tests/config/custom_components/switch/test.py @@ -24,6 +24,6 @@ def init(empty=False): ] -def get_switches(hass, config): - """ Returns mock devices. """ - return DEVICES +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """ Find and return test switches. """ + add_devices_callback(DEVICES) diff --git a/tests/test_component_light.py b/tests/test_component_light.py index c9264a37e40..eeb7a53e4f1 100644 --- a/tests/test_component_light.py +++ b/tests/test_component_light.py @@ -103,7 +103,7 @@ class TestLight(unittest.TestCase): self.assertTrue( light.setup(self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}})) - dev1, dev2, dev3 = platform.get_lights() + dev1, dev2, dev3 = platform.DEVICES # Test init self.assertTrue(light.is_on(self.hass, dev1.entity_id)) @@ -244,7 +244,7 @@ class TestLight(unittest.TestCase): self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}} )) - dev1, dev2, dev3 = platform.get_lights() + dev1, dev2, dev3 = platform.DEVICES light.turn_on(self.hass, dev1.entity_id, profile='test') diff --git a/tests/test_component_switch.py b/tests/test_component_switch.py index 9c2624e0ce6..cbc161be853 100644 --- a/tests/test_component_switch.py +++ b/tests/test_component_switch.py @@ -30,7 +30,7 @@ class TestSwitch(unittest.TestCase): # Switch 1 is ON, switch 2 is OFF self.switch_1, self.switch_2, self.switch_3 = \ - platform.get_switches(None, None) + platform.DEVICES def tearDown(self): # pylint: disable=invalid-name """ Stop down stuff we started. """