diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 802da19e938..9db3f79e498 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -1,13 +1,4 @@ -""" -homeassistant.bootstrap -~~~~~~~~~~~~~~~~~~~~~~~ -Provides methods to bootstrap a home assistant instance. - -Each method will return a tuple (bus, statemachine). - -After bootstrapping you can add your own components or -start by calling homeassistant.start_home_assistant(bus) -""" +"""Provides methods to bootstrap a home assistant instance.""" import logging import logging.handlers @@ -15,6 +6,7 @@ import os import shutil import sys from collections import defaultdict +from threading import RLock import homeassistant.components as core_components import homeassistant.components.group as group @@ -32,6 +24,8 @@ from homeassistant.helpers import event_decorators, service from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) +_SETUP_LOCK = RLock() +_CURRENT_SETUP = [] ATTR_COMPONENT = 'component' @@ -78,42 +72,57 @@ def _handle_requirements(hass, component, name): def _setup_component(hass, domain, config): - """ Setup a component for Home Assistant. """ + """Setup a component for Home Assistant.""" + # pylint: disable=too-many-return-statements if domain in hass.config.components: return True - component = loader.get_component(domain) - missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', []) - if dep not in hass.config.components] + with _SETUP_LOCK: + # It might have been loaded while waiting for lock + if domain in hass.config.components: + return True - if missing_deps: - _LOGGER.error( - 'Not initializing %s because not all dependencies loaded: %s', - domain, ", ".join(missing_deps)) - return False - - if not _handle_requirements(hass, component, domain): - return False - - try: - if not component.setup(hass, config): - _LOGGER.error('component %s failed to initialize', domain) + if domain in _CURRENT_SETUP: + _LOGGER.error('Attempt made to setup %s during setup of %s', + domain, domain) return False - except Exception: # pylint: disable=broad-except - _LOGGER.exception('Error during setup of component %s', domain) - return False - hass.config.components.append(component.DOMAIN) + component = loader.get_component(domain) + missing_deps = [dep for dep in getattr(component, 'DEPENDENCIES', []) + if dep not in hass.config.components] - # Assumption: if a component does not depend on groups - # it communicates with devices - if group.DOMAIN not in getattr(component, 'DEPENDENCIES', []): - hass.pool.add_worker() + if missing_deps: + _LOGGER.error( + 'Not initializing %s because not all dependencies loaded: %s', + domain, ", ".join(missing_deps)) + return False - hass.bus.fire( - EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}) + if not _handle_requirements(hass, component, domain): + return False - return True + _CURRENT_SETUP.append(domain) + + try: + if not component.setup(hass, config): + _LOGGER.error('component %s failed to initialize', domain) + return False + except Exception: # pylint: disable=broad-except + _LOGGER.exception('Error during setup of component %s', domain) + return False + finally: + _CURRENT_SETUP.remove(domain) + + hass.config.components.append(component.DOMAIN) + + # Assumption: if a component does not depend on groups + # it communicates with devices + if group.DOMAIN not in getattr(component, 'DEPENDENCIES', []): + hass.pool.add_worker() + + hass.bus.fire( + EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}) + + return True def prepare_setup_platform(hass, config, domain, platform_name): diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 3934feae179..c51b30d8b7c 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -12,7 +12,7 @@ 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 (mysensors, ) +from homeassistant.components import (bloomsky, mysensors) DOMAIN = 'binary_sensor' SCAN_INTERVAL = 30 @@ -32,6 +32,7 @@ SENSOR_CLASSES = [ # Maps discovered services to their platforms DISCOVERY_PLATFORMS = { + bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky', mysensors.DISCOVER_BINARY_SENSORS: 'mysensors', } @@ -61,19 +62,17 @@ class BinarySensorDevice(Entity): """Return the state of the binary sensor.""" return STATE_ON if self.is_on else STATE_OFF - @property - def friendly_state(self): - """Return the friendly state of the binary sensor.""" - return None - @property def sensor_class(self): - """Return the class of this sensor, from SENSOR_CASSES.""" + """Return the class of this sensor, from SENSOR_CLASSES.""" return None @property def state_attributes(self): """Return device specific state attributes.""" - return { - 'sensor_class': self.sensor_class, - } + attr = {} + + if self.sensor_class is not None: + attr['sensor_class'] = self.sensor_class + + return attr diff --git a/homeassistant/components/binary_sensor/bloomsky.py b/homeassistant/components/binary_sensor/bloomsky.py new file mode 100644 index 00000000000..ce24a0c1015 --- /dev/null +++ b/homeassistant/components/binary_sensor/bloomsky.py @@ -0,0 +1,74 @@ +""" +Support the binary sensors of a BloomSky weather station. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.bloomsky/ +""" +import logging + +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.loader import get_component + +DEPENDENCIES = ["bloomsky"] + +# These are the available sensors mapped to binary_sensor class +SENSOR_TYPES = { + "Rain": "moisture", + "Night": None, +} + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the available BloomSky weather binary sensors.""" + logger = logging.getLogger(__name__) + bloomsky = get_component('bloomsky') + sensors = config.get('monitored_conditions', SENSOR_TYPES) + + for device in bloomsky.BLOOMSKY.devices.values(): + for variable in sensors: + if variable in SENSOR_TYPES: + add_devices([BloomSkySensor(bloomsky.BLOOMSKY, + device, + variable)]) + else: + logger.error("Cannot find definition for device: %s", variable) + + +class BloomSkySensor(BinarySensorDevice): + """ Represents a single binary sensor in a BloomSky device. """ + + def __init__(self, bs, device, sensor_name): + """Initialize a bloomsky binary sensor.""" + self._bloomsky = bs + self._device_id = device["DeviceID"] + self._sensor_name = sensor_name + self._name = "{} {}".format(device["DeviceName"], sensor_name) + self._unique_id = "bloomsky_binary_sensor {}".format(self._name) + self.update() + + @property + def name(self): + """The name of the BloomSky device and this sensor.""" + return self._name + + @property + def unique_id(self): + """Unique ID for this sensor.""" + return self._unique_id + + @property + def sensor_class(self): + """Return the class of this sensor, from SENSOR_CLASSES.""" + return SENSOR_TYPES.get(self._sensor_name) + + @property + def is_on(self): + """If binary sensor is on.""" + return self._state + + def update(self): + """Request an update from the BloomSky API.""" + self._bloomsky.refresh_devices() + + self._state = \ + self._bloomsky.devices[self._device_id]["Data"][self._sensor_name] diff --git a/homeassistant/components/bloomsky.py b/homeassistant/components/bloomsky.py index f13b9ce85c9..44a90007725 100644 --- a/homeassistant/components/bloomsky.py +++ b/homeassistant/components/bloomsky.py @@ -11,6 +11,7 @@ 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.util import Throttle @@ -24,6 +25,10 @@ _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): @@ -42,6 +47,12 @@ 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) + return True diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 7fab1fe3ae6..615554ae56f 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -13,6 +13,7 @@ import requests from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_component import EntityComponent +from homeassistant.components import bloomsky from homeassistant.const import ( ATTR_ENTITY_PICTURE, HTTP_NOT_FOUND, @@ -26,7 +27,9 @@ SCAN_INTERVAL = 30 ENTITY_ID_FORMAT = DOMAIN + '.{}' # Maps discovered services to their platforms -DISCOVERY_PLATFORMS = {} +DISCOVERY_PLATFORMS = { + bloomsky.DISCOVER_CAMERAS: 'bloomsky', +} STATE_RECORDING = 'recording' STATE_STREAMING = 'streaming' diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 09816d73f9a..f6678fb35e5 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -1,6 +1,4 @@ """ -homeassistant.components.discovery -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Starts a service to scan in intervals for new devices. Will emit EVENT_PLATFORM_DISCOVERED whenever a new service has been discovered. @@ -39,24 +37,41 @@ SERVICE_HANDLERS = { def listen(hass, service, callback): - """ - Setup listener for discovery of specific service. + """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): - """ Listens for discovery events. """ + """Listen for discovery events.""" if event.data[ATTR_SERVICE] in service: - callback(event.data[ATTR_SERVICE], event.data[ATTR_DISCOVERED]) + 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 setup(hass, config): """ Starts a discovery service. """ logger = logging.getLogger(__name__) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index ea3e441f6c5..255f032d803 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """ DO NOT MODIFY. Auto-generated by build_frontend script """ -VERSION = "4ae370eaaad6bc779d08713b79ce3cba" +VERSION = "72a593fc8887c08d6f4ad9bd483bd232" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 0013746b4d2..df86e85e4c0 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -2860,6 +2860,7 @@ ready:function(){this.templatize(this)}}),Polymer._collections=new WeakMap,Polym font-size: 0px; border-radius: 2px; cursor: pointer; + min-height: 48px; } .camera-feed { width: 100%; @@ -2879,7 +2880,10 @@ ready:function(){this.templatize(this)}}),Polymer._collections=new WeakMap,Polym font-weight: 500; line-height: 16px; color: white; - }[[stateObj.entityDisplay]][[stateObj.entityDisplay]](Error loading image)