diff --git a/.coveragerc b/.coveragerc index 5d54e8c10da..ba936e424e1 100644 --- a/.coveragerc +++ b/.coveragerc @@ -39,6 +39,9 @@ omit = homeassistant/components/verisure.py homeassistant/components/*/verisure.py + homeassistant/components/wemo.py + homeassistant/components/*/wemo.py + homeassistant/components/wink.py homeassistant/components/*/wink.py @@ -91,7 +94,6 @@ omit = homeassistant/components/light/hyperion.py homeassistant/components/light/lifx.py homeassistant/components/light/limitlessled.py - homeassistant/components/light/wemo.py homeassistant/components/media_player/cast.py homeassistant/components/media_player/denon.py homeassistant/components/media_player/firetv.py @@ -149,7 +151,6 @@ omit = homeassistant/components/switch/orvibo.py homeassistant/components/switch/rest.py homeassistant/components/switch/transmission.py - homeassistant/components/switch/wemo.py homeassistant/components/thermostat/heatmiser.py homeassistant/components/thermostat/homematic.py homeassistant/components/thermostat/proliphix.py diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index f6678fb35e5..6dd4e667a75 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -27,7 +27,7 @@ SERVICE_SONOS = 'sonos' SERVICE_PLEX = 'plex_mediaserver' SERVICE_HANDLERS = { - SERVICE_WEMO: "switch", + SERVICE_WEMO: "wemo", SERVICE_CAST: "media_player", SERVICE_HUE: "light", SERVICE_NETGEAR: 'device_tracker', diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 9e84a56ee73..cc00cd6be42 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -11,7 +11,7 @@ import os import csv from homeassistant.components import ( - group, discovery, wink, isy994, zwave, insteon_hub, mysensors) + group, discovery, wemo, wink, isy994, zwave, insteon_hub, mysensors) from homeassistant.config import load_yaml_config_file from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, @@ -59,6 +59,7 @@ 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', diff --git a/homeassistant/components/light/wemo.py b/homeassistant/components/light/wemo.py index 1b8ed06a148..61b3d4585f2 100644 --- a/homeassistant/components/light/wemo.py +++ b/homeassistant/components/light/wemo.py @@ -13,7 +13,8 @@ import homeassistant.util as util from homeassistant.components.light import ( Light, ATTR_BRIGHTNESS) -# REQUIREMENTS = ['pywemo==0.3.12'] +DEPENDENCIES = ['wemo'] + MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) @@ -21,8 +22,7 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find WeMo bridges and return connected lights. """ - import pywemo + """Probe WeMo bridges and register connected lights.""" import pywemo.discovery as discovery if discovery_info is not None: @@ -30,28 +30,17 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): mac = discovery_info[3] device = discovery.device_from_description(location, mac) - if device and isinstance(device, pywemo.Bridge): - setup_bridge(device, add_devices_callback) - return - - _LOGGER.info("Scanning for WeMo devices.") - devices = pywemo.discover_devices() - - # Filter out the bridges - for device in devices: - if isinstance(device, pywemo.Bridge): - _LOGGER.info("Found bridge %s.", device) + if device: setup_bridge(device, add_devices_callback) def setup_bridge(bridge, add_devices_callback): - """ Setup a WeMo link. """ - + """Setup a WeMo link.""" lights = {} @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS) def update_lights(): - """ Updates the WeMo led objects with latest info from the bridge. """ + """Updates the WeMo led objects with latest info from the bridge.""" bridge.bridge_get_lights() @@ -59,8 +48,8 @@ def setup_bridge(bridge, add_devices_callback): for light_id, info in bridge.Lights.items(): if light_id not in lights: - lights[light_id] = WemoLight(light_id, info, - bridge, update_lights) + lights[light_id] = WemoLight(bridge, light_id, info, + update_lights) new_lights.append(lights[light_id]) else: lights[light_id].info = info @@ -72,46 +61,46 @@ def setup_bridge(bridge, add_devices_callback): class WemoLight(Light): - """ Represents a WeMo light """ + """Represents a WeMo light""" - def __init__(self, light_id, info, bridge, update_lights): + def __init__(self, bridge, light_id, info, update_lights): + self.bridge = bridge self.light_id = light_id self.info = info - self.bridge = bridge self.update_lights = update_lights @property def unique_id(self): - """ Returns the id of this light """ + """Returns the id of this light""" deviceid = self.bridge.light_get_id(self.info) return "{}.{}".format(self.__class__, deviceid) @property def name(self): - """ Get the name of the light. """ + """Get the name of the light.""" return self.bridge.light_name(self.info) @property def brightness(self): - """ Brightness of this light between 0..255. """ + """Brightness of this light between 0..255.""" state = self.bridge.light_get_state(self.info) return int(state['dim']) @property def is_on(self): - """ True if device is on. """ + """True if device is on.""" state = self.bridge.light_get_state(self.info) return int(state['state']) def turn_on(self, **kwargs): - """ Turn the light on. """ + """Turn the light on.""" dim = kwargs.get(ATTR_BRIGHTNESS, self.brightness) self.bridge.light_set_state(self.info, state=1, dim=dim) def turn_off(self, **kwargs): - """ Turn the light off. """ + """Turn the light off.""" self.bridge.light_set_state(self.info, state=0, dim=0) def update(self): - """ Synchronize state with bridge. """ + """Synchronize state with bridge.""" self.update_lights(no_throttle=True) diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 742de5fb10e..4bd4b82e1c9 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -18,7 +18,7 @@ from homeassistant.const import ( STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID) from homeassistant.components import ( - group, discovery, wink, isy994, verisure, zwave, tellduslive, mysensors) + group, wemo, wink, isy994, verisure, zwave, tellduslive, mysensors) DOMAIN = 'switch' SCAN_INTERVAL = 30 @@ -35,7 +35,7 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) # Maps discovered services to their platforms DISCOVERY_PLATFORMS = { - discovery.SERVICE_WEMO: 'wemo', + wemo.DISCOVER_SWITCHES: 'wemo', wink.DISCOVER_SWITCHES: 'wink', isy994.DISCOVER_SWITCHES: 'isy994', verisure.DISCOVER_SWITCHES: 'verisure', diff --git a/homeassistant/components/switch/wemo.py b/homeassistant/components/switch/wemo.py index 0304b90003d..0e6a1014ca3 100644 --- a/homeassistant/components/switch/wemo.py +++ b/homeassistant/components/switch/wemo.py @@ -9,14 +9,13 @@ https://home-assistant.io/components/switch.wemo/ import logging from homeassistant.components.switch import SwitchDevice -from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON, STATE_STANDBY) +from homeassistant.const import STATE_OFF, STATE_ON, STATE_STANDBY +from homeassistant.loader import get_component + +DEPENDENCIES = ['wemo'] -REQUIREMENTS = ['pywemo==0.3.12'] _LOGGER = logging.getLogger(__name__) -_WEMO_SUBSCRIPTION_REGISTRY = None - ATTR_SENSOR_STATE = "sensor_state" ATTR_SWITCH_MODE = "switch_mode" @@ -24,73 +23,33 @@ MAKER_SWITCH_MOMENTARY = "momentary" MAKER_SWITCH_TOGGLE = "toggle" -def _find_manual_wemos(pywemo, static_config): - for address in static_config: - port = pywemo.ouimeaux_device.probe_wemo(address) - if not port: - _LOGGER.warning('Unable to probe wemo at %s', address) - continue - _LOGGER.info('Adding static wemo at %s:%i', address, port) - url = 'http://%s:%i/setup.xml' % (address, port) - yield pywemo.discovery.device_from_description(url, None) - - # pylint: disable=unused-argument, too-many-function-args def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """ Find and return WeMo switches. """ - import pywemo + """Register discovered WeMo switches.""" import pywemo.discovery as discovery - global _WEMO_SUBSCRIPTION_REGISTRY - if _WEMO_SUBSCRIPTION_REGISTRY is None: - _WEMO_SUBSCRIPTION_REGISTRY = pywemo.SubscriptionRegistry() - _WEMO_SUBSCRIPTION_REGISTRY.start() - - def stop_wemo(event): - """ Shutdown Wemo subscriptions and subscription thread on exit""" - _LOGGER.info("Shutting down subscriptions.") - _WEMO_SUBSCRIPTION_REGISTRY.stop() - - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_wemo) - if discovery_info is not None: location = discovery_info[2] mac = discovery_info[3] device = discovery.device_from_description(location, mac) - if device and isinstance(device, pywemo.Switch): + if device: add_devices_callback([WemoSwitch(device)]) - return - - _LOGGER.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)]) - - # Add manually-defined wemo devices - if discovery_info is None and 'static' in config: - add_devices_callback( - [WemoSwitch(wemo) - for wemo in _find_manual_wemos(pywemo, config['static'])]) - class WemoSwitch(SwitchDevice): - """ Represents a WeMo switch. """ - def __init__(self, wemo): - self.wemo = wemo + """Represents a WeMo switch.""" + def __init__(self, device): + self.wemo = device self.insight_params = None self.maker_params = None - _WEMO_SUBSCRIPTION_REGISTRY.register(wemo) - _WEMO_SUBSCRIPTION_REGISTRY.on( - wemo, None, self._update_callback) + wemo = get_component('wemo') + wemo.SUBSCRIPTION_REGISTRY.register(self.wemo) + wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback) def _update_callback(self, _device, _params): - """ Called by the wemo device callback to update state. """ + """Called by the wemo device callback to update state.""" _LOGGER.info( 'Subscription update for %s', _device) @@ -98,17 +57,17 @@ class WemoSwitch(SwitchDevice): @property def should_poll(self): - """ No polling needed with subscriptions """ + """No polling needed with subscriptions""" return False @property def unique_id(self): - """ Returns the id of this WeMo switch """ + """Returns the id of this WeMo switch""" return "{}.{}".format(self.__class__, self.wemo.serialnumber) @property def name(self): - """ Returns the name of the switch if any. """ + """Returns the name of the switch if any.""" return self.wemo.name @property @@ -133,7 +92,7 @@ class WemoSwitch(SwitchDevice): @property def state(self): - """ Returns the state. """ + """Returns the state.""" is_on = self.is_on if not is_on: return STATE_OFF @@ -143,19 +102,19 @@ class WemoSwitch(SwitchDevice): @property def current_power_mwh(self): - """ Current power usage in mwh. """ + """Current power usage in mwh.""" if self.insight_params: return self.insight_params['currentpower'] @property def today_power_mw(self): - """ Today total power usage in mw. """ + """Today total power usage in mw.""" if self.insight_params: return self.insight_params['todaymw'] @property def is_standby(self): - """ Is the device on - or in standby. """ + """Is the device on - or in standby.""" if self.insight_params: standby_state = self.insight_params['state'] # Standby is actually '8' but seems more defensive @@ -167,12 +126,12 @@ class WemoSwitch(SwitchDevice): @property def is_on(self): - """ True if switch is on. """ + """True if switch is on.""" return self.wemo.get_state() @property def available(self): - """ True if switch is available. """ + """True if switch is available.""" if (self.wemo.model_name == 'Insight' and self.insight_params is None): return False @@ -183,15 +142,15 @@ class WemoSwitch(SwitchDevice): return True def turn_on(self, **kwargs): - """ Turns the switch on. """ + """Turns the switch on.""" self.wemo.on() def turn_off(self): - """ Turns the switch off. """ + """Turns the switch off.""" self.wemo.off() def update(self): - """ Update WeMo state. """ + """Update WeMo state.""" try: self.wemo.get_state(True) if self.wemo.model_name == 'Insight': diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py new file mode 100644 index 00000000000..1d4c1592dfc --- /dev/null +++ b/homeassistant/components/wemo.py @@ -0,0 +1,95 @@ +""" +homeassistant.components.wemo +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +WeMo device discovery. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/wemo/ +""" +import logging + +from homeassistant.components import discovery +from homeassistant.const import EVENT_HOMEASSISTANT_STOP + +REQUIREMENTS = ['pywemo==0.3.12'] + +DOMAIN = 'wemo' +DISCOVER_LIGHTS = 'wemo.light' +DISCOVER_MOTION = 'wemo.motion' +DISCOVER_SWITCHES = 'wemo.switch' + +# mapping from Wemo model_name to service +WEMO_MODEL_DISPATCH = { + 'Bridge': DISCOVER_LIGHTS, + 'Insight': DISCOVER_SWITCHES, + 'Maker': DISCOVER_SWITCHES, + 'Motion': DISCOVER_MOTION, + 'Switch': DISCOVER_SWITCHES, +} +WEMO_SERVICE_DISPATCH = { + DISCOVER_LIGHTS: 'light', + DISCOVER_MOTION: 'binary_sensor', + DISCOVER_SWITCHES: 'switch', +} + +SUBSCRIPTION_REGISTRY = None +KNOWN_DEVICES = [] + +_LOGGER = logging.getLogger(__name__) + + +# pylint: disable=unused-argument, too-many-function-args +def setup(hass, config): + """Common set up for WeMo devices.""" + import pywemo + + global SUBSCRIPTION_REGISTRY + SUBSCRIPTION_REGISTRY = pywemo.SubscriptionRegistry() + SUBSCRIPTION_REGISTRY.start() + + def stop_wemo(event): + """Shutdown Wemo subscriptions and subscription thread on exit.""" + _LOGGER.info("Shutting down subscriptions.") + SUBSCRIPTION_REGISTRY.stop() + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_wemo) + + def discovery_dispatch(service, discovery_info): + """Dispatcher for WeMo discovery events.""" + # name, model, location, mac + _, model_name, _, mac = discovery_info + + # Only register a device once + if mac in KNOWN_DEVICES: + return + KNOWN_DEVICES.append(mac) + + service = WEMO_MODEL_DISPATCH.get(model_name) + component = WEMO_SERVICE_DISPATCH.get(service) + + if service is not None: + discovery.discover(hass, service, discovery_info, + component, config) + + discovery.listen(hass, discovery.SERVICE_WEMO, discovery_dispatch) + + _LOGGER.info("Scanning for WeMo devices.") + devices = [(device.host, device) for device in pywemo.discover_devices()] + + # Add static devices from the config file + devices.extend((address, None) for address in config.get('static', [])) + + for address, device in devices: + port = pywemo.ouimeaux_device.probe_wemo(address) + if not port: + _LOGGER.warning('Unable to probe wemo at %s', address) + continue + _LOGGER.info('Adding wemo at %s:%i', address, port) + + url = 'http://%s:%i/setup.xml' % (address, port) + if device is None: + device = pywemo.discovery.device_from_description(url, None) + + discovery_info = (device.name, device.model_name, url, device.mac) + discovery.discover(hass, discovery.SERVICE_WEMO, discovery_info) + return True diff --git a/requirements_all.txt b/requirements_all.txt index 26ca2b92072..b5736ef3801 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -216,7 +216,7 @@ pyuserinput==0.1.9 # homeassistant.components.switch.vera pyvera==0.2.8 -# homeassistant.components.switch.wemo +# homeassistant.components.wemo pywemo==0.3.12 # homeassistant.components.thermostat.radiotherm