diff --git a/config/home-assistant.conf.example b/config/home-assistant.conf.example index f0dab565a17..230d3dd76fd 100644 --- a/config/home-assistant.conf.example +++ b/config/home-assistant.conf.example @@ -11,6 +11,10 @@ api_password=mypass [light] platform=hue +[wink] +# Get your token at https://winkbearertoken.appspot.com +token=YOUR_TOKEN + [device_tracker] # The following types are available: netgear, tomato, luci, nmap_tracker platform=netgear diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 99c13c63794..1860fe8110e 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -15,15 +15,13 @@ from homeassistant.external.netdisco.netdisco import DiscoveryService import homeassistant.external.netdisco.netdisco.const as services from homeassistant import bootstrap -from homeassistant.const import EVENT_HOMEASSISTANT_START, ATTR_SERVICE +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, EVENT_SERVICE_DISCOVERED, + ATTR_SERVICE, ATTR_DISCOVERED) DOMAIN = "discovery" DEPENDENCIES = [] -EVENT_SERVICE_DISCOVERED = "service_discovered" - -ATTR_DISCOVERED = "discovered" - SCAN_INTERVAL = 300 # seconds SERVICE_HANDLERS = { diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index c99bd56eb24..7b56a981901 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -52,12 +52,13 @@ import logging import os import csv +from homeassistant.loader import get_component import homeassistant.util as util from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) from homeassistant.helpers import ( extract_entity_ids, platform_devices_from_config) -from homeassistant.components import group +from homeassistant.components import group, discovery, wink DOMAIN = "light" @@ -89,6 +90,11 @@ FLASH_LONG = "long" LIGHT_PROFILES_FILE = "light_profiles.csv" +# Maps discovered services to their platforms +DISCOVERY_PLATFORMS = { + wink.DISCOVER_LIGHTS: 'wink', +} + _LOGGER = logging.getLogger(__name__) @@ -165,19 +171,41 @@ def setup(hass, config): lights = platform_devices_from_config( config, DOMAIN, hass, ENTITY_ID_FORMAT, _LOGGER) - if not lights: - return False - # pylint: disable=unused-argument def update_lights_state(now): """ Update the states of all the lights. """ - for light in lights.values(): - light.update_ha_state(hass) + if lights: + _LOGGER.info("Updating light states") + + for light in lights.values(): + light.update_ha_state(hass) update_lights_state(None) # Track all lights in a group - group.Group(hass, GROUP_NAME_ALL_LIGHTS, lights.keys(), False) + light_group = group.Group( + hass, GROUP_NAME_ALL_LIGHTS, lights.keys(), False) + + def light_discovered(service, info): + """ Called when a light is discovered. """ + platform = get_component( + "{}.{}".format(DOMAIN, DISCOVERY_PLATFORMS[service])) + + discovered = platform.device_discovered(hass, config, info) + + for light in discovered: + if light is not None and light not in lights.values(): + light.entity_id = util.ensure_unique_string( + ENTITY_ID_FORMAT.format(util.slugify(light.get_name())), + lights.keys()) + + lights[light.entity_id] = light + + light.update_ha_state(hass) + + light_group.update_tracked_entity_ids(lights.keys()) + + discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), light_discovered) def handle_light_service(service): """ Hande a turn light on or off service call. """ diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/light/wink.py index b0abc8e2451..061ff06b7f8 100644 --- a/homeassistant/components/light/wink.py +++ b/homeassistant/components/light/wink.py @@ -1,25 +1,37 @@ """ Support for Hue lights. """ import logging +# pylint: disable=no-name-in-module, import-error import homeassistant.external.wink.pywink as pywink from homeassistant.helpers import ToggleDevice -from homeassistant.const import ATTR_FRIENDLY_NAME +from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_ACCESS_TOKEN # pylint: disable=unused-argument def get_devices(hass, config): """ Find and return Wink lights. """ - token = config.get('access_token') + 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 False + return [] pywink.set_bearer_token(token) + return get_lights() + + +# pylint: disable=unused-argument +def devices_discovered(hass, config, info): + """ Called when a device is discovered. """ + return get_lights() + + +def get_lights(): + """ Returns the Wink switches. """ return [WinkLight(light) for light in pywink.get_bulbs()] diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index caf6355d179..0e1570b9b00 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -12,7 +12,7 @@ from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) from homeassistant.helpers import ( extract_entity_ids, platform_devices_from_config) -from homeassistant.components import group, discovery +from homeassistant.components import group, discovery, wink DOMAIN = 'switch' DEPENDENCIES = [] @@ -29,8 +29,9 @@ ATTR_CURRENT_POWER_MWH = "current_power_mwh" MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) # Maps discovered services to their platforms -DISCOVERY = { - discovery.services.BELKIN_WEMO: 'wemo' +DISCOVERY_PLATFORMS = { + discovery.services.BELKIN_WEMO: 'wemo', + wink.DISCOVER_SWITCHES: 'wink', } _LOGGER = logging.getLogger(__name__) @@ -82,22 +83,24 @@ def setup(hass, config): def switch_discovered(service, info): """ Called when a switch is discovered. """ - platform = get_component("{}.{}".format(DOMAIN, DISCOVERY[service])) + platform = get_component("{}.{}".format( + DOMAIN, DISCOVERY_PLATFORMS[service])) - switch = platform.device_discovered(hass, config, info) + discovered = platform.device_discovered(hass, config, info) - if switch is not None and switch not in switches.values(): - switch.entity_id = util.ensure_unique_string( - ENTITY_ID_FORMAT.format(util.slugify(switch.get_name())), - switches.keys()) + for switch in discovered: + if switch is not None and switch not in switches.values(): + switch.entity_id = util.ensure_unique_string( + ENTITY_ID_FORMAT.format(util.slugify(switch.get_name())), + switches.keys()) - switches[switch.entity_id] = switch + switches[switch.entity_id] = switch - switch.update_ha_state(hass) + switch.update_ha_state(hass) - switch_group.update_tracked_entity_ids(switches.keys()) + switch_group.update_tracked_entity_ids(switches.keys()) - discovery.listen(hass, discovery.services.BELKIN_WEMO, switch_discovered) + discovery.listen(hass, DISCOVERY_PLATFORMS.keys(), switch_discovered) def handle_switch_service(service): """ Handles calls to the switch services. """ diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 63c6fe3b815..cadb4445fa9 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -24,16 +24,16 @@ def get_devices(hass, config): if isinstance(switch, pywemo.Switch)] -def device_discovered(hass, config, info): +def devices_discovered(hass, config, info): """ Called when a device is discovered. """ _, discovery = get_pywemo() if discovery is None: - return + return [] device = discovery.device_from_description(info) - return None if device is None else WemoSwitch(device) + return [] if device is None else [WemoSwitch(device)] def get_pywemo(): diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/switch/wink.py index d99d4bdacf5..edc404781fe 100644 --- a/homeassistant/components/switch/wink.py +++ b/homeassistant/components/switch/wink.py @@ -1,25 +1,37 @@ """ Support for WeMo switchces. """ import logging +# pylint: disable=no-name-in-module, import-error import homeassistant.external.wink.pywink as pywink from homeassistant.helpers import ToggleDevice -from homeassistant.const import ATTR_FRIENDLY_NAME +from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_ACCESS_TOKEN # pylint: disable=unused-argument def get_devices(hass, config): """ Find and return Wink switches. """ - token = config.get('access_token') + 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 False + return [] pywink.set_bearer_token(token) + return get_switches() + + +# pylint: disable=unused-argument +def devices_discovered(hass, config, info): + """ Called when a device is discovered. """ + return get_switches() + + +def get_switches(): + """ Returns the Wink switches. """ return [WinkSwitch(switch) for switch in pywink.get_switches()] diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py new file mode 100644 index 00000000000..ada55f90cb8 --- /dev/null +++ b/homeassistant/components/wink.py @@ -0,0 +1,51 @@ +""" +Connects to a Wink hub and loads relevant components to control its devices. +""" +import logging + +# pylint: disable=no-name-in-module, import-error +import homeassistant.external.wink.pywink as pywink + +from homeassistant import bootstrap +from homeassistant.loader import get_component +from homeassistant.helpers import validate_config +from homeassistant.const import ( + EVENT_SERVICE_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED, CONF_ACCESS_TOKEN) + +DOMAIN = "wink" +DEPENDENCIES = [] + +DISCOVER_LIGHTS = "wink.lights" +DISCOVER_SWITCHES = "wink.switches" + + +def setup(hass, config): + """ Sets up the Wink component. """ + logger = logging.getLogger(__name__) + + print(config) + + if not validate_config(config, {DOMAIN: [CONF_ACCESS_TOKEN]}, logger): + return False + + 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', pywink.get_switches, DISCOVER_SWITCHES)): + + component = get_component(component_name) + + if func_exists(): + # Ensure component is loaded + if component.DOMAIN not in hass.components: + # Add a worker on succesfull setup + if bootstrap.setup_component(hass, component.DOMAIN, config): + hass.pool.add_worker() + + # Fire discovery event + hass.bus.fire(EVENT_SERVICE_DISCOVERED, { + ATTR_SERVICE: discovery_type, + ATTR_DISCOVERED: {} + }) diff --git a/homeassistant/const.py b/homeassistant/const.py index 9711b28465c..3aebeb5ede7 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -15,6 +15,7 @@ CONF_HOSTS = "hosts" CONF_USERNAME = "username" CONF_PASSWORD = "password" CONF_API_KEY = "api_key" +CONF_ACCESS_TOKEN = "access_token" # #### EVENTS #### EVENT_HOMEASSISTANT_START = "homeassistant_start" @@ -23,6 +24,7 @@ EVENT_STATE_CHANGED = "state_changed" EVENT_TIME_CHANGED = "time_changed" EVENT_CALL_SERVICE = "call_service" EVENT_SERVICE_EXECUTED = "service_executed" +EVENT_SERVICE_DISCOVERED = "service_discovered" # #### STATES #### STATE_ON = 'on' @@ -54,6 +56,9 @@ ATTR_ENTITY_PICTURE = "entity_picture" # The unit of measurement if applicable ATTR_UNIT_OF_MEASUREMENT = "unit_of_measurement" +# Contains the information that is discovered +ATTR_DISCOVERED = "discovered" + # #### SERVICES #### SERVICE_HOMEASSISTANT_STOP = "stop" diff --git a/tests/test_component_light.py b/tests/test_component_light.py index 84fb07d6427..c5db2f37299 100644 --- a/tests/test_component_light.py +++ b/tests/test_component_light.py @@ -214,28 +214,6 @@ class TestLight(unittest.TestCase): light.ATTR_XY_COLOR: [prof_x, prof_y]}, data) - def test_setup(self): - """ Test the setup method. """ - # Bogus config - self.assertFalse(light.setup(self.hass, {})) - - self.assertFalse(light.setup(self.hass, {light.DOMAIN: {}})) - - # Test with non-existing component - self.assertFalse(light.setup( - self.hass, {light.DOMAIN: {CONF_TYPE: 'nonexisting'}} - )) - - # Test if light component returns 0 lightes - platform = loader.get_component('light.test') - platform.init(True) - - self.assertEqual([], platform.get_lights(None, None)) - - self.assertFalse(light.setup( - self.hass, {light.DOMAIN: {CONF_TYPE: 'test'}} - )) - def test_light_profiles(self): """ Test light profiles. """ platform = loader.get_component('light.test')