diff --git a/.coveragerc b/.coveragerc index fb7e3edd62c..d57aa96b40d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -59,6 +59,9 @@ omit = homeassistant/components/lutron.py homeassistant/components/*/lutron.py + homeassistant/components/lutron_caseta.py + homeassistant/components/*/lutron_caseta.py + homeassistant/components/modbus.py homeassistant/components/*/modbus.py @@ -115,9 +118,6 @@ omit = homeassistant/components/zigbee.py homeassistant/components/*/zigbee.py - homeassistant/components/zwave/* - homeassistant/components/*/zwave.py - homeassistant/components/enocean.py homeassistant/components/*/enocean.py @@ -145,6 +145,9 @@ omit = homeassistant/components/maxcube.py homeassistant/components/*/maxcube.py + homeassistant/components/tado.py + homeassistant/components/*/tado.py + homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/alarm_control_panel/concord232.py homeassistant/components/alarm_control_panel/nx584.py @@ -171,6 +174,7 @@ omit = homeassistant/components/climate/oem.py homeassistant/components/climate/proliphix.py homeassistant/components/climate/radiotherm.py + homeassistant/components/config/zwave.py homeassistant/components/cover/garadget.py homeassistant/components/cover/homematic.py homeassistant/components/cover/myq.py @@ -269,6 +273,7 @@ omit = homeassistant/components/media_player/sonos.py homeassistant/components/media_player/squeezebox.py homeassistant/components/media_player/vlc.py + homeassistant/components/media_player/volumio.py homeassistant/components/media_player/yamaha.py homeassistant/components/notify/aws_lambda.py homeassistant/components/notify/aws_sns.py @@ -327,7 +332,6 @@ omit = homeassistant/components/sensor/dovado.py homeassistant/components/sensor/dte_energy_bridge.py homeassistant/components/sensor/ebox.py - homeassistant/components/sensor/efergy.py homeassistant/components/sensor/eliqonline.py homeassistant/components/sensor/emoncms.py homeassistant/components/sensor/fastdotcom.py @@ -352,6 +356,7 @@ omit = homeassistant/components/sensor/lastfm.py homeassistant/components/sensor/linux_battery.py homeassistant/components/sensor/loopenergy.py + homeassistant/components/sensor/lyft.py homeassistant/components/sensor/miflora.py homeassistant/components/sensor/modem_callerid.py homeassistant/components/sensor/mqtt_room.py @@ -429,6 +434,9 @@ omit = homeassistant/components/weather/openweathermap.py homeassistant/components/weather/zamg.py homeassistant/components/zeroconf.py + homeassistant/components/zwave/__init__.py + homeassistant/components/zwave/util.py + homeassistant/components/zwave/workaround.py [report] diff --git a/MANIFEST.in b/MANIFEST.in index d04d86bae58..6f8652fe270 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include README.rst -include LICENSE +include LICENSE.md graft homeassistant prune homeassistant/components/frontend/www_static/home-assistant-polymer recursive-exclude * *.py[co] diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index d9ab410b745..1d5da4e798f 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -255,10 +255,13 @@ def closefds_osx(min_fd: int, max_fd: int) -> None: def cmdline() -> List[str]: """Collect path and arguments to re-execute the current hass instance.""" - if sys.argv[0].endswith('/__main__.py'): + if sys.argv[0].endswith(os.path.sep + '__main__.py'): modulepath = os.path.dirname(sys.argv[0]) os.environ['PYTHONPATH'] = os.path.dirname(modulepath) - return [sys.executable] + [arg for arg in sys.argv if arg != '--daemon'] + return [sys.executable] + [arg for arg in sys.argv if + arg != '--daemon'] + else: + return [arg for arg in sys.argv if arg != '--daemon'] def setup_and_run_hass(config_dir: str, diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 2cca8e1495b..e05fff98865 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -21,7 +21,6 @@ import homeassistant.loader as loader from homeassistant.util.logging import AsyncHandler from homeassistant.util.yaml import clear_secret_cache from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers import event_decorators, service from homeassistant.helpers.signal import async_register_signal_handling _LOGGER = logging.getLogger(__name__) @@ -127,10 +126,6 @@ def async_from_config_dict(config: Dict[str, Any], _LOGGER.info('Home Assistant core initialized') - # Give event decorators access to HASS - event_decorators.HASS = hass - service.HASS = hass - # stage 1 for component in components: if component not in FIRST_INIT_COMPONENT: diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index f6fbc933467..a13abfae8f9 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -113,7 +113,7 @@ def async_setup(hass, config): if not alarm.should_poll: continue - update_coro = hass.loop.create_task( + update_coro = hass.async_add_job( alarm.async_update_ha_state(True)) if hasattr(alarm, 'async_update'): update_tasks.append(update_coro) diff --git a/homeassistant/components/android_ip_webcam.py b/homeassistant/components/android_ip_webcam.py index e6810a67029..e2929d159da 100644 --- a/homeassistant/components/android_ip_webcam.py +++ b/homeassistant/components/android_ip_webcam.py @@ -27,7 +27,7 @@ from homeassistant.components.camera.mjpeg import ( CONF_MJPEG_URL, CONF_STILL_IMAGE_URL) DOMAIN = 'android_ip_webcam' -REQUIREMENTS = ["pydroid-ipcam==0.4"] +REQUIREMENTS = ["pydroid-ipcam==0.6"] _LOGGER = logging.getLogger(__name__) SCAN_INTERVAL = timedelta(seconds=10) @@ -125,11 +125,8 @@ SENSORS = ['audio_connections', 'battery_level', 'battery_temp', SIGNAL_UPDATE_DATA = 'android_ip_webcam_update' -CONF_AUTO_DISCOVERY = 'auto_discovery' CONF_MOTION_SENSOR = 'motion_sensor' -DEFAULT_AUTO_DISCOVERY = True -DEFAULT_MOTION_SENSOR = False DEFAULT_NAME = 'IP Webcam' DEFAULT_PORT = 8080 DEFAULT_TIMEOUT = 10 @@ -145,14 +142,11 @@ CONFIG_SCHEMA = vol.Schema({ cv.time_period, vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string, vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string, - vol.Optional(CONF_AUTO_DISCOVERY, default=DEFAULT_AUTO_DISCOVERY): - cv.boolean, - vol.Optional(CONF_SWITCHES, default=[]): + vol.Optional(CONF_SWITCHES, default=None): vol.All(cv.ensure_list, [vol.In(SWITCHES)]), - vol.Optional(CONF_SENSORS, default=[]): + vol.Optional(CONF_SENSORS, default=None): vol.All(cv.ensure_list, [vol.In(SENSORS)]), - vol.Optional(CONF_MOTION_SENSOR, default=DEFAULT_MOTION_SENSOR): - cv.boolean, + vol.Optional(CONF_MOTION_SENSOR, default=None): cv.boolean, })]) }, extra=vol.ALLOW_EXTRA) @@ -184,6 +178,18 @@ def async_setup(hass, config): timeout=cam_config[CONF_TIMEOUT] ) + if switches is None: + switches = [setting for setting in cam.enabled_settings + if setting in SWITCHES] + + if sensors is None: + sensors = [sensor for sensor in cam.enabled_sensors + if sensor in SENSORS] + sensors.extend(['audio_connections', 'video_connections']) + + if motion is None: + motion = 'motion_active' in cam.enabled_sensors + @asyncio.coroutine def async_update_data(now): """Update data from ipcam in SCAN_INTERVAL.""" @@ -195,20 +201,6 @@ def async_setup(hass, config): yield from async_update_data(None) - # use autodiscovery to detect sensors/configs - if cam_config[CONF_AUTO_DISCOVERY]: - if not cam.available: - _LOGGER.error( - "Android webcam %s not found for discovery!", cam.base_url) - return - - sensors = [sensor for sensor in cam.enabled_sensors - if sensor in SENSORS] - switches = [setting for setting in cam.enabled_settings - if setting in SWITCHES] - motion = True if 'motion_active' in cam.enabled_sensors else False - sensors.extend(['audio_connections', 'video_connections']) - # load platforms webcams[host] = cam diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index 8e03133e032..1af85605874 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -327,6 +327,8 @@ class APIEventForwardingView(HomeAssistantView): @asyncio.coroutine def post(self, request): """Setup an event forwarder.""" + _LOGGER.warning('Event forwarding is deprecated. ' + 'Will be removed by 0.43') hass = request.app['hass'] try: data = yield from request.json() diff --git a/homeassistant/components/binary_sensor/digital_ocean.py b/homeassistant/components/binary_sensor/digital_ocean.py index 31f29597f06..ea02196f3eb 100644 --- a/homeassistant/components/binary_sensor/digital_ocean.py +++ b/homeassistant/components/binary_sensor/digital_ocean.py @@ -36,6 +36,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): dev = [] for droplet in droplets: droplet_id = digital_ocean.DIGITAL_OCEAN.get_droplet_id(droplet) + if droplet_id is None: + _LOGGER.error("Droplet %s is not available", droplet) + return False dev.append(DigitalOceanBinarySensor( digital_ocean.DIGITAL_OCEAN, droplet_id)) diff --git a/homeassistant/components/binary_sensor/workday.py b/homeassistant/components/binary_sensor/workday.py new file mode 100644 index 00000000000..1b53738a25f --- /dev/null +++ b/homeassistant/components/binary_sensor/workday.py @@ -0,0 +1,157 @@ +"""Sensor to indicate whether the current day is a workday.""" +import asyncio +import logging +import datetime + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import (STATE_ON, STATE_OFF, STATE_UNKNOWN, + CONF_NAME, WEEKDAYS) +import homeassistant.util.dt as dt_util +from homeassistant.helpers.entity import Entity +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +REQUIREMENTS = ['holidays==0.8.1'] + +# List of all countries currently supported by holidays +# There seems to be no way to get the list out at runtime +ALL_COUNTRIES = ['Australia', 'AU', 'Austria', 'AT', 'Canada', 'CA', + 'Colombia', 'CO', 'Czech', 'CZ', 'Denmark', 'DK', 'England', + 'EuropeanCentralBank', 'ECB', 'TAR', 'Germany', 'DE', + 'Ireland', 'Isle of Man', 'Mexico', 'MX', 'Netherlands', 'NL', + 'NewZealand', 'NZ', 'Northern Ireland', 'Norway', 'NO', + 'Portugal', 'PT', 'PortugalExt', 'PTE', 'Scotland', 'Spain', + 'ES', 'UnitedKingdom', 'UK', 'UnitedStates', 'US', 'Wales'] +CONF_COUNTRY = 'country' +CONF_PROVINCE = 'province' +CONF_WORKDAYS = 'workdays' +# By default, Monday - Friday are workdays +DEFAULT_WORKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri'] +CONF_EXCLUDES = 'excludes' +# By default, public holidays, Saturdays and Sundays are excluded from workdays +DEFAULT_EXCLUDES = ['sat', 'sun', 'holiday'] +DEFAULT_NAME = 'Workday Sensor' +ALLOWED_DAYS = WEEKDAYS + ['holiday'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES), + vol.Optional(CONF_PROVINCE, default=None): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS): + vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]), + vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES): + vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]), +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Workday sensor.""" + import holidays + + # Get the Sensor name from the config + sensor_name = config.get(CONF_NAME) + + # Get the country code from the config + country = config.get(CONF_COUNTRY) + + # Get the province from the config + province = config.get(CONF_PROVINCE) + + # Get the list of workdays from the config + workdays = config.get(CONF_WORKDAYS) + + # Get the list of excludes from the config + excludes = config.get(CONF_EXCLUDES) + + # Instantiate the holidays module for the current year + year = datetime.datetime.now().year + obj_holidays = getattr(holidays, country)(years=year) + + # Also apply the provience, if available for the configured country + if province: + if province not in obj_holidays.PROVINCES: + _LOGGER.error('There is no province/state %s in country %s', + province, country) + return False + else: + year = datetime.datetime.now().year + obj_holidays = getattr(holidays, country)(prov=province, + years=year) + + # Output found public holidays via the debug channel + _LOGGER.debug("Found the following holidays for your configuration:") + for date, name in sorted(obj_holidays.items()): + _LOGGER.debug("%s %s", date, name) + + # Add ourselves as device + add_devices([IsWorkdaySensor(obj_holidays, workdays, + excludes, sensor_name)], True) + + +def day_to_string(day): + """Convert day index 0 - 7 to string.""" + try: + return ALLOWED_DAYS[day] + except IndexError: + return None + + +class IsWorkdaySensor(Entity): + """Implementation of a Workday sensor.""" + + def __init__(self, obj_holidays, workdays, excludes, name): + """Initialize the Workday sensor.""" + self._name = name + self._obj_holidays = obj_holidays + self._workdays = workdays + self._excludes = excludes + self._state = STATE_UNKNOWN + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the device.""" + return self._state + + def is_include(self, day, now): + """Check if given day is in the includes list.""" + # Check includes + if day in self._workdays: + return True + elif 'holiday' in self._workdays and now in self._obj_holidays: + return True + + return False + + def is_exclude(self, day, now): + """Check if given day is in the excludes list.""" + # Check excludes + if day in self._excludes: + return True + elif 'holiday' in self._excludes and now in self._obj_holidays: + return True + + return False + + @asyncio.coroutine + def async_update(self): + """Get date and look whether it is a holiday.""" + # Default is no workday + self._state = STATE_OFF + + # Get iso day of the week (1 = Monday, 7 = Sunday) + day = datetime.datetime.today().isoweekday() - 1 + day_of_week = day_to_string(day) + + if self.is_include(day_of_week, dt_util.now()): + self._state = STATE_ON + + if self.is_exclude(day_of_week, dt_util.now()): + self._state = STATE_OFF diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py index 48ef1479eec..5fd9c39ef2a 100644 --- a/homeassistant/components/binary_sensor/zwave.py +++ b/homeassistant/components/binary_sensor/zwave.py @@ -19,34 +19,34 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] -def get_device(value, **kwargs): +def get_device(values, **kwargs): """Create zwave entity device.""" - device_mapping = workaround.get_device_mapping(value) + device_mapping = workaround.get_device_mapping(values.primary) if device_mapping == workaround.WORKAROUND_NO_OFF_EVENT: # Default the multiplier to 4 - re_arm_multiplier = (zwave.get_config_value(value.node, 9) or 4) - return ZWaveTriggerSensor(value, "motion", re_arm_multiplier * 8) + re_arm_multiplier = zwave.get_config_value(values.primary.node, 9) or 4 + return ZWaveTriggerSensor(values, "motion", re_arm_multiplier * 8) - if workaround.get_device_component_mapping(value) == DOMAIN: - return ZWaveBinarySensor(value, None) + if workaround.get_device_component_mapping(values.primary) == DOMAIN: + return ZWaveBinarySensor(values, None) - if value.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY: - return ZWaveBinarySensor(value, None) + if values.primary.command_class == zwave.const.COMMAND_CLASS_SENSOR_BINARY: + return ZWaveBinarySensor(values, None) return None class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity): """Representation of a binary sensor within Z-Wave.""" - def __init__(self, value, device_class): + def __init__(self, values, device_class): """Initialize the sensor.""" - zwave.ZWaveDeviceEntity.__init__(self, value, DOMAIN) + zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN) self._sensor_type = device_class - self._state = self._value.data + self._state = self.values.primary.data def update_properties(self): """Callback on data changes for node values.""" - self._state = self._value.data + self._state = self.values.primary.data @property def is_on(self): @@ -58,24 +58,19 @@ class ZWaveBinarySensor(BinarySensorDevice, zwave.ZWaveDeviceEntity): """Return the class of this sensor, from DEVICE_CLASSES.""" return self._sensor_type - @property - def should_poll(self): - """No polling needed.""" - return False - class ZWaveTriggerSensor(ZWaveBinarySensor): """Representation of a stateless sensor within Z-Wave.""" - def __init__(self, value, device_class, re_arm_sec=60): + def __init__(self, values, device_class, re_arm_sec=60): """Initialize the sensor.""" - super(ZWaveTriggerSensor, self).__init__(value, device_class) + super(ZWaveTriggerSensor, self).__init__(values, device_class) self.re_arm_sec = re_arm_sec self.invalidate_after = None def update_properties(self): """Called when a value for this entity's node has changed.""" - self._state = self._value.data + self._state = self.values.primary.data # only allow this value to be true for re_arm secs if not self.hass: return diff --git a/homeassistant/components/blink.py b/homeassistant/components/blink.py index 94635e2ae59..2a079716b68 100644 --- a/homeassistant/components/blink.py +++ b/homeassistant/components/blink.py @@ -15,7 +15,7 @@ from homeassistant.helpers import discovery _LOGGER = logging.getLogger(__name__) DOMAIN = 'blink' -REQUIREMENTS = ['blinkpy==0.4.4'] +REQUIREMENTS = ['blinkpy==0.5.2'] CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ diff --git a/homeassistant/components/camera/blink.py b/homeassistant/components/camera/blink.py index 685ee5bd0fa..59616b0ded2 100644 --- a/homeassistant/components/camera/blink.py +++ b/homeassistant/components/camera/blink.py @@ -15,7 +15,7 @@ from homeassistant.util import Throttle DEPENDENCIES = ['blink'] -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/camera/dispatcher.py b/homeassistant/components/camera/dispatcher.py deleted file mode 100644 index b5a846665ad..00000000000 --- a/homeassistant/components/camera/dispatcher.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -Support for internal dispatcher image push to Camera. - -For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/camera.dispatcher/ -""" -import asyncio -import logging - -import voluptuous as vol - -from homeassistant.core import callback -from homeassistant.const import CONF_NAME -from homeassistant.components.camera import PLATFORM_SCHEMA, Camera -from homeassistant.helpers import config_validation as cv -from homeassistant.helpers.dispatcher import async_dispatcher_connect - -_LOGGER = logging.getLogger(__name__) - -CONF_SIGNAL = 'signal' -DEFAULT_NAME = 'Dispatcher Camera' - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SIGNAL): cv.slugify, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) - - -@asyncio.coroutine -def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Setup a dispatcher camera.""" - if discovery_info: - config = PLATFORM_SCHEMA(discovery_info) - - async_add_devices( - [DispatcherCamera(config[CONF_NAME], config[CONF_SIGNAL])]) - - -class DispatcherCamera(Camera): - """A dispatcher implementation of an camera.""" - - def __init__(self, name, signal): - """Initialize a dispatcher camera.""" - super().__init__() - self._name = name - self._signal = signal - self._image = None - - @asyncio.coroutine - def async_added_to_hass(self): - """Register dispatcher and callbacks.""" - @callback - def async_update_image(image): - """Update image from dispatcher call.""" - self._image = image - - async_dispatcher_connect(self.hass, self._signal, async_update_image) - - @asyncio.coroutine - def async_camera_image(self): - """Return a still image response from the camera.""" - return self._image - - @property - def name(self): - """Return the name of this device.""" - return self._name diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/camera/synology.py index c5d87c39086..2b4d72f92f8 100644 --- a/homeassistant/components/camera/synology.py +++ b/homeassistant/components/camera/synology.py @@ -14,7 +14,7 @@ import async_timeout from homeassistant.const import ( CONF_NAME, CONF_USERNAME, CONF_PASSWORD, - CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL) + CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL, CONF_TIMEOUT) from homeassistant.components.camera import ( Camera, PLATFORM_SCHEMA) from homeassistant.helpers.aiohttp_client import ( @@ -27,7 +27,7 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'Synology Camera' DEFAULT_STREAM_ID = '0' -TIMEOUT = 5 +DEFAULT_TIMEOUT = 5 CONF_CAMERA_NAME = 'camera_name' CONF_STREAM_ID = 'stream_id' @@ -51,6 +51,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_URL): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, vol.Optional(CONF_WHITELIST, default=[]): cv.ensure_list, vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, }) @@ -60,6 +61,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup a Synology IP Camera.""" verify_ssl = config.get(CONF_VERIFY_SSL) + timeout = config.get(CONF_TIMEOUT) websession_init = async_get_clientsession(hass, verify_ssl) # Determine API to use for authentication @@ -74,7 +76,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): } query_req = None try: - with async_timeout.timeout(TIMEOUT, loop=hass.loop): + with async_timeout.timeout(timeout, loop=hass.loop): query_req = yield from websession_init.get( syno_api_url, params=query_payload @@ -103,7 +105,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): websession_init, config.get(CONF_USERNAME), config.get(CONF_PASSWORD), - syno_auth_url + syno_auth_url, + timeout ) # init websession @@ -120,7 +123,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): 'version': '1' } try: - with async_timeout.timeout(TIMEOUT, loop=hass.loop): + with async_timeout.timeout(timeout, loop=hass.loop): camera_req = yield from websession.get( syno_camera_url, params=camera_payload @@ -149,7 +152,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): snapshot_path, streaming_path, camera_path, - auth_path + auth_path, + timeout ) devices.append(device) @@ -157,7 +161,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): @asyncio.coroutine -def get_session_id(hass, websession, username, password, login_url): +def get_session_id(hass, websession, username, password, login_url, timeout): """Get a session id.""" auth_payload = { 'api': AUTH_API, @@ -170,7 +174,7 @@ def get_session_id(hass, websession, username, password, login_url): } auth_req = None try: - with async_timeout.timeout(TIMEOUT, loop=hass.loop): + with async_timeout.timeout(timeout, loop=hass.loop): auth_req = yield from websession.get( login_url, params=auth_payload @@ -192,7 +196,7 @@ class SynologyCamera(Camera): def __init__(self, hass, websession, config, camera_id, camera_name, snapshot_path, streaming_path, camera_path, - auth_path): + auth_path, timeout): """Initialize a Synology Surveillance Station camera.""" super().__init__() self.hass = hass @@ -206,6 +210,7 @@ class SynologyCamera(Camera): self._streaming_path = streaming_path self._camera_path = camera_path self._auth_path = auth_path + self._timeout = timeout def camera_image(self): """Return bytes of camera image.""" @@ -225,7 +230,7 @@ class SynologyCamera(Camera): 'cameraId': self._camera_id } try: - with async_timeout.timeout(TIMEOUT, loop=self.hass.loop): + with async_timeout.timeout(self._timeout, loop=self.hass.loop): response = yield from self._websession.get( image_url, params=image_payload diff --git a/homeassistant/components/camera/zoneminder.py b/homeassistant/components/camera/zoneminder.py index 5148ce8b245..be38d468f2d 100644 --- a/homeassistant/components/camera/zoneminder.py +++ b/homeassistant/components/camera/zoneminder.py @@ -19,6 +19,9 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['zoneminder'] DOMAIN = 'zoneminder' +# From ZoneMinder's web/includes/config.php.in +ZM_STATE_ALARM = "2" + def _get_image_url(hass, monitor, mode): zm_data = hass.data[DOMAIN] @@ -69,10 +72,43 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): CONF_MJPEG_URL: _get_image_url(hass, monitor, 'jpeg'), CONF_STILL_IMAGE_URL: _get_image_url(hass, monitor, 'single') } - cameras.append(MjpegCamera(hass, device_info)) + cameras.append(ZoneMinderCamera(hass, device_info, monitor)) if not cameras: _LOGGER.warning('No active cameras found') return async_add_devices(cameras) + + +class ZoneMinderCamera(MjpegCamera): + """Representation of a ZoneMinder Monitor Stream.""" + + def __init__(self, hass, device_info, monitor): + """Initialize as a subclass of MjpegCamera.""" + super().__init__(hass, device_info) + self._monitor_id = int(monitor['Id']) + self._is_recording = None + + @property + def should_poll(self): + """Update the recording state periodically.""" + return True + + def update(self): + """Update our recording state from the ZM API.""" + _LOGGER.debug('Updating camera state for monitor %i', self._monitor_id) + status_response = zoneminder.get_state( + 'api/monitors/alarm/id:%i/command:status.json' % self._monitor_id + ) + if not status_response: + _LOGGER.warning('Could not get status for monitor %i', + self._monitor_id) + return + + self._is_recording = status_response['status'] == ZM_STATE_ALARM + + @property + def is_recording(self): + """Return whether the monitor is in alarm mode.""" + return self._is_recording diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index cb684785207..bd2e38433d6 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -224,7 +224,7 @@ def async_setup(hass, config): if not climate.should_poll: continue - update_coro = hass.loop.create_task( + update_coro = hass.async_add_job( climate.async_update_ha_state(True)) if hasattr(climate, 'async_update'): update_tasks.append(update_coro) diff --git a/homeassistant/components/climate/tado.py b/homeassistant/components/climate/tado.py new file mode 100644 index 00000000000..e5242f88162 --- /dev/null +++ b/homeassistant/components/climate/tado.py @@ -0,0 +1,296 @@ +"""tado component to create a climate device for each zone.""" + +import logging + +from homeassistant.const import TEMP_CELSIUS + +from homeassistant.components.climate import ( + ClimateDevice) +from homeassistant.const import ( + ATTR_TEMPERATURE) +from homeassistant.components.tado import ( + DATA_TADO) + +CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Default mytado mode +CONST_MODE_OFF = "OFF" # Switch off heating in a zone + +# When we change the temperature setting, we need an overlay mode +# wait until tado changes the mode automatic +CONST_OVERLAY_TADO_MODE = "TADO_MODE" +# the user has change the temperature or mode manually +CONST_OVERLAY_MANUAL = "MANUAL" +# the temperature will be reset after a timespan +CONST_OVERLAY_TIMER = "TIMER" + +OPERATION_LIST = { + CONST_OVERLAY_MANUAL: "Manual", + CONST_OVERLAY_TIMER: "Timer", + CONST_OVERLAY_TADO_MODE: "Tado mode", + CONST_MODE_SMART_SCHEDULE: "Smart schedule", + CONST_MODE_OFF: "Off"} + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the climate platform.""" + # get the PyTado object from the hub component + tado = hass.data[DATA_TADO] + + try: + zones = tado.get_zones() + except RuntimeError: + _LOGGER.error("Unable to get zone info from mytado") + return False + + climate_devices = [] + for zone in zones: + climate_devices.append(create_climate_device(tado, hass, + zone, + zone['name'], + zone['id'])) + + if len(climate_devices) > 0: + add_devices(climate_devices, True) + return True + else: + return False + + +def create_climate_device(tado, hass, zone, name, zone_id): + """Create a climate device.""" + capabilities = tado.get_capabilities(zone_id) + + unit = TEMP_CELSIUS + min_temp = float(capabilities["temperatures"]["celsius"]["min"]) + max_temp = float(capabilities["temperatures"]["celsius"]["max"]) + ac_mode = capabilities["type"] != "HEATING" + + data_id = 'zone {} {}'.format(name, zone_id) + device = TadoClimate(tado, + name, zone_id, data_id, + hass.config.units.temperature(min_temp, unit), + hass.config.units.temperature(max_temp, unit), + ac_mode) + + tado.add_sensor(data_id, { + "id": zone_id, + "zone": zone, + "name": name, + "climate": device + }) + + return device + + +class TadoClimate(ClimateDevice): + """Representation of a tado climate device.""" + + def __init__(self, store, zone_name, zone_id, data_id, + min_temp, max_temp, ac_mode, + tolerance=0.3): + """Initialization of TadoClimate device.""" + self._store = store + self._data_id = data_id + + self.zone_name = zone_name + self.zone_id = zone_id + + self.ac_mode = ac_mode + + self._active = False + self._device_is_active = False + + self._unit = TEMP_CELSIUS + self._cur_temp = None + self._cur_humidity = None + self._is_away = False + self._min_temp = min_temp + self._max_temp = max_temp + self._target_temp = None + self._tolerance = tolerance + + self._current_operation = CONST_MODE_SMART_SCHEDULE + self._overlay_mode = CONST_MODE_SMART_SCHEDULE + + @property + def name(self): + """Return the name of the sensor.""" + return self.zone_name + + @property + def current_humidity(self): + """Return the current humidity.""" + return self._cur_humidity + + @property + def current_temperature(self): + """Return the sensor temperature.""" + return self._cur_temp + + @property + def current_operation(self): + """Return current readable operation mode.""" + return OPERATION_LIST.get(self._current_operation) + + @property + def operation_list(self): + """List of available operation modes (readable).""" + return list(OPERATION_LIST.values()) + + @property + def temperature_unit(self): + """The unit of measurement used by the platform.""" + return self._unit + + @property + def is_away_mode_on(self): + """Return true if away mode is on.""" + return self._is_away + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + return self._target_temp + + def set_temperature(self, **kwargs): + """Set new target temperature.""" + temperature = kwargs.get(ATTR_TEMPERATURE) + if temperature is None: + return + + self._current_operation = CONST_OVERLAY_TADO_MODE + self._overlay_mode = None + self._target_temp = temperature + self._control_heating() + + def set_operation_mode(self, readable_operation_mode): + """Set new operation mode.""" + operation_mode = CONST_MODE_SMART_SCHEDULE + + for mode, readable in OPERATION_LIST.items(): + if readable == readable_operation_mode: + operation_mode = mode + break + + self._current_operation = operation_mode + self._overlay_mode = None + self._control_heating() + + @property + def min_temp(self): + """Return the minimum temperature.""" + if self._min_temp: + return self._min_temp + else: + # get default temp from super class + return super().min_temp + + @property + def max_temp(self): + """Return the maximum temperature.""" + if self._max_temp: + return self._max_temp + else: + # Get default temp from super class + return super().max_temp + + def update(self): + """Update the state of this climate device.""" + self._store.update() + + data = self._store.get_data(self._data_id) + + if data is None: + _LOGGER.debug('Recieved no data for zone %s', + self.zone_name) + return + + if 'sensorDataPoints' in data: + sensor_data = data['sensorDataPoints'] + temperature = float( + sensor_data['insideTemperature']['celsius']) + humidity = float( + sensor_data['humidity']['percentage']) + setting = 0 + + # temperature setting will not exist when device is off + if 'temperature' in data['setting'] and \ + data['setting']['temperature'] is not None: + setting = float( + data['setting']['temperature']['celsius']) + + unit = TEMP_CELSIUS + + self._cur_temp = self.hass.config.units.temperature( + temperature, unit) + + self._target_temp = self.hass.config.units.temperature( + setting, unit) + + self._cur_humidity = humidity + + if 'tadoMode' in data: + mode = data['tadoMode'] + self._is_away = mode == "AWAY" + + if 'setting' in data: + power = data['setting']['power'] + if power == "OFF": + self._current_operation = CONST_MODE_OFF + self._device_is_active = False + else: + self._device_is_active = True + + if 'overlay' in data and data['overlay'] is not None: + overlay = True + termination = data['overlay']['termination']['type'] + else: + overlay = False + termination = "" + + # if you set mode manualy to off, there will be an overlay + # and a termination, but we want to see the mode "OFF" + + if overlay and self._device_is_active: + # there is an overlay the device is on + self._overlay_mode = termination + self._current_operation = termination + else: + # there is no overlay, the mode will always be + # "SMART_SCHEDULE" + self._overlay_mode = CONST_MODE_SMART_SCHEDULE + self._current_operation = CONST_MODE_SMART_SCHEDULE + + def _control_heating(self): + """Send new target temperature to mytado.""" + if not self._active and None not in (self._cur_temp, + self._target_temp): + self._active = True + _LOGGER.info('Obtained current and target temperature. ' + 'tado thermostat active.') + + if not self._active or self._current_operation == self._overlay_mode: + return + + if self._current_operation == CONST_MODE_SMART_SCHEDULE: + _LOGGER.info('Switching mytado.com to SCHEDULE (default) ' + 'for zone %s', self.zone_name) + self._store.reset_zone_overlay(self.zone_id) + self._overlay_mode = self._current_operation + return + + if self._current_operation == CONST_MODE_OFF: + _LOGGER.info('Switching mytado.com to OFF for zone %s', + self.zone_name) + self._store.set_zone_overlay(self.zone_id, CONST_OVERLAY_MANUAL) + self._overlay_mode = self._current_operation + return + + _LOGGER.info('Switching mytado.com to %s mode for zone %s', + self._current_operation, self.zone_name) + self._store.set_zone_overlay(self.zone_id, + self._current_operation, + self._target_temp) + + self._overlay_mode = self._current_operation diff --git a/homeassistant/components/climate/vera.py b/homeassistant/components/climate/vera.py index ffedcb82602..79f10bc0421 100644 --- a/homeassistant/components/climate/vera.py +++ b/homeassistant/components/climate/vera.py @@ -85,11 +85,11 @@ class VeraThermostat(VeraDevice, ClimateDevice): return self.vera_device.fan_cycle() @property - def current_power_mwh(self): - """Current power usage in mWh.""" + def current_power_w(self): + """Current power usage in W.""" power = self.vera_device.power if power: - return convert(power, float, 0.0) * 1000 + return convert(power, float, 0.0) def update(self): """Called by the vera device callback to update state.""" diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py index 660eb76098d..4aaea884816 100755 --- a/homeassistant/components/climate/zwave.py +++ b/homeassistant/components/climate/zwave.py @@ -10,7 +10,6 @@ import logging from homeassistant.components.climate import DOMAIN from homeassistant.components.climate import ClimateDevice from homeassistant.components.zwave import ZWaveDeviceEntity -from homeassistant.components import zwave from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import from homeassistant.const import ( TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) @@ -33,20 +32,18 @@ DEVICE_MAPPINGS = { } -def get_device(hass, value, **kwargs): +def get_device(hass, values, **kwargs): """Create zwave entity device.""" temp_unit = hass.config.units.temperature_unit - return ZWaveClimate(value, temp_unit) + return ZWaveClimate(values, temp_unit) class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): """Representation of a Z-Wave Climate device.""" - def __init__(self, value, temp_unit): + def __init__(self, values, temp_unit): """Initialize the Z-Wave climate device.""" - ZWaveDeviceEntity.__init__(self, value, DOMAIN) - self._index = value.index - self._node = value.node + ZWaveDeviceEntity.__init__(self, values, DOMAIN) self._target_temperature = None self._current_temperature = None self._current_operation = None @@ -61,10 +58,11 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): _LOGGER.debug("temp_unit is %s", self._unit) self._zxt_120 = None # Make sure that we have values for the key before converting to int - if (value.node.manufacturer_id.strip() and - value.node.product_id.strip()): - specific_sensor_key = (int(value.node.manufacturer_id, 16), - int(value.node.product_id, 16)) + if (self.node.manufacturer_id.strip() and + self.node.product_id.strip()): + specific_sensor_key = ( + int(self.node.manufacturer_id, 16), + int(self.node.product_id, 16)) if specific_sensor_key in DEVICE_MAPPINGS: if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120: _LOGGER.debug("Remotec ZXT-120 Zwave Thermostat" @@ -75,86 +73,58 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): def update_properties(self): """Callback on data changes for node values.""" # Operation Mode - self._current_operation = self.get_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, member='data') - operation_list = self.get_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, - member='data_items') - if operation_list: - self._operation_list = list(operation_list) + if self.values.mode: + self._current_operation = self.values.mode.data + operation_list = self.values.mode.data_items + if operation_list: + self._operation_list = list(operation_list) _LOGGER.debug("self._operation_list=%s", self._operation_list) _LOGGER.debug("self._current_operation=%s", self._current_operation) # Current Temp - self._current_temperature = self.get_value( - class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL, - label=['Temperature'], member='data') - device_unit = self.get_value( - class_id=zwave.const.COMMAND_CLASS_SENSOR_MULTILEVEL, - label=['Temperature'], member='units') - if device_unit is not None: - self._unit = device_unit + if self.values.temperature: + self._current_temperature = self.values.temperature.data + device_unit = self.values.temperature.units + if device_unit is not None: + self._unit = device_unit # Fan Mode - self._current_fan_mode = self.get_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE, - member='data') - fan_list = self.get_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE, - member='data_items') - if fan_list: - self._fan_list = list(fan_list) + if self.values.fan_mode: + self._current_fan_mode = self.values.fan_mode.data + fan_list = self.values.fan_mode.data_items + if fan_list: + self._fan_list = list(fan_list) _LOGGER.debug("self._fan_list=%s", self._fan_list) _LOGGER.debug("self._current_fan_mode=%s", self._current_fan_mode) # Swing mode if self._zxt_120 == 1: - self._current_swing_mode = ( - self.get_value( - class_id=zwave.const.COMMAND_CLASS_CONFIGURATION, - index=33, - member='data')) - swing_list = self.get_value(class_id=zwave.const - .COMMAND_CLASS_CONFIGURATION, - index=33, - member='data_items') - if swing_list: - self._swing_list = list(swing_list) + if self.values.zxt_120_swing_mode: + self._current_swing_mode = self.values.zxt_120_swing_mode.data + swing_list = self.values.zxt_120_swing_mode.data_items + if swing_list: + self._swing_list = list(swing_list) _LOGGER.debug("self._swing_list=%s", self._swing_list) _LOGGER.debug("self._current_swing_mode=%s", self._current_swing_mode) # Set point - temps = [] - for value in ( - self._node.get_values( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT) - .values()): - temps.append((round(float(value.data)), 1)) - if value.index == self._index: - if value.data == 0: - _LOGGER.debug("Setpoint is 0, setting default to " - "current_temperature=%s", - self._current_temperature) - self._target_temperature = ( - round((float(self._current_temperature)), 1)) - break - else: - self._target_temperature = round((float(value.data)), 1) + if self.values.primary.data == 0: + _LOGGER.debug("Setpoint is 0, setting default to " + "current_temperature=%s", + self._current_temperature) + self._target_temperature = ( + round((float(self._current_temperature)), 1)) + else: + self._target_temperature = round( + (float(self.values.primary.data)), 1) # Operating state - self._operating_state = self.get_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_OPERATING_STATE, - member='data') + if self.values.operating_state: + self._operating_state = self.values.operating_state.data # Fan operating state - self._fan_state = self.get_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_STATE, - member='data') - - @property - def should_poll(self): - """No polling on Z-Wave.""" - return False + if self.values.fan_state: + self._fan_state = self.values.fan_state.data @property def current_fan_mode(self): @@ -213,41 +183,31 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): else: return - self.set_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_SETPOINT, - index=self._index, data=temperature) - self.schedule_update_ha_state() + self.values.primary.data = temperature def set_fan_mode(self, fan): """Set new target fan mode.""" - self.set_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_FAN_MODE, - index=0, data=bytes(fan, 'utf-8')) + if self.values.fan_mode: + self.values.fan_mode.data = bytes(fan, 'utf-8') def set_operation_mode(self, operation_mode): """Set new target operation mode.""" - self.set_value( - class_id=zwave.const.COMMAND_CLASS_THERMOSTAT_MODE, - index=0, data=bytes(operation_mode, 'utf-8')) + if self.values.mode: + self.values.mode.data = bytes(operation_mode, 'utf-8') def set_swing_mode(self, swing_mode): """Set new target swing mode.""" if self._zxt_120 == 1: - self.set_value( - class_id=zwave.const.COMMAND_CLASS_CONFIGURATION, - index=33, data=bytes(swing_mode, 'utf-8')) + if self.values.zxt_120_swing_mode: + self.values.zxt_120_swing_mode.data = bytes( + swing_mode, 'utf-8') @property def device_state_attributes(self): """Return the device specific state attributes.""" data = super().device_state_attributes if self._operating_state: - data[ATTR_OPERATING_STATE] = self._operating_state, + data[ATTR_OPERATING_STATE] = self._operating_state if self._fan_state: data[ATTR_FAN_STATE] = self._fan_state return data - - @property - def dependent_value_ids(self): - """List of value IDs a device depends on.""" - return None diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index 5d5fa265a0e..c857793f579 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -166,7 +166,7 @@ def async_setup(hass, config): if not cover.should_poll: continue - update_coro = hass.loop.create_task( + update_coro = hass.async_add_job( cover.async_update_ha_state(True)) if hasattr(cover, 'async_update'): update_tasks.append(update_coro) diff --git a/homeassistant/components/cover/myq.py b/homeassistant/components/cover/myq.py index 826be580f93..b749fde465f 100644 --- a/homeassistant/components/cover/myq.py +++ b/homeassistant/components/cover/myq.py @@ -14,8 +14,8 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv REQUIREMENTS = [ - 'https://github.com/arraylabs/pymyq/archive/v0.0.7.zip' - '#pymyq==0.0.7'] + 'https://github.com/arraylabs/pymyq/archive/v0.0.8.zip' + '#pymyq==0.0.8'] COVER_SCHEMA = vol.Schema({ vol.Required(CONF_TYPE): cv.string, diff --git a/homeassistant/components/cover/zwave.py b/homeassistant/components/cover/zwave.py index 46f23a68515..129dbd32ffe 100644 --- a/homeassistant/components/cover/zwave.py +++ b/homeassistant/components/cover/zwave.py @@ -20,64 +20,49 @@ _LOGGER = logging.getLogger(__name__) SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE -def get_device(value, **kwargs): +def get_device(values, **kwargs): """Create zwave entity device.""" - if (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL - and value.index == 0): - return ZwaveRollershutter(value) - elif (value.command_class == zwave.const.COMMAND_CLASS_SWITCH_BINARY or - value.command_class == zwave.const.COMMAND_CLASS_BARRIER_OPERATOR): - return ZwaveGarageDoor(value) + if (values.primary.command_class == + zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL + and values.primary.index == 0): + return ZwaveRollershutter(values) + elif (values.primary.command_class in [ + zwave.const.COMMAND_CLASS_SWITCH_BINARY, + zwave.const.COMMAND_CLASS_BARRIER_OPERATOR]): + return ZwaveGarageDoor(values) return None class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): """Representation of an Zwave roller shutter.""" - def __init__(self, value): + def __init__(self, values): """Initialize the zwave rollershutter.""" - ZWaveDeviceEntity.__init__(self, value, DOMAIN) + ZWaveDeviceEntity.__init__(self, values, DOMAIN) # pylint: disable=no-member - self._node = value.node self._open_id = None self._close_id = None - self._current_position_id = None self._current_position = None - self._workaround = workaround.get_device_mapping(value) + self._workaround = workaround.get_device_mapping(values.primary) if self._workaround: _LOGGER.debug("Using workaround %s", self._workaround) self.update_properties() - @property - def dependent_value_ids(self): - """List of value IDs a device depends on.""" - if not self._node.is_ready: - return None - return [self._current_position_id] - def update_properties(self): """Callback on data changes for node values.""" # Position value - if not self._node.is_ready: - if self._current_position_id is None: - self._current_position_id = self.get_value( - class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL, - label=['Level'], member='value_id') - if self._open_id is None: - self._open_id = self.get_value( - class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL, - label=['Open', 'Up'], member='value_id') - if self._close_id is None: - self._close_id = self.get_value( - class_id=zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL, - label=['Close', 'Down'], member='value_id') - if self._open_id and self._close_id and \ - self._workaround == workaround.WORKAROUND_REVERSE_OPEN_CLOSE: - self._open_id, self._close_id = self._close_id, self._open_id - self._workaround = None - self._current_position = self._node.get_dimmer_level( - self._current_position_id) + self._current_position = self.values.primary.data + + if self.values.open and self.values.close and \ + self._open_id is None and self._close_id is None: + if self._workaround == workaround.WORKAROUND_REVERSE_OPEN_CLOSE: + self._open_id = self.values.close.value_id + self._close_id = self.values.open.value_id + self._workaround = None + else: + self._open_id = self.values.open.value_id + self._close_id = self.values.close.value_id @property def is_closed(self): @@ -112,7 +97,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): def set_cover_position(self, position, **kwargs): """Move the roller shutter to a specific position.""" - self._node.set_dimmer(self._value.value_id, position) + self.node.set_dimmer(self.values.primary.value_id, position) def stop_cover(self, **kwargs): """Stop the roller shutter.""" @@ -122,14 +107,14 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice): """Representation of an Zwave garage door device.""" - def __init__(self, value): + def __init__(self, values): """Initialize the zwave garage door.""" - ZWaveDeviceEntity.__init__(self, value, DOMAIN) + ZWaveDeviceEntity.__init__(self, values, DOMAIN) self.update_properties() def update_properties(self): """Callback on data changes for node values.""" - self._state = self._value.data + self._state = self.values.primary.data @property def is_closed(self): @@ -138,11 +123,11 @@ class ZwaveGarageDoor(zwave.ZWaveDeviceEntity, CoverDevice): def close_cover(self): """Close the garage door.""" - self._value.data = False + self.values.primary.data = False def open_cover(self): """Open the garage door.""" - self._value.data = True + self.values.primary.data = True @property def device_class(self): diff --git a/homeassistant/components/device_tracker/fritz.py b/homeassistant/components/device_tracker/fritz.py index c262a8fdf2a..a11139afa0f 100644 --- a/homeassistant/components/device_tracker/fritz.py +++ b/homeassistant/components/device_tracker/fritz.py @@ -15,7 +15,7 @@ from homeassistant.components.device_tracker import ( from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.util import Throttle -REQUIREMENTS = ['fritzconnection==0.6'] +REQUIREMENTS = ['fritzconnection==0.6.3'] # Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) diff --git a/homeassistant/components/device_tracker/luci.py b/homeassistant/components/device_tracker/luci.py index 9dd9a11c426..8cc6af48767 100644 --- a/homeassistant/components/device_tracker/luci.py +++ b/homeassistant/components/device_tracker/luci.py @@ -14,6 +14,7 @@ import requests import voluptuous as vol import homeassistant.helpers.config_validation as cv +from homeassistant.exceptions import HomeAssistantError from homeassistant.components.device_tracker import ( DOMAIN, PLATFORM_SCHEMA, DeviceScanner) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME @@ -31,6 +32,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) +class InvalidLuciTokenError(HomeAssistantError): + """When an invalid token is detected.""" + + pass + + def get_scanner(hass, config): """Validate the configuration and return a Luci scanner.""" scanner = LuciDeviceScanner(config[DOMAIN]) @@ -46,8 +53,9 @@ class LuciDeviceScanner(DeviceScanner): def __init__(self, config): """Initialize the scanner.""" - host = config[CONF_HOST] - username, password = config[CONF_USERNAME], config[CONF_PASSWORD] + self.host = config[CONF_HOST] + self.username = config[CONF_USERNAME] + self.password = config[CONF_PASSWORD] self.parse_api_pattern = re.compile(r"(?P\w*) = (?P.*);") @@ -55,12 +63,15 @@ class LuciDeviceScanner(DeviceScanner): self.last_results = {} - self.token = _get_token(host, username, password) - self.host = host + self.refresh_token() self.mac2name = None self.success_init = self.token is not None + def refresh_token(self): + """Get a new token.""" + self.token = _get_token(self.host, self.username, self.password) + def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" self._update_info() @@ -98,8 +109,15 @@ class LuciDeviceScanner(DeviceScanner): _LOGGER.info('Checking ARP') url = 'http://{}/cgi-bin/luci/rpc/sys'.format(self.host) - result = _req_json_rpc(url, 'net.arptable', - params={'auth': self.token}) + + try: + result = _req_json_rpc(url, 'net.arptable', + params={'auth': self.token}) + except InvalidLuciTokenError: + _LOGGER.info('Refreshing token') + self.refresh_token() + return False + if result: self.last_results = [] for device_entry in result: @@ -116,6 +134,7 @@ class LuciDeviceScanner(DeviceScanner): def _req_json_rpc(url, method, *args, **kwargs): """Perform one JSON RPC operation.""" data = json.dumps({'method': method, 'params': args}) + try: res = requests.post(url, data=data, timeout=5, **kwargs) except requests.exceptions.Timeout: @@ -139,6 +158,10 @@ def _req_json_rpc(url, method, *args, **kwargs): "Failed to authenticate, " "please check your username and password") return + elif res.status_code == 403: + _LOGGER.error('Luci responded with a 403 Invalid token') + raise InvalidLuciTokenError + else: _LOGGER.error('Invalid response from luci: %s', res) diff --git a/homeassistant/components/digital_ocean.py b/homeassistant/components/digital_ocean.py index dd8c7de99d4..d4c567a295a 100644 --- a/homeassistant/components/digital_ocean.py +++ b/homeassistant/components/digital_ocean.py @@ -13,7 +13,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-digitalocean==1.10.1'] +REQUIREMENTS = ['python-digitalocean==1.11'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py index dd8dda36121..fc2ddb32500 100644 --- a/homeassistant/components/fan/__init__.py +++ b/homeassistant/components/fan/__init__.py @@ -218,7 +218,7 @@ def async_setup(hass, config: dict): if not fan.should_poll: continue - update_coro = hass.loop.create_task( + update_coro = hass.async_add_job( fan.async_update_ha_state(True)) if hasattr(fan, 'async_update'): update_tasks.append(update_coro) diff --git a/homeassistant/components/frontend/templates/index.html b/homeassistant/components/frontend/templates/index.html index 24c0584b05e..f385d89c9aa 100644 --- a/homeassistant/components/frontend/templates/index.html +++ b/homeassistant/components/frontend/templates/index.html @@ -63,7 +63,7 @@ window.Polymer = { lazyRegister: true, useNativeCSSProperties: true, - dom: 'shady', + dom: 'shadow', suppressTemplateNotifications: true, suppressBindingNotifications: true, }; diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index b90dcfce4c1..cae324af9ba 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -2,19 +2,19 @@ FINGERPRINTS = { "compatibility.js": "83d9c77748dafa9db49ae77d7f3d8fb0", - "core.js": "1f7f88d8f5dada08bce1d935cfa5f33e", - "frontend.html": "418f6ef8354ce71f1b9594ee2068ebef", - "mdi.html": "65413cdf82f822bd6480e577852f0292", + "core.js": "5d08475f03adb5969bd31855d5ca0cfd", + "frontend.html": "53c45b837a3bcae7cfb9ef4a5919844f", + "mdi.html": "4921d26f29dc148c3e8bd5bcd8ce5822", "micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a", - "panels/ha-panel-config.html": "412b3e24515ffa1ee8074ce974cf4057", - "panels/ha-panel-dev-event.html": "91347dedf3b4fa9b49ccf4c0a28a03c4", + "panels/ha-panel-config.html": "6dcb246cd356307a638f81c4f89bf9b3", + "panels/ha-panel-dev-event.html": "1f169700c2345785855b1d7919d12326", "panels/ha-panel-dev-info.html": "61610e015a411cfc84edd2c4d489e71d", - "panels/ha-panel-dev-service.html": "153aad076f98bbd626466bac50986874", - "panels/ha-panel-dev-state.html": "90f3bede9602241552ef7bb7958198c6", - "panels/ha-panel-dev-template.html": "c249a4fc18a3a6994de3d6330cfe6cbb", - "panels/ha-panel-history.html": "fdaa4d2402d49d4c8bd64a1708ab7a50", + "panels/ha-panel-dev-service.html": "0fe8e6acdccf2dc3d1ae657b2c7f2df0", + "panels/ha-panel-dev-state.html": "48d37db4a1d6708314ded1d624d0f4d4", + "panels/ha-panel-dev-template.html": "6f353392d68574fbc5af188bca44d0ae", + "panels/ha-panel-history.html": "bfd5f929d5aa9cefdd799ec37624efa1", "panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab", - "panels/ha-panel-logbook.html": "2af1feb30b37427f481d5437a438a3f2", - "panels/ha-panel-map.html": "e10704a3469e44d1714eac9ed8e4b6a0", + "panels/ha-panel-logbook.html": "a1fc2b5d739bedb9d87e4da4cd929a71", + "panels/ha-panel-map.html": "9aa065b1908089f3bb5af7fdf9485be5", "websocket_test.html": "575de64b431fe11c3785bf96d7813450" } diff --git a/homeassistant/components/frontend/www_static/compatibility.js.gz b/homeassistant/components/frontend/www_static/compatibility.js.gz index c8714e89f1e..77ca6c1bdac 100644 Binary files a/homeassistant/components/frontend/www_static/compatibility.js.gz and b/homeassistant/components/frontend/www_static/compatibility.js.gz differ diff --git a/homeassistant/components/frontend/www_static/core.js b/homeassistant/components/frontend/www_static/core.js index b62cbf2df7a..df563364eaa 100644 --- a/homeassistant/components/frontend/www_static/core.js +++ b/homeassistant/components/frontend/www_static/core.js @@ -1 +1 @@ -!(function(){"use strict";function e(e){return{type:"auth",api_password:e}}function t(){return{type:"get_states"}}function n(){return{type:"get_config"}}function r(){return{type:"get_services"}}function i(){return{type:"get_panels"}}function s(e,t,n){var r={type:"call_service",domain:e,service:t};return n&&(r.service_data=n),r}function o(e){var t={type:"subscribe_events"};return e&&(t.event_type=e),t}function u(e){return{type:"unsubscribe_events",subscription:e}}function c(){return{type:"ping"}}function a(e,t){return{type:"result",success:!1,error:{code:e,message:t}}}function d(t,n){function r(i,s,o){var u=new WebSocket(t),c=!1,a=function(){if(c)return void o(C);if(0===i)return void o(O);var e=i===-1?-1:i-1;setTimeout((function(){return r(e,s,o)}),1e3)},d=function(t){var r=JSON.parse(t.data);switch(r.type){case"auth_required":"authToken"in n?u.send(JSON.stringify(e(n.authToken))):(c=!0,u.close());break;case"auth_invalid":c=!0,u.close();break;case"auth_ok":u.removeEventListener("message",d),u.removeEventListener("close",a),s(u)}};u.addEventListener("message",d),u.addEventListener("close",a)}return new Promise(function(e,t){return r(n.setupRetry||0,e,t)})}function f(e){return e.result}function v(e,t){return void 0===t&&(t={}),d(e,t).then((function(n){var r=new j(e,t);return r.setSocket(n),r}))}function h(e,t){return e._subscribeConfig?e._subscribeConfig(t):new Promise(function(n,r){function i(e){a=Object.assign({},a,e);for(var t=0;t \ No newline at end of file +}()); \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/frontend.html.gz b/homeassistant/components/frontend/www_static/frontend.html.gz index 40657a51e08..09e5808da65 100644 Binary files a/homeassistant/components/frontend/www_static/frontend.html.gz and b/homeassistant/components/frontend/www_static/frontend.html.gz differ diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index de1b20b70a1..f4c59e1eff3 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit de1b20b70a16aeb7c48a1b4867c97864c88adb1c +Subproject commit f4c59e1eff3223262c198a29cf70c62572de019b diff --git a/homeassistant/components/frontend/www_static/mdi.html b/homeassistant/components/frontend/www_static/mdi.html index 07fd6d9e02e..0354cac0c44 100644 --- a/homeassistant/components/frontend/www_static/mdi.html +++ b/homeassistant/components/frontend/www_static/mdi.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/mdi.html.gz b/homeassistant/components/frontend/www_static/mdi.html.gz index e8350d32a91..e58a965a79c 100644 Binary files a/homeassistant/components/frontend/www_static/mdi.html.gz and b/homeassistant/components/frontend/www_static/mdi.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-config.html b/homeassistant/components/frontend/www_static/panels/ha-panel-config.html index fbd4c3ee29b..62a9e31885b 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-config.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-config.html @@ -1,4 +1,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz index b837adbe5a3..4af3bed93ba 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html index cb9b5c206c4..b650277a15b 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz index bc0d2b540aa..c094f8d9fb0 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-service.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html index daacce7f492..1cc4b362708 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz index a40999b24b2..b9dea8501fe 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-state.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html index c70c9f83533..12bfbb395cb 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html.gz index f4043f7d3b9..d663a683324 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-template.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-history.html b/homeassistant/components/frontend/www_static/panels/ha-panel-history.html index 247190870a4..b665faaf2fd 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-history.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-history.html @@ -1,4 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-history.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-history.html.gz index 3cddc96e2c2..e8d34afeb38 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-history.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-history.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-logbook.html b/homeassistant/components/frontend/www_static/panels/ha-panel-logbook.html index 0e49516175d..3cdf5654da8 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-logbook.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-logbook.html @@ -1,4 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-logbook.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-logbook.html.gz index 4d95744ee28..728de3ce443 100644 Binary files a/homeassistant/components/frontend/www_static/panels/ha-panel-logbook.html.gz and b/homeassistant/components/frontend/www_static/panels/ha-panel-logbook.html.gz differ diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-map.html b/homeassistant/components/frontend/www_static/panels/ha-panel-map.html index a407c7c681a..f04929a0b2e 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-map.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-map.html @@ -1,8 +1,8 @@ -