From 4748e7f7e93f01e1399d68473238bda66dcd888a Mon Sep 17 00:00:00 2001 From: Robbie Trencheny Date: Sat, 10 Sep 2016 18:23:21 -0700 Subject: [PATCH 001/162] Version bump --- homeassistant/const.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index eb8b65df998..ffb162ebff5 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,8 +1,8 @@ # coding: utf-8 """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 -MINOR_VERSION = 28 -PATCH_VERSION = '0' +MINOR_VERSION = 29 +PATCH_VERSION = '0.dev0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 4) From 9bbe7be684ca680c1e7b47518a6dfe7bca54f763 Mon Sep 17 00:00:00 2001 From: Marcelo Moreira de Mello Date: Sun, 11 Sep 2016 03:07:13 -0400 Subject: [PATCH 002/162] Fixed voluptuous to accept string instead positive_int for CODE on Simplisafe (#3310) --- homeassistant/components/alarm_control_panel/simplisafe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/alarm_control_panel/simplisafe.py b/homeassistant/components/alarm_control_panel/simplisafe.py index 82927246ec6..38128489ba0 100644 --- a/homeassistant/components/alarm_control_panel/simplisafe.py +++ b/homeassistant/components/alarm_control_panel/simplisafe.py @@ -26,7 +26,7 @@ DEFAULT_NAME = 'SimpliSafe' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_PASSWORD): cv.string, vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_CODE): cv.positive_int, + vol.Optional(CONF_CODE): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) From 3f4d30c8daf45b89490e00b9bed13d4e3c5ef639 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 09:21:01 +0200 Subject: [PATCH 003/162] Add timeout (#3304) --- homeassistant/components/notify/instapush.py | 42 ++++++++++---------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/notify/instapush.py b/homeassistant/components/notify/instapush.py index 3a8f2d9ee0a..ef06fe87b24 100644 --- a/homeassistant/components/notify/instapush.py +++ b/homeassistant/components/notify/instapush.py @@ -15,11 +15,15 @@ from homeassistant.components.notify import ( ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) from homeassistant.const import CONF_API_KEY +_LOGGER = logging.getLogger(__name__) +_RESOURCE = 'https://api.instapush.im/v1/' CONF_APP_SECRET = 'app_secret' CONF_EVENT = 'event' CONF_TRACKER = 'tracker' +DEFAULT_TIMEOUT = 10 + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_API_KEY): cv.string, vol.Required(CONF_APP_SECRET): cv.string, @@ -28,18 +32,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ }) -_LOGGER = logging.getLogger(__name__) -_RESOURCE = 'https://api.instapush.im/v1/' - - def get_service(hass, config): - """Get the instapush notification service.""" + """Get the Instapush notification service.""" headers = {'x-instapush-appid': config[CONF_API_KEY], 'x-instapush-appsecret': config[CONF_APP_SECRET]} try: - response = requests.get(_RESOURCE + 'events/list', - headers=headers).json() + response = requests.get( + '{}{}'.format(_RESOURCE, 'events/list'), headers=headers, + timeout=DEFAULT_TIMEOUT).json() except ValueError: _LOGGER.error('Unexpected answer from Instapush API.') return None @@ -50,19 +51,18 @@ def get_service(hass, config): if len([app for app in response if app['title'] == config[CONF_EVENT]]) == 0: - _LOGGER.error( - "No app match your given value. " - "Please create an app at https://instapush.im") + _LOGGER.error("No app match your given value. " + "Please create an app at https://instapush.im") return None return InstapushNotificationService( - config[CONF_API_KEY], config[CONF_APP_SECRET], config[CONF_EVENT], - config[CONF_TRACKER]) + config.get(CONF_API_KEY), config.get(CONF_APP_SECRET), + config.get(CONF_EVENT), config.get(CONF_TRACKER)) # pylint: disable=too-few-public-methods class InstapushNotificationService(BaseNotificationService): - """Implement the notification service for Instapush.""" + """Implementation of the notification service for Instapush.""" def __init__(self, api_key, app_secret, event, tracker): """Initialize the service.""" @@ -78,13 +78,15 @@ class InstapushNotificationService(BaseNotificationService): def send_message(self, message="", **kwargs): """Send a message to a user.""" title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) - data = {"event": self._event, - "trackers": {self._tracker: title + " : " + message}} + data = { + 'event': self._event, + 'trackers': {self._tracker: title + ' : ' + message} + } - response = requests.post(_RESOURCE + 'post', data=json.dumps(data), - headers=self._headers) + response = requests.post( + '{}{}'.format(_RESOURCE, 'post'), data=json.dumps(data), + headers=self._headers, timeout=DEFAULT_TIMEOUT) if response.json()['status'] == 401: - _LOGGER.error( - response.json()['msg'], - "Please check your details at https://instapush.im/") + _LOGGER.error(response.json()['msg'], + "Please check your Instapush settings") From 78313c793c6817286bf536825dae5d9dc8e4b122 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 09:21:16 +0200 Subject: [PATCH 004/162] Migrate to voluptuous (#3298) --- homeassistant/components/climate/honeywell.py | 97 ++++++++++--------- tests/components/climate/test_honeywell.py | 77 +++++++++------ 2 files changed, 97 insertions(+), 77 deletions(-) diff --git a/homeassistant/components/climate/honeywell.py b/homeassistant/components/climate/honeywell.py index 001bf8806ac..fa22b7aebe4 100644 --- a/homeassistant/components/climate/honeywell.py +++ b/homeassistant/components/climate/honeywell.py @@ -7,44 +7,67 @@ https://home-assistant.io/components/climate.honeywell/ import logging import socket -from homeassistant.components.climate import ClimateDevice +import voluptuous as vol + +from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA) from homeassistant.const import ( CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['evohomeclient==0.2.5', 'somecomfort==0.2.1'] _LOGGER = logging.getLogger(__name__) -CONF_AWAY_TEMP = "away_temperature" -DEFAULT_AWAY_TEMP = 16 +ATTR_FAN = 'fan' +ATTR_FANMODE = 'fanmode' +ATTR_SYSTEM_MODE = 'system_mode' + +CONF_AWAY_TEMPERATURE = 'away_temperature' +CONF_REGION = 'region' + +DEFAULT_AWAY_TEMPERATURE = 16 +DEFAULT_REGION = 'eu' +REGIONS = ['eu', 'us'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_AWAY_TEMPERATURE, default=DEFAULT_AWAY_TEMPERATURE): + vol.Coerce(float), + vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(REGIONS), +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the HoneywelL thermostat.""" + username = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + region = config.get(CONF_REGION) + + if region == 'us': + return _setup_us(username, password, config, add_devices) + else: + return _setup_round(username, password, config, add_devices) def _setup_round(username, password, config, add_devices): """Setup rounding function.""" from evohomeclient import EvohomeClient - try: - away_temp = float(config.get(CONF_AWAY_TEMP, DEFAULT_AWAY_TEMP)) - except ValueError: - _LOGGER.error("value entered for item %s should convert to a number", - CONF_AWAY_TEMP) - return False - + away_temp = config.get(CONF_AWAY_TEMPERATURE) evo_api = EvohomeClient(username, password) try: zones = evo_api.temperatures(force_refresh=True) for i, zone in enumerate(zones): - add_devices([RoundThermostat(evo_api, - zone['id'], - i == 0, - away_temp)]) + add_devices( + [RoundThermostat(evo_api, zone['id'], i == 0, away_temp)] + ) except socket.error: _LOGGER.error( - "Connection error logging into the honeywell evohome web service" - ) + "Connection error logging into the honeywell evohome web service") return False return True @@ -74,26 +97,6 @@ def _setup_us(username, password, config, add_devices): return True -def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the honeywel thermostat.""" - username = config.get(CONF_USERNAME) - password = config.get(CONF_PASSWORD) - region = config.get('region', 'eu').lower() - - if username is None or password is None: - _LOGGER.error("Missing required configuration items %s or %s", - CONF_USERNAME, CONF_PASSWORD) - return False - if region not in ('us', 'eu'): - _LOGGER.error('Region `%s` is invalid (use either us or eu)', region) - return False - - if region == 'us': - return _setup_us(username, password, config, add_devices) - else: - return _setup_round(username, password, config, add_devices) - - class RoundThermostat(ClimateDevice): """Representation of a Honeywell Round Connected thermostat.""" @@ -103,7 +106,7 @@ class RoundThermostat(ClimateDevice): self.device = device self._current_temperature = None self._target_temperature = None - self._name = "round connected" + self._name = 'round connected' self._id = zone_id self._master = master self._is_dhw = False @@ -143,7 +146,7 @@ class RoundThermostat(ClimateDevice): @property def current_operation(self: ClimateDevice) -> str: """Get the current operation of the system.""" - return getattr(self.device, 'system_mode', None) + return getattr(self.device, ATTR_SYSTEM_MODE, None) @property def is_away_mode_on(self): @@ -152,7 +155,7 @@ class RoundThermostat(ClimateDevice): def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None: """Set the HVAC mode for the thermostat.""" - if hasattr(self.device, 'system_mode'): + if hasattr(self.device, ATTR_SYSTEM_MODE): self.device.system_mode = operation_mode def turn_away_mode_on(self): @@ -186,8 +189,8 @@ class RoundThermostat(ClimateDevice): self._current_temperature = data['temp'] self._target_temperature = data['setpoint'] - if data['thermostat'] == "DOMESTIC_HOT_WATER": - self._name = "Hot Water" + if data['thermostat'] == 'DOMESTIC_HOT_WATER': + self._name = 'Hot Water' self._is_dhw = True else: self._name = data['name'] @@ -236,7 +239,7 @@ class HoneywellUSThermostat(ClimateDevice): @property def current_operation(self: ClimateDevice) -> str: """Return current operation ie. heat, cool, idle.""" - return getattr(self._device, 'system_mode', None) + return getattr(self._device, ATTR_SYSTEM_MODE, None) def set_temperature(self, **kwargs): """Set target temperature.""" @@ -255,9 +258,11 @@ class HoneywellUSThermostat(ClimateDevice): @property def device_state_attributes(self): """Return the device specific state attributes.""" - return {'fan': (self.is_fan_on and 'running' or 'idle'), - 'fanmode': self._device.fan_mode, - 'system_mode': self._device.system_mode} + return { + ATTR_FAN: (self.is_fan_on and 'running' or 'idle'), + ATTR_FANMODE: self._device.fan_mode, + ATTR_SYSTEM_MODE: self._device.system_mode, + } def turn_away_mode_on(self): """Turn away on.""" @@ -269,5 +274,5 @@ class HoneywellUSThermostat(ClimateDevice): def set_operation_mode(self: ClimateDevice, operation_mode: str) -> None: """Set the system mode (Cool, Heat, etc).""" - if hasattr(self._device, 'system_mode'): + if hasattr(self._device, ATTR_SYSTEM_MODE): self._device.system_mode = operation_mode diff --git a/tests/components/climate/test_honeywell.py b/tests/components/climate/test_honeywell.py index 75a4d1081f3..470e280faa7 100644 --- a/tests/components/climate/test_honeywell.py +++ b/tests/components/climate/test_honeywell.py @@ -3,10 +3,11 @@ import socket import unittest from unittest import mock +import voluptuous as vol import somecomfort -from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, - TEMP_CELSIUS, TEMP_FAHRENHEIT) +from homeassistant.const import ( + CONF_USERNAME, CONF_PASSWORD, TEMP_CELSIUS, TEMP_FAHRENHEIT) import homeassistant.components.climate.honeywell as honeywell @@ -21,17 +22,30 @@ class TestHoneywell(unittest.TestCase): config = { CONF_USERNAME: 'user', CONF_PASSWORD: 'pass', - 'region': 'us', + honeywell.CONF_REGION: 'us', } bad_pass_config = { CONF_USERNAME: 'user', - 'region': 'us', + honeywell.CONF_REGION: 'us', } bad_region_config = { CONF_USERNAME: 'user', CONF_PASSWORD: 'pass', - 'region': 'un', + honeywell.CONF_REGION: 'un', } + + with self.assertRaises(vol.Invalid): + honeywell.PLATFORM_SCHEMA(None) + + with self.assertRaises(vol.Invalid): + honeywell.PLATFORM_SCHEMA({}) + + with self.assertRaises(vol.Invalid): + honeywell.PLATFORM_SCHEMA(bad_pass_config) + + with self.assertRaises(vol.Invalid): + honeywell.PLATFORM_SCHEMA(bad_region_config) + hass = mock.MagicMock() add_devices = mock.MagicMock() @@ -46,10 +60,6 @@ class TestHoneywell(unittest.TestCase): locations[0].devices_by_id.values.return_value = devices_1 locations[1].devices_by_id.values.return_value = devices_2 - result = honeywell.setup_platform(hass, bad_pass_config, add_devices) - self.assertFalse(result) - result = honeywell.setup_platform(hass, bad_region_config, add_devices) - self.assertFalse(result) result = honeywell.setup_platform(hass, config, add_devices) self.assertTrue(result) mock_sc.assert_called_once_with('user', 'pass') @@ -67,7 +77,7 @@ class TestHoneywell(unittest.TestCase): config = { CONF_USERNAME: 'user', CONF_PASSWORD: 'pass', - 'region': 'us', + honeywell.CONF_REGION: 'us', } mock_sc.side_effect = somecomfort.AuthError @@ -88,7 +98,7 @@ class TestHoneywell(unittest.TestCase): config = { CONF_USERNAME: 'user', CONF_PASSWORD: 'pass', - 'region': 'us', + honeywell.CONF_REGION: 'us', 'location': loc, 'thermostat': dev, } @@ -152,12 +162,12 @@ class TestHoneywell(unittest.TestCase): @mock.patch('homeassistant.components.climate.honeywell.' 'RoundThermostat') def test_eu_setup_full_config(self, mock_round, mock_evo): - """Test the EU setup wwith complete configuration.""" + """Test the EU setup with complete configuration.""" config = { CONF_USERNAME: 'user', CONF_PASSWORD: 'pass', - honeywell.CONF_AWAY_TEMP: 20, - 'region': 'eu', + honeywell.CONF_AWAY_TEMPERATURE: 20.0, + honeywell.CONF_REGION: 'eu', } mock_evo.return_value.temperatures.return_value = [ {'id': 'foo'}, {'id': 'bar'}] @@ -168,8 +178,8 @@ class TestHoneywell(unittest.TestCase): mock_evo.return_value.temperatures.assert_called_once_with( force_refresh=True) mock_round.assert_has_calls([ - mock.call(mock_evo.return_value, 'foo', True, 20), - mock.call(mock_evo.return_value, 'bar', False, 20), + mock.call(mock_evo.return_value, 'foo', True, 20.0), + mock.call(mock_evo.return_value, 'bar', False, 20.0), ]) self.assertEqual(2, add_devices.call_count) @@ -181,17 +191,20 @@ class TestHoneywell(unittest.TestCase): config = { CONF_USERNAME: 'user', CONF_PASSWORD: 'pass', - 'region': 'eu', + honeywell.CONF_REGION: 'eu', } + mock_evo.return_value.temperatures.return_value = [ {'id': 'foo'}, {'id': 'bar'}] + config[honeywell.CONF_AWAY_TEMPERATURE] = \ + honeywell.DEFAULT_AWAY_TEMPERATURE + hass = mock.MagicMock() add_devices = mock.MagicMock() self.assertTrue(honeywell.setup_platform(hass, config, add_devices)) - default = honeywell.DEFAULT_AWAY_TEMP mock_round.assert_has_calls([ - mock.call(mock_evo.return_value, 'foo', True, default), - mock.call(mock_evo.return_value, 'bar', False, default), + mock.call(mock_evo.return_value, 'foo', True, 16), + mock.call(mock_evo.return_value, 'bar', False, 16), ]) @mock.patch('evohomeclient.EvohomeClient') @@ -202,10 +215,12 @@ class TestHoneywell(unittest.TestCase): config = { CONF_USERNAME: 'user', CONF_PASSWORD: 'pass', - honeywell.CONF_AWAY_TEMP: 'ponies', - 'region': 'eu', + honeywell.CONF_AWAY_TEMPERATURE: 'ponies', + honeywell.CONF_REGION: 'eu', } - self.assertFalse(honeywell.setup_platform(None, config, None)) + + with self.assertRaises(vol.Invalid): + honeywell.PLATFORM_SCHEMA(config) @mock.patch('evohomeclient.EvohomeClient') @mock.patch('homeassistant.components.climate.honeywell.' @@ -215,8 +230,8 @@ class TestHoneywell(unittest.TestCase): config = { CONF_USERNAME: 'user', CONF_PASSWORD: 'pass', - honeywell.CONF_AWAY_TEMP: 20, - 'region': 'eu', + honeywell.CONF_AWAY_TEMPERATURE: 20, + honeywell.CONF_REGION: 'eu', } mock_evo.return_value.temperatures.side_effect = socket.error add_devices = mock.MagicMock() @@ -356,9 +371,9 @@ class TestHoneywellUS(unittest.TestCase): def test_attributes(self): """Test the attributes.""" expected = { - 'fan': 'running', - 'fanmode': 'auto', - 'system_mode': 'heat', + honeywell.ATTR_FAN: 'running', + honeywell.ATTR_FANMODE: 'auto', + honeywell.ATTR_SYSTEM_MODE: 'heat', } self.assertEqual(expected, self.honeywell.device_state_attributes) expected['fan'] = 'idle' @@ -370,8 +385,8 @@ class TestHoneywellUS(unittest.TestCase): self.device.fan_running = False self.device.fan_mode = None expected = { - 'fan': 'idle', - 'fanmode': None, - 'system_mode': 'heat', + honeywell.ATTR_FAN: 'idle', + honeywell.ATTR_FANMODE: None, + honeywell.ATTR_SYSTEM_MODE: 'heat', } self.assertEqual(expected, self.honeywell.device_state_attributes) From f341974b8b6e0dd416a58008902ddc2cfa32f1c9 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 09:22:08 +0200 Subject: [PATCH 005/162] Migrate to voluptuous (#3290) --- homeassistant/components/switch/netio.py | 100 +++++++++++++---------- 1 file changed, 59 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/switch/netio.py b/homeassistant/components/switch/netio.py index 7d30990e823..03a3d311f3c 100644 --- a/homeassistant/components/switch/netio.py +++ b/homeassistant/components/switch/netio.py @@ -7,59 +7,75 @@ https://home-assistant.io/components/switch.netio/ import logging from collections import namedtuple from datetime import timedelta + +import voluptuous as vol + from homeassistant import util from homeassistant.components.http import HomeAssistantView -from homeassistant.const import CONF_HOST, CONF_PORT, CONF_USERNAME, \ - CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP, STATE_ON -from homeassistant.helpers import validate_config -from homeassistant.components.switch import SwitchDevice +from homeassistant.const import ( + CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, + EVENT_HOMEASSISTANT_STOP, STATE_ON) +from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['pynetio==0.1.6'] _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['http'] -REQUIREMENTS = ['pynetio==0.1.6'] -DEFAULT_USERNAME = 'admin' +ATTR_CURRENT_POWER_MWH = 'current_power_mwh' +ATTR_CURRENT_POWER_W = 'current_power_w' +ATTR_START_DATE = 'start_date' +ATTR_TODAY_MWH = 'today_mwh' +ATTR_TOTAL_CONSUMPTION_KWH = 'total_energy_kwh' + +CONF_OUTLETS = 'outlets' + DEFAULT_PORT = 1234 -URL_API_NETIO_EP = "/api/netio/" - -CONF_OUTLETS = "outlets" -REQ_CONF = [CONF_HOST, CONF_OUTLETS] -ATTR_TODAY_MWH = "today_mwh" -ATTR_TOTAL_CONSUMPTION_KWH = "total_energy_kwh" -ATTR_CURRENT_POWER_MWH = "current_power_mwh" -ATTR_CURRENT_POWER_W = "current_power_w" - +DEFAULT_USERNAME = 'admin' +DEPENDENCIES = ['http'] Device = namedtuple('device', ['netio', 'entities']) DEVICES = {} -ATTR_START_DATE = 'start_date' + MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) +REQ_CONF = [CONF_HOST, CONF_OUTLETS] -def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Configure the netio platform.""" +URL_API_NETIO_EP = '/api/netio/' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Required(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_OUTLETS): {cv.string: cv.string}, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Configure the Netio platform.""" from pynetio import Netio - if validate_config({"conf": config}, {"conf": [CONF_OUTLETS, - CONF_HOST]}, _LOGGER): - if len(DEVICES) == 0: - hass.wsgi.register_view(NetioApiView) + host = config.get(CONF_HOST) + username = config.get(CONF_USERNAME) + password = config.get(CONF_PASSWORD) + port = config.get(CONF_PORT) - dev = Netio(config[CONF_HOST], - config.get(CONF_PORT, DEFAULT_PORT), - config.get(CONF_USERNAME, DEFAULT_USERNAME), - config.get(CONF_PASSWORD, DEFAULT_USERNAME)) + if len(DEVICES) == 0: + hass.wsgi.register_view(NetioApiView) - DEVICES[config[CONF_HOST]] = Device(dev, []) + dev = Netio(host, port, username, password) - # Throttle the update for all NetioSwitches of one Netio - dev.update = util.Throttle(MIN_TIME_BETWEEN_SCANS)(dev.update) + DEVICES[host] = Device(dev, []) - for key in config[CONF_OUTLETS]: - switch = NetioSwitch(DEVICES[config[CONF_HOST]].netio, key, - config[CONF_OUTLETS][key]) - DEVICES[config[CONF_HOST]].entities.append(switch) + # Throttle the update for all NetioSwitches of one Netio + dev.update = util.Throttle(MIN_TIME_BETWEEN_SCANS)(dev.update) - add_devices_callback(DEVICES[config[CONF_HOST]].entities) + for key in config[CONF_OUTLETS]: + switch = NetioSwitch( + DEVICES[host].netio, key, config[CONF_OUTLETS][key]) + DEVICES[host].entities.append(switch) + + add_devices(DEVICES[host].entities) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, dispose) return True @@ -75,7 +91,7 @@ class NetioApiView(HomeAssistantView): """WSGI handler class.""" url = URL_API_NETIO_EP - name = "api:netio" + name = 'api:netio' def get(self, request, host): """Request handler.""" @@ -135,7 +151,7 @@ class NetioSwitch(SwitchDevice): def _set(self, value): val = list('uuuu') - val[self.outlet - 1] = "1" if value else "0" + val[self.outlet - 1] = '1' if value else '0' self.netio.get('port list %s' % ''.join(val)) self.netio.states[self.outlet - 1] = value self.update_ha_state() @@ -146,15 +162,17 @@ class NetioSwitch(SwitchDevice): return self.netio.states[self.outlet - 1] def update(self): - """Called by HA.""" + """Called by Home Assistant.""" self.netio.update() @property def state_attributes(self): """Return optional state attributes.""" - return {ATTR_CURRENT_POWER_W: self.current_power_w, - ATTR_TOTAL_CONSUMPTION_KWH: self.cumulated_consumption_kwh, - ATTR_START_DATE: self.start_date.split('|')[0]} + return { + ATTR_CURRENT_POWER_W: self.current_power_w, + ATTR_TOTAL_CONSUMPTION_KWH: self.cumulated_consumption_kwh, + ATTR_START_DATE: self.start_date.split('|')[0] + } @property def current_power_w(self): From ac9151af541a34ad7b939aa5c34eb71ef585ce2e Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 09:22:49 +0200 Subject: [PATCH 006/162] Migrate to voluptuous (#3292) --- homeassistant/components/tellduslive.py | 114 ++++++++++++------------ 1 file changed, 56 insertions(+), 58 deletions(-) diff --git a/homeassistant/components/tellduslive.py b/homeassistant/components/tellduslive.py index ebc62174623..3f9e67cf2bc 100644 --- a/homeassistant/components/tellduslive.py +++ b/homeassistant/components/tellduslive.py @@ -7,31 +7,62 @@ https://home-assistant.io/components/tellduslive/ import logging from datetime import timedelta -from homeassistant.helpers import validate_config, discovery -from homeassistant.util import Throttle +import voluptuous as vol -DOMAIN = "tellduslive" +from homeassistant.helpers import discovery +from homeassistant.util import Throttle +import homeassistant.helpers.config_validation as cv + +DOMAIN = 'tellduslive' REQUIREMENTS = ['tellive-py==0.5.2'] _LOGGER = logging.getLogger(__name__) -CONF_PUBLIC_KEY = "public_key" -CONF_PRIVATE_KEY = "private_key" -CONF_TOKEN = "token" -CONF_TOKEN_SECRET = "token_secret" +CONF_PUBLIC_KEY = 'public_key' +CONF_PRIVATE_KEY = 'private_key' +CONF_TOKEN = 'token' +CONF_TOKEN_SECRET = 'token_secret' MIN_TIME_BETWEEN_SWITCH_UPDATES = timedelta(minutes=1) MIN_TIME_BETWEEN_SENSOR_UPDATES = timedelta(minutes=5) NETWORK = None +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_PUBLIC_KEY): cv.string, + vol.Required(CONF_PRIVATE_KEY): cv.string, + vol.Required(CONF_TOKEN): cv.string, + vol.Required(CONF_TOKEN_SECRET): cv.string, + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Setup the Telldus Live component.""" + # fixme: aquire app key and provide authentication using username+password + + global NETWORK + NETWORK = TelldusLiveData(hass, config) + + if not NETWORK.validate_session(): + _LOGGER.error( + "Authentication Error: " + "Please make sure you have configured your keys " + "that can be aquired from https://api.telldus.com/keys/index") + return False + + NETWORK.discover() + + return True + @Throttle(MIN_TIME_BETWEEN_SWITCH_UPDATES) def request_switches(): """Make request to online service.""" _LOGGER.debug("Updating switches from Telldus Live") - switches = NETWORK.request("devices/list")["device"] + switches = NETWORK.request('devices/list')['device'] # Filter out any group of switches. switches = {switch["id"]: switch for switch in switches if switch["type"] == "device"} @@ -42,11 +73,11 @@ def request_switches(): def request_sensors(): """Make request to online service.""" _LOGGER.debug("Updating sensors from Telldus Live") - units = NETWORK.request("sensors/list")["sensor"] + units = NETWORK.request('sensors/list')['sensor'] # One unit can contain many sensors. - sensors = {unit["id"]+sensor["name"]: dict(unit, data=sensor) + sensors = {unit['id']+sensor['name']: dict(unit, data=sensor) for unit in units - for sensor in unit["data"]} + for sensor in unit['data']} return sensors @@ -68,10 +99,9 @@ class TelldusLiveData(object): self._hass = hass self._config = config - self._client = LiveClient(public_key=public_key, - private_key=private_key, - access_token=token, - access_secret=token_secret) + self._client = LiveClient( + public_key=public_key, private_key=private_key, access_token=token, + access_secret=token_secret) def validate_session(self): """Make a dummy request to see if the session is valid.""" @@ -112,9 +142,9 @@ class TelldusLiveData(object): # | const.TELLSTICK_STOP default_params = {'supportedMethods': supported_methods, - "includeValues": 1, - "includeScale": 1, - "includeIgnored": 0} + 'includeValues': 1, + 'includeScale': 1, + 'includeIgnored': 0} params.update(default_params) # room for improvement: the telllive library doesn't seem to @@ -149,15 +179,13 @@ class TelldusLiveData(object): def update_sensors(self): """Update local list of sensors.""" - self._sensors = self.update_devices(self._sensors, - request_sensors(), - "sensor") + self._sensors = self.update_devices( + self._sensors, request_sensors(), 'sensor') def update_switches(self): """Update local list of switches.""" - self._switches = self.update_devices(self._switches, - request_switches(), - "switch") + self._switches = self.update_devices( + self._switches, request_switches(), 'switch') def _check_request(self, what, **params): """Make request, check result if successful.""" @@ -174,42 +202,12 @@ class TelldusLiveData(object): def turn_switch_on(self, switch_id): """Turn switch off.""" - if self._check_request("device/turnOn", id=switch_id): + if self._check_request('device/turnOn', id=switch_id): from tellive.live import const - self.get_switch(switch_id)["state"] = const.TELLSTICK_TURNON + self.get_switch(switch_id)['state'] = const.TELLSTICK_TURNON def turn_switch_off(self, switch_id): """Turn switch on.""" - if self._check_request("device/turnOff", id=switch_id): + if self._check_request('device/turnOff', id=switch_id): from tellive.live import const - self.get_switch(switch_id)["state"] = const.TELLSTICK_TURNOFF - - -def setup(hass, config): - """Setup the Telldus Live component.""" - # fixme: aquire app key and provide authentication using username+password - if not validate_config(config, - {DOMAIN: [CONF_PUBLIC_KEY, - CONF_PRIVATE_KEY, - CONF_TOKEN, - CONF_TOKEN_SECRET]}, - _LOGGER): - _LOGGER.error( - "Configuration Error: " - "Please make sure you have configured your keys " - "that can be aquired from https://api.telldus.com/keys/index") - return False - - global NETWORK - NETWORK = TelldusLiveData(hass, config) - - if not NETWORK.validate_session(): - _LOGGER.error( - "Authentication Error: " - "Please make sure you have configured your keys " - "that can be aquired from https://api.telldus.com/keys/index") - return False - - NETWORK.discover() - - return True + self.get_switch(switch_id)['state'] = const.TELLSTICK_TURNOFF From cce3e284d735ad2b4c71c4100bf7ce924cbf0c7f Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 09:23:33 +0200 Subject: [PATCH 007/162] Use voluptuous for Neurio (#3289) * Migrate to voluptuous * Migrate to voluptuous --- .../components/sensor/neurio_energy.py | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/sensor/neurio_energy.py b/homeassistant/components/sensor/neurio_energy.py index 77e66e71d1c..c8a53f819e8 100644 --- a/homeassistant/components/sensor/neurio_energy.py +++ b/homeassistant/components/sensor/neurio_energy.py @@ -1,5 +1,5 @@ """ -Support for monitoring an neurio hub. +Support for monitoring an Neurio hub. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.neurio_energy/ @@ -7,26 +7,39 @@ https://home-assistant.io/components/sensor.neurio_energy/ import logging import requests.exceptions +import voluptuous as vol +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import (CONF_API_KEY, CONF_NAME) from homeassistant.helpers.entity import Entity +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['neurio==0.2.10'] _LOGGER = logging.getLogger(__name__) +CONF_API_SECRET = 'api_secret' +CONF_SENSOR_ID = 'sensor_id' + +DEFAULT_NAME = 'Energy Usage' + ICON = 'mdi:flash' +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_API_SECRET): cv.string, + vol.Optional(CONF_SENSOR_ID): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Neurio sensor.""" - api_key = config.get("api_key") - api_secret = config.get("api_secret") - sensor_id = config.get("sensor_id") - if not api_key and not api_secret: - _LOGGER.error( - "Configuration Error" - "Please make sure you have configured your api key and api secret") - return None + name = config.get(CONF_NAME) + api_key = config.get(CONF_API_KEY) + api_secret = config.get(CONF_API_SECRET) + sensor_id = config.get(CONF_SENSOR_ID) + if not sensor_id: import neurio neurio_tp = neurio.TokenProvider(key=api_key, secret=api_secret) @@ -35,9 +48,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.warning('Sensor ID auto-detected, set api_sensor_id: "%s"', user_info["locations"][0]["sensors"][0]["sensorId"]) sensor_id = user_info["locations"][0]["sensors"][0]["sensorId"] - dev = [] - dev.append(NeurioEnergy(api_key, api_secret, sensor_id)) - add_devices(dev) + + add_devices([NeurioEnergy(api_key, api_secret, name, sensor_id)]) # pylint: disable=too-many-instance-attributes @@ -45,14 +57,14 @@ class NeurioEnergy(Entity): """Implementation of an Neurio energy.""" # pylint: disable=too-many-arguments - def __init__(self, api_key, api_secret, sensor_id): + def __init__(self, api_key, api_secret, name, sensor_id): """Initialize the sensor.""" - self._name = "Energy Usage" + self._name = name self.api_key = api_key self.api_secret = api_secret self.sensor_id = sensor_id self._state = None - self._unit_of_measurement = "W" + self._unit_of_measurement = 'W' @property def name(self): @@ -78,8 +90,8 @@ class NeurioEnergy(Entity): """Get the Neurio monitor data from the web service.""" import neurio try: - neurio_tp = neurio.TokenProvider(key=self.api_key, - secret=self.api_secret) + neurio_tp = neurio.TokenProvider( + key=self.api_key, secret=self.api_secret) neurio_client = neurio.Client(token_provider=neurio_tp) sample = neurio_client.get_samples_live_last( sensor_id=self.sensor_id) From d48ed41122b6ca88af1431e39ac3d10ca24967a2 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 09:24:07 +0200 Subject: [PATCH 008/162] Use constants (#3284) --- .../components/binary_sensor/arest.py | 9 +++------ homeassistant/components/sensor/arest.py | 7 ++----- homeassistant/components/switch/arest.py | 20 +++++++++---------- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/binary_sensor/arest.py b/homeassistant/components/binary_sensor/arest.py index 899dd44a42b..56362611514 100644 --- a/homeassistant/components/binary_sensor/arest.py +++ b/homeassistant/components/binary_sensor/arest.py @@ -9,18 +9,15 @@ from datetime import timedelta import requests -from homeassistant.components.binary_sensor import (BinarySensorDevice, - SENSOR_CLASSES) +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, SENSOR_CLASSES) +from homeassistant.const import CONF_RESOURCE, CONF_PIN from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -# Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) -CONF_RESOURCE = 'resource' -CONF_PIN = 'pin' - def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the aREST binary sensor.""" diff --git a/homeassistant/components/sensor/arest.py b/homeassistant/components/sensor/arest.py index c563380228f..782204d9d9a 100644 --- a/homeassistant/components/sensor/arest.py +++ b/homeassistant/components/sensor/arest.py @@ -10,7 +10,8 @@ from datetime import timedelta import requests from homeassistant.const import ( - ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, DEVICE_DEFAULT_NAME) + ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, DEVICE_DEFAULT_NAME, + CONF_RESOURCE, CONF_MONITORED_VARIABLES) from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity from homeassistant.helpers import template @@ -18,12 +19,8 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -# Return cached results if last scan was less then this time ago. MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) -CONF_RESOURCE = 'resource' -CONF_MONITORED_VARIABLES = 'monitored_variables' - def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the aREST sensor.""" diff --git a/homeassistant/components/switch/arest.py b/homeassistant/components/switch/arest.py index 08138db9e70..18fabd3cb74 100644 --- a/homeassistant/components/switch/arest.py +++ b/homeassistant/components/switch/arest.py @@ -10,14 +10,15 @@ import logging import requests from homeassistant.components.switch import SwitchDevice -from homeassistant.const import DEVICE_DEFAULT_NAME +from homeassistant.const import ( + DEVICE_DEFAULT_NAME, CONF_NAME, CONF_RESOURCE) _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the aREST switches.""" - resource = config.get('resource', None) + resource = config.get(CONF_RESOURCE, None) try: response = requests.get(resource, timeout=10) @@ -34,18 +35,15 @@ def setup_platform(hass, config, add_devices, discovery_info=None): dev = [] pins = config.get('pins', {}) for pinnum, pin in pins.items(): - dev.append(ArestSwitchPin(resource, - config.get('name', response.json()['name']), - pin.get('name'), - pinnum)) + dev.append(ArestSwitchPin( + resource, config.get(CONF_NAME, response.json()['name']), + pin.get('name'), pinnum)) functions = config.get('functions', {}) for funcname, func in functions.items(): - dev.append(ArestSwitchFunction(resource, - config.get('name', - response.json()['name']), - func.get('name'), - funcname)) + dev.append(ArestSwitchFunction( + resource, config.get(CONF_NAME, response.json()['name']), + func.get('name'), funcname)) add_devices(dev) From f6bc63092cb2a75c80b442fa62f0a896cc3da5a4 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 09:24:25 +0200 Subject: [PATCH 009/162] Migrate to voluptuous (#3281) --- homeassistant/components/light/hyperion.py | 63 ++++++++++++++-------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/light/hyperion.py b/homeassistant/components/light/hyperion.py index 139edd9188e..385cc43717f 100644 --- a/homeassistant/components/light/hyperion.py +++ b/homeassistant/components/light/hyperion.py @@ -8,24 +8,41 @@ import json import logging import socket -from homeassistant.components.light import (ATTR_RGB_COLOR, SUPPORT_RGB_COLOR, - Light) -from homeassistant.const import CONF_HOST +import voluptuous as vol + +from homeassistant.components.light import ( + ATTR_RGB_COLOR, SUPPORT_RGB_COLOR, Light, PLATFORM_SCHEMA) +from homeassistant.const import (CONF_HOST, CONF_PORT, CONF_NAME) +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = [] + +CONF_DEFAULT_COLOR = 'default_color' + +DEFAULT_COLOR = [255, 255, 255] +DEFAULT_NAME = 'Hyperion' +DEFAULT_PORT = 19444 SUPPORT_HYPERION = SUPPORT_RGB_COLOR +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_DEFAULT_COLOR, default=DEFAULT_COLOR): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): + +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup a Hyperion server remote.""" - host = config.get(CONF_HOST, None) - port = config.get("port", 19444) - default_color = config.get("default_color", [255, 255, 255]) - device = Hyperion(config.get('name', host), host, port, default_color) + host = config.get(CONF_HOST) + port = config.get(CONF_PORT) + default_color = config.get(CONF_DEFAULT_COLOR) + + device = Hyperion(config.get(CONF_NAME), host, port, default_color) + if device.setup(): - add_devices_callback([device]) + add_devices([device]) return True return False @@ -68,34 +85,34 @@ class Hyperion(Light): else: self._rgb_color = self._default_color - self.json_request({"command": "color", "priority": 128, - "color": self._rgb_color}) + self.json_request( + {'command': 'color', 'priority': 128, 'color': self._rgb_color}) def turn_off(self, **kwargs): """Disconnect all remotes.""" - self.json_request({"command": "clearall"}) + self.json_request({'command': 'clearall'}) self._rgb_color = [0, 0, 0] def update(self): """Get the remote's active color.""" - response = self.json_request({"command": "serverinfo"}) + response = self.json_request({'command': 'serverinfo'}) if response: # workaround for outdated Hyperion - if "activeLedColor" not in response["info"]: + if 'activeLedColor' not in response['info']: self._rgb_color = self._default_color return - if response["info"]["activeLedColor"] == []: + if response['info']['activeLedColor'] == []: self._rgb_color = [0, 0, 0] else: self._rgb_color =\ - response["info"]["activeLedColor"][0]["RGB Value"] + response['info']['activeLedColor'][0]['RGB Value'] def setup(self): """Get the hostname of the remote.""" - response = self.json_request({"command": "serverinfo"}) + response = self.json_request({'command': 'serverinfo'}) if response: - self._name = response["info"]["hostname"] + self._name = response['info']['hostname'] return True return False @@ -110,7 +127,7 @@ class Hyperion(Light): sock.close() return False - sock.send(bytearray(json.dumps(request) + "\n", "utf-8")) + sock.send(bytearray(json.dumps(request) + '\n', 'utf-8')) try: buf = sock.recv(4096) except socket.timeout: @@ -121,8 +138,8 @@ class Hyperion(Light): # Read until a newline or timeout buffering = True while buffering: - if "\n" in str(buf, "utf-8"): - response = str(buf, "utf-8").split("\n")[0] + if '\n' in str(buf, 'utf-8'): + response = str(buf, 'utf-8').split('\n')[0] buffering = False else: try: @@ -131,7 +148,7 @@ class Hyperion(Light): more = None if not more: buffering = False - response = str(buf, "utf-8") + response = str(buf, 'utf-8') else: buf += more From aed1348411e2505cf83d5acb9c313bbb42b8c4cf Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 09:25:19 +0200 Subject: [PATCH 010/162] Use constants and update ordering (#3274) --- homeassistant/components/light/mqtt.py | 94 +++++++++++---------- homeassistant/components/light/mqtt_json.py | 84 +++++++++--------- 2 files changed, 90 insertions(+), 88 deletions(-) diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py index ed8603a0ae8..d96f11ec47e 100644 --- a/homeassistant/components/light/mqtt.py +++ b/homeassistant/components/light/mqtt.py @@ -13,7 +13,9 @@ import homeassistant.components.mqtt as mqtt from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_RGB_COLOR, Light) -from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE +from homeassistant.const import ( + CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF, + CONF_PAYLOAD_ON, CONF_STATE, CONF_BRIGHTNESS, CONF_RGB) from homeassistant.components.mqtt import ( CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) import homeassistant.helpers.config_validation as cv @@ -30,8 +32,6 @@ CONF_BRIGHTNESS_VALUE_TEMPLATE = 'brightness_value_template' CONF_RGB_STATE_TOPIC = 'rgb_state_topic' CONF_RGB_COMMAND_TOPIC = 'rgb_command_topic' CONF_RGB_VALUE_TEMPLATE = 'rgb_value_template' -CONF_PAYLOAD_ON = 'payload_on' -CONF_PAYLOAD_OFF = 'payload_off' CONF_BRIGHTNESS_SCALE = 'brightness_scale' DEFAULT_NAME = 'MQTT Light' @@ -57,13 +57,13 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ }) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Add MQTT Light.""" config.setdefault(CONF_STATE_VALUE_TEMPLATE, config.get(CONF_VALUE_TEMPLATE)) - add_devices_callback([MqttLight( + add_devices([MqttLight( hass, - config[CONF_NAME], + config.get(CONF_NAME), { key: config.get(key) for key in ( CONF_STATE_TOPIC, @@ -75,18 +75,18 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): ) }, { - 'state': config.get(CONF_STATE_VALUE_TEMPLATE), - 'brightness': config.get(CONF_BRIGHTNESS_VALUE_TEMPLATE), - 'rgb': config.get(CONF_RGB_VALUE_TEMPLATE) + CONF_STATE: config.get(CONF_STATE_VALUE_TEMPLATE), + CONF_BRIGHTNESS: config.get(CONF_BRIGHTNESS_VALUE_TEMPLATE), + CONF_RGB: config.get(CONF_RGB_VALUE_TEMPLATE) }, - config[CONF_QOS], - config[CONF_RETAIN], + config.get(CONF_QOS), + config.get(CONF_RETAIN), { - 'on': config[CONF_PAYLOAD_ON], - 'off': config[CONF_PAYLOAD_OFF], + 'on': config.get(CONF_PAYLOAD_ON), + 'off': config.get(CONF_PAYLOAD_OFF), }, - config[CONF_OPTIMISTIC], - config[CONF_BRIGHTNESS_SCALE], + config.get(CONF_OPTIMISTIC), + config.get(CONF_BRIGHTNESS_SCALE), )]) @@ -103,17 +103,19 @@ class MqttLight(Light): self._qos = qos self._retain = retain self._payload = payload - self._optimistic = optimistic or topic["state_topic"] is None - self._optimistic_rgb = optimistic or topic["rgb_state_topic"] is None - self._optimistic_brightness = (optimistic or - topic["brightness_state_topic"] is None) + self._optimistic = optimistic or topic[CONF_STATE_TOPIC] is None + self._optimistic_rgb = \ + optimistic or topic[CONF_RGB_STATE_TOPIC] is None + self._optimistic_brightness = ( + optimistic or topic[CONF_BRIGHTNESS_STATE_TOPIC] is None) self._brightness_scale = brightness_scale self._state = False self._supported_features = 0 self._supported_features |= ( - topic['rgb_state_topic'] is not None and SUPPORT_RGB_COLOR) + topic[CONF_RGB_STATE_TOPIC] is not None and SUPPORT_RGB_COLOR) self._supported_features |= ( - topic['brightness_state_topic'] is not None and SUPPORT_BRIGHTNESS) + topic[CONF_BRIGHTNESS_STATE_TOPIC] is not None + and SUPPORT_BRIGHTNESS) templates = {key: ((lambda value: value) if tpl is None else partial(render_with_possible_json_value, hass, tpl)) @@ -121,30 +123,31 @@ class MqttLight(Light): def state_received(topic, payload, qos): """A new MQTT message has been received.""" - payload = templates['state'](payload) - if payload == self._payload["on"]: + payload = templates[CONF_STATE](payload) + if payload == self._payload['on']: self._state = True - elif payload == self._payload["off"]: + elif payload == self._payload['off']: self._state = False self.update_ha_state() - if self._topic["state_topic"] is not None: - mqtt.subscribe(self._hass, self._topic["state_topic"], + if self._topic[CONF_STATE_TOPIC] is not None: + mqtt.subscribe(self._hass, self._topic[CONF_STATE_TOPIC], state_received, self._qos) def brightness_received(topic, payload, qos): """A new MQTT message for the brightness has been received.""" - device_value = float(templates['brightness'](payload)) + device_value = float(templates[CONF_BRIGHTNESS](payload)) percent_bright = device_value / self._brightness_scale self._brightness = int(percent_bright * 255) self.update_ha_state() - if self._topic["brightness_state_topic"] is not None: - mqtt.subscribe(self._hass, self._topic["brightness_state_topic"], - brightness_received, self._qos) + if self._topic[CONF_BRIGHTNESS_STATE_TOPIC] is not None: + mqtt.subscribe( + self._hass, self._topic[CONF_BRIGHTNESS_STATE_TOPIC], + brightness_received, self._qos) self._brightness = 255 - elif self._topic["brightness_command_topic"] is not None: + elif self._topic[CONF_BRIGHTNESS_COMMAND_TOPIC] is not None: self._brightness = 255 else: self._brightness = None @@ -152,14 +155,14 @@ class MqttLight(Light): def rgb_received(topic, payload, qos): """A new MQTT message has been received.""" self._rgb = [int(val) for val in - templates['rgb'](payload).split(',')] + templates[CONF_RGB](payload).split(',')] self.update_ha_state() - if self._topic["rgb_state_topic"] is not None: - mqtt.subscribe(self._hass, self._topic["rgb_state_topic"], + if self._topic[CONF_RGB_STATE_TOPIC] is not None: + mqtt.subscribe(self._hass, self._topic[CONF_RGB_STATE_TOPIC], rgb_received, self._qos) self._rgb = [255, 255, 255] - if self._topic["rgb_command_topic"] is not None: + if self._topic[CONF_RGB_COMMAND_TOPIC] is not None: self._rgb = [255, 255, 255] else: self._rgb = None @@ -204,10 +207,10 @@ class MqttLight(Light): should_update = False if ATTR_RGB_COLOR in kwargs and \ - self._topic["rgb_command_topic"] is not None: + self._topic[CONF_RGB_COMMAND_TOPIC] is not None: - mqtt.publish(self._hass, self._topic["rgb_command_topic"], - "{},{},{}".format(*kwargs[ATTR_RGB_COLOR]), + mqtt.publish(self._hass, self._topic[CONF_RGB_COMMAND_TOPIC], + '{},{},{}'.format(*kwargs[ATTR_RGB_COLOR]), self._qos, self._retain) if self._optimistic_rgb: @@ -215,18 +218,19 @@ class MqttLight(Light): should_update = True if ATTR_BRIGHTNESS in kwargs and \ - self._topic["brightness_command_topic"] is not None: + self._topic[CONF_BRIGHTNESS_COMMAND_TOPIC] is not None: percent_bright = float(kwargs[ATTR_BRIGHTNESS]) / 255 device_brightness = int(percent_bright * self._brightness_scale) - mqtt.publish(self._hass, self._topic["brightness_command_topic"], - device_brightness, self._qos, self._retain) + mqtt.publish( + self._hass, self._topic[CONF_BRIGHTNESS_COMMAND_TOPIC], + device_brightness, self._qos, self._retain) if self._optimistic_brightness: self._brightness = kwargs[ATTR_BRIGHTNESS] should_update = True - mqtt.publish(self._hass, self._topic["command_topic"], - self._payload["on"], self._qos, self._retain) + mqtt.publish(self._hass, self._topic[CONF_COMMAND_TOPIC], + self._payload['on'], self._qos, self._retain) if self._optimistic: # Optimistically assume that switch has changed state. @@ -238,8 +242,8 @@ class MqttLight(Light): def turn_off(self, **kwargs): """Turn the device off.""" - mqtt.publish(self._hass, self._topic["command_topic"], - self._payload["off"], self._qos, self._retain) + mqtt.publish(self._hass, self._topic[CONF_COMMAND_TOPIC], + self._payload['off'], self._qos, self._retain) if self._optimistic: # Optimistically assume that switch has changed state. diff --git a/homeassistant/components/light/mqtt_json.py b/homeassistant/components/light/mqtt_json.py index abc3c53f37f..01fd03955fd 100755 --- a/homeassistant/components/light/mqtt_json.py +++ b/homeassistant/components/light/mqtt_json.py @@ -11,38 +11,36 @@ import voluptuous as vol import homeassistant.components.mqtt as mqtt from homeassistant.components.light import ( - ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_TRANSITION, + ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_TRANSITION, PLATFORM_SCHEMA, ATTR_FLASH, FLASH_LONG, FLASH_SHORT, SUPPORT_BRIGHTNESS, SUPPORT_FLASH, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, Light) -from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_PLATFORM +from homeassistant.const import ( + CONF_NAME, CONF_OPTIMISTIC, CONF_BRIGHTNESS, CONF_RGB) from homeassistant.components.mqtt import ( CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DOMAIN = "mqtt_json" +DOMAIN = 'mqtt_json' -DEPENDENCIES = ["mqtt"] +DEPENDENCIES = ['mqtt'] -DEFAULT_NAME = "MQTT JSON Light" +DEFAULT_NAME = 'MQTT JSON Light' DEFAULT_OPTIMISTIC = False DEFAULT_BRIGHTNESS = False DEFAULT_RGB = False DEFAULT_FLASH_TIME_SHORT = 2 DEFAULT_FLASH_TIME_LONG = 10 -CONF_BRIGHTNESS = "brightness" -CONF_RGB = "rgb" -CONF_FLASH_TIME_SHORT = "flash_time_short" -CONF_FLASH_TIME_LONG = "flash_time_long" +CONF_FLASH_TIME_SHORT = 'flash_time_short' +CONF_FLASH_TIME_LONG = 'flash_time_long' SUPPORT_MQTT_JSON = (SUPPORT_BRIGHTNESS | SUPPORT_FLASH | SUPPORT_RGB_COLOR | SUPPORT_TRANSITION) # Stealing some of these from the base MQTT configs. -PLATFORM_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): DOMAIN, +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_QOS, default=mqtt.DEFAULT_QOS): vol.All(vol.Coerce(int), vol.In([0, 1, 2])), vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, @@ -59,22 +57,22 @@ PLATFORM_SCHEMA = vol.Schema({ }) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup a MQTT JSON Light.""" - add_devices_callback([MqttJson( + add_devices([MqttJson( hass, - config[CONF_NAME], + config.get(CONF_NAME), { key: config.get(key) for key in ( CONF_STATE_TOPIC, CONF_COMMAND_TOPIC ) }, - config[CONF_QOS], - config[CONF_RETAIN], - config[CONF_OPTIMISTIC], - config[CONF_BRIGHTNESS], - config[CONF_RGB], + config.get(CONF_QOS), + config.get(CONF_RETAIN), + config.get(CONF_OPTIMISTIC), + config.get(CONF_BRIGHTNESS), + config.get(CONF_RGB), { key: config.get(key) for key in ( CONF_FLASH_TIME_SHORT, @@ -96,7 +94,7 @@ class MqttJson(Light): self._topic = topic self._qos = qos self._retain = retain - self._optimistic = optimistic or topic["state_topic"] is None + self._optimistic = optimistic or topic[CONF_STATE_TOPIC] is None self._state = False if brightness: self._brightness = 255 @@ -114,35 +112,35 @@ class MqttJson(Light): """A new MQTT message has been received.""" values = json.loads(payload) - if values["state"] == "ON": + if values['state'] == 'ON': self._state = True - elif values["state"] == "OFF": + elif values['state'] == 'OFF': self._state = False if self._rgb is not None: try: - red = int(values["color"]["r"]) - green = int(values["color"]["g"]) - blue = int(values["color"]["b"]) + red = int(values['color']['r']) + green = int(values['color']['g']) + blue = int(values['color']['b']) self._rgb = [red, green, blue] except KeyError: pass except ValueError: - _LOGGER.warning("Invalid color value received.") + _LOGGER.warning("Invalid color value received") if self._brightness is not None: try: - self._brightness = int(values["brightness"]) + self._brightness = int(values['brightness']) except KeyError: pass except ValueError: - _LOGGER.warning("Invalid brightness value received.") + _LOGGER.warning('Invalid brightness value received') self.update_ha_state() - if self._topic["state_topic"] is not None: - mqtt.subscribe(self._hass, self._topic["state_topic"], + if self._topic[CONF_STATE_TOPIC] is not None: + mqtt.subscribe(self._hass, self._topic[CONF_STATE_TOPIC], state_received, self._qos) @property @@ -179,13 +177,13 @@ class MqttJson(Light): """Turn the device on.""" should_update = False - message = {"state": "ON"} + message = {'state': 'ON'} if ATTR_RGB_COLOR in kwargs: - message["color"] = { - "r": kwargs[ATTR_RGB_COLOR][0], - "g": kwargs[ATTR_RGB_COLOR][1], - "b": kwargs[ATTR_RGB_COLOR][2] + message['color'] = { + 'r': kwargs[ATTR_RGB_COLOR][0], + 'g': kwargs[ATTR_RGB_COLOR][1], + 'b': kwargs[ATTR_RGB_COLOR][2] } if self._optimistic: @@ -196,21 +194,21 @@ class MqttJson(Light): flash = kwargs.get(ATTR_FLASH) if flash == FLASH_LONG: - message["flash"] = self._flash_times[CONF_FLASH_TIME_LONG] + message['flash'] = self._flash_times[CONF_FLASH_TIME_LONG] elif flash == FLASH_SHORT: - message["flash"] = self._flash_times[CONF_FLASH_TIME_SHORT] + message['flash'] = self._flash_times[CONF_FLASH_TIME_SHORT] if ATTR_TRANSITION in kwargs: - message["transition"] = kwargs[ATTR_TRANSITION] + message['transition'] = kwargs[ATTR_TRANSITION] if ATTR_BRIGHTNESS in kwargs: - message["brightness"] = int(kwargs[ATTR_BRIGHTNESS]) + message['brightness'] = int(kwargs[ATTR_BRIGHTNESS]) if self._optimistic: self._brightness = kwargs[ATTR_BRIGHTNESS] should_update = True - mqtt.publish(self._hass, self._topic["command_topic"], + mqtt.publish(self._hass, self._topic[CONF_COMMAND_TOPIC], json.dumps(message), self._qos, self._retain) if self._optimistic: @@ -223,12 +221,12 @@ class MqttJson(Light): def turn_off(self, **kwargs): """Turn the device off.""" - message = {"state": "OFF"} + message = {'state': 'OFF'} if ATTR_TRANSITION in kwargs: - message["transition"] = kwargs[ATTR_TRANSITION] + message['transition'] = kwargs[ATTR_TRANSITION] - mqtt.publish(self._hass, self._topic["command_topic"], + mqtt.publish(self._hass, self._topic[CONF_COMMAND_TOPIC], json.dumps(message), self._qos, self._retain) if self._optimistic: From cc99d266b7b7cacca9102bb4a0c153f3ce24d4d5 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 10:01:46 +0200 Subject: [PATCH 011/162] Use constants and update ordering (#3275) --- homeassistant/components/cover/mqtt.py | 39 ++++++------ homeassistant/components/fan/mqtt.py | 86 +++++++++++++------------- 2 files changed, 61 insertions(+), 64 deletions(-) diff --git a/homeassistant/components/cover/mqtt.py b/homeassistant/components/cover/mqtt.py index b47bcf124e1..3c1cf73eca0 100644 --- a/homeassistant/components/cover/mqtt.py +++ b/homeassistant/components/cover/mqtt.py @@ -28,10 +28,10 @@ CONF_PAYLOAD_STOP = 'payload_stop' CONF_STATE_OPEN = 'state_open' CONF_STATE_CLOSED = 'state_closed' -DEFAULT_NAME = "MQTT Cover" -DEFAULT_PAYLOAD_OPEN = "OPEN" -DEFAULT_PAYLOAD_CLOSE = "CLOSE" -DEFAULT_PAYLOAD_STOP = "STOP" +DEFAULT_NAME = 'MQTT Cover' +DEFAULT_PAYLOAD_OPEN = 'OPEN' +DEFAULT_PAYLOAD_CLOSE = 'CLOSE' +DEFAULT_PAYLOAD_STOP = 'STOP' DEFAULT_OPTIMISTIC = False DEFAULT_RETAIN = False @@ -44,25 +44,24 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, - }) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Add MQTT Cover.""" - add_devices_callback([MqttCover( +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the MQTT Cover.""" + add_devices([MqttCover( hass, - config[CONF_NAME], + config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), - config[CONF_COMMAND_TOPIC], - config[CONF_QOS], - config[CONF_RETAIN], - config[CONF_STATE_OPEN], - config[CONF_STATE_CLOSED], - config[CONF_PAYLOAD_OPEN], - config[CONF_PAYLOAD_CLOSE], - config[CONF_PAYLOAD_STOP], - config[CONF_OPTIMISTIC], + config.get(CONF_COMMAND_TOPIC), + config.get(CONF_QOS), + config.get(CONF_RETAIN), + config.get(CONF_STATE_OPEN), + config.get(CONF_STATE_CLOSED), + config.get(CONF_PAYLOAD_OPEN), + config.get(CONF_PAYLOAD_CLOSE), + config.get(CONF_PAYLOAD_STOP), + config.get(CONF_OPTIMISTIC), config.get(CONF_VALUE_TEMPLATE) )]) @@ -111,8 +110,8 @@ class MqttCover(CoverDevice): self.update_ha_state() else: _LOGGER.warning( - "Payload is not True or False or" - " integer(0-100) %s", payload) + "Payload is not True, False, or integer (0-100): %s", + payload) if self._state_topic is None: # Force into optimistic mode. self._optimistic = True diff --git a/homeassistant/components/fan/mqtt.py b/homeassistant/components/fan/mqtt.py index 9d824a715c2..09363fa099d 100644 --- a/homeassistant/components/fan/mqtt.py +++ b/homeassistant/components/fan/mqtt.py @@ -10,8 +10,9 @@ from functools import partial import voluptuous as vol import homeassistant.components.mqtt as mqtt -from homeassistant.const import (CONF_NAME, CONF_OPTIMISTIC, CONF_STATE, - STATE_ON, STATE_OFF) +from homeassistant.const import ( + CONF_NAME, CONF_OPTIMISTIC, CONF_STATE, STATE_ON, STATE_OFF, + CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON) from homeassistant.components.mqtt import ( CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN) import homeassistant.helpers.config_validation as cv @@ -23,33 +24,31 @@ from homeassistant.components.fan import (SPEED_LOW, SPEED_MED, SPEED_MEDIUM, _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ["mqtt"] +DEPENDENCIES = ['mqtt'] -CONF_STATE_VALUE_TEMPLATE = "state_value_template" -CONF_SPEED_STATE_TOPIC = "speed_state_topic" -CONF_SPEED_COMMAND_TOPIC = "speed_command_topic" -CONF_SPEED_VALUE_TEMPLATE = "speed_value_template" -CONF_OSCILLATION_STATE_TOPIC = "oscillation_state_topic" -CONF_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic" -CONF_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template" -CONF_PAYLOAD_ON = "payload_on" -CONF_PAYLOAD_OFF = "payload_off" -CONF_PAYLOAD_OSCILLATION_ON = "payload_oscillation_on" -CONF_PAYLOAD_OSCILLATION_OFF = "payload_oscillation_off" -CONF_PAYLOAD_LOW_SPEED = "payload_low_speed" -CONF_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed" -CONF_PAYLOAD_HIGH_SPEED = "payload_high_speed" -CONF_SPEED_LIST = "speeds" +CONF_STATE_VALUE_TEMPLATE = 'state_value_template' +CONF_SPEED_STATE_TOPIC = 'speed_state_topic' +CONF_SPEED_COMMAND_TOPIC = 'speed_command_topic' +CONF_SPEED_VALUE_TEMPLATE = 'speed_value_template' +CONF_OSCILLATION_STATE_TOPIC = 'oscillation_state_topic' +CONF_OSCILLATION_COMMAND_TOPIC = 'oscillation_command_topic' +CONF_OSCILLATION_VALUE_TEMPLATE = 'oscillation_value_template' +CONF_PAYLOAD_OSCILLATION_ON = 'payload_oscillation_on' +CONF_PAYLOAD_OSCILLATION_OFF = 'payload_oscillation_off' +CONF_PAYLOAD_LOW_SPEED = 'payload_low_speed' +CONF_PAYLOAD_MEDIUM_SPEED = 'payload_medium_speed' +CONF_PAYLOAD_HIGH_SPEED = 'payload_high_speed' +CONF_SPEED_LIST = 'speeds' -DEFAULT_NAME = "MQTT Fan" -DEFAULT_PAYLOAD_ON = "ON" -DEFAULT_PAYLOAD_OFF = "OFF" +DEFAULT_NAME = 'MQTT Fan' +DEFAULT_PAYLOAD_ON = 'ON' +DEFAULT_PAYLOAD_OFF = 'OFF' DEFAULT_OPTIMISTIC = False -OSCILLATE_ON_PAYLOAD = "oscillate_on" -OSCILLATE_OFF_PAYLOAD = "oscillate_off" +OSCILLATE_ON_PAYLOAD = 'oscillate_on' +OSCILLATE_OFF_PAYLOAD = 'oscillate_off' -OSCILLATION = "oscillation" +OSCILLATION = 'oscillation' PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -77,11 +76,11 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ # pylint: disable=unused-argument -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup MQTT fan platform.""" - add_devices_callback([MqttFan( + add_devices([MqttFan( hass, - config[CONF_NAME], + config.get(CONF_NAME), { key: config.get(key) for key in ( CONF_STATE_TOPIC, @@ -97,19 +96,19 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): ATTR_SPEED: config.get(CONF_SPEED_VALUE_TEMPLATE), OSCILLATION: config.get(CONF_OSCILLATION_VALUE_TEMPLATE) }, - config[CONF_QOS], - config[CONF_RETAIN], + config.get(CONF_QOS), + config.get(CONF_RETAIN), { - STATE_ON: config[CONF_PAYLOAD_ON], - STATE_OFF: config[CONF_PAYLOAD_OFF], - OSCILLATE_ON_PAYLOAD: config[CONF_PAYLOAD_OSCILLATION_ON], - OSCILLATE_OFF_PAYLOAD: config[CONF_PAYLOAD_OSCILLATION_OFF], - SPEED_LOW: config[CONF_PAYLOAD_LOW_SPEED], - SPEED_MEDIUM: config[CONF_PAYLOAD_MEDIUM_SPEED], - SPEED_HIGH: config[CONF_PAYLOAD_HIGH_SPEED], + STATE_ON: config.get(CONF_PAYLOAD_ON), + STATE_OFF: config.get(CONF_PAYLOAD_OFF), + OSCILLATE_ON_PAYLOAD: config.get(CONF_PAYLOAD_OSCILLATION_ON), + OSCILLATE_OFF_PAYLOAD: config.get(CONF_PAYLOAD_OSCILLATION_OFF), + SPEED_LOW: config.get(CONF_PAYLOAD_LOW_SPEED), + SPEED_MEDIUM: config.get(CONF_PAYLOAD_MEDIUM_SPEED), + SPEED_HIGH: config.get(CONF_PAYLOAD_HIGH_SPEED), }, - config[CONF_SPEED_LIST], - config[CONF_OPTIMISTIC], + config.get(CONF_SPEED_LIST), + config.get(CONF_OPTIMISTIC), )]) @@ -120,7 +119,7 @@ class MqttFan(FanEntity): # pylint: disable=too-many-arguments def __init__(self, hass, name, topic, templates, qos, retain, payload, speed_list, optimistic): - """Initialize MQTT fan.""" + """Initialize the MQTT fan.""" self._hass = hass self._name = name self._topic = topic @@ -129,11 +128,10 @@ class MqttFan(FanEntity): self._payload = payload self._speed_list = speed_list self._optimistic = optimistic or topic[CONF_STATE_TOPIC] is None - self._optimistic_oscillation = (optimistic or - topic[CONF_OSCILLATION_STATE_TOPIC] - is None) - self._optimistic_speed = (optimistic or - topic[CONF_SPEED_STATE_TOPIC] is None) + self._optimistic_oscillation = ( + optimistic or topic[CONF_OSCILLATION_STATE_TOPIC] is None) + self._optimistic_speed = ( + optimistic or topic[CONF_SPEED_STATE_TOPIC] is None) self._state = False self._supported_features = 0 self._supported_features |= (topic[CONF_OSCILLATION_STATE_TOPIC] From f5df5615bee83f9997f73868cfbc90c36fb8015b Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 10:04:07 +0200 Subject: [PATCH 012/162] Migrate to voluptuous (#3282) --- homeassistant/components/light/lifx.py | 83 ++++++++++++++------------ 1 file changed, 45 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/light/lifx.py b/homeassistant/components/light/lifx.py index 39038fb0356..ed0d55b92f4 100644 --- a/homeassistant/components/light/lifx.py +++ b/homeassistant/components/light/lifx.py @@ -7,34 +7,59 @@ https://home-assistant.io/components/light.lifx/ import colorsys import logging +import voluptuous as vol + from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, - SUPPORT_TRANSITION, Light) + SUPPORT_TRANSITION, Light, PLATFORM_SCHEMA) from homeassistant.helpers.event import track_time_change +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['liffylights==0.9.4'] -CONF_SERVER = "server" # server address configuration item -CONF_BROADCAST = "broadcast" # broadcast address configuration item -SHORT_MAX = 65535 # short int maximum -BYTE_MAX = 255 # byte maximum -TEMP_MIN = 2500 # lifx minimum temperature -TEMP_MAX = 9000 # lifx maximum temperature -TEMP_MIN_HASS = 154 # home assistant minimum temperature -TEMP_MAX_HASS = 500 # home assistant maximum temperature +BYTE_MAX = 255 + +CONF_BROADCAST = 'broadcast' +CONF_SERVER = 'server' + +SHORT_MAX = 65535 + +TEMP_MAX = 9000 +TEMP_MAX_HASS = 500 +TEMP_MIN = 2500 +TEMP_MIN_HASS = 154 SUPPORT_LIFX = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_RGB_COLOR | SUPPORT_TRANSITION) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_SERVER, default=None): cv.string, + vol.Optional(CONF_BROADCAST, default=None): cv.string, +}) -class LIFX(): + +# pylint: disable=unused-argument +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the LIFX platform.""" + server_addr = config.get(CONF_SERVER) + broadcast_addr = config.get(CONF_BROADCAST) + + lifx_library = LIFX(add_devices, server_addr, broadcast_addr) + + # Register our poll service + track_time_change(hass, lifx_library.poll, second=[10, 40]) + + lifx_library.probe() + + +class LIFX(object): """Representation of a LIFX light.""" - def __init__(self, add_devices_callback, - server_addr=None, broadcast_addr=None): + def __init__(self, add_devices_callback, server_addr=None, + broadcast_addr=None): """Initialize the light.""" import liffylights @@ -43,10 +68,7 @@ class LIFX(): self._add_devices_callback = add_devices_callback self._liffylights = liffylights.LiffyLights( - self.on_device, - self.on_power, - self.on_color, - server_addr, + self.on_device, self.on_power, self.on_color, server_addr, broadcast_addr) def find_bulb(self, ipaddr): @@ -66,8 +88,8 @@ class LIFX(): if bulb is None: _LOGGER.debug("new bulb %s %s %d %d %d %d %d", ipaddr, name, power, hue, sat, bri, kel) - bulb = LIFXLight(self._liffylights, ipaddr, name, - power, hue, sat, bri, kel) + bulb = LIFXLight( + self._liffylights, ipaddr, name, power, hue, sat, bri, kel) self._devices.append(bulb) self._add_devices_callback([bulb]) else: @@ -104,20 +126,6 @@ class LIFX(): self._liffylights.probe(address) -# pylint: disable=unused-argument -def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup the LIFX platform.""" - server_addr = config.get(CONF_SERVER, None) - broadcast_addr = config.get(CONF_BROADCAST, None) - - lifx_library = LIFX(add_devices_callback, server_addr, broadcast_addr) - - # Register our poll service - track_time_change(hass, lifx_library.poll, second=[10, 40]) - - lifx_library.probe() - - def convert_rgb_to_hsv(rgb): """Convert Home Assistant RGB values to HSV values.""" red, green, blue = [_ / BYTE_MAX for _ in rgb] @@ -134,11 +142,10 @@ class LIFXLight(Light): """Representation of a LIFX light.""" # pylint: disable=too-many-arguments - def __init__(self, liffy, ipaddr, name, power, hue, - saturation, brightness, kelvin): + def __init__(self, liffy, ipaddr, name, power, hue, saturation, brightness, + kelvin): """Initialize the light.""" - _LOGGER.debug("LIFXLight: %s %s", - ipaddr, name) + _LOGGER.debug("LIFXLight: %s %s", ipaddr, name) self._liffylights = liffy self._ip = ipaddr @@ -164,8 +171,8 @@ class LIFXLight(Light): @property def rgb_color(self): """Return the RGB value.""" - _LOGGER.debug("rgb_color: [%d %d %d]", - self._rgb[0], self._rgb[1], self._rgb[2]) + _LOGGER.debug( + "rgb_color: [%d %d %d]", self._rgb[0], self._rgb[1], self._rgb[2]) return self._rgb @property From 1b77b2c3a3c6de53b1be0a938f813863b1a873a8 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 11:19:10 +0200 Subject: [PATCH 013/162] Migrate to voluptuous (#3280) --- homeassistant/components/wink.py | 37 +++++++++++++++++++------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index 6d6e09b1918..fe57fa289f2 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -7,30 +7,38 @@ https://home-assistant.io/components/wink/ import logging import json -from homeassistant.helpers import validate_config, discovery +import voluptuous as vol + +from homeassistant.helpers import discovery from homeassistant.const import CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL from homeassistant.helpers.entity import Entity +import homeassistant.helpers.config_validation as cv -DOMAIN = "wink" REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] -SUBSCRIPTION_HANDLER = None +_LOGGER = logging.getLogger(__name__) + CHANNELS = [] +DOMAIN = 'wink' + +SUBSCRIPTION_HANDLER = None + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Optional(CONF_ACCESS_TOKEN): cv.string, + }), +}, extra=vol.ALLOW_EXTRA) + def setup(hass, config): """Setup the Wink component.""" - logger = logging.getLogger(__name__) - - if not validate_config(config, {DOMAIN: [CONF_ACCESS_TOKEN]}, logger): - return False - import pywink from pubnub import Pubnub pywink.set_bearer_token(config[DOMAIN][CONF_ACCESS_TOKEN]) global SUBSCRIPTION_HANDLER - SUBSCRIPTION_HANDLER = Pubnub("N/A", pywink.get_subscription_key(), - ssl_on=True) + SUBSCRIPTION_HANDLER = Pubnub( + 'N/A', pywink.get_subscription_key(), ssl_on=True) SUBSCRIPTION_HANDLER.set_heartbeat(120) # Load components for the devices in the Wink that we support @@ -51,7 +59,7 @@ def setup(hass, config): class WinkDevice(Entity): - """Represents a base Wink device.""" + """Representation a base Wink device.""" def __init__(self, wink): """Initialize the Wink device.""" @@ -59,7 +67,7 @@ class WinkDevice(Entity): self.wink = wink self._battery = self.wink.battery_level if self.wink.pubnub_channel in CHANNELS: - pubnub = Pubnub("N/A", self.wink.pubnub_key, ssl_on=True) + pubnub = Pubnub('N/A', self.wink.pubnub_key, ssl_on=True) pubnub.set_heartbeat(120) pubnub.subscribe(self.wink.pubnub_channel, self._pubnub_update, @@ -75,13 +83,12 @@ class WinkDevice(Entity): self.update_ha_state() def _pubnub_error(self, message): - logging.getLogger(__name__).error( - "Error on pubnub update for " + self.wink.name()) + _LOGGER.error("Error on pubnub update for " + self.wink.name()) @property def unique_id(self): """Return the ID of this Wink device.""" - return "{}.{}".format(self.__class__, self.wink.device_id()) + return '{}.{}'.format(self.__class__, self.wink.device_id()) @property def name(self): From d20b4c17a20356b558a675d3e3f180dc01cc6def Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 11:30:27 +0200 Subject: [PATCH 014/162] Migrate to voluptuous (#3277) --- homeassistant/components/climate/proliphix.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/climate/proliphix.py b/homeassistant/components/climate/proliphix.py index fa2230fba55..da5f5918d7c 100644 --- a/homeassistant/components/climate/proliphix.py +++ b/homeassistant/components/climate/proliphix.py @@ -4,13 +4,24 @@ Support for Proliphix NT10e Thermostats. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/climate.proliphix/ """ +import voluptuous as vol + from homeassistant.components.climate import ( - STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice) + STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA) from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['proliphix==0.3.1'] +ATTR_FAN = 'fan' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, +}) + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Proliphix thermostats.""" @@ -22,9 +33,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pdp = proliphix.PDP(host, username, password) - add_devices([ - ProliphixThermostat(pdp) - ]) + add_devices([ProliphixThermostat(pdp)]) # pylint: disable=abstract-method @@ -56,7 +65,7 @@ class ProliphixThermostat(ClimateDevice): def device_state_attributes(self): """Return the device specific state attributes.""" return { - "fan": self._pdp.fan_state + ATTR_FAN: self._pdp.fan_state } @property From ab826eef0daeca9e62b767e8f5bce688d7dfdcb1 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 11:38:43 +0200 Subject: [PATCH 015/162] Migrate to voluptuous (#3276) --- .../components/climate/radiotherm.py | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/climate/radiotherm.py b/homeassistant/components/climate/radiotherm.py index 90611ce20b2..e4d87fec7ec 100644 --- a/homeassistant/components/climate/radiotherm.py +++ b/homeassistant/components/climate/radiotherm.py @@ -8,15 +8,28 @@ import datetime import logging from urllib.error import URLError +import voluptuous as vol + from homeassistant.components.climate import ( STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, STATE_OFF, - ClimateDevice) + ClimateDevice, PLATFORM_SCHEMA) from homeassistant.const import CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['radiotherm==1.2'] -HOLD_TEMP = 'hold_temp' + _LOGGER = logging.getLogger(__name__) +ATTR_FAN = 'fan' +ATTR_MODE = 'mode' + +CONF_HOLD_TEMP = 'hold_temp' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_HOST): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean, +}) + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Radio Thermostat.""" @@ -29,10 +42,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): hosts.append(radiotherm.discover.discover_address()) if hosts is None: - _LOGGER.error("No radiotherm thermostats detected.") + _LOGGER.error("No Radiotherm Thermostats detected") return False - hold_temp = config.get(HOLD_TEMP, False) + hold_temp = config.get(CONF_HOLD_TEMP) tstats = [] for host in hosts: @@ -75,8 +88,8 @@ class RadioThermostat(ClimateDevice): def device_state_attributes(self): """Return the device specific state attributes.""" return { - "fan": self.device.fmode['human'], - "mode": self.device.tmode['human'] + ATTR_FAN: self.device.fmode['human'], + ATTR_MODE: self.device.tmode['human'] } @property @@ -124,8 +137,11 @@ class RadioThermostat(ClimateDevice): def set_time(self): """Set device time.""" now = datetime.datetime.now() - self.device.time = {'day': now.weekday(), - 'hour': now.hour, 'minute': now.minute} + self.device.time = { + 'day': now.weekday(), + 'hour': now.hour, + 'minute': now.minute + } def set_operation_mode(self, operation_mode): """Set operation mode (auto, cool, heat, off).""" From 11396a221e733157faf800c78cb223a712ee58a9 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 11:45:38 +0200 Subject: [PATCH 016/162] Extend schema (#3278) --- .../components/climate/generic_thermostat.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py index fd85d7fd46b..b64a59c9364 100644 --- a/homeassistant/components/climate/generic_thermostat.py +++ b/homeassistant/components/climate/generic_thermostat.py @@ -5,16 +5,19 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/climate.generic_thermostat/ """ import logging + import voluptuous as vol -import homeassistant.helpers.config_validation as cv from homeassistant.components import switch from homeassistant.components.climate import ( - STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice) + STATE_HEAT, STATE_COOL, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA) from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, STATE_ON, STATE_OFF, ATTR_TEMPERATURE) from homeassistant.helpers import condition from homeassistant.helpers.event import track_state_change +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['switch', 'sensor'] @@ -30,18 +33,16 @@ CONF_TARGET_TEMP = 'target_temp' CONF_AC_MODE = 'ac_mode' CONF_MIN_DUR = 'min_cycle_duration' -_LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = vol.Schema({ - vol.Required("platform"): "generic_thermostat", - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HEATER): cv.entity_id, vol.Required(CONF_SENSOR): cv.entity_id, - vol.Optional(CONF_MIN_TEMP): vol.Coerce(float), + vol.Optional(CONF_AC_MODE): cv.boolean, vol.Optional(CONF_MAX_TEMP): vol.Coerce(float), - vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float), - vol.Optional(CONF_AC_MODE): vol.Coerce(bool), vol.Optional(CONF_MIN_DUR): vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_MIN_TEMP): vol.Coerce(float), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float), }) @@ -56,10 +57,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): ac_mode = config.get(CONF_AC_MODE) min_cycle_duration = config.get(CONF_MIN_DUR) - add_devices([GenericThermostat(hass, name, heater_entity_id, - sensor_entity_id, min_temp, - max_temp, target_temp, ac_mode, - min_cycle_duration)]) + add_devices([GenericThermostat( + hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp, + target_temp, ac_mode, min_cycle_duration)]) # pylint: disable=too-many-instance-attributes, abstract-method From 58c0990508b3b816f9b5b8185c7e4ea1cd0bfce8 Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Sun, 11 Sep 2016 10:30:31 -0400 Subject: [PATCH 017/162] Convert rgb to hsb for Wink Osram light --- .../components/binary_sensor/wink.py | 2 +- homeassistant/components/cover/wink.py | 2 +- homeassistant/components/garage_door/wink.py | 2 +- homeassistant/components/light/wink.py | 29 ++++++++++++++++--- homeassistant/components/lock/wink.py | 2 +- .../components/rollershutter/wink.py | 2 +- homeassistant/components/sensor/wink.py | 2 +- homeassistant/components/switch/wink.py | 2 +- homeassistant/components/wink.py | 2 +- requirements_all.txt | 2 +- 10 files changed, 34 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/binary_sensor/wink.py b/homeassistant/components/binary_sensor/wink.py index 9ba717782e9..4ebe7971660 100644 --- a/homeassistant/components/binary_sensor/wink.py +++ b/homeassistant/components/binary_sensor/wink.py @@ -13,7 +13,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.helpers.entity import Entity from homeassistant.loader import get_component -REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] +REQUIREMENTS = ['python-wink==0.7.14', 'pubnub==3.8.2'] # These are the available sensors mapped to binary_sensor class SENSOR_TYPES = { diff --git a/homeassistant/components/cover/wink.py b/homeassistant/components/cover/wink.py index 9b76e234303..a8b90809255 100644 --- a/homeassistant/components/cover/wink.py +++ b/homeassistant/components/cover/wink.py @@ -10,7 +10,7 @@ from homeassistant.components.cover import CoverDevice from homeassistant.components.wink import WinkDevice from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] +REQUIREMENTS = ['python-wink==0.7.14', 'pubnub==3.8.2'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/garage_door/wink.py b/homeassistant/components/garage_door/wink.py index c59d48d48bd..1cc63515e2e 100644 --- a/homeassistant/components/garage_door/wink.py +++ b/homeassistant/components/garage_door/wink.py @@ -10,7 +10,7 @@ from homeassistant.components.garage_door import GarageDoorDevice from homeassistant.components.wink import WinkDevice from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] +REQUIREMENTS = ['python-wink==0.7.14', 'pubnub==3.8.2'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/light/wink.py index 957c3a4e116..c41d88a9c30 100644 --- a/homeassistant/components/light/wink.py +++ b/homeassistant/components/light/wink.py @@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.wink/ """ import logging +import colorsys from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \ ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, \ @@ -15,7 +16,7 @@ from homeassistant.util import color as color_util from homeassistant.util.color import \ color_temperature_mired_to_kelvin as mired_to_kelvin -REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] +REQUIREMENTS = ['python-wink==0.7.14', 'pubnub==3.8.2'] SUPPORT_WINK = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_RGB_COLOR @@ -56,6 +57,21 @@ class WinkLight(WinkDevice, Light): """Return the brightness of the light.""" return int(self.wink.brightness() * 255) + @property + def rgb_color(self): + """Current bulb color in RGB.""" + if not self.wink.supports_hue_saturation(): + return None + else: + hue = self.wink.color_hue() + saturation = self.wink.color_saturation() + value = int(self.wink.brightness() * 255) + rgb = colorsys.hsv_to_rgb(hue, saturation, value) + r_value = int(round(rgb[0])) + g_value = int(round(rgb[1])) + b_value = int(round(rgb[2])) + return r_value, g_value, b_value + @property def xy_color(self): """Current bulb color in CIE 1931 (XY) color space.""" @@ -87,9 +103,14 @@ class WinkLight(WinkDevice, Light): } if rgb_color: - xyb = color_util.color_RGB_to_xy(*rgb_color) - state_kwargs['color_xy'] = xyb[0], xyb[1] - state_kwargs['brightness'] = xyb[2] + if self.wink.supports_xy_color(): + xyb = color_util.color_RGB_to_xy(*rgb_color) + state_kwargs['color_xy'] = xyb[0], xyb[1] + state_kwargs['brightness'] = xyb[2] + elif self.wink.supports_hue_saturation(): + hsv = colorsys.rgb_to_hsv(rgb_color[0], + rgb_color[1], rgb_color[2]) + state_kwargs['color_hue_saturation'] = hsv[0], hsv[1] if color_temp_mired: state_kwargs['color_kelvin'] = mired_to_kelvin(color_temp_mired) diff --git a/homeassistant/components/lock/wink.py b/homeassistant/components/lock/wink.py index 1a09414f8c3..15526ebad8c 100644 --- a/homeassistant/components/lock/wink.py +++ b/homeassistant/components/lock/wink.py @@ -10,7 +10,7 @@ from homeassistant.components.lock import LockDevice from homeassistant.components.wink import WinkDevice from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] +REQUIREMENTS = ['python-wink==0.7.14', 'pubnub==3.8.2'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/rollershutter/wink.py b/homeassistant/components/rollershutter/wink.py index 18ed193060b..3772c2495fd 100644 --- a/homeassistant/components/rollershutter/wink.py +++ b/homeassistant/components/rollershutter/wink.py @@ -10,7 +10,7 @@ from homeassistant.components.rollershutter import RollershutterDevice from homeassistant.components.wink import WinkDevice from homeassistant.const import CONF_ACCESS_TOKEN -REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] +REQUIREMENTS = ['python-wink==0.7.14', 'pubnub==3.8.2'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/sensor/wink.py b/homeassistant/components/sensor/wink.py index ddc160c5064..a8e1253dec1 100644 --- a/homeassistant/components/sensor/wink.py +++ b/homeassistant/components/sensor/wink.py @@ -12,7 +12,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.components.wink import WinkDevice from homeassistant.loader import get_component -REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] +REQUIREMENTS = ['python-wink==0.7.14', 'pubnub==3.8.2'] SENSOR_TYPES = ['temperature', 'humidity'] diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/switch/wink.py index 3a0cd1b7736..3d60749823d 100644 --- a/homeassistant/components/switch/wink.py +++ b/homeassistant/components/switch/wink.py @@ -10,7 +10,7 @@ from homeassistant.components.wink import WinkDevice from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.helpers.entity import ToggleEntity -REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] +REQUIREMENTS = ['python-wink==0.7.14', 'pubnub==3.8.2'] def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index fe57fa289f2..144e1e0b237 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -14,7 +14,7 @@ from homeassistant.const import CONF_ACCESS_TOKEN, ATTR_BATTERY_LEVEL from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-wink==0.7.13', 'pubnub==3.8.2'] +REQUIREMENTS = ['python-wink==0.7.14', 'pubnub==3.8.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index c0ef98ffdc1..61d221ba7fb 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -394,7 +394,7 @@ python-twitch==1.3.0 # homeassistant.components.rollershutter.wink # homeassistant.components.sensor.wink # homeassistant.components.switch.wink -python-wink==0.7.13 +python-wink==0.7.14 # homeassistant.components.keyboard pyuserinput==0.1.11 From 05a3b610ffb1c8741338665ff457bcc4feb08b32 Mon Sep 17 00:00:00 2001 From: Teagan Glenn Date: Sun, 11 Sep 2016 12:18:53 -0600 Subject: [PATCH 018/162] Add ISY programs and support for all device types (#3082) * ISY Lock, Binary Sensor, Cover devices, Sensors and Fan support * Support for ISY Programs --- .../components/binary_sensor/isy994.py | 76 ++++ homeassistant/components/cover/isy994.py | 109 ++++++ homeassistant/components/fan/isy994.py | 120 ++++++ homeassistant/components/isy994.py | 343 +++++++++------- homeassistant/components/light/isy994.py | 90 +++-- homeassistant/components/lock/isy994.py | 123 ++++++ homeassistant/components/sensor/isy994.py | 370 ++++++++++++++---- homeassistant/components/switch/isy994.py | 145 ++++--- requirements_all.txt | 2 +- 9 files changed, 1057 insertions(+), 321 deletions(-) create mode 100644 homeassistant/components/binary_sensor/isy994.py create mode 100644 homeassistant/components/cover/isy994.py create mode 100644 homeassistant/components/fan/isy994.py create mode 100644 homeassistant/components/lock/isy994.py diff --git a/homeassistant/components/binary_sensor/isy994.py b/homeassistant/components/binary_sensor/isy994.py new file mode 100644 index 00000000000..d845f4de9f7 --- /dev/null +++ b/homeassistant/components/binary_sensor/isy994.py @@ -0,0 +1,76 @@ +""" +Support for ISY994 binary sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.isy994/ +""" +import logging +from typing import Callable # noqa + +from homeassistant.components.binary_sensor import BinarySensorDevice, DOMAIN +import homeassistant.components.isy994 as isy +from homeassistant.const import STATE_ON, STATE_OFF +from homeassistant.helpers.typing import ConfigType + + +_LOGGER = logging.getLogger(__name__) + +VALUE_TO_STATE = { + False: STATE_OFF, + True: STATE_ON, +} + +UOM = ['2', '78'] +STATES = [STATE_OFF, STATE_ON, 'true', 'false'] + + +# pylint: disable=unused-argument +def setup_platform(hass, config: ConfigType, + add_devices: Callable[[list], None], discovery_info=None): + """Setup the ISY994 binary sensor platform.""" + if isy.ISY is None or not isy.ISY.connected: + _LOGGER.error('A connection has not been made to the ISY controller.') + return False + + devices = [] + + for node in isy.filter_nodes(isy.SENSOR_NODES, units=UOM, + states=STATES): + devices.append(ISYBinarySensorDevice(node)) + + for program in isy.PROGRAMS.get(DOMAIN, []): + try: + status = program[isy.KEY_STATUS] + except (KeyError, AssertionError): + pass + else: + devices.append(ISYBinarySensorProgram(program.name, status)) + + add_devices(devices) + + +class ISYBinarySensorDevice(isy.ISYDevice, BinarySensorDevice): + """Representation of an ISY994 binary sensor device.""" + + def __init__(self, node) -> None: + """Initialize the ISY994 binary sensor device.""" + isy.ISYDevice.__init__(self, node) + + @property + def is_on(self) -> bool: + """Get whether the ISY994 binary sensor device is on.""" + return bool(self.state) + + +class ISYBinarySensorProgram(ISYBinarySensorDevice): + """Representation of an ISY994 binary sensor program.""" + + def __init__(self, name, node) -> None: + """Initialize the ISY994 binary sensor program.""" + ISYBinarySensorDevice.__init__(self, node) + self._name = name + + @property + def is_on(self): + """Get whether the ISY994 binary sensor program is on.""" + return bool(self.value) diff --git a/homeassistant/components/cover/isy994.py b/homeassistant/components/cover/isy994.py new file mode 100644 index 00000000000..def3ef009c7 --- /dev/null +++ b/homeassistant/components/cover/isy994.py @@ -0,0 +1,109 @@ +""" +Support for ISY994 covers. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/cover.isy994/ +""" +import logging +from typing import Callable # noqa + +from homeassistant.components.cover import CoverDevice, DOMAIN +import homeassistant.components.isy994 as isy +from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN +from homeassistant.helpers.typing import ConfigType + + +_LOGGER = logging.getLogger(__name__) + +VALUE_TO_STATE = { + 0: STATE_CLOSED, + 101: STATE_UNKNOWN, +} + +UOM = ['97'] +STATES = [STATE_OPEN, STATE_CLOSED, 'closing', 'opening'] + + +# pylint: disable=unused-argument +def setup_platform(hass, config: ConfigType, + add_devices: Callable[[list], None], discovery_info=None): + """Setup the ISY994 cover platform.""" + if isy.ISY is None or not isy.ISY.connected: + _LOGGER.error('A connection has not been made to the ISY controller.') + return False + + devices = [] + + for node in isy.filter_nodes(isy.NODES, units=UOM, + states=STATES): + devices.append(ISYCoverDevice(node)) + + for program in isy.PROGRAMS.get(DOMAIN, []): + try: + status = program[isy.KEY_STATUS] + actions = program[isy.KEY_ACTIONS] + assert actions.dtype == 'program', 'Not a program' + except (KeyError, AssertionError): + pass + else: + devices.append(ISYCoverProgram(program.name, status, actions)) + + add_devices(devices) + + +class ISYCoverDevice(isy.ISYDevice, CoverDevice): + """Representation of an ISY994 cover device.""" + + def __init__(self, node: object): + """Initialize the ISY994 cover device.""" + isy.ISYDevice.__init__(self, node) + + @property + def current_cover_position(self) -> int: + """Get the current cover position.""" + return sorted((0, self.value, 100))[1] + + @property + def is_closed(self) -> bool: + """Get whether the ISY994 cover device is closed.""" + return self.state == STATE_CLOSED + + @property + def state(self) -> str: + """Get the state of the ISY994 cover device.""" + return VALUE_TO_STATE.get(self.value, STATE_OPEN) + + def open_cover(self, **kwargs) -> None: + """Send the open cover command to the ISY994 cover device.""" + if not self._node.on(val=100): + _LOGGER.error('Unable to open the cover') + + def close_cover(self, **kwargs) -> None: + """Send the close cover command to the ISY994 cover device.""" + if not self._node.off(): + _LOGGER.error('Unable to close the cover') + + +class ISYCoverProgram(ISYCoverDevice): + """Representation of an ISY994 cover program.""" + + def __init__(self, name: str, node: object, actions: object) -> None: + """Initialize the ISY994 cover program.""" + ISYCoverDevice.__init__(self, node) + self._name = name + self._actions = actions + + @property + def state(self) -> str: + """Get the state of the ISY994 cover program.""" + return STATE_CLOSED if bool(self.value) else STATE_OPEN + + def open_cover(self, **kwargs) -> None: + """Send the open cover command to the ISY994 cover program.""" + if not self._actions.runThen(): + _LOGGER.error('Unable to open the cover') + + def close_cover(self, **kwargs) -> None: + """Send the close cover command to the ISY994 cover program.""" + if not self._actions.runElse(): + _LOGGER.error('Unable to close the cover') diff --git a/homeassistant/components/fan/isy994.py b/homeassistant/components/fan/isy994.py new file mode 100644 index 00000000000..2deb938d337 --- /dev/null +++ b/homeassistant/components/fan/isy994.py @@ -0,0 +1,120 @@ +""" +Support for ISY994 fans. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/fan.isy994/ +""" +import logging +from typing import Callable + +from homeassistant.components.fan import (FanEntity, DOMAIN, SPEED_OFF, + SPEED_LOW, SPEED_MED, + SPEED_HIGH) +import homeassistant.components.isy994 as isy +from homeassistant.const import STATE_UNKNOWN, STATE_ON, STATE_OFF +from homeassistant.helpers.typing import ConfigType + +_LOGGER = logging.getLogger(__name__) + +VALUE_TO_STATE = { + 0: SPEED_OFF, + 63: SPEED_LOW, + 64: SPEED_LOW, + 190: SPEED_MED, + 191: SPEED_MED, + 255: SPEED_HIGH, +} + +STATE_TO_VALUE = {} +for key in VALUE_TO_STATE: + STATE_TO_VALUE[VALUE_TO_STATE[key]] = key + +STATES = [SPEED_OFF, SPEED_LOW, SPEED_MED, SPEED_HIGH] + + +# pylint: disable=unused-argument +def setup_platform(hass, config: ConfigType, + add_devices: Callable[[list], None], discovery_info=None): + """Setup the ISY994 fan platform.""" + if isy.ISY is None or not isy.ISY.connected: + _LOGGER.error('A connection has not been made to the ISY controller.') + return False + + devices = [] + + for node in isy.filter_nodes(isy.NODES, states=STATES): + devices.append(ISYFanDevice(node)) + + for program in isy.PROGRAMS.get(DOMAIN, []): + try: + status = program[isy.KEY_STATUS] + actions = program[isy.KEY_ACTIONS] + assert actions.dtype == 'program', 'Not a program' + except (KeyError, AssertionError): + pass + else: + devices.append(ISYFanProgram(program.name, status, actions)) + + add_devices(devices) + + +class ISYFanDevice(isy.ISYDevice, FanEntity): + """Representation of an ISY994 fan device.""" + + def __init__(self, node) -> None: + """Initialize the ISY994 fan device.""" + isy.ISYDevice.__init__(self, node) + self.speed = self.state + + @property + def state(self) -> str: + """Get the state of the ISY994 fan device.""" + return VALUE_TO_STATE.get(self.value, STATE_UNKNOWN) + + def set_speed(self, speed: str) -> None: + """Send the set speed command to the ISY994 fan device.""" + if not self._node.on(val=STATE_TO_VALUE.get(speed, 0)): + _LOGGER.debug('Unable to set fan speed') + else: + self.speed = self.state + + def turn_on(self, speed: str=None, **kwargs) -> None: + """Send the turn on command to the ISY994 fan device.""" + self.set_speed(speed) + + def turn_off(self, **kwargs) -> None: + """Send the turn off command to the ISY994 fan device.""" + if not self._node.off(): + _LOGGER.debug('Unable to set fan speed') + else: + self.speed = self.state + + +class ISYFanProgram(ISYFanDevice): + """Representation of an ISY994 fan program.""" + + def __init__(self, name: str, node, actions) -> None: + """Initialize the ISY994 fan program.""" + ISYFanDevice.__init__(self, node) + self._name = name + self._actions = actions + self.speed = STATE_ON if self.is_on else STATE_OFF + + @property + def state(self) -> str: + """Get the state of the ISY994 fan program.""" + return STATE_ON if bool(self.value) else STATE_OFF + + def turn_off(self, **kwargs) -> None: + """Send the turn on command to ISY994 fan program.""" + if not self._actions.runThen(): + _LOGGER.error('Unable to open the cover') + else: + self.speed = STATE_ON if self.is_on else STATE_OFF + + def turn_on(self, **kwargs) -> None: + """Send the turn off command to ISY994 fan program.""" + if not self._actions.runElse(): + _LOGGER.error('Unable to close the cover') + else: + self.speed = STATE_ON if self.is_on else STATE_OFF diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index be964ebef7c..379712fa989 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -6,43 +6,150 @@ https://home-assistant.io/components/isy994/ """ import logging from urllib.parse import urlparse +import voluptuous as vol +from homeassistant.core import HomeAssistant # noqa from homeassistant.const import ( CONF_HOST, CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP) -from homeassistant.helpers import validate_config, discovery -from homeassistant.helpers.entity import ToggleEntity +from homeassistant.helpers import discovery, config_validation as cv +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import ConfigType, Dict # noqa + DOMAIN = "isy994" -REQUIREMENTS = ['PyISY==1.0.6'] +REQUIREMENTS = ['PyISY==1.0.7'] ISY = None -SENSOR_STRING = 'Sensor' -HIDDEN_STRING = '{HIDE ME}' +DEFAULT_SENSOR_STRING = 'sensor' +DEFAULT_HIDDEN_STRING = '{HIDE ME}' CONF_TLS_VER = 'tls' +CONF_HIDDEN_STRING = 'hidden_string' +CONF_SENSOR_STRING = 'sensor_string' +KEY_MY_PROGRAMS = 'My Programs' +KEY_FOLDER = 'folder' +KEY_ACTIONS = 'actions' +KEY_STATUS = 'status' _LOGGER = logging.getLogger(__name__) +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_HOST): cv.url, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_TLS_VER): vol.Coerce(float), + vol.Optional(CONF_HIDDEN_STRING, + default=DEFAULT_HIDDEN_STRING): cv.string, + vol.Optional(CONF_SENSOR_STRING, + default=DEFAULT_SENSOR_STRING): cv.string + }) +}, extra=vol.ALLOW_EXTRA) -def setup(hass, config): - """Setup ISY994 component. +SENSOR_NODES = [] +NODES = [] +GROUPS = [] +PROGRAMS = {} - This will automatically import associated lights, switches, and sensors. - """ - import PyISY +PYISY = None - # pylint: disable=global-statement - # check for required values in configuration file - if not validate_config(config, - {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, - _LOGGER): - return False +HIDDEN_STRING = DEFAULT_HIDDEN_STRING - # Pull and parse standard configuration. - user = config[DOMAIN][CONF_USERNAME] - password = config[DOMAIN][CONF_PASSWORD] - host = urlparse(config[DOMAIN][CONF_HOST]) +SUPPORTED_DOMAINS = ['binary_sensor', 'cover', 'fan', 'light', 'lock', + 'sensor', 'switch'] + + +def filter_nodes(nodes: list, units: list=None, states: list=None) -> list: + """Filter a list of ISY nodes based on the units and states provided.""" + filtered_nodes = [] + units = units if units else [] + states = states if states else [] + for node in nodes: + match_unit = False + match_state = True + for uom in node.uom: + if uom in units: + match_unit = True + continue + elif uom not in states: + match_state = False + + if match_unit: + continue + + if match_unit or match_state: + filtered_nodes.append(node) + + return filtered_nodes + + +def _categorize_nodes(hidden_identifier: str, sensor_identifier: str) -> None: + """Categorize the ISY994 nodes.""" + global SENSOR_NODES + global NODES + global GROUPS + + SENSOR_NODES = [] + NODES = [] + GROUPS = [] + + for (path, node) in ISY.nodes: + hidden = hidden_identifier in path or hidden_identifier in node.name + if hidden: + node.name += hidden_identifier + if sensor_identifier in path or sensor_identifier in node.name: + SENSOR_NODES.append(node) + elif isinstance(node, PYISY.Nodes.Node): # pylint: disable=no-member + NODES.append(node) + elif isinstance(node, PYISY.Nodes.Group): # pylint: disable=no-member + GROUPS.append(node) + + +def _categorize_programs() -> None: + """Categorize the ISY994 programs.""" + global PROGRAMS + + PROGRAMS = {} + + for component in SUPPORTED_DOMAINS: + try: + folder = ISY.programs[KEY_MY_PROGRAMS]['HA.' + component] + except KeyError: + pass + else: + for dtype, _, node_id in folder.children: + if dtype is KEY_FOLDER: + program = folder[node_id] + try: + node = program[KEY_STATUS].leaf + assert node.dtype == 'program', 'Not a program' + except (KeyError, AssertionError): + pass + else: + if component not in PROGRAMS: + PROGRAMS[component] = [] + PROGRAMS[component].append(program) + + +# pylint: disable=too-many-locals +def setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the ISY 994 platform.""" + isy_config = config.get(DOMAIN) + + user = isy_config.get(CONF_USERNAME) + password = isy_config.get(CONF_PASSWORD) + tls_version = isy_config.get(CONF_TLS_VER) + host = urlparse(isy_config.get(CONF_HOST)) + port = host.port addr = host.geturl() + hidden_identifier = isy_config.get(CONF_HIDDEN_STRING, + DEFAULT_HIDDEN_STRING) + sensor_identifier = isy_config.get(CONF_SENSOR_STRING, + DEFAULT_SENSOR_STRING) + + global HIDDEN_STRING + HIDDEN_STRING = hidden_identifier + if host.scheme == 'http': addr = addr.replace('http://', '') https = False @@ -50,169 +157,125 @@ def setup(hass, config): addr = addr.replace('https://', '') https = True else: - _LOGGER.error('isy994 host value in configuration file is invalid.') + _LOGGER.error('isy994 host value in configuration is invalid.') return False - port = host.port + addr = addr.replace(':{}'.format(port), '') - # Pull and parse optional configuration. - global SENSOR_STRING - global HIDDEN_STRING - SENSOR_STRING = str(config[DOMAIN].get('sensor_string', SENSOR_STRING)) - HIDDEN_STRING = str(config[DOMAIN].get('hidden_string', HIDDEN_STRING)) - tls_version = config[DOMAIN].get(CONF_TLS_VER, None) + import PyISY + + global PYISY + PYISY = PyISY # Connect to ISY controller. global ISY - ISY = PyISY.ISY(addr, port, user, password, use_https=https, - tls_ver=tls_version, log=_LOGGER) + ISY = PyISY.ISY(addr, port, username=user, password=password, + use_https=https, tls_ver=tls_version, log=_LOGGER) if not ISY.connected: return False + _categorize_nodes(hidden_identifier, sensor_identifier) + + _categorize_programs() + # Listen for HA stop to disconnect. hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop) # Load platforms for the devices in the ISY controller that we support. - for component in ('sensor', 'light', 'switch'): + for component in SUPPORTED_DOMAINS: discovery.load_platform(hass, component, DOMAIN, {}, config) ISY.auto_update = True return True -def stop(event): - """Cleanup the ISY subscription.""" +# pylint: disable=unused-argument +def stop(event: object) -> None: + """Stop ISY auto updates.""" ISY.auto_update = False -class ISYDeviceABC(ToggleEntity): - """An abstract Class for an ISY device.""" +class ISYDevice(Entity): + """Representation of an ISY994 device.""" _attrs = {} - _onattrs = [] - _states = [] - _dtype = None - _domain = None - _name = None + _domain = None # type: str + _name = None # type: str - def __init__(self, node): - """Initialize the device.""" - # setup properties - self.node = node + def __init__(self, node) -> None: + """Initialize the insteon device.""" + self._node = node - # track changes - self._change_handler = self.node.status. \ - subscribe('changed', self.on_update) + self._change_handler = self._node.status.subscribe('changed', + self.on_update) - def __del__(self): - """Cleanup subscriptions because it is the right thing to do.""" + def __del__(self) -> None: + """Cleanup the subscriptions.""" self._change_handler.unsubscribe() + # pylint: disable=unused-argument + def on_update(self, event: object) -> None: + """Handle the update event from the ISY994 Node.""" + self.update_ha_state() + @property - def domain(self): - """Return the domain of the entity.""" + def domain(self) -> str: + """Get the domain of the device.""" return self._domain @property - def dtype(self): - """Return the data type of the entity (binary or analog).""" - if self._dtype in ['analog', 'binary']: - return self._dtype - return 'binary' if self.unit_of_measurement is None else 'analog' - - @property - def should_poll(self): - """No polling needed.""" - return False - - @property - def value(self): - """Return the unclean value from the controller.""" + def unique_id(self) -> str: + """Get the unique identifier of the device.""" # pylint: disable=protected-access - return self.node.status._val + return self._node._id @property - def state_attributes(self): - """Return the state attributes for the node.""" - attr = {} - for name, prop in self._attrs.items(): - attr[name] = getattr(self, prop) - attr = self._attr_filter(attr) - return attr - - def _attr_filter(self, attr): - """A Placeholder for attribute filters.""" - # pylint: disable=no-self-use - return attr - - @property - def unique_id(self): - """Return the ID of this ISY sensor.""" - # pylint: disable=protected-access - return self.node._id - - @property - def raw_name(self): - """Return the unclean node name.""" + def raw_name(self) -> str: + """Get the raw name of the device.""" return str(self._name) \ - if self._name is not None else str(self.node.name) + if self._name is not None else str(self._node.name) @property - def name(self): - """Return the cleaned name of the node.""" + def name(self) -> str: + """Get the name of the device.""" return self.raw_name.replace(HIDDEN_STRING, '').strip() \ .replace('_', ' ') @property - def hidden(self): - """Suggestion if the entity should be hidden from UIs.""" + def should_poll(self) -> bool: + """No polling required since we're using the subscription.""" + return False + + @property + def value(self) -> object: + """Get the current value of the device.""" + # pylint: disable=protected-access + return self._node.status._val + + @property + def state_attributes(self) -> Dict: + """Get the state attributes for the device.""" + attr = {} + if hasattr(self._node, 'aux_properties'): + for name, val in self._node.aux_properties.items(): + attr[name] = '{} {}'.format(val.get('value'), val.get('uom')) + return attr + + @property + def hidden(self) -> bool: + """Get whether the device should be hidden from the UI.""" return HIDDEN_STRING in self.raw_name - def update(self): - """Update state of the sensor.""" - # ISY objects are automatically updated by the ISY's event stream + @property + def unit_of_measurement(self) -> str: + """Get the device unit of measure.""" + return None + + def _attr_filter(self, attr: str) -> str: + """Filter the attribute.""" + # pylint: disable=no-self-use + return attr + + def update(self) -> None: + """Perform an update for the device.""" pass - - def on_update(self, event): - """Handle the update received event.""" - self.update_ha_state() - - @property - def is_on(self): - """Return a boolean response if the node is on.""" - return bool(self.value) - - @property - def is_open(self): - """Return boolean response if the node is open. On = Open.""" - return self.is_on - - @property - def state(self): - """Return the state of the node.""" - if len(self._states) > 0: - return self._states[0] if self.is_on else self._states[1] - return self.value - - def turn_on(self, **kwargs): - """Turn the device on.""" - if self.domain is not 'sensor': - attrs = [kwargs.get(name) for name in self._onattrs] - self.node.on(*attrs) - else: - _LOGGER.error('ISY cannot turn on sensors.') - - def turn_off(self, **kwargs): - """Turn the device off.""" - if self.domain is not 'sensor': - self.node.off() - else: - _LOGGER.error('ISY cannot turn off sensors.') - - @property - def unit_of_measurement(self): - """Return the defined units of measurement or None.""" - try: - return self.node.units - except AttributeError: - return None diff --git a/homeassistant/components/light/isy994.py b/homeassistant/components/light/isy994.py index 031fa7debb6..fe7abbc26e8 100644 --- a/homeassistant/components/light/isy994.py +++ b/homeassistant/components/light/isy994.py @@ -2,58 +2,68 @@ Support for ISY994 lights. For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/isy994/ +https://home-assistant.io/components/light.isy994/ """ import logging +from typing import Callable -from homeassistant.components.isy994 import ( - HIDDEN_STRING, ISY, SENSOR_STRING, ISYDeviceABC) -from homeassistant.components.light import (ATTR_BRIGHTNESS, - ATTR_SUPPORTED_FEATURES, - SUPPORT_BRIGHTNESS) -from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.components.light import Light +import homeassistant.components.isy994 as isy +from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNKNOWN +from homeassistant.helpers.typing import ConfigType -SUPPORT_ISY994 = SUPPORT_BRIGHTNESS +_LOGGER = logging.getLogger(__name__) + +VALUE_TO_STATE = { + False: STATE_OFF, + True: STATE_ON, +} + +UOM = ['2', '78'] +STATES = [STATE_OFF, STATE_ON, 'true', 'false'] -def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the ISY994 platform.""" - logger = logging.getLogger(__name__) - devs = [] - - if ISY is None or not ISY.connected: - logger.error('A connection has not been made to the ISY controller.') +# pylint: disable=unused-argument +def setup_platform(hass, config: ConfigType, + add_devices: Callable[[list], None], discovery_info=None): + """Set up the ISY994 light platform.""" + if isy.ISY is None or not isy.ISY.connected: + _LOGGER.error('A connection has not been made to the ISY controller.') return False - # Import dimmable nodes - for (path, node) in ISY.nodes: - if node.dimmable and SENSOR_STRING not in node.name: - if HIDDEN_STRING in path: - node.name += HIDDEN_STRING - devs.append(ISYLightDevice(node)) + devices = [] - add_devices(devs) + for node in isy.filter_nodes(isy.NODES, units=UOM, + states=STATES): + if node.dimmable: + devices.append(ISYLightDevice(node)) + + add_devices(devices) -class ISYLightDevice(ISYDeviceABC): - """Representation of a ISY light.""" +class ISYLightDevice(isy.ISYDevice, Light): + """Representation of an ISY994 light devie.""" - _domain = 'light' - _dtype = 'analog' - _attrs = { - ATTR_BRIGHTNESS: 'value', - ATTR_SUPPORTED_FEATURES: 'supported_features', - } - _onattrs = [ATTR_BRIGHTNESS] - _states = [STATE_ON, STATE_OFF] + def __init__(self, node: object) -> None: + """Initialize the ISY994 light device.""" + isy.ISYDevice.__init__(self, node) @property - def supported_features(self): - """Flag supported features.""" - return SUPPORT_ISY994 + def is_on(self) -> bool: + """Get whether the ISY994 light is on.""" + return self.state == STATE_ON - def _attr_filter(self, attr): - """Filter brightness out of entity while off.""" - if ATTR_BRIGHTNESS in attr and not self.is_on: - del attr[ATTR_BRIGHTNESS] - return attr + @property + def state(self) -> str: + """Get the state of the ISY994 light.""" + return VALUE_TO_STATE.get(bool(self.value), STATE_UNKNOWN) + + def turn_off(self, **kwargs) -> None: + """Send the turn off command to the ISY994 light device.""" + if not self._node.fastOff(): + _LOGGER.debug('Unable to turn on light.') + + def turn_on(self, brightness=100, **kwargs) -> None: + """Send the turn on command to the ISY994 light device.""" + if not self._node.on(val=brightness): + _LOGGER.debug('Unable to turn on light.') diff --git a/homeassistant/components/lock/isy994.py b/homeassistant/components/lock/isy994.py new file mode 100644 index 00000000000..d7e921a16e5 --- /dev/null +++ b/homeassistant/components/lock/isy994.py @@ -0,0 +1,123 @@ +""" +Support for ISY994 locks. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/lock.isy994/ +""" +import logging +from typing import Callable # noqa + +from homeassistant.components.lock import LockDevice, DOMAIN +import homeassistant.components.isy994 as isy +from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN +from homeassistant.helpers.typing import ConfigType + +_LOGGER = logging.getLogger(__name__) + +VALUE_TO_STATE = { + 0: STATE_UNLOCKED, + 100: STATE_LOCKED +} + +UOM = ['11'] +STATES = [STATE_LOCKED, STATE_UNLOCKED] + + +# pylint: disable=unused-argument +def setup_platform(hass, config: ConfigType, + add_devices: Callable[[list], None], discovery_info=None): + """Set up the ISY994 lock platform.""" + if isy.ISY is None or not isy.ISY.connected: + _LOGGER.error('A connection has not been made to the ISY controller.') + return False + + devices = [] + + for node in isy.filter_nodes(isy.NODES, units=UOM, + states=STATES): + devices.append(ISYLockDevice(node)) + + for program in isy.PROGRAMS.get(DOMAIN, []): + try: + status = program[isy.KEY_STATUS] + actions = program[isy.KEY_ACTIONS] + assert actions.dtype == 'program', 'Not a program' + except (KeyError, AssertionError): + pass + else: + devices.append(ISYLockProgram(program.name, status, actions)) + + add_devices(devices) + + +class ISYLockDevice(isy.ISYDevice, LockDevice): + """Representation of an ISY994 lock device.""" + + def __init__(self, node) -> None: + """Initialize the ISY994 lock device.""" + isy.ISYDevice.__init__(self, node) + self._conn = node.parent.parent.conn + + @property + def is_locked(self) -> bool: + """Get whether the lock is in locked state.""" + return self.state == STATE_LOCKED + + @property + def state(self) -> str: + """Get the state of the lock.""" + return VALUE_TO_STATE.get(self.value, STATE_UNKNOWN) + + def lock(self, **kwargs) -> None: + """Send the lock command to the ISY994 device.""" + # Hack until PyISY is updated + req_url = self._conn.compileURL(['nodes', self.unique_id, 'cmd', + 'SECMD', '1']) + response = self._conn.request(req_url) + + if response is None: + _LOGGER.error('Unable to lock device') + + self._node.update(0.5) + + def unlock(self, **kwargs) -> None: + """Send the unlock command to the ISY994 device.""" + # Hack until PyISY is updated + req_url = self._conn.compileURL(['nodes', self.unique_id, 'cmd', + 'SECMD', '0']) + response = self._conn.request(req_url) + + if response is None: + _LOGGER.error('Unable to lock device') + + self._node.update(0.5) + + +class ISYLockProgram(ISYLockDevice): + """Representation of a ISY lock program.""" + + def __init__(self, name: str, node, actions) -> None: + """Initialize the lock.""" + ISYLockDevice.__init__(self, node) + self._name = name + self._actions = actions + + @property + def is_locked(self) -> bool: + """Return true if the device is locked.""" + return bool(self.value) + + @property + def state(self) -> str: + """Return the state of the lock.""" + return STATE_LOCKED if self.is_locked else STATE_UNLOCKED + + def lock(self, **kwargs) -> None: + """Lock the device.""" + if not self._actions.runThen(): + _LOGGER.error('Unable to lock device') + + def unlock(self, **kwargs) -> None: + """Unlock the device.""" + if not self._actions.runElse(): + _LOGGER.error('Unable to unlock device') diff --git a/homeassistant/components/sensor/isy994.py b/homeassistant/components/sensor/isy994.py index 237a1228d6c..df3ae9ed7ba 100644 --- a/homeassistant/components/sensor/isy994.py +++ b/homeassistant/components/sensor/isy994.py @@ -1,95 +1,311 @@ """ -Support for ISY994 sensors. +Support for ISY994 binary sensors. For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/isy994/ +https://home-assistant.io/components/binary_sensor.isy994/ """ import logging +from typing import Callable # noqa -from homeassistant.components.isy994 import ( - HIDDEN_STRING, ISY, SENSOR_STRING, ISYDeviceABC) -from homeassistant.const import ( - STATE_CLOSED, STATE_HOME, STATE_NOT_HOME, STATE_OFF, STATE_ON, STATE_OPEN) +import homeassistant.components.isy994 as isy +from homeassistant.const import (TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_OFF, + STATE_ON) +from homeassistant.helpers.typing import ConfigType -DEFAULT_HIDDEN_WEATHER = ['Temperature_High', 'Temperature_Low', 'Feels_Like', - 'Temperature_Average', 'Pressure', 'Dew_Point', - 'Gust_Speed', 'Evapotranspiration', - 'Irrigation_Requirement', 'Water_Deficit_Yesterday', - 'Elevation', 'Average_Temperature_Tomorrow', - 'High_Temperature_Tomorrow', - 'Low_Temperature_Tomorrow', 'Humidity_Tomorrow', - 'Wind_Speed_Tomorrow', 'Gust_Speed_Tomorrow', - 'Rain_Tomorrow', 'Snow_Tomorrow', - 'Forecast_Average_Temperature', - 'Forecast_High_Temperature', - 'Forecast_Low_Temperature', 'Forecast_Humidity', - 'Forecast_Rain', 'Forecast_Snow'] +_LOGGER = logging.getLogger(__name__) + +UOM_FRIENDLY_NAME = { + '1': 'amp', + '3': 'btu/h', + '4': TEMP_CELSIUS, + '5': 'cm', + '6': 'ft³', + '7': 'ft³/min', + '8': 'm³', + '9': 'day', + '10': 'days', + '12': 'dB', + '13': 'dB A', + '14': '°', + '16': 'macroseismic', + '17': TEMP_FAHRENHEIT, + '18': 'ft', + '19': 'hour', + '20': 'hours', + '21': 'abs. humidity (%)', + '22': 'rel. humidity (%)', + '23': 'inHg', + '24': 'in/hr', + '25': 'index', + '26': 'K', + '27': 'keyword', + '28': 'kg', + '29': 'kV', + '30': 'kW', + '31': 'kPa', + '32': 'KPH', + '33': 'kWH', + '34': 'liedu', + '35': 'l', + '36': 'lux', + '37': 'mercalli', + '38': 'm', + '39': 'm³/hr', + '40': 'm/s', + '41': 'mA', + '42': 'ms', + '43': 'mV', + '44': 'min', + '45': 'min', + '46': 'mm/hr', + '47': 'month', + '48': 'MPH', + '49': 'm/s', + '50': 'ohm', + '51': '%', + '52': 'lb', + '53': 'power factor', + '54': 'ppm', + '55': 'pulse count', + '57': 's', + '58': 's', + '59': 'seimens/m', + '60': 'body wave magnitude scale', + '61': 'Ricter scale', + '62': 'moment magnitude scale', + '63': 'surface wave magnitude scale', + '64': 'shindo', + '65': 'SML', + '69': 'gal', + '71': 'UV index', + '72': 'V', + '73': 'W', + '74': 'W/m²', + '75': 'weekday', + '76': 'Wind Direction (°)', + '77': 'year', + '82': 'mm', + '83': 'km', + '85': 'ohm', + '86': 'kOhm', + '87': 'm³/m³', + '88': 'Water activity', + '89': 'RPM', + '90': 'Hz', + '91': '° (Relative to North)', + '92': '° (Relative to South)', +} + +UOM_TO_STATES = { + '11': { + '0': 'unlocked', + '100': 'locked', + '102': 'jammed', + }, + '15': { + '1': 'master code changed', + '2': 'tamper code entry limit', + '3': 'escutcheon removed', + '4': 'key/manually locked', + '5': 'locked by touch', + '6': 'key/manually unlocked', + '7': 'remote locking jammed bolt', + '8': 'remotely locked', + '9': 'remotely unlocked', + '10': 'deadbolt jammed', + '11': 'battery too low to operate', + '12': 'critical low battery', + '13': 'low battery', + '14': 'automatically locked', + '15': 'automatic locking jammed bolt', + '16': 'remotely power cycled', + '17': 'lock handling complete', + '19': 'user deleted', + '20': 'user added', + '21': 'duplicate pin', + '22': 'jammed bolt by locking with keypad', + '23': 'locked by keypad', + '24': 'unlocked by keypad', + '25': 'keypad attempt outside schedule', + '26': 'hardware failure', + '27': 'factory reset' + }, + '66': { + '0': 'idle', + '1': 'heating', + '2': 'cooling', + '3': 'fan only', + '4': 'pending heat', + '5': 'pending cool', + '6': 'vent', + '7': 'aux heat', + '8': '2nd stage heating', + '9': '2nd stage cooling', + '10': '2nd stage aux heat', + '11': '3rd stage aux heat' + }, + '67': { + '0': 'off', + '1': 'heat', + '2': 'cool', + '3': 'auto', + '4': 'aux/emergency heat', + '5': 'resume', + '6': 'fan only', + '7': 'furnace', + '8': 'dry air', + '9': 'moist air', + '10': 'auto changeover', + '11': 'energy save heat', + '12': 'energy save cool', + '13': 'away' + }, + '68': { + '0': 'auto', + '1': 'on', + '2': 'auto high', + '3': 'high', + '4': 'auto medium', + '5': 'medium', + '6': 'circulation', + '7': 'humidity circulation' + }, + '93': { + '1': 'power applied', + '2': 'ac mains disconnected', + '3': 'ac mains reconnected', + '4': 'surge detection', + '5': 'volt drop or drift', + '6': 'over current detected', + '7': 'over voltage detected', + '8': 'over load detected', + '9': 'load error', + '10': 'replace battery soon', + '11': 'replace battery now', + '12': 'battery is charging', + '13': 'battery is fully charged', + '14': 'charge battery soon', + '15': 'charge battery now' + }, + '94': { + '1': 'program started', + '2': 'program in progress', + '3': 'program completed', + '4': 'replace main filter', + '5': 'failure to set target temperature', + '6': 'supplying water', + '7': 'water supply failure', + '8': 'boiling', + '9': 'boiling failure', + '10': 'washing', + '11': 'washing failure', + '12': 'rinsing', + '13': 'rinsing failure', + '14': 'draining', + '15': 'draining failure', + '16': 'spinning', + '17': 'spinning failure', + '18': 'drying', + '19': 'drying failure', + '20': 'fan failure', + '21': 'compressor failure' + }, + '95': { + '1': 'leaving bed', + '2': 'sitting on bed', + '3': 'lying on bed', + '4': 'posture changed', + '5': 'sitting on edge of bed' + }, + '96': { + '1': 'clean', + '2': 'slightly polluted', + '3': 'moderately polluted', + '4': 'highly polluted' + }, + '97': { + '0': 'closed', + '100': 'open', + '102': 'stopped', + '103': 'closing', + '104': 'opening' + } +} + +BINARY_UOM = ['2', '78'] -def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the ISY994 platform.""" - # pylint: disable=protected-access - logger = logging.getLogger(__name__) - devs = [] - # Verify connection - if ISY is None or not ISY.connected: - logger.error('A connection has not been made to the ISY controller.') +# pylint: disable=unused-argument +def setup_platform(hass, config: ConfigType, + add_devices: Callable[[list], None], discovery_info=None): + """Setup the ISY994 sensor platform.""" + if isy.ISY is None or not isy.ISY.connected: + _LOGGER.error('A connection has not been made to the ISY controller.') return False - # Import weather - if ISY.climate is not None: - for prop in ISY.climate._id2name: - if prop is not None: - prefix = HIDDEN_STRING \ - if prop in DEFAULT_HIDDEN_WEATHER else '' - node = WeatherPseudoNode('ISY.weather.' + prop, prefix + prop, - getattr(ISY.climate, prop), - getattr(ISY.climate, prop + '_units')) - devs.append(ISYSensorDevice(node)) + devices = [] - # Import sensor nodes - for (path, node) in ISY.nodes: - if SENSOR_STRING in node.name: - if HIDDEN_STRING in path: - node.name += HIDDEN_STRING - devs.append(ISYSensorDevice(node, [STATE_ON, STATE_OFF])) + for node in isy.SENSOR_NODES: + if (len(node.uom) == 0 or node.uom[0] not in BINARY_UOM) and \ + STATE_OFF not in node.uom and STATE_ON not in node.uom: + _LOGGER.debug('LOADING %s', node.name) + devices.append(ISYSensorDevice(node)) - # Import sensor programs - for (folder_name, states) in ( - ('HA.locations', [STATE_HOME, STATE_NOT_HOME]), - ('HA.sensors', [STATE_OPEN, STATE_CLOSED]), - ('HA.states', [STATE_ON, STATE_OFF])): - try: - folder = ISY.programs['My Programs'][folder_name] - except KeyError: - # folder does not exist - pass + add_devices(devices) + + +class ISYSensorDevice(isy.ISYDevice): + """Representation of an ISY994 sensor device.""" + + def __init__(self, node) -> None: + """Initialize the ISY994 sensor device.""" + isy.ISYDevice.__init__(self, node) + + @property + def raw_unit_of_measurement(self) -> str: + """Get the raw unit of measurement for the ISY994 sensor device.""" + if len(self._node.uom) == 1: + if self._node.uom[0] in UOM_FRIENDLY_NAME: + friendly_name = UOM_FRIENDLY_NAME.get(self._node.uom[0]) + if friendly_name == TEMP_CELSIUS or \ + friendly_name == TEMP_FAHRENHEIT: + friendly_name = self.hass.config.units.temperature_unit + return friendly_name + else: + return self._node.uom[0] else: - for _, _, node_id in folder.children: - node = folder[node_id].leaf - devs.append(ISYSensorDevice(node, states)) + return None - add_devices(devs) + @property + def state(self) -> str: + """Get the state of the ISY994 sensor device.""" + if len(self._node.uom) == 1: + if self._node.uom[0] in UOM_TO_STATES: + states = UOM_TO_STATES.get(self._node.uom[0]) + if self.value in states: + return states.get(self.value) + elif self._node.prec and self._node.prec != [0]: + str_val = str(self.value) + int_prec = int(self._node.prec) + decimal_part = str_val[-int_prec:] + whole_part = str_val[:len(str_val) - int_prec] + val = float('{}.{}'.format(whole_part, decimal_part)) + raw_units = self.raw_unit_of_measurement + if raw_units in ( + TEMP_CELSIUS, TEMP_FAHRENHEIT): + val = self.hass.config.units.temperature(val, raw_units) + return str(val) + else: + return self.value -class WeatherPseudoNode(object): - """This class allows weather variable to act as regular nodes.""" + return None - # pylint: disable=too-few-public-methods - def __init__(self, device_id, name, status, units=None): - """Initialize the sensor.""" - self._id = device_id - self.name = name - self.status = status - self.units = units - - -class ISYSensorDevice(ISYDeviceABC): - """Representation of an ISY sensor.""" - - _domain = 'sensor' - - def __init__(self, node, states=None): - """Initialize the device.""" - super().__init__(node) - self._states = states or [] + @property + def unit_of_measurement(self) -> str: + """Get the unit of measurement for the ISY994 sensor device.""" + raw_units = self.raw_unit_of_measurement + if raw_units in (TEMP_FAHRENHEIT, TEMP_CELSIUS): + return self.hass.config.units.temperature_unit + else: + return raw_units diff --git a/homeassistant/components/switch/isy994.py b/homeassistant/components/switch/isy994.py index 004c82b8ad0..b930bedc2c7 100644 --- a/homeassistant/components/switch/isy994.py +++ b/homeassistant/components/switch/isy994.py @@ -2,87 +2,106 @@ Support for ISY994 switches. For more details about this platform, please refer to the documentation at -https://home-assistant.io/components/isy994/ +https://home-assistant.io/components/switch.isy994/ """ import logging +from typing import Callable # noqa -from homeassistant.components.isy994 import ( - HIDDEN_STRING, ISY, SENSOR_STRING, ISYDeviceABC) -from homeassistant.const import STATE_OFF, STATE_ON # STATE_OPEN, STATE_CLOSED +from homeassistant.components.switch import SwitchDevice, DOMAIN +import homeassistant.components.isy994 as isy +from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNKNOWN +from homeassistant.helpers.typing import ConfigType # noqa + +_LOGGER = logging.getLogger(__name__) + +VALUE_TO_STATE = { + False: STATE_OFF, + True: STATE_ON, +} + +UOM = ['2', '78'] +STATES = [STATE_OFF, STATE_ON, 'true', 'false'] -# The frontend doesn't seem to fully support the open and closed states yet. -# Once it does, the HA.doors programs should report open and closed instead of -# off and on. It appears that on should be open and off should be closed. - - -def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the ISY994 platform.""" - # pylint: disable=too-many-locals - logger = logging.getLogger(__name__) - devs = [] - # verify connection - if ISY is None or not ISY.connected: - logger.error('A connection has not been made to the ISY controller.') +# pylint: disable=unused-argument +def setup_platform(hass, config: ConfigType, + add_devices: Callable[[list], None], discovery_info=None): + """Set up the ISY994 switch platform.""" + if isy.ISY is None or not isy.ISY.connected: + _LOGGER.error('A connection has not been made to the ISY controller.') return False - # Import not dimmable nodes and groups - for (path, node) in ISY.nodes: - if not node.dimmable and SENSOR_STRING not in node.name: - if HIDDEN_STRING in path: - node.name += HIDDEN_STRING - devs.append(ISYSwitchDevice(node)) + devices = [] - # Import ISY doors programs - for folder_name, states in (('HA.doors', [STATE_ON, STATE_OFF]), - ('HA.switches', [STATE_ON, STATE_OFF])): + for node in isy.filter_nodes(isy.NODES, units=UOM, + states=STATES): + if not node.dimmable: + devices.append(ISYSwitchDevice(node)) + + for node in isy.GROUPS: + devices.append(ISYSwitchDevice(node)) + + for program in isy.PROGRAMS.get(DOMAIN, []): try: - folder = ISY.programs['My Programs'][folder_name] - except KeyError: - # HA.doors folder does not exist + status = program[isy.KEY_STATUS] + actions = program[isy.KEY_ACTIONS] + assert actions.dtype == 'program', 'Not a program' + except (KeyError, AssertionError): pass else: - for dtype, name, node_id in folder.children: - if dtype is 'folder': - custom_switch = folder[node_id] - try: - actions = custom_switch['actions'].leaf - assert actions.dtype == 'program', 'Not a program' - node = custom_switch['status'].leaf - except (KeyError, AssertionError): - pass - else: - devs.append(ISYProgramDevice(name, node, actions, - states)) + devices.append(ISYSwitchProgram(program.name, status, actions)) - add_devices(devs) + add_devices(devices) -class ISYSwitchDevice(ISYDeviceABC): - """Representation of an ISY switch.""" +class ISYSwitchDevice(isy.ISYDevice, SwitchDevice): + """Representation of an ISY994 switch device.""" - _domain = 'switch' - _dtype = 'binary' - _states = [STATE_ON, STATE_OFF] + def __init__(self, node) -> None: + """Initialize the ISY994 switch device.""" + isy.ISYDevice.__init__(self, node) + + @property + def is_on(self) -> bool: + """Get whether the ISY994 device is in the on state.""" + return self.state == STATE_ON + + @property + def state(self) -> str: + """Get the state of the ISY994 device.""" + return VALUE_TO_STATE.get(bool(self.value), STATE_UNKNOWN) + + def turn_off(self, **kwargs) -> None: + """Send the turn on command to the ISY994 switch.""" + if not self._node.off(): + _LOGGER.debug('Unable to turn on switch.') + + def turn_on(self, **kwargs) -> None: + """Send the turn off command to the ISY994 switch.""" + if not self._node.on(): + _LOGGER.debug('Unable to turn on switch.') -class ISYProgramDevice(ISYSwitchDevice): - """Representation of an ISY door.""" +class ISYSwitchProgram(ISYSwitchDevice): + """A representation of an ISY994 program switch.""" - _domain = 'switch' - _dtype = 'binary' - - def __init__(self, name, node, actions, states): - """Initialize the switch.""" - super().__init__(node) - self._states = states + def __init__(self, name: str, node, actions) -> None: + """Initialize the ISY994 switch program.""" + ISYSwitchDevice.__init__(self, node) self._name = name - self.action_node = actions + self._actions = actions - def turn_on(self, **kwargs): - """Turn the device on/close the device.""" - self.action_node.runThen() + @property + def is_on(self) -> bool: + """Get whether the ISY994 switch program is on.""" + return bool(self.value) - def turn_off(self, **kwargs): - """Turn the device off/open the device.""" - self.action_node.runElse() + def turn_on(self, **kwargs) -> None: + """Send the turn on command to the ISY994 switch program.""" + if not self._actions.runThen(): + _LOGGER.error('Unable to turn on switch') + + def turn_off(self, **kwargs) -> None: + """Send the turn off command to the ISY994 switch program.""" + if not self._actions.runElse(): + _LOGGER.error('Unable to turn off switch') diff --git a/requirements_all.txt b/requirements_all.txt index 61d221ba7fb..731f8c25439 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -8,7 +8,7 @@ voluptuous==0.9.2 typing>=3,<4 # homeassistant.components.isy994 -PyISY==1.0.6 +PyISY==1.0.7 # homeassistant.components.notify.html5 PyJWT==1.4.2 From 515c4773f3a92a1afd1de09ec31913c89c209370 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 11 Sep 2016 21:27:58 +0200 Subject: [PATCH 019/162] Use voluptuous for netatmo (#3287) * Migrate to voluptuous * Switch back to archive (reverting #3285) --- homeassistant/components/netatmo.py | 40 +++++------ homeassistant/components/sensor/netatmo.py | 79 ++++++++++++---------- requirements_all.txt | 2 +- 3 files changed, 67 insertions(+), 54 deletions(-) diff --git a/homeassistant/components/netatmo.py b/homeassistant/components/netatmo.py index f56c9b515b9..2769f8e2f73 100644 --- a/homeassistant/components/netatmo.py +++ b/homeassistant/components/netatmo.py @@ -6,46 +6,48 @@ https://home-assistant.io/components/netatmo/ """ import logging from urllib.error import HTTPError + +import voluptuous as vol + from homeassistant.const import ( CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME) -from homeassistant.helpers import validate_config, discovery +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv REQUIREMENTS = [ 'https://github.com/jabesq/netatmo-api-python/archive/' - 'master.zip#lnetatmo==0.5.0'] + 'v0.5.0.zip#lnetatmo==0.5.0'] _LOGGER = logging.getLogger(__name__) CONF_SECRET_KEY = 'secret_key' DOMAIN = 'netatmo' + NETATMO_AUTH = None +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_SECRET_KEY): cv.string, + vol.Required(CONF_USERNAME): cv.string, + }) +}, extra=vol.ALLOW_EXTRA) + def setup(hass, config): """Setup the Netatmo devices.""" - if not validate_config(config, - {DOMAIN: [CONF_API_KEY, - CONF_USERNAME, - CONF_PASSWORD, - CONF_SECRET_KEY]}, - _LOGGER): - return None - import lnetatmo global NETATMO_AUTH try: - NETATMO_AUTH = lnetatmo.ClientAuth(config[DOMAIN][CONF_API_KEY], - config[DOMAIN][CONF_SECRET_KEY], - config[DOMAIN][CONF_USERNAME], - config[DOMAIN][CONF_PASSWORD], - "read_station read_camera " - "access_camera") + NETATMO_AUTH = lnetatmo.ClientAuth( + config[DOMAIN][CONF_API_KEY], config[DOMAIN][CONF_SECRET_KEY], + config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD], + 'read_station read_camera access_camera') except HTTPError: - _LOGGER.error( - "Connection error " - "Please check your settings for NatAtmo API.") + _LOGGER.error("Unable to connect to NatAtmo API") return False for component in 'camera', 'sensor': diff --git a/homeassistant/components/sensor/netatmo.py b/homeassistant/components/sensor/netatmo.py index 05498d41496..c3588e24853 100644 --- a/homeassistant/components/sensor/netatmo.py +++ b/homeassistant/components/sensor/netatmo.py @@ -6,44 +6,59 @@ https://home-assistant.io/components/sensor.netatmo/ """ import logging from datetime import timedelta + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle from homeassistant.loader import get_component - - -DEPENDENCIES = ["netatmo"] +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -SENSOR_TYPES = { - 'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'], - 'co2': ['CO2', 'ppm', 'mdi:cloud'], - 'pressure': ['Pressure', 'mbar', 'mdi:gauge'], - 'noise': ['Noise', 'dB', 'mdi:volume-high'], - 'humidity': ['Humidity', '%', 'mdi:water-percent'], - 'rain': ['Rain', 'mm', 'mdi:weather-rainy'], - 'sum_rain_1': ['sum_rain_1', 'mm', 'mdi:weather-rainy'], - 'sum_rain_24': ['sum_rain_24', 'mm', 'mdi:weather-rainy'], - 'battery_vp': ['Battery', '', 'mdi:battery'], - 'min_temp': ['Min Temp.', TEMP_CELSIUS, 'mdi:thermometer'], - 'max_temp': ['Max Temp.', TEMP_CELSIUS, 'mdi:thermometer'], - 'WindAngle': ['Angle', '', 'mdi:compass'], - 'WindStrength': ['Strength', 'km/h', 'mdi:weather-windy'], - 'GustAngle': ['Gust Angle', '', 'mdi:compass'], - 'GustStrength': ['Gust Strength', 'km/h', 'mdi:weather-windy'], - 'rf_status': ['Radio', '', 'mdi:signal'], - 'wifi_status': ['Wifi', '', 'mdi:wifi'] -} - -CONF_STATION = 'station' ATTR_MODULE = 'modules' -# Return cached results if last scan was less then this time ago -# NetAtmo Data is uploaded to server every 10mn -# so this time should not be under +CONF_MODULES = 'modules' +CONF_MODULE_NAME = 'module_name' +CONF_STATION = 'station' + +DEPENDENCIES = ['netatmo'] + +# NetAtmo Data is uploaded to server every 10 minutes MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600) +SENSOR_TYPES = { + 'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'], + 'co2': ['CO2', 'ppm', 'mdi:cloud'], + 'pressure': ['Pressure', 'mbar', 'mdi:gauge'], + 'noise': ['Noise', 'dB', 'mdi:volume-high'], + 'humidity': ['Humidity', '%', 'mdi:water-percent'], + 'rain': ['Rain', 'mm', 'mdi:weather-rainy'], + 'sum_rain_1': ['sum_rain_1', 'mm', 'mdi:weather-rainy'], + 'sum_rain_24': ['sum_rain_24', 'mm', 'mdi:weather-rainy'], + 'battery_vp': ['Battery', '', 'mdi:battery'], + 'min_temp': ['Min Temp.', TEMP_CELSIUS, 'mdi:thermometer'], + 'max_temp': ['Max Temp.', TEMP_CELSIUS, 'mdi:thermometer'], + 'WindAngle': ['Angle', '', 'mdi:compass'], + 'WindStrength': ['Strength', 'km/h', 'mdi:weather-windy'], + 'GustAngle': ['Gust Angle', '', 'mdi:compass'], + 'GustStrength': ['Gust Strength', 'km/h', 'mdi:weather-windy'], + 'rf_status': ['Radio', '', 'mdi:signal'], + 'wifi_status': ['Wifi', '', 'mdi:wifi'] +} + +MODULE_SCHEMA = vol.Schema({ + vol.Required(CONF_MODULE_NAME, default=[]): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_STATION): cv.string, + vol.Required(CONF_MODULES): MODULE_SCHEMA, +}) + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the available Netatmo weather sensors.""" @@ -53,18 +68,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): dev = [] try: # Iterate each module - for module_name, monitored_conditions in config[ATTR_MODULE].items(): + for module_name, monitored_conditions in config[CONF_MODULES].items(): # Test if module exist """ if module_name not in data.get_module_names(): _LOGGER.error('Module name: "%s" not found', module_name) continue # Only create sensor for monitored """ for variable in monitored_conditions: - if variable not in SENSOR_TYPES: - _LOGGER.error('Sensor type: "%s" does not exist', variable) - else: - dev.append( - NetAtmoSensor(data, module_name, variable)) + dev.append(NetAtmoSensor(data, module_name, variable)) except KeyError: pass @@ -77,7 +88,7 @@ class NetAtmoSensor(Entity): def __init__(self, netatmo_data, module_name, sensor_type): """Initialize the sensor.""" - self._name = "NetAtmo {} {}".format(module_name, + self._name = 'NetAtmo {} {}'.format(module_name, SENSOR_TYPES[sensor_type][0]) self.netatmo_data = netatmo_data self.module_name = module_name diff --git a/requirements_all.txt b/requirements_all.txt index 731f8c25439..6652a653c22 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -164,7 +164,7 @@ https://github.com/danieljkemp/onkyo-eiscp/archive/python3.zip#onkyo-eiscp==0.9. https://github.com/gadgetreactor/pyHS100/archive/master.zip#pyHS100==0.1.2 # homeassistant.components.netatmo -https://github.com/jabesq/netatmo-api-python/archive/master.zip#lnetatmo==0.5.0 +https://github.com/jabesq/netatmo-api-python/archive/v0.5.0.zip#lnetatmo==0.5.0 # homeassistant.components.sensor.sabnzbd https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 From dba78b02daa5674769cccf56b867f5266d6ec0f1 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Mon, 12 Sep 2016 01:12:28 +0200 Subject: [PATCH 020/162] Add voluptuous to locative (#3254) --- .../components/device_tracker/locative.py | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py index 1a0ec457304..f3f2c3c94f5 100644 --- a/homeassistant/components/device_tracker/locative.py +++ b/homeassistant/components/device_tracker/locative.py @@ -6,9 +6,11 @@ https://home-assistant.io/components/device_tracker.locative/ """ import logging -from homeassistant.components.device_tracker import DOMAIN from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME from homeassistant.components.http import HomeAssistantView +# pylint: disable=unused-import +from homeassistant.components.device_tracker import ( # NOQA + DOMAIN, PLATFORM_SCHEMA) _LOGGER = logging.getLogger(__name__) @@ -25,8 +27,8 @@ def setup_scanner(hass, config, see): class LocativeView(HomeAssistantView): """View to handle locative requests.""" - url = "/api/locative" - name = "api:locative" + url = '/api/locative' + name = 'api:locative' def __init__(self, hass, see): """Initialize Locative url endpoints.""" @@ -43,22 +45,22 @@ class LocativeView(HomeAssistantView): data = request.values if 'latitude' not in data or 'longitude' not in data: - return ("Latitude and longitude not specified.", + return ('Latitude and longitude not specified.', HTTP_UNPROCESSABLE_ENTITY) if 'device' not in data: - _LOGGER.error("Device id not specified.") - return ("Device id not specified.", + _LOGGER.error('Device id not specified.') + return ('Device id not specified.', HTTP_UNPROCESSABLE_ENTITY) if 'id' not in data: - _LOGGER.error("Location id not specified.") - return ("Location id not specified.", + _LOGGER.error('Location id not specified.') + return ('Location id not specified.', HTTP_UNPROCESSABLE_ENTITY) if 'trigger' not in data: - _LOGGER.error("Trigger is not specified.") - return ("Trigger is not specified.", + _LOGGER.error('Trigger is not specified.') + return ('Trigger is not specified.', HTTP_UNPROCESSABLE_ENTITY) device = data['device'].replace('-', '') @@ -67,15 +69,15 @@ class LocativeView(HomeAssistantView): if direction == 'enter': self.see(dev_id=device, location_name=location_name) - return "Setting location to {}".format(location_name) + return 'Setting location to {}'.format(location_name) elif direction == 'exit': current_state = self.hass.states.get( - "{}.{}".format(DOMAIN, device)) + '{}.{}'.format(DOMAIN, device)) if current_state is None or current_state.state == location_name: self.see(dev_id=device, location_name=STATE_NOT_HOME) - return "Setting location to not home" + return 'Setting location to not home' else: # Ignore the message if it is telling us to exit a zone that we # aren't currently in. This occurs when a zone is entered @@ -87,10 +89,10 @@ class LocativeView(HomeAssistantView): elif direction == 'test': # In the app, a test message can be sent. Just return something to # the user to let them know that it works. - return "Received test message." + return 'Received test message.' else: - _LOGGER.error("Received unidentified message from Locative: %s", + _LOGGER.error('Received unidentified message from Locative: %s', direction) - return ("Received unidentified message: {}".format(direction), + return ('Received unidentified message: {}'.format(direction), HTTP_UNPROCESSABLE_ENTITY) From 26abe83be581d7397fe559224581944160474c41 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Mon, 12 Sep 2016 06:46:14 +0200 Subject: [PATCH 021/162] Revert only add 1 device (#3324) --- homeassistant/components/climate/zwave.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py index 0ba85105c18..6c08bf391d8 100755 --- a/homeassistant/components/climate/zwave.py +++ b/homeassistant/components/climate/zwave.py @@ -70,8 +70,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None): node = zwave.NETWORK.nodes[discovery_info[ATTR_NODE_ID]] value = node.values[discovery_info[ATTR_VALUE_ID]] value.set_change_verified(False) - if value.index != 1: # Only add 1 device - return add_devices([ZWaveClimate(value, temp_unit)]) _LOGGER.debug("discovery_info=%s and zwave.NETWORK=%s", discovery_info, zwave.NETWORK) From 360a65037096ddfd9e956b4883b610e24fbc1578 Mon Sep 17 00:00:00 2001 From: Teagan Glenn Date: Sun, 11 Sep 2016 22:53:05 -0600 Subject: [PATCH 022/162] Automatic Device Tracker Bug Fix (#3330) * Iterate over items * Pass display name as host name --- homeassistant/components/device_tracker/__init__.py | 2 +- homeassistant/components/device_tracker/automatic.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 4247213087b..236fde6fb3f 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -338,7 +338,7 @@ class Device(Entity): attr[ATTR_BATTERY] = self.battery if self.attributes: - for key, value in self.attributes: + for key, value in self.attributes.items(): attr[key] = value return attr diff --git a/homeassistant/components/device_tracker/automatic.py b/homeassistant/components/device_tracker/automatic.py index 927c515b3a5..7855323ba06 100644 --- a/homeassistant/components/device_tracker/automatic.py +++ b/homeassistant/components/device_tracker/automatic.py @@ -142,6 +142,7 @@ class AutomaticDeviceScanner(object): for vehicle in self.last_results: dev_id = vehicle.get('id') + host_name = vehicle.get('display_name') attrs = { 'fuel_level': vehicle.get('fuel_level_percent') @@ -149,6 +150,7 @@ class AutomaticDeviceScanner(object): kwargs = { 'dev_id': dev_id, + 'host_name': host_name, 'mac': dev_id, ATTR_ATTRIBUTES: attrs } From fa4b25387185f368bfacd5d4fd2b927d7aaf6621 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 11 Sep 2016 21:59:48 -0700 Subject: [PATCH 023/162] Comment out pyuserinput in requirements_all (#3307) * Comment out pyuserinput in requirements_all * Ignore import error for keyboard component --- homeassistant/components/keyboard.py | 1 + requirements_all.txt | 2 +- script/gen_requirements_all.py | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/keyboard.py b/homeassistant/components/keyboard.py index 65f94c730bc..697b5b6873c 100644 --- a/homeassistant/components/keyboard.py +++ b/homeassistant/components/keyboard.py @@ -50,6 +50,7 @@ def media_prev_track(hass): def setup(hass, config): """Listen for keyboard events.""" + # pylint: disable=import-error import pykeyboard keyboard = pykeyboard.PyKeyboard() diff --git a/requirements_all.txt b/requirements_all.txt index 6652a653c22..0eced3bca5b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -397,7 +397,7 @@ python-twitch==1.3.0 python-wink==0.7.14 # homeassistant.components.keyboard -pyuserinput==0.1.11 +# pyuserinput==0.1.11 # homeassistant.components.vera pyvera==0.2.15 diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 8621cb24c95..65ffa26116a 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -14,7 +14,8 @@ COMMENT_REQUIREMENTS = ( 'pybluez', 'bluepy', 'python-lirc', - 'gattlib' + 'gattlib', + 'pyuserinput', ) IGNORE_PACKAGES = ( From 838b09bb8f61f46b632df4d7a59b77d79611f375 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 11 Sep 2016 22:25:01 -0700 Subject: [PATCH 024/162] Bugfix group order (#3323) * Add ordered dict config validator * Have group component use ordered dict config validator * Improve config_validation testing * update doc string config_validation.ordered_dict * validate full dict entries * Further simplify ordered_dict validator. * Lint fix --- homeassistant/components/group.py | 4 +- homeassistant/helpers/config_validation.py | 22 ++++++++++ tests/components/test_group.py | 17 +++++--- tests/helpers/test_config_validation.py | 50 ++++++++++++++++++++++ 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index c4cd177925d..41901d87e86 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -46,12 +46,12 @@ def _conf_preprocess(value): CONFIG_SCHEMA = vol.Schema({ - DOMAIN: {cv.match_all: vol.Schema(vol.All(_conf_preprocess, { + DOMAIN: cv.ordered_dict(vol.All(_conf_preprocess, { vol.Optional(CONF_ENTITIES): vol.Any(cv.entity_ids, None), CONF_VIEW: cv.boolean, CONF_NAME: cv.string, CONF_ICON: cv.icon, - }))} + }, cv.match_all)) }, extra=vol.ALLOW_EXTRA) # List of ON/OFF state tuples for groupable states diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 1be157c789d..009736024a1 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -1,4 +1,5 @@ """Helpers for config validation using voluptuous.""" +from collections import OrderedDict from datetime import timedelta import os from urllib.parse import urlparse @@ -290,6 +291,27 @@ def url(value: Any) -> str: raise vol.Invalid('invalid url') +def ordered_dict(value_validator, key_validator=match_all): + """Validate an ordered dict validator that maintains ordering. + + value_validator will be applied to each value of the dictionary. + key_validator (optional) will be applied to each key of the dictionary. + """ + item_validator = vol.Schema({key_validator: value_validator}) + + def validator(value): + """Validate ordered dict.""" + config = OrderedDict() + + for key, val in value.items(): + v_res = item_validator({key: val}) + config.update(v_res) + + return config + + return validator + + # Validator helpers def key_dependency(key, dependency): diff --git a/tests/components/test_group.py b/tests/components/test_group.py index e82190a3f29..6c601a411fb 100644 --- a/tests/components/test_group.py +++ b/tests/components/test_group.py @@ -1,5 +1,6 @@ """The tests for the Group components.""" # pylint: disable=protected-access,too-many-public-methods +from collections import OrderedDict import unittest from unittest.mock import patch @@ -220,16 +221,16 @@ class TestComponentsGroup(unittest.TestCase): test_group = group.Group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) - _setup_component(self.hass, 'group', {'group': { - 'second_group': { + group_conf = OrderedDict() + group_conf['second_group'] = { 'entities': 'light.Bowl, ' + test_group.entity_id, 'icon': 'mdi:work', 'view': True, - }, - 'test_group': 'hello.world,sensor.happy', - 'empty_group': {'name': 'Empty Group', 'entities': None}, - } - }) + } + group_conf['test_group'] = 'hello.world,sensor.happy' + group_conf['empty_group'] = {'name': 'Empty Group', 'entities': None} + + _setup_component(self.hass, 'group', {'group': group_conf}) group_state = self.hass.states.get( group.ENTITY_ID_FORMAT.format('second_group')) @@ -241,6 +242,7 @@ class TestComponentsGroup(unittest.TestCase): group_state.attributes.get(ATTR_ICON)) self.assertTrue(group_state.attributes.get(group.ATTR_VIEW)) self.assertTrue(group_state.attributes.get(ATTR_HIDDEN)) + self.assertEqual(1, group_state.attributes.get(group.ATTR_ORDER)) group_state = self.hass.states.get( group.ENTITY_ID_FORMAT.format('test_group')) @@ -251,6 +253,7 @@ class TestComponentsGroup(unittest.TestCase): self.assertIsNone(group_state.attributes.get(ATTR_ICON)) self.assertIsNone(group_state.attributes.get(group.ATTR_VIEW)) self.assertIsNone(group_state.attributes.get(ATTR_HIDDEN)) + self.assertEqual(2, group_state.attributes.get(group.ATTR_ORDER)) def test_groups_get_unique_names(self): """Two groups with same name should both have a unique entity id.""" diff --git a/tests/helpers/test_config_validation.py b/tests/helpers/test_config_validation.py index d9da2c51da7..60b14757378 100644 --- a/tests/helpers/test_config_validation.py +++ b/tests/helpers/test_config_validation.py @@ -1,3 +1,5 @@ +"""Test config validators.""" +from collections import OrderedDict from datetime import timedelta import os import tempfile @@ -367,3 +369,51 @@ def test_has_at_least_one_key(): for value in ({'beer': None}, {'soda': None}): schema(value) + + +def test_ordered_dict_order(): + """Test ordered_dict validator.""" + schema = vol.Schema(cv.ordered_dict(int, cv.string)) + + val = OrderedDict() + val['first'] = 1 + val['second'] = 2 + + validated = schema(val) + + assert isinstance(validated, OrderedDict) + assert ['first', 'second'] == list(validated.keys()) + + +def test_ordered_dict_key_validator(): + """Test ordered_dict key validator.""" + schema = vol.Schema(cv.ordered_dict(cv.match_all, cv.string)) + + with pytest.raises(vol.Invalid): + schema({None: 1}) + + schema({'hello': 'world'}) + + schema = vol.Schema(cv.ordered_dict(cv.match_all, int)) + + with pytest.raises(vol.Invalid): + schema({'hello': 1}) + + schema({1: 'works'}) + + +def test_ordered_dict_value_validator(): + """Test ordered_dict validator.""" + schema = vol.Schema(cv.ordered_dict(cv.string)) + + with pytest.raises(vol.Invalid): + schema({'hello': None}) + + schema({'hello': 'world'}) + + schema = vol.Schema(cv.ordered_dict(int)) + + with pytest.raises(vol.Invalid): + schema({'hello': 'world'}) + + schema({'hello': 5}) From c90cc77c41690a07aa6b7c11ea1b259dbed6804e Mon Sep 17 00:00:00 2001 From: clach04 Date: Sun, 11 Sep 2016 22:56:12 -0700 Subject: [PATCH 025/162] bluetooth_le_tracker clarify header with LE (#3328) --- homeassistant/components/device_tracker/bluetooth_le_tracker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/device_tracker/bluetooth_le_tracker.py b/homeassistant/components/device_tracker/bluetooth_le_tracker.py index 9ee30dd0ce2..5b5b9ce2411 100644 --- a/homeassistant/components/device_tracker/bluetooth_le_tracker.py +++ b/homeassistant/components/device_tracker/bluetooth_le_tracker.py @@ -1,4 +1,4 @@ -"""Tracking for bluetooth devices.""" +"""Tracking for bluetooth low energy devices.""" import logging from datetime import timedelta From 44681ebd554c9e7ec9886c13977f90f7ada8f520 Mon Sep 17 00:00:00 2001 From: Richard Cox Date: Mon, 12 Sep 2016 00:49:26 -0700 Subject: [PATCH 026/162] Slack notification optional username / icon (#3314) * Slack notification optional username / icon * Small bugfix to handle defaults better * Dedup'ing code --- homeassistant/components/notify/slack.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/notify/slack.py b/homeassistant/components/notify/slack.py index 780a27b9795..aded8cd56a9 100644 --- a/homeassistant/components/notify/slack.py +++ b/homeassistant/components/notify/slack.py @@ -10,7 +10,8 @@ import voluptuous as vol from homeassistant.components.notify import ( PLATFORM_SCHEMA, BaseNotificationService) -from homeassistant.const import CONF_API_KEY +from homeassistant.const import ( + CONF_API_KEY, CONF_USERNAME, CONF_ICON) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['slacker==0.9.25'] @@ -22,6 +23,8 @@ CONF_CHANNEL = 'default_channel' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_API_KEY): cv.string, vol.Required(CONF_CHANNEL): cv.string, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_ICON): cv.string, }) @@ -33,7 +36,9 @@ def get_service(hass, config): try: return SlackNotificationService( config[CONF_CHANNEL], - config[CONF_API_KEY]) + config[CONF_API_KEY], + config.get(CONF_USERNAME, None), + config.get(CONF_ICON, None)) except slacker.Error: _LOGGER.exception("Slack authentication failed") @@ -44,11 +49,18 @@ def get_service(hass, config): class SlackNotificationService(BaseNotificationService): """Implement the notification service for Slack.""" - def __init__(self, default_channel, api_token): + def __init__(self, default_channel, api_token, username, icon): """Initialize the service.""" from slacker import Slacker self._default_channel = default_channel self._api_token = api_token + self._username = username + self._icon = icon + if self._username or self._icon: + self._as_user = False + else: + self._as_user = True + self.slack = Slacker(self._api_token) self.slack.auth.test() @@ -62,7 +74,9 @@ class SlackNotificationService(BaseNotificationService): try: self.slack.chat.post_message(channel, message, - as_user=True, + as_user=self._as_user, + username=self._username, + icon_emoji=self._icon, attachments=attachments, link_names=True) except slacker.Error as err: From c028e1fc6f4b0f6205a11364805466999e9c7a74 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 12 Sep 2016 15:52:22 +0200 Subject: [PATCH 027/162] Update ordering, constants, and callback name (light.*) (#3339) * Extend schema * Update ordering * Add line breaks * Align callback name with other platforms * ALign callbackname with other platforms * Update callback name to match other platforms * Update callback name * Update callback name --- .../components/light/blinksticklight.py | 13 +++-- homeassistant/components/light/enocean.py | 8 +-- homeassistant/components/light/flux_led.py | 52 +++++++++---------- homeassistant/components/light/homematic.py | 4 +- homeassistant/components/light/rfxtrx.py | 6 +-- homeassistant/components/light/vera.py | 15 +++--- homeassistant/components/light/wemo.py | 10 ++-- homeassistant/components/light/wink.py | 13 +++-- 8 files changed, 62 insertions(+), 59 deletions(-) diff --git a/homeassistant/components/light/blinksticklight.py b/homeassistant/components/light/blinksticklight.py index 627097d47b9..700840fb4bd 100644 --- a/homeassistant/components/light/blinksticklight.py +++ b/homeassistant/components/light/blinksticklight.py @@ -8,14 +8,19 @@ import logging import voluptuous as vol -from homeassistant.components.light import (ATTR_RGB_COLOR, SUPPORT_RGB_COLOR, - Light, PLATFORM_SCHEMA) +from homeassistant.components.light import ( + ATTR_RGB_COLOR, SUPPORT_RGB_COLOR, Light, PLATFORM_SCHEMA) from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv +REQUIREMENTS = ['blinkstick==1.1.8'] + +_LOGGER = logging.getLogger(__name__) + CONF_SERIAL = 'serial' + DEFAULT_NAME = 'Blinkstick' -REQUIREMENTS = ["blinkstick==1.1.8"] + SUPPORT_BLINKSTICK = SUPPORT_RGB_COLOR PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -23,8 +28,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) -_LOGGER = logging.getLogger(__name__) - # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): diff --git a/homeassistant/components/light/enocean.py b/homeassistant/components/light/enocean.py index 772cb55c4e4..ce65d8cc041 100644 --- a/homeassistant/components/light/enocean.py +++ b/homeassistant/components/light/enocean.py @@ -17,10 +17,12 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['enocean'] -DEFAULT_NAME = 'EnOcean Light' CONF_SENDER_ID = 'sender_id' +DEFAULT_NAME = 'EnOcean Light' + +DEPENDENCIES = ['enocean'] + SUPPORT_ENOCEAN = SUPPORT_BRIGHTNESS PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -50,7 +52,7 @@ class EnOceanLight(enocean.EnOceanDevice, Light): self._sender_id = sender_id self.dev_id = dev_id self._devname = devname - self.stype = "dimmer" + self.stype = 'dimmer' @property def name(self): diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py index 0d48e4c794b..5b25b86b9b9 100644 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -4,56 +4,57 @@ Support for Flux lights. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/light.flux_led/ """ - import logging import socket import random + import voluptuous as vol -from homeassistant.components.light import (ATTR_BRIGHTNESS, ATTR_RGB_COLOR, - ATTR_EFFECT, EFFECT_RANDOM, - SUPPORT_BRIGHTNESS, - SUPPORT_EFFECT, - SUPPORT_RGB_COLOR, Light) +from homeassistant.const import CONF_DEVICES, CONF_NAME +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_RGB_COLOR, ATTR_EFFECT, EFFECT_RANDOM, + SUPPORT_BRIGHTNESS, SUPPORT_EFFECT, SUPPORT_RGB_COLOR, Light, + PLATFORM_SCHEMA) import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['https://github.com/Danielhiversen/flux_led/archive/0.6.zip' '#flux_led==0.6'] _LOGGER = logging.getLogger(__name__) -DOMAIN = "flux_led" -ATTR_NAME = 'name' -DEVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_NAME): cv.string, -}) +CONF_AUTOMATIC_ADD = 'automatic_add' -PLATFORM_SCHEMA = vol.Schema({ - vol.Required('platform'): DOMAIN, - vol.Optional('devices', default={}): {cv.string: DEVICE_SCHEMA}, - vol.Optional('automatic_add', default=False): cv.boolean, -}, extra=vol.ALLOW_EXTRA) +DOMAIN = 'flux_led' SUPPORT_FLUX_LED = (SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | SUPPORT_RGB_COLOR) +DEVICE_SCHEMA = vol.Schema({ + vol.Optional(CONF_NAME): cv.string, +}) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_DEVICES, default={}): {cv.string: DEVICE_SCHEMA}, + vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Flux lights.""" import flux_led lights = [] light_ips = [] - for ipaddr, device_config in config["devices"].items(): + for ipaddr, device_config in config[CONF_DEVICES].items(): device = {} - device['name'] = device_config[ATTR_NAME] + device['name'] = device_config[CONF_NAME] device['ipaddr'] = ipaddr light = FluxLight(device) if light.is_valid: lights.append(light) light_ips.append(ipaddr) - if not config['automatic_add']: - add_devices_callback(lights) + if not config[CONF_AUTOMATIC_ADD]: + add_devices(lights) return # Find the bulbs on the LAN @@ -69,7 +70,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): lights.append(light) light_ips.append(ipaddr) - add_devices_callback(lights) + add_devices(lights) class FluxLight(Light): @@ -88,14 +89,13 @@ class FluxLight(Light): self._bulb = flux_led.WifiLedBulb(self._ipaddr) except socket.error: self.is_valid = False - _LOGGER.error("Failed to connect to bulb %s, %s", - self._ipaddr, self._name) + _LOGGER.error( + "Failed to connect to bulb %s, %s", self._ipaddr, self._name) @property def unique_id(self): """Return the ID of this light.""" - return "{}.{}".format( - self.__class__, self._ipaddr) + return "{}.{}".format(self.__class__, self._ipaddr) @property def name(self): diff --git a/homeassistant/components/light/homematic.py b/homeassistant/components/light/homematic.py index 3f8eb1a22a5..299791939c8 100644 --- a/homeassistant/components/light/homematic.py +++ b/homeassistant/components/light/homematic.py @@ -17,7 +17,7 @@ DEPENDENCIES = ['homematic'] SUPPORT_HOMEMATIC = SUPPORT_BRIGHTNESS -def setup_platform(hass, config, add_callback_devices, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Homematic light platform.""" if discovery_info is None: return @@ -25,7 +25,7 @@ def setup_platform(hass, config, add_callback_devices, discovery_info=None): return homematic.setup_hmdevice_discovery_helper( HMLight, discovery_info, - add_callback_devices + add_devices ) diff --git a/homeassistant/components/light/rfxtrx.py b/homeassistant/components/light/rfxtrx.py index f63a03c0534..623b42d77ad 100644 --- a/homeassistant/components/light/rfxtrx.py +++ b/homeassistant/components/light/rfxtrx.py @@ -19,12 +19,12 @@ PLATFORM_SCHEMA = rfxtrx.DEFAULT_SCHEMA SUPPORT_RFXTRX = SUPPORT_BRIGHTNESS -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the RFXtrx platform.""" import RFXtrx as rfxtrxmod lights = rfxtrx.get_devices_from_config(config, RfxtrxLight) - add_devices_callback(lights) + add_devices(lights) def light_update(event): """Callback for light updates from the RFXtrx gateway.""" @@ -34,7 +34,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): new_device = rfxtrx.get_new_device(event, config, RfxtrxLight) if new_device: - add_devices_callback([new_device]) + add_devices([new_device]) rfxtrx.apply_received_command(event) diff --git a/homeassistant/components/light/vera.py b/homeassistant/components/light/vera.py index 9e169ce4412..59b309e42aa 100644 --- a/homeassistant/components/light/vera.py +++ b/homeassistant/components/light/vera.py @@ -6,24 +6,23 @@ https://home-assistant.io/components/light.vera/ """ import logging -from homeassistant.components.light import (ATTR_BRIGHTNESS, - SUPPORT_BRIGHTNESS, Light) -from homeassistant.const import ( - STATE_OFF, STATE_ON) +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, SUPPORT_BRIGHTNESS, Light) +from homeassistant.const import (STATE_OFF, STATE_ON) from homeassistant.components.vera import ( VeraDevice, VERA_DEVICES, VERA_CONTROLLER) -DEPENDENCIES = ['vera'] - _LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['vera'] + SUPPORT_VERA = SUPPORT_BRIGHTNESS # pylint: disable=unused-argument -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup Vera lights.""" - add_devices_callback( + add_devices( VeraLight(device, VERA_CONTROLLER) for device in VERA_DEVICES['light']) diff --git a/homeassistant/components/light/wemo.py b/homeassistant/components/light/wemo.py index e1f741f4c73..41143246931 100644 --- a/homeassistant/components/light/wemo.py +++ b/homeassistant/components/light/wemo.py @@ -25,7 +25,7 @@ SUPPORT_WEMO = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_RGB_COLOR | SUPPORT_TRANSITION | SUPPORT_XY_COLOR) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup WeMo bridges and register connected lights.""" import pywemo.discovery as discovery @@ -35,10 +35,10 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): device = discovery.device_from_description(location, mac) if device: - setup_bridge(device, add_devices_callback) + setup_bridge(device, add_devices) -def setup_bridge(bridge, add_devices_callback): +def setup_bridge(bridge, add_devices): """Setup a WeMo link.""" lights = {} @@ -55,7 +55,7 @@ def setup_bridge(bridge, add_devices_callback): new_lights.append(lights[light_id]) if new_lights: - add_devices_callback(new_lights) + add_devices(new_lights) update_lights() @@ -73,7 +73,7 @@ class WemoLight(Light): def unique_id(self): """Return the ID of this light.""" deviceid = self.device.uniqueID - return "{}.{}".format(self.__class__, deviceid) + return '{}.{}'.format(self.__class__, deviceid) @property def name(self): diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/light/wink.py index c41d88a9c30..5e479b8b47c 100644 --- a/homeassistant/components/light/wink.py +++ b/homeassistant/components/light/wink.py @@ -7,9 +7,9 @@ https://home-assistant.io/components/light.wink/ import logging import colorsys -from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, \ - ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, \ - SUPPORT_RGB_COLOR, Light +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, SUPPORT_BRIGHTNESS, + SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, Light) from homeassistant.components.wink import WinkDevice from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.util import color as color_util @@ -21,8 +21,8 @@ REQUIREMENTS = ['python-wink==0.7.14', 'pubnub==3.8.2'] SUPPORT_WINK = SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_RGB_COLOR -def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup Wink lights.""" +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Wink lights.""" import pywink token = config.get(CONF_ACCESS_TOKEN) @@ -36,8 +36,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): elif token is not None: pywink.set_bearer_token(token) - add_devices_callback( - WinkLight(light) for light in pywink.get_bulbs()) + add_devices(WinkLight(light) for light in pywink.get_bulbs()) class WinkLight(WinkDevice, Light): From ac063f8e61dd9bd5718d01dc989c563ef19fa626 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Mon, 12 Sep 2016 16:19:46 +0200 Subject: [PATCH 028/162] Fix typo (#3343) --- homeassistant/components/bloomsky.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/bloomsky.py b/homeassistant/components/bloomsky.py index e610082951b..dd27a0ba954 100644 --- a/homeassistant/components/bloomsky.py +++ b/homeassistant/components/bloomsky.py @@ -65,7 +65,7 @@ class BloomSky(object): @Throttle(MIN_TIME_BETWEEN_UPDATES) def refresh_devices(self): - """Use the API to retreive a list of devices.""" + """Use the API to retrieve a list of devices.""" _LOGGER.debug("Fetching BloomSky update") response = requests.get(self.API_URL, headers={"Authorization": self._api_key}, From 240cb9b8f03b5fe44c7958153603da41ab040853 Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Mon, 12 Sep 2016 16:58:49 +0200 Subject: [PATCH 029/162] Minor naming fix (#3318) --- homeassistant/components/sensor/xbox_live.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/xbox_live.py b/homeassistant/components/sensor/xbox_live.py index 90983e1df83..9552eea1001 100644 --- a/homeassistant/components/sensor/xbox_live.py +++ b/homeassistant/components/sensor/xbox_live.py @@ -11,6 +11,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import (CONF_API_KEY, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity +from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) @@ -75,7 +76,7 @@ class XboxSensor(Entity): @property def entity_id(self): """Return the entity ID.""" - return 'sensor.xbox_' + self._gamertag + return 'sensor.xbox_' + slugify(self._gamertag) @property def state(self): From a69c575dab90c2bf7f7ebe6f8faf88ebcc8f9994 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Mon, 12 Sep 2016 17:40:46 +0200 Subject: [PATCH 030/162] Bugfix ecobee: inverted high and low temps and enforce int to temps (#3325) * inverted high and low temps * Looks like somethings are mixed up * Added debugentires * Added debugentires 2 * Enforce int on temperatures --- homeassistant/components/climate/ecobee.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/climate/ecobee.py b/homeassistant/components/climate/ecobee.py index 5d78aeb8597..21a2c89c31a 100644 --- a/homeassistant/components/climate/ecobee.py +++ b/homeassistant/components/climate/ecobee.py @@ -116,9 +116,6 @@ class Thermostat(ClimateDevice): return self.target_temperature_low elif self.operation_mode == 'cool': return self.target_temperature_high - else: - return (self.target_temperature_low + - self.target_temperature_high) / 2 @property def target_temperature_low(self): @@ -223,19 +220,27 @@ class Thermostat(ClimateDevice): """Set new target temperature.""" if kwargs.get(ATTR_TEMPERATURE) is not None: temperature = kwargs.get(ATTR_TEMPERATURE) - low_temp = temperature - 1 - high_temp = temperature + 1 + low_temp = int(temperature) + high_temp = int(temperature) if kwargs.get(ATTR_TARGET_TEMP_LOW) is not None and \ kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None: - low_temp = kwargs.get(ATTR_TARGET_TEMP_LOW) - high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) + high_temp = int(kwargs.get(ATTR_TARGET_TEMP_LOW)) + low_temp = int(kwargs.get(ATTR_TARGET_TEMP_HIGH)) if self.hold_temp: self.data.ecobee.set_hold_temp(self.thermostat_index, low_temp, high_temp, "indefinite") + _LOGGER.debug("Setting ecobee hold_temp to: low=%s, is=%s, " + "high=%s, is=%s", low_temp, isinstance( + low_temp, (int, float)), high_temp, + isinstance(high_temp, (int, float))) else: self.data.ecobee.set_hold_temp(self.thermostat_index, low_temp, high_temp) + _LOGGER.debug("Setting ecobee temp to: low=%s, is=%s, " + "high=%s, is=%s", low_temp, isinstance( + low_temp, (int, float)), high_temp, + isinstance(high_temp, (int, float))) def set_operation_mode(self, operation_mode): """Set HVAC mode (auto, auxHeatOnly, cool, heat, off).""" From a9ef8d8568f64632a88b19e143bfeefef7dda2c8 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Mon, 12 Sep 2016 22:52:49 +0200 Subject: [PATCH 031/162] Update ha-ffmpeg version 0.12 and add tests (#3301) * update ha-ffmpeg version 0.12 and add tests * change error logging --- .../components/binary_sensor/ffmpeg.py | 19 +++++++++++++++---- homeassistant/components/camera/ffmpeg.py | 14 +++++++++++++- requirements_all.txt | 2 +- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/binary_sensor/ffmpeg.py b/homeassistant/components/binary_sensor/ffmpeg.py index 9c37ff7744c..cedb978cc68 100644 --- a/homeassistant/components/binary_sensor/ffmpeg.py +++ b/homeassistant/components/binary_sensor/ffmpeg.py @@ -16,7 +16,7 @@ from homeassistant.config import load_yaml_config_file from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME, ATTR_ENTITY_ID) -REQUIREMENTS = ["ha-ffmpeg==0.10"] +REQUIREMENTS = ["ha-ffmpeg==0.12"] SERVICE_RESTART = 'ffmpeg_restart' @@ -39,12 +39,16 @@ CONF_RESET = 'reset' CONF_CHANGES = 'changes' CONF_REPEAT = 'repeat' CONF_REPEAT_TIME = 'repeat_time' +CONF_RUN_TEST = 'run_test' + +DEFAULT_RUN_TEST = True PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_TOOL): vol.In(MAP_FFMPEG_BIN), vol.Required(CONF_INPUT): cv.string, vol.Optional(CONF_FFMPEG_BIN, default="ffmpeg"): cv.string, vol.Optional(CONF_NAME, default="FFmpeg"): cv.string, + vol.Optional(CONF_RUN_TEST, default=DEFAULT_RUN_TEST): cv.boolean, vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, vol.Optional(CONF_OUTPUT): cv.string, vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int), @@ -73,8 +77,16 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """Create the binary sensor.""" - from haffmpeg import SensorNoise, SensorMotion + from haffmpeg import Test, SensorNoise, SensorMotion + # check source + if config.get(CONF_RUN_TEST): + test = Test(config.get(CONF_FFMPEG_BIN)) + if not test.run_test(config.get(CONF_INPUT)): + _LOGGER.error("FFmpeg '%s' test fails!", config.get(CONF_INPUT)) + return + + # generate sensor object if config.get(CONF_TOOL) == FFMPEG_SENSOR_NOISE: entity = FFmpegNoise(SensorNoise, config) else: @@ -88,7 +100,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # exists service? if hass.services.has_service(DOMAIN, SERVICE_RESTART): - return True + return descriptions = load_yaml_config_file( path.join(path.dirname(__file__), 'services.yaml')) @@ -111,7 +123,6 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _service_handle_restart, descriptions.get(SERVICE_RESTART), schema=SERVICE_RESTART_SCHEMA) - return True class FFmpegBinarySensor(BinarySensorDevice): diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py index af21537e3c3..a0cbe1ca436 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/camera/ffmpeg.py @@ -12,27 +12,39 @@ from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_NAME -REQUIREMENTS = ['ha-ffmpeg==0.10'] +REQUIREMENTS = ['ha-ffmpeg==0.12'] _LOGGER = logging.getLogger(__name__) CONF_INPUT = 'input' CONF_FFMPEG_BIN = 'ffmpeg_bin' CONF_EXTRA_ARGUMENTS = 'extra_arguments' +CONF_RUN_TEST = 'run_test' DEFAULT_BINARY = 'ffmpeg' DEFAULT_NAME = 'FFmpeg' +DEFAULT_RUN_TEST = True PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_INPUT): cv.string, vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, vol.Optional(CONF_FFMPEG_BIN, default=DEFAULT_BINARY): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_RUN_TEST, default=DEFAULT_RUN_TEST): cv.boolean, }) def setup_platform(hass, config, add_devices, discovery_info=None): """Setup a FFmpeg Camera.""" + from haffmpeg import Test + + # run Test + if config.get(CONF_RUN_TEST): + test = Test(config.get(CONF_FFMPEG_BIN)) + if not test.run_test(config.get(CONF_INPUT)): + _LOGGER.error("FFmpeg '%s' test fails!", config.get(CONF_INPUT)) + return + add_devices([FFmpegCamera(config)]) diff --git a/requirements_all.txt b/requirements_all.txt index 0eced3bca5b..dc832841c17 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -113,7 +113,7 @@ gps3==0.33.3 # homeassistant.components.binary_sensor.ffmpeg # homeassistant.components.camera.ffmpeg -ha-ffmpeg==0.10 +ha-ffmpeg==0.12 # homeassistant.components.mqtt.server hbmqtt==0.7.1 From d7e3fa22eba3d134c302d4874474063f068ed87d Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 13 Sep 2016 03:21:35 +0200 Subject: [PATCH 032/162] Bugfix voluptuous on recorder (#3350) --- homeassistant/components/recorder/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 5e4415d81a0..8f373700165 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -20,6 +20,7 @@ from homeassistant.core import HomeAssistant from homeassistant.const import (EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.helpers.typing import ConfigType, QueryType import homeassistant.util.dt as dt_util @@ -40,10 +41,9 @@ QUERY_RETRY_WAIT = 0.1 CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ - vol.Optional(CONF_PURGE_DAYS): vol.All(vol.Coerce(int), - vol.Range(min=1)), - # pylint: disable=no-value-for-parameter - vol.Optional(CONF_DB_URL): vol.Url(), + vol.Optional(CONF_PURGE_DAYS): + vol.All(vol.Coerce(int), vol.Range(min=1)), + vol.Optional(CONF_DB_URL): cv.string, }) }, extra=vol.ALLOW_EXTRA) From 14b6f9d927859244180dd26cdc71743bbeb940b4 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Tue, 13 Sep 2016 03:23:18 +0200 Subject: [PATCH 033/162] Missing garage door detection (#3349) --- homeassistant/components/cover/wink.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/cover/wink.py b/homeassistant/components/cover/wink.py index a8b90809255..a1612b344d2 100644 --- a/homeassistant/components/cover/wink.py +++ b/homeassistant/components/cover/wink.py @@ -28,8 +28,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None): pywink.set_bearer_token(token) - add_devices(WinkCoverDevice(shade) for shade, door in + add_devices(WinkCoverDevice(shade) for shade in pywink.get_shades()) + add_devices(WinkCoverDevice(door) for door in + pywink.get_garage_doors()) class WinkCoverDevice(WinkDevice, CoverDevice): From 6959407dfdf726edd8488dec39830c1d657cdd4d Mon Sep 17 00:00:00 2001 From: David-Leon Pohl Date: Tue, 13 Sep 2016 03:28:11 +0200 Subject: [PATCH 034/162] Bugfix pilight component (#3355) * BUG Message data cannot be changed thus use voluptuous to ensure format * Pilight daemon expects JSON serializable data Thus dict is needed and not a mapping proxy. * Add explanation why dict as message data is needed * Use more obvious voluptuous validation scheme * Pylint: Trailing whitespace --- homeassistant/components/pilight.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/pilight.py b/homeassistant/components/pilight.py index 764b972d393..3475a6be65a 100644 --- a/homeassistant/components/pilight.py +++ b/homeassistant/components/pilight.py @@ -10,7 +10,6 @@ import socket import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.config_validation import ensure_list from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT, CONF_WHITELIST) @@ -29,7 +28,10 @@ EVENT = 'pilight_received' # The pilight code schema depends on the protocol # Thus only require to have the protocol information -RF_CODE_SCHEMA = vol.Schema({vol.Required(ATTR_PROTOCOL): cv.string}, +# Ensure that protocol is in a list otherwise segfault in pilight-daemon +# https://github.com/pilight/pilight/issues/296 +RF_CODE_SCHEMA = vol.Schema({vol.Required(ATTR_PROTOCOL): + vol.All(cv.ensure_list, [cv.string])}, extra=vol.ALLOW_EXTRA) SERVICE_NAME = 'send' @@ -71,12 +73,9 @@ def setup(hass, config): def send_code(call): """Send RF code to the pilight-daemon.""" - message_data = call.data - - # Patch data because of bug: - # https://github.com/pilight/pilight/issues/296 - # Protocol has to be in a list otherwise segfault in pilight-daemon - message_data['protocol'] = ensure_list(message_data['protocol']) + # Change type to dict from mappingproxy + # since data has to be JSON serializable + message_data = dict(call.data) try: pilight_client.send_code(message_data) From 24f1bff7f13b802d7bdda7415d3f53177512445c Mon Sep 17 00:00:00 2001 From: Nick Vella Date: Tue, 13 Sep 2016 11:31:44 +1000 Subject: [PATCH 035/162] Add open/closed state for open_cover and close_cover in SERVICE_TO_STATE (#3180) * Add open/closed state mapping for open_cover and close_cover * Add 'open', 'closed' for open/close_cover_tilt * Revert "Add 'open', 'closed' for open/close_cover_tilt" This reverts commit e45582d4394a33feedfce190a1dba96473d24825. --- homeassistant/helpers/state.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/helpers/state.py b/homeassistant/helpers/state.py index 9c6e797acd1..4935251db7d 100644 --- a/homeassistant/helpers/state.py +++ b/homeassistant/helpers/state.py @@ -29,10 +29,10 @@ from homeassistant.const import ( SERVICE_CLOSE, SERVICE_LOCK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_SEEK, SERVICE_MOVE_DOWN, SERVICE_MOVE_UP, SERVICE_OPEN, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_UNLOCK, SERVICE_VOLUME_MUTE, - SERVICE_VOLUME_SET, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, - STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, STATE_CLOSED, STATE_LOCKED, - STATE_OFF, STATE_ON, STATE_OPEN, STATE_PAUSED, STATE_PLAYING, - STATE_UNKNOWN, STATE_UNLOCKED) + SERVICE_VOLUME_SET, SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, + STATE_ALARM_TRIGGERED, STATE_CLOSED, STATE_LOCKED, STATE_OFF, STATE_ON, + STATE_OPEN, STATE_PAUSED, STATE_PLAYING, STATE_UNKNOWN, STATE_UNLOCKED) from homeassistant.core import State _LOGGER = logging.getLogger(__name__) @@ -77,6 +77,8 @@ SERVICE_TO_STATE = { SERVICE_OPEN: STATE_OPEN, SERVICE_MOVE_UP: STATE_OPEN, SERVICE_MOVE_DOWN: STATE_CLOSED, + SERVICE_OPEN_COVER: STATE_OPEN, + SERVICE_CLOSE_COVER: STATE_CLOSED } From 609d7ebea5551cd75bf9a2e9ac303470a6721a41 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 12 Sep 2016 19:16:14 -0700 Subject: [PATCH 036/162] Migrate core from threads to async awesomeness (#3248) * Add event loop to the core * Add block_till_done to HA core object * Fix some tests * Linting core * Fix statemachine tests * Core test fixes * fix block_till_done to wait for loop and queue to empty * fix test_core for passing, and correct start/stop/block_till_done * Fix remote tests * Fix tests: block_till_done * Fix linting * Fix more tests * Fix final linting * Fix remote test * remove unnecessary import * reduce sleep to avoid slowing down the tests excessively * fix remaining tests to wait for non-threadsafe operations * Add async_ doc strings for event loop / coroutine info * Fix command line test to block for the right timeout * Fix py3.4.2 loop var access * Fix SERVICE_CALL_LIMIT being in effect for other tests * Fix lint errors * Fix lint error with proper placement * Fix slave start to not start a timer * Add asyncio compatible listeners. * Increase min Python version to 3.4.2 * Move async backports to util * Add backported async tests * Fix linting * Simplify Python version check * Fix lint * Remove unneeded try/except and queue listener appproriately. * Fix tuple vs. list unorderable error on version compare. * Fix version tests --- homeassistant/__main__.py | 20 +- homeassistant/bootstrap.py | 33 +- homeassistant/components/api.py | 2 + homeassistant/components/recorder/__init__.py | 2 + homeassistant/const.py | 2 +- homeassistant/core.py | 587 +++++++++++++----- homeassistant/remote.py | 20 +- homeassistant/util/__init__.py | 20 + homeassistant/util/async.py | 154 +++++ tests/common.py | 26 +- .../alarm_control_panel/test_manual.py | 40 +- .../alarm_control_panel/test_mqtt.py | 16 +- tests/components/automation/test_event.py | 10 +- tests/components/automation/test_init.py | 76 +-- tests/components/automation/test_mqtt.py | 10 +- .../automation/test_numeric_state.py | 66 +- tests/components/automation/test_state.py | 40 +- tests/components/automation/test_sun.py | 40 +- tests/components/automation/test_template.py | 60 +- tests/components/automation/test_time.py | 42 +- tests/components/automation/test_zone.py | 26 +- tests/components/binary_sensor/test_mqtt.py | 4 +- .../components/binary_sensor/test_template.py | 4 +- tests/components/binary_sensor/test_trend.py | 32 +- tests/components/climate/test_demo.py | 32 +- .../climate/test_generic_thermostat.py | 70 +-- tests/components/cover/test_command_line.py | 11 +- tests/components/cover/test_demo.py | 36 +- tests/components/cover/test_mqtt.py | 20 +- tests/components/device_tracker/test_init.py | 8 +- .../device_tracker/test_locative.py | 2 +- tests/components/device_tracker/test_mqtt.py | 2 +- .../device_tracker/test_owntracks.py | 4 +- tests/components/fan/test_demo.py | 18 +- tests/components/garage_door/test_demo.py | 4 +- tests/components/garage_door/test_mqtt.py | 12 +- tests/components/hvac/test_demo.py | 32 +- tests/components/light/test_init.py | 26 +- tests/components/light/test_mqtt.py | 36 +- tests/components/light/test_mqtt_json.py | 32 +- tests/components/lock/test_demo.py | 4 +- tests/components/lock/test_mqtt.py | 12 +- tests/components/media_player/test_demo.py | 50 +- tests/components/mqtt/test_init.py | 30 +- tests/components/notify/test_command_line.py | 12 +- tests/components/notify/test_demo.py | 10 +- tests/components/notify/test_group.py | 6 +- tests/components/recorder/test_init.py | 8 +- .../rollershutter/test_command_line.py | 10 +- tests/components/rollershutter/test_demo.py | 8 +- tests/components/rollershutter/test_mqtt.py | 20 +- tests/components/sensor/test_forecast.py | 5 +- tests/components/sensor/test_moldindicator.py | 8 +- tests/components/sensor/test_mqtt.py | 4 +- tests/components/sensor/test_mqtt_room.py | 2 +- tests/components/sensor/test_template.py | 2 +- tests/components/sensor/test_wunderground.py | 5 +- tests/components/switch/test_command_line.py | 16 +- tests/components/switch/test_flux.py | 46 +- tests/components/switch/test_init.py | 6 +- tests/components/switch/test_mqtt.py | 12 +- tests/components/switch/test_template.py | 12 +- tests/components/test_alexa.py | 2 +- tests/components/test_api.py | 14 +- tests/components/test_configurator.py | 4 +- .../test_device_sun_light_trigger.py | 12 +- tests/components/test_frontend.py | 2 +- tests/components/test_group.py | 20 +- tests/components/test_history.py | 4 +- tests/components/test_init.py | 12 +- tests/components/test_input_boolean.py | 4 +- tests/components/test_input_select.py | 4 +- tests/components/test_input_slider.py | 6 +- tests/components/test_logbook.py | 4 +- tests/components/test_mqtt_eventstream.py | 16 +- .../test_persistent_notification.py | 8 +- tests/components/test_proximity.py | 74 +-- tests/components/test_rfxtrx.py | 6 +- tests/components/test_scene.py | 8 +- tests/components/test_script.py | 12 +- tests/components/test_shell_command.py | 1 + tests/components/test_sun.py | 2 +- tests/components/test_updater.py | 2 +- tests/components/thermostat/test_demo.py | 16 +- .../thermostat/test_heat_control.py | 70 +-- tests/helpers/test_discovery.py | 14 +- tests/helpers/test_entity_component.py | 6 +- tests/helpers/test_event.py | 86 +-- tests/helpers/test_event_decorators.py | 26 +- tests/helpers/test_script.py | 26 +- tests/helpers/test_service.py | 10 +- tests/helpers/test_state.py | 20 +- tests/scripts/test_check_config.py | 13 +- tests/test_config.py | 2 + tests/test_core.py | 147 ++--- tests/test_main.py | 41 ++ tests/test_remote.py | 53 +- tests/util/test_async.py | 77 +++ 98 files changed, 1680 insertions(+), 1109 deletions(-) create mode 100644 homeassistant/util/async.py create mode 100644 tests/test_main.py create mode 100644 tests/util/test_async.py diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 39a18feb1f2..a7b2027963f 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -16,16 +16,14 @@ from homeassistant.const import ( REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, ) +from homeassistant.util.async import run_callback_threadsafe def validate_python() -> None: """Validate we're running the right Python version.""" - major, minor = sys.version_info[:2] - req_major, req_minor = REQUIRED_PYTHON_VER - - if major < req_major or (major == req_major and minor < req_minor): - print("Home Assistant requires at least Python {}.{}".format( - req_major, req_minor)) + if sys.version_info[:3] < REQUIRED_PYTHON_VER: + print("Home Assistant requires at least Python {}.{}.{}".format( + *REQUIRED_PYTHON_VER)) sys.exit(1) @@ -256,12 +254,14 @@ def setup_and_run_hass(config_dir: str, import webbrowser webbrowser.open(hass.config.api.base_url) - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser) + run_callback_threadsafe( + hass.loop, + hass.bus.async_listen_once, + EVENT_HOMEASSISTANT_START, open_browser + ) hass.start() - exit_code = int(hass.block_till_stopped()) - - return exit_code + return hass.exit_code def try_to_restart() -> None: diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 5e291e90717..083fe639fea 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -122,7 +122,8 @@ def _setup_component(hass: core.HomeAssistant, domain: str, config) -> bool: hass.pool.add_worker() hass.bus.fire( - EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN}) + EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: component.DOMAIN} + ) return True @@ -278,23 +279,29 @@ def from_config_dict(config: Dict[str, Any], components = set(key.split(' ')[0] for key in config.keys() if key != core.DOMAIN) - if not core_components.setup(hass, config): - _LOGGER.error('Home Assistant core failed to initialize. ' - 'Further initialization aborted.') - return hass + # Setup in a thread to avoid blocking + def component_setup(): + """Set up a component.""" + if not core_components.setup(hass, config): + _LOGGER.error('Home Assistant core failed to initialize. ' + 'Further initialization aborted.') + return hass - persistent_notification.setup(hass, config) + persistent_notification.setup(hass, config) - _LOGGER.info('Home Assistant core initialized') + _LOGGER.info('Home Assistant core initialized') - # Give event decorators access to HASS - event_decorators.HASS = hass - service.HASS = hass + # Give event decorators access to HASS + event_decorators.HASS = hass + service.HASS = hass - # Setup the components - for domain in loader.load_order_components(components): - _setup_component(hass, domain, config) + # Setup the components + for domain in loader.load_order_components(components): + _setup_component(hass, domain, config) + hass.loop.run_until_complete( + hass.loop.run_in_executor(None, component_setup) + ) return hass diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index be455995743..7b1841386b9 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -4,6 +4,7 @@ Rest API for Home Assistant. For more details about the RESTful API, please refer to the documentation at https://home-assistant.io/developers/api/ """ +import asyncio import json import logging import queue @@ -79,6 +80,7 @@ class APIEventStream(HomeAssistantView): if restrict: restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP] + @asyncio.coroutine def forward_events(event): """Forward events to the open request.""" if event.event_type == EVENT_TIME_CHANGED: diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 8f373700165..70a923f6f30 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -7,6 +7,7 @@ to query this database. For more details about this component, please refer to the documentation at https://home-assistant.io/components/recorder/ """ +import asyncio import logging import queue import threading @@ -230,6 +231,7 @@ class Recorder(threading.Thread): self.queue.task_done() + @asyncio.coroutine def event_listener(self, event): """Listen for new events and put them in the process queue.""" self.queue.put(event) diff --git a/homeassistant/const.py b/homeassistant/const.py index ffb162ebff5..b8b3756f37b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -5,7 +5,7 @@ MINOR_VERSION = 29 PATCH_VERSION = '0.dev0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) -REQUIRED_PYTHON_VER = (3, 4) +REQUIRED_PYTHON_VER = (3, 4, 2) PROJECT_NAME = 'Home Assistant' PROJECT_PACKAGE_NAME = 'homeassistant' diff --git a/homeassistant/core.py b/homeassistant/core.py index 03f9658325f..50b04e79f6d 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -4,18 +4,20 @@ Core components of Home Assistant. Home Assistant is a Home Automation framework for observing the state of entities and react to changes. """ - +# pylint: disable=unused-import, too-many-lines +import asyncio import enum import functools as ft import logging import os import re import signal -import threading +import sys import time +from concurrent.futures import ThreadPoolExecutor + from types import MappingProxyType -# pylint: disable=unused-import from typing import Optional, Any, Callable, List # NOQA import voluptuous as vol @@ -30,6 +32,8 @@ from homeassistant.const import ( SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, __version__) from homeassistant.exceptions import ( HomeAssistantError, InvalidEntityFormatError) +from homeassistant.util.async import ( + run_coroutine_threadsafe, run_callback_threadsafe) import homeassistant.util as util import homeassistant.util.dt as dt_util import homeassistant.util.location as location @@ -103,14 +107,20 @@ class JobPriority(util.OrderedEnum): class HomeAssistant(object): """Root object of the Home Assistant home automation.""" - def __init__(self): + # pylint: disable=too-many-instance-attributes + + def __init__(self, loop=None): """Initialize new Home Assistant object.""" + self.loop = loop or asyncio.get_event_loop() + self.executer = ThreadPoolExecutor(max_workers=5) + self.loop.set_default_executor(self.executer) self.pool = pool = create_worker_pool() - self.bus = EventBus(pool) - self.services = ServiceRegistry(self.bus, self.add_job) - self.states = StateMachine(self.bus) + self.bus = EventBus(pool, self.loop) + self.services = ServiceRegistry(self.bus, self.add_job, self.loop) + self.states = StateMachine(self.bus, self.loop) self.config = Config() # type: Config self.state = CoreState.not_running + self.exit_code = None @property def is_running(self) -> bool: @@ -123,9 +133,65 @@ class HomeAssistant(object): "Starting Home Assistant (%d threads)", self.pool.worker_count) self.state = CoreState.starting + # Register the async start + self.loop.create_task(self.async_start()) + + @asyncio.coroutine + def stop_homeassistant(*args): + """Stop Home Assistant.""" + self.exit_code = 0 + yield from self.async_stop() + + @asyncio.coroutine + def restart_homeassistant(*args): + """Restart Home Assistant.""" + self.exit_code = RESTART_EXIT_CODE + yield from self.async_stop() + + # Register the restart/stop event + self.loop.call_soon( + self.services.async_register, + DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant + ) + self.loop.call_soon( + self.services.async_register, + DOMAIN, SERVICE_HOMEASSISTANT_RESTART, restart_homeassistant + ) + + # Setup signal handling + try: + signal.signal(signal.SIGTERM, stop_homeassistant) + except ValueError: + _LOGGER.warning( + 'Could not bind to SIGTERM. Are you running in a thread?') + try: + signal.signal(signal.SIGHUP, restart_homeassistant) + except ValueError: + _LOGGER.warning( + 'Could not bind to SIGHUP. Are you running in a thread?') + except AttributeError: + pass + + # Run forever and catch keyboard interrupt + try: + # Block until stopped + _LOGGER.info("Starting Home Assistant core loop") + self.loop.run_forever() + except KeyboardInterrupt: + pass + finally: + self.loop.create_task(stop_homeassistant()) + self.loop.run_forever() + + @asyncio.coroutine + def async_start(self): + """Finalize startup from inside the event loop. + + This method is a coroutine. + """ create_timer(self) - self.bus.fire(EVENT_HOMEASSISTANT_START) - self.pool.block_till_done() + self.bus.async_fire(EVENT_HOMEASSISTANT_START) + yield from self.loop.run_in_executor(None, self.pool.block_till_done) self.state = CoreState.running def add_job(self, @@ -139,55 +205,66 @@ class HomeAssistant(object): """ self.pool.add_job(priority, (target,) + args) - def block_till_stopped(self) -> int: - """Register service homeassistant/stop and will block until called.""" - request_shutdown = threading.Event() - request_restart = threading.Event() + def _loop_empty(self): + """Python 3.4.2 empty loop compatibility function.""" + # pylint: disable=protected-access + if sys.version_info < (3, 4, 3): + return len(self.loop._scheduled) == 0 and \ + len(self.loop._ready) == 0 + else: + return self.loop._current_handle is None and \ + len(self.loop._ready) == 0 - def stop_homeassistant(*args): - """Stop Home Assistant.""" - request_shutdown.set() + def block_till_done(self): + """Block till all pending work is done.""" + import threading - def restart_homeassistant(*args): - """Reset Home Assistant.""" - _LOGGER.warning('Home Assistant requested a restart.') - request_restart.set() - request_shutdown.set() + complete = threading.Event() - self.services.register( - DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant) - self.services.register( - DOMAIN, SERVICE_HOMEASSISTANT_RESTART, restart_homeassistant) + @asyncio.coroutine + def sleep_wait(): + """Sleep in thread pool.""" + yield from self.loop.run_in_executor(None, time.sleep, 0) - try: - signal.signal(signal.SIGTERM, stop_homeassistant) - except ValueError: - _LOGGER.warning( - 'Could not bind to SIGTERM. Are you running in a thread?') - try: - signal.signal(signal.SIGHUP, restart_homeassistant) - except ValueError: - _LOGGER.warning( - 'Could not bind to SIGHUP. Are you running in a thread?') - except AttributeError: - pass - try: - while not request_shutdown.is_set(): - time.sleep(1) - except KeyboardInterrupt: - pass - finally: - self.stop() + def notify_when_done(): + """Notify event loop when pool done.""" + while True: + # Wait for the work queue to empty + self.pool.block_till_done() - return RESTART_EXIT_CODE if request_restart.is_set() else 0 + # Verify the loop is empty + if self._loop_empty(): + break + + # sleep in the loop executor, this forces execution back into + # the event loop to avoid the block thread from starving the + # async loop + run_coroutine_threadsafe( + sleep_wait(), + self.loop + ).result() + + complete.set() + + threading.Thread(name="BlockThread", target=notify_when_done).start() + complete.wait() def stop(self) -> None: """Stop Home Assistant and shuts down all threads.""" - _LOGGER.info("Stopping") + run_coroutine_threadsafe(self.async_stop(), self.loop) + + @asyncio.coroutine + def async_stop(self) -> None: + """Stop Home Assistant and shuts down all threads. + + This method is a coroutine. + """ self.state = CoreState.stopping - self.bus.fire(EVENT_HOMEASSISTANT_STOP) - self.pool.stop() + self.bus.async_fire(EVENT_HOMEASSISTANT_STOP) + yield from self.loop.run_in_executor(None, self.pool.block_till_done) + yield from self.loop.run_in_executor(None, self.pool.stop) self.state = CoreState.not_running + self.loop.stop() class EventOrigin(enum.Enum): @@ -247,43 +324,69 @@ class Event(object): class EventBus(object): """Allows firing of and listening for events.""" - def __init__(self, pool: util.ThreadPool) -> None: + def __init__(self, pool: util.ThreadPool, + loop: asyncio.AbstractEventLoop) -> None: """Initialize a new event bus.""" self._listeners = {} - self._lock = threading.Lock() self._pool = pool + self._loop = loop + + def async_listeners(self): + """Dict with events and the number of listeners. + + This method must be run in the event loop. + """ + return {key: len(self._listeners[key]) + for key in self._listeners} @property def listeners(self): """Dict with events and the number of listeners.""" - with self._lock: - return {key: len(self._listeners[key]) - for key in self._listeners} + return run_callback_threadsafe( + self._loop, self.async_listeners + ).result() def fire(self, event_type: str, event_data=None, origin=EventOrigin.local): """Fire an event.""" if not self._pool.running: raise HomeAssistantError('Home Assistant has shut down.') - with self._lock: - # Copy the list of the current listeners because some listeners - # remove themselves as a listener while being executed which - # causes the iterator to be confused. - get = self._listeners.get - listeners = get(MATCH_ALL, []) + get(event_type, []) + self._loop.call_soon_threadsafe(self.async_fire, event_type, + event_data, origin) + return - event = Event(event_type, event_data, origin) + def async_fire(self, event_type: str, event_data=None, + origin=EventOrigin.local, wait=False): + """Fire an event. - if event_type != EVENT_TIME_CHANGED: - _LOGGER.info("Bus:Handling %s", event) + This method must be run in the event loop. + """ + # Copy the list of the current listeners because some listeners + # remove themselves as a listener while being executed which + # causes the iterator to be confused. + get = self._listeners.get + listeners = get(MATCH_ALL, []) + get(event_type, []) - if not listeners: - return + event = Event(event_type, event_data, origin) - job_priority = JobPriority.from_event_type(event_type) + if event_type != EVENT_TIME_CHANGED: + _LOGGER.info("Bus:Handling %s", event) - for func in listeners: - self._pool.add_job(job_priority, (func, event)) + if not listeners: + return + + job_priority = JobPriority.from_event_type(event_type) + + sync_jobs = [] + for func in listeners: + if asyncio.iscoroutinefunction(func): + self._loop.create_task(func(event)) + else: + sync_jobs.append((job_priority, (func, event))) + + # Send all the sync jobs at once + if sync_jobs: + self._pool.add_many_jobs(sync_jobs) def listen(self, event_type, listener): """Listen for all events or events of a specific type. @@ -291,11 +394,9 @@ class EventBus(object): To listen to all events specify the constant ``MATCH_ALL`` as event_type. """ - with self._lock: - if event_type in self._listeners: - self._listeners[event_type].append(listener) - else: - self._listeners[event_type] = [listener] + future = run_callback_threadsafe( + self._loop, self.async_listen, event_type, listener) + future.result() def remove_listener(): """Remove the listener.""" @@ -303,6 +404,25 @@ class EventBus(object): return remove_listener + def async_listen(self, event_type, listener): + """Listen for all events or events of a specific type. + + To listen to all events specify the constant ``MATCH_ALL`` + as event_type. + + This method must be run in the event loop. + """ + if event_type in self._listeners: + self._listeners[event_type].append(listener) + else: + self._listeners[event_type] = [listener] + + def remove_listener(): + """Remove the listener.""" + self.async_remove_listener(event_type, listener) + + return remove_listener + def listen_once(self, event_type, listener): """Listen once for event of a specific type. @@ -331,6 +451,41 @@ class EventBus(object): return remove_listener + def async_listen_once(self, event_type, listener): + """Listen once for event of a specific type. + + To listen to all events specify the constant ``MATCH_ALL`` + as event_type. + + Returns registered listener that can be used with remove_listener. + + This method must be run in the event loop. + """ + @ft.wraps(listener) + @asyncio.coroutine + def onetime_listener(event): + """Remove listener from eventbus and then fire listener.""" + if hasattr(onetime_listener, 'run'): + return + # Set variable so that we will never run twice. + # Because the event bus loop might have async_fire queued multiple + # times, its possible this listener may already be lined up + # multiple times as well. + # This will make sure the second time it does nothing. + setattr(onetime_listener, 'run', True) + + self.async_remove_listener(event_type, onetime_listener) + + if asyncio.iscoroutinefunction(listener): + yield from listener(event) + else: + job_priority = JobPriority.from_event_type(event.event_type) + self._pool.add_job(job_priority, (listener, event)) + + self.async_listen(event_type, onetime_listener) + + return onetime_listener + def remove_listener(self, event_type, listener): """Remove a listener of a specific event_type. (DEPRECATED 0.28).""" _LOGGER.warning('bus.remove_listener has been deprecated. Please use ' @@ -339,19 +494,28 @@ class EventBus(object): def _remove_listener(self, event_type, listener): """Remove a listener of a specific event_type.""" - with self._lock: - try: - self._listeners[event_type].remove(listener) + future = run_callback_threadsafe( + self._loop, + self.async_remove_listener, event_type, listener + ) + future.result() - # delete event_type list if empty - if not self._listeners[event_type]: - self._listeners.pop(event_type) + def async_remove_listener(self, event_type, listener): + """Remove a listener of a specific event_type. - except (KeyError, ValueError): - # KeyError is key event_type listener did not exist - # ValueError if listener did not exist within event_type - _LOGGER.warning('Unable to remove unknown listener %s', - listener) + This method must be run in the event loop. + """ + try: + self._listeners[event_type].remove(listener) + + # delete event_type list if empty + if not self._listeners[event_type]: + self._listeners.pop(event_type) + except (KeyError, ValueError): + # KeyError is key event_type listener did not exist + # ValueError if listener did not exist within event_type + _LOGGER.warning('Unable to remove unknown listener %s', + listener) class State(object): @@ -455,27 +619,39 @@ class State(object): class StateMachine(object): """Helper class that tracks the state of different entities.""" - def __init__(self, bus): + def __init__(self, bus, loop): """Initialize state machine.""" self._states = {} self._bus = bus - self._lock = threading.Lock() + self._loop = loop def entity_ids(self, domain_filter=None): + """List of entity ids that are being tracked.""" + future = run_callback_threadsafe( + self._loop, self.async_entity_ids, domain_filter + ) + return future.result() + + def async_entity_ids(self, domain_filter=None): """List of entity ids that are being tracked.""" if domain_filter is None: return list(self._states.keys()) domain_filter = domain_filter.lower() - with self._lock: - return [state.entity_id for state in self._states.values() - if state.domain == domain_filter] + return [state.entity_id for state in self._states.values() + if state.domain == domain_filter] def all(self): """Create a list of all states.""" - with self._lock: - return list(self._states.values()) + return run_callback_threadsafe(self._loop, self.async_all).result() + + def async_all(self): + """Create a list of all states. + + This method must be run in the event loop. + """ + return list(self._states.values()) def get(self, entity_id): """Retrieve state of entity_id or None if not found.""" @@ -483,6 +659,15 @@ class StateMachine(object): def is_state(self, entity_id, state): """Test if entity exists and is specified state.""" + return run_callback_threadsafe( + self._loop, self.async_is_state, entity_id, state + ).result() + + def async_is_state(self, entity_id, state): + """Test if entity exists and is specified state. + + This method must be run in the event loop. + """ entity_id = entity_id.lower() return (entity_id in self._states and @@ -490,6 +675,15 @@ class StateMachine(object): def is_state_attr(self, entity_id, name, value): """Test if entity exists and has a state attribute set to value.""" + return run_callback_threadsafe( + self._loop, self.async_is_state_attr, entity_id, name, value + ).result() + + def async_is_state_attr(self, entity_id, name, value): + """Test if entity exists and has a state attribute set to value. + + This method must be run in the event loop. + """ entity_id = entity_id.lower() return (entity_id in self._states and @@ -500,23 +694,32 @@ class StateMachine(object): Returns boolean to indicate if an entity was removed. """ + return run_callback_threadsafe( + self._loop, self.async_remove, entity_id).result() + + def async_remove(self, entity_id): + """Remove the state of an entity. + + Returns boolean to indicate if an entity was removed. + + This method must be run in the event loop. + """ entity_id = entity_id.lower() - with self._lock: - old_state = self._states.pop(entity_id, None) + old_state = self._states.pop(entity_id, None) - if old_state is None: - return False + if old_state is None: + return False - event_data = { - 'entity_id': entity_id, - 'old_state': old_state, - 'new_state': None, - } + event_data = { + 'entity_id': entity_id, + 'old_state': old_state, + 'new_state': None, + } - self._bus.fire(EVENT_STATE_CHANGED, event_data) + self._bus.async_fire(EVENT_STATE_CHANGED, event_data) - return True + return True def set(self, entity_id, new_state, attributes=None, force_update=False): """Set the state of an entity, add entity if it does not exist. @@ -526,34 +729,49 @@ class StateMachine(object): If you just update the attributes and not the state, last changed will not be affected. """ + run_callback_threadsafe( + self._loop, + self.async_set, entity_id, new_state, attributes, force_update, + ).result() + + def async_set(self, entity_id, new_state, attributes=None, + force_update=False): + """Set the state of an entity, add entity if it does not exist. + + Attributes is an optional dict to specify attributes of this state. + + If you just update the attributes and not the state, last changed will + not be affected. + + This method must be run in the event loop. + """ entity_id = entity_id.lower() new_state = str(new_state) attributes = attributes or {} - with self._lock: - old_state = self._states.get(entity_id) + old_state = self._states.get(entity_id) - is_existing = old_state is not None - same_state = (is_existing and old_state.state == new_state and - not force_update) - same_attr = is_existing and old_state.attributes == attributes + is_existing = old_state is not None + same_state = (is_existing and old_state.state == new_state and + not force_update) + same_attr = is_existing and old_state.attributes == attributes - if same_state and same_attr: - return + if same_state and same_attr: + return - # If state did not exist or is different, set it - last_changed = old_state.last_changed if same_state else None + # If state did not exist or is different, set it + last_changed = old_state.last_changed if same_state else None - state = State(entity_id, new_state, attributes, last_changed) - self._states[entity_id] = state + state = State(entity_id, new_state, attributes, last_changed) + self._states[entity_id] = state - event_data = { - 'entity_id': entity_id, - 'old_state': old_state, - 'new_state': state, - } + event_data = { + 'entity_id': entity_id, + 'old_state': old_state, + 'new_state': state, + } - self._bus.fire(EVENT_STATE_CHANGED, event_data) + self._bus.async_fire(EVENT_STATE_CHANGED, event_data) # pylint: disable=too-few-public-methods @@ -615,22 +833,30 @@ class ServiceCall(object): class ServiceRegistry(object): """Offers services over the eventbus.""" - def __init__(self, bus, add_job): + def __init__(self, bus, add_job, loop): """Initialize a service registry.""" self._services = {} - self._lock = threading.Lock() self._add_job = add_job self._bus = bus + self._loop = loop self._cur_id = 0 - bus.listen(EVENT_CALL_SERVICE, self._event_to_service_call) + run_callback_threadsafe( + loop, + bus.async_listen, EVENT_CALL_SERVICE, self._event_to_service_call, + ) @property def services(self): """Dict with per domain a list of available services.""" - with self._lock: - return {domain: {key: value.as_dict() for key, value - in self._services[domain].items()} - for domain in self._services} + return run_callback_threadsafe( + self._loop, self.async_services, + ).result() + + def async_services(self): + """Dict with per domain a list of available services.""" + return {domain: {key: value.as_dict() for key, value + in self._services[domain].items()} + for domain in self._services} def has_service(self, domain, service): """Test if specified service exists.""" @@ -647,20 +873,39 @@ class ServiceRegistry(object): Schema is called to coerce and validate the service data. """ + run_callback_threadsafe( + self._loop, + self.async_register, domain, service, service_func, description, + schema + ).result() + + def async_register(self, domain, service, service_func, description=None, + schema=None): + """ + Register a service. + + Description is a dict containing key 'description' to describe + the service and a key 'fields' to describe the fields. + + Schema is called to coerce and validate the service data. + + This method must be run in the event loop. + """ domain = domain.lower() service = service.lower() description = description or {} service_obj = Service(service_func, description.get('description'), description.get('fields', {}), schema) - with self._lock: - if domain in self._services: - self._services[domain][service] = service_obj - else: - self._services[domain] = {service: service_obj} - self._bus.fire( - EVENT_SERVICE_REGISTERED, - {ATTR_DOMAIN: domain, ATTR_SERVICE: service}) + if domain in self._services: + self._services[domain][service] = service_obj + else: + self._services[domain] = {service: service_obj} + + self._bus.async_fire( + EVENT_SERVICE_REGISTERED, + {ATTR_DOMAIN: domain, ATTR_SERVICE: service} + ) def call(self, domain, service, service_data=None, blocking=False): """ @@ -679,6 +924,31 @@ class ServiceRegistry(object): Because the service is sent as an event you are not allowed to use the keys ATTR_DOMAIN and ATTR_SERVICE in your service_data. """ + return run_coroutine_threadsafe( + self.async_call(domain, service, service_data, blocking), + self._loop + ).result() + + @asyncio.coroutine + def async_call(self, domain, service, service_data=None, blocking=False): + """ + Call a service. + + Specify blocking=True to wait till service is executed. + Waits a maximum of SERVICE_CALL_LIMIT. + + If blocking = True, will return boolean if service executed + succesfully within SERVICE_CALL_LIMIT. + + This method will fire an event to call the service. + This event will be picked up by this ServiceRegistry and any + other ServiceRegistry that is listening on the EventBus. + + Because the service is sent as an event you are not allowed to use + the keys ATTR_DOMAIN and ATTR_SERVICE in your service_data. + + This method is a coroutine. + """ call_id = self._generate_unique_id() event_data = { @@ -689,19 +959,23 @@ class ServiceRegistry(object): } if blocking: - executed_event = threading.Event() + fut = asyncio.Future(loop=self._loop) + @asyncio.coroutine def service_executed(call): """Callback method that is called when service is executed.""" if call.data[ATTR_SERVICE_CALL_ID] == call_id: - executed_event.set() + fut.set_result(True) - unsub = self._bus.listen(EVENT_SERVICE_EXECUTED, service_executed) + unsub = self._bus.async_listen(EVENT_SERVICE_EXECUTED, + service_executed) - self._bus.fire(EVENT_CALL_SERVICE, event_data) + self._bus.async_fire(EVENT_CALL_SERVICE, event_data) if blocking: - success = executed_event.wait(SERVICE_CALL_LIMIT) + done, _ = yield from asyncio.wait([fut], loop=self._loop, + timeout=SERVICE_CALL_LIMIT) + success = bool(done) unsub() return success @@ -797,16 +1071,19 @@ def create_timer(hass, interval=TIMER_INTERVAL): # every minute. assert 60 % interval == 0, "60 % TIMER_INTERVAL should be 0!" - def timer(): - """Send an EVENT_TIME_CHANGED on interval.""" - stop_event = threading.Event() + stop_event = asyncio.Event(loop=hass.loop) - def stop_timer(event): - """Stop the timer.""" - stop_event.set() + # Setting the Event inside the loop by marking it as a coroutine + @asyncio.coroutine + def stop_timer(event): + """Stop the timer.""" + stop_event.set() - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_timer) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_timer) + @asyncio.coroutine + def timer(interval, stop_event): + """Create an async timer.""" _LOGGER.info("Timer:starting") last_fired_on_second = -1 @@ -830,7 +1107,7 @@ def create_timer(hass, interval=TIMER_INTERVAL): slp_seconds = interval - now.second % interval + \ .5 - now.microsecond/1000000.0 - time.sleep(slp_seconds) + yield from asyncio.sleep(slp_seconds, loop=hass.loop) now = calc_now() @@ -839,18 +1116,22 @@ def create_timer(hass, interval=TIMER_INTERVAL): # Event might have been set while sleeping if not stop_event.is_set(): try: - hass.bus.fire(EVENT_TIME_CHANGED, {ATTR_NOW: now}) + # Schedule the bus event + hass.loop.call_soon( + hass.bus.async_fire, + EVENT_TIME_CHANGED, + {ATTR_NOW: now} + ) except HomeAssistantError: # HA raises error if firing event after it has shut down break + @asyncio.coroutine def start_timer(event): - """Start the timer.""" - thread = threading.Thread(target=timer, name='Timer') - thread.daemon = True - thread.start() + """Start our async timer.""" + hass.loop.create_task(timer(interval, stop_event)) - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_timer) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_timer) def create_worker_pool(worker_count=None): diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 4564878a5ad..2edc368424f 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -7,6 +7,7 @@ HomeAssistantError will be raised. For more details about the Python API, please refer to the documentation at https://home-assistant.io/developers/python_api/ """ +import asyncio from datetime import datetime import enum import json @@ -113,7 +114,7 @@ class HomeAssistant(ha.HomeAssistant): """Home Assistant that forwards work.""" # pylint: disable=super-init-not-called,too-many-instance-attributes - def __init__(self, remote_api, local_api=None): + def __init__(self, remote_api, local_api=None, loop=None): """Initalize the forward instance.""" if not remote_api.validate_api(): raise HomeAssistantError( @@ -122,11 +123,12 @@ class HomeAssistant(ha.HomeAssistant): self.remote_api = remote_api + self.loop = loop or asyncio.get_event_loop() self.pool = pool = ha.create_worker_pool() - self.bus = EventBus(remote_api, pool) - self.services = ha.ServiceRegistry(self.bus, pool) - self.states = StateMachine(self.bus, self.remote_api) + self.bus = EventBus(remote_api, pool, self.loop) + self.services = ha.ServiceRegistry(self.bus, self.add_job, self.loop) + self.states = StateMachine(self.bus, self.loop, self.remote_api) self.config = ha.Config() self.state = ha.CoreState.not_running @@ -147,7 +149,7 @@ class HomeAssistant(ha.HomeAssistant): origin=ha.EventOrigin.remote) # Ensure local HTTP is started - self.pool.block_till_done() + self.block_till_done() self.state = ha.CoreState.running time.sleep(0.05) @@ -178,9 +180,9 @@ class EventBus(ha.EventBus): """EventBus implementation that forwards fire_event to remote API.""" # pylint: disable=too-few-public-methods - def __init__(self, api, pool=None): + def __init__(self, api, pool, loop): """Initalize the eventbus.""" - super().__init__(pool) + super().__init__(pool, loop) self._api = api def fire(self, event_type, event_data=None, origin=ha.EventOrigin.local): @@ -256,9 +258,9 @@ class EventForwarder(object): class StateMachine(ha.StateMachine): """Fire set events to an API. Uses state_change events to track states.""" - def __init__(self, bus, api): + def __init__(self, bus, loop, api): """Initalize the statemachine.""" - super().__init__(None) + super().__init__(bus, loop) self._api = api self.mirror() diff --git a/homeassistant/util/__init__.py b/homeassistant/util/__init__.py index c5df3834e72..a9df428513a 100644 --- a/homeassistant/util/__init__.py +++ b/homeassistant/util/__init__.py @@ -377,6 +377,26 @@ class ThreadPool(object): self.worker_count, self.current_jobs, self._work_queue.qsize()) + def add_many_jobs(self, jobs): + """Add a list of jobs to the queue.""" + with self._lock: + if not self.running: + raise RuntimeError("ThreadPool not running") + + for priority, job in jobs: + self._work_queue.put(PriorityQueueItem(priority, job)) + + # Check if our queue is getting too big. + if self._work_queue.qsize() > self.busy_warning_limit \ + and self._busy_callback is not None: + + # Increase limit we will issue next warning. + self.busy_warning_limit *= 2 + + self._busy_callback( + self.worker_count, self.current_jobs, + self._work_queue.qsize()) + def block_till_done(self): """Block till current work is done.""" self._work_queue.join() diff --git a/homeassistant/util/async.py b/homeassistant/util/async.py new file mode 100644 index 00000000000..54a3204c78d --- /dev/null +++ b/homeassistant/util/async.py @@ -0,0 +1,154 @@ +"""Asyncio backports for Python 3.4.3 compatibility.""" +import concurrent.futures +from asyncio import coroutines +from asyncio.futures import Future + +try: + from asyncio import ensure_future +except ImportError: + # Python 3.4.3 and earlier has this as async + # pylint: disable=unused-import + from asyncio import async + ensure_future = async + + +def _set_result_unless_cancelled(fut, result): + """Helper setting the result only if the future was not cancelled.""" + if fut.cancelled(): + return + fut.set_result(result) + + +def _set_concurrent_future_state(concurr, source): + """Copy state from a future to a concurrent.futures.Future.""" + assert source.done() + if source.cancelled(): + concurr.cancel() + if not concurr.set_running_or_notify_cancel(): + return + exception = source.exception() + if exception is not None: + concurr.set_exception(exception) + else: + result = source.result() + concurr.set_result(result) + + +def _copy_future_state(source, dest): + """Internal helper to copy state from another Future. + + The other Future may be a concurrent.futures.Future. + """ + assert source.done() + if dest.cancelled(): + return + assert not dest.done() + if source.cancelled(): + dest.cancel() + else: + exception = source.exception() + if exception is not None: + dest.set_exception(exception) + else: + result = source.result() + dest.set_result(result) + + +def _chain_future(source, destination): + """Chain two futures so that when one completes, so does the other. + + The result (or exception) of source will be copied to destination. + If destination is cancelled, source gets cancelled too. + Compatible with both asyncio.Future and concurrent.futures.Future. + """ + if not isinstance(source, (Future, concurrent.futures.Future)): + raise TypeError('A future is required for source argument') + if not isinstance(destination, (Future, concurrent.futures.Future)): + raise TypeError('A future is required for destination argument') + # pylint: disable=protected-access + source_loop = source._loop if isinstance(source, Future) else None + dest_loop = destination._loop if isinstance(destination, Future) else None + + def _set_state(future, other): + if isinstance(future, Future): + _copy_future_state(other, future) + else: + _set_concurrent_future_state(future, other) + + def _call_check_cancel(destination): + if destination.cancelled(): + if source_loop is None or source_loop is dest_loop: + source.cancel() + else: + source_loop.call_soon_threadsafe(source.cancel) + + def _call_set_state(source): + if dest_loop is None or dest_loop is source_loop: + _set_state(destination, source) + else: + dest_loop.call_soon_threadsafe(_set_state, destination, source) + + destination.add_done_callback(_call_check_cancel) + source.add_done_callback(_call_set_state) + + +def run_coroutine_threadsafe(coro, loop): + """Submit a coroutine object to a given event loop. + + Return a concurrent.futures.Future to access the result. + """ + if not coroutines.iscoroutine(coro): + raise TypeError('A coroutine object is required') + future = concurrent.futures.Future() + + def callback(): + """Callback to call the coroutine.""" + try: + # pylint: disable=deprecated-method + _chain_future(ensure_future(coro, loop=loop), future) + except Exception as exc: + if future.set_running_or_notify_cancel(): + future.set_exception(exc) + raise + + loop.call_soon_threadsafe(callback) + return future + + +def fire_coroutine_threadsafe(coro, loop): + """Submit a coroutine object to a given event loop. + + This method does not provide a way to retrieve the result and + is intended for fire-and-forget use. This reduces the + work involved to fire the function on the loop. + """ + if not coroutines.iscoroutine(coro): + raise TypeError('A coroutine object is required: %s' % coro) + + def callback(): + """Callback to fire coroutine.""" + # pylint: disable=deprecated-method + ensure_future(coro, loop=loop) + + loop.call_soon_threadsafe(callback) + return + + +def run_callback_threadsafe(loop, callback, *args): + """Submit a callback object to a given event loop. + + Return a concurrent.futures.Future to access the result. + """ + future = concurrent.futures.Future() + + def run_callback(): + """Run callback and store result.""" + try: + future.set_result(callback(*args)) + except Exception as exc: + if future.set_running_or_notify_cancel(): + future.set_exception(exc) + raise + + loop.call_soon_threadsafe(run_callback) + return future diff --git a/tests/common.py b/tests/common.py index 3c6815ece02..3f6b88d7a8a 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,10 +1,12 @@ """Test the helper method for writing tests.""" +import asyncio import os from datetime import timedelta from unittest import mock from unittest.mock import patch from io import StringIO import logging +import threading from homeassistant import core as ha, loader from homeassistant.bootstrap import setup_component @@ -29,11 +31,13 @@ def get_test_config_dir(*add_path): def get_test_home_assistant(num_threads=None): """Return a Home Assistant object pointing at test config dir.""" + loop = asyncio.new_event_loop() + if num_threads: orig_num_threads = ha.MIN_WORKER_THREAD ha.MIN_WORKER_THREAD = num_threads - hass = ha.HomeAssistant() + hass = ha.HomeAssistant(loop) if num_threads: ha.MIN_WORKER_THREAD = orig_num_threads @@ -49,6 +53,26 @@ def get_test_home_assistant(num_threads=None): if 'custom_components.test' not in loader.AVAILABLE_COMPONENTS: loader.prepare(hass) + # FIXME should not be a daemon. Means hass.stop() not called in teardown + threading.Thread(name="LoopThread", target=loop.run_forever, + daemon=True).start() + + orig_start = hass.start + + @asyncio.coroutine + def fake_stop(): + yield None + + def start_hass(): + """Helper to start hass.""" + with patch.object(hass.loop, 'run_forever', return_value=None): + with patch.object(hass, 'async_stop', return_value=fake_stop()): + with patch.object(ha, 'create_timer', return_value=None): + orig_start() + hass.block_till_done() + + hass.start = start_hass + return hass diff --git a/tests/components/alarm_control_panel/test_manual.py b/tests/components/alarm_control_panel/test_manual.py index e77180a8bc2..85d34002524 100644 --- a/tests/components/alarm_control_panel/test_manual.py +++ b/tests/components/alarm_control_panel/test_manual.py @@ -42,7 +42,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.states.get(entity_id).state) alarm_control_panel.alarm_arm_home(self.hass, CODE) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_ARMED_HOME, self.hass.states.get(entity_id).state) @@ -64,7 +64,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.states.get(entity_id).state) alarm_control_panel.alarm_arm_home(self.hass, CODE, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_PENDING, self.hass.states.get(entity_id).state) @@ -73,7 +73,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): with patch(('homeassistant.components.alarm_control_panel.manual.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_ARMED_HOME, self.hass.states.get(entity_id).state) @@ -95,7 +95,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.states.get(entity_id).state) alarm_control_panel.alarm_arm_home(self.hass, CODE + '2') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_DISARMED, self.hass.states.get(entity_id).state) @@ -117,7 +117,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.states.get(entity_id).state) alarm_control_panel.alarm_arm_away(self.hass, CODE, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_ARMED_AWAY, self.hass.states.get(entity_id).state) @@ -139,7 +139,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.states.get(entity_id).state) alarm_control_panel.alarm_arm_away(self.hass, CODE) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_PENDING, self.hass.states.get(entity_id).state) @@ -148,7 +148,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): with patch(('homeassistant.components.alarm_control_panel.manual.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_ARMED_AWAY, self.hass.states.get(entity_id).state) @@ -170,7 +170,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.states.get(entity_id).state) alarm_control_panel.alarm_arm_away(self.hass, CODE + '2') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_DISARMED, self.hass.states.get(entity_id).state) @@ -191,7 +191,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.states.get(entity_id).state) alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_TRIGGERED, self.hass.states.get(entity_id).state) @@ -213,7 +213,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.states.get(entity_id).state) alarm_control_panel.alarm_trigger(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_PENDING, self.hass.states.get(entity_id).state) @@ -222,7 +222,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): with patch(('homeassistant.components.alarm_control_panel.manual.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_TRIGGERED, self.hass.states.get(entity_id).state) @@ -231,7 +231,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): with patch(('homeassistant.components.alarm_control_panel.manual.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_DISARMED, self.hass.states.get(entity_id).state) @@ -253,7 +253,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.states.get(entity_id).state) alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_TRIGGERED, self.hass.states.get(entity_id).state) @@ -262,7 +262,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): with patch(('homeassistant.components.alarm_control_panel.manual.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_DISARMED, self.hass.states.get(entity_id).state) @@ -283,13 +283,13 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.states.get(entity_id).state) alarm_control_panel.alarm_trigger(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_PENDING, self.hass.states.get(entity_id).state) alarm_control_panel.alarm_disarm(self.hass, entity_id=entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_DISARMED, self.hass.states.get(entity_id).state) @@ -298,7 +298,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): with patch(('homeassistant.components.alarm_control_panel.manual.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_DISARMED, self.hass.states.get(entity_id).state) @@ -320,13 +320,13 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.states.get(entity_id).state) alarm_control_panel.alarm_trigger(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_PENDING, self.hass.states.get(entity_id).state) alarm_control_panel.alarm_disarm(self.hass, entity_id=entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_PENDING, self.hass.states.get(entity_id).state) @@ -335,7 +335,7 @@ class TestAlarmControlPanelManual(unittest.TestCase): with patch(('homeassistant.components.alarm_control_panel.manual.' 'dt_util.utcnow'), return_value=future): fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ALARM_TRIGGERED, self.hass.states.get(entity_id).state) diff --git a/tests/components/alarm_control_panel/test_mqtt.py b/tests/components/alarm_control_panel/test_mqtt.py index 9941c500a75..e4e120cec19 100644 --- a/tests/components/alarm_control_panel/test_mqtt.py +++ b/tests/components/alarm_control_panel/test_mqtt.py @@ -66,7 +66,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED): fire_mqtt_message(self.hass, 'alarm/state', state) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(state, self.hass.states.get(entity_id).state) def test_ignore_update_state_if_unknown_via_state_topic(self): @@ -87,7 +87,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): self.hass.states.get(entity_id).state) fire_mqtt_message(self.hass, 'alarm/state', 'unsupported state') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_UNKNOWN, self.hass.states.get(entity_id).state) def test_arm_home_publishes_mqtt(self): @@ -103,7 +103,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): }) alarm_control_panel.alarm_arm_home(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('alarm/command', 'ARM_HOME', 0, False), self.mock_publish.mock_calls[-1][1]) @@ -122,7 +122,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): call_count = self.mock_publish.call_count alarm_control_panel.alarm_arm_home(self.hass, 'abcd') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(call_count, self.mock_publish.call_count) def test_arm_away_publishes_mqtt(self): @@ -138,7 +138,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): }) alarm_control_panel.alarm_arm_away(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('alarm/command', 'ARM_AWAY', 0, False), self.mock_publish.mock_calls[-1][1]) @@ -157,7 +157,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): call_count = self.mock_publish.call_count alarm_control_panel.alarm_arm_away(self.hass, 'abcd') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(call_count, self.mock_publish.call_count) def test_disarm_publishes_mqtt(self): @@ -173,7 +173,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): }) alarm_control_panel.alarm_disarm(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('alarm/command', 'DISARM', 0, False), self.mock_publish.mock_calls[-1][1]) @@ -192,5 +192,5 @@ class TestAlarmControlPanelMQTT(unittest.TestCase): call_count = self.mock_publish.call_count alarm_control_panel.alarm_disarm(self.hass, 'abcd') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(call_count, self.mock_publish.call_count) diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index 80b1f507651..33fc5bf117a 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -41,14 +41,14 @@ class TestAutomationEvent(unittest.TestCase): }) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) automation.turn_off(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_event_with_data(self): @@ -68,7 +68,7 @@ class TestAutomationEvent(unittest.TestCase): self.hass.bus.fire('test_event', {'some_attr': 'some_value', 'another': 'value'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_not_fires_if_event_data_not_matches(self): @@ -87,5 +87,5 @@ class TestAutomationEvent(unittest.TestCase): }) self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py index 3d69cca2d32..cc10b134caf 100644 --- a/tests/components/automation/test_init.py +++ b/tests/components/automation/test_init.py @@ -68,7 +68,7 @@ class TestAutomation(unittest.TestCase): with patch('homeassistant.components.automation.utcnow', return_value=time): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 assert 'event - test_event' == self.calls[0].data['some'] state = self.hass.states.get('automation.hello') @@ -91,7 +91,7 @@ class TestAutomation(unittest.TestCase): }) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual(['hello.world'], self.calls[0].data.get(ATTR_ENTITY_ID)) @@ -112,7 +112,7 @@ class TestAutomation(unittest.TestCase): }) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual(['hello.world', 'hello.world2'], self.calls[0].data.get(ATTR_ENTITY_ID)) @@ -138,10 +138,10 @@ class TestAutomation(unittest.TestCase): }) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.hass.states.set('test.entity', 'hello') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(self.calls)) def test_two_conditions_with_and(self): @@ -175,17 +175,17 @@ class TestAutomation(unittest.TestCase): self.hass.states.set(entity_id, 100) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.hass.states.set(entity_id, 101) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.hass.states.set(entity_id, 151) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_two_conditions_with_or(self): @@ -220,17 +220,17 @@ class TestAutomation(unittest.TestCase): self.hass.states.set(entity_id, 200) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.hass.states.set(entity_id, 100) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(self.calls)) self.hass.states.set(entity_id, 250) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(self.calls)) def test_using_trigger_as_condition(self): @@ -259,19 +259,19 @@ class TestAutomation(unittest.TestCase): }) self.hass.states.set(entity_id, 100) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.hass.states.set(entity_id, 120) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.hass.states.set(entity_id, 100) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(self.calls)) self.hass.states.set(entity_id, 151) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(self.calls)) def test_using_trigger_as_condition_with_invalid_condition(self): @@ -299,7 +299,7 @@ class TestAutomation(unittest.TestCase): }) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_automation_list_setting(self): @@ -326,11 +326,11 @@ class TestAutomation(unittest.TestCase): })) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.hass.bus.fire('test_event_2') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(self.calls)) def test_automation_calling_two_actions(self): @@ -353,7 +353,7 @@ class TestAutomation(unittest.TestCase): })) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 2 assert self.calls[0].data['position'] == 0 @@ -383,37 +383,37 @@ class TestAutomation(unittest.TestCase): assert automation.is_on(self.hass, entity_id) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 automation.turn_off(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert not automation.is_on(self.hass, entity_id) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 automation.toggle(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert automation.is_on(self.hass, entity_id) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 2 automation.trigger(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 3 automation.turn_off(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() automation.trigger(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 4 automation.turn_on(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert automation.is_on(self.hass, entity_id) @patch('homeassistant.config.load_yaml_config_file', return_value={ @@ -455,13 +455,13 @@ class TestAutomation(unittest.TestCase): assert listeners.get('test_event2') is None self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 assert self.calls[0].data.get('event') == 'test_event' automation.reload(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.get('automation.hello') is None assert self.hass.states.get('automation.bye') is not None @@ -470,11 +470,11 @@ class TestAutomation(unittest.TestCase): assert listeners.get('test_event2') == 1 self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 self.hass.bus.fire('test_event2') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 2 assert self.calls[1].data.get('event') == 'test_event2' @@ -501,18 +501,18 @@ class TestAutomation(unittest.TestCase): assert self.hass.states.get('automation.hello') is not None self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 assert self.calls[0].data.get('event') == 'test_event' automation.reload(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.get('automation.hello') is not None self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 2 def test_reload_config_handles_load_fails(self): @@ -535,7 +535,7 @@ class TestAutomation(unittest.TestCase): assert self.hass.states.get('automation.hello') is not None self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 assert self.calls[0].data.get('event') == 'test_event' @@ -543,10 +543,10 @@ class TestAutomation(unittest.TestCase): with patch('homeassistant.config.load_yaml_config_file', side_effect=HomeAssistantError('bla')): automation.reload(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.get('automation.hello') is not None self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 2 diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py index 9bd22d0675c..aade8b7dad8 100644 --- a/tests/components/automation/test_mqtt.py +++ b/tests/components/automation/test_mqtt.py @@ -45,15 +45,15 @@ class TestAutomationMQTT(unittest.TestCase): }) fire_mqtt_message(self.hass, 'test-topic', 'test_payload') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual('mqtt - test-topic - test_payload', self.calls[0].data['some']) automation.turn_off(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_mqtt_message(self.hass, 'test-topic', 'test_payload') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_topic_and_payload_match(self): @@ -72,7 +72,7 @@ class TestAutomationMQTT(unittest.TestCase): }) fire_mqtt_message(self.hass, 'test-topic', 'hello') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_not_fires_on_topic_but_no_payload_match(self): @@ -91,5 +91,5 @@ class TestAutomationMQTT(unittest.TestCase): }) fire_mqtt_message(self.hass, 'test-topic', 'no-hello') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py index 9ee8514052c..01ce5e7c4e6 100644 --- a/tests/components/automation/test_numeric_state.py +++ b/tests/components/automation/test_numeric_state.py @@ -42,21 +42,21 @@ class TestAutomationNumericState(unittest.TestCase): }) # 9 is below 10 self.hass.states.set('test.entity', 9) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) # Set above 12 so the automation will fire again self.hass.states.set('test.entity', 12) automation.turn_off(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('test.entity', 9) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_entity_change_over_to_below(self): """"Test the firing with changed entity.""" self.hass.states.set('test.entity', 11) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { @@ -73,13 +73,13 @@ class TestAutomationNumericState(unittest.TestCase): # 9 is below 10 self.hass.states.set('test.entity', 9) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_not_fires_on_entity_change_below_to_below(self): """"Test the firing with changed entity.""" self.hass.states.set('test.entity', 9) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { @@ -96,7 +96,7 @@ class TestAutomationNumericState(unittest.TestCase): # 9 is below 10 so this should not fire again self.hass.states.set('test.entity', 8) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_fires_on_entity_change_above(self): @@ -115,14 +115,14 @@ class TestAutomationNumericState(unittest.TestCase): }) # 11 is above 10 self.hass.states.set('test.entity', 11) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_entity_change_below_to_above(self): """"Test the firing with changed entity.""" # set initial state self.hass.states.set('test.entity', 9) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { @@ -139,14 +139,14 @@ class TestAutomationNumericState(unittest.TestCase): # 11 is above 10 and 9 is below self.hass.states.set('test.entity', 11) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_not_fires_on_entity_change_above_to_above(self): """"Test the firing with changed entity.""" # set initial state self.hass.states.set('test.entity', 11) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { @@ -163,7 +163,7 @@ class TestAutomationNumericState(unittest.TestCase): # 11 is above 10 so this should fire again self.hass.states.set('test.entity', 12) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_fires_on_entity_change_below_range(self): @@ -183,7 +183,7 @@ class TestAutomationNumericState(unittest.TestCase): }) # 9 is below 10 self.hass.states.set('test.entity', 9) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_entity_change_below_above_range(self): @@ -203,13 +203,13 @@ class TestAutomationNumericState(unittest.TestCase): }) # 4 is below 5 self.hass.states.set('test.entity', 4) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_fires_on_entity_change_over_to_below_range(self): """"Test the firing with changed entity.""" self.hass.states.set('test.entity', 11) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { @@ -227,13 +227,13 @@ class TestAutomationNumericState(unittest.TestCase): # 9 is below 10 self.hass.states.set('test.entity', 9) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_entity_change_over_to_below_above_range(self): """"Test the firing with changed entity.""" self.hass.states.set('test.entity', 11) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { @@ -251,7 +251,7 @@ class TestAutomationNumericState(unittest.TestCase): # 4 is below 5 so it should not fire self.hass.states.set('test.entity', 4) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_not_fires_if_entity_not_match(self): @@ -270,7 +270,7 @@ class TestAutomationNumericState(unittest.TestCase): }) self.hass.states.set('test.entity', 11) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_fires_on_entity_change_below_with_attribute(self): @@ -289,7 +289,7 @@ class TestAutomationNumericState(unittest.TestCase): }) # 9 is below 10 self.hass.states.set('test.entity', 9, {'test_attribute': 11}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_not_fires_on_entity_change_not_below_with_attribute(self): @@ -308,7 +308,7 @@ class TestAutomationNumericState(unittest.TestCase): }) # 11 is not below 10 self.hass.states.set('test.entity', 11, {'test_attribute': 9}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_fires_on_attribute_change_with_attribute_below(self): @@ -328,7 +328,7 @@ class TestAutomationNumericState(unittest.TestCase): }) # 9 is below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': 9}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_not_fires_on_attribute_change_with_attribute_not_below(self): @@ -348,7 +348,7 @@ class TestAutomationNumericState(unittest.TestCase): }) # 11 is not below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': 11}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_not_fires_on_entity_change_with_attribute_below(self): @@ -368,7 +368,7 @@ class TestAutomationNumericState(unittest.TestCase): }) # 11 is not below 10, entity state value should not be tested self.hass.states.set('test.entity', '9', {'test_attribute': 11}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_not_fires_on_entity_change_with_not_attribute_below(self): @@ -388,7 +388,7 @@ class TestAutomationNumericState(unittest.TestCase): }) # 11 is not below 10, entity state value should not be tested self.hass.states.set('test.entity', 'entity') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_fires_on_attr_change_with_attribute_below_and_multiple_attr(self): @@ -409,7 +409,7 @@ class TestAutomationNumericState(unittest.TestCase): # 9 is not below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': 9, 'not_test_attribute': 11}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_template_list(self): @@ -431,7 +431,7 @@ class TestAutomationNumericState(unittest.TestCase): # 3 is below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': [11, 15, 3]}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_template_string(self): @@ -457,10 +457,10 @@ class TestAutomationNumericState(unittest.TestCase): }) self.hass.states.set('test.entity', 'test state 1', {'test_attribute': '1.2'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('test.entity', 'test state 2', {'test_attribute': '0.9'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual( 'numeric_state - test.entity - 10.0 - None - test state 1 - ' @@ -485,7 +485,7 @@ class TestAutomationNumericState(unittest.TestCase): # 11 is not below 10 self.hass.states.set('test.entity', 'entity', {'test_attribute': 11, 'not_test_attribute': 9}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_action(self): @@ -512,18 +512,18 @@ class TestAutomationNumericState(unittest.TestCase): self.hass.states.set(entity_id, test_state) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.hass.states.set(entity_id, test_state - 1) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.hass.states.set(entity_id, test_state + 1) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(self.calls)) diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py index 0b715cb365c..7132bf9e7a2 100644 --- a/tests/components/automation/test_state.py +++ b/tests/components/automation/test_state.py @@ -32,7 +32,7 @@ class TestAutomationState(unittest.TestCase): def test_if_fires_on_entity_change(self): """Test for firing on entity change.""" self.hass.states.set('test.entity', 'hello') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { @@ -53,16 +53,16 @@ class TestAutomationState(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual( 'state - test.entity - hello - world - None', self.calls[0].data['some']) automation.turn_off(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('test.entity', 'planet') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_entity_change_with_from_filter(self): @@ -81,7 +81,7 @@ class TestAutomationState(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_entity_change_with_to_filter(self): @@ -100,7 +100,7 @@ class TestAutomationState(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_entity_change_with_state_filter(self): @@ -119,7 +119,7 @@ class TestAutomationState(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_entity_change_with_both_filters(self): @@ -139,7 +139,7 @@ class TestAutomationState(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_not_fires_if_to_filter_not_match(self): @@ -159,7 +159,7 @@ class TestAutomationState(unittest.TestCase): }) self.hass.states.set('test.entity', 'moon') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_not_fires_if_from_filter_not_match(self): @@ -181,7 +181,7 @@ class TestAutomationState(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_not_fires_if_entity_not_match(self): @@ -199,7 +199,7 @@ class TestAutomationState(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_action(self): @@ -225,13 +225,13 @@ class TestAutomationState(unittest.TestCase): self.hass.states.set(entity_id, test_state) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.hass.states.set(entity_id, test_state + 'something') self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) @@ -315,11 +315,11 @@ class TestAutomationState(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('test.entity', 'not_world') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_fires_on_entity_change_with_for(self): @@ -341,9 +341,9 @@ class TestAutomationState(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_for_condition(self): @@ -373,13 +373,13 @@ class TestAutomationState(unittest.TestCase): # not enough time has passed self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) # Time travel 10 secs into the future mock_utcnow.return_value = point2 self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fails_setup_for_without_time(self): diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py index d3bbd254e1b..8058854aaa9 100644 --- a/tests/components/automation/test_sun.py +++ b/tests/components/automation/test_sun.py @@ -55,19 +55,19 @@ class TestAutomationSun(unittest.TestCase): }) automation.turn_off(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, trigger_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) with patch('homeassistant.util.dt.utcnow', return_value=now): automation.turn_on(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, trigger_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_sunrise_trigger(self): @@ -94,7 +94,7 @@ class TestAutomationSun(unittest.TestCase): }) fire_time_changed(self.hass, trigger_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_sunset_trigger_with_offset(self): @@ -127,7 +127,7 @@ class TestAutomationSun(unittest.TestCase): }) fire_time_changed(self.hass, trigger_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual('sun - sunset - 0:30:00', self.calls[0].data['some']) @@ -156,7 +156,7 @@ class TestAutomationSun(unittest.TestCase): }) fire_time_changed(self.hass, trigger_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_action_before(self): @@ -185,14 +185,14 @@ class TestAutomationSun(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) now = datetime(2015, 9, 16, 10, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_action_after(self): @@ -221,14 +221,14 @@ class TestAutomationSun(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_action_before_with_offset(self): @@ -258,14 +258,14 @@ class TestAutomationSun(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_action_after_with_offset(self): @@ -295,14 +295,14 @@ class TestAutomationSun(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) now = datetime(2015, 9, 16, 15, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_action_before_and_after_during(self): @@ -333,21 +333,21 @@ class TestAutomationSun(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) now = datetime(2015, 9, 16, 15, 1, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) now = datetime(2015, 9, 16, 12, tzinfo=dt_util.UTC) with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_action_after_different_tz(self): @@ -379,7 +379,7 @@ class TestAutomationSun(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) # After @@ -387,5 +387,5 @@ class TestAutomationSun(unittest.TestCase): with patch('homeassistant.util.dt.now', return_value=now): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py index a33da951cc8..327bedb23f2 100644 --- a/tests/components/automation/test_template.py +++ b/tests/components/automation/test_template.py @@ -42,14 +42,14 @@ class TestAutomationTemplate(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) automation.turn_off(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('test.entity', 'planet') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_change_str(self): @@ -67,7 +67,7 @@ class TestAutomationTemplate(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_change_str_crazy(self): @@ -85,7 +85,7 @@ class TestAutomationTemplate(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_not_fires_on_change_bool(self): @@ -103,7 +103,7 @@ class TestAutomationTemplate(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_not_fires_on_change_str(self): @@ -121,7 +121,7 @@ class TestAutomationTemplate(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_not_fires_on_change_str_crazy(self): @@ -139,7 +139,7 @@ class TestAutomationTemplate(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_fires_on_no_change(self): @@ -156,11 +156,11 @@ class TestAutomationTemplate(unittest.TestCase): } }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.calls = [] self.hass.states.set('test.entity', 'hello') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_fires_on_two_change(self): @@ -179,12 +179,12 @@ class TestAutomationTemplate(unittest.TestCase): # Trigger once self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) # Trigger again self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_change_with_template(self): @@ -202,7 +202,7 @@ class TestAutomationTemplate(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_not_fires_on_change_with_template(self): @@ -219,11 +219,11 @@ class TestAutomationTemplate(unittest.TestCase): } }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.calls = [] self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 0 def test_if_fires_on_change_with_template_advanced(self): @@ -250,11 +250,11 @@ class TestAutomationTemplate(unittest.TestCase): } }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.calls = [] self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual( 'template - test.entity - hello - world', @@ -280,12 +280,12 @@ class TestAutomationTemplate(unittest.TestCase): # Different state self.hass.states.set('test.entity', 'worldz') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) # Different state self.hass.states.set('test.entity', 'hello') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_fires_on_change_with_template_2(self): @@ -303,31 +303,31 @@ class TestAutomationTemplate(unittest.TestCase): } }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.calls = [] self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 0 self.hass.states.set('test.entity', 'home') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 self.hass.states.set('test.entity', 'work') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 self.hass.states.set('test.entity', 'not_home') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 self.hass.states.set('test.entity', 'home') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 2 def test_if_action(self): @@ -350,17 +350,17 @@ class TestAutomationTemplate(unittest.TestCase): # Condition is not true yet self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) # Change condition to true, but it shouldn't be triggered yet self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) # Condition is true and event is triggered self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_on_change_with_bad_template(self): @@ -392,5 +392,5 @@ class TestAutomationTemplate(unittest.TestCase): }) self.hass.states.set('test.entity', 'world') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py index 3c195f2eb38..edc5fb4cdc1 100644 --- a/tests/components/automation/test_time.py +++ b/tests/components/automation/test_time.py @@ -43,14 +43,14 @@ class TestAutomationTime(unittest.TestCase): }) fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) automation.turn_off(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_when_minute_matches(self): @@ -69,7 +69,7 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace(minute=0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_when_second_matches(self): @@ -88,7 +88,7 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_when_all_matches(self): @@ -110,7 +110,7 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace( hour=1, minute=2, second=3)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_periodic_seconds(self): @@ -130,7 +130,7 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace( hour=0, minute=0, second=2)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_periodic_minutes(self): @@ -150,7 +150,7 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace( hour=0, minute=2, second=0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_periodic_hours(self): @@ -170,7 +170,7 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace( hour=2, minute=0, second=0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_if_fires_using_after(self): @@ -194,7 +194,7 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace( hour=5, minute=0, second=0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual('time - 5', self.calls[0].data['some']) @@ -214,7 +214,7 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace( hour=5, minute=0, second=0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_not_fires_using_wrong_after(self): @@ -238,7 +238,7 @@ class TestAutomationTime(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace( hour=1, minute=0, second=5)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_if_action_before(self): @@ -265,14 +265,14 @@ class TestAutomationTime(unittest.TestCase): with patch('homeassistant.helpers.condition.dt_util.now', return_value=before_10): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) with patch('homeassistant.helpers.condition.dt_util.now', return_value=after_10): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) @@ -300,14 +300,14 @@ class TestAutomationTime(unittest.TestCase): with patch('homeassistant.helpers.condition.dt_util.now', return_value=before_10): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) with patch('homeassistant.helpers.condition.dt_util.now', return_value=after_10): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) @@ -336,14 +336,14 @@ class TestAutomationTime(unittest.TestCase): with patch('homeassistant.helpers.condition.dt_util.now', return_value=monday): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) with patch('homeassistant.helpers.condition.dt_util.now', return_value=tuesday): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) @@ -373,20 +373,20 @@ class TestAutomationTime(unittest.TestCase): with patch('homeassistant.helpers.condition.dt_util.now', return_value=monday): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) with patch('homeassistant.helpers.condition.dt_util.now', return_value=tuesday): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(self.calls)) with patch('homeassistant.helpers.condition.dt_util.now', return_value=wednesday): self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(self.calls)) diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index 9d4161547ef..a083b6bccd6 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -40,7 +40,7 @@ class TestAutomationZone(unittest.TestCase): 'latitude': 32.881011, 'longitude': -117.234758 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { @@ -67,7 +67,7 @@ class TestAutomationZone(unittest.TestCase): 'latitude': 32.880586, 'longitude': -117.237564 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual( @@ -79,16 +79,16 @@ class TestAutomationZone(unittest.TestCase): 'latitude': 32.881011, 'longitude': -117.234758 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() automation.turn_off(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('test.entity', 'hello', { 'latitude': 32.880586, 'longitude': -117.237564 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) @@ -98,7 +98,7 @@ class TestAutomationZone(unittest.TestCase): 'latitude': 32.880586, 'longitude': -117.237564 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { @@ -118,7 +118,7 @@ class TestAutomationZone(unittest.TestCase): 'latitude': 32.881011, 'longitude': -117.234758 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) @@ -128,7 +128,7 @@ class TestAutomationZone(unittest.TestCase): 'latitude': 32.880586, 'longitude': -117.237564 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { @@ -148,7 +148,7 @@ class TestAutomationZone(unittest.TestCase): 'latitude': 32.881011, 'longitude': -117.234758 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) @@ -158,7 +158,7 @@ class TestAutomationZone(unittest.TestCase): 'latitude': 32.881011, 'longitude': -117.234758 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { @@ -178,7 +178,7 @@ class TestAutomationZone(unittest.TestCase): 'latitude': 32.880586, 'longitude': -117.237564 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) @@ -188,7 +188,7 @@ class TestAutomationZone(unittest.TestCase): 'latitude': 32.880586, 'longitude': -117.237564 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert _setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { @@ -208,5 +208,5 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.bus.fire('test_event') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) diff --git a/tests/components/binary_sensor/test_mqtt.py b/tests/components/binary_sensor/test_mqtt.py index 833fef14fc3..ada4a9b4224 100644 --- a/tests/components/binary_sensor/test_mqtt.py +++ b/tests/components/binary_sensor/test_mqtt.py @@ -38,12 +38,12 @@ class TestSensorMQTT(unittest.TestCase): self.assertEqual(STATE_OFF, state.state) fire_mqtt_message(self.hass, 'test-topic', 'ON') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test') self.assertEqual(STATE_ON, state.state) fire_mqtt_message(self.hass, 'test-topic', 'OFF') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test') self.assertEqual(STATE_OFF, state.state) diff --git a/tests/components/binary_sensor/test_template.py b/tests/components/binary_sensor/test_template.py index 0f08817f15a..fd47cfe4d24 100644 --- a/tests/components/binary_sensor/test_template.py +++ b/tests/components/binary_sensor/test_template.py @@ -110,11 +110,11 @@ class TestBinarySensorTemplate(unittest.TestCase): vs = template.BinarySensorTemplate(hass, 'parent', 'Parent', 'motion', '{{ 1 > 1 }}', MATCH_ALL) vs.update_ha_state() - hass.pool.block_till_done() + hass.block_till_done() with mock.patch.object(vs, 'update') as mock_update: hass.bus.fire(EVENT_STATE_CHANGED) - hass.pool.block_till_done() + hass.block_till_done() try: assert mock_update.call_count == 1 finally: diff --git a/tests/components/binary_sensor/test_trend.py b/tests/components/binary_sensor/test_trend.py index beb8683e97f..475e445175b 100644 --- a/tests/components/binary_sensor/test_trend.py +++ b/tests/components/binary_sensor/test_trend.py @@ -30,9 +30,9 @@ class TestTrendBinarySensor: }) self.hass.states.set('sensor.test_state', '1') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('sensor.test_state', '2') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_trend_sensor') assert state.state == 'on' @@ -51,9 +51,9 @@ class TestTrendBinarySensor: }) self.hass.states.set('sensor.test_state', '2') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('sensor.test_state', '1') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_trend_sensor') assert state.state == 'off' @@ -73,9 +73,9 @@ class TestTrendBinarySensor: }) self.hass.states.set('sensor.test_state', '1') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('sensor.test_state', '2') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_trend_sensor') assert state.state == 'off' @@ -95,9 +95,9 @@ class TestTrendBinarySensor: }) self.hass.states.set('sensor.test_state', '2') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('sensor.test_state', '1') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_trend_sensor') assert state.state == 'on' @@ -116,9 +116,9 @@ class TestTrendBinarySensor: } }) self.hass.states.set('sensor.test_state', 'State', {'attr': '1'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('sensor.test_state', 'State', {'attr': '2'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_trend_sensor') assert state.state == 'on' @@ -138,10 +138,10 @@ class TestTrendBinarySensor: }) self.hass.states.set('sensor.test_state', 'State', {'attr': '2'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('sensor.test_state', 'State', {'attr': '1'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_trend_sensor') assert state.state == 'off' @@ -160,9 +160,9 @@ class TestTrendBinarySensor: }) self.hass.states.set('sensor.test_state', 'Non') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('sensor.test_state', 'Numeric') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_trend_sensor') assert state.state == 'off' @@ -182,10 +182,10 @@ class TestTrendBinarySensor: }) self.hass.states.set('sensor.test_state', 'State', {'attr': '2'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set('sensor.test_state', 'State', {'attr': '1'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('binary_sensor.test_trend_sensor') assert state.state == 'off' diff --git a/tests/components/climate/test_demo.py b/tests/components/climate/test_demo.py index dbb9f8a192e..d6bb2c8d69d 100644 --- a/tests/components/climate/test_demo.py +++ b/tests/components/climate/test_demo.py @@ -54,7 +54,7 @@ class TestDemoClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual(21, state.attributes.get('temperature')) climate.set_temperature(self.hass, None, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(21, state.attributes.get('temperature')) def test_set_only_target_temp(self): @@ -62,7 +62,7 @@ class TestDemoClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual(21, state.attributes.get('temperature')) climate.set_temperature(self.hass, 30, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual(30.0, state.attributes.get('temperature')) @@ -98,7 +98,7 @@ class TestDemoClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual(67, state.attributes.get('humidity')) climate.set_humidity(self.hass, None, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual(67, state.attributes.get('humidity')) @@ -107,7 +107,7 @@ class TestDemoClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual(67, state.attributes.get('humidity')) climate.set_humidity(self.hass, 64, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual(64.0, state.attributes.get('humidity')) @@ -116,7 +116,7 @@ class TestDemoClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual("On High", state.attributes.get('fan_mode')) climate.set_fan_mode(self.hass, None, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual("On High", state.attributes.get('fan_mode')) @@ -125,7 +125,7 @@ class TestDemoClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual("On High", state.attributes.get('fan_mode')) climate.set_fan_mode(self.hass, "On Low", ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual("On Low", state.attributes.get('fan_mode')) @@ -134,7 +134,7 @@ class TestDemoClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual("Off", state.attributes.get('swing_mode')) climate.set_swing_mode(self.hass, None, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual("Off", state.attributes.get('swing_mode')) @@ -143,7 +143,7 @@ class TestDemoClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual("Off", state.attributes.get('swing_mode')) climate.set_swing_mode(self.hass, "Auto", ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual("Auto", state.attributes.get('swing_mode')) @@ -154,7 +154,7 @@ class TestDemoClimate(unittest.TestCase): self.assertEqual("cool", state.attributes.get('operation_mode')) self.assertEqual("cool", state.state) climate.set_operation_mode(self.hass, None, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual("cool", state.attributes.get('operation_mode')) self.assertEqual("cool", state.state) @@ -165,7 +165,7 @@ class TestDemoClimate(unittest.TestCase): self.assertEqual("cool", state.attributes.get('operation_mode')) self.assertEqual("cool", state.state) climate.set_operation_mode(self.hass, "heat", ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual("heat", state.attributes.get('operation_mode')) self.assertEqual("heat", state.state) @@ -175,20 +175,20 @@ class TestDemoClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual('on', state.attributes.get('away_mode')) climate.set_away_mode(self.hass, None, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('on', state.attributes.get('away_mode')) def test_set_away_mode_on(self): """Test setting the away mode on/true.""" climate.set_away_mode(self.hass, True, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual('on', state.attributes.get('away_mode')) def test_set_away_mode_off(self): """Test setting the away mode off/false.""" climate.set_away_mode(self.hass, False, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual('off', state.attributes.get('away_mode')) @@ -197,19 +197,19 @@ class TestDemoClimate(unittest.TestCase): state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual('off', state.attributes.get('aux_heat')) climate.set_aux_heat(self.hass, None, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('off', state.attributes.get('aux_heat')) def test_set_aux_heat_on(self): """Test setting the axillary heater on/true.""" climate.set_aux_heat(self.hass, True, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual('on', state.attributes.get('aux_heat')) def test_set_aux_heat_off(self): """Test setting the auxillary heater off/false.""" climate.set_aux_heat(self.hass, False, ENTITY_CLIMATE) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_CLIMATE) self.assertEqual('off', state.attributes.get('aux_heat')) diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py index 5c03abdf90f..1447d9f7919 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -121,7 +121,7 @@ class TestClimateGenericThermostat(unittest.TestCase): def test_set_target_temp(self): """Test the setting of the target temperature.""" climate.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY) self.assertEqual(30.0, state.attributes.get('temperature')) @@ -132,7 +132,7 @@ class TestClimateGenericThermostat(unittest.TestCase): unit = state.attributes.get('unit_of_measurement') self._setup_sensor(22.0, unit='bad_unit') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY) self.assertEqual(unit, state.attributes.get('unit_of_measurement')) @@ -145,7 +145,7 @@ class TestClimateGenericThermostat(unittest.TestCase): unit = state.attributes.get('unit_of_measurement') self._setup_sensor(None) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY) self.assertEqual(unit, state.attributes.get('unit_of_measurement')) @@ -155,9 +155,9 @@ class TestClimateGenericThermostat(unittest.TestCase): """Test if target temperature turn heater on.""" self._setup_switch(False) self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() climate.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -168,9 +168,9 @@ class TestClimateGenericThermostat(unittest.TestCase): """Test if target temperature turn heater off.""" self._setup_switch(True) self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() climate.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -181,9 +181,9 @@ class TestClimateGenericThermostat(unittest.TestCase): """Test if temperature change turn heater on.""" self._setup_switch(False) climate.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -194,9 +194,9 @@ class TestClimateGenericThermostat(unittest.TestCase): """Test if temperature change turn heater off.""" self._setup_switch(True) climate.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -245,9 +245,9 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): """Test if target temperature turn ac off.""" self._setup_switch(True) self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() climate.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -258,9 +258,9 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): """Test if target temperature turn ac on.""" self._setup_switch(False) self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() climate.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -271,9 +271,9 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): """Test if temperature change turn ac off.""" self._setup_switch(True) climate.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -284,9 +284,9 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): """Test if temperature change turn ac on.""" self._setup_switch(False) climate.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -336,9 +336,9 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): """Test if temperature change turn ac on.""" self._setup_switch(False) climate.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_temp_change_ac_trigger_on_long_enough(self): @@ -349,9 +349,9 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): return_value=fake_changed): self._setup_switch(False) climate.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -362,9 +362,9 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): """Test if temperature change turn ac on.""" self._setup_switch(True) climate.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_temp_change_ac_trigger_off_long_enough(self): @@ -375,9 +375,9 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): return_value=fake_changed): self._setup_switch(True) climate.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -426,18 +426,18 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): """Test if temp change doesn't turn heater off because of time.""" self._setup_switch(True) climate.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_temp_change_heater_trigger_on_not_long_enough(self): """Test if temp change doesn't turn heater on because of time.""" self._setup_switch(False) climate.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_temp_change_heater_trigger_on_long_enough(self): @@ -448,9 +448,9 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): return_value=fake_changed): self._setup_switch(False) climate.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -465,9 +465,9 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): return_value=fake_changed): self._setup_switch(True) climate.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) diff --git a/tests/components/cover/test_command_line.py b/tests/components/cover/test_command_line.py index e4ef6793127..dc9ec105431 100644 --- a/tests/components/cover/test_command_line.py +++ b/tests/components/cover/test_command_line.py @@ -5,18 +5,19 @@ import tempfile import unittest from unittest import mock -import homeassistant.core as ha import homeassistant.components.cover as cover from homeassistant.components.cover import ( command_line as cmd_rs) +from tests.common import get_test_home_assistant + class TestCommandCover(unittest.TestCase): """Test the cover command line platform.""" def setup_method(self, method): """Setup things to be run when tests are started.""" - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.rs = cmd_rs.CommandCover(self.hass, 'foo', 'command_open', 'command_close', 'command_stop', 'command_state', @@ -64,19 +65,19 @@ class TestCommandCover(unittest.TestCase): self.assertEqual('unknown', state.state) cover.open_cover(self.hass, 'cover.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('cover.test') self.assertEqual('open', state.state) cover.close_cover(self.hass, 'cover.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('cover.test') self.assertEqual('open', state.state) cover.stop_cover(self.hass, 'cover.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('cover.test') self.assertEqual('closed', state.state) diff --git a/tests/components/cover/test_demo.py b/tests/components/cover/test_demo.py index d7431f8fcbb..f51ea66c743 100644 --- a/tests/components/cover/test_demo.py +++ b/tests/components/cover/test_demo.py @@ -28,11 +28,11 @@ class TestCoverDemo(unittest.TestCase): state = self.hass.states.get(ENTITY_COVER) self.assertEqual(70, state.attributes.get('current_position')) cover.close_cover(self.hass, ENTITY_COVER) - self.hass.pool.block_till_done() + self.hass.block_till_done() for _ in range(7): future = dt_util.utcnow() + timedelta(seconds=1) fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_COVER) self.assertEqual(0, state.attributes.get('current_position')) @@ -42,11 +42,11 @@ class TestCoverDemo(unittest.TestCase): state = self.hass.states.get(ENTITY_COVER) self.assertEqual(70, state.attributes.get('current_position')) cover.open_cover(self.hass, ENTITY_COVER) - self.hass.pool.block_till_done() + self.hass.block_till_done() for _ in range(7): future = dt_util.utcnow() + timedelta(seconds=1) fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_COVER) self.assertEqual(100, state.attributes.get('current_position')) @@ -56,11 +56,11 @@ class TestCoverDemo(unittest.TestCase): state = self.hass.states.get(ENTITY_COVER) self.assertEqual(70, state.attributes.get('current_position')) cover.set_cover_position(self.hass, 10, ENTITY_COVER) - self.hass.pool.block_till_done() + self.hass.block_till_done() for _ in range(6): future = dt_util.utcnow() + timedelta(seconds=1) fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_COVER) self.assertEqual(10, state.attributes.get('current_position')) @@ -70,12 +70,12 @@ class TestCoverDemo(unittest.TestCase): state = self.hass.states.get(ENTITY_COVER) self.assertEqual(70, state.attributes.get('current_position')) cover.open_cover(self.hass, ENTITY_COVER) - self.hass.pool.block_till_done() + self.hass.block_till_done() future = dt_util.utcnow() + timedelta(seconds=1) fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() cover.stop_cover(self.hass, ENTITY_COVER) - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, future) state = self.hass.states.get(ENTITY_COVER) self.assertEqual(80, state.attributes.get('current_position')) @@ -85,11 +85,11 @@ class TestCoverDemo(unittest.TestCase): state = self.hass.states.get(ENTITY_COVER) self.assertEqual(50, state.attributes.get('current_tilt_position')) cover.close_cover_tilt(self.hass, ENTITY_COVER) - self.hass.pool.block_till_done() + self.hass.block_till_done() for _ in range(7): future = dt_util.utcnow() + timedelta(seconds=1) fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_COVER) self.assertEqual(0, state.attributes.get('current_tilt_position')) @@ -99,11 +99,11 @@ class TestCoverDemo(unittest.TestCase): state = self.hass.states.get(ENTITY_COVER) self.assertEqual(50, state.attributes.get('current_tilt_position')) cover.open_cover_tilt(self.hass, ENTITY_COVER) - self.hass.pool.block_till_done() + self.hass.block_till_done() for _ in range(7): future = dt_util.utcnow() + timedelta(seconds=1) fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_COVER) self.assertEqual(100, state.attributes.get('current_tilt_position')) @@ -113,11 +113,11 @@ class TestCoverDemo(unittest.TestCase): state = self.hass.states.get(ENTITY_COVER) self.assertEqual(50, state.attributes.get('current_tilt_position')) cover.set_cover_tilt_position(self.hass, 90, ENTITY_COVER) - self.hass.pool.block_till_done() + self.hass.block_till_done() for _ in range(7): future = dt_util.utcnow() + timedelta(seconds=1) fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_COVER) self.assertEqual(90, state.attributes.get('current_tilt_position')) @@ -127,12 +127,12 @@ class TestCoverDemo(unittest.TestCase): state = self.hass.states.get(ENTITY_COVER) self.assertEqual(50, state.attributes.get('current_tilt_position')) cover.close_cover_tilt(self.hass, ENTITY_COVER) - self.hass.pool.block_till_done() + self.hass.block_till_done() future = dt_util.utcnow() + timedelta(seconds=1) fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() cover.stop_cover_tilt(self.hass, ENTITY_COVER) - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, future) state = self.hass.states.get(ENTITY_COVER) self.assertEqual(40, state.attributes.get('current_tilt_position')) diff --git a/tests/components/cover/test_mqtt.py b/tests/components/cover/test_mqtt.py index e2bc008f3d7..d46b79ac382 100644 --- a/tests/components/cover/test_mqtt.py +++ b/tests/components/cover/test_mqtt.py @@ -41,19 +41,19 @@ class TestCoverMQTT(unittest.TestCase): self.assertEqual(STATE_UNKNOWN, state.state) fire_mqtt_message(self.hass, 'state-topic', '0') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('cover.test') self.assertEqual(STATE_CLOSED, state.state) fire_mqtt_message(self.hass, 'state-topic', '50') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('cover.test') self.assertEqual(STATE_OPEN, state.state) fire_mqtt_message(self.hass, 'state-topic', '100') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('cover.test') self.assertEqual(STATE_OPEN, state.state) @@ -75,7 +75,7 @@ class TestCoverMQTT(unittest.TestCase): self.assertEqual(STATE_UNKNOWN, state.state) cover.open_cover(self.hass, 'cover.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('command-topic', 'OPEN', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -99,7 +99,7 @@ class TestCoverMQTT(unittest.TestCase): self.assertEqual(STATE_UNKNOWN, state.state) cover.close_cover(self.hass, 'cover.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('command-topic', 'CLOSE', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -123,7 +123,7 @@ class TestCoverMQTT(unittest.TestCase): self.assertEqual(STATE_UNKNOWN, state.state) cover.stop_cover(self.hass, 'cover.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('command-topic', 'STOP', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -150,25 +150,25 @@ class TestCoverMQTT(unittest.TestCase): self.assertFalse('current_position' in state_attributes_dict) fire_mqtt_message(self.hass, 'state-topic', '0') - self.hass.pool.block_till_done() + self.hass.block_till_done() current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] self.assertEqual(0, current_cover_position) fire_mqtt_message(self.hass, 'state-topic', '50') - self.hass.pool.block_till_done() + self.hass.block_till_done() current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] self.assertEqual(50, current_cover_position) fire_mqtt_message(self.hass, 'state-topic', '101') - self.hass.pool.block_till_done() + self.hass.block_till_done() current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] self.assertEqual(50, current_cover_position) fire_mqtt_message(self.hass, 'state-topic', 'non-numeric') - self.hass.pool.block_till_done() + self.hass.block_till_done() current_cover_position = self.hass.states.get( 'cover.test').attributes['current_position'] self.assertEqual(50, current_cover_position) diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index 4aef11d3a36..b0ca306001b 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -194,7 +194,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): with patch('homeassistant.components.device_tracker.dt_util.utcnow', return_value=scan_time): fire_time_changed(self.hass, scan_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_NOT_HOME, self.hass.states.get('device_tracker.dev1').state) @@ -266,7 +266,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): 'gps': [.3, .8] } device_tracker.see(self.hass, **params) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert mock_see.call_count == 1 mock_see.assert_called_once_with(**params) @@ -274,7 +274,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): params['dev_id'] += chr(233) # e' acute accent from icloud device_tracker.see(self.hass, **params) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert mock_see.call_count == 1 mock_see.assert_called_once_with(**params) @@ -286,7 +286,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): device_tracker.see(self.hass, 'mac_1', host_name='hello') device_tracker.see(self.hass, 'mac_2', host_name='hello') - self.hass.pool.block_till_done() + self.hass.block_till_done() config = device_tracker.load_config(self.yaml_devices, self.hass, timedelta(seconds=0)) diff --git a/tests/components/device_tracker/test_locative.py b/tests/components/device_tracker/test_locative.py index 7c018eaa69a..5cc857070e8 100644 --- a/tests/components/device_tracker/test_locative.py +++ b/tests/components/device_tracker/test_locative.py @@ -60,7 +60,7 @@ class TestLocative(unittest.TestCase): def tearDown(self): """Stop everything that was started.""" - hass.pool.block_till_done() + hass.block_till_done() def test_missing_data(self, update_config): """Test missing data.""" diff --git a/tests/components/device_tracker/test_mqtt.py b/tests/components/device_tracker/test_mqtt.py index 321ab16ac3f..e2af75ed76b 100644 --- a/tests/components/device_tracker/test_mqtt.py +++ b/tests/components/device_tracker/test_mqtt.py @@ -65,5 +65,5 @@ class TestComponentsDeviceTrackerMQTT(unittest.TestCase): } }) fire_mqtt_message(self.hass, topic, location) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(location, self.hass.states.get(enttiy_id).state) diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index 57125d6e6ea..f59c9cb39fc 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -252,7 +252,7 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): else: mod_message = str_message fire_mqtt_message(self.hass, topic, mod_message) - self.hass.pool.block_till_done() + self.hass.block_till_done() def assert_location_state(self, location): """Test the assertion of a location state.""" @@ -574,7 +574,7 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): fire_mqtt_message( self.hass, EVENT_TOPIC, json.dumps(enter_message)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.send_message(EVENT_TOPIC, exit_message) self.assertEqual(owntracks.MOBILE_BEACONS_ACTIVE['greg_phone'], []) diff --git a/tests/components/fan/test_demo.py b/tests/components/fan/test_demo.py index db894ee54ed..3d0e7dabff8 100644 --- a/tests/components/fan/test_demo.py +++ b/tests/components/fan/test_demo.py @@ -22,7 +22,7 @@ class TestDemoFan(unittest.TestCase): self.assertTrue(fan.setup(self.hass, {'fan': { 'platform': 'demo', }})) - self.hass.pool.block_till_done() + self.hass.block_till_done() def tearDown(self): """Tear down unit test data.""" @@ -33,11 +33,11 @@ class TestDemoFan(unittest.TestCase): self.assertEqual(STATE_OFF, self.get_entity().state) fan.turn_on(self.hass, FAN_ENTITY_ID) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertNotEqual(STATE_OFF, self.get_entity().state) fan.turn_on(self.hass, FAN_ENTITY_ID, fan.SPEED_HIGH) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_ON, self.get_entity().state) self.assertEqual(fan.SPEED_HIGH, self.get_entity().attributes[fan.ATTR_SPEED]) @@ -47,11 +47,11 @@ class TestDemoFan(unittest.TestCase): self.assertEqual(STATE_OFF, self.get_entity().state) fan.turn_on(self.hass, FAN_ENTITY_ID) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertNotEqual(STATE_OFF, self.get_entity().state) fan.turn_off(self.hass, FAN_ENTITY_ID) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_OFF, self.get_entity().state) def test_set_speed(self): @@ -59,7 +59,7 @@ class TestDemoFan(unittest.TestCase): self.assertEqual(STATE_OFF, self.get_entity().state) fan.set_speed(self.hass, FAN_ENTITY_ID, fan.SPEED_LOW) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(fan.SPEED_LOW, self.get_entity().attributes.get('speed')) @@ -68,11 +68,11 @@ class TestDemoFan(unittest.TestCase): self.assertFalse(self.get_entity().attributes.get('oscillating')) fan.oscillate(self.hass, FAN_ENTITY_ID, True) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(self.get_entity().attributes.get('oscillating')) fan.oscillate(self.hass, FAN_ENTITY_ID, False) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertFalse(self.get_entity().attributes.get('oscillating')) def test_is_on(self): @@ -80,5 +80,5 @@ class TestDemoFan(unittest.TestCase): self.assertFalse(fan.is_on(self.hass, FAN_ENTITY_ID)) fan.turn_on(self.hass, FAN_ENTITY_ID) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(fan.is_on(self.hass, FAN_ENTITY_ID)) diff --git a/tests/components/garage_door/test_demo.py b/tests/components/garage_door/test_demo.py index 7c1ea056318..ae0a346676e 100644 --- a/tests/components/garage_door/test_demo.py +++ b/tests/components/garage_door/test_demo.py @@ -37,13 +37,13 @@ class TestGarageDoorDemo(unittest.TestCase): def test_open_door(self): """Test opeing of the door.""" gd.open_door(self.hass, LEFT) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertFalse(gd.is_closed(self.hass, LEFT)) def test_close_door(self): """Test closing ot the door.""" gd.close_door(self.hass, RIGHT) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(gd.is_closed(self.hass, RIGHT)) diff --git a/tests/components/garage_door/test_mqtt.py b/tests/components/garage_door/test_mqtt.py index 7e6dc8736d6..f2f5e61d1fb 100644 --- a/tests/components/garage_door/test_mqtt.py +++ b/tests/components/garage_door/test_mqtt.py @@ -53,13 +53,13 @@ class TestGarageDoorMQTT(unittest.TestCase): self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE)) fire_mqtt_message(self.hass, 'state-topic', '1') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('garage_door.test') self.assertEqual(STATE_OPEN, state.state) fire_mqtt_message(self.hass, 'state-topic', '0') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('garage_door.test') self.assertEqual(STATE_CLOSED, state.state) @@ -84,7 +84,7 @@ class TestGarageDoorMQTT(unittest.TestCase): self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) garage_door.open_door(self.hass, 'garage_door.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('command-topic', 'beer open', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -92,7 +92,7 @@ class TestGarageDoorMQTT(unittest.TestCase): self.assertEqual(STATE_OPEN, state.state) garage_door.close_door(self.hass, 'garage_door.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('command-topic', 'beer close', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -119,13 +119,13 @@ class TestGarageDoorMQTT(unittest.TestCase): self.assertEqual(STATE_CLOSED, state.state) fire_mqtt_message(self.hass, 'state-topic', '{"val":"beer open"}') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('garage_door.test') self.assertEqual(STATE_OPEN, state.state) fire_mqtt_message(self.hass, 'state-topic', '{"val":"beer closed"}') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('garage_door.test') self.assertEqual(STATE_CLOSED, state.state) diff --git a/tests/components/hvac/test_demo.py b/tests/components/hvac/test_demo.py index 536a1ac36f7..28842064a07 100644 --- a/tests/components/hvac/test_demo.py +++ b/tests/components/hvac/test_demo.py @@ -53,13 +53,13 @@ class TestDemoHvac(unittest.TestCase): state = self.hass.states.get(ENTITY_HVAC) self.assertEqual(21, state.attributes.get('temperature')) hvac.set_temperature(self.hass, None, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(21, state.attributes.get('temperature')) def test_set_target_temp(self): """Test the setting of the target temperature.""" hvac.set_temperature(self.hass, 30, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_HVAC) self.assertEqual(30.0, state.attributes.get('temperature')) @@ -68,13 +68,13 @@ class TestDemoHvac(unittest.TestCase): state = self.hass.states.get(ENTITY_HVAC) self.assertEqual(67, state.attributes.get('humidity')) hvac.set_humidity(self.hass, None, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(67, state.attributes.get('humidity')) def test_set_target_humidity(self): """Test the setting of the target humidity.""" hvac.set_humidity(self.hass, 64, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_HVAC) self.assertEqual(64.0, state.attributes.get('humidity')) @@ -83,13 +83,13 @@ class TestDemoHvac(unittest.TestCase): state = self.hass.states.get(ENTITY_HVAC) self.assertEqual("On High", state.attributes.get('fan_mode')) hvac.set_fan_mode(self.hass, None, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual("On High", state.attributes.get('fan_mode')) def test_set_fan_mode(self): """Test setting of new fan mode.""" hvac.set_fan_mode(self.hass, "On Low", ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_HVAC) self.assertEqual("On Low", state.attributes.get('fan_mode')) @@ -98,13 +98,13 @@ class TestDemoHvac(unittest.TestCase): state = self.hass.states.get(ENTITY_HVAC) self.assertEqual("Off", state.attributes.get('swing_mode')) hvac.set_swing_mode(self.hass, None, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual("Off", state.attributes.get('swing_mode')) def test_set_swing(self): """Test setting of new swing mode.""" hvac.set_swing_mode(self.hass, "Auto", ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_HVAC) self.assertEqual("Auto", state.attributes.get('swing_mode')) @@ -112,13 +112,13 @@ class TestDemoHvac(unittest.TestCase): """Test setting operation mode without required attribute.""" self.assertEqual("Cool", self.hass.states.get(ENTITY_HVAC).state) hvac.set_operation_mode(self.hass, None, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual("Cool", self.hass.states.get(ENTITY_HVAC).state) def test_set_operation(self): """Test setting of new operation mode.""" hvac.set_operation_mode(self.hass, "Heat", ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual("Heat", self.hass.states.get(ENTITY_HVAC).state) def test_set_away_mode_bad_attr(self): @@ -126,20 +126,20 @@ class TestDemoHvac(unittest.TestCase): state = self.hass.states.get(ENTITY_HVAC) self.assertEqual('on', state.attributes.get('away_mode')) hvac.set_away_mode(self.hass, None, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('on', state.attributes.get('away_mode')) def test_set_away_mode_on(self): """Test setting the away mode on/true.""" hvac.set_away_mode(self.hass, True, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_HVAC) self.assertEqual('on', state.attributes.get('away_mode')) def test_set_away_mode_off(self): """Test setting the away mode off/false.""" hvac.set_away_mode(self.hass, False, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_HVAC) self.assertEqual('off', state.attributes.get('away_mode')) @@ -148,19 +148,19 @@ class TestDemoHvac(unittest.TestCase): state = self.hass.states.get(ENTITY_HVAC) self.assertEqual('off', state.attributes.get('aux_heat')) hvac.set_aux_heat(self.hass, None, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('off', state.attributes.get('aux_heat')) def test_set_aux_heat_on(self): """Test setting the axillary heater on/true.""" hvac.set_aux_heat(self.hass, True, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_HVAC) self.assertEqual('on', state.attributes.get('aux_heat')) def test_set_aux_heat_off(self): """Test setting the auxillary heater off/false.""" hvac.set_aux_heat(self.hass, False, ENTITY_HVAC) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_HVAC) self.assertEqual('off', state.attributes.get('aux_heat')) diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index 1f6619c8b12..b24cbf53ba7 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -56,7 +56,7 @@ class TestLight(unittest.TestCase): xy_color='xy_color_val', profile='profile_val') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(turn_on_calls)) call = turn_on_calls[-1] @@ -79,7 +79,7 @@ class TestLight(unittest.TestCase): light.turn_off( self.hass, entity_id='entity_id_val', transition='transition_val') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(turn_off_calls)) call = turn_off_calls[-1] @@ -96,7 +96,7 @@ class TestLight(unittest.TestCase): light.toggle( self.hass, entity_id='entity_id_val', transition='transition_val') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(toggle_calls)) call = toggle_calls[-1] @@ -125,7 +125,7 @@ class TestLight(unittest.TestCase): light.turn_off(self.hass, entity_id=dev1.entity_id) light.turn_on(self.hass, entity_id=dev2.entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertFalse(light.is_on(self.hass, dev1.entity_id)) self.assertTrue(light.is_on(self.hass, dev2.entity_id)) @@ -133,7 +133,7 @@ class TestLight(unittest.TestCase): # turn on all lights light.turn_on(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(light.is_on(self.hass, dev1.entity_id)) self.assertTrue(light.is_on(self.hass, dev2.entity_id)) @@ -142,7 +142,7 @@ class TestLight(unittest.TestCase): # turn off all lights light.turn_off(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertFalse(light.is_on(self.hass, dev1.entity_id)) self.assertFalse(light.is_on(self.hass, dev2.entity_id)) @@ -151,7 +151,7 @@ class TestLight(unittest.TestCase): # toggle all lights light.toggle(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(light.is_on(self.hass, dev1.entity_id)) self.assertTrue(light.is_on(self.hass, dev2.entity_id)) @@ -160,7 +160,7 @@ class TestLight(unittest.TestCase): # toggle all lights light.toggle(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertFalse(light.is_on(self.hass, dev1.entity_id)) self.assertFalse(light.is_on(self.hass, dev2.entity_id)) @@ -173,7 +173,7 @@ class TestLight(unittest.TestCase): self.hass, dev2.entity_id, rgb_color=(255, 255, 255)) light.turn_on(self.hass, dev3.entity_id, xy_color=(.4, .6)) - self.hass.pool.block_till_done() + self.hass.block_till_done() method, data = dev1.last_call('turn_on') self.assertEqual( @@ -197,7 +197,7 @@ class TestLight(unittest.TestCase): self.hass, dev2.entity_id, profile=prof_name, brightness=100, xy_color=(.4, .6)) - self.hass.pool.block_till_done() + self.hass.block_till_done() method, data = dev1.last_call('turn_on') self.assertEqual( @@ -217,7 +217,7 @@ class TestLight(unittest.TestCase): light.turn_on(self.hass, dev2.entity_id, xy_color=["bla-di-bla", 5]) light.turn_on(self.hass, dev3.entity_id, rgb_color=[255, None, 2]) - self.hass.pool.block_till_done() + self.hass.block_till_done() method, data = dev1.last_call('turn_on') self.assertEqual({}, data) @@ -233,7 +233,7 @@ class TestLight(unittest.TestCase): self.hass, dev1.entity_id, profile=prof_name, brightness='bright', rgb_color='yellowish') - self.hass.pool.block_till_done() + self.hass.block_till_done() method, data = dev1.last_call('turn_on') self.assertEqual({}, data) @@ -273,7 +273,7 @@ class TestLight(unittest.TestCase): light.turn_on(self.hass, dev1.entity_id, profile='test') - self.hass.pool.block_till_done() + self.hass.block_till_done() method, data = dev1.last_call('turn_on') diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py index b830c2d0dd3..c6fc07fa438 100644 --- a/tests/components/light/test_mqtt.py +++ b/tests/components/light/test_mqtt.py @@ -106,7 +106,7 @@ class TestLightMQTT(unittest.TestCase): self.assertIsNone(state.attributes.get('brightness')) fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_ON, state.state) @@ -139,7 +139,7 @@ class TestLightMQTT(unittest.TestCase): self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE)) fire_mqtt_message(self.hass, 'test_light_rgb/status', '1') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_ON, state.state) @@ -147,28 +147,28 @@ class TestLightMQTT(unittest.TestCase): self.assertEqual(255, state.attributes.get('brightness')) fire_mqtt_message(self.hass, 'test_light_rgb/status', '0') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) fire_mqtt_message(self.hass, 'test_light_rgb/status', '1') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_mqtt_message(self.hass, 'test_light_rgb/brightness/status', '100') - self.hass.pool.block_till_done() + self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(100, light_state.attributes['brightness']) fire_mqtt_message(self.hass, 'test_light_rgb/status', '1') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_mqtt_message(self.hass, 'test_light_rgb/rgb/status', '125,125,125') - self.hass.pool.block_till_done() + self.hass.block_till_done() light_state = self.hass.states.get('light.test') self.assertEqual([125, 125, 125], @@ -198,26 +198,26 @@ class TestLightMQTT(unittest.TestCase): self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE)) fire_mqtt_message(self.hass, 'test_scale/status', 'on') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_ON, state.state) self.assertEqual(255, state.attributes.get('brightness')) fire_mqtt_message(self.hass, 'test_scale/status', 'off') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) fire_mqtt_message(self.hass, 'test_scale/status', 'on') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_mqtt_message(self.hass, 'test_scale/brightness/status', '99') - self.hass.pool.block_till_done() + self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(255, light_state.attributes['brightness']) @@ -249,7 +249,7 @@ class TestLightMQTT(unittest.TestCase): '{"hello": "ON"}') fire_mqtt_message(self.hass, 'test_light_rgb/brightness/status', '{"hello": "50"}') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_ON, state.state) @@ -277,7 +277,7 @@ class TestLightMQTT(unittest.TestCase): self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) light.turn_on(self.hass, 'light.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('test_light_rgb/set', 'on', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -285,7 +285,7 @@ class TestLightMQTT(unittest.TestCase): self.assertEqual(STATE_ON, state.state) light.turn_off(self.hass, 'light.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('test_light_rgb/set', 'off', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -294,7 +294,7 @@ class TestLightMQTT(unittest.TestCase): light.turn_on(self.hass, 'light.test', rgb_color=[75, 75, 75], brightness=50) - self.hass.pool.block_till_done() + self.hass.block_till_done() # Calls are threaded so we need to reorder them bright_call, rgb_call, state_call = \ @@ -333,7 +333,7 @@ class TestLightMQTT(unittest.TestCase): self.assertIsNone(state.attributes.get('brightness')) fire_mqtt_message(self.hass, 'test_light_rgb/status', 'ON') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_ON, state.state) diff --git a/tests/components/light/test_mqtt_json.py b/tests/components/light/test_mqtt_json.py index d149d2ba04c..6ea01dccccd 100755 --- a/tests/components/light/test_mqtt_json.py +++ b/tests/components/light/test_mqtt_json.py @@ -78,7 +78,7 @@ class TestLightMQTTJSON(unittest.TestCase): self.assertIsNone(state.attributes.get('brightness')) fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"ON"}') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_ON, state.state) @@ -112,7 +112,7 @@ class TestLightMQTTJSON(unittest.TestCase): '"color":{"r":255,"g":255,"b":255},' + '"brightness":255}' ) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_ON, state.state) @@ -121,7 +121,7 @@ class TestLightMQTTJSON(unittest.TestCase): # Turn the light off fire_mqtt_message(self.hass, 'test_light_rgb', '{"state":"OFF"}') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_OFF, state.state) @@ -130,10 +130,10 @@ class TestLightMQTTJSON(unittest.TestCase): '{"state":"ON",' + '"brightness":100}' ) - self.hass.pool.block_till_done() + self.hass.block_till_done() light_state = self.hass.states.get('light.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(100, light_state.attributes['brightness']) @@ -141,7 +141,7 @@ class TestLightMQTTJSON(unittest.TestCase): '{"state":"ON",' + '"color":{"r":125,"g":125,"b":125}}' ) - self.hass.pool.block_till_done() + self.hass.block_till_done() light_state = self.hass.states.get('light.test') self.assertEqual([125, 125, 125], @@ -166,7 +166,7 @@ class TestLightMQTTJSON(unittest.TestCase): self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) light.turn_on(self.hass, 'light.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('test_light_rgb/set', '{"state": "ON"}', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -174,7 +174,7 @@ class TestLightMQTTJSON(unittest.TestCase): self.assertEqual(STATE_ON, state.state) light.turn_off(self.hass, 'light.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('test_light_rgb/set', '{"state": "OFF"}', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -183,7 +183,7 @@ class TestLightMQTTJSON(unittest.TestCase): light.turn_on(self.hass, 'light.test', rgb_color=[75, 75, 75], brightness=50) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('test_light_rgb/set', self.mock_publish.mock_calls[-1][1][0]) @@ -221,7 +221,7 @@ class TestLightMQTTJSON(unittest.TestCase): self.assertEqual(STATE_OFF, state.state) light.turn_on(self.hass, 'light.test', flash="short") - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('test_light_rgb/set', self.mock_publish.mock_calls[-1][1][0]) @@ -233,7 +233,7 @@ class TestLightMQTTJSON(unittest.TestCase): self.assertEqual("ON", message_json["state"]) light.turn_on(self.hass, 'light.test', flash="long") - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('test_light_rgb/set', self.mock_publish.mock_calls[-1][1][0]) @@ -261,7 +261,7 @@ class TestLightMQTTJSON(unittest.TestCase): self.assertEqual(STATE_OFF, state.state) light.turn_on(self.hass, 'light.test', transition=10) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('test_light_rgb/set', self.mock_publish.mock_calls[-1][1][0]) @@ -274,7 +274,7 @@ class TestLightMQTTJSON(unittest.TestCase): # Transition back off light.turn_off(self.hass, 'light.test', transition=10) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('test_light_rgb/set', self.mock_publish.mock_calls[-1][1][0]) @@ -312,7 +312,7 @@ class TestLightMQTTJSON(unittest.TestCase): '"color":{"r":255,"g":255,"b":255},' + '"brightness": 255}' ) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('light.test') self.assertEqual(STATE_ON, state.state) @@ -324,7 +324,7 @@ class TestLightMQTTJSON(unittest.TestCase): '{"state":"ON",' + '"color":{"r":"bad","g":"val","b":"test"}}' ) - self.hass.pool.block_till_done() + self.hass.block_till_done() # Color should not have changed state = self.hass.states.get('light.test') @@ -336,7 +336,7 @@ class TestLightMQTTJSON(unittest.TestCase): '{"state":"ON",' + '"brightness": "badValue"}' ) - self.hass.pool.block_till_done() + self.hass.block_till_done() # Brightness should not have changed state = self.hass.states.get('light.test') diff --git a/tests/components/lock/test_demo.py b/tests/components/lock/test_demo.py index 84be7629fce..71ac6b40aab 100644 --- a/tests/components/lock/test_demo.py +++ b/tests/components/lock/test_demo.py @@ -37,13 +37,13 @@ class TestLockDemo(unittest.TestCase): def test_locking(self): """Test the locking of a lock.""" lock.lock(self.hass, KITCHEN) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(lock.is_locked(self.hass, KITCHEN)) def test_unlocking(self): """Test the unlocking of a lock.""" lock.unlock(self.hass, FRONT) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertFalse(lock.is_locked(self.hass, FRONT)) diff --git a/tests/components/lock/test_mqtt.py b/tests/components/lock/test_mqtt.py index 006d241bcad..c51d2736b73 100644 --- a/tests/components/lock/test_mqtt.py +++ b/tests/components/lock/test_mqtt.py @@ -40,13 +40,13 @@ class TestLockMQTT(unittest.TestCase): self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE)) fire_mqtt_message(self.hass, 'state-topic', 'LOCK') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('lock.test') self.assertEqual(STATE_LOCKED, state.state) fire_mqtt_message(self.hass, 'state-topic', 'UNLOCK') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('lock.test') self.assertEqual(STATE_UNLOCKED, state.state) @@ -70,7 +70,7 @@ class TestLockMQTT(unittest.TestCase): self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) lock.lock(self.hass, 'lock.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('command-topic', 'LOCK', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -78,7 +78,7 @@ class TestLockMQTT(unittest.TestCase): self.assertEqual(STATE_LOCKED, state.state) lock.unlock(self.hass, 'lock.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('command-topic', 'UNLOCK', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -104,13 +104,13 @@ class TestLockMQTT(unittest.TestCase): self.assertEqual(STATE_UNLOCKED, state.state) fire_mqtt_message(self.hass, 'state-topic', '{"val":"LOCK"}') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('lock.test') self.assertEqual(STATE_LOCKED, state.state) fire_mqtt_message(self.hass, 'state-topic', '{"val":"UNLOCK"}') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('lock.test') self.assertEqual(STATE_UNLOCKED, state.state) diff --git a/tests/components/media_player/test_demo.py b/tests/components/media_player/test_demo.py index 03a97a44b30..97e7abc9818 100644 --- a/tests/components/media_player/test_demo.py +++ b/tests/components/media_player/test_demo.py @@ -60,12 +60,12 @@ class TestDemoMediaPlayer(unittest.TestCase): assert 'dvd' == state.attributes.get('source') mp.select_source(self.hass, None, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) assert 'dvd' == state.attributes.get('source') mp.select_source(self.hass, 'xbox', entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) assert 'xbox' == state.attributes.get('source') @@ -75,7 +75,7 @@ class TestDemoMediaPlayer(unittest.TestCase): assert self.hass.states.is_state(entity_id, 'playing') mp.clear_playlist(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.is_state(entity_id, 'off') def test_volume_services(self): @@ -85,34 +85,34 @@ class TestDemoMediaPlayer(unittest.TestCase): assert 1.0 == state.attributes.get('volume_level') mp.set_volume_level(self.hass, None, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) assert 1.0 == state.attributes.get('volume_level') mp.set_volume_level(self.hass, 0.5, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) assert 0.5 == state.attributes.get('volume_level') mp.volume_down(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) assert 0.4 == state.attributes.get('volume_level') mp.volume_up(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) assert 0.5 == state.attributes.get('volume_level') assert False is state.attributes.get('is_volume_muted') mp.mute_volume(self.hass, None, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) assert False is state.attributes.get('is_volume_muted') mp.mute_volume(self.hass, True, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) assert True is state.attributes.get('is_volume_muted') @@ -122,16 +122,16 @@ class TestDemoMediaPlayer(unittest.TestCase): assert self.hass.states.is_state(entity_id, 'playing') mp.turn_off(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.is_state(entity_id, 'off') assert not mp.is_on(self.hass, entity_id) mp.turn_on(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.is_state(entity_id, 'playing') mp.toggle(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.is_state(entity_id, 'off') assert not mp.is_on(self.hass, entity_id) @@ -141,19 +141,19 @@ class TestDemoMediaPlayer(unittest.TestCase): assert self.hass.states.is_state(entity_id, 'playing') mp.media_pause(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.is_state(entity_id, 'paused') mp.media_play_pause(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.is_state(entity_id, 'playing') mp.media_play_pause(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.is_state(entity_id, 'paused') mp.media_play(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.is_state(entity_id, 'playing') def test_prev_next_track(self): @@ -165,21 +165,21 @@ class TestDemoMediaPlayer(unittest.TestCase): state.attributes.get('supported_media_commands')) mp.media_next_track(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) assert 2 == state.attributes.get('media_track') assert 0 < (mp.SUPPORT_PREVIOUS_TRACK & state.attributes.get('supported_media_commands')) mp.media_next_track(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) assert 3 == state.attributes.get('media_track') assert 0 < (mp.SUPPORT_PREVIOUS_TRACK & state.attributes.get('supported_media_commands')) mp.media_previous_track(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) assert 2 == state.attributes.get('media_track') assert 0 < (mp.SUPPORT_PREVIOUS_TRACK & @@ -193,14 +193,14 @@ class TestDemoMediaPlayer(unittest.TestCase): state.attributes.get('supported_media_commands')) mp.media_next_track(self.hass, ent_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ent_id) assert 2 == state.attributes.get('media_episode') assert 0 < (mp.SUPPORT_PREVIOUS_TRACK & state.attributes.get('supported_media_commands')) mp.media_previous_track(self.hass, ent_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ent_id) assert 1 == state.attributes.get('media_episode') assert 0 == (mp.SUPPORT_PREVIOUS_TRACK & @@ -231,14 +231,14 @@ class TestDemoMediaPlayer(unittest.TestCase): assert state.attributes.get('media_content_id') is not None mp.play_media(self.hass, None, 'some_id', ent_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ent_id) assert 0 < (mp.SUPPORT_PLAY_MEDIA & state.attributes.get('supported_media_commands')) assert not 'some_id' == state.attributes.get('media_content_id') mp.play_media(self.hass, 'youtube', 'some_id', ent_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ent_id) assert 0 < (mp.SUPPORT_PLAY_MEDIA & state.attributes.get('supported_media_commands')) @@ -246,8 +246,8 @@ class TestDemoMediaPlayer(unittest.TestCase): assert not mock_seek.called mp.media_seek(self.hass, None, ent_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert not mock_seek.called mp.media_seek(self.hass, 100, ent_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert mock_seek.called diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 3678585141d..155e5a48845 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -36,15 +36,15 @@ class TestMQTT(unittest.TestCase): def test_client_starts_on_home_assistant_start(self): """"Test if client start on HA launch.""" self.hass.bus.fire(EVENT_HOMEASSISTANT_START) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(mqtt.MQTT_CLIENT.start.called) def test_client_stops_on_home_assistant_start(self): """Test if client stops on HA launch.""" self.hass.bus.fire(EVENT_HOMEASSISTANT_START) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.bus.fire(EVENT_HOMEASSISTANT_STOP) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(mqtt.MQTT_CLIENT.stop.called) def test_setup_fails_if_no_connect_broker(self): @@ -75,7 +75,7 @@ class TestMQTT(unittest.TestCase): mqtt.publish(self.hass, 'test-topic', 'test-payload') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual( @@ -91,7 +91,7 @@ class TestMQTT(unittest.TestCase): ATTR_DOMAIN: mqtt.DOMAIN, ATTR_SERVICE: mqtt.SERVICE_PUBLISH }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(not mqtt.MQTT_CLIENT.publish.called) def test_service_call_with_template_payload_renders_template(self): @@ -100,7 +100,7 @@ class TestMQTT(unittest.TestCase): If 'payload_template' is provided and 'payload' is not, then render it. """ mqtt.publish_template(self.hass, "test/topic", "{{ 1+1 }}") - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(mqtt.MQTT_CLIENT.publish.called) self.assertEqual(mqtt.MQTT_CLIENT.publish.call_args[0][1], "2") @@ -153,7 +153,7 @@ class TestMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', 'test-payload') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual('test-topic', self.calls[0][0]) self.assertEqual('test-payload', self.calls[0][1]) @@ -162,7 +162,7 @@ class TestMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', 'test-payload') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) def test_subscribe_topic_not_match(self): @@ -171,7 +171,7 @@ class TestMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'another-test-topic', 'test-payload') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_subscribe_topic_level_wildcard(self): @@ -180,7 +180,7 @@ class TestMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic/bier/on', 'test-payload') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual('test-topic/bier/on', self.calls[0][0]) self.assertEqual('test-payload', self.calls[0][1]) @@ -191,7 +191,7 @@ class TestMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic/bier', 'test-payload') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_subscribe_topic_subtree_wildcard_subtree_topic(self): @@ -200,7 +200,7 @@ class TestMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic/bier/on', 'test-payload') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual('test-topic/bier/on', self.calls[0][0]) self.assertEqual('test-payload', self.calls[0][1]) @@ -211,7 +211,7 @@ class TestMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'test-topic', 'test-payload') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) self.assertEqual('test-topic', self.calls[0][0]) self.assertEqual('test-payload', self.calls[0][1]) @@ -222,7 +222,7 @@ class TestMQTT(unittest.TestCase): fire_mqtt_message(self.hass, 'another-test-topic', 'test-payload') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) @@ -260,7 +260,7 @@ class TestMQTTCallbacks(unittest.TestCase): message = MQTTMessage('test_topic', 1, 'Hello World!'.encode('utf-8')) mqtt.MQTT_CLIENT._mqtt_on_message(None, {'hass': self.hass}, message) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(calls)) last_event = calls[0] diff --git a/tests/components/notify/test_command_line.py b/tests/components/notify/test_command_line.py index d350b0e4b37..f904fe744f3 100644 --- a/tests/components/notify/test_command_line.py +++ b/tests/components/notify/test_command_line.py @@ -51,8 +51,10 @@ class TestCommandLine(unittest.TestCase): } })) - self.hass.services.call('notify', 'test', {'message': message}, - blocking=True) + self.assertTrue( + self.hass.services.call('notify', 'test', {'message': message}, + blocking=True) + ) result = open(filename).read() # the echo command adds a line break @@ -69,6 +71,8 @@ class TestCommandLine(unittest.TestCase): } })) - self.hass.services.call('notify', 'test', {'message': 'error'}, - blocking=True) + self.assertTrue( + self.hass.services.call('notify', 'test', {'message': 'error'}, + blocking=True) + ) self.assertEqual(1, mock_error.call_count) diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py index 6f0daeaf7b8..70fbe0a1d79 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/notify/test_demo.py @@ -41,7 +41,7 @@ class TestNotifyDemo(unittest.TestCase): def test_sending_none_message(self): """Test send with None as message.""" notify.send_message(self.hass, None) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(len(self.events) == 0) def test_sending_templated_message(self): @@ -49,7 +49,7 @@ class TestNotifyDemo(unittest.TestCase): self.hass.states.set('sensor.temperature', 10) notify.send_message(self.hass, '{{ states.sensor.temperature.state }}', '{{ states.sensor.temperature.name }}') - self.hass.pool.block_till_done() + self.hass.block_till_done() last_event = self.events[-1] self.assertEqual(last_event.data[notify.ATTR_TITLE], 'temperature') self.assertEqual(last_event.data[notify.ATTR_MESSAGE], '10') @@ -58,7 +58,7 @@ class TestNotifyDemo(unittest.TestCase): """Test that all data from the service gets forwarded to service.""" notify.send_message(self.hass, 'my message', 'my title', {'hello': 'world'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(len(self.events) == 1) data = self.events[0].data assert { @@ -87,7 +87,7 @@ data_template: conf = yaml.load_yaml(fp.name) script.call_from_config(self.hass, conf) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(len(self.events) == 1) assert { 'message': 'Test 123 4', @@ -145,7 +145,7 @@ data_template: 'title': 'my title', 'data': {'hello': 'world'}}) - self.hass.pool.block_till_done() + self.hass.block_till_done() data = self.calls[0][0].data diff --git a/tests/components/notify/test_group.py b/tests/components/notify/test_group.py index 20e2259ca6e..951200efb08 100644 --- a/tests/components/notify/test_group.py +++ b/tests/components/notify/test_group.py @@ -48,7 +48,7 @@ class TestNotifyGroup(unittest.TestCase): def test_send_message_to_group(self): """Test sending a message to a notify group.""" self.service.send_message('Hello', title='Test notification') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(len(self.events) == 2) last_event = self.events[-1] self.assertEqual(last_event.data[notify.ATTR_TITLE], @@ -60,7 +60,7 @@ class TestNotifyGroup(unittest.TestCase): notify_data = {'hello': 'world'} self.service.send_message('Hello', title='Test notification', data=notify_data) - self.hass.pool.block_till_done() + self.hass.block_till_done() last_event = self.events[-1] self.assertEqual(last_event.data[notify.ATTR_TITLE], 'Test notification') @@ -72,7 +72,7 @@ class TestNotifyGroup(unittest.TestCase): notify_data = {'hello': 'world'} self.service.send_message('Hello', title='Test notification', data=notify_data) - self.hass.pool.block_till_done() + self.hass.block_till_done() data = self.events[-1].data assert { 'message': 'Hello', diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 5ebd951a682..657e7401562 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -37,7 +37,7 @@ class TestRecorder(unittest.TestCase): five_days_ago = now - timedelta(days=5) attributes = {'test_attr': 5, 'test_attr_10': 'nice'} - self.hass.pool.block_till_done() + self.hass.block_till_done() recorder._INSTANCE.block_till_done() for event_id in range(5): @@ -67,7 +67,7 @@ class TestRecorder(unittest.TestCase): five_days_ago = now - timedelta(days=5) event_data = {'test_attr': 5, 'test_attr_10': 'nice'} - self.hass.pool.block_till_done() + self.hass.block_till_done() recorder._INSTANCE.block_till_done() for event_id in range(5): if event_id < 2: @@ -93,7 +93,7 @@ class TestRecorder(unittest.TestCase): self.hass.states.set(entity_id, state, attributes) - self.hass.pool.block_till_done() + self.hass.block_till_done() recorder._INSTANCE.block_till_done() db_states = recorder.query('States') @@ -120,7 +120,7 @@ class TestRecorder(unittest.TestCase): self.hass.bus.fire(event_type, event_data) - self.hass.pool.block_till_done() + self.hass.block_till_done() recorder._INSTANCE.block_till_done() db_events = recorder.execute( diff --git a/tests/components/rollershutter/test_command_line.py b/tests/components/rollershutter/test_command_line.py index 6538c5bc756..ded75bb29b2 100644 --- a/tests/components/rollershutter/test_command_line.py +++ b/tests/components/rollershutter/test_command_line.py @@ -5,10 +5,10 @@ import tempfile import unittest from unittest import mock -import homeassistant.core as ha import homeassistant.components.rollershutter as rollershutter from homeassistant.components.rollershutter import ( command_line as cmd_rs) +from tests.common import get_test_home_assistant class TestCommandRollerShutter(unittest.TestCase): @@ -16,7 +16,7 @@ class TestCommandRollerShutter(unittest.TestCase): def setup_method(self, method): """Setup things to be run when tests are started.""" - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.hass.config.latitude = 32.87336 self.hass.config.longitude = 117.22743 self.rs = cmd_rs.CommandRollershutter(self.hass, 'foo', @@ -66,19 +66,19 @@ class TestCommandRollerShutter(unittest.TestCase): self.assertEqual('unknown', state.state) rollershutter.move_up(self.hass, 'rollershutter.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('rollershutter.test') self.assertEqual('open', state.state) rollershutter.move_down(self.hass, 'rollershutter.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('rollershutter.test') self.assertEqual('open', state.state) rollershutter.stop(self.hass, 'rollershutter.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('rollershutter.test') self.assertEqual('closed', state.state) diff --git a/tests/components/rollershutter/test_demo.py b/tests/components/rollershutter/test_demo.py index 039221fad6e..4740845adcc 100644 --- a/tests/components/rollershutter/test_demo.py +++ b/tests/components/rollershutter/test_demo.py @@ -23,7 +23,7 @@ class TestRollershutterDemo(unittest.TestCase): entity.move_up() fire_time_changed(self.hass, dt_util.utcnow()) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(90, entity.current_position) def test_move_down(self): @@ -32,7 +32,7 @@ class TestRollershutterDemo(unittest.TestCase): entity.move_down() fire_time_changed(self.hass, dt_util.utcnow()) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(10, entity.current_position) def test_move_position(self): @@ -41,7 +41,7 @@ class TestRollershutterDemo(unittest.TestCase): entity.move_position(10) fire_time_changed(self.hass, dt_util.utcnow()) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(10, entity.current_position) def test_stop(self): @@ -51,5 +51,5 @@ class TestRollershutterDemo(unittest.TestCase): entity.stop() fire_time_changed(self.hass, dt_util.utcnow()) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, entity.current_position) diff --git a/tests/components/rollershutter/test_mqtt.py b/tests/components/rollershutter/test_mqtt.py index 4345864f83f..eaff07d061b 100644 --- a/tests/components/rollershutter/test_mqtt.py +++ b/tests/components/rollershutter/test_mqtt.py @@ -41,19 +41,19 @@ class TestRollershutterMQTT(unittest.TestCase): self.assertEqual(STATE_UNKNOWN, state.state) fire_mqtt_message(self.hass, 'state-topic', '0') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('rollershutter.test') self.assertEqual(STATE_CLOSED, state.state) fire_mqtt_message(self.hass, 'state-topic', '50') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('rollershutter.test') self.assertEqual(STATE_OPEN, state.state) fire_mqtt_message(self.hass, 'state-topic', '100') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('rollershutter.test') self.assertEqual(STATE_OPEN, state.state) @@ -75,7 +75,7 @@ class TestRollershutterMQTT(unittest.TestCase): self.assertEqual(STATE_UNKNOWN, state.state) rollershutter.move_up(self.hass, 'rollershutter.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('command-topic', 'UP', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -99,7 +99,7 @@ class TestRollershutterMQTT(unittest.TestCase): self.assertEqual(STATE_UNKNOWN, state.state) rollershutter.move_down(self.hass, 'rollershutter.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('command-topic', 'DOWN', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -123,7 +123,7 @@ class TestRollershutterMQTT(unittest.TestCase): self.assertEqual(STATE_UNKNOWN, state.state) rollershutter.stop(self.hass, 'rollershutter.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('command-topic', 'STOP', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -150,25 +150,25 @@ class TestRollershutterMQTT(unittest.TestCase): self.assertFalse('current_position' in state_attributes_dict) fire_mqtt_message(self.hass, 'state-topic', '0') - self.hass.pool.block_till_done() + self.hass.block_till_done() current_position = self.hass.states.get( 'rollershutter.test').attributes['current_position'] self.assertEqual(0, current_position) fire_mqtt_message(self.hass, 'state-topic', '50') - self.hass.pool.block_till_done() + self.hass.block_till_done() current_position = self.hass.states.get( 'rollershutter.test').attributes['current_position'] self.assertEqual(50, current_position) fire_mqtt_message(self.hass, 'state-topic', '101') - self.hass.pool.block_till_done() + self.hass.block_till_done() current_position = self.hass.states.get( 'rollershutter.test').attributes['current_position'] self.assertEqual(50, current_position) fire_mqtt_message(self.hass, 'state-topic', 'non-numeric') - self.hass.pool.block_till_done() + self.hass.block_till_done() current_position = self.hass.states.get( 'rollershutter.test').attributes['current_position'] self.assertEqual(50, current_position) diff --git a/tests/components/sensor/test_forecast.py b/tests/components/sensor/test_forecast.py index 55bdec20a35..af88eec6b94 100644 --- a/tests/components/sensor/test_forecast.py +++ b/tests/components/sensor/test_forecast.py @@ -8,9 +8,8 @@ from requests.exceptions import HTTPError import requests_mock from homeassistant.components.sensor import forecast -from homeassistant import core as ha -from tests.common import load_fixture +from tests.common import load_fixture, get_test_home_assistant class TestForecastSetup(unittest.TestCase): @@ -18,7 +17,7 @@ class TestForecastSetup(unittest.TestCase): def setUp(self): """Initialize values for this testcase class.""" - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.key = 'foo' self.config = { 'api_key': 'foo', diff --git a/tests/components/sensor/test_moldindicator.py b/tests/components/sensor/test_moldindicator.py index c634c043db5..23dd07f1190 100644 --- a/tests/components/sensor/test_moldindicator.py +++ b/tests/components/sensor/test_moldindicator.py @@ -22,7 +22,7 @@ class TestSensorMoldIndicator(unittest.TestCase): {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) self.hass.states.set('test.indoorhumidity', '50', {ATTR_UNIT_OF_MEASUREMENT: '%'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() def tearDown(self): """Stop down everything that was started.""" @@ -112,15 +112,15 @@ class TestSensorMoldIndicator(unittest.TestCase): self.hass.states.set('test.indoortemp', '30', {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.get('sensor.mold_indicator').state == '90' self.hass.states.set('test.outdoortemp', '25', {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.get('sensor.mold_indicator').state == '57' self.hass.states.set('test.indoorhumidity', '20', {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.get('sensor.mold_indicator').state == '23' diff --git a/tests/components/sensor/test_mqtt.py b/tests/components/sensor/test_mqtt.py index 1c8aa8996db..2ef341d9e21 100644 --- a/tests/components/sensor/test_mqtt.py +++ b/tests/components/sensor/test_mqtt.py @@ -33,7 +33,7 @@ class TestSensorMQTT(unittest.TestCase): }) fire_mqtt_message(self.hass, 'test-topic', '100') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('sensor.test') self.assertEqual('100', state.state) @@ -54,7 +54,7 @@ class TestSensorMQTT(unittest.TestCase): }) fire_mqtt_message(self.hass, 'test-topic', '{ "val": "100" }') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('sensor.test') self.assertEqual('100', state.state) diff --git a/tests/components/sensor/test_mqtt_room.py b/tests/components/sensor/test_mqtt_room.py index f5e9202234e..db885b54434 100644 --- a/tests/components/sensor/test_mqtt_room.py +++ b/tests/components/sensor/test_mqtt_room.py @@ -73,7 +73,7 @@ class TestMQTTRoomSensor(unittest.TestCase): """Test the sending of a message.""" fire_mqtt_message( self.hass, topic, json.dumps(message)) - self.hass.pool.block_till_done() + self.hass.block_till_done() def assert_state(self, room): """Test the assertion of a room state.""" diff --git a/tests/components/sensor/test_template.py b/tests/components/sensor/test_template.py index d85c9164851..b80f8032bf1 100644 --- a/tests/components/sensor/test_template.py +++ b/tests/components/sensor/test_template.py @@ -33,7 +33,7 @@ class TestTemplateSensor: assert state.state == 'It .' self.hass.states.set('sensor.test_state', 'Works') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('sensor.test_template_sensor') assert state.state == 'It Works.' diff --git a/tests/components/sensor/test_wunderground.py b/tests/components/sensor/test_wunderground.py index 3aea771012e..ffb070f9ab9 100644 --- a/tests/components/sensor/test_wunderground.py +++ b/tests/components/sensor/test_wunderground.py @@ -3,7 +3,8 @@ import unittest from homeassistant.components.sensor import wunderground from homeassistant.const import TEMP_CELSIUS -from homeassistant import core as ha + +from tests.common import get_test_home_assistant VALID_CONFIG_PWS = { 'platform': 'wunderground', @@ -90,7 +91,7 @@ class TestWundergroundSetup(unittest.TestCase): def setUp(self): """Initialize values for this testcase class.""" self.DEVICES = [] - self.hass = ha.HomeAssistant() + self.hass = get_test_home_assistant() self.key = 'foo' self.config = VALID_CONFIG_PWS self.lat = 37.8267 diff --git a/tests/components/switch/test_command_line.py b/tests/components/switch/test_command_line.py index 5e17710f8fd..bbc7214f8e1 100644 --- a/tests/components/switch/test_command_line.py +++ b/tests/components/switch/test_command_line.py @@ -43,13 +43,13 @@ class TestCommandSwitch(unittest.TestCase): self.assertEqual(STATE_OFF, state.state) switch.turn_on(self.hass, 'switch.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test') self.assertEqual(STATE_ON, state.state) switch.turn_off(self.hass, 'switch.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test') self.assertEqual(STATE_OFF, state.state) @@ -77,13 +77,13 @@ class TestCommandSwitch(unittest.TestCase): self.assertEqual(STATE_OFF, state.state) switch.turn_on(self.hass, 'switch.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test') self.assertEqual(STATE_ON, state.state) switch.turn_off(self.hass, 'switch.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test') self.assertEqual(STATE_OFF, state.state) @@ -113,13 +113,13 @@ class TestCommandSwitch(unittest.TestCase): self.assertEqual(STATE_OFF, state.state) switch.turn_on(self.hass, 'switch.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test') self.assertEqual(STATE_ON, state.state) switch.turn_off(self.hass, 'switch.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test') self.assertEqual(STATE_OFF, state.state) @@ -146,13 +146,13 @@ class TestCommandSwitch(unittest.TestCase): self.assertEqual(STATE_OFF, state.state) switch.turn_on(self.hass, 'switch.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test') self.assertEqual(STATE_ON, state.state) switch.turn_off(self.hass, 'switch.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test') self.assertEqual(STATE_ON, state.state) diff --git a/tests/components/switch/test_flux.py b/tests/components/switch/test_flux.py index 3e12f2e2d37..c3d13d83a85 100644 --- a/tests/components/switch/test_flux.py +++ b/tests/components/switch/test_flux.py @@ -102,7 +102,7 @@ class TestSwitchFlux(unittest.TestCase): turn_on_calls = mock_service( self.hass, light.DOMAIN, SERVICE_TURN_ON) fire_time_changed(self.hass, test_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(turn_on_calls)) def test_flux_before_sunrise(self): @@ -141,9 +141,9 @@ class TestSwitchFlux(unittest.TestCase): turn_on_calls = mock_service( self.hass, light.DOMAIN, SERVICE_TURN_ON) switch.turn_on(self.hass, 'switch.flux') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, test_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 119) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.591, 0.395]) @@ -185,9 +185,9 @@ class TestSwitchFlux(unittest.TestCase): turn_on_calls = mock_service( self.hass, light.DOMAIN, SERVICE_TURN_ON) switch.turn_on(self.hass, 'switch.flux') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, test_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 180) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.431, 0.38]) @@ -229,9 +229,9 @@ class TestSwitchFlux(unittest.TestCase): turn_on_calls = mock_service( self.hass, light.DOMAIN, SERVICE_TURN_ON) switch.turn_on(self.hass, 'switch.flux') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, test_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 153) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.496, 0.397]) @@ -273,9 +273,9 @@ class TestSwitchFlux(unittest.TestCase): turn_on_calls = mock_service( self.hass, light.DOMAIN, SERVICE_TURN_ON) switch.turn_on(self.hass, 'switch.flux') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, test_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 119) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.591, 0.395]) @@ -319,9 +319,9 @@ class TestSwitchFlux(unittest.TestCase): turn_on_calls = mock_service( self.hass, light.DOMAIN, SERVICE_TURN_ON) switch.turn_on(self.hass, 'switch.flux') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, test_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 154) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.494, 0.397]) @@ -365,9 +365,9 @@ class TestSwitchFlux(unittest.TestCase): turn_on_calls = mock_service( self.hass, light.DOMAIN, SERVICE_TURN_ON) switch.turn_on(self.hass, 'switch.flux') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, test_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 167) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.461, 0.389]) @@ -410,9 +410,9 @@ class TestSwitchFlux(unittest.TestCase): turn_on_calls = mock_service( self.hass, light.DOMAIN, SERVICE_TURN_ON) switch.turn_on(self.hass, 'switch.flux') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, test_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 255) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.496, 0.397]) @@ -426,9 +426,9 @@ class TestSwitchFlux(unittest.TestCase): dev1, dev2, dev3 = platform.DEVICES light.turn_on(self.hass, entity_id=dev2.entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() light.turn_on(self.hass, entity_id=dev3.entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(dev1.entity_id) self.assertEqual(STATE_ON, state.state) @@ -468,9 +468,9 @@ class TestSwitchFlux(unittest.TestCase): turn_on_calls = mock_service( self.hass, light.DOMAIN, SERVICE_TURN_ON) switch.turn_on(self.hass, 'switch.flux') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, test_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_BRIGHTNESS], 171) self.assertEqual(call.data[light.ATTR_XY_COLOR], [0.452, 0.386]) @@ -517,9 +517,9 @@ class TestSwitchFlux(unittest.TestCase): turn_on_calls = mock_service( self.hass, light.DOMAIN, SERVICE_TURN_ON) switch.turn_on(self.hass, 'switch.flux') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, test_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_COLOR_TEMP], 269) @@ -559,8 +559,8 @@ class TestSwitchFlux(unittest.TestCase): turn_on_calls = mock_service( self.hass, light.DOMAIN, SERVICE_TURN_ON) switch.turn_on(self.hass, 'switch.flux') - self.hass.pool.block_till_done() + self.hass.block_till_done() fire_time_changed(self.hass, test_time) - self.hass.pool.block_till_done() + self.hass.block_till_done() call = turn_on_calls[-1] self.assertEqual(call.data[light.ATTR_COLOR_TEMP], 3708) diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py index b49c7866984..5d662cbc943 100644 --- a/tests/components/switch/test_init.py +++ b/tests/components/switch/test_init.py @@ -42,7 +42,7 @@ class TestSwitch(unittest.TestCase): switch.turn_off(self.hass, self.switch_1.entity_id) switch.turn_on(self.hass, self.switch_2.entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(switch.is_on(self.hass)) self.assertFalse(switch.is_on(self.hass, self.switch_1.entity_id)) @@ -51,7 +51,7 @@ class TestSwitch(unittest.TestCase): # Turn all off switch.turn_off(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertFalse(switch.is_on(self.hass)) self.assertEqual( @@ -64,7 +64,7 @@ class TestSwitch(unittest.TestCase): # Turn all on switch.turn_on(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(switch.is_on(self.hass)) self.assertEqual( diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py index 61c14be70d1..6d7314a0895 100644 --- a/tests/components/switch/test_mqtt.py +++ b/tests/components/switch/test_mqtt.py @@ -39,13 +39,13 @@ class TestSensorMQTT(unittest.TestCase): self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE)) fire_mqtt_message(self.hass, 'state-topic', '1') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test') self.assertEqual(STATE_ON, state.state) fire_mqtt_message(self.hass, 'state-topic', '0') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test') self.assertEqual(STATE_OFF, state.state) @@ -69,7 +69,7 @@ class TestSensorMQTT(unittest.TestCase): self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) switch.turn_on(self.hass, 'switch.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('command-topic', 'beer on', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -77,7 +77,7 @@ class TestSensorMQTT(unittest.TestCase): self.assertEqual(STATE_ON, state.state) switch.turn_off(self.hass, 'switch.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(('command-topic', 'beer off', 2, False), self.mock_publish.mock_calls[-1][1]) @@ -103,13 +103,13 @@ class TestSensorMQTT(unittest.TestCase): self.assertEqual(STATE_OFF, state.state) fire_mqtt_message(self.hass, 'state-topic', '{"val":"beer on"}') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test') self.assertEqual(STATE_ON, state.state) fire_mqtt_message(self.hass, 'state-topic', '{"val":"beer off"}') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test') self.assertEqual(STATE_OFF, state.state) diff --git a/tests/components/switch/test_template.py b/tests/components/switch/test_template.py index 2d8cf636217..e13b0f7392b 100644 --- a/tests/components/switch/test_template.py +++ b/tests/components/switch/test_template.py @@ -50,13 +50,13 @@ class TestTemplateSwitch: }) state = self.hass.states.set('switch.test_state', STATE_ON) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test_template_switch') assert state.state == STATE_ON state = self.hass.states.set('switch.test_state', STATE_OFF) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test_template_switch') assert state.state == STATE_OFF @@ -268,13 +268,13 @@ class TestTemplateSwitch: } }) self.hass.states.set('switch.test_state', STATE_OFF) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test_template_switch') assert state.state == STATE_OFF core.switch.turn_on(self.hass, 'switch.test_template_switch') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 @@ -300,12 +300,12 @@ class TestTemplateSwitch: } }) self.hass.states.set('switch.test_state', STATE_ON) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('switch.test_template_switch') assert state.state == STATE_ON core.switch.turn_off(self.hass, 'switch.test_template_switch') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.calls) == 1 diff --git a/tests/components/test_alexa.py b/tests/components/test_alexa.py index 97d73b8b49d..0c0a30dc718 100644 --- a/tests/components/test_alexa.py +++ b/tests/components/test_alexa.py @@ -104,7 +104,7 @@ class TestAlexa(unittest.TestCase): def tearDown(self): """Stop everything that was started.""" - hass.pool.block_till_done() + hass.block_till_done() def test_launch_request(self): """Test the launch of a request.""" diff --git a/tests/components/test_api.py b/tests/components/test_api.py index 752980e65c8..4e7d98cd6cc 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -61,7 +61,7 @@ class TestAPI(unittest.TestCase): def tearDown(self): """Stop everything that was started.""" - hass.pool.block_till_done() + hass.block_till_done() def test_api_list_state_entities(self): """Test if the debug interface allows us to list state entities.""" @@ -169,7 +169,7 @@ class TestAPI(unittest.TestCase): _url(const.URL_API_EVENTS_EVENT.format("test.event_no_data")), headers=HA_HEADERS) - hass.pool.block_till_done() + hass.block_till_done() self.assertEqual(1, len(test_value)) @@ -193,7 +193,7 @@ class TestAPI(unittest.TestCase): data=json.dumps({"test": 1}), headers=HA_HEADERS) - hass.pool.block_till_done() + hass.block_till_done() self.assertEqual(1, len(test_value)) @@ -213,7 +213,7 @@ class TestAPI(unittest.TestCase): data=json.dumps('not an object'), headers=HA_HEADERS) - hass.pool.block_till_done() + hass.block_till_done() self.assertEqual(400, req.status_code) self.assertEqual(0, len(test_value)) @@ -224,7 +224,7 @@ class TestAPI(unittest.TestCase): data=json.dumps([1, 2, 3]), headers=HA_HEADERS) - hass.pool.block_till_done() + hass.block_till_done() self.assertEqual(400, req.status_code) self.assertEqual(0, len(test_value)) @@ -294,7 +294,7 @@ class TestAPI(unittest.TestCase): "test_domain", "test_service")), headers=HA_HEADERS) - hass.pool.block_till_done() + hass.block_till_done() self.assertEqual(1, len(test_value)) @@ -318,7 +318,7 @@ class TestAPI(unittest.TestCase): data=json.dumps({"test": 1}), headers=HA_HEADERS) - hass.pool.block_till_done() + hass.block_till_done() self.assertEqual(1, len(test_value)) diff --git a/tests/components/test_configurator.py b/tests/components/test_configurator.py index 3b91c28b3a5..100c0a9ce2a 100644 --- a/tests/components/test_configurator.py +++ b/tests/components/test_configurator.py @@ -70,7 +70,7 @@ class TestConfigurator(unittest.TestCase): configurator.DOMAIN, configurator.SERVICE_CONFIGURE, {configurator.ATTR_CONFIGURE_ID: request_id}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(calls), "Callback not called") def test_state_change_on_notify_errors(self): @@ -95,7 +95,7 @@ class TestConfigurator(unittest.TestCase): self.assertEqual(1, len(self.hass.states.all())) self.hass.bus.fire(EVENT_TIME_CHANGED) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.hass.states.all())) def test_request_done_fail_silently_on_bad_request_id(self): diff --git a/tests/components/test_device_sun_light_trigger.py b/tests/components/test_device_sun_light_trigger.py index 88c0bae60ec..444a7acc5b1 100644 --- a/tests/components/test_device_sun_light_trigger.py +++ b/tests/components/test_device_sun_light_trigger.py @@ -76,10 +76,10 @@ class TestDeviceSunLightTrigger(unittest.TestCase): ensure_sun_risen(self.hass) light.turn_off(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() ensure_sun_set(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(light.is_on(self.hass)) @@ -88,7 +88,7 @@ class TestDeviceSunLightTrigger(unittest.TestCase): """Test lights turn off when everyone leaves the house.""" light.turn_on(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(device_sun_light_trigger.setup( self.hass, {device_sun_light_trigger.DOMAIN: {}})) @@ -96,7 +96,7 @@ class TestDeviceSunLightTrigger(unittest.TestCase): self.hass.states.set(device_tracker.ENTITY_ID_ALL_DEVICES, STATE_NOT_HOME) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertFalse(light.is_on(self.hass)) @@ -106,7 +106,7 @@ class TestDeviceSunLightTrigger(unittest.TestCase): light.turn_off(self.hass) ensure_sun_set(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(device_sun_light_trigger.setup( self.hass, {device_sun_light_trigger.DOMAIN: {}})) @@ -114,5 +114,5 @@ class TestDeviceSunLightTrigger(unittest.TestCase): self.hass.states.set( device_tracker.ENTITY_ID_FORMAT.format('device_2'), STATE_HOME) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(light.is_on(self.hass)) diff --git a/tests/components/test_frontend.py b/tests/components/test_frontend.py index 679029f85ea..2023ea24a35 100644 --- a/tests/components/test_frontend.py +++ b/tests/components/test_frontend.py @@ -56,7 +56,7 @@ class TestFrontend(unittest.TestCase): def tearDown(self): """Stop everything that was started.""" - hass.pool.block_till_done() + hass.block_till_done() def test_frontend_and_static(self): """Test if we can get the frontend.""" diff --git a/tests/components/test_group.py b/tests/components/test_group.py index 6c601a411fb..9a2de824e90 100644 --- a/tests/components/test_group.py +++ b/tests/components/test_group.py @@ -85,7 +85,7 @@ class TestComponentsGroup(unittest.TestCase): test_group = group.Group( self.hass, 'init_group', ['light.Bowl', 'light.Ceiling'], False) - self.hass.pool.block_till_done() + self.hass.block_till_done() group_state = self.hass.states.get(test_group.entity_id) self.assertEqual(STATE_OFF, group_state.state) @@ -99,7 +99,7 @@ class TestComponentsGroup(unittest.TestCase): # Turn one on self.hass.states.set('light.Ceiling', STATE_ON) - self.hass.pool.block_till_done() + self.hass.block_till_done() group_state = self.hass.states.get(test_group.entity_id) self.assertEqual(STATE_ON, group_state.state) @@ -113,7 +113,7 @@ class TestComponentsGroup(unittest.TestCase): self.assertTrue(group.is_on(self.hass, test_group.entity_id)) self.hass.states.set('light.Bowl', STATE_OFF) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertFalse(group.is_on(self.hass, test_group.entity_id)) # Try on non existing state @@ -193,7 +193,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass.states.set('light.not_there_1', STATE_ON) - self.hass.pool.block_till_done() + self.hass.block_till_done() group_state = self.hass.states.get(test_group.entity_id) self.assertEqual(STATE_ON, group_state.state) @@ -209,7 +209,7 @@ class TestComponentsGroup(unittest.TestCase): self.hass.states.set('light.not_there_1', STATE_OFF) - self.hass.pool.block_till_done() + self.hass.block_till_done() group_state = self.hass.states.get(test_group.entity_id) self.assertEqual(STATE_OFF, group_state.state) @@ -288,13 +288,13 @@ class TestComponentsGroup(unittest.TestCase): self.hass.states.set('light.Bowl', STATE_ON, { ATTR_ASSUMED_STATE: True }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(test_group.entity_id) self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE)) self.hass.states.set('light.Bowl', STATE_ON) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(test_group.entity_id) self.assertIsNone(state.attributes.get(ATTR_ASSUMED_STATE)) @@ -303,12 +303,12 @@ class TestComponentsGroup(unittest.TestCase): """Test group state when device tracker in group changes zone.""" self.hass.states.set('device_tracker.Adam', STATE_HOME) self.hass.states.set('device_tracker.Eve', STATE_NOT_HOME) - self.hass.pool.block_till_done() + self.hass.block_till_done() group.Group( self.hass, 'peeps', ['device_tracker.Adam', 'device_tracker.Eve']) self.hass.states.set('device_tracker.Adam', 'cool_state_not_home') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(STATE_NOT_HOME, self.hass.states.get( group.ENTITY_ID_FORMAT.format('peeps')).state) @@ -338,7 +338,7 @@ class TestComponentsGroup(unittest.TestCase): 'view': True, }}}): group.reload(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert self.hass.states.entity_ids() == ['group.hello'] assert self.hass.bus.listeners['state_changed'] == 1 diff --git a/tests/components/test_history.py b/tests/components/test_history.py index 447629ee070..9631522ea25 100644 --- a/tests/components/test_history.py +++ b/tests/components/test_history.py @@ -17,7 +17,7 @@ class TestComponentHistory(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" - self.hass = get_test_home_assistant(1) + self.hass = get_test_home_assistant() def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" @@ -36,7 +36,7 @@ class TestComponentHistory(unittest.TestCase): def wait_recording_done(self): """Block till recording is done.""" - self.hass.pool.block_till_done() + self.hass.block_till_done() recorder._INSTANCE.block_till_done() def test_setup(self): diff --git a/tests/components/test_init.py b/tests/components/test_init.py index 7abaf63b407..62467c14a2f 100644 --- a/tests/components/test_init.py +++ b/tests/components/test_init.py @@ -42,28 +42,28 @@ class TestComponentsCore(unittest.TestCase): """Test turn_on method without entities.""" calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) comps.turn_on(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(calls)) def test_turn_on(self): """Test turn_on method.""" calls = mock_service(self.hass, 'light', SERVICE_TURN_ON) comps.turn_on(self.hass, 'light.Ceiling') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(calls)) def test_turn_off(self): """Test turn_off method.""" calls = mock_service(self.hass, 'light', SERVICE_TURN_OFF) comps.turn_off(self.hass, 'light.Bowl') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(calls)) def test_toggle(self): """Test toggle method.""" calls = mock_service(self.hass, 'light', SERVICE_TOGGLE) comps.toggle(self.hass, 'light.Bowl') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(calls)) @patch('homeassistant.core.ServiceRegistry.call') @@ -118,7 +118,7 @@ class TestComponentsCore(unittest.TestCase): })) comps.reload_core_config(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert 10 == self.hass.config.latitude assert 20 == self.hass.config.longitude @@ -142,7 +142,7 @@ class TestComponentsCore(unittest.TestCase): fp.write(yaml.dump(['invalid', 'config'])) comps.reload_core_config(self.hass) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert mock_error.called assert mock_process.called is False diff --git a/tests/components/test_input_boolean.py b/tests/components/test_input_boolean.py index 3e0bb0e6c48..5eb45117662 100644 --- a/tests/components/test_input_boolean.py +++ b/tests/components/test_input_boolean.py @@ -51,14 +51,14 @@ class TestInputBoolean(unittest.TestCase): input_boolean.turn_on(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue( input_boolean.is_on(self.hass, entity_id)) input_boolean.turn_off(self.hass, entity_id) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertFalse( input_boolean.is_on(self.hass, entity_id)) diff --git a/tests/components/test_input_select.py b/tests/components/test_input_select.py index 20d710a5b92..589a72952eb 100644 --- a/tests/components/test_input_select.py +++ b/tests/components/test_input_select.py @@ -69,13 +69,13 @@ class TestInputSelect(unittest.TestCase): self.assertEqual('some option', state.state) input_select.select_option(self.hass, entity_id, 'another option') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) self.assertEqual('another option', state.state) input_select.select_option(self.hass, entity_id, 'non existing option') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) self.assertEqual('another option', state.state) diff --git a/tests/components/test_input_slider.py b/tests/components/test_input_slider.py index 3c13f797c42..73ffcd9ba7a 100644 --- a/tests/components/test_input_slider.py +++ b/tests/components/test_input_slider.py @@ -52,19 +52,19 @@ class TestInputSlider(unittest.TestCase): self.assertEqual(50, float(state.state)) input_slider.select_value(self.hass, entity_id, '30.4') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) self.assertEqual(30.4, float(state.state)) input_slider.select_value(self.hass, entity_id, '70') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) self.assertEqual(70, float(state.state)) input_slider.select_value(self.hass, entity_id, '110') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(entity_id) self.assertEqual(70, float(state.state)) diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py index 7d15557c1bf..1c91c7a3cc3 100644 --- a/tests/components/test_logbook.py +++ b/tests/components/test_logbook.py @@ -39,7 +39,7 @@ class TestComponentLogbook(unittest.TestCase): logbook.ATTR_DOMAIN: 'switch', logbook.ATTR_ENTITY_ID: 'switch.test_switch' }, True) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(calls)) last_call = calls[-1] @@ -60,7 +60,7 @@ class TestComponentLogbook(unittest.TestCase): self.hass.bus.listen(logbook.EVENT_LOGBOOK_ENTRY, event_listener) self.hass.services.call(logbook.DOMAIN, 'log', {}, True) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(calls)) diff --git a/tests/components/test_mqtt_eventstream.py b/tests/components/test_mqtt_eventstream.py index ad576a1df7a..226cccea261 100644 --- a/tests/components/test_mqtt_eventstream.py +++ b/tests/components/test_mqtt_eventstream.py @@ -50,7 +50,7 @@ class TestMqttEventStream(unittest.TestCase): self.assertEqual(self.hass.bus.listeners.get('*'), None) self.assertTrue(self.add_eventstream(pub_topic='bar')) - self.hass.pool.block_till_done() + self.hass.block_till_done() # Verify that the event handler has been added as a listener self.assertEqual(self.hass.bus.listeners.get('*'), 1) @@ -60,7 +60,7 @@ class TestMqttEventStream(unittest.TestCase): """"Test the subscription.""" sub_topic = 'foo' self.assertTrue(self.add_eventstream(sub_topic=sub_topic)) - self.hass.pool.block_till_done() + self.hass.block_till_done() # Verify that the this entity was subscribed to the topic mock_sub.assert_called_with(self.hass, sub_topic, ANY) @@ -76,7 +76,7 @@ class TestMqttEventStream(unittest.TestCase): # Add the eventstream component for publishing events self.assertTrue(self.add_eventstream(pub_topic=pub_topic)) - self.hass.pool.block_till_done() + self.hass.block_till_done() # Reset the mock because it will have already gotten calls for the # mqtt_eventstream state change on initialization, etc. @@ -84,7 +84,7 @@ class TestMqttEventStream(unittest.TestCase): # Set a state of an entity mock_state_change_event(self.hass, State(e_id, 'on')) - self.hass.pool.block_till_done() + self.hass.block_till_done() # The order of the JSON is indeterminate, # so first just check that publish was called @@ -112,7 +112,7 @@ class TestMqttEventStream(unittest.TestCase): def test_time_event_does_not_send_message(self, mock_pub): """"Test the sending of a new message if time event.""" self.assertTrue(self.add_eventstream(pub_topic='bar')) - self.hass.pool.block_till_done() + self.hass.block_till_done() # Reset the mock because it will have already gotten calls for the # mqtt_eventstream state change on initialization, etc. @@ -125,17 +125,17 @@ class TestMqttEventStream(unittest.TestCase): """"Test the receiving of the remotely fired event.""" sub_topic = 'foo' self.assertTrue(self.add_eventstream(sub_topic=sub_topic)) - self.hass.pool.block_till_done() + self.hass.block_till_done() calls = [] self.hass.bus.listen_once('test_event', lambda _: calls.append(1)) - self.hass.pool.block_till_done() + self.hass.block_till_done() payload = json.dumps( {'event_type': 'test_event', 'event_data': {}}, cls=JSONEncoder ) fire_mqtt_message(self.hass, sub_topic, payload) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(calls)) diff --git a/tests/components/test_persistent_notification.py b/tests/components/test_persistent_notification.py index 6f6d8b8e1b0..94d76ef3208 100644 --- a/tests/components/test_persistent_notification.py +++ b/tests/components/test_persistent_notification.py @@ -22,7 +22,7 @@ class TestPersistentNotification: pn.create(self.hass, 'Hello World {{ 1 + 1 }}', title='{{ 1 + 1 }} beers') - self.hass.pool.block_till_done() + self.hass.block_till_done() entity_ids = self.hass.states.entity_ids(pn.DOMAIN) assert len(entity_ids) == 1 @@ -36,14 +36,14 @@ class TestPersistentNotification: assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 pn.create(self.hass, 'test', notification_id='Beer 2') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(self.hass.states.entity_ids()) == 1 state = self.hass.states.get('persistent_notification.beer_2') assert state.state == 'test' pn.create(self.hass, 'test 2', notification_id='Beer 2') - self.hass.pool.block_till_done() + self.hass.block_till_done() # We should have overwritten old one assert len(self.hass.states.entity_ids()) == 1 @@ -55,7 +55,7 @@ class TestPersistentNotification: assert len(self.hass.states.entity_ids(pn.DOMAIN)) == 0 pn.create(self.hass, '{{ message + 1 }}', '{{ title + 1 }}') - self.hass.pool.block_till_done() + self.hass.block_till_done() entity_ids = self.hass.states.entity_ids(pn.DOMAIN) assert len(entity_ids) == 1 diff --git a/tests/components/test_proximity.py b/tests/components/test_proximity.py index 479b9459f03..cbd36a1fc1f 100644 --- a/tests/components/test_proximity.py +++ b/tests/components/test_proximity.py @@ -62,7 +62,7 @@ class TestProximity: assert state.attributes.get('dir_of_travel') == 'not set' self.hass.states.set('proximity.' + prox, '0') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.' + prox) assert state.state == '0' @@ -107,7 +107,7 @@ class TestProximity: assert state.attributes.get('dir_of_travel') == 'not set' self.hass.states.set('proximity.home', '0') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.state == '0' @@ -188,7 +188,7 @@ class TestProximity: 'latitude': 2.1, 'longitude': 1.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.state == '0' assert state.attributes.get('nearest') == 'test1' @@ -217,7 +217,7 @@ class TestProximity: 'latitude': 2.1, 'longitude': 1.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set( 'device_tracker.test2', 'home', { @@ -225,7 +225,7 @@ class TestProximity: 'latitude': 2.1, 'longitude': 1.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.state == '0' assert ((state.attributes.get('nearest') == 'test1, test2') or @@ -254,7 +254,7 @@ class TestProximity: 'latitude': 20.1, 'longitude': 10.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -280,7 +280,7 @@ class TestProximity: 'latitude': 20.1, 'longitude': 10.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -291,7 +291,7 @@ class TestProximity: 'latitude': 40.1, 'longitude': 20.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'away_from' @@ -317,7 +317,7 @@ class TestProximity: 'latitude': 40.1, 'longitude': 20.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -328,7 +328,7 @@ class TestProximity: 'latitude': 20.1, 'longitude': 10.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'towards' @@ -352,7 +352,7 @@ class TestProximity: { 'friendly_name': 'test1' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.state == 'not set' assert state.attributes.get('nearest') == 'not set' @@ -378,7 +378,7 @@ class TestProximity: { 'friendly_name': 'test1' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'not set' assert state.attributes.get('dir_of_travel') == 'not set' @@ -390,13 +390,13 @@ class TestProximity: { 'friendly_name': 'test1' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set( 'device_tracker.test2', 'not_home', { 'friendly_name': 'test2' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert proximity.setup(self.hass, { 'proximity': { 'zone': 'home', @@ -417,7 +417,7 @@ class TestProximity: 'latitude': 20.1, 'longitude': 10.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -428,7 +428,7 @@ class TestProximity: 'latitude': 40.1, 'longitude': 20.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -440,13 +440,13 @@ class TestProximity: { 'friendly_name': 'test1' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set( 'device_tracker.test2', 'not_home', { 'friendly_name': 'test2' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert proximity.setup(self.hass, { 'proximity': { 'zone': 'home', @@ -467,7 +467,7 @@ class TestProximity: 'latitude': 40.1, 'longitude': 20.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test2' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -478,7 +478,7 @@ class TestProximity: 'latitude': 20.1, 'longitude': 10.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -490,13 +490,13 @@ class TestProximity: { 'friendly_name': 'test1' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set( 'device_tracker.test2', 'work', { 'friendly_name': 'test2' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert proximity.setup(self.hass, { 'proximity': { 'zone': 'home', @@ -517,7 +517,7 @@ class TestProximity: 'latitude': 20.1, 'longitude': 10.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -529,13 +529,13 @@ class TestProximity: { 'friendly_name': 'test1' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set( 'device_tracker.test2', 'not_home', { 'friendly_name': 'test2' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert proximity.setup(self.hass, { 'proximity': { 'zone': 'home', @@ -556,7 +556,7 @@ class TestProximity: 'latitude': 10.1, 'longitude': 5.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set( 'device_tracker.test2', 'not_home', @@ -565,7 +565,7 @@ class TestProximity: 'latitude': 20.1, 'longitude': 10.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -573,7 +573,7 @@ class TestProximity: 'latitude': 40.1, 'longitude': 20.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set( 'device_tracker.test1', 'not_home', { @@ -581,13 +581,13 @@ class TestProximity: 'latitude': 35.1, 'longitude': 15.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set( 'device_tracker.test1', 'work', { 'friendly_name': 'test1' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test2' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -614,7 +614,7 @@ class TestProximity: 'latitude': 20.1000001, 'longitude': 10.1000001 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -625,7 +625,7 @@ class TestProximity: 'latitude': 20.1000002, 'longitude': 10.1000002 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'stationary' @@ -637,13 +637,13 @@ class TestProximity: { 'friendly_name': 'test1' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.hass.states.set( 'device_tracker.test2', 'not_home', { 'friendly_name': 'test2' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert proximity.setup(self.hass, { 'proximity': { 'zone': 'home', @@ -664,7 +664,7 @@ class TestProximity: 'latitude': 20.1, 'longitude': 10.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -676,7 +676,7 @@ class TestProximity: 'latitude': 10.1, 'longitude': 5.1 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test2' assert state.attributes.get('dir_of_travel') == 'unknown' @@ -688,7 +688,7 @@ class TestProximity: 'latitude': 12.6, 'longitude': 7.6 }) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get('proximity.home') assert state.attributes.get('nearest') == 'test1' assert state.attributes.get('dir_of_travel') == 'unknown' diff --git a/tests/components/test_rfxtrx.py b/tests/components/test_rfxtrx.py index 61a8f3bd82d..a62ed236066 100644 --- a/tests/components/test_rfxtrx.py +++ b/tests/components/test_rfxtrx.py @@ -94,6 +94,7 @@ class TestRFXTRX(unittest.TestCase): calls.append(event) self.hass.bus.listen(rfxtrx.EVENT_BUTTON_PRESSED, record_event) + self.hass.block_till_done() entity = rfxtrx.RFX_DEVICES['213c7f216'] self.assertEqual('Test', entity.name) @@ -104,7 +105,7 @@ class TestRFXTRX(unittest.TestCase): event.data = bytearray([0x0b, 0x11, 0x00, 0x10, 0x01, 0x18, 0xcd, 0xea, 0x01, 0x01, 0x0f, 0x70]) rfxtrx.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(event.values['Command'], "On") self.assertEqual('on', entity.state) @@ -138,11 +139,12 @@ class TestRFXTRX(unittest.TestCase): calls.append(event) self.hass.bus.listen("signal_received", record_event) + self.hass.block_till_done() event = rfxtrx.get_rfx_object('0a520802060101ff0f0269') event.data = bytearray(b'\nR\x08\x01\x07\x01\x00\xb8\x1b\x02y') rfxtrx.RECEIVED_EVT_SUBSCRIBERS[0](event) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(rfxtrx.RFX_DEVICES)) self.assertEqual(1, len(calls)) self.assertEqual(calls[0].data, diff --git a/tests/components/test_scene.py b/tests/components/test_scene.py index 4a83a29453d..0f07dac528b 100644 --- a/tests/components/test_scene.py +++ b/tests/components/test_scene.py @@ -46,7 +46,7 @@ class TestScene(unittest.TestCase): light.turn_off(self.hass, [light_1.entity_id, light_2.entity_id]) - self.hass.pool.block_till_done() + self.hass.block_till_done() entity_state = { 'state': 'on', @@ -63,7 +63,7 @@ class TestScene(unittest.TestCase): })) scene.activate(self.hass, 'scene.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(light_1.is_on) self.assertTrue(light_2.is_on) @@ -85,7 +85,7 @@ class TestScene(unittest.TestCase): light.turn_off(self.hass, [light_1.entity_id, light_2.entity_id]) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(scene.setup(self.hass, { 'scene': [{ @@ -101,7 +101,7 @@ class TestScene(unittest.TestCase): })) scene.activate(self.hass, 'scene.test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(light_1.is_on) self.assertTrue(light_2.is_on) diff --git a/tests/components/test_script.py b/tests/components/test_script.py index 30cf69d7922..04ac9594221 100644 --- a/tests/components/test_script.py +++ b/tests/components/test_script.py @@ -73,13 +73,13 @@ class TestScriptComponent(unittest.TestCase): }) script.turn_on(self.hass, ENTITY_ID) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(script.is_on(self.hass, ENTITY_ID)) self.assertEqual(0, len(events)) # Calling turn_on a second time should not advance the script script.turn_on(self.hass, ENTITY_ID) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(events)) def test_toggle_service(self): @@ -108,12 +108,12 @@ class TestScriptComponent(unittest.TestCase): }) script.toggle(self.hass, ENTITY_ID) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(script.is_on(self.hass, ENTITY_ID)) self.assertEqual(0, len(events)) script.toggle(self.hass, ENTITY_ID) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertFalse(script.is_on(self.hass, ENTITY_ID)) self.assertEqual(0, len(events)) @@ -144,7 +144,7 @@ class TestScriptComponent(unittest.TestCase): 'greeting': 'world' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(calls) == 1 assert calls[-1].data['hello'] == 'world' @@ -153,7 +153,7 @@ class TestScriptComponent(unittest.TestCase): 'greeting': 'universe', }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(calls) == 2 assert calls[-1].data['hello'] == 'universe' diff --git a/tests/components/test_shell_command.py b/tests/components/test_shell_command.py index 0318ef4742a..037da39baa9 100644 --- a/tests/components/test_shell_command.py +++ b/tests/components/test_shell_command.py @@ -34,6 +34,7 @@ class TestShellCommand(unittest.TestCase): self.hass.services.call('shell_command', 'test_service', blocking=True) + self.hass.block_till_done() self.assertTrue(os.path.isfile(path)) diff --git a/tests/components/test_sun.py b/tests/components/test_sun.py index 5b9df91e9a8..be486bb7e70 100644 --- a/tests/components/test_sun.py +++ b/tests/components/test_sun.py @@ -85,7 +85,7 @@ class TestSun(unittest.TestCase): self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: test_time + timedelta(seconds=5)}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(test_state, self.hass.states.get(sun.ENTITY_ID).state) diff --git a/tests/components/test_updater.py b/tests/components/test_updater.py index 3aa7054b187..d5ff225dfc0 100644 --- a/tests/components/test_updater.py +++ b/tests/components/test_updater.py @@ -55,7 +55,7 @@ class TestUpdater(unittest.TestCase): fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0, minute=0, second=0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(self.hass.states.is_state(updater.ENTITY_ID, NEW_VERSION)) diff --git a/tests/components/thermostat/test_demo.py b/tests/components/thermostat/test_demo.py index 673626136ab..2d564e97103 100644 --- a/tests/components/thermostat/test_demo.py +++ b/tests/components/thermostat/test_demo.py @@ -45,13 +45,13 @@ class TestDemoThermostat(unittest.TestCase): """Test setting the target temperature without required attribute.""" self.assertEqual('21.0', self.hass.states.get(ENTITY_NEST).state) thermostat.set_temperature(self.hass, None, ENTITY_NEST) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('21.0', self.hass.states.get(ENTITY_NEST).state) def test_set_target_temp(self): """Test the setting of the target temperature.""" thermostat.set_temperature(self.hass, 30, ENTITY_NEST) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('30.0', self.hass.states.get(ENTITY_NEST).state) def test_set_away_mode_bad_attr(self): @@ -59,21 +59,21 @@ class TestDemoThermostat(unittest.TestCase): state = self.hass.states.get(ENTITY_NEST) self.assertEqual('off', state.attributes.get('away_mode')) thermostat.set_away_mode(self.hass, None, ENTITY_NEST) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_NEST) self.assertEqual('off', state.attributes.get('away_mode')) def test_set_away_mode_on(self): """Test setting the away mode on/true.""" thermostat.set_away_mode(self.hass, True, ENTITY_NEST) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_NEST) self.assertEqual('on', state.attributes.get('away_mode')) def test_set_away_mode_off(self): """Test setting the away mode off/false.""" thermostat.set_away_mode(self.hass, False, ENTITY_NEST) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_NEST) self.assertEqual('off', state.attributes.get('away_mode')) @@ -82,20 +82,20 @@ class TestDemoThermostat(unittest.TestCase): state = self.hass.states.get(ENTITY_NEST) self.assertEqual('off', state.attributes.get('fan')) thermostat.set_fan_mode(self.hass, None, ENTITY_NEST) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_NEST) self.assertEqual('off', state.attributes.get('fan')) def test_set_fan_mode_on(self): """Test setting the fan mode on/true.""" thermostat.set_fan_mode(self.hass, True, ENTITY_NEST) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_NEST) self.assertEqual('on', state.attributes.get('fan')) def test_set_fan_mode_off(self): """Test setting the fan mode off/false.""" thermostat.set_fan_mode(self.hass, False, ENTITY_NEST) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY_NEST) self.assertEqual('off', state.attributes.get('fan')) diff --git a/tests/components/thermostat/test_heat_control.py b/tests/components/thermostat/test_heat_control.py index a01c1595393..475e9c70046 100644 --- a/tests/components/thermostat/test_heat_control.py +++ b/tests/components/thermostat/test_heat_control.py @@ -122,7 +122,7 @@ class TestThermostatHeatControl(unittest.TestCase): def test_set_target_temp(self): """Test the setting of the target temperature.""" thermostat.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('30.0', self.hass.states.get(ENTITY).state) def test_sensor_bad_unit(self): @@ -132,7 +132,7 @@ class TestThermostatHeatControl(unittest.TestCase): unit = state.attributes.get('unit_of_measurement') self._setup_sensor(22.0, unit='bad_unit') - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY) self.assertEqual(unit, state.attributes.get('unit_of_measurement')) @@ -145,7 +145,7 @@ class TestThermostatHeatControl(unittest.TestCase): unit = state.attributes.get('unit_of_measurement') self._setup_sensor(None) - self.hass.pool.block_till_done() + self.hass.block_till_done() state = self.hass.states.get(ENTITY) self.assertEqual(unit, state.attributes.get('unit_of_measurement')) @@ -155,9 +155,9 @@ class TestThermostatHeatControl(unittest.TestCase): """Test if target temperature turn heater on.""" self._setup_switch(False) self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() thermostat.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -168,9 +168,9 @@ class TestThermostatHeatControl(unittest.TestCase): """Test if target temperature turn heater off.""" self._setup_switch(True) self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() thermostat.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -181,9 +181,9 @@ class TestThermostatHeatControl(unittest.TestCase): """Test if temperature change turn heater on.""" self._setup_switch(False) thermostat.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -194,9 +194,9 @@ class TestThermostatHeatControl(unittest.TestCase): """Test if temperature change turn heater off.""" self._setup_switch(True) thermostat.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -245,9 +245,9 @@ class TestThermostatHeatControlACMode(unittest.TestCase): """Test if target temperature turn ac off.""" self._setup_switch(True) self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() thermostat.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -258,9 +258,9 @@ class TestThermostatHeatControlACMode(unittest.TestCase): """Test if target temperature turn ac on.""" self._setup_switch(False) self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() thermostat.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -271,9 +271,9 @@ class TestThermostatHeatControlACMode(unittest.TestCase): """Test if temperature change turn ac off.""" self._setup_switch(True) thermostat.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -284,9 +284,9 @@ class TestThermostatHeatControlACMode(unittest.TestCase): """Test if temperature change turn ac on.""" self._setup_switch(False) thermostat.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -336,9 +336,9 @@ class TestThermostatHeatControlACModeMinCycle(unittest.TestCase): """Test if temperature change turn ac on.""" self._setup_switch(False) thermostat.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_temp_change_ac_trigger_on_long_enough(self): @@ -349,9 +349,9 @@ class TestThermostatHeatControlACModeMinCycle(unittest.TestCase): return_value=fake_changed): self._setup_switch(False) thermostat.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -362,9 +362,9 @@ class TestThermostatHeatControlACModeMinCycle(unittest.TestCase): """Test if temperature change turn ac on.""" self._setup_switch(True) thermostat.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_temp_change_ac_trigger_off_long_enough(self): @@ -375,9 +375,9 @@ class TestThermostatHeatControlACModeMinCycle(unittest.TestCase): return_value=fake_changed): self._setup_switch(True) thermostat.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -426,18 +426,18 @@ class TestThermostatHeatControlMinCycle(unittest.TestCase): """Test if temp change doesn't turn heater off because of time.""" self._setup_switch(True) thermostat.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_temp_change_heater_trigger_on_not_long_enough(self): """Test if temp change doesn't turn heater on because of time.""" self._setup_switch(False) thermostat.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(self.calls)) def test_temp_change_heater_trigger_on_long_enough(self): @@ -448,9 +448,9 @@ class TestThermostatHeatControlMinCycle(unittest.TestCase): return_value=fake_changed): self._setup_switch(False) thermostat.set_temperature(self.hass, 30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) @@ -465,9 +465,9 @@ class TestThermostatHeatControlMinCycle(unittest.TestCase): return_value=fake_changed): self._setup_switch(True) thermostat.set_temperature(self.hass, 25) - self.hass.pool.block_till_done() + self.hass.block_till_done() self._setup_sensor(30) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(self.calls)) call = self.calls[0] self.assertEqual('switch', call.domain) diff --git a/tests/helpers/test_discovery.py b/tests/helpers/test_discovery.py index d7d9d629d9a..4664549fb77 100644 --- a/tests/helpers/test_discovery.py +++ b/tests/helpers/test_discovery.py @@ -38,11 +38,11 @@ class TestHelpersDiscovery: discovery.discover(self.hass, 'test service', 'discovery info', 'test_component') - self.hass.pool.block_till_done() + self.hass.block_till_done() discovery.discover(self.hass, 'another service', 'discovery info', 'test_component') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert mock_setup_component.called assert mock_setup_component.call_args[0] == \ @@ -69,15 +69,15 @@ class TestHelpersDiscovery: discovery.load_platform(self.hass, 'test_component', 'test_platform', 'discovery info') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert mock_setup_component.called assert mock_setup_component.call_args[0] == \ (self.hass, 'test_component', None) - self.hass.pool.block_till_done() + self.hass.block_till_done() discovery.load_platform(self.hass, 'test_component_2', 'test_platform', 'discovery info') - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(calls) == 1 assert calls[0] == ('test_platform', 'discovery info') @@ -86,7 +86,7 @@ class TestHelpersDiscovery: discovery.ATTR_SERVICE: discovery.EVENT_LOAD_PLATFORM.format('test_component') }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(calls) == 1 @@ -122,7 +122,7 @@ class TestHelpersDiscovery: 'platform': 'test_circular', }], }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert 'test_component' in self.hass.config.components assert 'switch' in self.hass.config.components diff --git a/tests/helpers/test_entity_component.py b/tests/helpers/test_entity_component.py index 2fa65f6d4ec..0ab87c57452 100644 --- a/tests/helpers/test_entity_component.py +++ b/tests/helpers/test_entity_component.py @@ -104,7 +104,7 @@ class TestHelpersEntityComponent(unittest.TestCase): poll_ent.update_ha_state.reset_mock() fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert not no_poll_ent.update_ha_state.called assert poll_ent.update_ha_state.called @@ -121,7 +121,7 @@ class TestHelpersEntityComponent(unittest.TestCase): ent2.update_ha_state = lambda *_: component.add_entities([ent1]) fire_time_changed(self.hass, dt_util.utcnow().replace(second=0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert 2 == len(self.hass.states.entity_ids()) @@ -236,7 +236,7 @@ class TestHelpersEntityComponent(unittest.TestCase): discovery.load_platform(self.hass, DOMAIN, 'platform_test', {'msg': 'discovery_info'}) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert mock_setup.called assert ('platform_test', {}, {'msg': 'discovery_info'}) == \ diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index 704a501eefc..25335de96aa 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -46,23 +46,23 @@ class TestEventHelpers(unittest.TestCase): self.hass, lambda x: runs.append(1), birthday_paulus) self._send_time_changed(before_birthday) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(runs)) self._send_time_changed(birthday_paulus) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(runs)) # A point in time tracker will only fire once, this should do nothing self._send_time_changed(birthday_paulus) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(runs)) track_point_in_time( self.hass, lambda x: runs.append(1), birthday_paulus) self._send_time_changed(after_birthday) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(runs)) unsub = track_point_in_time( @@ -70,7 +70,7 @@ class TestEventHelpers(unittest.TestCase): unsub() self._send_time_changed(after_birthday) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(runs)) def test_track_time_change(self): @@ -83,17 +83,17 @@ class TestEventHelpers(unittest.TestCase): self.hass, lambda x: specific_runs.append(1), second=[0, 30]) self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) self._send_time_changed(datetime(2014, 5, 24, 12, 0, 15)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(2, len(wildcard_runs)) self._send_time_changed(datetime(2014, 5, 24, 12, 0, 30)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(specific_runs)) self.assertEqual(3, len(wildcard_runs)) @@ -101,7 +101,7 @@ class TestEventHelpers(unittest.TestCase): unsub_utc() self._send_time_changed(datetime(2014, 5, 24, 12, 0, 30)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(specific_runs)) self.assertEqual(3, len(wildcard_runs)) @@ -126,7 +126,7 @@ class TestEventHelpers(unittest.TestCase): # Adding state to state machine self.hass.states.set("light.Bowl", "on") - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) self.assertEqual(1, len(wildercard_runs)) @@ -135,34 +135,34 @@ class TestEventHelpers(unittest.TestCase): # Set same state should not trigger a state change/listener self.hass.states.set('light.Bowl', 'on') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) self.assertEqual(1, len(wildercard_runs)) # State change off -> on self.hass.states.set('light.Bowl', 'off') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(2, len(wildcard_runs)) self.assertEqual(2, len(wildercard_runs)) # State change off -> off self.hass.states.set('light.Bowl', 'off', {"some_attr": 1}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(3, len(wildcard_runs)) self.assertEqual(3, len(wildercard_runs)) # State change off -> on self.hass.states.set('light.Bowl', 'on') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(4, len(wildcard_runs)) self.assertEqual(4, len(wildercard_runs)) self.hass.states.remove('light.bowl') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(5, len(wildcard_runs)) self.assertEqual(5, len(wildercard_runs)) @@ -173,7 +173,7 @@ class TestEventHelpers(unittest.TestCase): # Set state for different entity id self.hass.states.set('switch.kitchen', 'on') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(5, len(wildcard_runs)) self.assertEqual(6, len(wildercard_runs)) @@ -211,17 +211,17 @@ class TestEventHelpers(unittest.TestCase): # run tests self._send_time_changed(next_rising - offset) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(runs)) self.assertEqual(0, len(offset_runs)) self._send_time_changed(next_rising) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(runs)) self.assertEqual(0, len(offset_runs)) self._send_time_changed(next_rising + offset) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(runs)) self.assertEqual(1, len(offset_runs)) @@ -229,7 +229,7 @@ class TestEventHelpers(unittest.TestCase): unsub2() self._send_time_changed(next_rising + offset) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(runs)) self.assertEqual(1, len(offset_runs)) @@ -265,17 +265,17 @@ class TestEventHelpers(unittest.TestCase): # Run tests self._send_time_changed(next_setting - offset) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(runs)) self.assertEqual(0, len(offset_runs)) self._send_time_changed(next_setting) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(runs)) self.assertEqual(0, len(offset_runs)) self._send_time_changed(next_setting + offset) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(runs)) self.assertEqual(1, len(offset_runs)) @@ -283,7 +283,7 @@ class TestEventHelpers(unittest.TestCase): unsub2() self._send_time_changed(next_setting + offset) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(runs)) self.assertEqual(1, len(offset_runs)) @@ -299,21 +299,21 @@ class TestEventHelpers(unittest.TestCase): self.hass, lambda x: specific_runs.append(1), minute='/5') self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self._send_time_changed(datetime(2014, 5, 24, 12, 3, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self._send_time_changed(datetime(2014, 5, 24, 12, 5, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(specific_runs)) unsub() self._send_time_changed(datetime(2014, 5, 24, 12, 5, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(specific_runs)) def test_periodic_task_hour(self): @@ -324,29 +324,29 @@ class TestEventHelpers(unittest.TestCase): self.hass, lambda x: specific_runs.append(1), hour='/2') self._send_time_changed(datetime(2014, 5, 24, 22, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self._send_time_changed(datetime(2014, 5, 24, 23, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self._send_time_changed(datetime(2014, 5, 24, 0, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(specific_runs)) self._send_time_changed(datetime(2014, 5, 25, 1, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(specific_runs)) self._send_time_changed(datetime(2014, 5, 25, 2, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(3, len(specific_runs)) unsub() self._send_time_changed(datetime(2014, 5, 25, 2, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(3, len(specific_runs)) def test_periodic_task_day(self): @@ -357,21 +357,21 @@ class TestEventHelpers(unittest.TestCase): self.hass, lambda x: specific_runs.append(1), day='/2') self._send_time_changed(datetime(2014, 5, 2, 0, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self._send_time_changed(datetime(2014, 5, 3, 12, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self._send_time_changed(datetime(2014, 5, 4, 0, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(specific_runs)) unsub() self._send_time_changed(datetime(2014, 5, 4, 0, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(specific_runs)) def test_periodic_task_year(self): @@ -382,21 +382,21 @@ class TestEventHelpers(unittest.TestCase): self.hass, lambda x: specific_runs.append(1), year='/2') self._send_time_changed(datetime(2014, 5, 2, 0, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self._send_time_changed(datetime(2015, 5, 2, 0, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self._send_time_changed(datetime(2016, 5, 2, 0, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(specific_runs)) unsub() self._send_time_changed(datetime(2016, 5, 2, 0, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(specific_runs)) def test_periodic_task_wrong_input(self): @@ -407,5 +407,5 @@ class TestEventHelpers(unittest.TestCase): self.hass, lambda x: specific_runs.append(1), year='/two') self._send_time_changed(datetime(2014, 5, 2, 0, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(specific_runs)) diff --git a/tests/helpers/test_event_decorators.py b/tests/helpers/test_event_decorators.py index 212ca295d8b..ebfd57aa76b 100644 --- a/tests/helpers/test_event_decorators.py +++ b/tests/helpers/test_event_decorators.py @@ -67,17 +67,17 @@ class TestEventDecoratorHelpers(unittest.TestCase): # Run tests self._send_time_changed(next_rising - offset) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(runs)) self.assertEqual(0, len(offset_runs)) self._send_time_changed(next_rising) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(runs)) self.assertEqual(0, len(offset_runs)) self._send_time_changed(next_rising + offset) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(runs)) self.assertEqual(1, len(offset_runs)) @@ -115,17 +115,17 @@ class TestEventDecoratorHelpers(unittest.TestCase): # run tests self._send_time_changed(next_setting - offset) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(runs)) self.assertEqual(0, len(offset_runs)) self._send_time_changed(next_setting) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(runs)) self.assertEqual(0, len(offset_runs)) self._send_time_changed(next_setting + offset) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(runs)) self.assertEqual(1, len(offset_runs)) @@ -141,17 +141,17 @@ class TestEventDecoratorHelpers(unittest.TestCase): decor(lambda x, y: specific_runs.append(1)) self._send_time_changed(datetime(2014, 5, 24, 12, 0, 0)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) self._send_time_changed(datetime(2014, 5, 24, 12, 0, 15)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(2, len(wildcard_runs)) self._send_time_changed(datetime(2014, 5, 24, 12, 0, 30)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(2, len(specific_runs)) self.assertEqual(3, len(wildcard_runs)) @@ -169,25 +169,25 @@ class TestEventDecoratorHelpers(unittest.TestCase): # Set same state should not trigger a state change/listener self.hass.states.set('light.Bowl', 'on') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(specific_runs)) self.assertEqual(0, len(wildcard_runs)) # State change off -> on self.hass.states.set('light.Bowl', 'off') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(1, len(wildcard_runs)) # State change off -> off self.hass.states.set('light.Bowl', 'off', {"some_attr": 1}) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(2, len(wildcard_runs)) # State change off -> on self.hass.states.set('light.Bowl', 'on') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(specific_runs)) self.assertEqual(3, len(wildcard_runs)) diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index ba7255a8fe4..fc3acb2f80b 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -45,7 +45,7 @@ class TestScriptHelper(unittest.TestCase): script_obj.run() - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(calls) == 1 assert calls[0].data.get('hello') == 'world' @@ -69,7 +69,7 @@ class TestScriptHelper(unittest.TestCase): }) script_obj.run() - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(calls) == 1 assert calls[0].data.get('hello') == 'world' @@ -104,7 +104,7 @@ class TestScriptHelper(unittest.TestCase): script_obj.run() - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(calls) == 1 assert calls[0].data.get('hello') == 'world' @@ -127,7 +127,7 @@ class TestScriptHelper(unittest.TestCase): script_obj.run() - self.hass.pool.block_till_done() + self.hass.block_till_done() assert script_obj.is_running assert script_obj.can_cancel @@ -136,7 +136,7 @@ class TestScriptHelper(unittest.TestCase): future = dt_util.utcnow() + timedelta(seconds=5) fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert not script_obj.is_running assert len(events) == 2 @@ -159,7 +159,7 @@ class TestScriptHelper(unittest.TestCase): script_obj.run() - self.hass.pool.block_till_done() + self.hass.block_till_done() assert script_obj.is_running assert script_obj.can_cancel @@ -168,7 +168,7 @@ class TestScriptHelper(unittest.TestCase): future = dt_util.utcnow() + timedelta(seconds=5) fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert not script_obj.is_running assert len(events) == 2 @@ -190,7 +190,7 @@ class TestScriptHelper(unittest.TestCase): script_obj.run() - self.hass.pool.block_till_done() + self.hass.block_till_done() assert script_obj.is_running assert len(events) == 0 @@ -202,7 +202,7 @@ class TestScriptHelper(unittest.TestCase): # Make sure the script is really stopped. future = dt_util.utcnow() + timedelta(seconds=5) fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert not script_obj.is_running assert len(events) == 0 @@ -237,7 +237,7 @@ class TestScriptHelper(unittest.TestCase): 'greeting2': 'universe', }) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert script_obj.is_running assert len(calls) == 1 @@ -245,7 +245,7 @@ class TestScriptHelper(unittest.TestCase): future = dt_util.utcnow() + timedelta(seconds=5) fire_time_changed(self.hass, future) - self.hass.pool.block_till_done() + self.hass.block_till_done() assert not script_obj.is_running assert len(calls) == 2 @@ -274,11 +274,11 @@ class TestScriptHelper(unittest.TestCase): ]) script_obj.run() - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(events) == 2 self.hass.states.set('test.entity', 'goodbye') script_obj.run() - self.hass.pool.block_till_done() + self.hass.block_till_done() assert len(events) == 3 diff --git a/tests/helpers/test_service.py b/tests/helpers/test_service.py index 34f321776d6..d9fe3ff9c15 100644 --- a/tests/helpers/test_service.py +++ b/tests/helpers/test_service.py @@ -33,7 +33,7 @@ class TestServiceHelpers(unittest.TestCase): decor(lambda x, y: runs.append(1)) self.hass.services.call('test', 'test') - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(runs)) def test_template_service_call(self): @@ -56,7 +56,7 @@ class TestServiceHelpers(unittest.TestCase): decor(lambda x, y: runs.append(y)) service.call_from_config(self.hass, config) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('goodbye', runs[0].data['hello']) self.assertEqual('complex', runs[0].data['data']['value']) @@ -81,7 +81,7 @@ class TestServiceHelpers(unittest.TestCase): 'var_service': 'test_domain.test_service', 'var_data': 'goodbye', }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual('goodbye', runs[0].data['hello']) @@ -91,7 +91,7 @@ class TestServiceHelpers(unittest.TestCase): 'service': 'test_domain.test_service', 'entity_id': 'hello.world, sensor.beer' }) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(['hello.world', 'sensor.beer'], self.calls[-1].data.get('entity_id')) @@ -105,7 +105,7 @@ class TestServiceHelpers(unittest.TestCase): }, } service.call_from_config(self.hass, orig) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual({ 'service': 'test_domain.test_service', 'entity_id': 'hello.world, sensor.beer', diff --git a/tests/helpers/test_state.py b/tests/helpers/test_state.py index 5b374866d61..1dbf86edae9 100644 --- a/tests/helpers/test_state.py +++ b/tests/helpers/test_state.py @@ -85,7 +85,7 @@ class TestStateHelpers(unittest.TestCase): state.reproduce_state(self.hass, ha.State('light.test', 'on')) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(len(calls) == 0) self.assertEqual(None, self.hass.states.get('light.test')) @@ -98,7 +98,7 @@ class TestStateHelpers(unittest.TestCase): state.reproduce_state(self.hass, ha.State('light.test', 'on')) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(len(calls) > 0) last_call = calls[-1] @@ -114,7 +114,7 @@ class TestStateHelpers(unittest.TestCase): state.reproduce_state(self.hass, ha.State('light.test', 'off')) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(len(calls) > 0) last_call = calls[-1] @@ -134,7 +134,7 @@ class TestStateHelpers(unittest.TestCase): 'complex': complex_data })) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(len(calls) > 0) last_call = calls[-1] @@ -154,7 +154,7 @@ class TestStateHelpers(unittest.TestCase): state.reproduce_state(self.hass, ha.State('media_player.test', 'None', media_attributes)) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(len(calls) > 0) last_call = calls[-1] @@ -172,7 +172,7 @@ class TestStateHelpers(unittest.TestCase): state.reproduce_state( self.hass, ha.State('media_player.test', 'playing')) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(len(calls) > 0) last_call = calls[-1] @@ -190,7 +190,7 @@ class TestStateHelpers(unittest.TestCase): state.reproduce_state( self.hass, ha.State('media_player.test', 'paused')) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(len(calls) > 0) last_call = calls[-1] @@ -207,7 +207,7 @@ class TestStateHelpers(unittest.TestCase): state.reproduce_state(self.hass, ha.State('light.test', 'bad')) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertTrue(len(calls) == 0) self.assertEqual('off', self.hass.states.get('light.test').state) @@ -221,7 +221,7 @@ class TestStateHelpers(unittest.TestCase): state.reproduce_state(self.hass, ha.State('group.test', 'on')) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(light_calls)) last_call = light_calls[-1] @@ -241,7 +241,7 @@ class TestStateHelpers(unittest.TestCase): ha.State('light.test1', 'on', {'brightness': 95}), ha.State('light.test2', 'on', {'brightness': 95})]) - self.hass.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(light_calls)) last_call = light_calls[-1] diff --git a/tests/scripts/test_check_config.py b/tests/scripts/test_check_config.py index f9ebaa634ff..968838c4bc0 100644 --- a/tests/scripts/test_check_config.py +++ b/tests/scripts/test_check_config.py @@ -1,7 +1,9 @@ """Test check_config script.""" -import unittest +import asyncio import logging import os +import unittest +from unittest.mock import patch import homeassistant.scripts.check_config as check_config from tests.common import patch_yaml_files, get_test_config_dir @@ -43,11 +45,12 @@ def tearDownModule(self): # pylint: disable=invalid-name os.remove(path) +@patch('asyncio.get_event_loop', return_value=asyncio.new_event_loop()) class TestCheckConfig(unittest.TestCase): """Tests for the homeassistant.scripts.check_config module.""" # pylint: disable=no-self-use,invalid-name - def test_config_platform_valid(self): + def test_config_platform_valid(self, mock_get_loop): """Test a valid platform setup.""" files = { 'light.yaml': BASE_CONFIG + 'light:\n platform: hue', @@ -63,7 +66,7 @@ class TestCheckConfig(unittest.TestCase): 'yaml_files': ['.../light.yaml'] }, res) - def test_config_component_platform_fail_validation(self): + def test_config_component_platform_fail_validation(self, mock_get_loop): """Test errors if component & platform not found.""" files = { 'component.yaml': BASE_CONFIG + 'http:\n password: err123', @@ -95,7 +98,7 @@ class TestCheckConfig(unittest.TestCase): 'yaml_files': ['.../platform.yaml'] }, res) - def test_component_platform_not_found(self): + def test_component_platform_not_found(self, mock_get_loop): """Test errors if component or platform not found.""" files = { 'badcomponent.yaml': BASE_CONFIG + 'beer:', @@ -124,7 +127,7 @@ class TestCheckConfig(unittest.TestCase): 'yaml_files': ['.../badplatform.yaml'] }, res) - def test_secrets(self): + def test_secrets(self, mock_get_loop): """Test secrets config checking method.""" files = { get_test_config_dir('secret.yaml'): ( diff --git a/tests/test_config.py b/tests/test_config.py index 4a4f1ef9b6f..1512a7688ea 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -206,6 +206,8 @@ class TestConfig(unittest.TestCase): entity.hass = self.hass entity.update_ha_state() + self.hass.block_till_done() + state = self.hass.states.get('test.test') assert state.attributes['hidden'] diff --git a/tests/test_core.py b/tests/test_core.py index 76c82252d30..118e9909815 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -5,15 +5,12 @@ import os import signal import unittest from unittest.mock import patch -import time -import threading from datetime import datetime, timedelta import pytz import homeassistant.core as ha -from homeassistant.exceptions import ( - HomeAssistantError, InvalidEntityFormatError) +from homeassistant.exceptions import InvalidEntityFormatError import homeassistant.util.dt as dt_util from homeassistant.util.unit_system import (METRIC_SYSTEM) from homeassistant.const import ( @@ -39,63 +36,28 @@ class TestHomeAssistant(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" - self.hass = get_test_home_assistant() - self.hass.states.set("light.Bowl", "on") - self.hass.states.set("switch.AC", "off") + self.hass = get_test_home_assistant(0) def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" - try: - self.hass.stop() - except HomeAssistantError: - # Already stopped after the block till stopped test - pass + self.hass.stop() - def test_start(self): + def test_start_and_sigterm(self): """Start the test.""" calls = [] self.hass.bus.listen_once(EVENT_HOMEASSISTANT_START, lambda event: calls.append(1)) + self.hass.start() - self.hass.pool.block_till_done() + self.assertEqual(1, len(calls)) - # @patch('homeassistant.core.time.sleep') - def test_block_till_stoped(self): - """Test if we can block till stop service is called.""" - with patch('time.sleep'): - blocking_thread = threading.Thread( - target=self.hass.block_till_stopped) - - self.assertFalse(blocking_thread.is_alive()) - - blocking_thread.start() - - self.assertTrue(blocking_thread.is_alive()) - - self.hass.services.call(ha.DOMAIN, ha.SERVICE_HOMEASSISTANT_STOP) - self.hass.pool.block_till_done() - - # Wait for thread to stop - for _ in range(20): - if not blocking_thread.is_alive(): - break - time.sleep(0.05) - - self.assertFalse(blocking_thread.is_alive()) - - def test_stopping_with_sigterm(self): - """Test for stopping with sigterm.""" - calls = [] self.hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: calls.append(1)) - def send_sigterm(length): - """Send sigterm.""" - os.kill(os.getpid(), signal.SIGTERM) + os.kill(os.getpid(), signal.SIGTERM) - with patch('homeassistant.core.time.sleep', send_sigterm): - self.hass.block_till_stopped() + self.hass.block_till_done() self.assertEqual(1, len(calls)) @@ -147,16 +109,16 @@ class TestEventBus(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" - self.bus = ha.EventBus(ha.create_worker_pool(0)) + self.hass = get_test_home_assistant() + self.bus = self.hass.bus self.bus.listen('test_event', lambda x: len) def tearDown(self): # pylint: disable=invalid-name """Stop down stuff we started.""" - self.bus._pool.stop() + self.hass.stop() def test_add_remove_listener(self): """Test remove_listener method.""" - self.bus._pool.add_worker() old_count = len(self.bus.listeners) def listener(_): pass @@ -177,7 +139,6 @@ class TestEventBus(unittest.TestCase): def test_unsubscribe_listener(self): """Test unsubscribe listener from returned function.""" - self.bus._pool.add_worker() calls = [] def listener(event): @@ -187,14 +148,14 @@ class TestEventBus(unittest.TestCase): unsub = self.bus.listen('test', listener) self.bus.fire('test') - self.bus._pool.block_till_done() + self.hass.block_till_done() assert len(calls) == 1 unsub() self.bus.fire('event') - self.bus._pool.block_till_done() + self.hass.block_till_done() assert len(calls) == 1 @@ -208,8 +169,7 @@ class TestEventBus(unittest.TestCase): # Second time it should not increase runs self.bus.fire('test_event') - self.bus._pool.add_worker() - self.bus._pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(runs)) @@ -274,15 +234,14 @@ class TestStateMachine(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" - self.pool = ha.create_worker_pool(0) - self.bus = ha.EventBus(self.pool) - self.states = ha.StateMachine(self.bus) + self.hass = get_test_home_assistant(0) + self.states = self.hass.states self.states.set("light.Bowl", "on") self.states.set("switch.AC", "off") def tearDown(self): # pylint: disable=invalid-name """Stop down stuff we started.""" - self.pool.stop() + self.hass.stop() def test_is_state(self): """Test is_state method.""" @@ -320,14 +279,13 @@ class TestStateMachine(unittest.TestCase): def test_remove(self): """Test remove method.""" - self.pool.add_worker() events = [] - self.bus.listen(EVENT_STATE_CHANGED, - lambda event: events.append(event)) + self.hass.bus.listen(EVENT_STATE_CHANGED, + lambda event: events.append(event)) self.assertIn('light.bowl', self.states.entity_ids()) self.assertTrue(self.states.remove('light.bowl')) - self.pool.block_till_done() + self.hass.block_till_done() self.assertNotIn('light.bowl', self.states.entity_ids()) self.assertEqual(1, len(events)) @@ -338,18 +296,18 @@ class TestStateMachine(unittest.TestCase): # If it does not exist, we should get False self.assertFalse(self.states.remove('light.Bowl')) - self.pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(events)) def test_case_insensitivty(self): """Test insensitivty.""" - self.pool.add_worker() runs = [] - self.bus.listen(EVENT_STATE_CHANGED, lambda event: runs.append(event)) + self.hass.bus.listen(EVENT_STATE_CHANGED, + lambda event: runs.append(event)) self.states.set('light.BOWL', 'off') - self.bus._pool.block_till_done() + self.hass.block_till_done() self.assertTrue(self.states.is_state('light.bowl', 'off')) self.assertEqual(1, len(runs)) @@ -362,22 +320,23 @@ class TestStateMachine(unittest.TestCase): with patch('homeassistant.util.dt.utcnow', return_value=future): self.states.set("light.Bowl", "on", {'attr': 'triggers_change'}) + self.hass.block_till_done() - self.assertEqual(state.last_changed, - self.states.get('light.Bowl').last_changed) + state2 = self.states.get('light.Bowl') + assert state2 is not None + assert state.last_changed == state2.last_changed def test_force_update(self): """Test force update option.""" - self.pool.add_worker() events = [] - self.bus.listen(EVENT_STATE_CHANGED, events.append) + self.hass.bus.listen(EVENT_STATE_CHANGED, events.append) self.states.set('light.bowl', 'on') - self.bus._pool.block_till_done() + self.hass.block_till_done() self.assertEqual(0, len(events)) self.states.set('light.bowl', 'on', None, True) - self.bus._pool.block_till_done() + self.hass.block_till_done() self.assertEqual(1, len(events)) @@ -400,21 +359,14 @@ class TestServiceRegistry(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" - self.pool = ha.create_worker_pool(0) - self.bus = ha.EventBus(self.pool) - - def add_job(*args, **kwargs): - """Forward calls to add_job on Home Assistant.""" - # self works because we also have self.pool defined. - return ha.HomeAssistant.add_job(self, *args, **kwargs) - - self.services = ha.ServiceRegistry(self.bus, add_job) + self.hass = get_test_home_assistant() + self.services = self.hass.services self.services.register("Test_Domain", "TEST_SERVICE", lambda x: None) + self.hass.block_till_done() def tearDown(self): # pylint: disable=invalid-name """Stop down stuff we started.""" - if self.pool.worker_count: - self.pool.stop() + self.hass.stop() def test_has_service(self): """Test has_service method.""" @@ -434,8 +386,6 @@ class TestServiceRegistry(unittest.TestCase): def test_call_with_blocking_done_in_time(self): """Test call with blocking.""" - self.pool.add_worker() - self.pool.add_worker() calls = [] self.services.register("test_domain", "register_calls", lambda x: calls.append(1)) @@ -444,28 +394,15 @@ class TestServiceRegistry(unittest.TestCase): self.services.call('test_domain', 'REGISTER_CALLS', blocking=True)) self.assertEqual(1, len(calls)) - def test_call_with_blocking_not_done_in_time(self): - """Test with blocking.""" - calls = [] - self.services.register("test_domain", "register_calls", - lambda x: calls.append(1)) - - orig_limit = ha.SERVICE_CALL_LIMIT - ha.SERVICE_CALL_LIMIT = 0.01 - self.assertFalse( - self.services.call('test_domain', 'register_calls', blocking=True)) - self.assertEqual(0, len(calls)) - ha.SERVICE_CALL_LIMIT = orig_limit - def test_call_non_existing_with_blocking(self): """Test non-existing with blocking.""" - self.pool.add_worker() - self.pool.add_worker() - orig_limit = ha.SERVICE_CALL_LIMIT - ha.SERVICE_CALL_LIMIT = 0.01 - self.assertFalse( - self.services.call('test_domain', 'i_do_not_exist', blocking=True)) - ha.SERVICE_CALL_LIMIT = orig_limit + prior = ha.SERVICE_CALL_LIMIT + try: + ha.SERVICE_CALL_LIMIT = 0.01 + assert not self.services.call('test_domain', 'i_do_not_exist', + blocking=True) + finally: + ha.SERVICE_CALL_LIMIT = prior class TestConfig(unittest.TestCase): diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 00000000000..d3bd3cf751b --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,41 @@ +"""Test methods in __main__.""" +from unittest.mock import patch, PropertyMock + +from homeassistant import __main__ as main + + +@patch('sys.exit') +def test_validate_python(mock_exit): + """Test validate Python version method.""" + with patch('sys.version_info', + new_callable=PropertyMock(return_value=(2, 7, 8))): + main.validate_python() + assert mock_exit.called is True + + mock_exit.reset_mock() + + with patch('sys.version_info', + new_callable=PropertyMock(return_value=(3, 2, 0))): + main.validate_python() + assert mock_exit.called is True + + mock_exit.reset_mock() + + with patch('sys.version_info', + new_callable=PropertyMock(return_value=(3, 4, 1))): + main.validate_python() + assert mock_exit.called is True + + mock_exit.reset_mock() + + with patch('sys.version_info', + new_callable=PropertyMock(return_value=(3, 4, 2))): + main.validate_python() + assert mock_exit.called is False + + mock_exit.reset_mock() + + with patch('sys.version_info', + new_callable=PropertyMock(return_value=(3, 5, 1))): + main.validate_python() + assert mock_exit.called is False diff --git a/tests/test_remote.py b/tests/test_remote.py index a5a7c0aa2d6..47aee687f66 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -1,7 +1,10 @@ """Test Home Assistant remote methods and classes.""" # pylint: disable=protected-access,too-many-public-methods +import asyncio +import threading import time import unittest +from unittest.mock import patch import homeassistant.core as ha import homeassistant.bootstrap as bootstrap @@ -52,14 +55,22 @@ def setUpModule(): # pylint: disable=invalid-name master_api = remote.API("127.0.0.1", API_PASSWORD, MASTER_PORT) # Start slave - slave = remote.HomeAssistant(master_api) + loop = asyncio.new_event_loop() + + # FIXME: should not be a daemon + threading.Thread(name="SlaveThread", daemon=True, + target=loop.run_forever).start() + + slave = remote.HomeAssistant(master_api, loop=loop) slave.config.config_dir = get_test_config_dir() + slave.config.skip_pip = True bootstrap.setup_component( slave, http.DOMAIN, {http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD, http.CONF_SERVER_PORT: SLAVE_PORT}}) - slave.start() + with patch.object(ha, 'create_timer', return_value=None): + slave.start() def tearDownModule(): # pylint: disable=invalid-name @@ -73,8 +84,8 @@ class TestRemoteMethods(unittest.TestCase): def tearDown(self): """Stop everything that was started.""" - slave.pool.block_till_done() - hass.pool.block_till_done() + slave.block_till_done() + hass.block_till_done() def test_validate_api(self): """Test Python API validate_api.""" @@ -111,7 +122,7 @@ class TestRemoteMethods(unittest.TestCase): hass.bus.listen("test.event_no_data", listener) remote.fire_event(master_api, "test.event_no_data") - hass.pool.block_till_done() + hass.block_till_done() self.assertEqual(1, len(test_value)) # Should not trigger any exception @@ -156,12 +167,12 @@ class TestRemoteMethods(unittest.TestCase): remote.set_state(master_api, 'test.test', 'set_test_2') remote.set_state(master_api, 'test.test', 'set_test_2') - hass.bus._pool.block_till_done() + hass.block_till_done() self.assertEqual(1, len(events)) remote.set_state( master_api, 'test.test', 'set_test_2', force_update=True) - hass.bus._pool.block_till_done() + hass.block_till_done() self.assertEqual(2, len(events)) def test_is_state(self): @@ -197,7 +208,7 @@ class TestRemoteMethods(unittest.TestCase): remote.call_service(master_api, "test_domain", "test_service") - hass.pool.block_till_done() + hass.block_till_done() self.assertEqual(1, len(test_value)) @@ -223,8 +234,8 @@ class TestRemoteClasses(unittest.TestCase): def tearDown(self): """Stop everything that was started.""" - slave.pool.block_till_done() - hass.pool.block_till_done() + slave.block_till_done() + hass.block_till_done() def test_home_assistant_init(self): """Test HomeAssistant init.""" @@ -248,9 +259,9 @@ class TestRemoteClasses(unittest.TestCase): slave.states.set("remote.test", "remote.statemachine test") # Wait till slave tells master - slave.pool.block_till_done() + slave.block_till_done() # Wait till master gives updated state - hass.pool.block_till_done() + hass.block_till_done() self.assertEqual("remote.statemachine test", slave.states.get("remote.test").state) @@ -258,27 +269,27 @@ class TestRemoteClasses(unittest.TestCase): def test_statemachine_remove_from_master(self): """Remove statemachine from master.""" hass.states.set("remote.master_remove", "remove me!") - hass.pool.block_till_done() - slave.pool.block_till_done() + hass.block_till_done() + slave.block_till_done() self.assertIn('remote.master_remove', slave.states.entity_ids()) hass.states.remove("remote.master_remove") - hass.pool.block_till_done() - slave.pool.block_till_done() + hass.block_till_done() + slave.block_till_done() self.assertNotIn('remote.master_remove', slave.states.entity_ids()) def test_statemachine_remove_from_slave(self): """Remove statemachine from slave.""" hass.states.set("remote.slave_remove", "remove me!") - hass.pool.block_till_done() + hass.block_till_done() self.assertIn('remote.slave_remove', slave.states.entity_ids()) self.assertTrue(slave.states.remove("remote.slave_remove")) - slave.pool.block_till_done() - hass.pool.block_till_done() + slave.block_till_done() + hass.block_till_done() self.assertNotIn('remote.slave_remove', slave.states.entity_ids()) @@ -292,9 +303,9 @@ class TestRemoteClasses(unittest.TestCase): slave.bus.fire("test.event_no_data") # Wait till slave tells master - slave.pool.block_till_done() + slave.block_till_done() # Wait till master gives updated event - hass.pool.block_till_done() + hass.block_till_done() self.assertEqual(1, len(hass_call)) self.assertEqual(1, len(slave_call)) diff --git a/tests/util/test_async.py b/tests/util/test_async.py new file mode 100644 index 00000000000..079097f3326 --- /dev/null +++ b/tests/util/test_async.py @@ -0,0 +1,77 @@ +"""Tests for async util methods from Python source.""" +import asyncio +from asyncio import test_utils + +from homeassistant.util import async as hasync + + +class RunCoroutineThreadsafeTests(test_utils.TestCase): + """Test case for asyncio.run_coroutine_threadsafe.""" + + def setUp(self): + self.loop = asyncio.new_event_loop() + self.set_event_loop(self.loop) # Will cleanup properly + + @asyncio.coroutine + def add(self, a, b, fail=False, cancel=False): + """Wait 0.05 second and return a + b.""" + yield from asyncio.sleep(0.05, loop=self.loop) + if fail: + raise RuntimeError("Fail!") + if cancel: + asyncio.tasks.Task.current_task(self.loop).cancel() + yield + return a + b + + def target(self, fail=False, cancel=False, timeout=None, + advance_coro=False): + """Run add coroutine in the event loop.""" + coro = self.add(1, 2, fail=fail, cancel=cancel) + future = hasync.run_coroutine_threadsafe(coro, self.loop) + if advance_coro: + # this is for test_run_coroutine_threadsafe_task_factory_exception; + # otherwise it spills errors and breaks **other** unittests, since + # 'target' is interacting with threads. + + # With this call, `coro` will be advanced, so that + # CoroWrapper.__del__ won't do anything when asyncio tests run + # in debug mode. + self.loop.call_soon_threadsafe(coro.send, None) + try: + return future.result(timeout) + finally: + future.done() or future.cancel() + + def test_run_coroutine_threadsafe(self): + """Test coroutine submission from a thread to an event loop.""" + future = self.loop.run_in_executor(None, self.target) + result = self.loop.run_until_complete(future) + self.assertEqual(result, 3) + + def test_run_coroutine_threadsafe_with_exception(self): + """Test coroutine submission from a thread to an event loop + when an exception is raised.""" + future = self.loop.run_in_executor(None, self.target, True) + with self.assertRaises(RuntimeError) as exc_context: + self.loop.run_until_complete(future) + self.assertIn("Fail!", exc_context.exception.args) + + def test_run_coroutine_threadsafe_with_timeout(self): + """Test coroutine submission from a thread to an event loop + when a timeout is raised.""" + callback = lambda: self.target(timeout=0) # noqa + future = self.loop.run_in_executor(None, callback) + with self.assertRaises(asyncio.TimeoutError): + self.loop.run_until_complete(future) + test_utils.run_briefly(self.loop) + # Check that there's no pending task (add has been cancelled) + for task in asyncio.Task.all_tasks(self.loop): + self.assertTrue(task.done()) + + def test_run_coroutine_threadsafe_task_cancelled(self): + """Test coroutine submission from a tread to an event loop + when the task is cancelled.""" + callback = lambda: self.target(cancel=True) # noqa + future = self.loop.run_in_executor(None, callback) + with self.assertRaises(asyncio.CancelledError): + self.loop.run_until_complete(future) From 7f6fb95afddc6f84cd5b2068e1c59737c15a3f7f Mon Sep 17 00:00:00 2001 From: beepmill Date: Mon, 12 Sep 2016 20:45:39 -0600 Subject: [PATCH 037/162] Ignore desktop.ini (Windows Explorer) (#3363) --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 147d68c36d3..43eae33f554 100644 --- a/.gitignore +++ b/.gitignore @@ -99,4 +99,7 @@ virtualization/vagrant/config .vscode # Built docs -docs/build \ No newline at end of file +docs/build + +# Windows Explorer +desktop.ini From 8189ec2c8d81ea946f8320ed69ae5efc5c42da60 Mon Sep 17 00:00:00 2001 From: Teagan Glenn Date: Mon, 12 Sep 2016 20:59:34 -0600 Subject: [PATCH 038/162] Automatic polling (#3360) * Test updating automatic * Scan interval * Schedule scan every time delta * Pass around has * Recursive issue * Method invocation * Oops * Set up poll * Default argument value * Unused import * Semicolon * Fix tests * Linting * Unneeded throttle as it's handled by time event * Use track time change event listener * Disable lint rule * Attribute removed - removing test * Debug instead of info * Unused import --- .../components/device_tracker/automatic.py | 31 +++++++------------ .../device_tracker/test_automatic.py | 26 ++++------------ 2 files changed, 17 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/device_tracker/automatic.py b/homeassistant/components/device_tracker/automatic.py index 7855323ba06..27bd9c6b477 100644 --- a/homeassistant/components/device_tracker/automatic.py +++ b/homeassistant/components/device_tracker/automatic.py @@ -15,12 +15,11 @@ from homeassistant.components.device_tracker import (PLATFORM_SCHEMA, ATTR_ATTRIBUTES) from homeassistant.const import CONF_USERNAME, CONF_PASSWORD import homeassistant.helpers.config_validation as cv -from homeassistant.util import Throttle, datetime as dt_util +from homeassistant.helpers.event import track_utc_time_change +from homeassistant.util import datetime as dt_util _LOGGER = logging.getLogger(__name__) -MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30) - CONF_CLIENT_ID = 'client_id' CONF_SECRET = 'secret' CONF_DEVICES = 'devices' @@ -53,7 +52,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_scanner(hass, config: dict, see): """Validate the configuration and return an Automatic scanner.""" try: - AutomaticDeviceScanner(config, see) + AutomaticDeviceScanner(hass, config, see) except requests.HTTPError as err: _LOGGER.error(str(err)) return False @@ -61,11 +60,14 @@ def setup_scanner(hass, config: dict, see): return True +# pylint: disable=too-many-instance-attributes +# pylint: disable=too-few-public-methods class AutomaticDeviceScanner(object): """A class representing an Automatic device.""" - def __init__(self, config: dict, see) -> None: + def __init__(self, hass, config: dict, see) -> None: """Initialize the automatic device scanner.""" + self.hass = hass self._devices = config.get(CONF_DEVICES, None) self._access_token_payload = { 'username': config.get(CONF_USERNAME), @@ -81,20 +83,10 @@ class AutomaticDeviceScanner(object): self.last_trips = {} self.see = see - self.scan_devices() - - def scan_devices(self): - """Scan for new devices and return a list with found device IDs.""" self._update_info() - return [item['id'] for item in self.last_results] - - def get_device_name(self, device): - """Get the device name from id.""" - vehicle = [item['display_name'] for item in self.last_results - if item['id'] == device] - - return vehicle[0] + track_utc_time_change(self.hass, self._update_info, + second=range(0, 60, 30)) def _update_headers(self): """Get the access token from automatic.""" @@ -114,10 +106,9 @@ class AutomaticDeviceScanner(object): 'Authorization': 'Bearer {}'.format(access_token) } - @Throttle(MIN_TIME_BETWEEN_SCANS) - def _update_info(self) -> None: + def _update_info(self, now=None) -> None: """Update the device info.""" - _LOGGER.info('Updating devices') + _LOGGER.debug('Updating devices %s', now) self._update_headers() response = requests.get(URL_VEHICLES, headers=self._headers) diff --git a/tests/components/device_tracker/test_automatic.py b/tests/components/device_tracker/test_automatic.py index e026d91a43c..2e476ac742d 100644 --- a/tests/components/device_tracker/test_automatic.py +++ b/tests/components/device_tracker/test_automatic.py @@ -6,8 +6,9 @@ import unittest from unittest.mock import patch from homeassistant.components.device_tracker.automatic import ( - URL_AUTHORIZE, URL_VEHICLES, URL_TRIPS, setup_scanner, - AutomaticDeviceScanner) + URL_AUTHORIZE, URL_VEHICLES, URL_TRIPS, setup_scanner) + +from tests.common import get_test_home_assistant _LOGGER = logging.getLogger(__name__) @@ -205,6 +206,7 @@ class TestAutomatic(unittest.TestCase): def setUp(self): """Set up test data.""" + self.hass = get_test_home_assistant() def tearDown(self): """Tear down test data.""" @@ -221,7 +223,7 @@ class TestAutomatic(unittest.TestCase): 'secret': CLIENT_SECRET } - self.assertFalse(setup_scanner(None, config, self.see_mock)) + self.assertFalse(setup_scanner(self.hass, config, self.see_mock)) @patch('requests.get', side_effect=mocked_requests) @patch('requests.post', side_effect=mocked_requests) @@ -235,20 +237,4 @@ class TestAutomatic(unittest.TestCase): 'secret': CLIENT_SECRET } - self.assertTrue(setup_scanner(None, config, self.see_mock)) - - @patch('requests.get', side_effect=mocked_requests) - @patch('requests.post', side_effect=mocked_requests) - def test_device_attributes(self, mock_get, mock_post): - """Test device attributes are set on load.""" - config = { - 'platform': 'automatic', - 'username': VALID_USERNAME, - 'password': PASSWORD, - 'client_id': CLIENT_ID, - 'secret': CLIENT_SECRET - } - - scanner = AutomaticDeviceScanner(config, self.see_mock) - - self.assertEqual(DISPLAY_NAME, scanner.get_device_name('vid')) + self.assertTrue(setup_scanner(self.hass, config, self.see_mock)) From 8ba952ee0e308d890d85c417ab27693fef94e3bd Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 13 Sep 2016 07:23:53 +0200 Subject: [PATCH 039/162] Use voluptuous for SCSGate (#3265) * Migrate to voluptuous * Extend platforms --- homeassistant/components/cover/scsgate.py | 39 +++++----- homeassistant/components/light/scsgate.py | 46 ++++++------ homeassistant/components/scsgate.py | 80 +++++++++++++-------- homeassistant/components/switch/scsgate.py | 84 +++++++++++----------- homeassistant/const.py | 1 + 5 files changed, 139 insertions(+), 111 deletions(-) diff --git a/homeassistant/components/cover/scsgate.py b/homeassistant/components/cover/scsgate.py index 18692534e90..c491f3bf548 100644 --- a/homeassistant/components/cover/scsgate.py +++ b/homeassistant/components/cover/scsgate.py @@ -6,37 +6,43 @@ https://home-assistant.io/components/cover.scsgate/ """ import logging +import voluptuous as vol + import homeassistant.components.scsgate as scsgate -from homeassistant.components.cover import CoverDevice -from homeassistant.const import CONF_NAME +from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA) +from homeassistant.const import (CONF_DEVICES, CONF_NAME) +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['scsgate'] -SCS_ID = 'scs_id' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA}), +}) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the SCSGate cover.""" - devices = config.get('devices') + devices = config.get(CONF_DEVICES) covers = [] logger = logging.getLogger(__name__) if devices: for _, entity_info in devices.items(): - if entity_info[SCS_ID] in scsgate.SCSGATE.devices: + if entity_info[scsgate.CONF_SCS_ID] in scsgate.SCSGATE.devices: continue - logger.info("Adding %s scsgate.cover", entity_info[CONF_NAME]) - name = entity_info[CONF_NAME] - scs_id = entity_info[SCS_ID] - cover = SCSGateCover( - name=name, - scs_id=scs_id, - logger=logger) + scs_id = entity_info[scsgate.CONF_SCS_ID] + + logger.info("Adding %s scsgate.cover", name) + + cover = SCSGateCover(name=name, scs_id=scs_id, logger=logger) scsgate.SCSGATE.add_device(cover) covers.append(cover) - add_devices_callback(covers) + add_devices(covers) # pylint: disable=too-many-arguments, too-many-instance-attributes @@ -91,6 +97,5 @@ class SCSGateCover(CoverDevice): def process_event(self, message): """Handle a SCSGate message related with this cover.""" - self._logger.debug( - "Rollershutter %s, got message %s", - self._scs_id, message.toggled) + self._logger.debug("Cover %s, got message %s", + self._scs_id, message.toggled) diff --git a/homeassistant/components/light/scsgate.py b/homeassistant/components/light/scsgate.py index 31c07513136..a33b30736fe 100644 --- a/homeassistant/components/light/scsgate.py +++ b/homeassistant/components/light/scsgate.py @@ -6,35 +6,43 @@ https://home-assistant.io/components/light.scsgate/ """ import logging +import voluptuous as vol + import homeassistant.components.scsgate as scsgate -from homeassistant.components.light import Light -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.components.light import (Light, PLATFORM_SCHEMA) +from homeassistant.const import ( + ATTR_ENTITY_ID, ATTR_STATE, CONF_DEVICES, CONF_NAME) +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['scsgate'] +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA}), +}) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Add the SCSGate swiches defined inside of the configuration file.""" - devices = config.get('devices') + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the SCSGate switches.""" + devices = config.get(CONF_DEVICES) lights = [] logger = logging.getLogger(__name__) if devices: for _, entity_info in devices.items(): - if entity_info['scs_id'] in scsgate.SCSGATE.devices: + if entity_info[scsgate.CONF_SCS_ID] in scsgate.SCSGATE.devices: continue - logger.info("Adding %s scsgate.light", entity_info['name']) + name = entity_info[CONF_NAME] + scs_id = entity_info[scsgate.CONF_SCS_ID] - name = entity_info['name'] - scs_id = entity_info['scs_id'] - light = SCSGateLight( - name=name, - scs_id=scs_id, - logger=logger) + logger.info("Adding %s scsgate.light", name) + + light = SCSGateLight(name=name, scs_id=scs_id, logger=logger) lights.append(light) - add_devices_callback(lights) + add_devices(lights) scsgate.SCSGATE.add_devices_to_register(lights) @@ -73,9 +81,7 @@ class SCSGateLight(Light): from scsgate.tasks import ToggleStatusTask scsgate.SCSGATE.append_task( - ToggleStatusTask( - target=self._scs_id, - toggled=True)) + ToggleStatusTask(target=self._scs_id, toggled=True)) self._toggled = True self.update_ha_state() @@ -85,9 +91,7 @@ class SCSGateLight(Light): from scsgate.tasks import ToggleStatusTask scsgate.SCSGATE.append_task( - ToggleStatusTask( - target=self._scs_id, - toggled=False)) + ToggleStatusTask(target=self._scs_id, toggled=False)) self._toggled = False self.update_ha_state() @@ -111,6 +115,6 @@ class SCSGateLight(Light): self.hass.bus.fire( 'button_pressed', { ATTR_ENTITY_ID: self._scs_id, - 'state': command + ATTR_STATE: command, } ) diff --git a/homeassistant/components/scsgate.py b/homeassistant/components/scsgate.py index bcbe0affbba..549759f5e12 100644 --- a/homeassistant/components/scsgate.py +++ b/homeassistant/components/scsgate.py @@ -7,15 +7,60 @@ https://home-assistant.io/components/scsgate/ import logging from threading import Lock +import voluptuous as vol + +from homeassistant.const import (CONF_DEVICE, CONF_NAME) from homeassistant.core import EVENT_HOMEASSISTANT_STOP +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['scsgate==0.1.0'] -DOMAIN = "scsgate" -SCSGATE = None + _LOGGER = logging.getLogger(__name__) +ATTR_STATE = 'state' -class SCSGate: +CONF_SCS_ID = 'scs_id' + +DOMAIN = 'scsgate' + +SCSGATE = None + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_DEVICE): cv.string, + }), +}, extra=vol.ALLOW_EXTRA) + +SCSGATE_SCHEMA = vol.Schema({ + vol.Required(CONF_SCS_ID): cv.string, + vol.Optional(CONF_NAME): cv.string, +}) + + +def setup(hass, config): + """Setup the SCSGate component.""" + device = config[DOMAIN][CONF_DEVICE] + global SCSGATE + + # pylint: disable=broad-except + try: + SCSGATE = SCSGate(device=device, logger=_LOGGER) + SCSGATE.start() + except Exception as exception: + _LOGGER.error("Cannot setup SCSGate component: %s", exception) + return False + + def stop_monitor(event): + """Stop the SCSGate.""" + _LOGGER.info("Stopping SCSGate monitor thread") + SCSGATE.stop() + + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_monitor) + + return True + + +class SCSGate(object): """The class for dealing with the SCSGate device via scsgate.Reactor.""" def __init__(self, device, logger): @@ -32,8 +77,7 @@ class SCSGate: from scsgate.reactor import Reactor self._reactor = Reactor( - connection=connection, - logger=self._logger, + connection=connection, logger=self._logger, handle_message=self.handle_message) def handle_message(self, message): @@ -61,8 +105,7 @@ class SCSGate: try: self._devices[message.entity].process_event(message) except Exception as exception: - msg = "Exception while processing event: {}".format( - exception) + msg = "Exception while processing event: {}".format(exception) self._logger.error(msg) else: self._logger.info( @@ -127,26 +170,3 @@ class SCSGate: def append_task(self, task): """Register a new task to be executed.""" self._reactor.append_task(task) - - -def setup(hass, config): - """Setup the SCSGate component.""" - device = config['scsgate']['device'] - global SCSGATE - - # pylint: disable=broad-except - try: - SCSGATE = SCSGate(device=device, logger=_LOGGER) - SCSGATE.start() - except Exception as exception: - _LOGGER.error("Cannot setup SCSGate component: %s", exception) - return False - - def stop_monitor(event): - """Stop the SCSGate.""" - _LOGGER.info("Stopping SCSGate monitor thread") - SCSGATE.stop() - - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_monitor) - - return True diff --git a/homeassistant/components/switch/scsgate.py b/homeassistant/components/switch/scsgate.py index 964b23c37da..c1d5b19fdee 100644 --- a/homeassistant/components/switch/scsgate.py +++ b/homeassistant/components/switch/scsgate.py @@ -6,48 +6,56 @@ https://home-assistant.io/components/switch.scsgate/ """ import logging +import voluptuous as vol + import homeassistant.components.scsgate as scsgate -from homeassistant.components.switch import SwitchDevice -from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.const import ( + ATTR_ENTITY_ID, ATTR_STATE, CONF_NAME, CONF_DEVICES) +import homeassistant.helpers.config_validation as cv + +ATTR_SCENARIO_ID = 'scenario_id' DEPENDENCIES = ['scsgate'] +CONF_TRADITIONAL = 'traditional' +CONF_SCENARIO = 'scenario' -def setup_platform(hass, config, add_devices_callback, discovery_info=None): +CONF_SCS_ID = 'scs_id' + +DOMAIN = 'scsgate' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA}), +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the SCSGate switches.""" logger = logging.getLogger(__name__) _setup_traditional_switches( - logger=logger, - config=config, - add_devices_callback=add_devices_callback) + logger=logger, config=config, add_devices_callback=add_devices) - _setup_scenario_switches( - logger=logger, - config=config, - hass=hass) + _setup_scenario_switches(logger=logger, config=config, hass=hass) def _setup_traditional_switches(logger, config, add_devices_callback): """Add traditional SCSGate switches.""" - traditional = config.get('traditional') + traditional = config.get(CONF_TRADITIONAL) switches = [] if traditional: for _, entity_info in traditional.items(): - if entity_info['scs_id'] in scsgate.SCSGATE.devices: + if entity_info[scsgate.CONF_SCS_ID] in scsgate.SCSGATE.devices: continue - logger.info( - "Adding %s scsgate.traditional_switch", entity_info['name']) + name = entity_info[CONF_NAME] + scs_id = entity_info[scsgate.CONF_SCS_ID] - name = entity_info['name'] - scs_id = entity_info['scs_id'] + logger.info("Adding %s scsgate.traditional_switch", name) - switch = SCSGateSwitch( - name=name, - scs_id=scs_id, - logger=logger) + switch = SCSGateSwitch(name=name, scs_id=scs_id, logger=logger) switches.append(switch) add_devices_callback(switches) @@ -56,24 +64,20 @@ def _setup_traditional_switches(logger, config, add_devices_callback): def _setup_scenario_switches(logger, config, hass): """Add only SCSGate scenario switches.""" - scenario = config.get("scenario") + scenario = config.get(CONF_SCENARIO) if scenario: for _, entity_info in scenario.items(): - if entity_info['scs_id'] in scsgate.SCSGATE.devices: + if entity_info[scsgate.CONF_SCS_ID] in scsgate.SCSGATE.devices: continue - logger.info( - "Adding %s scsgate.scenario_switch", entity_info['name']) + name = entity_info[CONF_NAME] + scs_id = entity_info[scsgate.CONF_SCS_ID] - name = entity_info['name'] - scs_id = entity_info['scs_id'] + logger.info("Adding %s scsgate.scenario_switch", name) switch = SCSGateScenarioSwitch( - name=name, - scs_id=scs_id, - logger=logger, - hass=hass) + name=name, scs_id=scs_id, logger=logger, hass=hass) scsgate.SCSGATE.add_device(switch) @@ -112,9 +116,7 @@ class SCSGateSwitch(SwitchDevice): from scsgate.tasks import ToggleStatusTask scsgate.SCSGATE.append_task( - ToggleStatusTask( - target=self._scs_id, - toggled=True)) + ToggleStatusTask(target=self._scs_id, toggled=True)) self._toggled = True self.update_ha_state() @@ -124,9 +126,7 @@ class SCSGateSwitch(SwitchDevice): from scsgate.tasks import ToggleStatusTask scsgate.SCSGATE.append_task( - ToggleStatusTask( - target=self._scs_id, - toggled=False)) + ToggleStatusTask(target=self._scs_id, toggled=False)) self._toggled = False self.update_ha_state() @@ -150,12 +150,11 @@ class SCSGateSwitch(SwitchDevice): self.hass.bus.fire( 'button_pressed', { ATTR_ENTITY_ID: self._scs_id, - 'state': command - } + ATTR_STATE: command} ) -class SCSGateScenarioSwitch: +class SCSGateScenarioSwitch(object): """Provides a SCSGate scenario switch. This switch is always in a 'off" state, when toggled it's used to trigger @@ -188,14 +187,13 @@ class SCSGateScenarioSwitch: elif isinstance(message, ScenarioTriggeredMessage): scenario_id = message.scenario else: - self._logger.warn( - "Scenario switch: received unknown message %s", - message) + self._logger.warn("Scenario switch: received unknown message %s", + message) return self._hass.bus.fire( 'scenario_switch_triggered', { ATTR_ENTITY_ID: int(self._scs_id), - 'scenario_id': int(scenario_id, 16) + ATTR_SCENARIO_ID: int(scenario_id, 16) } ) diff --git a/homeassistant/const.py b/homeassistant/const.py index b8b3756f37b..4107fb65a24 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -263,6 +263,7 @@ ATTR_GPS_ACCURACY = 'gps_accuracy' # If state is assumed ATTR_ASSUMED_STATE = 'assumed_state' +ATTR_STATE = 'state' # #### SERVICES #### SERVICE_HOMEASSISTANT_STOP = 'stop' From efbc378226e4b6c58e9f7a403b42c50eccb85dd0 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Tue, 13 Sep 2016 19:16:17 +0200 Subject: [PATCH 040/162] Fix temp conversion for nest setpoint (#3373) * Fix temp conversion for nest setpoint * DEbug --- homeassistant/components/climate/nest.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/climate/nest.py index f55d1d856eb..1b3c38ce449 100644 --- a/homeassistant/components/climate/nest.py +++ b/homeassistant/components/climate/nest.py @@ -4,15 +4,17 @@ Support for Nest thermostats. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/climate.nest/ """ +import logging import voluptuous as vol - import homeassistant.components.nest as nest from homeassistant.components.climate import ( STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA) from homeassistant.const import ( TEMP_CELSIUS, CONF_SCAN_INTERVAL, ATTR_TEMPERATURE) +from homeassistant.util.temperature import convert as convert_temperature DEPENDENCIES = ['nest'] +_LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_SCAN_INTERVAL): @@ -22,7 +24,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Nest thermostat.""" - add_devices([NestThermostat(structure, device) + temp_unit = hass.config.units.temperature_unit + add_devices([NestThermostat(structure, device, temp_unit) for structure, device in nest.devices()]) @@ -30,8 +33,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class NestThermostat(ClimateDevice): """Representation of a Nest thermostat.""" - def __init__(self, structure, device): + def __init__(self, structure, device, temp_unit): """Initialize the thermostat.""" + self._unit = temp_unit self.structure = structure self.device = device @@ -134,7 +138,9 @@ class NestThermostat(ClimateDevice): def set_temperature(self, **kwargs): """Set new target temperature.""" - temperature = kwargs.get(ATTR_TEMPERATURE) + temperature = convert_temperature(kwargs.get(ATTR_TEMPERATURE), + self._unit, TEMP_CELSIUS) + _LOGGER.debug("Nest set_temperature-input-value=%s", temperature) if temperature is None: return if self.device.mode == 'range': @@ -142,6 +148,7 @@ class NestThermostat(ClimateDevice): temperature = (temperature, self.target_temperature_high) elif self.target_temperature == self.target_temperature_high: temperature = (self.target_temperature_low, temperature) + _LOGGER.debug("Nest set_temperature-output-value=%s", temperature) self.device.target = temperature def set_operation_mode(self, operation_mode): From bba75bf6c3bfce95391e80e6d3c644955330d689 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Tue, 13 Sep 2016 19:26:47 +0200 Subject: [PATCH 041/162] Add Simplepush notifications (#3336) --- .coveragerc | 1 + homeassistant/components/notify/simplepush.py | 55 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 homeassistant/components/notify/simplepush.py diff --git a/.coveragerc b/.coveragerc index ff540ca1f2e..fadff7fe053 100644 --- a/.coveragerc +++ b/.coveragerc @@ -196,6 +196,7 @@ omit = homeassistant/components/notify/pushover.py homeassistant/components/notify/rest.py homeassistant/components/notify/sendgrid.py + homeassistant/components/notify/simplepush.py homeassistant/components/notify/slack.py homeassistant/components/notify/smtp.py homeassistant/components/notify/syslog.py diff --git a/homeassistant/components/notify/simplepush.py b/homeassistant/components/notify/simplepush.py new file mode 100644 index 00000000000..af75ffeadd3 --- /dev/null +++ b/homeassistant/components/notify/simplepush.py @@ -0,0 +1,55 @@ +""" +Simplepush notification service. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.simplepush/ +""" +import logging + +import requests +import voluptuous as vol + +from homeassistant.components.notify import ( + ATTR_TITLE, ATTR_TITLE_DEFAULT, PLATFORM_SCHEMA, BaseNotificationService) +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) +_RESOURCE = 'https://api.simplepush.io/send' + +CONF_DEVICE_KEY = 'device_key' + +DEFAULT_TIMEOUT = 10 + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_DEVICE_KEY): cv.string, +}) + + +def get_service(hass, config): + """Get the Simplepush notification service.""" + return SimplePushNotificationService(config.get(CONF_DEVICE_KEY)) + + +# pylint: disable=too-few-public-methods +class SimplePushNotificationService(BaseNotificationService): + """Implementation of the notification service for SimplePush.""" + + def __init__(self, device_key): + """Initialize the service.""" + self._device_key = device_key + + def send_message(self, message='', **kwargs): + """Send a message to a user.""" + title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) + + # Upstream bug will be fixed soon, but no dead-line available. + # payload = 'key={}&title={}&msg={}'.format( + # self._device_key, title, message).replace(' ', '%') + # response = requests.get( + # _RESOURCE, data=payload, timeout=DEFAULT_TIMEOUT) + response = requests.get( + '{}/{}/{}/{}'.format(_RESOURCE, self._device_key, title, message), + timeout=DEFAULT_TIMEOUT) + + if response.json()['status'] != 'OK': + _LOGGER.error("Not possible to send notification") From 898cf1b35269ffa516731041cd2433e1ec7fe717 Mon Sep 17 00:00:00 2001 From: John Arild Berentsen Date: Tue, 13 Sep 2016 20:26:44 +0200 Subject: [PATCH 042/162] zxt_120 set temperature did not update on setpoint (#3380) --- homeassistant/components/climate/zwave.py | 1 + 1 file changed, 1 insertion(+) diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py index 6c08bf391d8..4eec1f9c652 100755 --- a/homeassistant/components/climate/zwave.py +++ b/homeassistant/components/climate/zwave.py @@ -261,6 +261,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): self._target_temperature = temperature # ZXT-120 responds only to whole int value.data = round(temperature, 0) + self.update_ha_state() break else: _LOGGER.debug("Setting new setpoint for %s, " From 812dc99073ef9864d7ef5b0d9f31e8e062b33c6f Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 13 Sep 2016 21:27:43 +0200 Subject: [PATCH 043/162] fix xbox live entity id (#3368) --- homeassistant/components/sensor/xbox_live.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/homeassistant/components/sensor/xbox_live.py b/homeassistant/components/sensor/xbox_live.py index 9552eea1001..36e100394e9 100644 --- a/homeassistant/components/sensor/xbox_live.py +++ b/homeassistant/components/sensor/xbox_live.py @@ -11,7 +11,6 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import (CONF_API_KEY, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) @@ -73,11 +72,6 @@ class XboxSensor(Entity): """Return the name of the sensor.""" return self._gamertag - @property - def entity_id(self): - """Return the entity ID.""" - return 'sensor.xbox_' + slugify(self._gamertag) - @property def state(self): """Return the state of the sensor.""" From e4f4e910966d9139af0179acd473aefbe5f09b34 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Tue, 13 Sep 2016 21:43:37 +0200 Subject: [PATCH 044/162] Bugfix auto/manual mode change (#3384) --- homeassistant/components/climate/homematic.py | 3 +++ homeassistant/components/homematic.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/climate/homematic.py b/homeassistant/components/climate/homematic.py index 7e0b4fd6450..c9901c40aea 100644 --- a/homeassistant/components/climate/homematic.py +++ b/homeassistant/components/climate/homematic.py @@ -99,6 +99,9 @@ class HMThermostat(homematic.HMDevice, ClimateDevice): return None if temperature is None: return + + if self.current_operation == STATE_AUTO: + return self._hmdevice.actionNodeData('MANU_MODE', temperature) self._hmdevice.set_temperature(temperature) def set_operation_mode(self, operation_mode): diff --git a/homeassistant/components/homematic.py b/homeassistant/components/homematic.py index 1baf52d37d7..466a81563e8 100644 --- a/homeassistant/components/homematic.py +++ b/homeassistant/components/homematic.py @@ -23,7 +23,7 @@ from homeassistant.config import load_yaml_config_file from homeassistant.util import Throttle DOMAIN = 'homematic' -REQUIREMENTS = ["pyhomematic==0.1.13"] +REQUIREMENTS = ["pyhomematic==0.1.14"] HOMEMATIC = None HOMEMATIC_LINK_DELAY = 0.5 diff --git a/requirements_all.txt b/requirements_all.txt index dc832841c17..61507807534 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -324,7 +324,7 @@ pyenvisalink==1.0 pyfttt==0.3 # homeassistant.components.homematic -pyhomematic==0.1.13 +pyhomematic==0.1.14 # homeassistant.components.device_tracker.icloud pyicloud==0.9.1 From ca646c08c26300e117994953d0b8cc9a86b06514 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20Sandstr=C3=B6m?= Date: Tue, 13 Sep 2016 22:47:44 +0200 Subject: [PATCH 045/162] Modbus component refactoring - sensors and switches (#3297) --- .../components/binary_sensor/modbus.py | 61 +++++++ homeassistant/components/modbus.py | 97 +++++++---- homeassistant/components/sensor/modbus.py | 150 +++++++----------- homeassistant/components/switch/modbus.py | 107 ++++--------- 4 files changed, 213 insertions(+), 202 deletions(-) create mode 100644 homeassistant/components/binary_sensor/modbus.py diff --git a/homeassistant/components/binary_sensor/modbus.py b/homeassistant/components/binary_sensor/modbus.py new file mode 100644 index 00000000000..d43c348f116 --- /dev/null +++ b/homeassistant/components/binary_sensor/modbus.py @@ -0,0 +1,61 @@ +""" +Support for Modbus Coil sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.modbus/ +""" +import logging +import voluptuous as vol + +import homeassistant.components.modbus as modbus +from homeassistant.const import CONF_NAME +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.helpers import config_validation as cv +from homeassistant.components.sensor import PLATFORM_SCHEMA + +_LOGGER = logging.getLogger(__name__) +DEPENDENCIES = ['modbus'] + +CONF_COIL = "coil" +CONF_COILS = "coils" +CONF_SLAVE = "slave" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_COILS): [{ + vol.Required(CONF_COIL): cv.positive_int, + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_SLAVE): cv.positive_int + }] +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup Modbus binary sensors.""" + sensors = [] + for coil in config.get(CONF_COILS): + sensors.append(ModbusCoilSensor( + coil.get(CONF_NAME), + coil.get(CONF_SLAVE), + coil.get(CONF_COIL))) + add_devices(sensors) + + +class ModbusCoilSensor(BinarySensorDevice): + """Modbus coil sensor.""" + + def __init__(self, name, slave, coil): + """Initialize the modbus coil sensor.""" + self._name = name + self._slave = int(slave) if slave else None + self._coil = int(coil) + self._value = None + + @property + def is_on(self): + """Return the state of the sensor.""" + return self._value + + def update(self): + """Update the state of the sensor.""" + result = modbus.HUB.read_coils(self._slave, self._coil, 1) + self._value = result.bits[0] diff --git a/homeassistant/components/modbus.py b/homeassistant/components/modbus.py index b0391f9ba45..3bf6cbf031a 100644 --- a/homeassistant/components/modbus.py +++ b/homeassistant/components/modbus.py @@ -7,8 +7,12 @@ https://home-assistant.io/components/modbus/ import logging import threading +import voluptuous as vol + from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) + EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + CONF_HOST, CONF_METHOD, CONF_PORT) +import homeassistant.helpers.config_validation as cv DOMAIN = "modbus" @@ -16,19 +20,33 @@ REQUIREMENTS = ['https://github.com/bashwork/pymodbus/archive/' 'd7fc4f1cc975631e0a9011390e8017f64b612661.zip#pymodbus==1.2.0'] # Type of network -MEDIUM = "type" +CONF_BAUDRATE = "baudrate" +CONF_BYTESIZE = "bytesize" +CONF_STOPBITS = "stopbits" +CONF_TYPE = "type" +CONF_PARITY = "parity" -# if MEDIUM == "serial" -METHOD = "method" -SERIAL_PORT = "port" -BAUDRATE = "baudrate" -STOPBITS = "stopbits" -BYTESIZE = "bytesize" -PARITY = "parity" +SERIAL_SCHEMA = { + vol.Required(CONF_BAUDRATE): cv.positive_int, + vol.Required(CONF_BYTESIZE): vol.Any(5, 6, 7, 8), + vol.Required(CONF_METHOD): vol.Any('rtu', 'ascii'), + vol.Required(CONF_PORT): cv.string, + vol.Required(CONF_PARITY): vol.Any('E', 'O', 'N'), + vol.Required(CONF_STOPBITS): vol.Any(1, 2), + vol.Required(CONF_TYPE): 'serial', +} + +ETHERNET_SCHEMA = { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT): cv.positive_int, + vol.Required(CONF_TYPE): vol.Any('tcp', 'udp'), +} + + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA) +}, extra=vol.ALLOW_EXTRA) -# if MEDIUM == "tcp" or "udp" -HOST = "host" -IP_PORT = "port" _LOGGER = logging.getLogger(__name__) @@ -38,36 +56,41 @@ ATTR_ADDRESS = "address" ATTR_UNIT = "unit" ATTR_VALUE = "value" +SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema({ + vol.Required(ATTR_UNIT): cv.positive_int, + vol.Required(ATTR_ADDRESS): cv.positive_int, + vol.Required(ATTR_VALUE): cv.positive_int +}) + + HUB = None -TYPE = None def setup(hass, config): """Setup Modbus component.""" # Modbus connection type # pylint: disable=global-statement, import-error - global TYPE - TYPE = config[DOMAIN][MEDIUM] + client_type = config[DOMAIN][CONF_TYPE] # Connect to Modbus network # pylint: disable=global-statement, import-error - if TYPE == "serial": + if client_type == "serial": from pymodbus.client.sync import ModbusSerialClient as ModbusClient - client = ModbusClient(method=config[DOMAIN][METHOD], - port=config[DOMAIN][SERIAL_PORT], - baudrate=config[DOMAIN][BAUDRATE], - stopbits=config[DOMAIN][STOPBITS], - bytesize=config[DOMAIN][BYTESIZE], - parity=config[DOMAIN][PARITY]) - elif TYPE == "tcp": + client = ModbusClient(method=config[DOMAIN][CONF_METHOD], + port=config[DOMAIN][CONF_PORT], + baudrate=config[DOMAIN][CONF_BAUDRATE], + stopbits=config[DOMAIN][CONF_STOPBITS], + bytesize=config[DOMAIN][CONF_BYTESIZE], + parity=config[DOMAIN][CONF_PARITY]) + elif client_type == "tcp": from pymodbus.client.sync import ModbusTcpClient as ModbusClient - client = ModbusClient(host=config[DOMAIN][HOST], - port=config[DOMAIN][IP_PORT]) - elif TYPE == "udp": + client = ModbusClient(host=config[DOMAIN][CONF_HOST], + port=config[DOMAIN][CONF_PORT]) + elif client_type == "udp": from pymodbus.client.sync import ModbusUdpClient as ModbusClient - client = ModbusClient(host=config[DOMAIN][HOST], - port=config[DOMAIN][IP_PORT]) + client = ModbusClient(host=config[DOMAIN][CONF_HOST], + port=config[DOMAIN][CONF_PORT]) else: return False @@ -84,7 +107,8 @@ def setup(hass, config): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_modbus) # Register services for modbus - hass.services.register(DOMAIN, SERVICE_WRITE_REGISTER, write_register) + hass.services.register(DOMAIN, SERVICE_WRITE_REGISTER, write_register, + schema=SERVICE_WRITE_REGISTER_SCHEMA) def write_register(service): """Write modbus registers.""" @@ -128,39 +152,44 @@ class ModbusHub(object): def read_coils(self, unit, address, count): """Read coils.""" with self._lock: + kwargs = {'unit': unit} if unit else {} return self._client.read_coils( address, count, - unit=unit) + **kwargs) def read_holding_registers(self, unit, address, count): """Read holding registers.""" with self._lock: + kwargs = {'unit': unit} if unit else {} return self._client.read_holding_registers( address, count, - unit=unit) + **kwargs) def write_coil(self, unit, address, value): """Write coil.""" with self._lock: + kwargs = {'unit': unit} if unit else {} self._client.write_coil( address, value, - unit=unit) + **kwargs) def write_register(self, unit, address, value): """Write register.""" with self._lock: + kwargs = {'unit': unit} if unit else {} self._client.write_register( address, value, - unit=unit) + **kwargs) def write_registers(self, unit, address, values): """Write registers.""" with self._lock: + kwargs = {'unit': unit} if unit else {} self._client.write_registers( address, values, - unit=unit) + **kwargs) diff --git a/homeassistant/components/sensor/modbus.py b/homeassistant/components/sensor/modbus.py index 063c1dc8600..da208492448 100644 --- a/homeassistant/components/sensor/modbus.py +++ b/homeassistant/components/sensor/modbus.py @@ -1,100 +1,80 @@ """ -Support for Modbus sensors. +Support for Modbus Register sensors. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/sensor.modbus/ """ import logging +import voluptuous as vol import homeassistant.components.modbus as modbus from homeassistant.const import ( - STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT) + CONF_NAME, CONF_OFFSET, CONF_UNIT_OF_MEASUREMENT) from homeassistant.helpers.entity import Entity +from homeassistant.helpers import config_validation as cv +from homeassistant.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['modbus'] +CONF_COUNT = "count" +CONF_PRECISION = "precision" +CONF_REGISTER = "register" +CONF_REGISTERS = "registers" +CONF_SCALE = "scale" +CONF_SLAVE = "slave" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_REGISTERS): [{ + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_REGISTER): cv.positive_int, + vol.Optional(CONF_COUNT, default=1): cv.positive_int, + vol.Optional(CONF_OFFSET, default=0): vol.Coerce(float), + vol.Optional(CONF_PRECISION, default=0): cv.positive_int, + vol.Optional(CONF_SCALE, default=1): vol.Coerce(float), + vol.Optional(CONF_SLAVE): cv.positive_int, + vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string + }] +}) + def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Modbus devices.""" + """Setup Modbus sensors.""" sensors = [] - slave = config.get("slave", None) - if modbus.TYPE == "serial" and not slave: - _LOGGER.error("No slave number provided for serial Modbus") - return False - registers = config.get("registers") - if registers: - for regnum, register in registers.items(): - if register.get("name"): - sensors.append( - ModbusSensor(register.get("name"), - slave, - regnum, - None, - register.get("unit"), - scale=register.get("scale", 1), - offset=register.get("offset", 0), - precision=register.get("precision", 0))) - if register.get("bits"): - bits = register.get("bits") - for bitnum, bit in bits.items(): - if bit.get("name"): - sensors.append(ModbusSensor(bit.get("name"), - slave, - regnum, - bitnum)) - coils = config.get("coils") - if coils: - for coilnum, coil in coils.items(): - sensors.append(ModbusSensor(coil.get("name"), - slave, - coilnum, - coil=True)) - + for register in config.get(CONF_REGISTERS): + sensors.append(ModbusRegisterSensor( + register.get(CONF_NAME), + register.get(CONF_SLAVE), + register.get(CONF_REGISTER), + register.get(CONF_UNIT_OF_MEASUREMENT), + register.get(CONF_COUNT), + register.get(CONF_SCALE), + register.get(CONF_OFFSET), + register.get(CONF_PRECISION))) add_devices(sensors) -class ModbusSensor(Entity): - """Representation of a Modbus Sensor.""" +class ModbusRegisterSensor(Entity): + """Modbus resgister sensor.""" - # pylint: disable=too-many-arguments, too-many-instance-attributes - def __init__(self, name, slave, register, bit=None, unit=None, coil=False, - scale=1, offset=0, precision=0): - """Initialize the sensor.""" + # pylint: disable=too-many-instance-attributes, too-many-arguments + def __init__(self, name, slave, register, unit_of_measurement, count, + scale, offset, precision): + """Initialize the modbus register sensor.""" self._name = name - self.slave = int(slave) if slave else 1 - self.register = int(register) - self.bit = int(bit) if bit else None - self._value = None - self._unit = unit - self._coil = coil + self._slave = int(slave) if slave else None + self._register = int(register) + self._unit_of_measurement = unit_of_measurement + self._count = int(count) self._scale = scale self._offset = offset self._precision = precision - - def __str__(self): - """Return the name and the state of the sensor.""" - return "%s: %s" % (self.name, self.state) - - @property - def should_poll(self): - """Polling needed.""" - return True - - @property - def unique_id(self): - """Return a unique id.""" - return "MODBUS-SENSOR-{}-{}-{}".format(self.slave, - self.register, - self.bit) + self._value = None @property def state(self): """Return the state of the sensor.""" - if self.bit: - return STATE_ON if self._value else STATE_OFF - else: - return self._value + return self._value @property def name(self): @@ -103,28 +83,18 @@ class ModbusSensor(Entity): @property def unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - if self._unit == "C": - return TEMP_CELSIUS - elif self._unit == "F": - return TEMP_FAHRENHEIT - else: - return self._unit + """Return the unit of measurement.""" + return self._unit_of_measurement def update(self): """Update the state of the sensor.""" - if self._coil: - result = modbus.HUB.read_coils(self.slave, self.register, 1) - self._value = result.bits[0] - else: - result = modbus.HUB.read_holding_registers( - self.slave, self.register, 1) - val = 0 - for i, res in enumerate(result.registers): - val += res * (2**(i*16)) - if self.bit: - self._value = val & (0x0001 << self.bit) - else: - self._value = format( - self._scale * val + self._offset, - ".{}f".format(self._precision)) + result = modbus.HUB.read_holding_registers( + self._slave, + self._register, + self._count) + val = 0 + for i, res in enumerate(result.registers): + val += res * (2**(i*16)) + self._value = format( + self._scale * val + self._offset, + ".{}f".format(self._precision)) diff --git a/homeassistant/components/switch/modbus.py b/homeassistant/components/switch/modbus.py index 2ae0c74991d..0d23d8927ea 100644 --- a/homeassistant/components/switch/modbus.py +++ b/homeassistant/components/switch/modbus.py @@ -5,74 +5,51 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.modbus/ """ import logging +import voluptuous as vol import homeassistant.components.modbus as modbus +from homeassistant.const import CONF_NAME from homeassistant.helpers.entity import ToggleEntity +from homeassistant.helpers import config_validation as cv +from homeassistant.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['modbus'] +CONF_COIL = "coil" +CONF_COILS = "coils" +CONF_SLAVE = "slave" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_COILS): [{ + vol.Required(CONF_COIL): cv.positive_int, + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_SLAVE): cv.positive_int, + }] +}) + def setup_platform(hass, config, add_devices, discovery_info=None): """Read configuration and create Modbus devices.""" switches = [] - slave = config.get("slave", None) - if modbus.TYPE == "serial" and not slave: - _LOGGER.error("No slave number provided for serial Modbus") - return False - registers = config.get("registers") - if registers: - for regnum, register in registers.items(): - bits = register.get("bits") - for bitnum, bit in bits.items(): - if bit.get("name"): - switches.append(ModbusSwitch(bit.get("name"), - slave, - regnum, - bitnum)) - coils = config.get("coils") - if coils: - for coilnum, coil in coils.items(): - switches.append(ModbusSwitch(coil.get("name"), - slave, - coilnum, - 0, - coil=True)) + for coil in config.get("coils"): + switches.append(ModbusCoilSwitch( + coil.get(CONF_NAME), + coil.get(CONF_SLAVE), + coil.get(CONF_COIL))) add_devices(switches) -class ModbusSwitch(ToggleEntity): +class ModbusCoilSwitch(ToggleEntity): """Representation of a Modbus switch.""" # pylint: disable=too-many-arguments - def __init__(self, name, slave, register, bit, coil=False): + def __init__(self, name, slave, coil): """Initialize the switch.""" self._name = name - self.slave = int(slave) if slave else 1 - self.register = int(register) - self.bit = int(bit) - self._coil = coil + self._slave = int(slave) if slave else None + self._coil = int(coil) self._is_on = None - self.register_value = None - - def __str__(self): - """String representation of Modbus switch.""" - return "%s: %s" % (self.name, self.state) - - @property - def should_poll(self): - """Poling needed. - - Slaves are not allowed to initiate communication on Modbus networks. - """ - return True - - @property - def unique_id(self): - """Return a unique ID.""" - return "MODBUS-SWITCH-{}-{}-{}".format(self.slave, - self.register, - self.bit) @property def is_on(self): @@ -86,39 +63,13 @@ class ModbusSwitch(ToggleEntity): def turn_on(self, **kwargs): """Set switch on.""" - if self.register_value is None: - self.update() - - if self._coil: - modbus.HUB.write_coil(self.slave, self.register, True) - else: - val = self.register_value | (0x0001 << self.bit) - modbus.HUB.write_register(self.slave, self.register, val) + modbus.HUB.write_coil(self._slave, self._coil, True) def turn_off(self, **kwargs): """Set switch off.""" - if self.register_value is None: - self.update() - - if self._coil: - modbus.HUB.write_coil(self.slave, self.register, False) - else: - val = self.register_value & ~(0x0001 << self.bit) - modbus.HUB.write_register(self.slave, self.register, val) + modbus.HUB.write_coil(self._slave, self._coil, False) def update(self): """Update the state of the switch.""" - if self._coil: - result = modbus.HUB.read_coils(self.slave, self.register, 1) - self.register_value = result.bits[0] - self._is_on = self.register_value - else: - result = modbus.HUB.read_holding_registers( - self.slave, - self.register, - 1) - val = 0 - for i, res in enumerate(result.registers): - val += res * (2**(i*16)) - self.register_value = val - self._is_on = (val & (0x0001 << self.bit) > 0) + result = modbus.HUB.read_coils(self._slave, self._coil, 1) + self._is_on = bool(result.bits[0]) From de2eed3c9ffb9ba61359547a87527a4c24f64713 Mon Sep 17 00:00:00 2001 From: Eric Clymer Date: Tue, 13 Sep 2016 16:22:55 -0500 Subject: [PATCH 046/162] Fix saving push_notification.conf as suggested by @robbiet480 (#3382) --- homeassistant/components/notify/html5.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/notify/html5.py b/homeassistant/components/notify/html5.py index 103ccc7885b..6ee60512631 100644 --- a/homeassistant/components/notify/html5.py +++ b/homeassistant/components/notify/html5.py @@ -144,7 +144,7 @@ def _save_config(filename, config): """Save configuration.""" try: with open(filename, 'w') as fdesc: - fdesc.write(json.dumps(config, indent=4, sort_keys=True)) + fdesc.write(json.dumps(config)) except (IOError, TypeError) as error: _LOGGER.error('Saving config file failed: %s', error) return False From 1697a8c774798719f9747f9f6ef4a1cd890d153e Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 14 Sep 2016 00:11:50 +0200 Subject: [PATCH 047/162] SleepIQ component with sensor and binary sensor platforms (#3390) Original from #2949 --- .../components/binary_sensor/sleepiq.py | 59 ++++++++ homeassistant/components/sensor/sleepiq.py | 58 ++++++++ homeassistant/components/sleepiq.py | 130 ++++++++++++++++++ requirements_all.txt | 3 + .../components/binary_sensor/test_sleepiq.py | 50 +++++++ tests/components/sensor/test_sleepiq.py | 50 +++++++ tests/components/test_sleepiq.py | 75 ++++++++++ tests/fixtures/sleepiq-bed.json | 28 ++++ tests/fixtures/sleepiq-familystatus.json | 24 ++++ tests/fixtures/sleepiq-login-failed.json | 1 + tests/fixtures/sleepiq-login.json | 7 + tests/fixtures/sleepiq-sleeper.json | 55 ++++++++ 12 files changed, 540 insertions(+) create mode 100644 homeassistant/components/binary_sensor/sleepiq.py create mode 100644 homeassistant/components/sensor/sleepiq.py create mode 100644 homeassistant/components/sleepiq.py create mode 100644 tests/components/binary_sensor/test_sleepiq.py create mode 100644 tests/components/sensor/test_sleepiq.py create mode 100644 tests/components/test_sleepiq.py create mode 100644 tests/fixtures/sleepiq-bed.json create mode 100644 tests/fixtures/sleepiq-familystatus.json create mode 100644 tests/fixtures/sleepiq-login-failed.json create mode 100644 tests/fixtures/sleepiq-login.json create mode 100644 tests/fixtures/sleepiq-sleeper.json diff --git a/homeassistant/components/binary_sensor/sleepiq.py b/homeassistant/components/binary_sensor/sleepiq.py new file mode 100644 index 00000000000..c842d0c9be9 --- /dev/null +++ b/homeassistant/components/binary_sensor/sleepiq.py @@ -0,0 +1,59 @@ +""" +Support for SleepIQ sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.sleepiq/ +""" +from homeassistant.components import sleepiq +from homeassistant.components.binary_sensor import BinarySensorDevice + +DEPENDENCIES = ['sleepiq'] + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the SleepIQ sensors.""" + if discovery_info is None: + return + + data = sleepiq.DATA + data.update() + + dev = list() + for bed_id, _ in data.beds.items(): + for side in sleepiq.SIDES: + dev.append(IsInBedBinarySensor( + data, + bed_id, + side)) + add_devices(dev) + + +# pylint: disable=too-many-instance-attributes +class IsInBedBinarySensor(sleepiq.SleepIQSensor, BinarySensorDevice): + """Implementation of a SleepIQ presence sensor.""" + + def __init__(self, sleepiq_data, bed_id, side): + """Initialize the sensor.""" + sleepiq.SleepIQSensor.__init__(self, + sleepiq_data, + bed_id, + side) + self.type = sleepiq.IS_IN_BED + self._state = None + self._name = sleepiq.SENSOR_TYPES[self.type] + self.update() + + @property + def is_on(self): + """Return the status of the sensor.""" + return self._state is True + + @property + def sensor_class(self): + """Return the class of this sensor.""" + return "occupancy" + + def update(self): + """Get the latest data from SleepIQ and updates the states.""" + sleepiq.SleepIQSensor.update(self) + self._state = self.side.is_in_bed diff --git a/homeassistant/components/sensor/sleepiq.py b/homeassistant/components/sensor/sleepiq.py new file mode 100644 index 00000000000..ed4bf26ce07 --- /dev/null +++ b/homeassistant/components/sensor/sleepiq.py @@ -0,0 +1,58 @@ +""" +Support for SleepIQ sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.sleepiq/ +""" +from homeassistant.components import sleepiq + +DEPENDENCIES = ['sleepiq'] +ICON = 'mdi:hotel' + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the SleepIQ sensors.""" + if discovery_info is None: + return + + data = sleepiq.DATA + data.update() + + dev = list() + for bed_id, _ in data.beds.items(): + for side in sleepiq.SIDES: + dev.append(SleepNumberSensor(data, bed_id, side)) + add_devices(dev) + + +# pylint: disable=too-few-public-methods, too-many-instance-attributes +class SleepNumberSensor(sleepiq.SleepIQSensor): + """Implementation of a SleepIQ sensor.""" + + def __init__(self, sleepiq_data, bed_id, side): + """Initialize the sensor.""" + sleepiq.SleepIQSensor.__init__(self, + sleepiq_data, + bed_id, + side) + + self._state = None + self.type = sleepiq.SLEEP_NUMBER + self._name = sleepiq.SENSOR_TYPES[self.type] + + self.update() + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return ICON + + def update(self): + """Get the latest data from SleepIQ and updates the states.""" + sleepiq.SleepIQSensor.update(self) + self._state = self.side.sleep_number diff --git a/homeassistant/components/sleepiq.py b/homeassistant/components/sleepiq.py new file mode 100644 index 00000000000..cabd1f0050e --- /dev/null +++ b/homeassistant/components/sleepiq.py @@ -0,0 +1,130 @@ +""" +Support for SleepIQ from SleepNumber. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sleepiq/ +""" + +import logging +from datetime import timedelta + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers import discovery +from homeassistant.helpers.entity import Entity +from homeassistant.const import CONF_USERNAME, CONF_PASSWORD +from homeassistant.util import Throttle +from requests.exceptions import HTTPError + +DOMAIN = 'sleepiq' + +REQUIREMENTS = ['sleepyq==0.6'] + +# Return cached results if last scan was less then this time ago. +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) + +IS_IN_BED = 'is_in_bed' +SLEEP_NUMBER = 'sleep_number' +SENSOR_TYPES = { + SLEEP_NUMBER: 'SleepNumber', + IS_IN_BED: 'Is In Bed', +} + +LEFT = 'left' +RIGHT = 'right' +SIDES = [LEFT, RIGHT] + +_LOGGER = logging.getLogger(__name__) + +DATA = None + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Setup SleepIQ. + + Will automatically load sensor components to support + devices discovered on the account. + """ + # pylint: disable=global-statement + global DATA + + from sleepyq import Sleepyq + username = config[DOMAIN][CONF_USERNAME] + password = config[DOMAIN][CONF_PASSWORD] + client = Sleepyq(username, password) + try: + DATA = SleepIQData(client) + DATA.update() + except HTTPError: + message = """ + SleepIQ failed to login, double check your username and password" + """ + _LOGGER.error(message) + return False + + discovery.load_platform(hass, 'sensor', DOMAIN, {}, config) + discovery.load_platform(hass, 'binary_sensor', DOMAIN, {}, config) + + return True + + +# pylint: disable=too-few-public-methods +class SleepIQData(object): + """Gets the latest data from SleepIQ.""" + + def __init__(self, client): + """Initialize the data object.""" + self._client = client + self.beds = {} + + self.update() + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Get the latest data from SleepIQ.""" + self._client.login() + beds = self._client.beds_with_sleeper_status() + + self.beds = {bed.bed_id: bed for bed in beds} + + +# pylint: disable=too-few-public-methods, too-many-instance-attributes +class SleepIQSensor(Entity): + """Implementation of a SleepIQ sensor.""" + + def __init__(self, sleepiq_data, bed_id, side): + """Initialize the sensor.""" + self._bed_id = bed_id + self._side = side + self.sleepiq_data = sleepiq_data + self.side = None + self.bed = None + + # added by subclass + self._name = None + self.type = None + + @property + def name(self): + """Return the name of the sensor.""" + return 'SleepNumber {} {} {}'.format(self.bed.name, + self.side.sleeper.first_name, + self._name) + + def update(self): + """Get the latest data from SleepIQ and updates the states.""" + # Call the API for new sleepiq data. Each sensor will re-trigger this + # same exact call, but thats fine. We cache results for a short period + # of time to prevent hitting API limits. + self.sleepiq_data.update() + + self.bed = self.sleepiq_data.beds[self._bed_id] + self.side = getattr(self.bed, self._side) diff --git a/requirements_all.txt b/requirements_all.txt index 61507807534..97e65bc8edd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -433,6 +433,9 @@ slacker==0.9.25 # homeassistant.components.notify.xmpp sleekxmpp==1.3.1 +# homeassistant.components.sleepiq +sleepyq==0.6 + # homeassistant.components.media_player.snapcast snapcast==1.2.2 diff --git a/tests/components/binary_sensor/test_sleepiq.py b/tests/components/binary_sensor/test_sleepiq.py new file mode 100644 index 00000000000..1c270c36e29 --- /dev/null +++ b/tests/components/binary_sensor/test_sleepiq.py @@ -0,0 +1,50 @@ +"""The tests for SleepIQ binary_sensor platform.""" +import unittest +from unittest.mock import MagicMock + +import requests_mock + +from homeassistant import core as ha +from homeassistant.components.binary_sensor import sleepiq + +from tests.components.test_sleepiq import mock_responses + + +class TestSleepIQBinarySensorSetup(unittest.TestCase): + """Tests the SleepIQ Binary Sensor platform.""" + + DEVICES = [] + + def add_devices(self, devices): + """Mock add devices.""" + for device in devices: + self.DEVICES.append(device) + + def setUp(self): + """Initialize values for this testcase class.""" + self.hass = ha.HomeAssistant() + self.username = 'foo' + self.password = 'bar' + self.config = { + 'username': self.username, + 'password': self.password, + } + + @requests_mock.Mocker() + def test_setup(self, mock): + """Test for succesfully setting up the SleepIQ platform.""" + mock_responses(mock) + + sleepiq.setup_platform(self.hass, + self.config, + self.add_devices, + MagicMock()) + self.assertEqual(2, len(self.DEVICES)) + + left_side = self.DEVICES[1] + self.assertEqual('SleepNumber ILE Test1 Is In Bed', left_side.name) + self.assertEqual('on', left_side.state) + + right_side = self.DEVICES[0] + self.assertEqual('SleepNumber ILE Test2 Is In Bed', right_side.name) + self.assertEqual('off', right_side.state) diff --git a/tests/components/sensor/test_sleepiq.py b/tests/components/sensor/test_sleepiq.py new file mode 100644 index 00000000000..5802781ae75 --- /dev/null +++ b/tests/components/sensor/test_sleepiq.py @@ -0,0 +1,50 @@ +"""The tests for SleepIQ sensor platform.""" +import unittest +from unittest.mock import MagicMock + +import requests_mock + +from homeassistant import core as ha +from homeassistant.components.sensor import sleepiq + +from tests.components.test_sleepiq import mock_responses + + +class TestSleepIQSensorSetup(unittest.TestCase): + """Tests the SleepIQ Sensor platform.""" + + DEVICES = [] + + def add_devices(self, devices): + """Mock add devices.""" + for device in devices: + self.DEVICES.append(device) + + def setUp(self): + """Initialize values for this testcase class.""" + self.hass = ha.HomeAssistant() + self.username = 'foo' + self.password = 'bar' + self.config = { + 'username': self.username, + 'password': self.password, + } + + @requests_mock.Mocker() + def test_setup(self, mock): + """Test for succesfully setting up the SleepIQ platform.""" + mock_responses(mock) + + sleepiq.setup_platform(self.hass, + self.config, + self.add_devices, + MagicMock()) + self.assertEqual(2, len(self.DEVICES)) + + left_side = self.DEVICES[1] + self.assertEqual('SleepNumber ILE Test1 SleepNumber', left_side.name) + self.assertEqual(40, left_side.state) + + right_side = self.DEVICES[0] + self.assertEqual('SleepNumber ILE Test2 SleepNumber', right_side.name) + self.assertEqual(80, right_side.state) diff --git a/tests/components/test_sleepiq.py b/tests/components/test_sleepiq.py new file mode 100644 index 00000000000..ceccefde778 --- /dev/null +++ b/tests/components/test_sleepiq.py @@ -0,0 +1,75 @@ +"""The tests for the SleepIQ component.""" +import unittest +import requests_mock + +from homeassistant import bootstrap +import homeassistant.components.sleepiq as sleepiq + +from tests.common import load_fixture, get_test_home_assistant + + +def mock_responses(mock): + base_url = 'https://api.sleepiq.sleepnumber.com/rest/' + mock.put( + base_url + 'login', + text=load_fixture('sleepiq-login.json')) + mock.get( + base_url + 'bed?_k=0987', + text=load_fixture('sleepiq-bed.json')) + mock.get( + base_url + 'sleeper?_k=0987', + text=load_fixture('sleepiq-sleeper.json')) + mock.get( + base_url + 'bed/familyStatus?_k=0987', + text=load_fixture('sleepiq-familystatus.json')) + + +class TestSleepIQ(unittest.TestCase): + """Tests the SleepIQ component.""" + + def setUp(self): + """Initialize values for this testcase class.""" + self.hass = get_test_home_assistant() + self.username = 'foo' + self.password = 'bar' + self.config = { + 'sleepiq': { + 'username': self.username, + 'password': self.password, + } + } + + def tearDown(self): # pylint: disable=invalid-name + """Stop everything that was started.""" + self.hass.stop() + + @requests_mock.Mocker() + def test_setup(self, mock): + """Test the setup.""" + mock_responses(mock) + + response = sleepiq.setup(self.hass, self.config) + self.assertTrue(response) + + @requests_mock.Mocker() + def test_setup_login_failed(self, mock): + """Test the setup if a bad username or password is given.""" + mock.put('https://api.sleepiq.sleepnumber.com/rest/login', + status_code=401, + json=load_fixture('sleepiq-login-failed.json')) + + response = sleepiq.setup(self.hass, self.config) + self.assertFalse(response) + + def test_setup_component_no_login(self): + """Test the setup when no login is configured.""" + conf = self.config.copy() + del conf['sleepiq']['username'] + assert not bootstrap._setup_component(self.hass, sleepiq.DOMAIN, conf) + + def test_setup_component_no_password(self): + """Test the setup when no password is configured.""" + conf = self.config.copy() + del conf['sleepiq']['password'] + + assert not bootstrap._setup_component(self.hass, sleepiq.DOMAIN, conf) diff --git a/tests/fixtures/sleepiq-bed.json b/tests/fixtures/sleepiq-bed.json new file mode 100644 index 00000000000..d03fb6e329f --- /dev/null +++ b/tests/fixtures/sleepiq-bed.json @@ -0,0 +1,28 @@ +{ + "beds" : [ + { + "dualSleep" : true, + "base" : "FlexFit", + "sku" : "AILE", + "model" : "ILE", + "size" : "KING", + "isKidsBed" : false, + "sleeperRightId" : "-80", + "accountId" : "-32", + "bedId" : "-31", + "registrationDate" : "2016-07-22T14:00:58Z", + "serial" : null, + "reference" : "95000794555-1", + "macAddress" : "CD13A384BA51", + "version" : null, + "purchaseDate" : "2016-06-22T00:00:00Z", + "sleeperLeftId" : "-92", + "zipcode" : "12345", + "returnRequestStatus" : 0, + "name" : "ILE", + "status" : 1, + "timezone" : "US/Eastern" + } + ] +} + diff --git a/tests/fixtures/sleepiq-familystatus.json b/tests/fixtures/sleepiq-familystatus.json new file mode 100644 index 00000000000..0c93d74d35f --- /dev/null +++ b/tests/fixtures/sleepiq-familystatus.json @@ -0,0 +1,24 @@ +{ + "beds" : [ + { + "bedId" : "-31", + "rightSide" : { + "alertId" : 0, + "lastLink" : "00:00:00", + "isInBed" : true, + "sleepNumber" : 40, + "alertDetailedMessage" : "No Alert", + "pressure" : -16 + }, + "status" : 1, + "leftSide" : { + "alertId" : 0, + "lastLink" : "00:00:00", + "sleepNumber" : 80, + "alertDetailedMessage" : "No Alert", + "isInBed" : false, + "pressure" : 2191 + } + } + ] +} diff --git a/tests/fixtures/sleepiq-login-failed.json b/tests/fixtures/sleepiq-login-failed.json new file mode 100644 index 00000000000..227609154b5 --- /dev/null +++ b/tests/fixtures/sleepiq-login-failed.json @@ -0,0 +1 @@ +{"Error":{"Code":401,"Message":"Authentication token of type [class org.apache.shiro.authc.UsernamePasswordToken] could not be authenticated by any configured realms. Please ensure that at least one realm can authenticate these tokens."}} diff --git a/tests/fixtures/sleepiq-login.json b/tests/fixtures/sleepiq-login.json new file mode 100644 index 00000000000..fdd8943574f --- /dev/null +++ b/tests/fixtures/sleepiq-login.json @@ -0,0 +1,7 @@ +{ + "edpLoginStatus" : 200, + "userId" : "-42", + "registrationState" : 13, + "key" : "0987", + "edpLoginMessage" : "not used" +} diff --git a/tests/fixtures/sleepiq-sleeper.json b/tests/fixtures/sleepiq-sleeper.json new file mode 100644 index 00000000000..4089e1b1d95 --- /dev/null +++ b/tests/fixtures/sleepiq-sleeper.json @@ -0,0 +1,55 @@ +{ + "sleepers" : [ + { + "timezone" : "US/Eastern", + "firstName" : "Test1", + "weight" : 150, + "birthMonth" : 12, + "birthYear" : "1990", + "active" : true, + "lastLogin" : "2016-08-26 21:43:27 CDT", + "side" : 1, + "accountId" : "-32", + "height" : 60, + "bedId" : "-31", + "username" : "test1@example.com", + "sleeperId" : "-80", + "avatar" : "", + "emailValidated" : true, + "licenseVersion" : 6, + "duration" : null, + "email" : "test1@example.com", + "isAccountOwner" : true, + "sleepGoal" : 480, + "zipCode" : "12345", + "isChild" : false, + "isMale" : true + }, + { + "email" : "test2@example.com", + "duration" : null, + "emailValidated" : true, + "licenseVersion" : 5, + "isChild" : false, + "isMale" : false, + "zipCode" : "12345", + "isAccountOwner" : false, + "sleepGoal" : 480, + "side" : 0, + "lastLogin" : "2016-07-17 15:37:30 CDT", + "birthMonth" : 1, + "birthYear" : "1991", + "active" : true, + "weight" : 151, + "firstName" : "Test2", + "timezone" : "US/Eastern", + "avatar" : "", + "username" : "test2@example.com", + "sleeperId" : "-92", + "bedId" : "-31", + "height" : 65, + "accountId" : "-32" + } + ] +} + From 165362da0c9ecc4aac5c12eb5e0ed0d378c34455 Mon Sep 17 00:00:00 2001 From: wokar Date: Wed, 14 Sep 2016 03:15:19 +0200 Subject: [PATCH 048/162] fixed link constructor to show icon again (#3388) --- homeassistant/components/weblink.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/weblink.py b/homeassistant/components/weblink.py index df9dcef9ac1..9a1f13f57a8 100644 --- a/homeassistant/components/weblink.py +++ b/homeassistant/components/weblink.py @@ -38,7 +38,7 @@ def setup(hass, config): for link in links.get(CONF_ENTITIES): Link(hass, link.get(CONF_NAME), link.get(CONF_URL), - link.get(CONF_URL)) + link.get(CONF_ICON)) return True From afc527ea552c6c5bfc27b9eb76e3769a1325dc05 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Wed, 14 Sep 2016 03:17:51 +0200 Subject: [PATCH 049/162] Fix tests (#3387) --- homeassistant/components/recorder/__init__.py | 22 ++++++++----------- tests/components/recorder/test_init.py | 15 ++++++------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 70a923f6f30..e786ffeccc3 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -98,8 +98,7 @@ def run_information(point_in_time: Optional[datetime]=None): def setup(hass: HomeAssistant, config: ConfigType) -> bool: """Setup the recorder.""" - # pylint: disable=global-statement - global _INSTANCE + global _INSTANCE # pylint: disable=global-statement if _INSTANCE is not None: _LOGGER.error('Only a single instance allowed.') @@ -164,7 +163,6 @@ class Recorder(threading.Thread): self.hass = hass self.purge_days = purge_days self.queue = queue.Queue() # type: Any - self.quit_object = object() self.recording_start = dt_util.utcnow() self.db_url = uri self.db_ready = threading.Event() @@ -205,12 +203,9 @@ class Recorder(threading.Thread): while True: event = self.queue.get() - if event == self.quit_object: + if event is None: self._close_run() self._close_connection() - # pylint: disable=global-statement - global _INSTANCE - _INSTANCE = None self.queue.task_done() return @@ -238,8 +233,11 @@ class Recorder(threading.Thread): def shutdown(self, event): """Tell the recorder to shut down.""" - self.queue.put(self.quit_object) - self.queue.join() + global _INSTANCE # pylint: disable=global-statement + _INSTANCE = None + + self.queue.put(None) + self.join() def block_till_done(self): """Block till all events processed.""" @@ -251,8 +249,7 @@ class Recorder(threading.Thread): def _setup_connection(self): """Ensure database is ready to fly.""" - # pylint: disable=global-statement - global Session + global Session # pylint: disable=global-statement import homeassistant.components.recorder.models as models from sqlalchemy import create_engine @@ -275,8 +272,7 @@ class Recorder(threading.Thread): def _close_connection(self): """Close the connection.""" - # pylint: disable=global-statement - global Session + global Session # pylint: disable=global-statement self.engine.dispose() self.engine = None Session = None diff --git a/tests/components/recorder/test_init.py b/tests/components/recorder/test_init.py index 657e7401562..c261e5eedbd 100644 --- a/tests/components/recorder/test_init.py +++ b/tests/components/recorder/test_init.py @@ -3,11 +3,10 @@ import json from datetime import datetime, timedelta import unittest -from unittest.mock import patch from homeassistant.const import MATCH_ALL from homeassistant.components import recorder - +from homeassistant.bootstrap import _setup_component from tests.common import get_test_home_assistant @@ -17,19 +16,19 @@ class TestRecorder(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - db_uri = 'sqlite://' - with patch('homeassistant.core.Config.path', return_value=db_uri): - recorder.setup(self.hass, config={ - "recorder": { - "db_url": db_uri}}) + db_uri = 'sqlite://' # In memory DB + _setup_component(self.hass, recorder.DOMAIN, { + recorder.DOMAIN: {recorder.CONF_DB_URL: db_uri}}) self.hass.start() - recorder._INSTANCE.block_till_db_ready() + recorder._verify_instance() self.session = recorder.Session() recorder._INSTANCE.block_till_done() def tearDown(self): # pylint: disable=invalid-name """Stop everything that was started.""" + recorder._INSTANCE.shutdown(None) self.hass.stop() + assert recorder._INSTANCE is None def _add_test_states(self): """Add multiple states to the db for testing.""" From cb3ab1e873a224b3b49b84572c02c89932491ea9 Mon Sep 17 00:00:00 2001 From: David Baumann Date: Wed, 14 Sep 2016 03:21:43 +0200 Subject: [PATCH 050/162] Implement Sensor for KNX Platform (#2911) * Added Sensor Support for KNX Devices Added Sensor for KNX Group Addresses - Temperature - Wind Speed - Illuminance(LUX) Mostly to fetch from a KNX Wetterstation * Some pylint,flake8 fixes * Pydoc Fixes * Fix Coverage Ordering * Refactor KNX Sensor Refactor to Idea from @usul27 and added Minimum Maximum * Removed Measurement Untis from const.py Removed needed Measurement Units from const.py and add it to sensor\knx.py * Change .coveragerc * Add as Requested from @Teagan42 the new Type Names Additional add CONF_MINIMUM and CONF_MAXIMUM * Added Changes as Requested from @Teagan42 * Fixed the Merge Conflict, Hopefully i done it rigth :-) * Fixed Styling --- .coveragerc | 4 +- homeassistant/components/sensor/knx.py | 128 +++++++++++++++++++++++++ homeassistant/const.py | 5 + 3 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/sensor/knx.py diff --git a/.coveragerc b/.coveragerc index fadff7fe053..de90021bfb7 100644 --- a/.coveragerc +++ b/.coveragerc @@ -93,9 +93,7 @@ omit = homeassistant/components/*/pilight.py homeassistant/components/knx.py - homeassistant/components/switch/knx.py - homeassistant/components/binary_sensor/knx.py - homeassistant/components/thermostat/knx.py + homeassistant/components/*/knx.py homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/alarm_control_panel/nx584.py diff --git a/homeassistant/components/sensor/knx.py b/homeassistant/components/sensor/knx.py new file mode 100644 index 00000000000..cebd1397366 --- /dev/null +++ b/homeassistant/components/sensor/knx.py @@ -0,0 +1,128 @@ +""" +Sensors of a KNX Device. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/knx/ +""" +from homeassistant.const import (TEMP_CELSIUS, TEMPERATURE, CONF_TYPE, + ILLUMINANCE, SPEED_MS, CONF_MINIMUM, + CONF_MAXIMUM) +from homeassistant.components.knx import (KNXConfig, KNXGroupAddress) + + +DEPENDENCIES = ["knx"] + +# Speed units +SPEED_METERPERSECOND = "m/s" # type: str + +# Illuminance units +ILLUMINANCE_LUX = "lx" # type: str + +# Predefined Minimum, Maximum Values for Sensors +# Temperature as defined in KNX Standard 3.10 - 9.001 DPT_Value_Temp +KNX_TEMP_MIN = -273 +KNX_TEMP_MAX = 670760 + +# Luminance(LUX) as Defined in KNX Standard 3.10 - 9.004 DPT_Value_Lux +KNX_LUX_MIN = 0 +KNX_LUX_MAX = 670760 + +# Speed m/s as defined in KNX Standard 3.10 - 9.005 DPT_Value_Wsp +KNX_SPEED_MS_MIN = 0 +KNX_SPEED_MS_MAX = 670760 + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Setup the KNX Sensor platform.""" + # Add KNX Temperature Sensors + # KNX Datapoint 9.001 DPT_Value_Temp + if config[CONF_TYPE] == TEMPERATURE: + minimum_value, maximum_value = \ + update_and_define_min_max(config, KNX_TEMP_MIN, + KNX_TEMP_MAX) + + add_entities([ + KNXSensorFloatClass(hass, KNXConfig(config), TEMP_CELSIUS, + minimum_value, maximum_value) + ]) + + # Add KNX Speed Sensors(Like Wind Speed) + # KNX Datapoint 9.005 DPT_Value_Wsp + elif config[CONF_TYPE] == SPEED_MS: + minimum_value, maximum_value = \ + update_and_define_min_max(config, KNX_SPEED_MS_MIN, + KNX_SPEED_MS_MAX) + + add_entities([ + KNXSensorFloatClass(hass, KNXConfig(config), SPEED_METERPERSECOND, + minimum_value, maximum_value) + ]) + + # Add KNX Illuminance Sensors(Lux) + # KNX Datapoint 9.004 DPT_Value_Lux + elif config[CONF_TYPE] == ILLUMINANCE: + minimum_value, maximum_value = \ + update_and_define_min_max(config, KNX_LUX_MIN, KNX_LUX_MAX) + + add_entities([ + KNXSensorFloatClass(hass, KNXConfig(config), ILLUMINANCE_LUX, + minimum_value, maximum_value) + ]) + + +def update_and_define_min_max(config, minimum_default, + maximum_default): + """Function help determinate a min/max value defined in config.""" + minimum_value = minimum_default + maximum_value = maximum_default + if config.get(CONF_MINIMUM): + minimum_value = config.get(CONF_MINIMUM) + + if config.get(CONF_MAXIMUM): + maximum_value = config.get(CONF_MAXIMUM) + + return minimum_value, maximum_value + + +class KNXSensorBaseClass(): # pylint: disable=too-few-public-methods + """Sensor Base Class for all KNX Sensors.""" + + _unit_of_measurement = None + + @property + def cache(self): + """We don't want to cache any Sensor Value.""" + return False + + @property + def unit_of_measurement(self): + """Return the defined Unit of Measurement for the KNX Sensor.""" + return self._unit_of_measurement + + +class KNXSensorFloatClass(KNXGroupAddress, KNXSensorBaseClass): + """ + Base Implementation of a 2byte Floating Point KNX Telegram. + + Defined in KNX 3.7.2 - 3.10 + """ + + # pylint: disable=too-many-arguments + def __init__(self, hass, config, unit_of_measurement, minimum_sensor_value, + maximum_sensor_value): + """Initialize a KNX Float Sensor.""" + self._unit_of_measurement = unit_of_measurement + self._minimum_value = minimum_sensor_value + self._maximum_value = maximum_sensor_value + + KNXGroupAddress.__init__(self, hass, config) + + @property + def state(self): + """Return the Value of the KNX Sensor.""" + if self._data: + from knxip.conversion import knx2_to_float + value = knx2_to_float(self._data) + if self._minimum_value <= value <= self._maximum_value: + return value + return None diff --git a/homeassistant/const.py b/homeassistant/const.py index 4107fb65a24..872f60562cd 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -94,6 +94,8 @@ CONF_LATITUDE = 'latitude' CONF_LONGITUDE = 'longitude' CONF_MAC = 'mac' CONF_METHOD = 'method' +CONF_MINIMUM = 'minimum' +CONF_MAXIMUM = 'maximum' CONF_MONITORED_CONDITIONS = 'monitored_conditions' CONF_MONITORED_VARIABLES = 'monitored_variables' CONF_NAME = 'name' @@ -125,6 +127,7 @@ CONF_TIME_ZONE = 'time_zone' CONF_TIMEOUT = 'timeout' CONF_TOKEN = 'token' CONF_TRIGGER_TIME = 'trigger_time' +CONF_TYPE = 'type' CONF_UNIT_OF_MEASUREMENT = 'unit_of_measurement' CONF_UNIT_SYSTEM = 'unit_system' CONF_URL = 'url' @@ -374,3 +377,5 @@ LENGTH = 'length' # type: str MASS = 'mass' # type: str VOLUME = 'volume' # type: str TEMPERATURE = 'temperature' # type: str +SPEED_MS = 'speed_ms' # type: str +ILLUMINANCE = 'illuminance' # type: str From ac5647a30e7e54631e371af06d88370ff90a6af7 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 14 Sep 2016 03:22:30 +0200 Subject: [PATCH 051/162] Use voluptuous for statsd (#2928) * Migrate to voluptuous * Update tests --- homeassistant/components/statsd.py | 48 ++++++++++++++++-------------- tests/components/test_statsd.py | 31 +++++++++++++++++-- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/homeassistant/components/statsd.py b/homeassistant/components/statsd.py index f2db14cceb7..3a99a65fe6a 100644 --- a/homeassistant/components/statsd.py +++ b/homeassistant/components/statsd.py @@ -6,27 +6,36 @@ https://home-assistant.io/components/statsd/ """ import logging -import homeassistant.util as util -from homeassistant.const import EVENT_STATE_CHANGED +import voluptuous as vol + +from homeassistant.const import ( + CONF_HOST, CONF_PORT, CONF_PREFIX, EVENT_STATE_CHANGED) +import homeassistant.helpers.config_validation as cv from homeassistant.helpers import state as state_helper +REQUIREMENTS = ['statsd==3.2.1'] + _LOGGER = logging.getLogger(__name__) -DOMAIN = "statsd" -DEPENDENCIES = [] +CONF_ATTR = 'log_attributes' +CONF_RATE = 'rate' DEFAULT_HOST = 'localhost' DEFAULT_PORT = 8125 DEFAULT_PREFIX = 'hass' DEFAULT_RATE = 1 +DOMAIN = 'statsd' -REQUIREMENTS = ['statsd==3.2.1'] - -CONF_HOST = 'host' -CONF_PORT = 'port' -CONF_PREFIX = 'prefix' -CONF_RATE = 'rate' -CONF_ATTR = 'log_attributes' +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_ATTR, default=False): cv.boolean, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_PREFIX, default=DEFAULT_PREFIX): cv.string, + vol.Optional(CONF_RATE, default=DEFAULT_RATE): + vol.All(vol.Coerce(int), vol.Range(min=1)), + }), +}, extra=vol.ALLOW_EXTRA) def setup(hass, config): @@ -34,18 +43,13 @@ def setup(hass, config): import statsd conf = config[DOMAIN] + host = conf.get(CONF_HOST) + port = conf.get(CONF_PORT) + sample_rate = conf.get(CONF_RATE) + prefix = conf.get(CONF_PREFIX) + show_attribute_flag = conf.get(CONF_ATTR) - host = conf[CONF_HOST] - port = util.convert(conf.get(CONF_PORT), int, DEFAULT_PORT) - sample_rate = util.convert(conf.get(CONF_RATE), int, DEFAULT_RATE) - prefix = util.convert(conf.get(CONF_PREFIX), str, DEFAULT_PREFIX) - show_attribute_flag = conf.get(CONF_ATTR, False) - - statsd_client = statsd.StatsClient( - host=host, - port=port, - prefix=prefix - ) + statsd_client = statsd.StatsClient(host=host, port=port, prefix=prefix) def statsd_event_listener(event): """Listen for new messages on the bus and sends them to StatsD.""" diff --git a/tests/components/test_statsd.py b/tests/components/test_statsd.py index 6209f1986f5..e3e14794026 100644 --- a/tests/components/test_statsd.py +++ b/tests/components/test_statsd.py @@ -2,14 +2,29 @@ import unittest from unittest import mock +import voluptuous as vol + import homeassistant.core as ha import homeassistant.components.statsd as statsd -from homeassistant.const import STATE_ON, STATE_OFF, EVENT_STATE_CHANGED +from homeassistant.const import (STATE_ON, STATE_OFF, EVENT_STATE_CHANGED) class TestStatsd(unittest.TestCase): """Test the StatsD component.""" + def test_invalid_config(self): + """Test configuration with defaults.""" + config = { + 'statsd': { + 'host1': 'host1', + } + } + + with self.assertRaises(vol.Invalid): + statsd.CONFIG_SCHEMA(None) + with self.assertRaises(vol.Invalid): + statsd.CONFIG_SCHEMA(config) + @mock.patch('statsd.StatsClient') def test_statsd_setup_full(self, mock_connection): """Test setup with all data.""" @@ -40,12 +55,16 @@ class TestStatsd(unittest.TestCase): 'host': 'host', } } + + config['statsd'][statsd.CONF_PORT] = statsd.DEFAULT_PORT + config['statsd'][statsd.CONF_PREFIX] = statsd.DEFAULT_PREFIX + hass = mock.MagicMock() self.assertTrue(statsd.setup(hass, config)) mock_connection.assert_called_once_with( host='host', - port=statsd.DEFAULT_PORT, - prefix=statsd.DEFAULT_PREFIX) + port=8125, + prefix='hass') self.assertTrue(hass.bus.listen.called) @mock.patch('statsd.StatsClient') @@ -56,6 +75,9 @@ class TestStatsd(unittest.TestCase): 'host': 'host', } } + + config['statsd'][statsd.CONF_RATE] = statsd.DEFAULT_RATE + hass = mock.MagicMock() statsd.setup(hass, config) self.assertTrue(hass.bus.listen.called) @@ -94,6 +116,9 @@ class TestStatsd(unittest.TestCase): 'log_attributes': True } } + + config['statsd'][statsd.CONF_RATE] = statsd.DEFAULT_RATE + hass = mock.MagicMock() statsd.setup(hass, config) self.assertTrue(hass.bus.listen.called) From 8da85d7a917fede9ece7886d79903c730d4622bb Mon Sep 17 00:00:00 2001 From: Heiko Rothe Date: Wed, 14 Sep 2016 05:34:59 +0200 Subject: [PATCH 052/162] Added away timeout setting for idle devices (#3321) --- homeassistant/components/sensor/mqtt_room.py | 33 ++++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/sensor/mqtt_room.py b/homeassistant/components/sensor/mqtt_room.py index a640d1e5268..ba01c50cdd6 100644 --- a/homeassistant/components/sensor/mqtt_room.py +++ b/homeassistant/components/sensor/mqtt_room.py @@ -6,13 +6,14 @@ https://home-assistant.io/components/sensor.mqtt_room/ """ import logging import json +from datetime import timedelta import voluptuous as vol import homeassistant.components.mqtt as mqtt from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, STATE_UNKNOWN, CONF_TIMEOUT) + CONF_NAME, CONF_TIMEOUT) from homeassistant.components.mqtt import CONF_STATE_TOPIC import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -29,16 +30,21 @@ ATTR_ROOM = 'room' CONF_DEVICE_ID = 'device_id' CONF_ROOM = 'room' +CONF_AWAY_TIMEOUT = 'away_timeout' DEFAULT_NAME = 'Room Sensor' DEFAULT_TIMEOUT = 5 +DEFAULT_AWAY_TIMEOUT = 0 DEFAULT_TOPIC = 'room_presence' -DEPENDENCIES = ['mqtt'] + +STATE_AWAY = 'away' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_DEVICE_ID): cv.string, vol.Required(CONF_STATE_TOPIC, default=DEFAULT_TOPIC): cv.string, vol.Required(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + vol.Optional(CONF_AWAY_TIMEOUT, + default=DEFAULT_AWAY_TIMEOUT): cv.positive_int, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string }) @@ -56,7 +62,8 @@ def setup_platform(hass, config, add_devices, discovery_info=None): config.get(CONF_NAME), config.get(CONF_STATE_TOPIC), config.get(CONF_DEVICE_ID), - config.get(CONF_TIMEOUT) + config.get(CONF_TIMEOUT), + config.get(CONF_AWAY_TIMEOUT) )]) @@ -64,14 +71,18 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class MQTTRoomSensor(Entity): """Representation of a room sensor that is updated via MQTT.""" - def __init__(self, hass, name, state_topic, device_id, timeout): + def __init__(self, hass, name, state_topic, device_id, timeout, + consider_home): """Initialize the sensor.""" - self._state = STATE_UNKNOWN + self._state = STATE_AWAY self._hass = hass self._name = name self._state_topic = '{}{}'.format(state_topic, '/+') self._device_id = slugify(device_id).upper() self._timeout = timeout + self._consider_home = \ + timedelta(seconds=consider_home) if consider_home \ + else None self._distance = None self._updated = None @@ -109,11 +120,6 @@ class MQTTRoomSensor(Entity): mqtt.subscribe(hass, self._state_topic, message_received, 1) - @property - def should_poll(self): - """No polling needed.""" - return False - @property def name(self): """Return the name of the sensor.""" @@ -131,6 +137,13 @@ class MQTTRoomSensor(Entity): """Return the current room of the entity.""" return self._state + def update(self): + """Update the state for absent devices.""" + if self._updated \ + and self._consider_home \ + and dt.utcnow() - self._updated > self._consider_home: + self._state = STATE_AWAY + def _parse_update_data(topic, data): """Parse the room presence update.""" From 7528da455c33cfa9d7a4ef26bd0ddfcc3d0ee8f1 Mon Sep 17 00:00:00 2001 From: Rob Johnson Date: Tue, 13 Sep 2016 23:36:49 -0500 Subject: [PATCH 053/162] Vera Thermostat Support (#3247) * vera thermostat & climate support * disable abstract * code review changes * fix * fix build * remove old method --- homeassistant/components/climate/vera.py | 137 ++++++++++++++++++ .../components/thermostat/__init__.py | 2 +- homeassistant/components/vera.py | 12 +- requirements_all.txt | 2 +- 4 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 homeassistant/components/climate/vera.py diff --git a/homeassistant/components/climate/vera.py b/homeassistant/components/climate/vera.py new file mode 100644 index 00000000000..26d81e2b510 --- /dev/null +++ b/homeassistant/components/climate/vera.py @@ -0,0 +1,137 @@ +""" +Support for Vera thermostats. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.vera/ +""" +import logging + +from homeassistant.util import convert +from homeassistant.components.climate import ClimateDevice +from homeassistant.const import TEMP_FAHRENHEIT, ATTR_TEMPERATURE + +from homeassistant.components.vera import ( + VeraDevice, VERA_DEVICES, VERA_CONTROLLER) + +DEPENDENCIES = ['vera'] + +_LOGGER = logging.getLogger(__name__) + +OPERATION_LIST = ["Heat", "Cool", "Auto Changeover", "Off"] +FAN_OPERATION_LIST = ["On", "Auto", "Cycle"] + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """Find and return Vera thermostats.""" + add_devices_callback( + VeraThermostat(device, VERA_CONTROLLER) for + device in VERA_DEVICES['climate']) + + +# pylint: disable=abstract-method +class VeraThermostat(VeraDevice, ClimateDevice): + """Representation of a Vera Thermostat.""" + + def __init__(self, vera_device, controller): + """Initialize the Vera device.""" + VeraDevice.__init__(self, vera_device, controller) + + @property + def current_operation(self): + """Return current operation ie. heat, cool, idle.""" + mode = self.vera_device.get_hvac_mode() + if mode == "HeatOn": + return OPERATION_LIST[0] # heat + elif mode == "CoolOn": + return OPERATION_LIST[1] # cool + elif mode == "AutoChangeOver": + return OPERATION_LIST[2] # auto + elif mode == "Off": + return OPERATION_LIST[3] # off + return "Off" + + @property + def operation_list(self): + """List of available operation modes.""" + return OPERATION_LIST + + @property + def current_fan_mode(self): + """Return the fan setting.""" + mode = self.vera_device.get_fan_mode() + if mode == "ContinuousOn": + return FAN_OPERATION_LIST[0] # on + elif mode == "Auto": + return FAN_OPERATION_LIST[1] # auto + elif mode == "PeriodicOn": + return FAN_OPERATION_LIST[2] # cycle + return "Auto" + + @property + def fan_list(self): + """List of available fan modes.""" + return FAN_OPERATION_LIST + + def set_fan_mode(self, mode): + """Set new target temperature.""" + if mode == FAN_OPERATION_LIST[0]: + self.vera_device.fan_on() + elif mode == FAN_OPERATION_LIST[1]: + self.vera_device.fan_auto() + elif mode == FAN_OPERATION_LIST[2]: + return self.vera_device.fan_cycle() + + @property + def current_power_mwh(self): + """Current power usage in mWh.""" + power = self.vera_device.power + if power: + return convert(power, float, 0.0) * 1000 + + def update(self): + """Called by the vera device callback to update state.""" + self._state = self.vera_device.get_hvac_mode() + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return TEMP_FAHRENHEIT + + @property + def current_temperature(self): + """Return the current temperature.""" + return self.vera_device.get_current_temperature() + + @property + def operation(self): + """Return current operation ie. heat, cool, idle.""" + return self.vera_device.get_hvac_state() + + @property + def target_temperature(self): + """Return the temperature we try to reach.""" + return self.vera_device.get_current_goal_temperature() + + def set_temperature(self, **kwargs): + """Set new target temperatures.""" + if kwargs.get(ATTR_TEMPERATURE) is not None: + self.vera_device.set_temperature(kwargs.get(ATTR_TEMPERATURE)) + + def set_operation_mode(self, operation_mode): + """Set HVAC mode (auto, cool, heat, off).""" + if operation_mode == OPERATION_LIST[3]: # off + self.vera_device.turn_off() + elif operation_mode == OPERATION_LIST[2]: # auto + self.vera_device.turn_auto_on() + elif operation_mode == OPERATION_LIST[1]: # cool + self.vera_device.turn_cool_on() + elif operation_mode == OPERATION_LIST[0]: # heat + self.vera_device.turn_heat_on() + + def turn_fan_on(self): + """Turn fan on.""" + self.vera_device.fan_on() + + def turn_fan_off(self): + """Turn fan off.""" + self.vera_device.fan_auto() diff --git a/homeassistant/components/thermostat/__init__.py b/homeassistant/components/thermostat/__init__.py index a9169ce4756..52452ef0e59 100644 --- a/homeassistant/components/thermostat/__init__.py +++ b/homeassistant/components/thermostat/__init__.py @@ -34,7 +34,7 @@ SERVICE_SET_HVAC_MODE = "set_hvac_mode" STATE_HEAT = "heat" STATE_COOL = "cool" STATE_IDLE = "idle" -STATE_AUTO = 'auto' +STATE_AUTO = "auto" ATTR_CURRENT_TEMPERATURE = "current_temperature" ATTR_AWAY_MODE = "away_mode" diff --git a/homeassistant/components/vera.py b/homeassistant/components/vera.py index f6162f5582c..cfe2add1315 100644 --- a/homeassistant/components/vera.py +++ b/homeassistant/components/vera.py @@ -20,7 +20,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['pyvera==0.2.15'] +REQUIREMENTS = ['pyvera==0.2.16'] _LOGGER = logging.getLogger(__name__) @@ -42,10 +42,14 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Required(CONF_CONTROLLER): cv.url, vol.Optional(CONF_EXCLUDE, default=[]): VERA_ID_LIST_SCHEMA, - vol.Optional(CONF_LIGHTS, default=[]): VERA_ID_LIST_SCHEMA, + vol.Optional(CONF_LIGHTS, default=[]): VERA_ID_LIST_SCHEMA }), }, extra=vol.ALLOW_EXTRA) +VERA_COMPONENTS = [ + 'binary_sensor', 'sensor', 'light', 'switch', 'lock', 'climate' +] + # pylint: disable=unused-argument, too-many-function-args def setup(hass, base_config): @@ -83,7 +87,7 @@ def setup(hass, base_config): continue VERA_DEVICES[dev_type].append(device) - for component in 'binary_sensor', 'sensor', 'light', 'switch', 'lock': + for component in VERA_COMPONENTS: discovery.load_platform(hass, component, DOMAIN, {}, base_config) return True @@ -103,6 +107,8 @@ def map_vera_device(vera_device, remap): return 'switch' if isinstance(vera_device, veraApi.VeraLock): return 'lock' + if isinstance(vera_device, veraApi.VeraThermostat): + return 'climate' if isinstance(vera_device, veraApi.VeraSwitch): if vera_device.device_id in remap: return 'light' diff --git a/requirements_all.txt b/requirements_all.txt index 97e65bc8edd..deb6b96d356 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -400,7 +400,7 @@ python-wink==0.7.14 # pyuserinput==0.1.11 # homeassistant.components.vera -pyvera==0.2.15 +pyvera==0.2.16 # homeassistant.components.wemo pywemo==0.4.6 From 68def21615acccdfbaaff6b0855fcf980fc07983 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 14 Sep 2016 06:39:03 +0200 Subject: [PATCH 054/162] Use voluptuous for Yamaha receiver (#3210) * Migrate to voluptuous * Add missing configuration variables --- .../components/media_player/yamaha.py | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/media_player/yamaha.py b/homeassistant/components/media_player/yamaha.py index 819ffe9fed7..9cf8d3db8b5 100644 --- a/homeassistant/components/media_player/yamaha.py +++ b/homeassistant/components/media_player/yamaha.py @@ -6,10 +6,13 @@ https://home-assistant.io/components/media_player.yamaha/ """ import logging +import voluptuous as vol + from homeassistant.components.media_player import ( SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, - SUPPORT_SELECT_SOURCE, MediaPlayerDevice) -from homeassistant.const import STATE_OFF, STATE_ON + SUPPORT_SELECT_SOURCE, MediaPlayerDevice, PLATFORM_SCHEMA) +from homeassistant.const import (CONF_NAME, STATE_OFF, STATE_ON) +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['rxv==0.1.11'] @@ -21,16 +24,26 @@ SUPPORT_YAMAHA = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ CONF_SOURCE_NAMES = 'source_names' CONF_SOURCE_IGNORE = 'source_ignore' +DEFAULT_NAME = 'Yamaha Receiver' + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_SOURCE_IGNORE, default=[]): + vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_SOURCE_NAMES, default={}): {cv.string: cv.string}, +}) + def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Yamaha platform.""" import rxv - source_ignore = config.get(CONF_SOURCE_IGNORE, []) - source_names = config.get(CONF_SOURCE_NAMES, {}) + name = config.get(CONF_NAME) + source_ignore = config.get(CONF_SOURCE_IGNORE) + source_names = config.get(CONF_SOURCE_NAMES) add_devices( - YamahaDevice(config.get("name"), receiver, source_ignore, source_names) + YamahaDevice(name, receiver, source_ignore, source_names) for receiver in rxv.find()) @@ -66,8 +79,8 @@ class YamahaDevice(MediaPlayerDevice): self.build_source_list() current_source = self._receiver.input - self._current_source = self._source_names.get(current_source, - current_source) + self._current_source = self._source_names.get( + current_source, current_source) def build_source_list(self): """Build the source list.""" @@ -135,5 +148,4 @@ class YamahaDevice(MediaPlayerDevice): def select_source(self, source): """Select input source.""" - self._receiver.input = self._reverse_mapping.get(source, - source) + self._receiver.input = self._reverse_mapping.get(source, source) From 2c01a6744639c80ab078b55a3679cf01ff08136b Mon Sep 17 00:00:00 2001 From: Nolan Gilley Date: Wed, 14 Sep 2016 00:46:11 -0400 Subject: [PATCH 055/162] short sleep (#3115) --- homeassistant/components/media_player/gpmdp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/media_player/gpmdp.py b/homeassistant/components/media_player/gpmdp.py index 430db46bca2..6f7e7cb1e82 100644 --- a/homeassistant/components/media_player/gpmdp.py +++ b/homeassistant/components/media_player/gpmdp.py @@ -8,6 +8,7 @@ import logging import json import os import socket +import time import voluptuous as vol @@ -232,6 +233,7 @@ class GPMDP(MediaPlayerDevice): def update(self): """Get the latest details from the player.""" + time.sleep(1) playstate = self.send_gpmdp_msg('playback', 'getPlaybackState') if playstate is None: return From 0c7c85dbfedacd3f9a054843fb52d67c293d1b99 Mon Sep 17 00:00:00 2001 From: Open Home Automation Date: Wed, 14 Sep 2016 07:37:57 +0200 Subject: [PATCH 056/162] Miflora (#3053) * First version of the MiFlora sensor (not yet finished) * First workign version * Added some documentation Get name from sensor, if not defined * Ignore IOError * Added force_update option * Updated comments * Renamed fertility to conductivity (what it really is) * MiFlora library update * Updated helper files * Formatting * Fixed pylint errors * Removed default from monitored conditions * Removed KeyError handling as a KeyError should never be raised * Added a return when no data is received * emoved unnecessary return statement * Changed default name * Changes quotes and string operation ( @Teagan42 ) * - number of samples for median calculation is now configurable - set state to None if no data could be polled from sensor * Bugfix in library more logging * Fixed miflora version number --- .coveragerc | 1 + homeassistant/components/sensor/miflora.py | 151 +++++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 155 insertions(+) create mode 100644 homeassistant/components/sensor/miflora.py diff --git a/.coveragerc b/.coveragerc index de90021bfb7..fdaaf8a12ea 100644 --- a/.coveragerc +++ b/.coveragerc @@ -227,6 +227,7 @@ omit = homeassistant/components/sensor/linux_battery.py homeassistant/components/sensor/loopenergy.py homeassistant/components/sensor/mhz19.py + homeassistant/components/sensor/miflora.py homeassistant/components/sensor/mqtt_room.py homeassistant/components/sensor/neurio_energy.py homeassistant/components/sensor/nzbget.py diff --git a/homeassistant/components/sensor/miflora.py b/homeassistant/components/sensor/miflora.py new file mode 100644 index 00000000000..baa1c752c69 --- /dev/null +++ b/homeassistant/components/sensor/miflora.py @@ -0,0 +1,151 @@ +""" +Support for Xiaomi Mi Flora BLE plant sensor. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.miflora/ +""" +from datetime import timedelta +import logging + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.helpers.entity import Entity +import homeassistant.helpers.config_validation as cv +from homeassistant.util import Throttle +from homeassistant.const import CONF_MONITORED_CONDITIONS, CONF_NAME + + +REQUIREMENTS = ['miflora==0.1.6'] + +LOGGER = logging.getLogger(__name__) +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=900) +CONF_MAC = 'mac' +CONF_FORCE_UPDATE = 'force_update' +CONF_MEDIAN = 'median' +DEFAULT_NAME = 'Mi Flora' + +# Sensor types are defined like: Name, units +SENSOR_TYPES = { + 'temperature': ['Temperature', '°C'], + 'light': ['Light intensity', 'lux'], + 'moisture': ['Moisture', '%'], + 'conductivity': ['Conductivity', 'µS/cm'], +} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_MAC): cv.string, + vol.Required(CONF_MONITORED_CONDITIONS): + vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_MEDIAN, default=3): cv.positive_int, + vol.Optional(CONF_FORCE_UPDATE, default=False): cv.boolean, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the MiFlora sensor.""" + from miflora import miflora_poller + + poller = miflora_poller.MiFloraPoller(config.get(CONF_MAC)) + force_update = config.get(CONF_FORCE_UPDATE) + median = config.get(CONF_MEDIAN) + + devs = [] + + for parameter in config[CONF_MONITORED_CONDITIONS]: + name = SENSOR_TYPES[parameter][0] + unit = SENSOR_TYPES[parameter][1] + + prefix = config.get(CONF_NAME) + + if len(prefix) > 0: + name = "{} {}".format(prefix, name) + + devs.append(MiFloraSensor(poller, + parameter, + name, + unit, + force_update, + median)) + + add_devices(devs) + + +class MiFloraSensor(Entity): + """Implementing the MiFlora sensor.""" + +# pylint: disable=too-many-instance-attributes,too-many-arguments + def __init__(self, poller, parameter, name, unit, force_update, median=3): + """Initialize the sensor.""" + self.poller = poller + self.parameter = parameter + self._unit = unit + self._name = name + self._state = None + self.data = [] + self._force_update = force_update + # Median is used to filter out outliers. median of 3 will filter + # single outliers, while median of 5 will filter double outliers + # Use median_count = 1 if no filtering is required. + self.median_count = median + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def unit_of_measurement(self): + """Return the units of measurement.""" + return self._unit + + @property + def force_update(self): + """Force update.""" + return self._force_update + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """ + Update current conditions. + + This uses a rolling median over 3 values to filter out outliers. + """ + try: + LOGGER.debug("Polling data for %s", self.name) + data = self.poller.parameter_value(self.parameter) + except IOError as ioerr: + LOGGER.info("Polling error %s", ioerr) + data = None + return + + if data is not None: + LOGGER.debug("%s = %s", self.name, data) + self.data.append(data) + else: + LOGGER.info("Did not receive any data from Mi Flora sensor %s", + self.name) + # Remove old data from median list or set sensor value to None + # if no data is available anymore + if len(self.data) > 0: + self.data = self.data[1:] + else: + self._state = None + return + + LOGGER.debug("Data collected: %s", self.data) + if len(self.data) > self.median_count: + self.data = self.data[1:] + + if len(self.data) == self.median_count: + median = sorted(self.data)[int((self.median_count - 1) / 2)] + LOGGER.debug("Median is: %s", median) + self._state = median + else: + LOGGER.debug("Not yet enough data for median calculation") diff --git a/requirements_all.txt b/requirements_all.txt index deb6b96d356..5355280a320 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -234,6 +234,9 @@ messagebird==1.2.0 # homeassistant.components.switch.mfi mficlient==0.3.0 +# homeassistant.components.sensor.miflora +miflora==0.1.6 + # homeassistant.components.discovery netdisco==0.7.1 From e7f9fdca67675fc6ced54b334985c0fb76951034 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 14 Sep 2016 07:51:13 +0200 Subject: [PATCH 057/162] Upgrade sqlalchemy to 1.0.15 (#3333) --- homeassistant/components/recorder/__init__.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index e786ffeccc3..7f836a1363d 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -28,7 +28,7 @@ import homeassistant.util.dt as dt_util DOMAIN = "recorder" -REQUIREMENTS = ['sqlalchemy==1.0.14'] +REQUIREMENTS = ['sqlalchemy==1.0.15'] DEFAULT_URL = "sqlite:///{hass_config_path}" DEFAULT_DB_FILE = "home-assistant_v2.db" diff --git a/requirements_all.txt b/requirements_all.txt index 5355280a320..4b1e1e72205 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -451,7 +451,7 @@ speedtest-cli==0.3.4 # homeassistant.components.recorder # homeassistant.scripts.db_migrator -sqlalchemy==1.0.14 +sqlalchemy==1.0.15 # homeassistant.components.http static3==0.7.0 From 26a118e75d310b4ec7c129acffa17466898e46cd Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 14 Sep 2016 07:51:55 +0200 Subject: [PATCH 058/162] Upgrade cherrypy to 8.1.0 (#3334) --- homeassistant/components/http.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index 37deb41eef4..4c46aa25524 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -25,7 +25,7 @@ import homeassistant.util.dt as dt_util import homeassistant.helpers.config_validation as cv DOMAIN = 'http' -REQUIREMENTS = ('cherrypy==7.1.0', 'static3==0.7.0', 'Werkzeug==0.11.11') +REQUIREMENTS = ('cherrypy==8.1.0', 'static3==0.7.0', 'Werkzeug==0.11.11') CONF_API_PASSWORD = 'api_password' CONF_SERVER_HOST = 'server_host' diff --git a/requirements_all.txt b/requirements_all.txt index 4b1e1e72205..03c0e0440cd 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -53,7 +53,7 @@ blockchain==1.3.3 boto3==1.3.1 # homeassistant.components.http -cherrypy==7.1.0 +cherrypy==8.1.0 # homeassistant.components.sensor.coinmarketcap coinmarketcap==2.0.1 From 1e8cf8c1b7ccae893eb57e4ab90002e45efe420c Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 14 Sep 2016 07:52:11 +0200 Subject: [PATCH 059/162] Upgrade PyMata to 2.13 (#3335) --- homeassistant/components/arduino.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py index 73bd7a51dad..239c80523df 100644 --- a/homeassistant/components/arduino.py +++ b/homeassistant/components/arduino.py @@ -13,7 +13,7 @@ from homeassistant.const import ( from homeassistant.const import CONF_PORT import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['PyMata==2.12'] +REQUIREMENTS = ['PyMata==2.13'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 03c0e0440cd..dae13766f8c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -14,7 +14,7 @@ PyISY==1.0.7 PyJWT==1.4.2 # homeassistant.components.arduino -PyMata==2.12 +PyMata==2.13 # homeassistant.components.rpi_gpio # RPi.GPIO==0.6.1 From 79bff0fc5750883c4d9f43ab701878544334b228 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 14 Sep 2016 07:52:51 +0200 Subject: [PATCH 060/162] Migrate to voluptuous (#3337) --- .../components/light/osramlightify.py | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/light/osramlightify.py b/homeassistant/components/light/osramlightify.py index a54cf2bcc32..cdcaef8eb50 100644 --- a/homeassistant/components/light/osramlightify.py +++ b/homeassistant/components/light/osramlightify.py @@ -9,30 +9,24 @@ import socket import random from datetime import timedelta +import voluptuous as vol + from homeassistant import util from homeassistant.const import CONF_HOST from homeassistant.components.light import ( - Light, - ATTR_BRIGHTNESS, - ATTR_COLOR_TEMP, - ATTR_EFFECT, - ATTR_RGB_COLOR, - ATTR_TRANSITION, - EFFECT_RANDOM, - SUPPORT_BRIGHTNESS, - SUPPORT_EFFECT, - SUPPORT_COLOR_TEMP, - SUPPORT_RGB_COLOR, - SUPPORT_TRANSITION, -) + Light, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_RGB_COLOR, + ATTR_TRANSITION, EFFECT_RANDOM, SUPPORT_BRIGHTNESS, SUPPORT_EFFECT, + SUPPORT_COLOR_TEMP, SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, PLATFORM_SCHEMA) +import homeassistant.helpers.config_validation as cv -_LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['lightify==1.0.3'] -TEMP_MIN = 2000 # lightify minimum temperature -TEMP_MAX = 6500 # lightify maximum temperature -TEMP_MIN_HASS = 154 # home assistant minimum temperature -TEMP_MAX_HASS = 500 # home assistant maximum temperature +_LOGGER = logging.getLogger(__name__) + +TEMP_MIN = 2000 # lightify minimum temperature +TEMP_MAX = 6500 # lightify maximum temperature +TEMP_MIN_HASS = 154 # home assistant minimum temperature +TEMP_MAX_HASS = 500 # home assistant maximum temperature MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100) @@ -40,9 +34,13 @@ SUPPORT_OSRAMLIGHTIFY = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP | SUPPORT_EFFECT | SUPPORT_RGB_COLOR | SUPPORT_TRANSITION) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, +}) -def setup_platform(hass, config, add_devices_callback, discovery_info=None): - """Setup Osram Lightify lights.""" + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Osram Lightify lights.""" import lightify host = config.get(CONF_HOST) if host: @@ -53,7 +51,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None): str(err)) _LOGGER.exception(msg) return False - setup_bridge(bridge, add_devices_callback) + setup_bridge(bridge, add_devices) else: _LOGGER.error('No host found in configuration') return False From 4791b5679e72b4e901a9c560825d171425324ddf Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 14 Sep 2016 08:01:51 +0200 Subject: [PATCH 061/162] Use voluptuous for iTunes (#3344) * Migrate to voluptuous * Add support for SSL/TLS --- .../components/media_player/itunes.py | 75 ++++++++++++------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/homeassistant/components/media_player/itunes.py b/homeassistant/components/media_player/itunes.py index 60f12456812..b46007a8d17 100644 --- a/homeassistant/components/media_player/itunes.py +++ b/homeassistant/components/media_player/itunes.py @@ -7,55 +7,75 @@ https://home-assistant.io/components/media_player.itunes/ import logging import requests +import voluptuous as vol from homeassistant.components.media_player import ( MEDIA_TYPE_MUSIC, MEDIA_TYPE_PLAYLIST, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_TURN_OFF, - SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, + SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, PLATFORM_SCHEMA, MediaPlayerDevice) from homeassistant.const import ( - STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING) + STATE_IDLE, STATE_OFF, STATE_ON, STATE_PAUSED, STATE_PLAYING, CONF_NAME, + CONF_HOST, CONF_PORT, CONF_SSL) +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) +DEFAULT_NAME = 'iTunes' +DEFAULT_PORT = 8181 +DEFAULT_TIMEOUT = 10 +DEFAULT_SSL = False +DOMAIN = 'itunes' + SUPPORT_ITUNES = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK | \ SUPPORT_PLAY_MEDIA SUPPORT_AIRPLAY = SUPPORT_VOLUME_SET | SUPPORT_TURN_ON | SUPPORT_TURN_OFF -DOMAIN = 'itunes' +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, +}) class Itunes(object): - """iTunes API client.""" + """The iTunes API client.""" - def __init__(self, host, port): + def __init__(self, host, port, use_ssl): """Initialize the iTunes device.""" self.host = host self.port = port + self.use_ssl = use_ssl @property def _base_url(self): """Return the base url for endpoints.""" - if self.port: - return self.host + ":" + str(self.port) + if self.use_ssl: + uri_scheme = 'https://' else: - return self.host + uri_scheme = 'http://' + + if self.port: + return '{}{}:{}'.format(uri_scheme, self.host, self.port) + else: + return '{}{}'.format(uri_scheme, self.host) def _request(self, method, path, params=None): - """Make the actual request and returns the parsed response.""" - url = self._base_url + path + """Make the actual request and return the parsed response.""" + url = '{}{}'.format(self._base_url, path) try: if method == 'GET': - response = requests.get(url) - elif method == "POST": - response = requests.put(url, params) - elif method == "PUT": - response = requests.put(url, params) - elif method == "DELETE": - response = requests.delete(url) + response = requests.get(url, timeout=DEFAULT_TIMEOUT) + elif method == 'POST': + response = requests.put(url, params, timeout=DEFAULT_TIMEOUT) + elif method == 'PUT': + response = requests.put(url, params, timeout=DEFAULT_TIMEOUT) + elif method == 'DELETE': + response = requests.delete(url, timeout=DEFAULT_TIMEOUT) return response.json() except requests.exceptions.HTTPError: @@ -136,12 +156,14 @@ class Itunes(object): # pylint: disable=unused-argument, abstract-method # pylint: disable=too-many-instance-attributes def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the itunes platform.""" + """Setup the iTunes platform.""" add_devices([ ItunesDevice( - config.get('name', 'iTunes'), - config.get('host'), - config.get('port'), + config.get(CONF_NAME), + config.get(CONF_HOST), + config.get(CONF_PORT), + config.get(CONF_SSL), + add_devices ) ]) @@ -150,15 +172,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class ItunesDevice(MediaPlayerDevice): """Representation of an iTunes API instance.""" - # pylint: disable=too-many-public-methods - def __init__(self, name, host, port, add_devices): + # pylint: disable=too-many-public-methods, too-many-arguments + def __init__(self, name, host, port, use_ssl, add_devices): """Initialize the iTunes device.""" self._name = name self._host = host self._port = port + self._use_ssl = use_ssl self._add_devices = add_devices - self.client = Itunes(self._host, self._port) + self.client = Itunes(self._host, self._port, self._use_ssl) self.current_volume = None self.muted = None @@ -380,9 +403,9 @@ class AirPlayDevice(MediaPlayerDevice): def icon(self): """Return the icon to use in the frontend, if any.""" if self.selected is True: - return "mdi:volume-high" + return 'mdi:volume-high' else: - return "mdi:volume-off" + return 'mdi:volume-off' @property def state(self): From 727b756054a2d055b757b08a7575feab809e5189 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 14 Sep 2016 08:03:30 +0200 Subject: [PATCH 062/162] Use voluptuous for KNX (#3345) * Migrate to voluptuous * Make host optional and set default --- homeassistant/components/binary_sensor/knx.py | 11 +-- homeassistant/components/climate/knx.py | 44 ++++++---- homeassistant/components/knx.py | 86 +++++++++---------- homeassistant/components/switch/knx.py | 27 ++++-- 4 files changed, 93 insertions(+), 75 deletions(-) diff --git a/homeassistant/components/binary_sensor/knx.py b/homeassistant/components/binary_sensor/knx.py index e5a36ceb867..304dad9d71b 100644 --- a/homeassistant/components/binary_sensor/knx.py +++ b/homeassistant/components/binary_sensor/knx.py @@ -5,17 +5,14 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/binary_sensor.knx/ """ from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.knx import ( - KNXConfig, KNXGroupAddress) +from homeassistant.components.knx import (KNXConfig, KNXGroupAddress) -DEPENDENCIES = ["knx"] +DEPENDENCIES = ['knx'] -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the KNX binary sensor platform.""" - add_entities([ - KNXSwitch(hass, KNXConfig(config)) - ]) + add_devices([KNXSwitch(hass, KNXConfig(config))]) class KNXSwitch(KNXGroupAddress, BinarySensorDevice): diff --git a/homeassistant/components/climate/knx.py b/homeassistant/components/climate/knx.py index a9d4358a059..5ea932ab8f5 100644 --- a/homeassistant/components/climate/knx.py +++ b/homeassistant/components/climate/knx.py @@ -2,26 +2,37 @@ Support for KNX thermostats. For more details about this platform, please refer to the documentation -https://home-assistant.io/components/knx/ +https://home-assistant.io/components/climate.knx/ """ import logging -from homeassistant.components.climate import ClimateDevice -from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE +import voluptuous as vol -from homeassistant.components.knx import ( - KNXConfig, KNXMultiAddressDevice) - -DEPENDENCIES = ["knx"] +from homeassistant.components.climate import (ClimateDevice, PLATFORM_SCHEMA) +from homeassistant.components.knx import (KNXConfig, KNXMultiAddressDevice) +from homeassistant.const import (CONF_NAME, TEMP_CELSIUS, ATTR_TEMPERATURE) +import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) +CONF_ADDRESS = 'address' +CONF_SETPOINT_ADDRESS = 'setpoint_address' +CONF_TEMPERATURE_ADDRESS = 'temperature_address' -def setup_platform(hass, config, add_entities, discovery_info=None): +DEFAULT_NAME = 'KNX Thermostat' +DEPENDENCIES = ['knx'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_ADDRESS): cv.string, + vol.Required(CONF_SETPOINT_ADDRESS): cv.string, + vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): """Create and add an entity based on the configuration.""" - add_entities([ - KNXThermostat(hass, KNXConfig(config)) - ]) + add_devices([KNXThermostat(hass, KNXConfig(config))]) class KNXThermostat(KNXMultiAddressDevice, ClimateDevice): @@ -39,9 +50,8 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice): def __init__(self, hass, config): """Initialize the thermostat based on the given configuration.""" - KNXMultiAddressDevice.__init__(self, hass, config, - ["temperature", "setpoint"], - ["mode"]) + KNXMultiAddressDevice.__init__( + self, hass, config, ['temperature', 'setpoint'], ['mode']) self._unit_of_measurement = TEMP_CELSIUS # KNX always used celsius self._away = False # not yet supported @@ -62,14 +72,14 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice): """Return the current temperature.""" from knxip.conversion import knx2_to_float - return knx2_to_float(self.value("temperature")) + return knx2_to_float(self.value('temperature')) @property def target_temperature(self): """Return the temperature we try to reach.""" from knxip.conversion import knx2_to_float - return knx2_to_float(self.value("setpoint")) + return knx2_to_float(self.value('setpoint')) def set_temperature(self, **kwargs): """Set new target temperature.""" @@ -78,7 +88,7 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice): return from knxip.conversion import float_to_knx2 - self.set_value("setpoint", float_to_knx2(temperature)) + self.set_value('setpoint', float_to_knx2(temperature)) _LOGGER.debug("Set target temperature to %s", temperature) def set_operation_mode(self, operation_mode): diff --git a/homeassistant/components/knx.py b/homeassistant/components/knx.py index ec837cbf7b6..5d096b30ee0 100644 --- a/homeassistant/components/knx.py +++ b/homeassistant/components/knx.py @@ -6,22 +6,31 @@ https://home-assistant.io/components/knx/ """ import logging -from homeassistant.const import EVENT_HOMEASSISTANT_STOP -from homeassistant.helpers.entity import Entity +import voluptuous as vol + +from homeassistant.const import ( + EVENT_HOMEASSISTANT_STOP, CONF_HOST, CONF_PORT) +from homeassistant.helpers.entity import Entity +import homeassistant.helpers.config_validation as cv -DOMAIN = "knx" REQUIREMENTS = ['knxip==0.3.3'] -EVENT_KNX_FRAME_RECEIVED = "knx_frame_received" +_LOGGER = logging.getLogger(__name__) -CONF_HOST = "host" -CONF_PORT = "port" +DEFAULT_HOST = '0.0.0.0' +DEFAULT_PORT = '3671' +DOMAIN = 'knx' -DEFAULT_PORT = "3671" +EVENT_KNX_FRAME_RECEIVED = 'knx_frame_received' KNXTUNNEL = None -_LOGGER = logging.getLogger(__name__) +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + }), +}, extra=vol.ALLOW_EXTRA) def setup(hass, config): @@ -31,17 +40,11 @@ def setup(hass, config): from knxip.ip import KNXIPTunnel from knxip.core import KNXException - host = config[DOMAIN].get(CONF_HOST, None) + host = config[DOMAIN].get(CONF_HOST) + port = config[DOMAIN].get(CONF_PORT) - if host is None: + if host is '0.0.0.0': _LOGGER.debug("Will try to auto-detect KNX/IP gateway") - host = "0.0.0.0" - - try: - port = int(config[DOMAIN].get(CONF_PORT, DEFAULT_PORT)) - except ValueError: - _LOGGER.exception("Can't parse KNX IP interface port") - return False KNXTUNNEL = KNXIPTunnel(host, port) try: @@ -78,21 +81,21 @@ class KNXConfig(object): from knxip.core import parse_group_address self.config = config - self.should_poll = config.get("poll", True) - if config.get("address"): - self._address = parse_group_address(config.get("address")) + self.should_poll = config.get('poll', True) + if config.get('address'): + self._address = parse_group_address(config.get('address')) else: self._address = None - if self.config.get("state_address"): + if self.config.get('state_address'): self._state_address = parse_group_address( - self.config.get("state_address")) + self.config.get('state_address')) else: self._state_address = None @property def name(self): """The name given to the entity.""" - return self.config["name"] + return self.config['name'] @property def address(self): @@ -175,7 +178,7 @@ class KNXGroupAddress(Entity): @property def cache(self): """The name given to the entity.""" - return self._config.config.get("cache", True) + return self._config.config.get('cache', True) def group_write(self, value): """Write to the group address.""" @@ -187,22 +190,21 @@ class KNXGroupAddress(Entity): try: if self.state_address: - res = KNXTUNNEL.group_read(self.state_address, - use_cache=self.cache) + res = KNXTUNNEL.group_read( + self.state_address, use_cache=self.cache) else: - res = KNXTUNNEL.group_read(self.address, - use_cache=self.cache) + res = KNXTUNNEL.group_read(self.address, use_cache=self.cache) if res: self._state = res[0] self._data = res else: - _LOGGER.debug("Unable to read from KNX address: %s (None)", - self.address) + _LOGGER.debug( + "Unable to read from KNX address: %s (None)", self.address) except KNXException: - _LOGGER.exception("Unable to read from KNX address: %s", - self.address) + _LOGGER.exception( + "Unable to read from KNX address: %s", self.address) return False @@ -234,19 +236,19 @@ class KNXMultiAddressDevice(Entity): # parse required addresses for name in required: _LOGGER.info(name) - paramname = name + "_address" + paramname = '{}{}'.format(name, '_address') addr = self._config.config.get(paramname) if addr is None: - _LOGGER.exception("Required KNX group address %s missing", - paramname) - raise KNXException("Group address for %s missing " - "in configuration", paramname) + _LOGGER.exception( + "Required KNX group address %s missing", paramname) + raise KNXException( + "Group address for %s missing in configuration", paramname) addr = parse_group_address(addr) self.names[addr] = name # parse optional addresses for name in optional: - paramname = name + "_address" + paramname = '{}{}'.format(name, '_address') addr = self._config.config.get(paramname) if addr: try: @@ -273,7 +275,7 @@ class KNXMultiAddressDevice(Entity): @property def cache(self): """The name given to the entity.""" - return self._config.config.get("cache", True) + return self._config.config.get('cache', True) def has_attribute(self, name): """Check if the attribute with the given name is defined. @@ -301,8 +303,7 @@ class KNXMultiAddressDevice(Entity): try: res = KNXTUNNEL.group_read(addr, use_cache=self.cache) except KNXException: - _LOGGER.exception("Unable to read from KNX address: %s", - addr) + _LOGGER.exception("Unable to read from KNX address: %s", addr) return False return res @@ -323,8 +324,7 @@ class KNXMultiAddressDevice(Entity): try: KNXTUNNEL.group_write(addr, value) except KNXException: - _LOGGER.exception("Unable to write to KNX address: %s", - addr) + _LOGGER.exception("Unable to write to KNX address: %s", addr) return False return True diff --git a/homeassistant/components/switch/knx.py b/homeassistant/components/switch/knx.py index 4ed24e68d4d..e290f3ba4e1 100644 --- a/homeassistant/components/switch/knx.py +++ b/homeassistant/components/switch/knx.py @@ -4,18 +4,29 @@ Support KNX switching actuators. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.knx/ """ -from homeassistant.components.switch import SwitchDevice -from homeassistant.components.knx import ( - KNXConfig, KNXGroupAddress) +import voluptuous as vol -DEPENDENCIES = ["knx"] +from homeassistant.components.knx import (KNXConfig, KNXGroupAddress) +from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) +from homeassistant.const import CONF_NAME +import homeassistant.helpers.config_validation as cv + +CONF_ADDRESS = 'address' +CONF_STATE_ADDRESS = 'state_address' + +DEFAULT_NAME = 'KNX Switch' +DEPENDENCIES = ['knx'] + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_ADDRESS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_STATE_ADDRESS): cv.string, +}) -def setup_platform(hass, config, add_entities, discovery_info=None): +def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the KNX switch platform.""" - add_entities([ - KNXSwitch(hass, KNXConfig(config)) - ]) + add_devices([KNXSwitch(hass, KNXConfig(config))]) class KNXSwitch(KNXGroupAddress, SwitchDevice): From b78e98702a2c11a65ae36e359a633fec0bb2d0e6 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 14 Sep 2016 08:04:26 +0200 Subject: [PATCH 063/162] Update yahooweather 0.8 / change request time (#3352) [Breaking change] --- homeassistant/components/sensor/yweather.py | 20 ++++++++++++-------- requirements_all.txt | 2 +- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/sensor/yweather.py b/homeassistant/components/sensor/yweather.py index 8e89b25282c..f482d8d2e2c 100644 --- a/homeassistant/components/sensor/yweather.py +++ b/homeassistant/components/sensor/yweather.py @@ -11,26 +11,28 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - TEMP_CELSIUS, CONF_MONITORED_CONDITIONS, STATE_UNKNOWN) + TEMP_CELSIUS, CONF_MONITORED_CONDITIONS, CONF_NAME, STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle -REQUIREMENTS = ["yahooweather==0.7"] +REQUIREMENTS = ["yahooweather==0.8"] _LOGGER = logging.getLogger(__name__) CONF_FORECAST = 'forecast' CONF_WOEID = 'woeid' -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=120) +DEFAULT_NAME = 'Yweather' + +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600) SENSOR_TYPES = { 'weather_current': ['Current', None], 'weather': ['Condition', None], 'temperature': ['Temperature', "temperature"], - 'temp_min': ['Temperature', "temperature"], - 'temp_max': ['Temperature', "temperature"], + 'temp_min': ['Temperature min', "temperature"], + 'temp_max': ['Temperature max', "temperature"], 'wind_speed': ['Wind speed', "speed"], 'humidity': ['Humidity', "%"], 'pressure': ['Pressure', "pressure"], @@ -39,6 +41,7 @@ SENSOR_TYPES = { PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_WOEID, default=None): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_FORECAST, default=0): vol.All(vol.Coerce(int), vol.Range(min=0, max=5)), vol.Required(CONF_MONITORED_CONDITIONS, default=[]): @@ -53,6 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): unit = hass.config.units.temperature_unit woeid = config.get(CONF_WOEID) forecast = config.get(CONF_FORECAST) + name = config.get(CONF_NAME) # convert unit yunit = UNIT_C if unit == TEMP_CELSIUS else UNIT_F @@ -86,7 +90,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): dev = [] for variable in config[CONF_MONITORED_CONDITIONS]: - dev.append(YahooWeatherSensor(yahoo_api, forecast, variable)) + dev.append(YahooWeatherSensor(yahoo_api, name, forecast, variable)) add_devices(dev) @@ -95,9 +99,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): class YahooWeatherSensor(Entity): """Implementation of an Yahoo! weather sensor.""" - def __init__(self, weather_data, forecast, sensor_type): + def __init__(self, weather_data, name, forecast, sensor_type): """Initialize the sensor.""" - self._client = 'Weather' + self._client = name self._name = SENSOR_TYPES[sensor_type][0] self._type = sensor_type self._state = STATE_UNKNOWN diff --git a/requirements_all.txt b/requirements_all.txt index dae13766f8c..49f63c6b4b2 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -511,7 +511,7 @@ xboxapi==0.1.1 xmltodict==0.10.2 # homeassistant.components.sensor.yweather -yahooweather==0.7 +yahooweather==0.8 # homeassistant.components.zeroconf zeroconf==0.17.6 From 7724cb9eb47e9c82af1951f8bdf2da58958b8173 Mon Sep 17 00:00:00 2001 From: Jeff Wilson Date: Wed, 14 Sep 2016 02:10:49 -0400 Subject: [PATCH 064/162] Fix octoprint sensor (#3385) * Fix non-temperature sensors for octoprint * Fix double space in octoprint temperature names * Fix tox linting errors --- homeassistant/components/sensor/octoprint.py | 21 +++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/sensor/octoprint.py b/homeassistant/components/sensor/octoprint.py index 3b4635c829a..3b55200e647 100644 --- a/homeassistant/components/sensor/octoprint.py +++ b/homeassistant/components/sensor/octoprint.py @@ -60,17 +60,14 @@ def setup_platform(hass, config, add_devices, discovery_info=None): tool) devices.append(new_sensor) else: - _LOGGER.error("Unknown OctoPrint sensor type: %s", octo_type) - - new_sensor = OctoPrintSensor(octoprint.OCTOPRINT, - octo_type, - SENSOR_TYPES[octo_type][2], - name, - SENSOR_TYPES[octo_type][3], - SENSOR_TYPES[octo_type][0], - SENSOR_TYPES[octo_type][1]) - devices.append(new_sensor) - + new_sensor = OctoPrintSensor(octoprint.OCTOPRINT, + octo_type, + SENSOR_TYPES[octo_type][2], + name, + SENSOR_TYPES[octo_type][3], + SENSOR_TYPES[octo_type][0], + SENSOR_TYPES[octo_type][1]) + devices.append(new_sensor) add_devices(devices) @@ -87,7 +84,7 @@ class OctoPrintSensor(Entity): self._name = '{} {}'.format(sensor_name, condition) else: self._name = '{} {} {} {}'.format( - sensor_name, condition, tool, ' temp') + sensor_name, condition, tool, 'temp') self.sensor_type = sensor_type self.api = api self._state = None From 782838af5688ea90c21f8e1acf3b5c560e3186e9 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 14 Sep 2016 08:13:10 +0200 Subject: [PATCH 065/162] Use voluptuous for Zone (#3377) * Migrate to voluptuous * Zone: Remove unneeded latitude/longitude check --- homeassistant/components/zone.py | 65 +++++++++++++++++++------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/zone.py b/homeassistant/components/zone.py index a7841578e2b..52f06c67785 100644 --- a/homeassistant/components/zone.py +++ b/homeassistant/components/zone.py @@ -6,30 +6,47 @@ https://home-assistant.io/components/zone/ """ import logging +import voluptuous as vol + from homeassistant.const import ( - ATTR_HIDDEN, ATTR_ICON, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_NAME) + ATTR_HIDDEN, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_NAME, CONF_LATITUDE, + CONF_LONGITUDE, CONF_ICON) from homeassistant.helpers import extract_domain_configs from homeassistant.helpers.entity import Entity, generate_entity_id from homeassistant.util.location import distance -from homeassistant.util import convert +import homeassistant.helpers.config_validation as cv -DOMAIN = "zone" -ENTITY_ID_FORMAT = 'zone.{}' -ENTITY_ID_HOME = ENTITY_ID_FORMAT.format('home') -STATE = 'zoning' - -DEFAULT_NAME = 'Unnamed zone' - -ATTR_RADIUS = 'radius' -DEFAULT_RADIUS = 100 +_LOGGER = logging.getLogger(__name__) ATTR_PASSIVE = 'passive' +ATTR_RADIUS = 'radius' + +CONF_PASSIVE = 'passive' +CONF_RADIUS = 'radius' + +DEFAULT_NAME = 'Unnamed zone' DEFAULT_PASSIVE = False +DEFAULT_RADIUS = 100 +DOMAIN = 'zone' + +ENTITY_ID_FORMAT = 'zone.{}' +ENTITY_ID_HOME = ENTITY_ID_FORMAT.format('home') ICON_HOME = 'mdi:home' ICON_IMPORT = 'mdi:import' -_LOGGER = logging.getLogger(__name__) +STATE = 'zoning' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_LATITUDE): cv.latitude, + vol.Required(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS): vol.Coerce(float), + vol.Optional(CONF_PASSIVE, default=DEFAULT_PASSIVE): cv.boolean, + vol.Optional(CONF_ICON): cv.icon, + }), +}, extra=vol.ALLOW_EXTRA) def active_zone(hass, latitude, longitude, radius=0): @@ -80,20 +97,15 @@ def setup(hass, config): entries = entries, for entry in entries: - name = entry.get(CONF_NAME, DEFAULT_NAME) - latitude = convert(entry.get(ATTR_LATITUDE), float) - longitude = convert(entry.get(ATTR_LONGITUDE), float) - radius = convert(entry.get(ATTR_RADIUS, DEFAULT_RADIUS), float) - icon = entry.get(ATTR_ICON) - passive = entry.get(ATTR_PASSIVE, DEFAULT_PASSIVE) + name = entry.get(CONF_NAME) + latitude = entry.get(CONF_LATITUDE) + longitude = entry.get(CONF_LONGITUDE) + radius = entry.get(CONF_RADIUS) + icon = entry.get(CONF_ICON) + passive = entry.get(CONF_PASSIVE) - if None in (latitude, longitude): - logging.getLogger(__name__).error( - 'Each zone needs a latitude and longitude.') - continue - - zone = Zone(hass, name, latitude, longitude, radius, - icon, passive, False) + zone = Zone( + hass, name, latitude, longitude, radius, icon, passive, False) add_zone(hass, name, zone, entities) entities.add(zone.entity_id) @@ -116,8 +128,7 @@ def add_zone(hass, name, zone, entities=None): _entities = set() else: _entities = entities - zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, - _entities) + zone.entity_id = generate_entity_id(ENTITY_ID_FORMAT, name, _entities) zone_exists = hass.states.get(zone.entity_id) if zone_exists is None: zone.update_ha_state() From c6fa07d0599bebd33367c484fc1bfb27dbecf61a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 14 Sep 2016 08:20:58 +0200 Subject: [PATCH 066/162] Migrate to voluptuous (#3372) --- homeassistant/components/insteon_hub.py | 31 +++++++++++++++---------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/insteon_hub.py b/homeassistant/components/insteon_hub.py index 306acab5361..906f15b6c3f 100644 --- a/homeassistant/components/insteon_hub.py +++ b/homeassistant/components/insteon_hub.py @@ -6,26 +6,33 @@ https://home-assistant.io/components/insteon_hub/ """ import logging -from homeassistant.const import CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME -from homeassistant.helpers import validate_config, discovery +import voluptuous as vol + +from homeassistant.const import (CONF_API_KEY, CONF_PASSWORD, CONF_USERNAME) +from homeassistant.helpers import discovery +import homeassistant.helpers.config_validation as cv -DOMAIN = "insteon_hub" REQUIREMENTS = ['insteon_hub==0.4.5'] -INSTEON = None + _LOGGER = logging.getLogger(__name__) +DOMAIN = 'insteon_hub' +INSTEON = None + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + }) +}, extra=vol.ALLOW_EXTRA) + def setup(hass, config): """Setup Insteon Hub component. This will automatically import associated lights. """ - if not validate_config( - config, - {DOMAIN: [CONF_USERNAME, CONF_PASSWORD, CONF_API_KEY]}, - _LOGGER): - return False - import insteon username = config[DOMAIN][CONF_USERNAME] @@ -36,8 +43,8 @@ def setup(hass, config): INSTEON = insteon.Insteon(username, password, api_key) if INSTEON is None: - _LOGGER.error("Could not connect to Insteon service.") - return + _LOGGER.error("Could not connect to Insteon service") + return False discovery.load_platform(hass, 'light', DOMAIN, {}, config) From d029861c93b109fb89abd07bc79eeedba4f2c0a6 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Wed, 14 Sep 2016 08:29:15 +0200 Subject: [PATCH 067/162] Use voluptuous for Tellstick (#3367) * Migrate to voluptuous * Update tellstick.py --- homeassistant/components/sensor/tellstick.py | 45 +++++++++++------ homeassistant/components/tellstick.py | 51 +++++++++----------- 2 files changed, 53 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/sensor/tellstick.py b/homeassistant/components/sensor/tellstick.py index e6caf5bfd96..b44e9d04666 100644 --- a/homeassistant/components/sensor/tellstick.py +++ b/homeassistant/components/sensor/tellstick.py @@ -7,25 +7,42 @@ https://home-assistant.io/components/sensor.tellstick/ import logging from collections import namedtuple -import homeassistant.util as util +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import TEMP_CELSIUS from homeassistant.helpers.entity import Entity - -DatatypeDescription = namedtuple("DatatypeDescription", ['name', 'unit']) +import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['tellcore-py==1.1.2'] +DatatypeDescription = namedtuple('DatatypeDescription', ['name', 'unit']) + +CONF_DATATYPE_MASK = 'datatype_mask' +CONF_ONLY_NAMED = 'only_named' +CONF_TEMPERATURE_SCALE = 'temperature_scale' + +DEFAULT_DATATYPE_MASK = 127 +DEFAULT_ONLY_NAMED = False +DEFAULT_TEMPERATURE_SCALE = TEMP_CELSIUS + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_ONLY_NAMED, default=DEFAULT_ONLY_NAMED): cv.boolean, + vol.Optional(CONF_TEMPERATURE_SCALE, default=DEFAULT_TEMPERATURE_SCALE): + cv.string, + vol.Optional(CONF_DATATYPE_MASK, default=DEFAULT_DATATYPE_MASK): cv.positive_int, +}) + # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup Tellstick sensors.""" + """Setup the Tellstick sensors.""" import tellcore.telldus as telldus import tellcore.constants as tellcore_constants sensor_value_descriptions = { tellcore_constants.TELLSTICK_TEMPERATURE: - DatatypeDescription( - 'temperature', config.get('temperature_scale', TEMP_CELSIUS)), + DatatypeDescription('temperature', config.get(CONF_TEMPERATURE_SCALE)), tellcore_constants.TELLSTICK_HUMIDITY: DatatypeDescription('humidity', '%'), @@ -49,29 +66,25 @@ def setup_platform(hass, config, add_devices, discovery_info=None): try: core = telldus.TelldusCore() except OSError: - logging.getLogger(__name__).exception( - 'Could not initialize Tellstick.') + logging.getLogger(__name__).exception('Could not initialize Tellstick') return sensors = [] - datatype_mask = util.convert(config.get('datatype_mask'), int, 127) + datatype_mask = config.get(CONF_DATATYPE_MASK) for ts_sensor in core.sensors(): try: sensor_name = config[ts_sensor.id] except KeyError: - if util.convert(config.get('only_named'), bool, False): + if config.get(CONF_ONLY_NAMED): continue sensor_name = str(ts_sensor.id) for datatype in sensor_value_descriptions: if datatype & datatype_mask and ts_sensor.has_value(datatype): - sensor_info = sensor_value_descriptions[datatype] - - sensors.append( - TellstickSensor( - sensor_name, ts_sensor, datatype, sensor_info)) + sensors.append(TellstickSensor( + sensor_name, ts_sensor, datatype, sensor_info)) add_devices(sensors) @@ -85,7 +98,7 @@ class TellstickSensor(Entity): self.sensor = sensor self._unit_of_measurement = sensor_info.unit or None - self._name = "{} {}".format(name, sensor_info.name) + self._name = '{} {}'.format(name, sensor_info.name) @property def name(self): diff --git a/homeassistant/components/tellstick.py b/homeassistant/components/tellstick.py index 0190f67982c..cccba502dc4 100644 --- a/homeassistant/components/tellstick.py +++ b/homeassistant/components/tellstick.py @@ -6,37 +6,37 @@ https://home-assistant.io/components/Tellstick/ """ import logging import threading + import voluptuous as vol from homeassistant.helpers import discovery from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.helpers.entity import Entity -DOMAIN = "tellstick" +DOMAIN = 'tellstick' REQUIREMENTS = ['tellcore-py==1.1.2'] _LOGGER = logging.getLogger(__name__) -ATTR_SIGNAL_REPETITIONS = "signal_repetitions" +ATTR_SIGNAL_REPETITIONS = 'signal_repetitions' DEFAULT_SIGNAL_REPETITIONS = 1 -ATTR_DISCOVER_DEVICES = "devices" -ATTR_DISCOVER_CONFIG = "config" +ATTR_DISCOVER_DEVICES = 'devices' +ATTR_DISCOVER_CONFIG = 'config' -# Use a global tellstick domain lock to handle -# tellcore errors then calling to concurrently +# Use a global tellstick domain lock to handle Tellcore errors then calling +# to concurrently TELLSTICK_LOCK = threading.Lock() -# Keep a reference the the callback registry -# Used from entities that register callback listeners +# Keep a reference the the callback registry. Used from entities that register +# callback listeners TELLCORE_REGISTRY = None CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Optional(ATTR_SIGNAL_REPETITIONS, - default=DEFAULT_SIGNAL_REPETITIONS): - vol.Coerce(int), + default=DEFAULT_SIGNAL_REPETITIONS): vol.Coerce(int), }), }, extra=vol.ALLOW_EXTRA) @@ -46,8 +46,8 @@ def _discover(hass, config, found_devices, component_name): if not len(found_devices): return - _LOGGER.info("discovered %d new %s devices", - len(found_devices), component_name) + _LOGGER.info( + "Discovered %d new %s devices", len(found_devices), component_name) signal_repetitions = config[DOMAIN].get(ATTR_SIGNAL_REPETITIONS) @@ -78,29 +78,28 @@ def setup(hass, config): _discover(hass, config, [switch.id for switch in devices if not switch.methods( tellcore_constants.TELLSTICK_DIM)], - "switch") + 'switch') # Discover the lights _discover(hass, config, [light.id for light in devices if light.methods( tellcore_constants.TELLSTICK_DIM)], - "light") + 'light') return True -class TellstickRegistry: - """Handle everything around tellstick callbacks. +class TellstickRegistry(object): + """Handle everything around Tellstick callbacks. Keeps a map device ids to home-assistant entities. Also responsible for registering / cleanup of callbacks. All device specific logic should be elsewhere (Entities). - """ def __init__(self, hass, tellcore_lib): - """Init the tellstick mappings and callbacks.""" + """Initialize the Tellstick mappings and callbacks.""" self._core_lib = tellcore_lib # used when map callback device id to ha entities. self._id_to_entity_map = {} @@ -108,7 +107,7 @@ class TellstickRegistry: self._setup_device_callback(hass, tellcore_lib) def _device_callback(self, tellstick_id, method, data, cid): - """Handle the actual callback from tellcore.""" + """Handle the actual callback from Tellcore.""" entity = self._id_to_entity_map.get(tellstick_id, None) if entity is not None: entity.set_tellstick_state(method, data) @@ -116,8 +115,7 @@ class TellstickRegistry: def _setup_device_callback(self, hass, tellcore_lib): """Register the callback handler.""" - callback_id = tellcore_lib.register_device_event( - self._device_callback) + callback_id = tellcore_lib.register_device_event(self._device_callback) def clean_up_callback(event): """Unregister the callback bindings.""" @@ -132,8 +130,8 @@ class TellstickRegistry: def register_devices(self, devices): """Register a list of devices.""" - self._id_to_device_map.update({device.id: - device for device in devices}) + self._id_to_device_map.update( + {device.id: device for device in devices}) def get_device(self, tellcore_id): """Return a device by tellcore_id.""" @@ -141,18 +139,17 @@ class TellstickRegistry: class TellstickDevice(Entity): - """Represents a Tellstick device. + """Representation of a Tellstick device. Contains the common logic for all Tellstick devices. - """ def __init__(self, tellstick_device, signal_repetitions): - """Init the tellstick device.""" + """Initalize the Tellstick device.""" self.signal_repetitions = signal_repetitions self._state = None self.tellstick_device = tellstick_device - # add to id to entity mapping + # Add to id to entity mapping TELLCORE_REGISTRY.register_entity(tellstick_device.id, self) # Query tellcore for the current state self.update() From 6f840de1d2b9c3ec3bb383d4d2ce7df6f081a566 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 14 Sep 2016 11:58:26 +0200 Subject: [PATCH 068/162] fix style in tellstick sensor --- homeassistant/components/sensor/tellstick.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/tellstick.py b/homeassistant/components/sensor/tellstick.py index b44e9d04666..464e3554324 100644 --- a/homeassistant/components/sensor/tellstick.py +++ b/homeassistant/components/sensor/tellstick.py @@ -30,7 +30,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_ONLY_NAMED, default=DEFAULT_ONLY_NAMED): cv.boolean, vol.Optional(CONF_TEMPERATURE_SCALE, default=DEFAULT_TEMPERATURE_SCALE): cv.string, - vol.Optional(CONF_DATATYPE_MASK, default=DEFAULT_DATATYPE_MASK): cv.positive_int, + vol.Optional(CONF_DATATYPE_MASK, default=DEFAULT_DATATYPE_MASK): + cv.positive_int, }) From 982a0bc19552efceab18ea73b9d8f723701f2713 Mon Sep 17 00:00:00 2001 From: chrom3 Date: Wed, 14 Sep 2016 21:54:45 +0200 Subject: [PATCH 069/162] Kodi notification platform (#3403) --- .coveragerc | 1 + homeassistant/components/notify/kodi.py | 70 +++++++++++++++++++++++++ requirements_all.txt | 1 + 3 files changed, 72 insertions(+) create mode 100644 homeassistant/components/notify/kodi.py diff --git a/.coveragerc b/.coveragerc index fdaaf8a12ea..938f42fe222 100644 --- a/.coveragerc +++ b/.coveragerc @@ -186,6 +186,7 @@ omit = homeassistant/components/notify/group.py homeassistant/components/notify/instapush.py homeassistant/components/notify/joaoapps_join.py + homeassistant/components/notify/kodi.py homeassistant/components/notify/llamalab_automate.py homeassistant/components/notify/message_bird.py homeassistant/components/notify/nma.py diff --git a/homeassistant/components/notify/kodi.py b/homeassistant/components/notify/kodi.py new file mode 100644 index 00000000000..60cf307515f --- /dev/null +++ b/homeassistant/components/notify/kodi.py @@ -0,0 +1,70 @@ +""" +Kodi notification service. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.kodi/ +""" +import logging +import voluptuous as vol + +from homeassistant.const import (ATTR_ICON, CONF_HOST, CONF_PORT, + CONF_USERNAME, CONF_PASSWORD) +from homeassistant.components.notify import (ATTR_TITLE, ATTR_TITLE_DEFAULT, + ATTR_DATA, PLATFORM_SCHEMA, + BaseNotificationService) +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) +REQUIREMENTS = ['jsonrpc-requests==0.3'] + +DEFAULT_PORT = 8080 + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, +}) + +ATTR_DISPLAYTIME = 'displaytime' + + +def get_service(hass, config): + """Return the notify service.""" + url = '{}:{}'.format(config.get(CONF_HOST), config.get(CONF_PORT)) + + auth = (config.get(CONF_USERNAME), config.get(CONF_PASSWORD)) + + return KODINotificationService( + url, + auth + ) + + +# pylint: disable=too-few-public-methods +class KODINotificationService(BaseNotificationService): + """Implement the notification service for Kodi.""" + + def __init__(self, url, auth=None): + """Initialize the service.""" + import jsonrpc_requests + self._url = url + self._server = jsonrpc_requests.Server( + '{}/jsonrpc'.format(self._url), + auth=auth, + timeout=5) + + def send_message(self, message="", **kwargs): + """Send a message to Kodi.""" + import jsonrpc_requests + try: + data = kwargs.get(ATTR_DATA) or {} + + displaytime = data.get(ATTR_DISPLAYTIME, 10000) + icon = data.get(ATTR_ICON, "info") + title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) + self._server.GUI.ShowNotification(title, message, icon, + displaytime) + + except jsonrpc_requests.jsonrpc.TransportError: + _LOGGER.warning('Unable to fetch Kodi data, Is Kodi online?') diff --git a/requirements_all.txt b/requirements_all.txt index 49f63c6b4b2..400ee949c7f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -210,6 +210,7 @@ influxdb==3.0.0 insteon_hub==0.4.5 # homeassistant.components.media_player.kodi +# homeassistant.components.notify.kodi jsonrpc-requests==0.3 # homeassistant.scripts.keyring From 70a79efb7777232358042316d7ceef43789615c7 Mon Sep 17 00:00:00 2001 From: Eric Jansen Date: Thu, 15 Sep 2016 02:41:45 +0200 Subject: [PATCH 070/162] Bugfix: incorrectly inverted value when setting cover position (#3376) --- homeassistant/components/cover/zwave.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/cover/zwave.py b/homeassistant/components/cover/zwave.py index d7ebfb834e8..df7b2e14cb5 100644 --- a/homeassistant/components/cover/zwave.py +++ b/homeassistant/components/cover/zwave.py @@ -139,7 +139,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, 100 - position) + self._node.set_dimmer(self._value.value_id, position) def stop_cover(self, **kwargs): """Stop the roller shutter.""" From b58976bc363748dd6c0e63544a5fb9e101418aa8 Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 14 Sep 2016 21:14:39 -0400 Subject: [PATCH 071/162] Add operation_list to radiotherm (#3393) * Add operation_list to radiotherm * Use constants --- homeassistant/components/climate/radiotherm.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/homeassistant/components/climate/radiotherm.py b/homeassistant/components/climate/radiotherm.py index e4d87fec7ec..6af0e96045c 100644 --- a/homeassistant/components/climate/radiotherm.py +++ b/homeassistant/components/climate/radiotherm.py @@ -73,6 +73,7 @@ class RadioThermostat(ClimateDevice): self._name = None self.hold_temp = hold_temp self.update() + self._operation_list = [STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_OFF] @property def name(self): @@ -102,6 +103,11 @@ class RadioThermostat(ClimateDevice): """Return the current operation. head, cool idle.""" return self._current_operation + @property + def operation_list(self): + """Return the operation modes list.""" + return self._operation_list + @property def target_temperature(self): """Return the temperature we try to reach.""" From c693db49b3251db0a05477476e5945d98c4bf9a1 Mon Sep 17 00:00:00 2001 From: gross1989 Date: Thu, 15 Sep 2016 03:20:49 +0200 Subject: [PATCH 072/162] Nuimo controller component based on SDK (#3039) --- .coveragerc | 1 + homeassistant/components/nuimo_controller.py | 185 +++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 189 insertions(+) create mode 100644 homeassistant/components/nuimo_controller.py diff --git a/.coveragerc b/.coveragerc index 938f42fe222..2f1bcf73561 100644 --- a/.coveragerc +++ b/.coveragerc @@ -203,6 +203,7 @@ omit = homeassistant/components/notify/twilio_sms.py homeassistant/components/notify/twitter.py homeassistant/components/notify/xmpp.py + homeassistant/components/nuimo_controller.py homeassistant/components/scene/hunterdouglas_powerview.py homeassistant/components/sensor/arest.py homeassistant/components/sensor/bitcoin.py diff --git a/homeassistant/components/nuimo_controller.py b/homeassistant/components/nuimo_controller.py new file mode 100644 index 00000000000..b383b4f45fc --- /dev/null +++ b/homeassistant/components/nuimo_controller.py @@ -0,0 +1,185 @@ +""" +Component that connects to a Nuimo device over Bluetooth LE. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/nuimo_controller/ +""" +import logging +import threading +import time +import voluptuous as vol +import homeassistant.helpers.config_validation as cv +from homeassistant.const import (CONF_MAC, CONF_NAME, EVENT_HOMEASSISTANT_STOP) + +REQUIREMENTS = [ + '--only-binary=all ' # avoid compilation of gattlib + 'git+https://github.com/getSenic/nuimo-linux-python' + '#nuimo==1.0.0'] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'nuimo_controller' +EVENT_NUIMO = 'nuimo_input' + +DEFAULT_NAME = 'None' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Optional(CONF_MAC): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string + }), +}, extra=vol.ALLOW_EXTRA) + +SERVICE_NUIMO = 'led_matrix' +DEFAULT_INTERVAL = 2.0 + +SERVICE_NUIMO_SCHEMA = vol.Schema({ + vol.Required('matrix'): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional('interval', default=DEFAULT_INTERVAL): float +}) + +DEFAULT_ADAPTER = 'hci0' + + +def setup(hass, config): + """Setup the Nuimo component.""" + conf = config[DOMAIN] + mac = conf.get(CONF_MAC) + name = conf.get(CONF_NAME) + NuimoThread(hass, mac, name).start() + return True + + +class NuimoLogger(object): # pylint: disable=too-few-public-methods + """Handle Nuimo Controller event callbacks.""" + + def __init__(self, hass, name): + """Initialize Logger object.""" + self._hass = hass + self._name = name + + def received_gesture_event(self, event): + """Input Event received.""" + _LOGGER.debug("received event: name=%s, gesture_id=%s,value=%s", + event.name, event.gesture, event.value) + self._hass.bus.fire(EVENT_NUIMO, + {'type': event.name, 'value': event.value, + 'name': self._name}) + + +class NuimoThread(threading.Thread): + """Manage one Nuimo controller.""" + + def __init__(self, hass, mac, name): + """Initialize thread object.""" + super(NuimoThread, self).__init__() + self._hass = hass + self._mac = mac + self._name = name + self._hass_is_running = True + self._nuimo = None + self._listener = hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, + self.stop) + + def run(self): + """Setup connection or be idle.""" + while self._hass_is_running: + if not self._nuimo or not self._nuimo.is_connected(): + self._attach() + self._connect() + else: + time.sleep(1) + + if self._nuimo: + self._nuimo.disconnect() + self._nuimo = None + + def stop(self, event): # pylint: disable=unused-argument + """Terminate Thread by unsetting flag.""" + _LOGGER.debug('Stopping thread for Nuimo %s', self._mac) + self._hass_is_running = False + self._hass.bus.remove_listener(EVENT_HOMEASSISTANT_STOP, + self._listener) + + def _attach(self): + """Create a nuimo object from mac address or discovery.""" + # pylint: disable=import-error + from nuimo import NuimoController, NuimoDiscoveryManager + + if self._nuimo: + self._nuimo.disconnect() + self._nuimo = None + + if self._mac: + self._nuimo = NuimoController(self._mac) + else: + nuimo_manager = NuimoDiscoveryManager( + bluetooth_adapter=DEFAULT_ADAPTER, delegate=DiscoveryLogger()) + nuimo_manager.start_discovery() + # Were any Nuimos found? + if not nuimo_manager.nuimos: + _LOGGER.debug('No Nuimos detected') + return + # Take the first Nuimo found. + self._nuimo = nuimo_manager.nuimos[0] + self._mac = self._nuimo.addr + + def _connect(self): + """Build up connection and set event delegator and service.""" + if not self._nuimo: + return + + try: + self._nuimo.connect() + _LOGGER.debug('connected to %s', self._mac) + except RuntimeError as error: + _LOGGER.error('could not connect to %s: %s', self._mac, error) + time.sleep(1) + return + + nuimo_event_delegate = NuimoLogger(self._hass, self._name) + self._nuimo.set_delegate(nuimo_event_delegate) + + def handle_write_matrix(call): + """Handle led matrix service.""" + matrix = call.data.get('matrix', None) + name = call.data.get(CONF_NAME, DEFAULT_NAME) + interval = call.data.get('interval', DEFAULT_INTERVAL) + if self._name == name and matrix: + self._nuimo.write_matrix(matrix, interval) + + self._hass.services.register(DOMAIN, SERVICE_NUIMO, + handle_write_matrix, + schema=SERVICE_NUIMO_SCHEMA) + + self._nuimo.write_matrix(HOMEASSIST_LOGO, 2.0) + + +# must be 9x9 matrix +HOMEASSIST_LOGO = ( + " . " + + " ... " + + " ..... " + + " ....... " + + "..... ..." + + " ....... " + + " .. .... " + + " .. .... " + + ".........") + + +class DiscoveryLogger(object): + """Handle Nuimo Discovery callbacks.""" + + def discovery_started(self): # pylint: disable=no-self-use + """Discovery startet.""" + _LOGGER.info("started discovery") + + def discovery_finished(self): # pylint: disable=no-self-use + """Discovery finished.""" + _LOGGER.info("finished discovery") + + def controller_added(self, nuimo): # pylint: disable=no-self-use + """Controller found.""" + _LOGGER.info("added Nuimo: %s", nuimo) diff --git a/requirements_all.txt b/requirements_all.txt index 400ee949c7f..1ecf4062463 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -7,6 +7,9 @@ jinja2>=2.8 voluptuous==0.9.2 typing>=3,<4 +# homeassistant.components.nuimo_controller +--only-binary=all git+https://github.com/getSenic/nuimo-linux-python#nuimo==1.0.0 + # homeassistant.components.isy994 PyISY==1.0.7 From d1b08824e8e4052294127a4bca53c560030512df Mon Sep 17 00:00:00 2001 From: Dan Date: Wed, 14 Sep 2016 21:21:01 -0400 Subject: [PATCH 073/162] Implemented onkyo reconnect (#3061) * Implemented onkyo reconnect Connection object is cleared after a failed command. It will be automatically recreated upon the next command running. This should allow for failed connections to be restored. * Remove reduntant error catching * Run all update commands with command wrapper. * Handle errors better * Removed unused global I have no idea how that got there. --- .../components/media_player/onkyo.py | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/media_player/onkyo.py b/homeassistant/components/media_player/onkyo.py index 53cd0b68df9..3d4a6fb553b 100644 --- a/homeassistant/components/media_player/onkyo.py +++ b/homeassistant/components/media_player/onkyo.py @@ -23,11 +23,10 @@ CONF_SOURCES = 'sources' DEFAULT_NAME = 'Onkyo Receiver' -KNOWN_HOSTS = [] - SUPPORT_ONKYO = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE +KNOWN_HOSTS = [] # type: List[str] DEFAULT_SOURCES = {'tv': 'TV', 'bd': 'Bluray', 'game': 'Game', 'aux1': 'Aux1', 'video1': 'Video 1', 'video2': 'Video 2', 'video3': 'Video 3', 'video4': 'Video 4', @@ -85,17 +84,35 @@ class OnkyoDevice(MediaPlayerDevice): self._reverse_mapping = {value: key for key, value in sources.items()} self.update() + def command(self, command): + """Run an eiscp command and catch connection errors.""" + try: + result = self._receiver.command(command) + except (ValueError, OSError, AttributeError, AssertionError): + if self._receiver.command_socket: + self._receiver.command_socket = None + _LOGGER.info('Reseting connection to %s.', self._name) + else: + _LOGGER.info('%s is disconnected. Attempting to reconnect.', + self._name) + return False + return result + def update(self): """Get the latest details from the device.""" - status = self._receiver.command('system-power query') + status = self.command('system-power query') + if not status: + return if status[1] == 'on': self._pwstate = STATE_ON else: self._pwstate = STATE_OFF return - volume_raw = self._receiver.command('volume query') - mute_raw = self._receiver.command('audio-muting query') - current_source_raw = self._receiver.command('input-selector query') + volume_raw = self.command('volume query') + mute_raw = self.command('audio-muting query') + current_source_raw = self.command('input-selector query') + if not (volume_raw and mute_raw and current_source_raw): + return for source in current_source_raw[1]: if source in self._source_mapping: self._current_source = self._source_mapping[source] @@ -143,18 +160,18 @@ class OnkyoDevice(MediaPlayerDevice): def turn_off(self): """Turn off media player.""" - self._receiver.command('system-power standby') + self.command('system-power standby') def set_volume_level(self, volume): """Set volume level, input is range 0..1. Onkyo ranges from 1-80.""" - self._receiver.command('volume {}'.format(int(volume*80))) + self.command('volume {}'.format(int(volume*80))) def mute_volume(self, mute): """Mute (true) or unmute (false) media player.""" if mute: - self._receiver.command('audio-muting on') + self.command('audio-muting on') else: - self._receiver.command('audio-muting off') + self.command('audio-muting off') def turn_on(self): """Turn the media player on.""" @@ -164,4 +181,4 @@ class OnkyoDevice(MediaPlayerDevice): """Set the input source.""" if source in self._source_list: source = self._reverse_mapping[source] - self._receiver.command('input-selector {}'.format(source)) + self.command('input-selector {}'.format(source)) From 07a92e8ac306ebfaf061b13af13a9be4e79266cb Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Thu, 15 Sep 2016 14:35:40 +0200 Subject: [PATCH 074/162] Split ffmpeg to compoment (#3396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an optional extended description… --- .coveragerc | 5 +- .../components/binary_sensor/ffmpeg.py | 44 ++++++------ homeassistant/components/camera/ffmpeg.py | 29 ++------ homeassistant/components/ffmpeg.py | 68 +++++++++++++++++++ requirements_all.txt | 3 +- 5 files changed, 100 insertions(+), 49 deletions(-) create mode 100644 homeassistant/components/ffmpeg.py diff --git a/.coveragerc b/.coveragerc index 2f1bcf73561..cd15ecb052b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -95,15 +95,16 @@ omit = homeassistant/components/knx.py homeassistant/components/*/knx.py + homeassistant/components/ffmpeg.py + homeassistant/components/*/ffmpeg.py + homeassistant/components/alarm_control_panel/alarmdotcom.py homeassistant/components/alarm_control_panel/nx584.py homeassistant/components/alarm_control_panel/simplisafe.py homeassistant/components/binary_sensor/arest.py - homeassistant/components/binary_sensor/ffmpeg.py homeassistant/components/binary_sensor/rest.py homeassistant/components/browser.py homeassistant/components/camera/bloomsky.py - homeassistant/components/camera/ffmpeg.py homeassistant/components/camera/foscam.py homeassistant/components/camera/mjpeg.py homeassistant/components/camera/rpi_camera.py diff --git a/homeassistant/components/binary_sensor/ffmpeg.py b/homeassistant/components/binary_sensor/ffmpeg.py index cedb978cc68..ce89ae2e4db 100644 --- a/homeassistant/components/binary_sensor/ffmpeg.py +++ b/homeassistant/components/binary_sensor/ffmpeg.py @@ -10,13 +10,17 @@ from os import path import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.binary_sensor import (BinarySensorDevice, - PLATFORM_SCHEMA, DOMAIN) +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, PLATFORM_SCHEMA, DOMAIN) +from homeassistant.components.ffmpeg import ( + get_binary, run_test, CONF_INPUT, CONF_OUTPUT, CONF_EXTRA_ARGUMENTS) from homeassistant.config import load_yaml_config_file from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_NAME, ATTR_ENTITY_ID) -REQUIREMENTS = ["ha-ffmpeg==0.12"] +DEPENDENCIES = ['ffmpeg'] + +_LOGGER = logging.getLogger(__name__) SERVICE_RESTART = 'ffmpeg_restart' @@ -29,26 +33,19 @@ MAP_FFMPEG_BIN = [ ] CONF_TOOL = 'tool' -CONF_INPUT = 'input' -CONF_FFMPEG_BIN = 'ffmpeg_bin' -CONF_EXTRA_ARGUMENTS = 'extra_arguments' -CONF_OUTPUT = 'output' CONF_PEAK = 'peak' CONF_DURATION = 'duration' CONF_RESET = 'reset' CONF_CHANGES = 'changes' CONF_REPEAT = 'repeat' CONF_REPEAT_TIME = 'repeat_time' -CONF_RUN_TEST = 'run_test' -DEFAULT_RUN_TEST = True +DEFAULT_NAME = 'FFmpeg' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_TOOL): vol.In(MAP_FFMPEG_BIN), vol.Required(CONF_INPUT): cv.string, - vol.Optional(CONF_FFMPEG_BIN, default="ffmpeg"): cv.string, - vol.Optional(CONF_NAME, default="FFmpeg"): cv.string, - vol.Optional(CONF_RUN_TEST, default=DEFAULT_RUN_TEST): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, vol.Optional(CONF_OUTPUT): cv.string, vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int), @@ -69,22 +66,23 @@ SERVICE_RESTART_SCHEMA = vol.Schema({ }) +def restart(hass, entity_id=None): + """Restart a ffmpeg process on entity.""" + data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} + hass.services.call(DOMAIN, SERVICE_RESTART, data) + + # list of all ffmpeg sensors DEVICES = [] -_LOGGER = logging.getLogger(__name__) - def setup_platform(hass, config, add_entities, discovery_info=None): """Create the binary sensor.""" - from haffmpeg import Test, SensorNoise, SensorMotion + from haffmpeg import SensorNoise, SensorMotion # check source - if config.get(CONF_RUN_TEST): - test = Test(config.get(CONF_FFMPEG_BIN)) - if not test.run_test(config.get(CONF_INPUT)): - _LOGGER.error("FFmpeg '%s' test fails!", config.get(CONF_INPUT)) - return + if not run_test(config.get(CONF_INPUT)): + return # generate sensor object if config.get(CONF_TOOL) == FFMPEG_SENSOR_NOISE: @@ -117,7 +115,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _devices = DEVICES for device in _devices: - device.reset_ffmpeg() + device.restart_ffmpeg() hass.services.register(DOMAIN, SERVICE_RESTART, _service_handle_restart, @@ -133,7 +131,7 @@ class FFmpegBinarySensor(BinarySensorDevice): self._state = False self._config = config self._name = config.get(CONF_NAME) - self._ffmpeg = ffobj(config.get(CONF_FFMPEG_BIN), self._callback) + self._ffmpeg = ffobj(get_binary(), self._callback) self._start_ffmpeg(config) @@ -150,7 +148,7 @@ class FFmpegBinarySensor(BinarySensorDevice): """For STOP event to shutdown ffmpeg.""" self._ffmpeg.close() - def reset_ffmpeg(self): + def restart_ffmpeg(self): """Restart ffmpeg with new config.""" self._ffmpeg.close() self._start_ffmpeg(self._config) diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py index a0cbe1ca436..1115bc2d2c1 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/camera/ffmpeg.py @@ -9,42 +9,28 @@ import logging import voluptuous as vol from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) +from homeassistant.components.ffmpeg import ( + run_test, get_binary, CONF_INPUT, CONF_EXTRA_ARGUMENTS) import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_NAME -REQUIREMENTS = ['ha-ffmpeg==0.12'] +DEPENDENCIES = ['ffmpeg'] _LOGGER = logging.getLogger(__name__) -CONF_INPUT = 'input' -CONF_FFMPEG_BIN = 'ffmpeg_bin' -CONF_EXTRA_ARGUMENTS = 'extra_arguments' -CONF_RUN_TEST = 'run_test' - -DEFAULT_BINARY = 'ffmpeg' DEFAULT_NAME = 'FFmpeg' -DEFAULT_RUN_TEST = True PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_INPUT): cv.string, vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, - vol.Optional(CONF_FFMPEG_BIN, default=DEFAULT_BINARY): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_RUN_TEST, default=DEFAULT_RUN_TEST): cv.boolean, }) def setup_platform(hass, config, add_devices, discovery_info=None): """Setup a FFmpeg Camera.""" - from haffmpeg import Test - - # run Test - if config.get(CONF_RUN_TEST): - test = Test(config.get(CONF_FFMPEG_BIN)) - if not test.run_test(config.get(CONF_INPUT)): - _LOGGER.error("FFmpeg '%s' test fails!", config.get(CONF_INPUT)) - return - + if not run_test(config.get(CONF_INPUT)): + return add_devices([FFmpegCamera(config)]) @@ -57,12 +43,11 @@ class FFmpegCamera(Camera): self._name = config.get(CONF_NAME) self._input = config.get(CONF_INPUT) self._extra_arguments = config.get(CONF_EXTRA_ARGUMENTS) - self._ffmpeg_bin = config.get(CONF_FFMPEG_BIN) def camera_image(self): """Return a still image response from the camera.""" from haffmpeg import ImageSingle, IMAGE_JPEG - ffmpeg = ImageSingle(self._ffmpeg_bin) + ffmpeg = ImageSingle(get_binary()) return ffmpeg.get_image(self._input, output_format=IMAGE_JPEG, extra_cmd=self._extra_arguments) @@ -71,7 +56,7 @@ class FFmpegCamera(Camera): """Generate an HTTP MJPEG stream from the camera.""" from haffmpeg import CameraMjpeg - stream = CameraMjpeg(self._ffmpeg_bin) + stream = CameraMjpeg(get_binary()) stream.open_camera(self._input, extra_cmd=self._extra_arguments) return response( stream, diff --git a/homeassistant/components/ffmpeg.py b/homeassistant/components/ffmpeg.py new file mode 100644 index 00000000000..03bf1db885f --- /dev/null +++ b/homeassistant/components/ffmpeg.py @@ -0,0 +1,68 @@ +""" +Component that will help set the ffmpeg component. + +For more details about this component, please refer to the documentation at +https://home-assistant.io/components/ffmpeg/ +""" +import logging + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv + +DOMAIN = 'ffmpeg' +REQUIREMENTS = ["ha-ffmpeg==0.12"] + +_LOGGER = logging.getLogger(__name__) + +CONF_INPUT = 'input' +CONF_FFMPEG_BIN = 'ffmpeg_bin' +CONF_EXTRA_ARGUMENTS = 'extra_arguments' +CONF_OUTPUT = 'output' +CONF_RUN_TEST = 'run_test' + +DEFAULT_BINARY = 'ffmpeg' +DEFAULT_RUN_TEST = True + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Optional(CONF_FFMPEG_BIN, default=DEFAULT_BINARY): cv.string, + vol.Optional(CONF_RUN_TEST, default=DEFAULT_RUN_TEST): cv.boolean, + }), +}, extra=vol.ALLOW_EXTRA) + + +FFMPEG_CONFIG = {} +FFMPEG_TEST_CACHE = {} + + +def setup(hass, config): + """Setup the FFmpeg component.""" + global FFMPEG_CONFIG + + FFMPEG_CONFIG = config.get(DOMAIN) + return True + + +def get_binary(): + """Return ffmpeg binary from config.""" + return FFMPEG_CONFIG.get(CONF_FFMPEG_BIN) + + +def run_test(input_source): + """Run test on this input. TRUE is deactivate or run correct.""" + from haffmpeg import Test + + if FFMPEG_CONFIG.get(CONF_RUN_TEST): + # if in cache + if input_source in FFMPEG_TEST_CACHE: + return FFMPEG_TEST_CACHE[input_source] + + # run test + test = Test(get_binary()) + if not test.run_test(input_source): + _LOGGER.error("FFmpeg '%s' test fails!", input_source) + FFMPEG_TEST_CACHE[input_source] = False + return False + FFMPEG_TEST_CACHE[input_source] = True + return True diff --git a/requirements_all.txt b/requirements_all.txt index 1ecf4062463..bfbeaae0296 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -114,8 +114,7 @@ googlemaps==2.4.4 # homeassistant.components.sensor.gpsd gps3==0.33.3 -# homeassistant.components.binary_sensor.ffmpeg -# homeassistant.components.camera.ffmpeg +# homeassistant.components.ffmpeg ha-ffmpeg==0.12 # homeassistant.components.mqtt.server From 0a6f496425c465c77ded6dc781e820c883dccdb0 Mon Sep 17 00:00:00 2001 From: Greg Dowling Date: Thu, 15 Sep 2016 19:47:03 +0100 Subject: [PATCH 075/162] Add support for Vera covers. (#3411) --- homeassistant/components/cover/vera.py | 70 ++++++++++++++++++++++++++ homeassistant/components/vera.py | 7 +-- requirements_all.txt | 2 +- 3 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 homeassistant/components/cover/vera.py diff --git a/homeassistant/components/cover/vera.py b/homeassistant/components/cover/vera.py new file mode 100644 index 00000000000..0a9e8abb243 --- /dev/null +++ b/homeassistant/components/cover/vera.py @@ -0,0 +1,70 @@ +""" +Support for Vera cover - curtains, rollershutters etc. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/cover.vera/ +""" +import logging + +from homeassistant.components.cover import CoverDevice +from homeassistant.components.vera import ( + VeraDevice, VERA_DEVICES, VERA_CONTROLLER) + +DEPENDENCIES = ['vera'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """Find and return Vera covers.""" + add_devices_callback( + VeraCover(device, VERA_CONTROLLER) for + device in VERA_DEVICES['cover']) + + +# pylint: disable=abstract-method +class VeraCover(VeraDevice, CoverDevice): + """Represents a Vera Cover in Home Assistant.""" + + def __init__(self, vera_device, controller): + """Initialize the Vera device.""" + VeraDevice.__init__(self, vera_device, controller) + + @property + def current_cover_position(self): + """ + Return current position of cover. + + 0 is closed, 100 is fully open. + """ + position = self.vera_device.get_level() + if position <= 5: + return 0 + if position >= 95: + return 100 + return position + + def set_cover_position(self, position, **kwargs): + """Move the cover to a specific position.""" + self.vera_device.set_level(position) + + @property + def is_closed(self): + """Return if the cover is closed.""" + if self.current_cover_position is not None: + if self.current_cover_position > 0: + return False + else: + return True + + def open_cover(self, **kwargs): + """Open the cover.""" + self.vera_device.open() + + def close_cover(self, **kwargs): + """Close the cover.""" + self.vera_device.close() + + def stop_cover(self, **kwargs): + """Stop the cover.""" + self.vera_device.stop() diff --git a/homeassistant/components/vera.py b/homeassistant/components/vera.py index cfe2add1315..c78c4461f88 100644 --- a/homeassistant/components/vera.py +++ b/homeassistant/components/vera.py @@ -20,7 +20,7 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP) from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['pyvera==0.2.16'] +REQUIREMENTS = ['pyvera==0.2.20'] _LOGGER = logging.getLogger(__name__) @@ -47,7 +47,7 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) VERA_COMPONENTS = [ - 'binary_sensor', 'sensor', 'light', 'switch', 'lock', 'climate' + 'binary_sensor', 'sensor', 'light', 'switch', 'lock', 'climate', 'cover' ] @@ -109,12 +109,13 @@ def map_vera_device(vera_device, remap): return 'lock' if isinstance(vera_device, veraApi.VeraThermostat): return 'climate' + if isinstance(vera_device, veraApi.VeraCurtain): + return 'cover' if isinstance(vera_device, veraApi.VeraSwitch): if vera_device.device_id in remap: return 'light' else: return 'switch' - # VeraCurtain: NOT SUPPORTED YET return None diff --git a/requirements_all.txt b/requirements_all.txt index bfbeaae0296..85ab6baf688 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -406,7 +406,7 @@ python-wink==0.7.14 # pyuserinput==0.1.11 # homeassistant.components.vera -pyvera==0.2.16 +pyvera==0.2.20 # homeassistant.components.wemo pywemo==0.4.6 From c23ad3e285c0f566e1c324c3736840a09d7e9a6f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 15 Sep 2016 19:40:18 -0700 Subject: [PATCH 076/162] Fix zones (#3413) --- homeassistant/components/zone.py | 19 +++++++++---------- tests/components/test_zone.py | 15 +++++++++------ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/zone.py b/homeassistant/components/zone.py index 52f06c67785..23bc6fd409c 100644 --- a/homeassistant/components/zone.py +++ b/homeassistant/components/zone.py @@ -37,16 +37,15 @@ ICON_IMPORT = 'mdi:import' STATE = 'zoning' -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_LATITUDE): cv.latitude, - vol.Required(CONF_LONGITUDE): cv.longitude, - vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS): vol.Coerce(float), - vol.Optional(CONF_PASSIVE, default=DEFAULT_PASSIVE): cv.boolean, - vol.Optional(CONF_ICON): cv.icon, - }), -}, extra=vol.ALLOW_EXTRA) +# The config that zone accepts is the same as if it has platforms. +PLATFORM_SCHEMA = vol.Schema({ + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_LATITUDE): cv.latitude, + vol.Required(CONF_LONGITUDE): cv.longitude, + vol.Optional(CONF_RADIUS, default=DEFAULT_RADIUS): vol.Coerce(float), + vol.Optional(CONF_PASSIVE, default=DEFAULT_PASSIVE): cv.boolean, + vol.Optional(CONF_ICON): cv.icon, +}) def active_zone(hass, latitude, longitude, radius=0): diff --git a/tests/components/test_zone.py b/tests/components/test_zone.py index 1c7c982a6eb..abc465bf9dd 100644 --- a/tests/components/test_zone.py +++ b/tests/components/test_zone.py @@ -1,6 +1,7 @@ """Test zone component.""" import unittest +from homeassistant import bootstrap from homeassistant.components import zone from tests.common import get_test_home_assistant @@ -26,7 +27,7 @@ class TestComponentZone(unittest.TestCase): 'radius': 250, 'passive': True } - assert zone.setup(self.hass, { + assert bootstrap.setup_component(self.hass, zone.DOMAIN, { 'zone': info }) @@ -39,7 +40,7 @@ class TestComponentZone(unittest.TestCase): def test_active_zone_skips_passive_zones(self): """Test active and passive zones.""" - assert zone.setup(self.hass, { + assert bootstrap.setup_component(self.hass, zone.DOMAIN, { 'zone': [ { 'name': 'Passive Zone', @@ -54,7 +55,8 @@ class TestComponentZone(unittest.TestCase): active = zone.active_zone(self.hass, 32.880600, -117.237561) assert active is None - assert zone.setup(self.hass, { + self.hass.config.components.remove('zone') + assert bootstrap.setup_component(self.hass, zone.DOMAIN, { 'zone': [ { 'name': 'Active Zone', @@ -72,7 +74,7 @@ class TestComponentZone(unittest.TestCase): """Test zone size preferences.""" latitude = 32.880600 longitude = -117.237561 - assert zone.setup(self.hass, { + assert bootstrap.setup_component(self.hass, zone.DOMAIN, { 'zone': [ { 'name': 'Small Zone', @@ -92,7 +94,8 @@ class TestComponentZone(unittest.TestCase): active = zone.active_zone(self.hass, latitude, longitude) assert 'zone.small_zone' == active.entity_id - assert zone.setup(self.hass, { + self.hass.config.components.remove('zone') + assert bootstrap.setup_component(self.hass, zone.DOMAIN, { 'zone': [ { 'name': 'Smallest Zone', @@ -110,7 +113,7 @@ class TestComponentZone(unittest.TestCase): """Test working in passive zones.""" latitude = 32.880600 longitude = -117.237561 - assert zone.setup(self.hass, { + assert bootstrap.setup_component(self.hass, zone.DOMAIN, { 'zone': [ { 'name': 'Passive Zone', From d7452f9d5d100d126d52dca3e46ae709fab8b339 Mon Sep 17 00:00:00 2001 From: Jeff Wilson Date: Fri, 16 Sep 2016 00:01:32 -0400 Subject: [PATCH 077/162] Add ability to set fan made to Nest climate component (#3399) * Add ability to set fan made to Nest climate component * Use constants for fan values * Use STATE_ON from cost * Fix lint error --- homeassistant/components/climate/nest.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/climate/nest.py index 1b3c38ce449..86b63d4229b 100644 --- a/homeassistant/components/climate/nest.py +++ b/homeassistant/components/climate/nest.py @@ -8,9 +8,10 @@ import logging import voluptuous as vol import homeassistant.components.nest as nest from homeassistant.components.climate import ( - STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA) + STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, ClimateDevice, + PLATFORM_SCHEMA) from homeassistant.const import ( - TEMP_CELSIUS, CONF_SCAN_INTERVAL, ATTR_TEMPERATURE) + TEMP_CELSIUS, CONF_SCAN_INTERVAL, ATTR_TEMPERATURE, STATE_ON) from homeassistant.util.temperature import convert as convert_temperature DEPENDENCIES = ['nest'] @@ -38,6 +39,7 @@ class NestThermostat(ClimateDevice): self._unit = temp_unit self.structure = structure self.device = device + self._fan_list = [STATE_ON, STATE_AUTO] @property def name(self): @@ -164,17 +166,18 @@ class NestThermostat(ClimateDevice): self.structure.away = False @property - def is_fan_on(self): + def current_fan_mode(self): """Return whether the fan is on.""" - return self.device.fan + return STATE_ON if self.device.fan else STATE_AUTO - def turn_fan_on(self): - """Turn fan on.""" - self.device.fan = True + @property + def fan_list(self): + """List of available fan modes.""" + return self._fan_list - def turn_fan_off(self): - """Turn fan off.""" - self.device.fan = False + def set_fan_mode(self, fan): + """Turn fan on/off.""" + self.device.fan = fan.lower() @property def min_temp(self): From 4076ccf639b7ca472e5fbecfc5f04f15805c231a Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 17 Sep 2016 10:29:58 -0700 Subject: [PATCH 078/162] Use setup_component in tests (#3414) * Alarm Control Panel Manual - use setup_component * Update automation - zone tests * Update climate - demo tests * Update climate - generic thermostat tests * Update cover - command line tests * Update cover - demo tests * Update device tracker tests * Update device tracker - owntracks tests * Update fan - demo tests * Update garage door - demo tests * Update light tests * Update lock - demo tests * Update media player - demo tests * Update notify - command line tests * Update notify - demo tests * Update notify - file tests * Update notify - group tests * Update sensor - mfi tests * Update sensor - moldindicator tests * Update sensor - mqtt room tests * Update switch - command line * Update switch - flux * Update switch tests * Update scene tests * Fix wrong default port for mfi switch --- .../components/alarm_control_panel/manual.py | 2 +- .../www_static/home-assistant-polymer | 2 +- homeassistant/components/sensor/mfi.py | 5 +- homeassistant/components/switch/mfi.py | 5 +- tests/common.py | 1 + .../alarm_control_panel/test_manual.py | 69 ++++++++++++------- tests/components/automation/test_zone.py | 14 ++-- tests/components/climate/test_demo.py | 8 ++- .../climate/test_generic_thermostat.py | 19 ++--- tests/components/cover/test_command_line.py | 3 +- tests/components/cover/test_demo.py | 3 +- tests/components/device_tracker/test_init.py | 39 +++++++---- .../device_tracker/test_owntracks.py | 3 +- tests/components/fan/test_demo.py | 3 +- tests/components/garage_door/test_demo.py | 3 +- tests/components/light/test_init.py | 12 ++-- tests/components/lock/test_demo.py | 3 +- tests/components/media_player/test_demo.py | 46 ++++++++++--- tests/components/notify/test_command_line.py | 10 +-- tests/components/notify/test_demo.py | 3 +- tests/components/notify/test_file.py | 3 +- tests/components/notify/test_group.py | 12 ++-- tests/components/sensor/test_mfi.py | 7 +- tests/components/sensor/test_moldindicator.py | 10 +-- tests/components/sensor/test_mqtt_room.py | 3 +- tests/components/switch/test_command_line.py | 9 +-- tests/components/switch/test_flux.py | 43 +++++++----- tests/components/switch/test_init.py | 12 ++-- tests/components/test_scene.py | 9 +-- 29 files changed, 220 insertions(+), 141 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py index a95eff20e1f..986f1966797 100644 --- a/homeassistant/components/alarm_control_panel/manual.py +++ b/homeassistant/components/alarm_control_panel/manual.py @@ -28,7 +28,7 @@ PLATFORM_SCHEMA = vol.Schema({ vol.Optional(CONF_NAME, default=DEFAULT_ALARM_NAME): cv.string, vol.Optional(CONF_CODE): cv.string, vol.Optional(CONF_PENDING_TIME, default=DEFAULT_PENDING_TIME): - vol.All(vol.Coerce(int), vol.Range(min=1)), + vol.All(vol.Coerce(int), vol.Range(min=0)), vol.Optional(CONF_TRIGGER_TIME, default=DEFAULT_TRIGGER_TIME): vol.All(vol.Coerce(int), vol.Range(min=1)), vol.Optional(CONF_DISARM_AFTER_TRIGGER, diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index ba588fc779d..bd6725d58d4 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit ba588fc779d34a2fbf7cc9a23103c38e3e3e0356 +Subproject commit bd6725d58d4493651a02184778d9f5d0a611698c diff --git a/homeassistant/components/sensor/mfi.py b/homeassistant/components/sensor/mfi.py index 0f06426a05b..af2c277f2cd 100644 --- a/homeassistant/components/sensor/mfi.py +++ b/homeassistant/components/sensor/mfi.py @@ -20,7 +20,6 @@ REQUIREMENTS = ['mficlient==0.3.0'] _LOGGER = logging.getLogger(__name__) -DEFAULT_PORT = 6443 DEFAULT_SSL = True DEFAULT_VERIFY_SSL = True @@ -43,7 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_PORT): cv.port, vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, }) @@ -57,7 +56,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): password = config.get(CONF_PASSWORD) use_tls = config.get(CONF_SSL) verify_tls = config.get(CONF_VERIFY_SSL) - default_port = use_tls and DEFAULT_PORT or 6080 + default_port = use_tls and 6443 or 6080 port = int(config.get(CONF_PORT, default_port)) from mficlient.client import FailedToLogin, MFiClient diff --git a/homeassistant/components/switch/mfi.py b/homeassistant/components/switch/mfi.py index 48e4741e770..81c60ce5803 100644 --- a/homeassistant/components/switch/mfi.py +++ b/homeassistant/components/switch/mfi.py @@ -19,7 +19,6 @@ REQUIREMENTS = ['mficlient==0.3.0'] _LOGGER = logging.getLogger(__name__) -DEFAULT_PORT = 6443 DEFAULT_SSL = True DEFAULT_VERIFY_SSL = True @@ -34,7 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_PORT): cv.port, vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, }) @@ -48,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): password = config.get(CONF_PASSWORD) use_tls = config.get(CONF_SSL) verify_tls = config.get(CONF_VERIFY_SSL) - default_port = use_tls and DEFAULT_PORT or 6080 + default_port = use_tls and 6443 or 6080 port = int(config.get(CONF_PORT, default_port)) from mficlient.client import FailedToLogin, MFiClient diff --git a/tests/common.py b/tests/common.py index 3f6b88d7a8a..e75de06013e 100644 --- a/tests/common.py +++ b/tests/common.py @@ -42,6 +42,7 @@ def get_test_home_assistant(num_threads=None): if num_threads: ha.MIN_WORKER_THREAD = orig_num_threads + hass.config.location_name = 'test home' hass.config.config_dir = get_test_config_dir() hass.config.latitude = 32.87336 hass.config.longitude = -117.22743 diff --git a/tests/components/alarm_control_panel/test_manual.py b/tests/components/alarm_control_panel/test_manual.py index 85d34002524..f033006c28c 100644 --- a/tests/components/alarm_control_panel/test_manual.py +++ b/tests/components/alarm_control_panel/test_manual.py @@ -3,6 +3,7 @@ from datetime import timedelta import unittest from unittest.mock import patch +from homeassistant.bootstrap import setup_component from homeassistant.const import ( STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED) @@ -27,8 +28,9 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_arm_home_no_pending(self): """Test arm home method.""" - self.assertTrue(alarm_control_panel.setup(self.hass, { - 'alarm_control_panel': { + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', 'code': CODE, @@ -49,8 +51,9 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_arm_home_with_pending(self): """Test arm home method.""" - self.assertTrue(alarm_control_panel.setup(self.hass, { - 'alarm_control_panel': { + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', 'code': CODE, @@ -80,8 +83,9 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_arm_home_with_invalid_code(self): """Attempt to arm home without a valid code.""" - self.assertTrue(alarm_control_panel.setup(self.hass, { - 'alarm_control_panel': { + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', 'code': CODE, @@ -102,8 +106,9 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_arm_away_no_pending(self): """Test arm home method.""" - self.assertTrue(alarm_control_panel.setup(self.hass, { - 'alarm_control_panel': { + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', 'code': CODE, @@ -124,8 +129,9 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_arm_away_with_pending(self): """Test arm home method.""" - self.assertTrue(alarm_control_panel.setup(self.hass, { - 'alarm_control_panel': { + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', 'code': CODE, @@ -155,8 +161,9 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_arm_away_with_invalid_code(self): """Attempt to arm away without a valid code.""" - self.assertTrue(alarm_control_panel.setup(self.hass, { - 'alarm_control_panel': { + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', 'code': CODE, @@ -176,12 +183,13 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.hass.states.get(entity_id).state) def test_trigger_no_pending(self): - """Test arm home method.""" - self.assertTrue(alarm_control_panel.setup(self.hass, { - 'alarm_control_panel': { + """Test triggering when no pending submitted method.""" + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', - 'trigger_time': 0, + 'trigger_time': 1, 'disarm_after_trigger': False }})) @@ -193,13 +201,23 @@ class TestAlarmControlPanelManual(unittest.TestCase): alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id) self.hass.block_till_done() + self.assertEqual(STATE_ALARM_PENDING, + self.hass.states.get(entity_id).state) + + future = dt_util.utcnow() + timedelta(seconds=60) + with patch(('homeassistant.components.alarm_control_panel.manual.' + 'dt_util.utcnow'), return_value=future): + fire_time_changed(self.hass, future) + self.hass.block_till_done() + self.assertEqual(STATE_ALARM_TRIGGERED, self.hass.states.get(entity_id).state) def test_trigger_with_pending(self): """Test arm home method.""" - self.assertTrue(alarm_control_panel.setup(self.hass, { - 'alarm_control_panel': { + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', 'pending_time': 2, @@ -238,8 +256,9 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_trigger_with_disarm_after_trigger(self): """Test disarm after trigger.""" - self.assertTrue(alarm_control_panel.setup(self.hass, { - 'alarm_control_panel': { + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', 'trigger_time': 5, @@ -269,8 +288,9 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_disarm_while_pending_trigger(self): """Test disarming while pending state.""" - self.assertTrue(alarm_control_panel.setup(self.hass, { - 'alarm_control_panel': { + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', 'trigger_time': 5, @@ -305,8 +325,9 @@ class TestAlarmControlPanelManual(unittest.TestCase): def test_disarm_during_trigger_with_invalid_code(self): """Test disarming while code is invalid.""" - self.assertTrue(alarm_control_panel.setup(self.hass, { - 'alarm_control_panel': { + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { 'platform': 'manual', 'name': 'test', 'pending_time': 5, diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py index a083b6bccd6..8041eceaeb3 100644 --- a/tests/components/automation/test_zone.py +++ b/tests/components/automation/test_zone.py @@ -1,7 +1,7 @@ """The tests for the location automation.""" import unittest -from homeassistant.bootstrap import _setup_component +from homeassistant.bootstrap import setup_component from homeassistant.components import automation, zone from tests.common import get_test_home_assistant @@ -14,7 +14,7 @@ class TestAutomationZone(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.config.components.append('group') - zone.setup(self.hass, { + assert setup_component(self.hass, zone.DOMAIN, { 'zone': { 'name': 'test', 'latitude': 32.880837, @@ -42,7 +42,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.block_till_done() - assert _setup_component(self.hass, automation.DOMAIN, { + assert setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'zone', @@ -100,7 +100,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.block_till_done() - assert _setup_component(self.hass, automation.DOMAIN, { + assert setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'zone', @@ -130,7 +130,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.block_till_done() - assert _setup_component(self.hass, automation.DOMAIN, { + assert setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'zone', @@ -160,7 +160,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.block_till_done() - assert _setup_component(self.hass, automation.DOMAIN, { + assert setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'zone', @@ -190,7 +190,7 @@ class TestAutomationZone(unittest.TestCase): }) self.hass.block_till_done() - assert _setup_component(self.hass, automation.DOMAIN, { + assert setup_component(self.hass, automation.DOMAIN, { automation.DOMAIN: { 'trigger': { 'platform': 'event', diff --git a/tests/components/climate/test_demo.py b/tests/components/climate/test_demo.py index d6bb2c8d69d..cd05dcb2b4e 100644 --- a/tests/components/climate/test_demo.py +++ b/tests/components/climate/test_demo.py @@ -4,6 +4,7 @@ import unittest from homeassistant.util.unit_system import ( METRIC_SYSTEM, ) +from homeassistant.bootstrap import setup_component from homeassistant.components import climate from tests.common import get_test_home_assistant @@ -20,9 +21,10 @@ class TestDemoClimate(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.config.units = METRIC_SYSTEM - self.assertTrue(climate.setup(self.hass, {'climate': { - 'platform': 'demo', - }})) + self.assertTrue(setup_component(self.hass, climate.DOMAIN, { + 'climate': { + 'platform': 'demo', + }})) def tearDown(self): # pylint: disable=invalid-name """Stop down everything that was started.""" diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py index 1447d9f7919..e399749a027 100644 --- a/tests/components/climate/test_generic_thermostat.py +++ b/tests/components/climate/test_generic_thermostat.py @@ -4,7 +4,7 @@ import unittest from unittest import mock -from homeassistant.bootstrap import _setup_component +from homeassistant.bootstrap import setup_component from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, SERVICE_TURN_OFF, @@ -44,12 +44,12 @@ class TestSetupClimateGenericThermostat(unittest.TestCase): 'name': 'test', 'target_sensor': ENT_SENSOR } - self.assertFalse(_setup_component(self.hass, 'climate', { + self.assertFalse(setup_component(self.hass, 'climate', { 'climate': config})) def test_valid_conf(self): """Test set up genreic_thermostat with valid config values.""" - self.assertTrue(_setup_component(self.hass, 'climate', + self.assertTrue(setup_component(self.hass, 'climate', {'climate': { 'platform': 'generic_thermostat', 'name': 'test', @@ -61,7 +61,7 @@ class TestSetupClimateGenericThermostat(unittest.TestCase): self.hass.states.set(ENT_SENSOR, 22.0, { ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS }) - climate.setup(self.hass, {'climate': { + assert setup_component(self.hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', 'heater': ENT_SWITCH, @@ -80,7 +80,7 @@ class TestClimateGenericThermostat(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.config.units = METRIC_SYSTEM - climate.setup(self.hass, {'climate': { + assert setup_component(self.hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', 'heater': ENT_SWITCH, @@ -104,7 +104,8 @@ class TestClimateGenericThermostat(unittest.TestCase): def test_custom_setup_params(self): """Test the setup with custom parameters.""" - climate.setup(self.hass, {'climate': { + self.hass.config.components.remove(climate.DOMAIN) + assert setup_component(self.hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', 'heater': ENT_SWITCH, @@ -229,7 +230,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.config.temperature_unit = TEMP_CELSIUS - climate.setup(self.hass, {'climate': { + assert setup_component(self.hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', 'heater': ENT_SWITCH, @@ -319,7 +320,7 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.config.temperature_unit = TEMP_CELSIUS - climate.setup(self.hass, {'climate': { + assert setup_component(self.hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', 'heater': ENT_SWITCH, @@ -410,7 +411,7 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.hass.config.temperature_unit = TEMP_CELSIUS - climate.setup(self.hass, {'climate': { + assert setup_component(self.hass, climate.DOMAIN, {'climate': { 'platform': 'generic_thermostat', 'name': 'test', 'heater': ENT_SWITCH, diff --git a/tests/components/cover/test_command_line.py b/tests/components/cover/test_command_line.py index dc9ec105431..f687094a038 100644 --- a/tests/components/cover/test_command_line.py +++ b/tests/components/cover/test_command_line.py @@ -5,6 +5,7 @@ import tempfile import unittest from unittest import mock +from homeassistant.bootstrap import setup_component import homeassistant.components.cover as cover from homeassistant.components.cover import ( command_line as cmd_rs) @@ -52,7 +53,7 @@ class TestCommandCover(unittest.TestCase): 'command_stop': 'echo 0 > {}'.format(path), 'value_template': '{{ value }}' } - self.assertTrue(cover.setup(self.hass, { + self.assertTrue(setup_component(self.hass, cover.DOMAIN, { 'cover': { 'platform': 'command_line', 'covers': { diff --git a/tests/components/cover/test_demo.py b/tests/components/cover/test_demo.py index f51ea66c743..1cf15d0d684 100644 --- a/tests/components/cover/test_demo.py +++ b/tests/components/cover/test_demo.py @@ -3,6 +3,7 @@ import unittest from datetime import timedelta import homeassistant.util.dt as dt_util +from homeassistant.bootstrap import setup_component from homeassistant.components import cover from tests.common import get_test_home_assistant, fire_time_changed @@ -15,7 +16,7 @@ class TestCoverDemo(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.assertTrue(cover.setup(self.hass, {'cover': { + self.assertTrue(setup_component(self.hass, cover.DOMAIN, {'cover': { 'platform': 'demo', }})) diff --git a/tests/components/device_tracker/test_init.py b/tests/components/device_tracker/test_init.py index b0ca306001b..9490938a646 100644 --- a/tests/components/device_tracker/test_init.py +++ b/tests/components/device_tracker/test_init.py @@ -6,6 +6,7 @@ from unittest.mock import patch from datetime import datetime, timedelta import os +from homeassistant.bootstrap import setup_component from homeassistant.loader import get_component import homeassistant.util.dt as dt_util from homeassistant.const import ( @@ -76,7 +77,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): 'AB:CD:EF:GH:IJ', 'Test name', picture='http://test.picture', away_hide=True) device_tracker.update_config(self.yaml_devices, dev_id, device) - self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM)) + self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, + TEST_PLATFORM)) config = device_tracker.load_config(self.yaml_devices, self.hass, device.consider_home)[0] self.assertEqual(device.dev_id, config.dev_id) @@ -120,7 +122,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): def test_setup_without_yaml_file(self): """Test with no YAML file.""" - self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM)) + self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, + TEST_PLATFORM)) def test_adding_unknown_device_to_config(self): \ # pylint: disable=invalid-name @@ -129,7 +132,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): scanner.reset() scanner.come_home('DEV1') - self.assertTrue(device_tracker.setup(self.hass, { + self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, { device_tracker.DOMAIN: {CONF_PLATFORM: 'test'}})) config = device_tracker.load_config(self.yaml_devices, self.hass, timedelta(seconds=0)) @@ -164,8 +167,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): with patch.dict(device_tracker.DISCOVERY_PLATFORMS, {'test': 'test'}): with patch.object(scanner, 'scan_devices') as mock_scan: - self.assertTrue(device_tracker.setup(self.hass, - TEST_PLATFORM)) + self.assertTrue(setup_component( + self.hass, device_tracker.DOMAIN, TEST_PLATFORM)) fire_service_discovered(self.hass, 'test', {}) self.assertTrue(mock_scan.called) @@ -180,7 +183,7 @@ class TestComponentsDeviceTracker(unittest.TestCase): with patch('homeassistant.components.device_tracker.dt_util.utcnow', return_value=register_time): - self.assertTrue(device_tracker.setup(self.hass, { + self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, { device_tracker.DOMAIN: { CONF_PLATFORM: 'test', device_tracker.CONF_CONSIDER_HOME: 59, @@ -211,7 +214,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): friendly_name, picture, away_hide=True) device_tracker.update_config(self.yaml_devices, dev_id, device) - self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM)) + self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, + TEST_PLATFORM)) attrs = self.hass.states.get(entity_id).attributes @@ -230,7 +234,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): scanner = get_component('device_tracker.test').SCANNER scanner.reset() - self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM)) + self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, + TEST_PLATFORM)) self.assertTrue(self.hass.states.get(entity_id) .attributes.get(ATTR_HIDDEN)) @@ -247,7 +252,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): scanner = get_component('device_tracker.test').SCANNER scanner.reset() - self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM)) + self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, + TEST_PLATFORM)) state = self.hass.states.get(device_tracker.ENTITY_ID_ALL_DEVICES) self.assertIsNotNone(state) @@ -258,7 +264,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): @patch('homeassistant.components.device_tracker.DeviceTracker.see') def test_see_service(self, mock_see): """Test the see service with a unicode dev_id and NO MAC.""" - self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM)) + self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, + TEST_PLATFORM)) params = { 'dev_id': 'some_device', 'host_name': 'example.com', @@ -281,7 +288,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): def test_not_write_duplicate_yaml_keys(self): \ # pylint: disable=invalid-name """Test that the device tracker will not generate invalid YAML.""" - self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM)) + self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, + TEST_PLATFORM)) device_tracker.see(self.hass, 'mac_1', host_name='hello') device_tracker.see(self.hass, 'mac_2', host_name='hello') @@ -294,7 +302,8 @@ class TestComponentsDeviceTracker(unittest.TestCase): def test_not_allow_invalid_dev_id(self): # pylint: disable=invalid-name """Test that the device tracker will not allow invalid dev ids.""" - self.assertTrue(device_tracker.setup(self.hass, TEST_PLATFORM)) + self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, + TEST_PLATFORM)) device_tracker.see(self.hass, dev_id='hello-world') @@ -329,6 +338,6 @@ class TestComponentsDeviceTracker(unittest.TestCase): @patch('homeassistant.components.device_tracker.log_exception') def test_config_failure(self, mock_ex): """Test that the device tracker see failures.""" - device_tracker.setup(self.hass, {device_tracker.DOMAIN: { - device_tracker.CONF_CONSIDER_HOME: -1}}) - assert mock_ex.call_count == 1 + assert not setup_component(self.hass, device_tracker.DOMAIN, + {device_tracker.DOMAIN: { + device_tracker.CONF_CONSIDER_HOME: -1}}) diff --git a/tests/components/device_tracker/test_owntracks.py b/tests/components/device_tracker/test_owntracks.py index f59c9cb39fc..2d2495aa68f 100644 --- a/tests/components/device_tracker/test_owntracks.py +++ b/tests/components/device_tracker/test_owntracks.py @@ -5,6 +5,7 @@ import unittest from collections import defaultdict +from homeassistant.bootstrap import setup_component from homeassistant.components import device_tracker from homeassistant.const import (STATE_NOT_HOME, CONF_PLATFORM) import homeassistant.components.device_tracker.owntracks as owntracks @@ -191,7 +192,7 @@ class TestDeviceTrackerOwnTracks(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() mock_mqtt_component(self.hass) - self.assertTrue(device_tracker.setup(self.hass, { + self.assertTrue(setup_component(self.hass, device_tracker.DOMAIN, { device_tracker.DOMAIN: { CONF_PLATFORM: 'owntracks', CONF_MAX_GPS_ACCURACY: 200, diff --git a/tests/components/fan/test_demo.py b/tests/components/fan/test_demo.py index 3d0e7dabff8..81e03c13705 100644 --- a/tests/components/fan/test_demo.py +++ b/tests/components/fan/test_demo.py @@ -2,6 +2,7 @@ import unittest +from homeassistant.bootstrap import setup_component from homeassistant.components import fan from homeassistant.components.fan.demo import FAN_ENTITY_ID from homeassistant.const import STATE_OFF, STATE_ON @@ -19,7 +20,7 @@ class TestDemoFan(unittest.TestCase): def setUp(self): """Initialize unit test data.""" self.hass = get_test_home_assistant() - self.assertTrue(fan.setup(self.hass, {'fan': { + self.assertTrue(setup_component(self.hass, fan.DOMAIN, {'fan': { 'platform': 'demo', }})) self.hass.block_till_done() diff --git a/tests/components/garage_door/test_demo.py b/tests/components/garage_door/test_demo.py index ae0a346676e..e282d697daf 100644 --- a/tests/components/garage_door/test_demo.py +++ b/tests/components/garage_door/test_demo.py @@ -1,6 +1,7 @@ """The tests for the Demo Garage door platform.""" import unittest +from homeassistant.bootstrap import setup_component import homeassistant.components.garage_door as gd from tests.common import get_test_home_assistant @@ -16,7 +17,7 @@ class TestGarageDoorDemo(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.assertTrue(gd.setup(self.hass, { + self.assertTrue(setup_component(self.hass, gd.DOMAIN, { 'garage_door': { 'platform': 'demo' } diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py index b24cbf53ba7..c0f2c532156 100644 --- a/tests/components/light/test_init.py +++ b/tests/components/light/test_init.py @@ -3,6 +3,7 @@ import unittest import os +from homeassistant.bootstrap import setup_component import homeassistant.loader as loader from homeassistant.const import ( ATTR_ENTITY_ID, STATE_ON, STATE_OFF, CONF_PLATFORM, @@ -112,7 +113,8 @@ class TestLight(unittest.TestCase): platform.init() self.assertTrue( - light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}})) dev1, dev2, dev3 = platform.DEVICES @@ -250,8 +252,8 @@ class TestLight(unittest.TestCase): user_file.write('id,x,y,brightness\n') user_file.write('I,WILL,NOT,WORK\n') - self.assertFalse(light.setup( - self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}} + self.assertFalse(setup_component( + self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: 'test'}} )) def test_light_profiles(self): @@ -265,8 +267,8 @@ class TestLight(unittest.TestCase): user_file.write('id,x,y,brightness\n') user_file.write('test,.4,.6,100\n') - self.assertTrue(light.setup( - self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}} + self.assertTrue(setup_component( + self.hass, light.DOMAIN, {light.DOMAIN: {CONF_PLATFORM: 'test'}} )) dev1, dev2, dev3 = platform.DEVICES diff --git a/tests/components/lock/test_demo.py b/tests/components/lock/test_demo.py index 71ac6b40aab..e7a086ad51a 100644 --- a/tests/components/lock/test_demo.py +++ b/tests/components/lock/test_demo.py @@ -1,6 +1,7 @@ """The tests for the Demo lock platform.""" import unittest +from homeassistant.bootstrap import setup_component from homeassistant.components import lock from tests.common import get_test_home_assistant @@ -16,7 +17,7 @@ class TestLockDemo(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.assertTrue(lock.setup(self.hass, { + self.assertTrue(setup_component(self.hass, lock.DOMAIN, { 'lock': { 'platform': 'demo' } diff --git a/tests/components/media_player/test_demo.py b/tests/components/media_player/test_demo.py index 97e7abc9818..b49502054f1 100644 --- a/tests/components/media_player/test_demo.py +++ b/tests/components/media_player/test_demo.py @@ -1,7 +1,8 @@ """The tests for the Demo Media player platform.""" import unittest from unittest.mock import patch -from homeassistant import bootstrap + +from homeassistant.bootstrap import setup_component from homeassistant.const import HTTP_HEADER_HA_AUTH import homeassistant.components.media_player as mp import homeassistant.components.http as http @@ -27,7 +28,7 @@ def setUpModule(): # pylint: disable=invalid-name global hass hass = get_test_home_assistant() - bootstrap.setup_component(hass, http.DOMAIN, { + setup_component(hass, http.DOMAIN, { http.DOMAIN: { http.CONF_SERVER_PORT: SERVER_PORT, http.CONF_API_PASSWORD: API_PASSWORD, @@ -49,13 +50,19 @@ class TestDemoMediaPlayer(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = hass + try: + self.hass.config.components.remove(mp.DOMAIN) + except ValueError: + pass def test_source_select(self): """Test the input source service.""" entity_id = 'media_player.lounge_room' - assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) + assert setup_component( + self.hass, mp.DOMAIN, + {'media_player': {'platform': 'demo'}}) state = self.hass.states.get(entity_id) assert 'dvd' == state.attributes.get('source') @@ -71,7 +78,9 @@ class TestDemoMediaPlayer(unittest.TestCase): def test_clear_playlist(self): """Test clear playlist.""" - assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) + assert setup_component( + self.hass, mp.DOMAIN, + {'media_player': {'platform': 'demo'}}) assert self.hass.states.is_state(entity_id, 'playing') mp.clear_playlist(self.hass, entity_id) @@ -80,8 +89,11 @@ class TestDemoMediaPlayer(unittest.TestCase): def test_volume_services(self): """Test the volume service.""" - assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) + assert setup_component( + self.hass, mp.DOMAIN, + {'media_player': {'platform': 'demo'}}) state = self.hass.states.get(entity_id) + print(state) assert 1.0 == state.attributes.get('volume_level') mp.set_volume_level(self.hass, None, entity_id) @@ -118,7 +130,9 @@ class TestDemoMediaPlayer(unittest.TestCase): def test_turning_off_and_on(self): """Test turn_on and turn_off.""" - assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) + assert setup_component( + self.hass, mp.DOMAIN, + {'media_player': {'platform': 'demo'}}) assert self.hass.states.is_state(entity_id, 'playing') mp.turn_off(self.hass, entity_id) @@ -137,7 +151,9 @@ class TestDemoMediaPlayer(unittest.TestCase): def test_playing_pausing(self): """Test media_pause.""" - assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) + assert setup_component( + self.hass, mp.DOMAIN, + {'media_player': {'platform': 'demo'}}) assert self.hass.states.is_state(entity_id, 'playing') mp.media_pause(self.hass, entity_id) @@ -158,7 +174,9 @@ class TestDemoMediaPlayer(unittest.TestCase): def test_prev_next_track(self): """Test media_next_track and media_previous_track .""" - assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) + assert setup_component( + self.hass, mp.DOMAIN, + {'media_player': {'platform': 'demo'}}) state = self.hass.states.get(entity_id) assert 1 == state.attributes.get('media_track') assert 0 == (mp.SUPPORT_PREVIOUS_TRACK & @@ -185,7 +203,9 @@ class TestDemoMediaPlayer(unittest.TestCase): assert 0 < (mp.SUPPORT_PREVIOUS_TRACK & state.attributes.get('supported_media_commands')) - assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) + assert setup_component( + self.hass, mp.DOMAIN, + {'media_player': {'platform': 'demo'}}) ent_id = 'media_player.lounge_room' state = self.hass.states.get(ent_id) assert 1 == state.attributes.get('media_episode') @@ -212,7 +232,9 @@ class TestDemoMediaPlayer(unittest.TestCase): fake_picture_data = 'test.test' m.get('https://graph.facebook.com/v2.5/107771475912710/' 'picture?type=large', text=fake_picture_data) - assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) + assert setup_component( + self.hass, mp.DOMAIN, + {'media_player': {'platform': 'demo'}}) assert self.hass.states.is_state(entity_id, 'playing') state = self.hass.states.get(entity_id) req = requests.get(HTTP_BASE_URL + @@ -223,7 +245,9 @@ class TestDemoMediaPlayer(unittest.TestCase): 'media_seek') def test_play_media(self, mock_seek): """Test play_media .""" - assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}}) + assert setup_component( + self.hass, mp.DOMAIN, + {'media_player': {'platform': 'demo'}}) ent_id = 'media_player.living_room' state = self.hass.states.get(ent_id) assert 0 < (mp.SUPPORT_PLAY_MEDIA & diff --git a/tests/components/notify/test_command_line.py b/tests/components/notify/test_command_line.py index f904fe744f3..0d2235514f8 100644 --- a/tests/components/notify/test_command_line.py +++ b/tests/components/notify/test_command_line.py @@ -4,8 +4,8 @@ import tempfile import unittest from unittest.mock import patch +from homeassistant.bootstrap import setup_component import homeassistant.components.notify as notify -from homeassistant import bootstrap from tests.common import get_test_home_assistant @@ -22,7 +22,7 @@ class TestCommandLine(unittest.TestCase): def test_setup(self): """Test setup.""" - assert bootstrap.setup_component(self.hass, 'notify', { + assert setup_component(self.hass, 'notify', { 'notify': { 'name': 'test', 'platform': 'command_line', @@ -31,7 +31,7 @@ class TestCommandLine(unittest.TestCase): def test_bad_config(self): """Test set up the platform with bad/missing configuration.""" - self.assertFalse(notify.setup(self.hass, { + self.assertFalse(setup_component(self.hass, notify.DOMAIN, { 'notify': { 'name': 'test', 'platform': 'bad_platform', @@ -43,7 +43,7 @@ class TestCommandLine(unittest.TestCase): with tempfile.TemporaryDirectory() as tempdirname: filename = os.path.join(tempdirname, 'message.txt') message = 'one, two, testing, testing' - self.assertTrue(notify.setup(self.hass, { + self.assertTrue(setup_component(self.hass, notify.DOMAIN, { 'notify': { 'name': 'test', 'platform': 'command_line', @@ -63,7 +63,7 @@ class TestCommandLine(unittest.TestCase): @patch('homeassistant.components.notify.command_line._LOGGER.error') def test_error_for_none_zero_exit_code(self, mock_error): """Test if an error is logged for non zero exit codes.""" - self.assertTrue(notify.setup(self.hass, { + self.assertTrue(setup_component(self.hass, notify.DOMAIN, { 'notify': { 'name': 'test', 'platform': 'command_line', diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py index 70fbe0a1d79..ddf5644c399 100644 --- a/tests/components/notify/test_demo.py +++ b/tests/components/notify/test_demo.py @@ -2,6 +2,7 @@ import tempfile import unittest +from homeassistant.bootstrap import setup_component import homeassistant.components.notify as notify from homeassistant.components.notify import demo from homeassistant.helpers import script @@ -16,7 +17,7 @@ class TestNotifyDemo(unittest.TestCase): def setUp(self): # pylint: disable=invalid-name """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.assertTrue(notify.setup(self.hass, { + self.assertTrue(setup_component(self.hass, notify.DOMAIN, { 'notify': { 'platform': 'demo' } diff --git a/tests/components/notify/test_file.py b/tests/components/notify/test_file.py index 2564b0bd65a..eaca5c3d962 100644 --- a/tests/components/notify/test_file.py +++ b/tests/components/notify/test_file.py @@ -4,6 +4,7 @@ import unittest import tempfile from unittest.mock import patch +from homeassistant.bootstrap import setup_component import homeassistant.components.notify as notify from homeassistant.components.notify import ( ATTR_TITLE_DEFAULT) @@ -41,7 +42,7 @@ class TestNotifyFile(unittest.TestCase): with tempfile.TemporaryDirectory() as tempdirname: filename = os.path.join(tempdirname, 'notify.txt') message = 'one, two, testing, testing' - self.assertTrue(notify.setup(self.hass, { + self.assertTrue(setup_component(self.hass, notify.DOMAIN, { 'notify': { 'name': 'test', 'platform': 'file', diff --git a/tests/components/notify/test_group.py b/tests/components/notify/test_group.py index 951200efb08..f1289a3dec1 100644 --- a/tests/components/notify/test_group.py +++ b/tests/components/notify/test_group.py @@ -1,6 +1,7 @@ """The tests for the notify.group platform.""" import unittest +from homeassistant.bootstrap import setup_component import homeassistant.components.notify as notify from homeassistant.components.notify import group @@ -14,17 +15,14 @@ class TestNotifyGroup(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() self.events = [] - self.assertTrue(notify.setup(self.hass, { - 'notify': { + self.assertTrue(setup_component(self.hass, notify.DOMAIN, { + 'notify': [{ 'name': 'demo1', 'platform': 'demo' - } - })) - self.assertTrue(notify.setup(self.hass, { - 'notify': { + }, { 'name': 'demo2', 'platform': 'demo' - } + }] })) self.service = group.get_service(self.hass, {'services': [ diff --git a/tests/components/sensor/test_mfi.py b/tests/components/sensor/test_mfi.py index c1e6ac899ec..957ddb7be75 100644 --- a/tests/components/sensor/test_mfi.py +++ b/tests/components/sensor/test_mfi.py @@ -4,6 +4,7 @@ import unittest.mock as mock import requests +from homeassistant.bootstrap import setup_component import homeassistant.components.sensor as sensor import homeassistant.components.sensor.mfi as mfi from homeassistant.const import TEMP_CELSIUS @@ -69,7 +70,7 @@ class TestMfiSensorSetup(unittest.TestCase): """Test setup with minimum configuration.""" config = dict(self.GOOD_CONFIG) del config[self.THING]['port'] - assert self.COMPONENT.setup(self.hass, config) + assert setup_component(self.hass, self.COMPONENT.DOMAIN, config) mock_client.assert_called_once_with( 'foo', 'user', 'pass', port=6443, use_tls=True, verify=True) @@ -78,7 +79,7 @@ class TestMfiSensorSetup(unittest.TestCase): """Test setup with port.""" config = dict(self.GOOD_CONFIG) config[self.THING]['port'] = 6123 - assert self.COMPONENT.setup(self.hass, config) + assert setup_component(self.hass, self.COMPONENT.DOMAIN, config) mock_client.assert_called_once_with( 'foo', 'user', 'pass', port=6123, use_tls=True, verify=True) @@ -89,7 +90,7 @@ class TestMfiSensorSetup(unittest.TestCase): del config[self.THING]['port'] config[self.THING]['ssl'] = False config[self.THING]['verify_ssl'] = False - assert self.COMPONENT.setup(self.hass, config) + assert setup_component(self.hass, self.COMPONENT.DOMAIN, config) mock_client.assert_called_once_with( 'foo', 'user', 'pass', port=6080, use_tls=False, verify=False) diff --git a/tests/components/sensor/test_moldindicator.py b/tests/components/sensor/test_moldindicator.py index 23dd07f1190..3b2eaabac9c 100644 --- a/tests/components/sensor/test_moldindicator.py +++ b/tests/components/sensor/test_moldindicator.py @@ -1,6 +1,7 @@ """The tests for the MoldIndicator sensor.""" import unittest +from homeassistant.bootstrap import setup_component import homeassistant.components.sensor as sensor from homeassistant.components.sensor.mold_indicator import (ATTR_DEWPOINT, ATTR_CRITICAL_TEMP) @@ -22,7 +23,6 @@ class TestSensorMoldIndicator(unittest.TestCase): {ATTR_UNIT_OF_MEASUREMENT: TEMP_CELSIUS}) self.hass.states.set('test.indoorhumidity', '50', {ATTR_UNIT_OF_MEASUREMENT: '%'}) - self.hass.block_till_done() def tearDown(self): """Stop down everything that was started.""" @@ -30,7 +30,7 @@ class TestSensorMoldIndicator(unittest.TestCase): def test_setup(self): """Test the mold indicator sensor setup.""" - self.assertTrue(sensor.setup(self.hass, { + self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { 'sensor': { 'platform': 'mold_indicator', 'indoor_temp_sensor': 'test.indoortemp', @@ -53,7 +53,7 @@ class TestSensorMoldIndicator(unittest.TestCase): self.hass.states.set('test.indoorhumidity', '0', {ATTR_UNIT_OF_MEASUREMENT: '%'}) - self.assertTrue(sensor.setup(self.hass, { + self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { 'sensor': { 'platform': 'mold_indicator', 'indoor_temp_sensor': 'test.indoortemp', @@ -68,7 +68,7 @@ class TestSensorMoldIndicator(unittest.TestCase): def test_calculation(self): """Test the mold indicator internal calculations.""" - self.assertTrue(sensor.setup(self.hass, { + self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { 'sensor': { 'platform': 'mold_indicator', 'indoor_temp_sensor': 'test.indoortemp', @@ -100,7 +100,7 @@ class TestSensorMoldIndicator(unittest.TestCase): def test_sensor_changed(self): """Test the sensor_changed function.""" - self.assertTrue(sensor.setup(self.hass, { + self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { 'sensor': { 'platform': 'mold_indicator', 'indoor_temp_sensor': 'test.indoortemp', diff --git a/tests/components/sensor/test_mqtt_room.py b/tests/components/sensor/test_mqtt_room.py index db885b54434..e85057d827c 100644 --- a/tests/components/sensor/test_mqtt_room.py +++ b/tests/components/sensor/test_mqtt_room.py @@ -4,6 +4,7 @@ import datetime import unittest from unittest.mock import patch +from homeassistant.bootstrap import setup_component import homeassistant.components.sensor as sensor from homeassistant.components.mqtt import (CONF_STATE_TOPIC, CONF_QOS, DEFAULT_QOS) @@ -52,7 +53,7 @@ class TestMQTTRoomSensor(unittest.TestCase): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() mock_mqtt_component(self.hass) - self.assertTrue(sensor.setup(self.hass, { + self.assertTrue(setup_component(self.hass, sensor.DOMAIN, { sensor.DOMAIN: { CONF_PLATFORM: 'mqtt_room', CONF_NAME: NAME, diff --git a/tests/components/switch/test_command_line.py b/tests/components/switch/test_command_line.py index bbc7214f8e1..e0f81ec9ee0 100644 --- a/tests/components/switch/test_command_line.py +++ b/tests/components/switch/test_command_line.py @@ -4,6 +4,7 @@ import os import tempfile import unittest +from homeassistant.bootstrap import setup_component from homeassistant.const import STATE_ON, STATE_OFF import homeassistant.components.switch as switch import homeassistant.components.switch.command_line as command_line @@ -30,7 +31,7 @@ class TestCommandSwitch(unittest.TestCase): 'command_on': 'echo 1 > {}'.format(path), 'command_off': 'echo 0 > {}'.format(path), } - self.assertTrue(switch.setup(self.hass, { + self.assertTrue(setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'command_line', 'switches': { @@ -64,7 +65,7 @@ class TestCommandSwitch(unittest.TestCase): 'command_off': 'echo 0 > {}'.format(path), 'value_template': '{{ value=="1" }}' } - self.assertTrue(switch.setup(self.hass, { + self.assertTrue(setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'command_line', 'switches': { @@ -100,7 +101,7 @@ class TestCommandSwitch(unittest.TestCase): 'command_off': 'echo \'{}\' > {}'.format(offcmd, path), 'value_template': '{{ value_json.status=="ok" }}' } - self.assertTrue(switch.setup(self.hass, { + self.assertTrue(setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'command_line', 'switches': { @@ -133,7 +134,7 @@ class TestCommandSwitch(unittest.TestCase): 'command_on': 'echo 1 > {}'.format(path), 'command_off': 'echo 0 > {}'.format(path), } - self.assertTrue(switch.setup(self.hass, { + self.assertTrue(setup_component(self.hass, switch.DOMAIN, { 'switch': { 'platform': 'command_line', 'switches': { diff --git a/tests/components/switch/test_flux.py b/tests/components/switch/test_flux.py index c3d13d83a85..ffb413ada4c 100644 --- a/tests/components/switch/test_flux.py +++ b/tests/components/switch/test_flux.py @@ -3,7 +3,7 @@ import unittest from datetime import timedelta from unittest.mock import patch -from homeassistant.bootstrap import _setup_component, setup_component +from homeassistant.bootstrap import setup_component from homeassistant.components import switch, light from homeassistant.const import CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON import homeassistant.loader as loader @@ -25,7 +25,7 @@ class TestSwitchFlux(unittest.TestCase): def test_valid_config(self): """Test configuration.""" - assert _setup_component(self.hass, 'switch', { + assert setup_component(self.hass, 'switch', { 'switch': { 'platform': 'flux', 'name': 'flux', @@ -35,7 +35,7 @@ class TestSwitchFlux(unittest.TestCase): def test_valid_config_with_info(self): """Test configuration.""" - assert _setup_component(self.hass, 'switch', { + assert setup_component(self.hass, 'switch', { 'switch': { 'platform': 'flux', 'name': 'flux', @@ -50,7 +50,7 @@ class TestSwitchFlux(unittest.TestCase): def test_valid_config_no_name(self): """Test configuration.""" - assert _setup_component(self.hass, 'switch', { + assert setup_component(self.hass, 'switch', { 'switch': { 'platform': 'flux', 'lights': ['light.desk', 'light.lamp'] @@ -59,7 +59,7 @@ class TestSwitchFlux(unittest.TestCase): def test_invalid_config_no_lights(self): """Test configuration.""" - assert not _setup_component(self.hass, 'switch', { + assert not setup_component(self.hass, 'switch', { 'switch': { 'platform': 'flux', 'name': 'flux' @@ -71,7 +71,8 @@ class TestSwitchFlux(unittest.TestCase): platform = loader.get_component('light.test') platform.init() self.assertTrue( - light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}})) dev1 = platform.DEVICES[0] @@ -110,7 +111,8 @@ class TestSwitchFlux(unittest.TestCase): platform = loader.get_component('light.test') platform.init() self.assertTrue( - light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}})) dev1 = platform.DEVICES[0] @@ -154,7 +156,8 @@ class TestSwitchFlux(unittest.TestCase): platform = loader.get_component('light.test') platform.init() self.assertTrue( - light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}})) dev1 = platform.DEVICES[0] @@ -198,7 +201,8 @@ class TestSwitchFlux(unittest.TestCase): platform = loader.get_component('light.test') platform.init() self.assertTrue( - light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}})) dev1 = platform.DEVICES[0] @@ -242,7 +246,8 @@ class TestSwitchFlux(unittest.TestCase): platform = loader.get_component('light.test') platform.init() self.assertTrue( - light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}})) dev1 = platform.DEVICES[0] @@ -286,7 +291,8 @@ class TestSwitchFlux(unittest.TestCase): platform = loader.get_component('light.test') platform.init() self.assertTrue( - light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}})) dev1 = platform.DEVICES[0] @@ -332,7 +338,8 @@ class TestSwitchFlux(unittest.TestCase): platform = loader.get_component('light.test') platform.init() self.assertTrue( - light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}})) dev1 = platform.DEVICES[0] @@ -378,7 +385,8 @@ class TestSwitchFlux(unittest.TestCase): platform = loader.get_component('light.test') platform.init() self.assertTrue( - light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}})) dev1 = platform.DEVICES[0] @@ -422,7 +430,8 @@ class TestSwitchFlux(unittest.TestCase): platform = loader.get_component('light.test') platform.init() self.assertTrue( - light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}})) dev1, dev2, dev3 = platform.DEVICES light.turn_on(self.hass, entity_id=dev2.entity_id) @@ -486,7 +495,8 @@ class TestSwitchFlux(unittest.TestCase): platform = loader.get_component('light.test') platform.init() self.assertTrue( - light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}})) dev1 = platform.DEVICES[0] @@ -528,7 +538,8 @@ class TestSwitchFlux(unittest.TestCase): platform = loader.get_component('light.test') platform.init() self.assertTrue( - light.setup(self.hass, {light.DOMAIN: {CONF_PLATFORM: 'test'}})) + setup_component(self.hass, light.DOMAIN, + {light.DOMAIN: {CONF_PLATFORM: 'test'}})) dev1 = platform.DEVICES[0] diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py index 5d662cbc943..4909ae20112 100644 --- a/tests/components/switch/test_init.py +++ b/tests/components/switch/test_init.py @@ -2,6 +2,7 @@ # pylint: disable=too-many-public-methods,protected-access import unittest +from homeassistant.bootstrap import setup_component from homeassistant import loader from homeassistant.components import switch from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM @@ -17,10 +18,6 @@ class TestSwitch(unittest.TestCase): self.hass = get_test_home_assistant() platform = loader.get_component('switch.test') platform.init() - self.assertTrue(switch.setup( - self.hass, {switch.DOMAIN: {CONF_PLATFORM: 'test'}} - )) - # Switch 1 is ON, switch 2 is OFF self.switch_1, self.switch_2, self.switch_3 = \ platform.DEVICES @@ -31,6 +28,9 @@ class TestSwitch(unittest.TestCase): def test_methods(self): """Test is_on, turn_on, turn_off methods.""" + self.assertTrue(setup_component( + self.hass, switch.DOMAIN, {switch.DOMAIN: {CONF_PLATFORM: 'test'}} + )) self.assertTrue(switch.is_on(self.hass)) self.assertEqual( STATE_ON, @@ -83,8 +83,8 @@ class TestSwitch(unittest.TestCase): loader.set_component('switch.test2', test_platform) test_platform.init(False) - self.assertTrue(switch.setup( - self.hass, { + self.assertTrue(setup_component( + self.hass, switch.DOMAIN, { switch.DOMAIN: {CONF_PLATFORM: 'test'}, '{} 2'.format(switch.DOMAIN): {CONF_PLATFORM: 'test2'}, } diff --git a/tests/components/test_scene.py b/tests/components/test_scene.py index 0f07dac528b..6e46e55e221 100644 --- a/tests/components/test_scene.py +++ b/tests/components/test_scene.py @@ -1,6 +1,7 @@ """The tests for the Scene component.""" import unittest +from homeassistant.bootstrap import setup_component from homeassistant import loader from homeassistant.components import light, scene @@ -38,7 +39,7 @@ class TestScene(unittest.TestCase): test_light = loader.get_component('light.test') test_light.init() - self.assertTrue(light.setup(self.hass, { + self.assertTrue(setup_component(self.hass, light.DOMAIN, { light.DOMAIN: {'platform': 'test'} })) @@ -52,7 +53,7 @@ class TestScene(unittest.TestCase): 'state': 'on', 'brightness': 100, } - self.assertTrue(scene.setup(self.hass, { + self.assertTrue(setup_component(self.hass, scene.DOMAIN, { 'scene': [{ 'name': 'test', 'entities': { @@ -77,7 +78,7 @@ class TestScene(unittest.TestCase): test_light = loader.get_component('light.test') test_light.init() - self.assertTrue(light.setup(self.hass, { + self.assertTrue(setup_component(self.hass, light.DOMAIN, { light.DOMAIN: {'platform': 'test'} })) @@ -87,7 +88,7 @@ class TestScene(unittest.TestCase): self.hass.block_till_done() - self.assertTrue(scene.setup(self.hass, { + self.assertTrue(setup_component(self.hass, scene.DOMAIN, { 'scene': [{ 'name': 'test', 'entities': { From aca375c31279ab61a504cbd15e7d6d1ddba5ddd1 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 17 Sep 2016 18:28:01 -0700 Subject: [PATCH 079/162] Asyncio event helpers (#3415) * Automation - Event: Use coroutine * Convert event helpers to coroutine * Fix linting * Add hass.async_add_job * Automation - Event to use async_add_job --- homeassistant/components/automation/event.py | 4 +++- homeassistant/core.py | 16 ++++++++++++--- homeassistant/helpers/event.py | 21 ++++++++++++++------ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py index 795dd94a71f..7b77dd04627 100644 --- a/homeassistant/components/automation/event.py +++ b/homeassistant/components/automation/event.py @@ -4,6 +4,7 @@ Offer event listening automation rules. For more details about this automation rule, please refer to the documentation at https://home-assistant.io/components/automation/#event-trigger """ +import asyncio import logging import voluptuous as vol @@ -28,11 +29,12 @@ def trigger(hass, config, action): event_type = config.get(CONF_EVENT_TYPE) event_data = config.get(CONF_EVENT_DATA) + @asyncio.coroutine def handle_event(event): """Listen for events and calls the action when data matches.""" if not event_data or all(val == event.data.get(key) for key, val in event_data.items()): - action({ + hass.async_add_job(action, { 'trigger': { 'platform': 'event', 'event': event, diff --git a/homeassistant/core.py b/homeassistant/core.py index 50b04e79f6d..48702c15513 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -6,6 +6,7 @@ of entities and react to changes. """ # pylint: disable=unused-import, too-many-lines import asyncio +from concurrent.futures import ThreadPoolExecutor import enum import functools as ft import logging @@ -13,8 +14,8 @@ import os import re import signal import sys +import threading import time -from concurrent.futures import ThreadPoolExecutor from types import MappingProxyType @@ -205,6 +206,17 @@ class HomeAssistant(object): """ self.pool.add_job(priority, (target,) + args) + def async_add_job(self, target: Callable[..., None], *args: Any): + """Add a job from within the eventloop. + + target: target to call. + args: parameters for method to call. + """ + if asyncio.iscoroutinefunction(target): + self.loop.create_task(target(*args)) + else: + self.add_job(target, *args) + def _loop_empty(self): """Python 3.4.2 empty loop compatibility function.""" # pylint: disable=protected-access @@ -217,8 +229,6 @@ class HomeAssistant(object): def block_till_done(self): """Block till all pending work is done.""" - import threading - complete = threading.Event() @asyncio.coroutine diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 512b173a249..ab0641cab9e 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -1,4 +1,5 @@ """Helpers for listening to events.""" +import asyncio import functools as ft from datetime import timedelta @@ -28,6 +29,7 @@ def track_state_change(hass, entity_ids, action, from_state=None, entity_ids = tuple(entity_id.lower() for entity_id in entity_ids) @ft.wraps(action) + @asyncio.coroutine def state_change_listener(event): """The listener that listens for specific state changes.""" if entity_ids != MATCH_ALL and \ @@ -45,9 +47,9 @@ def track_state_change(hass, entity_ids, action, from_state=None, new_state = None if _matcher(old_state, from_state) and _matcher(new_state, to_state): - action(event.data.get('entity_id'), - event.data.get('old_state'), - event.data.get('new_state')) + hass.async_add_job(action, event.data.get('entity_id'), + event.data.get('old_state'), + event.data.get('new_state')) return hass.bus.listen(EVENT_STATE_CHANGED, state_change_listener) @@ -70,6 +72,7 @@ def track_point_in_utc_time(hass, action, point_in_time): point_in_time = dt_util.as_utc(point_in_time) @ft.wraps(action) + @asyncio.coroutine def point_in_time_listener(event): """Listen for matching time_changed events.""" now = event.data[ATTR_NOW] @@ -83,8 +86,13 @@ def track_point_in_utc_time(hass, action, point_in_time): # listener gets lined up twice to be executed. This will make # sure the second time it does nothing. point_in_time_listener.run = True - remove() - action(now) + + def fire_action(): + """Run the point in time listener action.""" + remove() + action(now) + + hass.add_job(fire_action) remove = hass.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener) return remove @@ -171,6 +179,7 @@ def track_utc_time_change(hass, action, year=None, month=None, day=None, hour, minute, second = pmp(hour), pmp(minute), pmp(second) @ft.wraps(action) + @asyncio.coroutine def pattern_time_change_listener(event): """Listen for matching time_changed events.""" now = event.data[ATTR_NOW] @@ -187,7 +196,7 @@ def track_utc_time_change(hass, action, year=None, month=None, day=None, mat(now.minute, minute) and \ mat(now.second, second): - action(now) + hass.async_add_job(action, now) return hass.bus.listen(EVENT_TIME_CHANGED, pattern_time_change_listener) From 325220e00945a9e40a012f29931c212b7b9a1b54 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 17 Sep 2016 19:51:18 -0700 Subject: [PATCH 080/162] Make track_point_in_utc_time more async (#3428) * Make track_point_in_utc_time more async * Make track_point_in_time async friendly --- homeassistant/helpers/event.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index ab0641cab9e..7331525c052 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -6,6 +6,7 @@ from datetime import timedelta from ..const import ( ATTR_NOW, EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) from ..util import dt as dt_util +from ..util.async import run_callback_threadsafe def track_state_change(hass, entity_ids, action, from_state=None, @@ -59,9 +60,10 @@ def track_point_in_time(hass, action, point_in_time): utc_point_in_time = dt_util.as_utc(point_in_time) @ft.wraps(action) + @asyncio.coroutine def utc_converter(utc_now): """Convert passed in UTC now to local now.""" - action(dt_util.as_local(utc_now)) + hass.async_add_job(action, dt_util.as_local(utc_now)) return track_point_in_utc_time(hass, utc_converter, utc_point_in_time) @@ -86,15 +88,19 @@ def track_point_in_utc_time(hass, action, point_in_time): # listener gets lined up twice to be executed. This will make # sure the second time it does nothing. point_in_time_listener.run = True + async_remove() - def fire_action(): - """Run the point in time listener action.""" - remove() - action(now) + hass.async_add_job(action, now) - hass.add_job(fire_action) + future = run_callback_threadsafe( + hass.loop, hass.bus.async_listen, EVENT_TIME_CHANGED, + point_in_time_listener) + async_remove = future.result() + + def remove(): + """Remove listener.""" + run_callback_threadsafe(hass.loop, async_remove).result() - remove = hass.bus.listen(EVENT_TIME_CHANGED, point_in_time_listener) return remove From 81928b1a6b0b31f23ba4e52677b395752f4319cd Mon Sep 17 00:00:00 2001 From: Lewis Juggins Date: Sun, 18 Sep 2016 06:51:40 +0100 Subject: [PATCH 081/162] Update pychromecast (#3416) --- homeassistant/components/media_player/cast.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py index 2b10448b241..8468390c590 100644 --- a/homeassistant/components/media_player/cast.py +++ b/homeassistant/components/media_player/cast.py @@ -19,7 +19,7 @@ from homeassistant.const import ( STATE_UNKNOWN) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pychromecast==0.7.2'] +REQUIREMENTS = ['pychromecast==0.7.4'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 85ab6baf688..5b4f6073432 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -314,7 +314,7 @@ pyasn1==0.1.9 # pybluez==0.22 # homeassistant.components.media_player.cast -pychromecast==0.7.2 +pychromecast==0.7.4 # homeassistant.components.media_player.cmus pycmus==0.1.0 From 534f56a3e2b064f9124281854ce7f6219c962190 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Sun, 18 Sep 2016 07:57:18 +0200 Subject: [PATCH 082/162] Bugfix if ffmpeg is not present on config / Update ha-ffmpeg 0.13 (#3418) --- homeassistant/components/ffmpeg.py | 12 +++++++----- requirements_all.txt | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/ffmpeg.py b/homeassistant/components/ffmpeg.py index 03bf1db885f..0ba015a4660 100644 --- a/homeassistant/components/ffmpeg.py +++ b/homeassistant/components/ffmpeg.py @@ -11,7 +11,7 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv DOMAIN = 'ffmpeg' -REQUIREMENTS = ["ha-ffmpeg==0.12"] +REQUIREMENTS = ["ha-ffmpeg==0.13"] _LOGGER = logging.getLogger(__name__) @@ -32,15 +32,17 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) -FFMPEG_CONFIG = {} +FFMPEG_CONFIG = { + CONF_FFMPEG_BIN: DEFAULT_BINARY, + CONF_RUN_TEST: DEFAULT_RUN_TEST, +} FFMPEG_TEST_CACHE = {} def setup(hass, config): """Setup the FFmpeg component.""" - global FFMPEG_CONFIG - - FFMPEG_CONFIG = config.get(DOMAIN) + if DOMAIN in config: + FFMPEG_CONFIG.update(config.get(DOMAIN)) return True diff --git a/requirements_all.txt b/requirements_all.txt index 5b4f6073432..ddce3d01a14 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -115,7 +115,7 @@ googlemaps==2.4.4 gps3==0.33.3 # homeassistant.components.ffmpeg -ha-ffmpeg==0.12 +ha-ffmpeg==0.13 # homeassistant.components.mqtt.server hbmqtt==0.7.1 From a90049568ee0db955475e5042f9a297158394d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Sun, 18 Sep 2016 08:21:24 +0200 Subject: [PATCH 083/162] Fix issue #3401 weblink (#3402) --- homeassistant/components/weblink.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/weblink.py b/homeassistant/components/weblink.py index 9a1f13f57a8..bec89787048 100644 --- a/homeassistant/components/weblink.py +++ b/homeassistant/components/weblink.py @@ -20,7 +20,8 @@ CONF_ENTITIES = 'entities' DOMAIN = 'weblink' ENTITIES_SCHEMA = vol.Schema({ - vol.Required(CONF_URL): cv.url, + # pylint: disable=no-value-for-parameter + vol.Required(CONF_URL): vol.Url(), vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_ICON): cv.icon, }) From 2b7d1fe20de754c55fcf00bae91724d3e091dc4a Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 18 Sep 2016 08:23:45 +0200 Subject: [PATCH 084/162] Use voluptuous for logger (#3375) * Migrate to voluptuous * No list for configuration check --- homeassistant/components/logger.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/homeassistant/components/logger.py b/homeassistant/components/logger.py index ed17e7520d0..58e745e3004 100644 --- a/homeassistant/components/logger.py +++ b/homeassistant/components/logger.py @@ -7,6 +7,10 @@ https://home-assistant.io/components/logger/ import logging from collections import OrderedDict +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv + DOMAIN = 'logger' LOGSEVERITY = { @@ -23,6 +27,17 @@ LOGSEVERITY = { LOGGER_DEFAULT = 'default' LOGGER_LOGS = 'logs' +_LOGS_SCHEMA = vol.Schema({ + cv.string: vol.In(vol.Lower(list(LOGSEVERITY))), +}) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(LOGGER_DEFAULT): vol.In(vol.Lower(list(LOGSEVERITY))), + vol.Required(LOGGER_LOGS): _LOGS_SCHEMA, + }), +}, extra=vol.ALLOW_EXTRA) + class HomeAssistantLogFilter(logging.Filter): """A log filter.""" From 04d31e4ef4db031d8d7260ad104d342bae770075 Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sun, 18 Sep 2016 08:28:37 +0200 Subject: [PATCH 085/162] Use voluptuous for RPi GPIO (#3371) * Migrate to voluptuous * Remove the check for lists --- .../components/binary_sensor/rpi_gpio.py | 31 ++++++++-- homeassistant/components/cover/rpi_gpio.py | 61 ++++++++++--------- homeassistant/components/rpi_gpio.py | 6 +- homeassistant/components/switch/rpi_gpio.py | 27 ++++++-- 4 files changed, 85 insertions(+), 40 deletions(-) diff --git a/homeassistant/components/binary_sensor/rpi_gpio.py b/homeassistant/components/binary_sensor/rpi_gpio.py index a52b020dfe2..7bc05cd6764 100644 --- a/homeassistant/components/binary_sensor/rpi_gpio.py +++ b/homeassistant/components/binary_sensor/rpi_gpio.py @@ -6,16 +6,37 @@ https://home-assistant.io/components/binary_sensor.rpi_gpio/ """ import logging -import homeassistant.components.rpi_gpio as rpi_gpio -from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.const import DEVICE_DEFAULT_NAME +import voluptuous as vol + +import homeassistant.components.rpi_gpio as rpi_gpio +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.const import DEVICE_DEFAULT_NAME +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +CONF_BOUNCETIME = 'bouncetime' +CONF_INVERT_LOGIC = 'invert_logic' +CONF_PORTS = 'ports' +CONF_PULL_MODE = 'pull_mode' -DEFAULT_PULL_MODE = "UP" DEFAULT_BOUNCETIME = 50 DEFAULT_INVERT_LOGIC = False +DEFAULT_PULL_MODE = 'UP' DEPENDENCIES = ['rpi_gpio'] -_LOGGER = logging.getLogger(__name__) + +_SENSORS_SCHEMA = vol.Schema({ + cv.positive_int: cv.string, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_PORTS): _SENSORS_SCHEMA, + vol.Optional(CONF_BOUNCETIME, default=DEFAULT_BOUNCETIME): cv.positive_int, + vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, + vol.Optional(CONF_PULL_MODE, default=DEFAULT_PULL_MODE): cv.string, +}) # pylint: disable=unused-argument diff --git a/homeassistant/components/cover/rpi_gpio.py b/homeassistant/components/cover/rpi_gpio.py index 6cef5dc08e7..00034bd718b 100644 --- a/homeassistant/components/cover/rpi_gpio.py +++ b/homeassistant/components/cover/rpi_gpio.py @@ -7,64 +7,69 @@ https://github.com/andrewshilliday/garage-door-controller For more details about this platform, please refer to the documentation at https://home-assistant.io/components/cover.rpi_gpio/ """ - import logging from time import sleep + import voluptuous as vol -from homeassistant.components.cover import CoverDevice +from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME import homeassistant.components.rpi_gpio as rpi_gpio import homeassistant.helpers.config_validation as cv -RELAY_TIME = 'relay_time' -STATE_PULL_MODE = 'state_pull_mode' -DEFAULT_PULL_MODE = 'UP' -DEFAULT_RELAY_TIME = .2 -DEPENDENCIES = ['rpi_gpio'] - _LOGGER = logging.getLogger(__name__) +CONF_COVERS = 'covers' +CONF_RELAY_PIN = 'relay_pin' +CONF_RELAY_TIME = 'relay_time' +CONF_STATE_PIN = 'state_pin' +CONF_STATE_PULL_MODE = 'state_pull_mode' + +DEFAULT_RELAY_TIME = .2 +DEFAULT_STATE_PULL_MODE = 'UP' +DEPENDENCIES = ['rpi_gpio'] + _COVERS_SCHEMA = vol.All( cv.ensure_list, [ vol.Schema({ - 'name': str, - 'relay_pin': int, - 'state_pin': int, + CONF_NAME: cv.string, + CONF_RELAY_PIN: cv.positive_int, + CONF_STATE_PIN: cv.positive_int, }) ] ) -PLATFORM_SCHEMA = vol.Schema({ - 'platform': str, - vol.Required('covers'): _COVERS_SCHEMA, - vol.Optional(STATE_PULL_MODE, default=DEFAULT_PULL_MODE): cv.string, - vol.Optional(RELAY_TIME, default=DEFAULT_RELAY_TIME): vol.Coerce(int), + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_COVERS): _COVERS_SCHEMA, + vol.Optional(CONF_STATE_PULL_MODE, default=DEFAULT_STATE_PULL_MODE): + cv.string, + vol.Optional(CONF_RELAY_TIME, default=DEFAULT_RELAY_TIME): cv.positive_int, }) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup the cover platform.""" - relay_time = config.get(RELAY_TIME) - state_pull_mode = config.get(STATE_PULL_MODE) + """Setup the RPi cover platform.""" + relay_time = config.get(CONF_RELAY_TIME) + state_pull_mode = config.get(CONF_STATE_PULL_MODE) covers = [] - covers_conf = config.get('covers') + covers_conf = config.get(CONF_COVERS) for cover in covers_conf: - covers.append(RPiGPIOCover(cover['name'], cover['relay_pin'], - cover['state_pin'], - state_pull_mode, - relay_time)) + covers.append(RPiGPIOCover( + cover[CONF_NAME], cover[CONF_RELAY_PIN], cover[CONF_STATE_PIN], + state_pull_mode, relay_time)) add_devices(covers) # pylint: disable=abstract-method class RPiGPIOCover(CoverDevice): - """Representation of a Raspberry cover.""" + """Representation of a Raspberry GPIO cover.""" # pylint: disable=too-many-arguments - def __init__(self, name, relay_pin, state_pin, - state_pull_mode, relay_time): + def __init__(self, name, relay_pin, state_pin, state_pull_mode, + relay_time): """Initialize the cover.""" self._name = name self._state = False @@ -79,7 +84,7 @@ class RPiGPIOCover(CoverDevice): @property def unique_id(self): """Return the ID of this cover.""" - return "{}.{}".format(self.__class__, self._name) + return '{}.{}'.format(self.__class__, self._name) @property def name(self): diff --git a/homeassistant/components/rpi_gpio.py b/homeassistant/components/rpi_gpio.py index 1b302eb1839..0f2f5792cbc 100644 --- a/homeassistant/components/rpi_gpio.py +++ b/homeassistant/components/rpi_gpio.py @@ -1,7 +1,7 @@ """ Support for controlling GPIO pins of a Raspberry Pi. -For more details about this platform, please refer to the documentation at +For more details about this component, please refer to the documentation at https://home-assistant.io/components/rpi_gpio/ """ # pylint: disable=import-error @@ -11,9 +11,11 @@ from homeassistant.const import ( EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) REQUIREMENTS = ['RPi.GPIO==0.6.1'] -DOMAIN = "rpi_gpio" + _LOGGER = logging.getLogger(__name__) +DOMAIN = 'rpi_gpio' + # pylint: disable=no-member def setup(hass, config): diff --git a/homeassistant/components/switch/rpi_gpio.py b/homeassistant/components/switch/rpi_gpio.py index 23a98a3f7ba..c6400432aa2 100644 --- a/homeassistant/components/switch/rpi_gpio.py +++ b/homeassistant/components/switch/rpi_gpio.py @@ -4,26 +4,43 @@ Allows to configure a switch using RPi GPIO. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/switch.rpi_gpio/ """ - import logging +import voluptuous as vol + +from homeassistant.components.switch import PLATFORM_SCHEMA import homeassistant.components.rpi_gpio as rpi_gpio from homeassistant.const import DEVICE_DEFAULT_NAME from homeassistant.helpers.entity import ToggleEntity +import homeassistant.helpers.config_validation as cv + +_LOGGER = logging.getLogger(__name__) + +DEPENDENCIES = ['rpi_gpio'] + +CONF_PULL_MODE = 'pull_mode' +CONF_PORTS = 'ports' +CONF_INVERT_LOGIC = 'invert_logic' DEFAULT_INVERT_LOGIC = False -DEPENDENCIES = ['rpi_gpio'] -_LOGGER = logging.getLogger(__name__) +_SWITCHES_SCHEMA = vol.Schema({ + cv.positive_int: cv.string, +}) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_PORTS): _SWITCHES_SCHEMA, + vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, +}) # pylint: disable=unused-argument def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Raspberry PI GPIO devices.""" - invert_logic = config.get('invert_logic', DEFAULT_INVERT_LOGIC) + invert_logic = config.get(CONF_INVERT_LOGIC) switches = [] - ports = config.get('ports') + ports = config.get(CONF_PORTS) for port, name in ports.items(): switches.append(RPiGPIOSwitch(name, port, invert_logic)) add_devices(switches) From 1c706834e0317a8f9dcb83b4f6a894d8e9e72d73 Mon Sep 17 00:00:00 2001 From: deisi Date: Sun, 18 Sep 2016 08:31:27 +0200 Subject: [PATCH 086/162] Recieve signals from a keyboard and use keyboard as a remote control (#3305) --- .coveragerc | 1 + homeassistant/components/keyboard_remote.py | 135 ++++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 139 insertions(+) create mode 100644 homeassistant/components/keyboard_remote.py diff --git a/.coveragerc b/.coveragerc index cd15ecb052b..70079e12412 100644 --- a/.coveragerc +++ b/.coveragerc @@ -146,6 +146,7 @@ omit = homeassistant/components/ifttt.py homeassistant/components/joaoapps_join.py homeassistant/components/keyboard.py + homeassistant/components/keyboard_remote.py homeassistant/components/light/blinksticklight.py homeassistant/components/light/flux_led.py homeassistant/components/light/hue.py diff --git a/homeassistant/components/keyboard_remote.py b/homeassistant/components/keyboard_remote.py new file mode 100644 index 00000000000..9a6a2a5d89b --- /dev/null +++ b/homeassistant/components/keyboard_remote.py @@ -0,0 +1,135 @@ +""" +Recieve signals from a keyboard and use it as a remote control. + +This component allows to use a keyboard as remote control. It will +fire ´keyboard_remote_command_received´ events witch can then be used +in automation rules. + +The `evdev` package is used to interface with the keyboard and thus this +is Linux only. It also means you can't use your normal keyboard for this, +because `evdev` will block it. + +Example: + keyboard_remote: + device_descriptor: '/dev/input/by-id/foo' + key_value: 'key_up' # optional alternaive 'key_down' and 'key_hold' + # be carefull, 'key_hold' fires a lot of events + + and an automation rule to bring breath live into it. + + automation: + alias: Keyboard All light on + trigger: + platform: event + event_type: keyboard_remote_command_received + event_data: + key_code: 107 # inspect log to obtain desired keycode + action: + service: light.turn_on + entity_id: light.all +""" + +# pylint: disable=import-error +import threading +import logging +import os + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.const import ( + EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP +) + +DOMAIN = "keyboard_remote" +REQUIREMENTS = ['evdev==0.6.1'] +_LOGGER = logging.getLogger(__name__) +ICON = 'mdi:remote' +KEYBOARD_REMOTE_COMMAND_RECEIVED = 'keyboard_remote_command_received' +KEY_CODE = 'key_code' +KEY_VALUE = {'key_up': 0, 'key_down': 1, 'key_hold': 2} +TYPE = 'type' +DEVICE_DESCRIPTOR = 'device_descriptor' + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(DEVICE_DESCRIPTOR): cv.string, + vol.Optional(TYPE, default='key_up'): + vol.All(cv.string, vol.Any('key_up', 'key_down', 'key_hold')), + }), +}, extra=vol.ALLOW_EXTRA) + + +def setup(hass, config): + """Setup keyboard_remote.""" + config = config.get(DOMAIN) + device_descriptor = config.get(DEVICE_DESCRIPTOR) + if not device_descriptor or not os.path.isfile(device_descriptor): + id_folder = '/dev/input/by-id/' + _LOGGER.error( + 'A device_descriptor must be defined. ' + 'Possible descriptors are %s:\n%s', + id_folder, os.listdir(id_folder) + ) + return + + key_value = KEY_VALUE.get(config.get(TYPE, 'key_up')) + + keyboard_remote = KeyboardRemote( + hass, + device_descriptor, + key_value + ) + + def _start_keyboard_remote(_event): + keyboard_remote.run() + + def _stop_keyboard_remote(_event): + keyboard_remote.stopped.set() + + hass.bus.listen_once( + EVENT_HOMEASSISTANT_START, + _start_keyboard_remote + ) + hass.bus.listen_once( + EVENT_HOMEASSISTANT_STOP, + _stop_keyboard_remote + ) + + return True + + +class KeyboardRemote(threading.Thread): + """This interfaces with the inputdevice using evdev.""" + + def __init__(self, hass, device_descriptor, key_value): + """Construct a KeyboardRemote interface object.""" + from evdev import InputDevice + + self.dev = InputDevice(device_descriptor) + threading.Thread.__init__(self) + self.stopped = threading.Event() + self.hass = hass + self.key_value = key_value + + def run(self): + """Main loop of the KeyboardRemote.""" + from evdev import categorize, ecodes + _LOGGER.debug('KeyboardRemote interface started for %s', self.dev) + + self.dev.grab() + + while not self.stopped.isSet(): + event = self.dev.read_one() + + if not event: + continue + + # pylint: disable=no-member + if event.type is ecodes.EV_KEY and event.value is self.key_value: + _LOGGER.debug(categorize(event)) + self.hass.bus.fire( + KEYBOARD_REMOTE_COMMAND_RECEIVED, + {KEY_CODE: event.code} + ) diff --git a/requirements_all.txt b/requirements_all.txt index ddce3d01a14..1160f4e236c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -80,6 +80,9 @@ eliqonline==1.0.12 # homeassistant.components.enocean enocean==0.31 +# homeassistant.components.keyboard_remote +evdev==0.6.1 + # homeassistant.components.climate.honeywell # homeassistant.components.thermostat.honeywell evohomeclient==0.2.5 From e19a0929347917768bde7d1945e36f3aa7f9622e Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 17 Sep 2016 23:32:11 -0700 Subject: [PATCH 087/162] Update Docker to use Python 3.5 (#3430) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 22c0c13ddf6..e54276b99b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.4 +FROM python:3.5 MAINTAINER Paulus Schoutsen VOLUME /config From 9184773f8f9111ad7d1d4473204aab6e0dfe97fe Mon Sep 17 00:00:00 2001 From: joyrider3774 Date: Sun, 18 Sep 2016 08:34:24 +0200 Subject: [PATCH 088/162] Emoncms feeds sensor component (#3258) * Add Initial version for emoncms feeds sensor * flake8 test fixes * pylint test fixes * - fix bug with include_feed_id_names not assigning the name to the element in the same postion as found in include_feed_id - a few structure changes to have less nesting (pylint fix) - minor other changes * update .coveragerc * voluptuous fixes: - exclude_feed_id and include_feed_id Exclusive group so that only one (or none) can be specified at once - id must be positive int - exclude_feed_id and include_feed_id must be positive int * Fix comment so it refers to the documentation * use string formatting * Remove outer try * clean up sensors.append calls (break them out for loop) * multiple changes like: - rename config value "include_feed_id" to "include_only_feed_id" - rename config value "include_feed_id_names" to "sensor_names" - renamed config value sensor_names is now a dictionary an can also be used when renamed config value "include_only_feed_id" is not specified - Set default value for scan_interval using the config validation - blank lines between default, 3rd party and own imports - removed homeassistant.util import, it was not needed anymore * fix extended voluptuous schema scan_interval should not be extended to PLATFORM_SCHEMA as it was already in the schema by config_validation helper so i should not add / change it. It was also causing problems reading the value from the config. * Use Home Assistant polling * remove statement that can never happen * Guard clause * Reduce instance variables --- .coveragerc | 1 + homeassistant/components/sensor/emoncms.py | 215 +++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 homeassistant/components/sensor/emoncms.py diff --git a/.coveragerc b/.coveragerc index 70079e12412..043825b7a08 100644 --- a/.coveragerc +++ b/.coveragerc @@ -216,6 +216,7 @@ omit = homeassistant/components/sensor/dte_energy_bridge.py homeassistant/components/sensor/efergy.py homeassistant/components/sensor/eliqonline.py + homeassistant/components/sensor/emoncms.py homeassistant/components/sensor/fastdotcom.py homeassistant/components/sensor/fitbit.py homeassistant/components/sensor/fixer.py diff --git a/homeassistant/components/sensor/emoncms.py b/homeassistant/components/sensor/emoncms.py new file mode 100644 index 00000000000..f893f48b165 --- /dev/null +++ b/homeassistant/components/sensor/emoncms.py @@ -0,0 +1,215 @@ +""" +Support for monitoring emoncms feeds. + +For more details about this component, please refer to the documentation +at https://home-assistant.io/components/sensor.emoncms/ +""" +from datetime import timedelta +import logging + +import voluptuous as vol +import requests + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import ( + CONF_API_KEY, CONF_URL, CONF_VALUE_TEMPLATE, + CONF_UNIT_OF_MEASUREMENT, CONF_ID, CONF_SCAN_INTERVAL, + STATE_UNKNOWN) +from homeassistant.helpers.entity import Entity +from homeassistant.helpers import template +from homeassistant.util import Throttle + +_LOGGER = logging.getLogger(__name__) + +DECIMALS = 2 +CONF_EXCLUDE_FEEDID = "exclude_feed_id" +CONF_ONLY_INCLUDE_FEEDID = "include_only_feed_id" +CONF_SENSOR_NAMES = "sensor_names" +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_URL): cv.string, + vol.Required(CONF_ID): cv.positive_int, + vol.Exclusive(CONF_ONLY_INCLUDE_FEEDID, 'only_include_exclude_or_none'): + vol.All(cv.ensure_list, [cv.positive_int]), + vol.Exclusive(CONF_EXCLUDE_FEEDID, 'only_include_exclude_or_none'): + vol.All(cv.ensure_list, [cv.positive_int]), + vol.Optional(CONF_SENSOR_NAMES): + vol.All({cv.positive_int: vol.All(cv.string, vol.Length(min=1))}), + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_UNIT_OF_MEASUREMENT, default="W"): cv.string, +}) + +ATTR_SIZE = 'Size' +ATTR_LASTUPDATETIME = 'LastUpdated' +ATTR_TAG = 'Tag' +ATTR_FEEDID = 'FeedId' +ATTR_USERID = 'UserId' +ATTR_FEEDNAME = 'FeedName' +ATTR_LASTUPDATETIMESTR = 'LastUpdatedStr' + + +def get_id(sensorid, feedtag, feedname, feedid, feeduserid): + """Return unique identifier for feed / sensor.""" + return "emoncms{}_{}_{}_{}_{}".format( + sensorid, feedtag, feedname, feedid, feeduserid) + + +# pylint: disable=too-many-locals +def setup_platform(hass, config, add_devices, discovery_info=None): + """Setup the Emoncms sensor.""" + apikey = config.get(CONF_API_KEY) + url = config.get(CONF_URL) + sensorid = config.get(CONF_ID) + value_template = config.get(CONF_VALUE_TEMPLATE) + unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT) + exclude_feeds = config.get(CONF_EXCLUDE_FEEDID) + include_only_feeds = config.get(CONF_ONLY_INCLUDE_FEEDID) + sensor_names = config.get(CONF_SENSOR_NAMES) + interval = config.get(CONF_SCAN_INTERVAL) + + data = EmonCmsData(hass, url, apikey, interval) + + data.update() + + if data.data is None: + return False + + sensors = [] + + for elem in data.data: + + if exclude_feeds is not None: + if int(elem["id"]) in exclude_feeds: + continue + + if include_only_feeds is not None: + if int(elem["id"]) not in include_only_feeds: + continue + + name = None + if sensor_names is not None: + name = sensor_names.get(int(elem["id"]), None) + + sensors.append(EmonCmsSensor(hass, data, name, value_template, + unit_of_measurement, str(sensorid), + elem)) + add_devices(sensors) + + +# pylint: disable=too-many-instance-attributes +class EmonCmsSensor(Entity): + """Implementation of an EmonCmsSensor sensor.""" + + # pylint: disable=too-many-arguments + def __init__(self, hass, data, name, value_template, + unit_of_measurement, sensorid, elem): + """Initialize the sensor.""" + if name is None: + self._name = "emoncms{}_feedid_{}".format( + sensorid, elem["id"]) + else: + self._name = name + self._identifier = get_id(sensorid, elem["tag"], + elem["name"], elem["id"], + elem["userid"]) + self._hass = hass + self._data = data + self._value_template = value_template + self._unit_of_measurement = unit_of_measurement + self._sensorid = sensorid + self._elem = elem + + if self._value_template is not None: + self._state = template.render_with_possible_json_value( + self._hass, self._value_template, elem["value"], + STATE_UNKNOWN) + else: + self._state = round(float(elem["value"]), DECIMALS) + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity, if any.""" + return self._unit_of_measurement + + @property + def state(self): + """Return the state of the device.""" + return self._state + + @property + def device_state_attributes(self): + """Return the atrributes of the sensor.""" + return { + ATTR_FEEDID: self._elem["id"], + ATTR_TAG: self._elem["tag"], + ATTR_FEEDNAME: self._elem["name"], + ATTR_SIZE: self._elem["size"], + ATTR_USERID: self._elem["userid"], + ATTR_LASTUPDATETIME: self._elem["time"], + ATTR_LASTUPDATETIMESTR: template.timestamp_local( + float(self._elem["time"])), + } + + def update(self): + """Get the latest data and updates the state.""" + self._data.update() + + if self._data.data is None: + return + + elem = next((elem for elem in self._data.data + if get_id(self._sensorid, elem["tag"], + elem["name"], elem["id"], + elem["userid"]) == self._identifier), + None) + + if elem is None: + return + + self._elem = elem + + if self._value_template is not None: + self._state = template.render_with_possible_json_value( + self._hass, self._value_template, elem["value"], + STATE_UNKNOWN) + else: + self._state = round(float(elem["value"]), DECIMALS) + + +# pylint: disable=too-few-public-methods +class EmonCmsData(object): + """The class for handling the data retrieval.""" + + def __init__(self, hass, url, apikey, interval): + """Initialize the data object.""" + self._apikey = apikey + self._url = "{}/feed/list.json".format(url) + self._interval = interval + self._hass = hass + self.data = None + + @Throttle(MIN_TIME_BETWEEN_UPDATES) + def update(self): + """Get the latest data.""" + try: + req = requests.get(self._url, params={"apikey": self._apikey}, + verify=False, allow_redirects=True, + timeout=5) + except requests.exceptions.RequestException as exception: + _LOGGER.error(exception) + return + else: + if req.status_code == 200: + self.data = req.json() + else: + _LOGGER.error("please verify if the specified config value " + "'%s' is correct! (HTTP Status_code = %d)", + CONF_URL, req.status_code) From 169f054c6c038a8b6e51d8ede81db56f7b6b6b62 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Sun, 18 Sep 2016 08:44:15 +0200 Subject: [PATCH 089/162] Use voluptuous for nmap, tplink, thomson device trackers (#3124) * Use Voluptuous for nmap, tplink, thomson device_trackers * Fix logic --- .../components/device_tracker/nmap_tracker.py | 41 ++++++++++--------- .../components/device_tracker/thomson.py | 37 +++++++++-------- .../components/device_tracker/tplink.py | 33 +++++++-------- 3 files changed, 57 insertions(+), 54 deletions(-) diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index e23d5f31145..19bc32965e0 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -10,11 +10,13 @@ import subprocess from collections import namedtuple from datetime import timedelta +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util -from homeassistant.components.device_tracker import DOMAIN +from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA from homeassistant.const import CONF_HOSTS -from homeassistant.helpers import validate_config -from homeassistant.util import Throttle, convert +from homeassistant.util import Throttle # Return cached results if last scan was less then this time ago MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) @@ -27,18 +29,21 @@ CONF_EXCLUDE = 'exclude' REQUIREMENTS = ['python-nmap==0.6.1'] +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOSTS): vol.All(cv.ensure_list, [cv.string]), + vol.Required(CONF_HOME_INTERVAL, default=0): cv.positive_int, + vol.Optional(CONF_EXCLUDE, default=[]): + vol.All(cv.ensure_list, vol.Length(min=1)) +}) + def get_scanner(hass, config): """Validate the configuration and return a Nmap scanner.""" - if not validate_config(config, {DOMAIN: [CONF_HOSTS]}, - _LOGGER): - return None - scanner = NmapDeviceScanner(config[DOMAIN]) return scanner if scanner.success_init else None -Device = namedtuple("Device", ["mac", "name", "ip", "last_update"]) +Device = namedtuple('Device', ['mac', 'name', 'ip', 'last_update']) def _arp(ip_address): @@ -49,24 +54,26 @@ def _arp(ip_address): match = re.search(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})', str(out)) if match: return match.group(0) - _LOGGER.info("No MAC address found for %s", ip_address) + _LOGGER.info('No MAC address found for %s', ip_address) return None class NmapDeviceScanner(object): """This class scans for devices using nmap.""" + exclude = [] + def __init__(self, config): """Initialize the scanner.""" self.last_results = [] self.hosts = config[CONF_HOSTS] self.exclude = config.get(CONF_EXCLUDE, []) - minutes = convert(config.get(CONF_HOME_INTERVAL), int, 0) + minutes = config[CONF_HOME_INTERVAL] self.home_interval = timedelta(minutes=minutes) self.success_init = self._update_info() - _LOGGER.info("nmap scanner initialized") + _LOGGER.info('nmap scanner initialized') def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" @@ -90,21 +97,18 @@ class NmapDeviceScanner(object): Returns boolean if scanning successful. """ - _LOGGER.info("Scanning") + _LOGGER.info('Scanning') from nmap import PortScanner, PortScannerError scanner = PortScanner() - options = "-F --host-timeout 5s " - exclude = "--exclude " + options = '-F --host-timeout 5s ' if self.home_interval: boundary = dt_util.now() - self.home_interval last_results = [device for device in self.last_results if device.last_update > boundary] if last_results: - # Pylint is confused here. - # pylint: disable=no-member exclude_hosts = self.exclude + [device.ip for device in last_results] else: @@ -113,8 +117,7 @@ class NmapDeviceScanner(object): last_results = [] exclude_hosts = self.exclude if exclude_hosts: - exclude = " --exclude {}".format(",".join(exclude_hosts)) - options += exclude + options += ' --exclude {}'.format(','.join(exclude_hosts)) try: result = scanner.scan(hosts=self.hosts, arguments=options) @@ -134,5 +137,5 @@ class NmapDeviceScanner(object): self.last_results = last_results - _LOGGER.info("nmap scan successful") + _LOGGER.info('nmap scan successful') return True diff --git a/homeassistant/components/device_tracker/thomson.py b/homeassistant/components/device_tracker/thomson.py index be3249c8b00..cf8f808f82a 100644 --- a/homeassistant/components/device_tracker/thomson.py +++ b/homeassistant/components/device_tracker/thomson.py @@ -10,9 +10,11 @@ import telnetlib import threading from datetime import timedelta -from homeassistant.components.device_tracker import DOMAIN +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.helpers import validate_config from homeassistant.util import Throttle # Return cached results if last scan was less then this time ago. @@ -21,23 +23,24 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) _LOGGER = logging.getLogger(__name__) _DEVICES_REGEX = re.compile( - r'(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' + - r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' + - r'(?P([^\s]+))\s+' + - r'(?P([^\s]+))\s+' + - r'(?P([^\s]+))\s+' + - r'(?P([^\s]+))\s+' + + r'(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' + r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' + r'(?P([^\s]+))\s+' + r'(?P([^\s]+))\s+' + r'(?P([^\s]+))\s+' + r'(?P([^\s]+))\s+' r'(?P([^\s]+))') +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string +}) + # pylint: disable=unused-argument def get_scanner(hass, config): """Validate the configuration and return a THOMSON scanner.""" - if not validate_config(config, - {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, - _LOGGER): - return None - scanner = ThomsonDeviceScanner(config[DOMAIN]) return scanner if scanner.success_init else None @@ -84,7 +87,7 @@ class ThomsonDeviceScanner(object): return False with self.lock: - _LOGGER.info("Checking ARP") + _LOGGER.info('Checking ARP') data = self.get_thomson_data() if not data: return False @@ -108,11 +111,11 @@ class ThomsonDeviceScanner(object): devices_result = telnet.read_until(b'=>').split(b'\r\n') telnet.write('exit\r\n'.encode('ascii')) except EOFError: - _LOGGER.exception("Unexpected response from router") + _LOGGER.exception('Unexpected response from router') return except ConnectionRefusedError: - _LOGGER.exception("Connection refused by router," + - " is telnet enabled?") + _LOGGER.exception('Connection refused by router,' + ' is telnet enabled?') return devices = {} diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py index ad295099bf5..3e691a7149d 100755 --- a/homeassistant/components/device_tracker/tplink.py +++ b/homeassistant/components/device_tracker/tplink.py @@ -12,10 +12,11 @@ import threading from datetime import timedelta import requests +import voluptuous as vol -from homeassistant.components.device_tracker import DOMAIN +import homeassistant.helpers.config_validation as cv +from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -from homeassistant.helpers import validate_config from homeassistant.util import Throttle # Return cached results if last scan was less then this time ago @@ -23,26 +24,22 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string +}) + def get_scanner(hass, config): """Validate the configuration and return a TP-Link scanner.""" - if not validate_config(config, - {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, - _LOGGER): - return None + for cls in [Tplink4DeviceScanner, Tplink3DeviceScanner, + Tplink2DeviceScanner, TplinkDeviceScanner]: + scanner = cls(config[DOMAIN]) + if scanner.success_init: + return scanner - scanner = Tplink4DeviceScanner(config[DOMAIN]) - - if not scanner.success_init: - scanner = Tplink3DeviceScanner(config[DOMAIN]) - - if not scanner.success_init: - scanner = Tplink2DeviceScanner(config[DOMAIN]) - - if not scanner.success_init: - scanner = TplinkDeviceScanner(config[DOMAIN]) - - return scanner if scanner.success_init else None + return None class TplinkDeviceScanner(object): From 91e36f380bb9dfde5c959da1e5a688f56876a92f Mon Sep 17 00:00:00 2001 From: Phil Hawthorne Date: Sun, 18 Sep 2016 16:57:12 +1000 Subject: [PATCH 090/162] Add PyBluez to Dockerfile (#3423) * Add PyBluez to Dockerfile Adds PyBluez to Dockerfile so people using Docker can run Bluetooth devices * Remove pip install of pybluez Pybluez will be installed automatically when the Bluetooth device tracker is enabled --- Dockerfile | 2 +- virtualization/Docker/Dockerfile.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index e54276b99b1..546c71966f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN pip3 install --no-cache-dir colorlog cython # For the nmap tracker, bluetooth tracker, Z-Wave RUN apt-get update && \ - apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev && \ + apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev bluetooth libbluetooth-dev && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* COPY script/build_python_openzwave script/build_python_openzwave diff --git a/virtualization/Docker/Dockerfile.test b/virtualization/Docker/Dockerfile.test index 6b9730bd8f4..651f19e4720 100644 --- a/virtualization/Docker/Dockerfile.test +++ b/virtualization/Docker/Dockerfile.test @@ -10,7 +10,7 @@ RUN pip3 install --no-cache-dir colorlog cython # For the nmap tracker, bluetooth tracker, Z-Wave RUN apt-get update && \ - apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev locales-all && \ + apt-get install -y --no-install-recommends nmap net-tools cython3 libudev-dev sudo libglib2.0-dev locales-all bluetooth libbluetooth-dev && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* RUN pip3 install --no-cache-dir tox From c89a77dc74a3309eb5c04d57ad068c90554bc398 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 18 Sep 2016 00:04:43 -0700 Subject: [PATCH 091/162] Update frontend --- homeassistant/components/frontend/version.py | 14 +++++++------- .../components/frontend/www_static/core.js | 8 ++++---- .../components/frontend/www_static/core.js.gz | Bin 32161 -> 32096 bytes .../frontend/www_static/frontend.html | 4 ++-- .../frontend/www_static/frontend.html.gz | Bin 126732 -> 127107 bytes .../www_static/home-assistant-polymer | 2 +- .../components/frontend/www_static/mdi.html | 2 +- .../frontend/www_static/mdi.html.gz | Bin 174430 -> 174998 bytes .../www_static/panels/ha-panel-dev-event.html | 2 +- .../panels/ha-panel-dev-event.html.gz | Bin 2639 -> 2645 bytes .../panels/ha-panel-dev-service.html | 2 +- .../panels/ha-panel-dev-service.html.gz | Bin 2824 -> 2831 bytes .../www_static/panels/ha-panel-dev-state.html | 2 +- .../panels/ha-panel-dev-state.html.gz | Bin 2772 -> 2776 bytes .../panels/ha-panel-dev-template.html | 2 +- .../panels/ha-panel-dev-template.html.gz | Bin 7290 -> 7303 bytes .../www_static/panels/ha-panel-map.html.gz | Bin 43920 -> 43920 bytes .../frontend/www_static/service_worker.js | 2 +- .../frontend/www_static/service_worker.js.gz | Bin 2282 -> 2279 bytes 19 files changed, 20 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 796d91cade8..ae93145ef0f 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,14 +1,14 @@ """DO NOT MODIFY. Auto-generated by script/fingerprint_frontend.""" FINGERPRINTS = { - "core.js": "1fd10c1fcdf56a61f60cf861d5a0368c", - "frontend.html": "20defe06c11b2fa2f076dc92b6c3b0dd", - "mdi.html": "710b84acc99b32514f52291aba9cd8e8", - "panels/ha-panel-dev-event.html": "3cc881ae8026c0fba5aa67d334a3ab2b", + "core.js": "a361122fce768dd519c66f7003044b7c", + "frontend.html": "c1753e1ce530f978036742477c96d2fd", + "mdi.html": "6bd013a8252e19b3c1f1de52994cfbe4", + "panels/ha-panel-dev-event.html": "c4a5f70eece9f92616a65e8d26be803e", "panels/ha-panel-dev-info.html": "34e2df1af32e60fffcafe7e008a92169", - "panels/ha-panel-dev-service.html": "bb5c587ada694e0fd42ceaaedd6fe6aa", - "panels/ha-panel-dev-state.html": "4608326978256644c42b13940c028e0a", - "panels/ha-panel-dev-template.html": "0a099d4589636ed3038a3e9f020468a7", + "panels/ha-panel-dev-service.html": "07e83c6b7f79d78a59258f6dba477b54", + "panels/ha-panel-dev-state.html": "fd8eb946856b1346a87a51d0c86854ff", + "panels/ha-panel-dev-template.html": "7cff8a2ef3f44fdaf357a0d41696bf6d", "panels/ha-panel-history.html": "efe1bcdd7733b09e55f4f965d171c295", "panels/ha-panel-iframe.html": "d920f0aa3c903680f2f8795e2255daab", "panels/ha-panel-logbook.html": "66108d82763359a218c9695f0553de40", diff --git a/homeassistant/components/frontend/www_static/core.js b/homeassistant/components/frontend/www_static/core.js index 336aab04ffe..b25bd9fef45 100644 --- a/homeassistant/components/frontend/www_static/core.js +++ b/homeassistant/components/frontend/www_static/core.js @@ -1,4 +1,4 @@ -!function(){"use strict";function t(t){return t&&"object"==typeof t&&"default"in t?t["default"]:t}function e(t,e){return e={exports:{}},t(e,e.exports),e.exports}function n(t,e){var n=e.authToken,r=e.host;return je({authToken:n,host:r,isValidating:!0,isInvalid:!1,errorMessage:""})}function r(){return Ne.getInitialState()}function i(t,e){var n=e.errorMessage;return t.withMutations(function(t){return t.set("isValidating",!1).set("isInvalid",!0).set("errorMessage",n)})}function o(t,e){var n=e.authToken,r=e.host;return Ue({authToken:n,host:r})}function u(){return Pe.getInitialState()}function a(t,e){var n=e.rememberAuth;return n}function s(t){return t.withMutations(function(t){t.set("isStreaming",!0).set("useStreaming",!0).set("hasError",!1)})}function c(t){return t.withMutations(function(t){t.set("isStreaming",!1).set("useStreaming",!1).set("hasError",!1)})}function f(t){return t.withMutations(function(t){t.set("isStreaming",!1).set("hasError",!0)})}function h(){return Ke.getInitialState()}function l(t,e){var n=e.model,r=e.result,i=e.params,o=n.entity;if(!r)return t;var u=i.replace?$e({}):t.get(o),a=Array.isArray(r)?r:[r],s=n.fromJSON||$e;return t.set(o,u.withMutations(function(t){for(var e=0;e199&&u.status<300?t(e):n(e)},u.onerror=function(){return n({})},r?(u.setRequestHeader("Content-Type","application/json;charset=UTF-8"),u.send(JSON.stringify(r))):u.send()})}function A(t,e){var n=e.message;return t.set(t.size,n)}function D(){return Dn.getInitialState()}function C(t,e){t.dispatch(wn.NOTIFICATION_CREATED,{message:e})}function z(t){t.registerStores({notifications:Dn})}function R(t,e){if("lock"===t)return!0;if("garage_door"===t)return!0;var n=e.get(t);return!!n&&n.services.has("turn_on")}function L(t,e){return!!t&&("group"===t.domain?"on"===t.state||"off"===t.state:R(t.domain,e))}function M(t,e){return[er(t),function(t){return!!t&&t.services.has(e)}]}function j(t){return[bn.byId(t),tr,L]}function N(t,e,n){function r(){var c=(new Date).getTime()-a;c0?i=setTimeout(r,e-c):(i=null,n||(s=t.apply(u,o),i||(u=o=null)))}var i,o,u,a,s;null==e&&(e=100);var c=function(){u=this,o=arguments,a=(new Date).getTime();var c=n&&!i;return i||(i=setTimeout(r,e)),c&&(s=t.apply(u,o),u=o=null),s};return c.clear=function(){i&&(clearTimeout(i),i=null)},c}function k(t,e){var n=e.component;return t.push(n)}function U(t,e){var n=e.components;return pr(n)}function P(){return _r.getInitialState()}function H(t,e){var n=e.latitude,r=e.longitude,i=e.location_name,o=e.temperature_unit,u=e.time_zone,a=e.version;return vr({latitude:n,longitude:r,location_name:i,temperature_unit:o,time_zone:u,serverVersion:a})}function x(){return yr.getInitialState()}function V(t,e){t.dispatch(hr.SERVER_CONFIG_LOADED,e)}function q(t){fn(t,"GET","config").then(function(e){return V(t,e)})}function F(t,e){t.dispatch(hr.COMPONENT_LOADED,{component:e})}function G(t){return[["serverComponent"],function(e){return e.contains(t)}]}function K(t){t.registerStores({serverComponent:_r,serverConfig:yr})}function Y(t,e){var n=e.pane;return n}function B(){return Dr.getInitialState()}function J(t,e){var n=e.panels;return zr(n)}function W(){return Rr.getInitialState()}function X(t,e){var n=e.show;return!!n}function Q(){return Mr.getInitialState()}function Z(t,e){t.dispatch(Tr.SHOW_SIDEBAR,{show:e})}function $(t,e){t.dispatch(Tr.NAVIGATE,{pane:e})}function tt(t,e){t.dispatch(Tr.PANELS_LOADED,{panels:e})}function et(t,e){var n=e.entityId;return n}function nt(){return qr.getInitialState()}function rt(t,e){t.dispatch(xr.SELECT_ENTITY,{entityId:e})}function it(t){t.dispatch(xr.SELECT_ENTITY,{entityId:null})}function ot(t){return!t||(new Date).getTime()-t>6e4}function ut(t,e){var n=e.date;return n.toISOString()}function at(){return Yr.getInitialState()}function st(t,e){var n=e.date,r=e.stateHistory;return 0===r.length?t.set(n,Jr({})):t.withMutations(function(t){r.forEach(function(e){return t.setIn([n,e[0].entity_id],Jr(e.map(dn.fromJSON)))})})}function ct(){return Wr.getInitialState()}function ft(t,e){var n=e.stateHistory;return t.withMutations(function(t){n.forEach(function(e){return t.set(e[0].entity_id,$r(e.map(dn.fromJSON)))})})}function ht(){return ti.getInitialState()}function lt(t,e){var n=e.stateHistory,r=(new Date).getTime();return t.withMutations(function(t){n.forEach(function(e){return t.set(e[0].entity_id,r)}),history.length>1&&t.set(ri,r)})}function pt(){return ii.getInitialState()}function _t(t,e){t.dispatch(Gr.ENTITY_HISTORY_DATE_SELECTED,{date:e})}function dt(t,e){void 0===e&&(e=null),t.dispatch(Gr.RECENT_ENTITY_HISTORY_FETCH_START,{});var n="history/period";return null!==e&&(n+="?filter_entity_id="+e),fn(t,"GET",n).then(function(e){return t.dispatch(Gr.RECENT_ENTITY_HISTORY_FETCH_SUCCESS,{stateHistory:e})},function(){return t.dispatch(Gr.RECENT_ENTITY_HISTORY_FETCH_ERROR,{})})}function vt(t,e){return t.dispatch(Gr.ENTITY_HISTORY_FETCH_START,{date:e}),fn(t,"GET","history/period/"+e).then(function(n){return t.dispatch(Gr.ENTITY_HISTORY_FETCH_SUCCESS,{date:e,stateHistory:n})},function(){return t.dispatch(Gr.ENTITY_HISTORY_FETCH_ERROR,{})})}function yt(t){var e=t.evaluate(ai);return vt(t,e)}function St(t){t.registerStores({currentEntityHistoryDate:Yr,entityHistory:Wr,isLoadingEntityHistory:Qr,recentEntityHistory:ti,recentEntityHistoryUpdated:ii})}function gt(t){t.registerStores({moreInfoEntityId:qr})}function mt(t,e){var n=e.model,r=e.result,i=e.params;if(null===t||"entity"!==n.entity||!i.replace)return t;for(var o=0;oru}function ae(t){t.registerStores({currentLogbookDate:Fo,isLoadingLogbookEntries:Ko,logbookEntries:Qo,logbookEntriesUpdated:tu})}function se(t){return t.set("active",!0)}function ce(t){return t.set("active",!1)}function fe(){return du.getInitialState()}function he(t){return navigator.serviceWorker.getRegistration().then(function(t){if(!t)throw new Error("No service worker registered.");return t.pushManager.subscribe({userVisibleOnly:!0})}).then(function(e){var n;return n=navigator.userAgent.toLowerCase().indexOf("firefox")>-1?"firefox":"chrome",fn(t,"POST","notify.html5",{subscription:e,browser:n}).then(function(){return t.dispatch(lu.PUSH_NOTIFICATIONS_SUBSCRIBE,{})}).then(function(){return!0})})["catch"](function(e){var n;return n=e.message&&e.message.indexOf("gcm_sender_id")!==-1?"Please setup the notify.html5 platform.":"Notification registration failed.",console.error(e),Mn.createNotification(t,n),!1})}function le(t){return navigator.serviceWorker.getRegistration().then(function(t){if(!t)throw new Error("No service worker registered");return t.pushManager.subscribe({userVisibleOnly:!0})}).then(function(e){return fn(t,"DELETE","notify.html5",{subscription:e}).then(function(){return e.unsubscribe()}).then(function(){return t.dispatch(lu.PUSH_NOTIFICATIONS_UNSUBSCRIBE,{})}).then(function(){return!0})})["catch"](function(e){var n="Failed unsubscribing for push notifications.";return console.error(e),Mn.createNotification(t,n),!1})}function pe(t){t.registerStores({pushNotifications:du})}function _e(t,e){return fn(t,"POST","template",{template:e})}function de(t){return t.set("isListening",!0)}function ve(t,e){var n=e.interimTranscript,r=e.finalTranscript;return t.withMutations(function(t){return t.set("isListening",!0).set("isTransmitting",!1).set("interimTranscript",n).set("finalTranscript",r)})}function ye(t,e){var n=e.finalTranscript;return t.withMutations(function(t){return t.set("isListening",!1).set("isTransmitting",!0).set("interimTranscript","").set("finalTranscript",n)})}function Se(){return Ru.getInitialState()}function ge(){return Ru.getInitialState()}function me(){return Ru.getInitialState()}function Ee(t){return Lu[t.hassId]}function Ie(t){var e=Ee(t);if(e){var n=e.finalTranscript||e.interimTranscript;t.dispatch(Du.VOICE_TRANSMITTING,{finalTranscript:n}),ir.callService(t,"conversation","process",{text:n}).then(function(){t.dispatch(Du.VOICE_DONE)},function(){t.dispatch(Du.VOICE_ERROR)})}}function be(t){var e=Ee(t);e&&(e.recognition.stop(),Lu[t.hassId]=!1)}function Oe(t){Ie(t),be(t)}function we(t){var e=Oe.bind(null,t);e();var n=new webkitSpeechRecognition;Lu[t.hassId]={recognition:n,interimTranscript:"",finalTranscript:""},n.interimResults=!0,n.onstart=function(){return t.dispatch(Du.VOICE_START)},n.onerror=function(){return t.dispatch(Du.VOICE_ERROR)},n.onend=e,n.onresult=function(e){var n=Ee(t);if(n){for(var r="",i="",o=e.resultIndex;o=n)}function c(t,e){return h(t,e,0)}function f(t,e){return h(t,e,e)}function h(t,e,n){return void 0===t?n:t<0?Math.max(0,e+t):void 0===e?t:Math.min(e,t)}function l(t){return v(t)?t:C(t)}function p(t){return y(t)?t:z(t)}function _(t){return S(t)?t:R(t)}function d(t){return v(t)&&!g(t)?t:L(t)}function v(t){return!(!t||!t[dn])}function y(t){return!(!t||!t[vn])}function S(t){return!(!t||!t[yn])}function g(t){return y(t)||S(t)}function m(t){return!(!t||!t[Sn])}function E(t){this.next=t}function I(t,e,n,r){var i=0===t?e:1===t?n:[e,n];return r?r.value=i:r={value:i,done:!1},r}function b(){return{value:void 0,done:!0}}function O(t){return!!A(t)}function w(t){return t&&"function"==typeof t.next}function T(t){var e=A(t);return e&&e.call(t)}function A(t){var e=t&&(In&&t[In]||t[bn]);if("function"==typeof e)return e}function D(t){return t&&"number"==typeof t.length}function C(t){return null===t||void 0===t?P():v(t)?t.toSeq():V(t)}function z(t){return null===t||void 0===t?P().toKeyedSeq():v(t)?y(t)?t.toSeq():t.fromEntrySeq():H(t)}function R(t){return null===t||void 0===t?P():v(t)?y(t)?t.entrySeq():t.toIndexedSeq():x(t)}function L(t){return(null===t||void 0===t?P():v(t)?y(t)?t.entrySeq():t:x(t)).toSetSeq()}function M(t){this._array=t,this.size=t.length}function j(t){var e=Object.keys(t);this._object=t,this._keys=e,this.size=e.length}function N(t){this._iterable=t,this.size=t.length||t.size}function k(t){this._iterator=t,this._iteratorCache=[]}function U(t){return!(!t||!t[wn])}function P(){return Tn||(Tn=new M([]))}function H(t){var e=Array.isArray(t)?new M(t).fromEntrySeq():w(t)?new k(t).fromEntrySeq():O(t)?new N(t).fromEntrySeq():"object"==typeof t?new j(t):void 0;if(!e)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+t);return e}function x(t){var e=q(t);if(!e)throw new TypeError("Expected Array or iterable object of values: "+t);return e}function V(t){var e=q(t)||"object"==typeof t&&new j(t);if(!e)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+t);return e}function q(t){return D(t)?new M(t):w(t)?new k(t):O(t)?new N(t):void 0}function F(t,e,n,r){var i=t._cache;if(i){for(var o=i.length-1,u=0;u<=o;u++){var a=i[n?o-u:u];if(e(a[1],r?a[0]:u,t)===!1)return u+1}return u}return t.__iterateUncached(e,n)}function G(t,e,n,r){var i=t._cache;if(i){var o=i.length-1,u=0;return new E(function(){var t=i[n?o-u:u];return u++>o?b():I(e,r?t[0]:u-1,t[1])})}return t.__iteratorUncached(e,n)}function K(){throw TypeError("Abstract")}function Y(){}function B(){}function J(){}function W(t,e){if(t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1;if("function"==typeof t.valueOf&&"function"==typeof e.valueOf){if(t=t.valueOf(),e=e.valueOf(),t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1}return!("function"!=typeof t.equals||"function"!=typeof e.equals||!t.equals(e))}function X(t,e){return e?Q(e,t,"",{"":t}):Z(t)}function Q(t,e,n,r){return Array.isArray(e)?t.call(r,n,R(e).map(function(n,r){return Q(t,n,r,e)})):$(e)?t.call(r,n,z(e).map(function(n,r){return Q(t,n,r,e)})):e}function Z(t){return Array.isArray(t)?R(t).map(Z).toList():$(t)?z(t).map(Z).toMap():t}function $(t){return t&&(t.constructor===Object||void 0===t.constructor)}function tt(t){return t>>>1&1073741824|3221225471&t}function et(t){if(t===!1||null===t||void 0===t)return 0;if("function"==typeof t.valueOf&&(t=t.valueOf(),t===!1||null===t||void 0===t))return 0;if(t===!0)return 1;var e=typeof t;if("number"===e){var n=0|t;for(n!==t&&(n^=4294967295*t);t>4294967295;)t/=4294967295,n^=t;return tt(n)}return"string"===e?t.length>jn?nt(t):rt(t):"function"==typeof t.hashCode?t.hashCode():it(t)}function nt(t){var e=Un[t];return void 0===e&&(e=rt(t),kn===Nn&&(kn=0,Un={}),kn++,Un[t]=e),e}function rt(t){for(var e=0,n=0;n0)switch(t.nodeType){case 1:return t.uniqueID;case 9:return t.documentElement&&t.documentElement.uniqueID}}function ut(t,e){if(!t)throw new Error(e)}function at(t){ut(t!==1/0,"Cannot perform this action with an infinite size.")}function st(t,e){this._iter=t,this._useKeys=e,this.size=t.size}function ct(t){this._iter=t,this.size=t.size}function ft(t){this._iter=t,this.size=t.size}function ht(t){this._iter=t,this.size=t.size}function lt(t){var e=Mt(t);return e._iter=t,e.size=t.size,e.flip=function(){return t},e.reverse=function(){var e=t.reverse.apply(this);return e.flip=function(){return t.reverse()},e},e.has=function(e){return t.includes(e)},e.includes=function(e){return t.has(e)},e.cacheResult=jt,e.__iterateUncached=function(e,n){var r=this;return t.__iterate(function(t,n){return e(n,t,r)!==!1},n)},e.__iteratorUncached=function(e,n){if(e===En){var r=t.__iterator(e,n);return new E(function(){var t=r.next();if(!t.done){var e=t.value[0];t.value[0]=t.value[1],t.value[1]=e}return t})}return t.__iterator(e===mn?gn:mn,n)},e}function pt(t,e,n){var r=Mt(t);return r.size=t.size,r.has=function(e){return t.has(e)},r.get=function(r,i){var o=t.get(r,ln);return o===ln?i:e.call(n,o,r,t)},r.__iterateUncached=function(r,i){var o=this;return t.__iterate(function(t,i,u){return r(e.call(n,t,i,u),i,o)!==!1},i)},r.__iteratorUncached=function(r,i){var o=t.__iterator(En,i);return new E(function(){var i=o.next();if(i.done)return i;var u=i.value,a=u[0];return I(r,a,e.call(n,u[1],a,t),i)})},r}function _t(t,e){var n=Mt(t);return n._iter=t,n.size=t.size,n.reverse=function(){return t},t.flip&&(n.flip=function(){var e=lt(t);return e.reverse=function(){return t.flip()},e}),n.get=function(n,r){return t.get(e?n:-1-n,r)},n.has=function(n){return t.has(e?n:-1-n)},n.includes=function(e){return t.includes(e)},n.cacheResult=jt,n.__iterate=function(e,n){var r=this;return t.__iterate(function(t,n){return e(t,n,r)},!n)},n.__iterator=function(e,n){return t.__iterator(e,!n)},n}function dt(t,e,n,r){var i=Mt(t);return r&&(i.has=function(r){var i=t.get(r,ln);return i!==ln&&!!e.call(n,i,r,t)},i.get=function(r,i){var o=t.get(r,ln);return o!==ln&&e.call(n,o,r,t)?o:i}),i.__iterateUncached=function(i,o){var u=this,a=0;return t.__iterate(function(t,o,s){if(e.call(n,t,o,s))return a++,i(t,r?o:a-1,u)},o),a},i.__iteratorUncached=function(i,o){var u=t.__iterator(En,o),a=0;return new E(function(){for(;;){var o=u.next();if(o.done)return o;var s=o.value,c=s[0],f=s[1];if(e.call(n,f,c,t))return I(i,r?c:a++,f,o)}})},i}function vt(t,e,n){var r=Ut().asMutable();return t.__iterate(function(i,o){r.update(e.call(n,i,o,t),0,function(t){return t+1})}),r.asImmutable()}function yt(t,e,n){var r=y(t),i=(m(t)?be():Ut()).asMutable();t.__iterate(function(o,u){i.update(e.call(n,o,u,t),function(t){return t=t||[],t.push(r?[u,o]:o),t})});var o=Lt(t);return i.map(function(e){return Ct(t,o(e))})}function St(t,e,n,r){var i=t.size;if(void 0!==e&&(e=0|e),void 0!==n&&(n=0|n),s(e,n,i))return t;var o=c(e,i),a=f(n,i);if(o!==o||a!==a)return St(t.toSeq().cacheResult(),e,n,r);var h,l=a-o;l===l&&(h=l<0?0:l);var p=Mt(t);return p.size=0===h?h:t.size&&h||void 0,!r&&U(t)&&h>=0&&(p.get=function(e,n){return e=u(this,e),e>=0&&eh)return b();var t=i.next();return r||e===mn?t:e===gn?I(e,a-1,void 0,t):I(e,a-1,t.value[1],t)})},p}function gt(t,e,n){var r=Mt(t);return r.__iterateUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterate(r,i);var u=0;return t.__iterate(function(t,i,a){return e.call(n,t,i,a)&&++u&&r(t,i,o)}),u},r.__iteratorUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterator(r,i);var u=t.__iterator(En,i),a=!0;return new E(function(){if(!a)return b();var t=u.next();if(t.done)return t;var i=t.value,s=i[0],c=i[1];return e.call(n,c,s,o)?r===En?t:I(r,s,c,t):(a=!1,b())})},r}function mt(t,e,n,r){var i=Mt(t);return i.__iterateUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterate(i,o);var a=!0,s=0;return t.__iterate(function(t,o,c){if(!a||!(a=e.call(n,t,o,c)))return s++,i(t,r?o:s-1,u)}),s},i.__iteratorUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterator(i,o);var a=t.__iterator(En,o),s=!0,c=0;return new E(function(){var t,o,f;do{if(t=a.next(),t.done)return r||i===mn?t:i===gn?I(i,c++,void 0,t):I(i,c++,t.value[1],t);var h=t.value;o=h[0],f=h[1],s&&(s=e.call(n,f,o,u))}while(s);return i===En?t:I(i,o,f,t)})},i}function Et(t,e){var n=y(t),r=[t].concat(e).map(function(t){return v(t)?n&&(t=p(t)):t=n?H(t):x(Array.isArray(t)?t:[t]),t}).filter(function(t){return 0!==t.size});if(0===r.length)return t;if(1===r.length){var i=r[0];if(i===t||n&&y(i)||S(t)&&S(i))return i}var o=new M(r);return n?o=o.toKeyedSeq():S(t)||(o=o.toSetSeq()),o=o.flatten(!0),o.size=r.reduce(function(t,e){if(void 0!==t){var n=e.size;if(void 0!==n)return t+n}},0),o}function It(t,e,n){var r=Mt(t);return r.__iterateUncached=function(r,i){function o(t,s){var c=this;t.__iterate(function(t,i){return(!e||s0}function Dt(t,e,n){var r=Mt(t);return r.size=new M(n).map(function(t){return t.size}).min(),r.__iterate=function(t,e){for(var n,r=this,i=this.__iterator(mn,e),o=0;!(n=i.next()).done&&t(n.value,o++,r)!==!1;);return o},r.__iteratorUncached=function(t,r){var i=n.map(function(t){return t=l(t),T(r?t.reverse():t)}),o=0,u=!1;return new E(function(){var n;return u||(n=i.map(function(t){return t.next()}),u=n.some(function(t){return t.done})),u?b():I(t,o++,e.apply(null,n.map(function(t){return t.value})))})},r}function Ct(t,e){return U(t)?e:t.constructor(e)}function zt(t){if(t!==Object(t))throw new TypeError("Expected [K, V] tuple: "+t)}function Rt(t){ -return at(t.size),o(t)}function Lt(t){return y(t)?p:S(t)?_:d}function Mt(t){return Object.create((y(t)?z:S(t)?R:L).prototype)}function jt(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):C.prototype.cacheResult.call(this)}function Nt(t,e){return t>e?1:t>>n)&hn,a=(0===n?r:r>>>n)&hn,s=u===a?[Zt(t,e,n+cn,r,i)]:(o=new Ft(e,r,i),u>>=1)u[a]=1&n?e[o++]:void 0;return u[r]=i,new Vt(t,o+1,u)}function ne(t,e,n){for(var r=[],i=0;i>1&1431655765,t=(858993459&t)+(t>>2&858993459),t=t+(t>>4)&252645135,t+=t>>8,t+=t>>16,127&t}function ae(t,e,n,r){var o=r?t:i(t);return o[e]=n,o}function se(t,e,n,r){var i=t.length+1;if(r&&e+1===i)return t[e]=n,t;for(var o=new Array(i),u=0,a=0;a0&&ro?0:o-n,c=u-n;return c>fn&&(c=fn),function(){if(i===c)return Bn;var t=e?--c:i++;return r&&r[t]}}function i(t,r,i){var a,s=t&&t.array,c=i>o?0:o-i>>r,f=(u-i>>r)+1;return f>fn&&(f=fn),function(){for(;;){if(a){var t=a();if(t!==Bn)return t;a=null}if(c===f)return Bn;var o=e?--f:c++;a=n(s&&s[o],r-cn,i+(o<=t.size||n<0)return t.withMutations(function(t){n<0?me(t,n).set(0,r):me(t,0,n+1).set(n,r)});n+=t._origin;var i=t._tail,o=t._root,a=e(_n);return n>=Ie(t._capacity)?i=ye(i,t.__ownerID,0,n,r,a):o=ye(o,t.__ownerID,t._level,n,r,a),a.value?t.__ownerID?(t._root=o,t._tail=i,t.__hash=void 0,t.__altered=!0,t):_e(t._origin,t._capacity,t._level,o,i):t}function ye(t,e,r,i,o,u){var a=i>>>r&hn,s=t&&a0){var f=t&&t.array[a],h=ye(f,e,r-cn,i,o,u);return h===f?t:(c=Se(t,e),c.array[a]=h,c)}return s&&t.array[a]===o?t:(n(u),c=Se(t,e),void 0===o&&a===c.array.length-1?c.array.pop():c.array[a]=o,c)}function Se(t,e){return e&&t&&e===t.ownerID?t:new le(t?t.array.slice():[],e)}function ge(t,e){if(e>=Ie(t._capacity))return t._tail;if(e<1<0;)n=n.array[e>>>r&hn],r-=cn;return n}}function me(t,e,n){void 0!==e&&(e=0|e),void 0!==n&&(n=0|n);var i=t.__ownerID||new r,o=t._origin,u=t._capacity,a=o+e,s=void 0===n?u:n<0?u+n:o+n;if(a===o&&s===u)return t;if(a>=s)return t.clear();for(var c=t._level,f=t._root,h=0;a+h<0;)f=new le(f&&f.array.length?[void 0,f]:[],i),c+=cn,h+=1<=1<l?new le([],i):_;if(_&&p>l&&acn;y-=cn){var S=l>>>y&hn;v=v.array[S]=Se(v.array[S],i)}v.array[l>>>cn&hn]=_}if(s=p)a-=p,s-=p,c=cn,f=null,d=d&&d.removeBefore(i,0,a);else if(a>o||p>>c&hn;if(g!==p>>>c&hn)break;g&&(h+=(1<o&&(f=f.removeBefore(i,c,a-h)),f&&pi&&(i=a.size),v(u)||(a=a.map(function(t){return X(t)})),r.push(a)}return i>t.size&&(t=t.setSize(i)),ie(t,e,r)}function Ie(t){return t>>cn<=fn&&u.size>=2*o.size?(i=u.filter(function(t,e){return void 0!==t&&a!==e}),r=i.toKeyedSeq().map(function(t){return t[0]}).flip().toMap(),t.__ownerID&&(r.__ownerID=i.__ownerID=t.__ownerID)):(r=o.remove(e),i=a===u.size-1?u.pop():u.set(a,void 0))}else if(s){if(n===u.get(a)[1])return t;r=o,i=u.set(a,[e,n])}else r=o.set(e,u.size),i=u.set(u.size,[e,n]);return t.__ownerID?(t.size=r.size,t._map=r,t._list=i,t.__hash=void 0,t):we(r,i)}function De(t){return null===t||void 0===t?Re():Ce(t)?t:Re().unshiftAll(t)}function Ce(t){return!(!t||!t[Wn])}function ze(t,e,n,r){var i=Object.create(Xn);return i.size=t,i._head=e,i.__ownerID=n,i.__hash=r,i.__altered=!1,i}function Re(){return Qn||(Qn=ze(0))}function Le(t){return null===t||void 0===t?ke():Me(t)&&!m(t)?t:ke().withMutations(function(e){var n=d(t);at(n.size),n.forEach(function(t){return e.add(t)})})}function Me(t){return!(!t||!t[Zn])}function je(t,e){return t.__ownerID?(t.size=e.size,t._map=e,t):e===t._map?t:0===e.size?t.__empty():t.__make(e)}function Ne(t,e){var n=Object.create($n);return n.size=t?t.size:0,n._map=t,n.__ownerID=e,n}function ke(){return tr||(tr=Ne(Jt()))}function Ue(t){return null===t||void 0===t?xe():Pe(t)?t:xe().withMutations(function(e){var n=d(t);at(n.size),n.forEach(function(t){return e.add(t)})})}function Pe(t){return Me(t)&&m(t)}function He(t,e){var n=Object.create(er);return n.size=t?t.size:0,n._map=t,n.__ownerID=e,n}function xe(){return nr||(nr=He(Te()))}function Ve(t,e){var n,r=function(o){if(o instanceof r)return o;if(!(this instanceof r))return new r(o);if(!n){n=!0;var u=Object.keys(t);Ge(i,u),i.size=u.length,i._name=e,i._keys=u,i._defaultValues=t}this._map=Ut(o)},i=r.prototype=Object.create(rr);return i.constructor=r,r}function qe(t,e,n){var r=Object.create(Object.getPrototypeOf(t));return r._map=e,r.__ownerID=n,r}function Fe(t){return t._name||t.constructor.name||"Record"}function Ge(t,e){try{e.forEach(Ke.bind(void 0,t))}catch(n){}}function Ke(t,e){Object.defineProperty(t,e,{get:function(){return this.get(e)},set:function(t){ut(this.__ownerID,"Cannot set on an immutable record."),this.set(e,t)}})}function Ye(t,e){if(t===e)return!0;if(!v(e)||void 0!==t.size&&void 0!==e.size&&t.size!==e.size||void 0!==t.__hash&&void 0!==e.__hash&&t.__hash!==e.__hash||y(t)!==y(e)||S(t)!==S(e)||m(t)!==m(e))return!1;if(0===t.size&&0===e.size)return!0;var n=!g(t);if(m(t)){var r=t.entries();return e.every(function(t,e){var i=r.next().value;return i&&W(i[1],t)&&(n||W(i[0],e))})&&r.next().done}var i=!1;if(void 0===t.size)if(void 0===e.size)"function"==typeof t.cacheResult&&t.cacheResult();else{i=!0;var o=t;t=e,e=o}var u=!0,a=e.__iterate(function(e,r){if(n?!t.has(e):i?!W(e,t.get(r,ln)):!W(t.get(r,ln),e))return u=!1,!1});return u&&t.size===a}function Be(t,e,n){if(!(this instanceof Be))return new Be(t,e,n);if(ut(0!==n,"Cannot step a Range by 0"),t=t||0,void 0===e&&(e=1/0),n=void 0===n?1:Math.abs(n),ee?-1:0}function rn(t){if(t.size===1/0)return 0;var e=m(t),n=y(t),r=e?1:0,i=t.__iterate(n?e?function(t,e){r=31*r+un(et(t),et(e))|0}:function(t,e){r=r+un(et(t),et(e))|0}:e?function(t){r=31*r+et(t)|0}:function(t){r=r+et(t)|0});return on(i,r)}function on(t,e){return e=Dn(e,3432918353),e=Dn(e<<15|e>>>-15,461845907),e=Dn(e<<13|e>>>-13,5),e=(e+3864292196|0)^t,e=Dn(e^e>>>16,2246822507),e=Dn(e^e>>>13,3266489909),e=tt(e^e>>>16)}function un(t,e){return t^e+2654435769+(t<<6)+(t>>2)|0}var an=Array.prototype.slice,sn="delete",cn=5,fn=1<r?b():I(t,i,n[e?r-i++:i++])})},t(j,z),j.prototype.get=function(t,e){return void 0===e||this.has(t)?this._object[t]:e},j.prototype.has=function(t){return this._object.hasOwnProperty(t)},j.prototype.__iterate=function(t,e){for(var n=this,r=this._object,i=this._keys,o=i.length-1,u=0;u<=o;u++){var a=i[e?o-u:u];if(t(r[a],a,n)===!1)return u+1}return u},j.prototype.__iterator=function(t,e){var n=this._object,r=this._keys,i=r.length-1,o=0;return new E(function(){var u=r[e?i-o:o];return o++>i?b():I(t,u,n[u])})},j.prototype[Sn]=!0,t(N,R),N.prototype.__iterateUncached=function(t,e){var n=this;if(e)return this.cacheResult().__iterate(t,e);var r=this._iterable,i=T(r),o=0;if(w(i))for(var u;!(u=i.next()).done&&t(u.value,o++,n)!==!1;);return o},N.prototype.__iteratorUncached=function(t,e){if(e)return this.cacheResult().__iterator(t,e);var n=this._iterable,r=T(n);if(!w(r))return new E(b);var i=0;return new E(function(){var e=r.next();return e.done?e:I(t,i++,e.value)})},t(k,R),k.prototype.__iterateUncached=function(t,e){var n=this;if(e)return this.cacheResult().__iterate(t,e);for(var r=this._iterator,i=this._iteratorCache,o=0;o=r.length){var e=n.next();if(e.done)return e;r[i]=e.value}return I(t,i,r[i++])})};var Tn;t(K,l),t(Y,K),t(B,K),t(J,K),K.Keyed=Y,K.Indexed=B,K.Set=J;var An,Dn="function"==typeof Math.imul&&Math.imul(4294967295,2)===-2?Math.imul:function(t,e){t=0|t,e=0|e;var n=65535&t,r=65535&e;return n*r+((t>>>16)*r+n*(e>>>16)<<16>>>0)|0},Cn=Object.isExtensible,zn=function(){try{return Object.defineProperty({},"@",{}),!0}catch(t){return!1}}(),Rn="function"==typeof WeakMap;Rn&&(An=new WeakMap);var Ln=0,Mn="__immutablehash__";"function"==typeof Symbol&&(Mn=Symbol(Mn));var jn=16,Nn=255,kn=0,Un={};t(st,z),st.prototype.get=function(t,e){return this._iter.get(t,e)},st.prototype.has=function(t){return this._iter.has(t)},st.prototype.valueSeq=function(){return this._iter.valueSeq()},st.prototype.reverse=function(){var t=this,e=_t(this,!0);return this._useKeys||(e.valueSeq=function(){return t._iter.toSeq().reverse()}),e},st.prototype.map=function(t,e){var n=this,r=pt(this,t,e);return this._useKeys||(r.valueSeq=function(){return n._iter.toSeq().map(t,e)}),r},st.prototype.__iterate=function(t,e){var n,r=this;return this._iter.__iterate(this._useKeys?function(e,n){return t(e,n,r)}:(n=e?Rt(this):0,function(i){return t(i,e?--n:n++,r)}),e)},st.prototype.__iterator=function(t,e){if(this._useKeys)return this._iter.__iterator(t,e);var n=this._iter.__iterator(mn,e),r=e?Rt(this):0;return new E(function(){var i=n.next();return i.done?i:I(t,e?--r:r++,i.value,i)})},st.prototype[Sn]=!0,t(ct,R),ct.prototype.includes=function(t){return this._iter.includes(t)},ct.prototype.__iterate=function(t,e){var n=this,r=0;return this._iter.__iterate(function(e){return t(e,r++,n)},e)},ct.prototype.__iterator=function(t,e){var n=this._iter.__iterator(mn,e),r=0;return new E(function(){var e=n.next();return e.done?e:I(t,r++,e.value,e)})},t(ft,L),ft.prototype.has=function(t){return this._iter.includes(t)},ft.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate(function(e){return t(e,e,n)},e)},ft.prototype.__iterator=function(t,e){var n=this._iter.__iterator(mn,e);return new E(function(){var e=n.next();return e.done?e:I(t,e.value,e.value,e)})},t(ht,z),ht.prototype.entrySeq=function(){return this._iter.toSeq()},ht.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate(function(e){if(e){zt(e);var r=v(e);return t(r?e.get(1):e[1],r?e.get(0):e[0],n)}},e)},ht.prototype.__iterator=function(t,e){var n=this._iter.__iterator(mn,e);return new E(function(){for(;;){var e=n.next();if(e.done)return e;var r=e.value;if(r){zt(r);var i=v(r);return I(t,i?r.get(0):r[0],i?r.get(1):r[1],e)}}})},ct.prototype.cacheResult=st.prototype.cacheResult=ft.prototype.cacheResult=ht.prototype.cacheResult=jt,t(Ut,Y),Ut.prototype.toString=function(){return this.__toString("Map {","}")},Ut.prototype.get=function(t,e){return this._root?this._root.get(0,void 0,t,e):e},Ut.prototype.set=function(t,e){return Wt(this,t,e)},Ut.prototype.setIn=function(t,e){return this.updateIn(t,ln,function(){return e})},Ut.prototype.remove=function(t){return Wt(this,t,ln)},Ut.prototype.deleteIn=function(t){return this.updateIn(t,function(){return ln})},Ut.prototype.update=function(t,e,n){return 1===arguments.length?t(this):this.updateIn([t],e,n)},Ut.prototype.updateIn=function(t,e,n){n||(n=e,e=void 0);var r=oe(this,kt(t),e,n);return r===ln?void 0:r},Ut.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):Jt()},Ut.prototype.merge=function(){return ne(this,void 0,arguments)},Ut.prototype.mergeWith=function(t){var e=an.call(arguments,1);return ne(this,t,e)},Ut.prototype.mergeIn=function(t){var e=an.call(arguments,1);return this.updateIn(t,Jt(),function(t){return"function"==typeof t.merge?t.merge.apply(t,e):e[e.length-1]})},Ut.prototype.mergeDeep=function(){return ne(this,re(void 0),arguments)},Ut.prototype.mergeDeepWith=function(t){var e=an.call(arguments,1);return ne(this,re(t),e)},Ut.prototype.mergeDeepIn=function(t){var e=an.call(arguments,1);return this.updateIn(t,Jt(),function(t){return"function"==typeof t.mergeDeep?t.mergeDeep.apply(t,e):e[e.length-1]})},Ut.prototype.sort=function(t){return be(wt(this,t))},Ut.prototype.sortBy=function(t,e){return be(wt(this,e,t))},Ut.prototype.withMutations=function(t){var e=this.asMutable();return t(e),e.wasAltered()?e.__ensureOwner(this.__ownerID):this},Ut.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new r)},Ut.prototype.asImmutable=function(){return this.__ensureOwner()},Ut.prototype.wasAltered=function(){return this.__altered},Ut.prototype.__iterator=function(t,e){return new Gt(this,t,e)},Ut.prototype.__iterate=function(t,e){var n=this,r=0;return this._root&&this._root.iterate(function(e){return r++,t(e[1],e[0],n)},e),r},Ut.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?Bt(this.size,this._root,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},Ut.isMap=Pt;var Pn="@@__IMMUTABLE_MAP__@@",Hn=Ut.prototype;Hn[Pn]=!0,Hn[sn]=Hn.remove,Hn.removeIn=Hn.deleteIn,Ht.prototype.get=function(t,e,n,r){for(var i=this.entries,o=0,u=i.length;o=Vn)return $t(t,f,o,u);var _=t&&t===this.ownerID,d=_?f:i(f);return p?c?h===l-1?d.pop():d[h]=d.pop():d[h]=[o,u]:d.push([o,u]),_?(this.entries=d,this):new Ht(t,d)}},xt.prototype.get=function(t,e,n,r){void 0===e&&(e=et(n));var i=1<<((0===t?e:e>>>t)&hn),o=this.bitmap;return 0===(o&i)?r:this.nodes[ue(o&i-1)].get(t+cn,e,n,r)},xt.prototype.update=function(t,e,n,r,i,o,u){void 0===n&&(n=et(r));var a=(0===e?n:n>>>e)&hn,s=1<=qn)return ee(t,l,c,a,_);if(f&&!_&&2===l.length&&Qt(l[1^h]))return l[1^h];if(f&&_&&1===l.length&&Qt(_))return _;var d=t&&t===this.ownerID,v=f?_?c:c^s:c|s,y=f?_?ae(l,h,_,d):ce(l,h,d):se(l,h,_,d);return d?(this.bitmap=v,this.nodes=y,this):new xt(t,v,y)},Vt.prototype.get=function(t,e,n,r){void 0===e&&(e=et(n));var i=(0===t?e:e>>>t)&hn,o=this.nodes[i];return o?o.get(t+cn,e,n,r):r},Vt.prototype.update=function(t,e,n,r,i,o,u){void 0===n&&(n=et(r));var a=(0===e?n:n>>>e)&hn,s=i===ln,c=this.nodes,f=c[a];if(s&&!f)return this;var h=Xt(f,t,e+cn,n,r,i,o,u);if(h===f)return this;var l=this.count;if(f){if(!h&&(l--,l=0&&t>>e&hn;if(r>=this.array.length)return new le([],t);var i,o=0===r;if(e>0){var u=this.array[r];if(i=u&&u.removeBefore(t,e-cn,n),i===u&&o)return this}if(o&&!i)return this;var a=Se(this,t);if(!o)for(var s=0;s>>e&hn;if(r>=this.array.length)return this;var i;if(e>0){var o=this.array[r];if(i=o&&o.removeAfter(t,e-cn,n),i===o&&r===this.array.length-1)return this}var u=Se(this,t);return u.array.splice(r+1),i&&(u.array[r]=i),u};var Yn,Bn={};t(be,Ut),be.of=function(){return this(arguments)},be.prototype.toString=function(){return this.__toString("OrderedMap {","}")},be.prototype.get=function(t,e){var n=this._map.get(t);return void 0!==n?this._list.get(n)[1]:e},be.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._map.clear(),this._list.clear(),this):Te()},be.prototype.set=function(t,e){return Ae(this,t,e)},be.prototype.remove=function(t){return Ae(this,t,ln)},be.prototype.wasAltered=function(){return this._map.wasAltered()||this._list.wasAltered()},be.prototype.__iterate=function(t,e){var n=this;return this._list.__iterate(function(e){return e&&t(e[1],e[0],n)},e)},be.prototype.__iterator=function(t,e){return this._list.fromEntrySeq().__iterator(t,e)},be.prototype.__ensureOwner=function(t){if(t===this.__ownerID)return this;var e=this._map.__ensureOwner(t),n=this._list.__ensureOwner(t);return t?we(e,n,t,this.__hash):(this.__ownerID=t,this._map=e,this._list=n,this)},be.isOrderedMap=Oe,be.prototype[Sn]=!0,be.prototype[sn]=be.prototype.remove;var Jn;t(De,B),De.of=function(){return this(arguments)},De.prototype.toString=function(){return this.__toString("Stack [","]")},De.prototype.get=function(t,e){var n=this._head;for(t=u(this,t);n&&t--;)n=n.next;return n?n.value:e},De.prototype.peek=function(){return this._head&&this._head.value},De.prototype.push=function(){var t=arguments;if(0===arguments.length)return this;for(var e=this.size+arguments.length,n=this._head,r=arguments.length-1;r>=0;r--)n={value:t[r],next:n};return this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):ze(e,n)},De.prototype.pushAll=function(t){if(t=_(t),0===t.size)return this;at(t.size);var e=this.size,n=this._head;return t.reverse().forEach(function(t){e++,n={value:t,next:n}}),this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):ze(e,n)},De.prototype.pop=function(){return this.slice(1)},De.prototype.unshift=function(){return this.push.apply(this,arguments)},De.prototype.unshiftAll=function(t){return this.pushAll(t)},De.prototype.shift=function(){return this.pop.apply(this,arguments)},De.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Re()},De.prototype.slice=function(t,e){if(s(t,e,this.size))return this;var n=c(t,this.size),r=f(e,this.size);if(r!==this.size)return B.prototype.slice.call(this,t,e);for(var i=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=i,this._head=o,this.__hash=void 0,this.__altered=!0,this):ze(i,o)},De.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?ze(this.size,this._head,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},De.prototype.__iterate=function(t,e){var n=this;if(e)return this.reverse().__iterate(t);for(var r=0,i=this._head;i&&t(i.value,r++,n)!==!1;)i=i.next;return r},De.prototype.__iterator=function(t,e){if(e)return this.reverse().__iterator(t);var n=0,r=this._head;return new E(function(){if(r){var e=r.value;return r=r.next,I(t,n++,e)}return b()})},De.isStack=Ce;var Wn="@@__IMMUTABLE_STACK__@@",Xn=De.prototype;Xn[Wn]=!0,Xn.withMutations=Hn.withMutations,Xn.asMutable=Hn.asMutable,Xn.asImmutable=Hn.asImmutable,Xn.wasAltered=Hn.wasAltered;var Qn;t(Le,J),Le.of=function(){return this(arguments)},Le.fromKeys=function(t){return this(p(t).keySeq())},Le.prototype.toString=function(){return this.__toString("Set {","}")},Le.prototype.has=function(t){return this._map.has(t)},Le.prototype.add=function(t){return je(this,this._map.set(t,!0))},Le.prototype.remove=function(t){return je(this,this._map.remove(t))},Le.prototype.clear=function(){return je(this,this._map.clear())},Le.prototype.union=function(){var t=an.call(arguments,0);return t=t.filter(function(t){return 0!==t.size}),0===t.length?this:0!==this.size||this.__ownerID||1!==t.length?this.withMutations(function(e){for(var n=0;n1?" by "+this._step:"")+" ]"},Be.prototype.get=function(t,e){return this.has(t)?this._start+u(this,t)*this._step:e},Be.prototype.includes=function(t){var e=(t-this._start)/this._step;return e>=0&&e=0&&nn?b():I(t,o++,u)})},Be.prototype.equals=function(t){return t instanceof Be?this._start===t._start&&this._end===t._end&&this._step===t._step:Ye(this,t)};var ir;t(Je,R),Je.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Je.prototype.get=function(t,e){return this.has(t)?this._value:e},Je.prototype.includes=function(t){return W(this._value,t)},Je.prototype.slice=function(t,e){var n=this.size;return s(t,e,n)?this:new Je(this._value,f(e,n)-c(t,n))},Je.prototype.reverse=function(){return this},Je.prototype.indexOf=function(t){return W(this._value,t)?0:-1},Je.prototype.lastIndexOf=function(t){return W(this._value,t)?this.size:-1},Je.prototype.__iterate=function(t,e){for(var n=this,r=0;rthis.size?e:this.find(function(e,n){return n===t},void 0,e)},has:function(t){return t=u(this,t),t>=0&&(void 0!==this.size?this.size===1/0||t-1&&t%1===0&&t<=Number.MAX_VALUE}var i=Function.prototype.bind;e.isString=function(t){return"string"==typeof t||"[object String]"===n(t)},e.isArray=Array.isArray||function(t){return"[object Array]"===n(t)},"function"!=typeof/./&&"object"!=typeof Int8Array?e.isFunction=function(t){return"function"==typeof t||!1}:e.isFunction=function(t){return"[object Function]"===toString.call(t)},e.isObject=function(t){var e=typeof t;return"function"===e||"object"===e&&!!t},e.extend=function(t){var e=arguments,n=arguments.length;if(!t||n<2)return t||{};for(var r=1;r0)){var e=this.reactorState.get("dirtyStores");if(0!==e.size){var n=c["default"].Set().withMutations(function(n){n.union(t.observerState.get("any")),e.forEach(function(e){var r=t.observerState.getIn(["stores",e]);r&&n.union(r)})});n.forEach(function(e){var n=t.observerState.getIn(["observersMap",e]);if(n){var r=n.get("getter"),i=n.get("handler"),o=p.evaluate(t.prevReactorState,r),u=p.evaluate(t.reactorState,r);t.prevReactorState=o.reactorState,t.reactorState=u.reactorState;var a=o.result,s=u.result;c["default"].is(a,s)||i.call(null,s)}});var r=p.resetDirtyStores(this.reactorState);this.prevReactorState=r,this.reactorState=r}}}},{key:"batchStart",value:function(){this.__batchDepth++}},{key:"batchEnd",value:function(){if(this.__batchDepth--,this.__batchDepth<=0){this.__isDispatching=!0;try{this.__notify()}catch(t){throw this.__isDispatching=!1,t}this.__isDispatching=!1}}}]),t}();e["default"]=(0,y.toFactory)(g),t.exports=e["default"]},function(t,e,n){function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t,e){var n={};return(0,o.each)(e,function(e,r){n[r]=t.evaluate(e)}),n}Object.defineProperty(e,"__esModule",{value:!0});var o=n(4);e["default"]=function(t){return{getInitialState:function(){return i(t,this.getDataBindings())},componentDidMount:function(){var e=this;this.__unwatchFns=[],(0,o.each)(this.getDataBindings(),function(n,i){var o=t.observe(n,function(t){e.setState(r({},i,t))});e.__unwatchFns.push(o)})},componentWillUnmount:function(){for(var t=this;this.__unwatchFns.length;)t.__unwatchFns.shift()()}}},t.exports=e["default"]},function(t,e,n){function r(t){return t&&t.__esModule?t:{"default":t}}function i(t,e){return new L({result:t,reactorState:e})}function o(t,e){return t.withMutations(function(t){(0,R.each)(e,function(e,n){t.getIn(["stores",n])&&console.warn("Store already defined for id = "+n);var r=e.getInitialState();if(void 0===r&&f(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store getInitialState() must return a value, did you forget a return statement");if(f(t,"throwOnNonImmutableStore")&&!(0,D.isImmutableValue)(r))throw new Error("Store getInitialState() must return an immutable value, did you forget to call toImmutable");t.update("stores",function(t){return t.set(n,e)}).update("state",function(t){return t.set(n,r)}).update("dirtyStores",function(t){return t.add(n)}).update("storeStates",function(t){return b(t,[n])})}),I(t)})}function u(t,e){return t.withMutations(function(t){(0,R.each)(e,function(e,n){t.update("stores",function(t){return t.set(n,e)})})})}function a(t,e,n){if(void 0===e&&f(t,"throwOnUndefinedActionType"))throw new Error("`dispatch` cannot be called with an `undefined` action type.");var r=t.get("state"),i=t.get("dirtyStores"),o=r.withMutations(function(r){A["default"].dispatchStart(t,e,n),t.get("stores").forEach(function(o,u){var a=r.get(u),s=void 0;try{s=o.handle(a,e,n)}catch(c){throw A["default"].dispatchError(t,c.message),c}if(void 0===s&&f(t,"throwOnUndefinedStoreReturnValue")){var h="Store handler must return a value, did you forget a return statement";throw A["default"].dispatchError(t,h),new Error(h)}r.set(u,s),a!==s&&(i=i.add(u))}),A["default"].dispatchEnd(t,r,i)}),u=t.set("state",o).set("dirtyStores",i).update("storeStates",function(t){return b(t,i)});return I(u)}function s(t,e){var n=[],r=(0,D.toImmutable)({}).withMutations(function(r){(0,R.each)(e,function(e,i){var o=t.getIn(["stores",i]);if(o){var u=o.deserialize(e);void 0!==u&&(r.set(i,u),n.push(i))}})}),i=w["default"].Set(n);return t.update("state",function(t){return t.merge(r)}).update("dirtyStores",function(t){return t.union(i)}).update("storeStates",function(t){return b(t,n)})}function c(t,e,n){var r=e;(0,z.isKeyPath)(e)&&(e=(0,C.fromKeyPath)(e));var i=t.get("nextId"),o=(0,C.getStoreDeps)(e),u=w["default"].Map({id:i,storeDeps:o,getterKey:r,getter:e,handler:n}),a=void 0;return a=0===o.size?t.update("any",function(t){return t.add(i)}):t.withMutations(function(t){o.forEach(function(e){var n=["stores",e];t.hasIn(n)||t.setIn(n,w["default"].Set()),t.updateIn(["stores",e],function(t){return t.add(i)})})}),a=a.set("nextId",i+1).setIn(["observersMap",i],u),{observerState:a,entry:u}}function f(t,e){var n=t.getIn(["options",e]);if(void 0===n)throw new Error("Invalid option: "+e);return n}function h(t,e,n){var r=t.get("observersMap").filter(function(t){var r=t.get("getterKey"),i=!n||t.get("handler")===n;return!!i&&((0,z.isKeyPath)(e)&&(0,z.isKeyPath)(r)?(0,z.isEqual)(e,r):e===r)});return t.withMutations(function(t){r.forEach(function(e){return l(t,e)})})}function l(t,e){return t.withMutations(function(t){var n=e.get("id"),r=e.get("storeDeps");0===r.size?t.update("any",function(t){return t.remove(n)}):r.forEach(function(e){t.updateIn(["stores",e],function(t){return t?t.remove(n):t})}),t.removeIn(["observersMap",n])})}function p(t){var e=t.get("state");return t.withMutations(function(t){var n=t.get("stores"),r=n.keySeq().toJS();n.forEach(function(n,r){var i=e.get(r),o=n.handleReset(i);if(void 0===o&&f(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store handleReset() must return a value, did you forget a return statement");if(f(t,"throwOnNonImmutableStore")&&!(0,D.isImmutableValue)(o))throw new Error("Store reset state must be an immutable value, did you forget to call toImmutable");t.setIn(["state",r],o)}),t.update("storeStates",function(t){return b(t,r)}),v(t)})}function _(t,e){var n=t.get("state");if((0,z.isKeyPath)(e))return i(n.getIn(e),t);if(!(0,C.isGetter)(e))throw new Error("evaluate must be passed a keyPath or Getter");if(g(t,e))return i(E(t,e),t);var r=(0,C.getDeps)(e).map(function(e){return _(t,e).result}),o=(0,C.getComputeFn)(e).apply(null,r);return i(o,m(t,e,o))}function d(t){var e={};return t.get("stores").forEach(function(n,r){var i=t.getIn(["state",r]),o=n.serialize(i);void 0!==o&&(e[r]=o)}),e}function v(t){return t.set("dirtyStores",w["default"].Set())}function y(t){return t}function S(t,e){var n=y(e);return t.getIn(["cache",n])}function g(t,e){var n=S(t,e);if(!n)return!1;var r=n.get("storeStates");return 0!==r.size&&r.every(function(e,n){return t.getIn(["storeStates",n])===e})}function m(t,e,n){var r=y(e),i=t.get("dispatchId"),o=(0,C.getStoreDeps)(e),u=(0,D.toImmutable)({}).withMutations(function(e){o.forEach(function(n){var r=t.getIn(["storeStates",n]);e.set(n,r)})});return t.setIn(["cache",r],w["default"].Map({value:n,storeStates:u,dispatchId:i}))}function E(t,e){var n=y(e);return t.getIn(["cache",n,"value"])}function I(t){return t.update("dispatchId",function(t){return t+1})}function b(t,e){return t.withMutations(function(t){e.forEach(function(e){var n=t.has(e)?t.get(e)+1:1;t.set(e,n)})})}Object.defineProperty(e,"__esModule",{value:!0}),e.registerStores=o,e.replaceStores=u,e.dispatch=a,e.loadState=s,e.addObserver=c,e.getOption=f,e.removeObserver=h,e.removeObserverByEntry=l,e.reset=p,e.evaluate=_,e.serialize=d,e.resetDirtyStores=v;var O=n(3),w=r(O),T=n(9),A=r(T),D=n(5),C=n(10),z=n(11),R=n(4),L=w["default"].Record({result:null,reactorState:null})},function(t,e,n){var r=n(8);e.dispatchStart=function(t,e,n){(0,r.getOption)(t,"logDispatches")&&console.group&&(console.groupCollapsed("Dispatch: %s",e),console.group("payload"),console.debug(n),console.groupEnd())},e.dispatchError=function(t,e){(0,r.getOption)(t,"logDispatches")&&console.group&&(console.debug("Dispatch error: "+e),console.groupEnd())},e.dispatchEnd=function(t,e,n){(0,r.getOption)(t,"logDispatches")&&console.group&&((0,r.getOption)(t,"logDirtyStores")&&console.log("Stores updated:",n.toList().toJS()),(0,r.getOption)(t,"logAppState")&&console.debug("Dispatch done, new state: ",e.toJS()),console.groupEnd())}},function(t,e,n){function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return(0,l.isArray)(t)&&(0,l.isFunction)(t[t.length-1])}function o(t){return t[t.length-1]}function u(t){return t.slice(0,t.length-1)}function a(t,e){e||(e=h["default"].Set());var n=h["default"].Set().withMutations(function(e){if(!i(t))throw new Error("getFlattenedDeps must be passed a Getter");u(t).forEach(function(t){if((0,p.isKeyPath)(t))e.add((0,f.List)(t));else{if(!i(t))throw new Error("Invalid getter, each dependency must be a KeyPath or Getter");e.union(a(t))}})});return e.union(n)}function s(t){if(!(0,p.isKeyPath)(t))throw new Error("Cannot create Getter from KeyPath: "+t);return[t,_]}function c(t){if(t.hasOwnProperty("__storeDeps"))return t.__storeDeps;var e=a(t).map(function(t){return t.first()}).filter(function(t){return!!t});return Object.defineProperty(t,"__storeDeps",{enumerable:!1,configurable:!1,writable:!1,value:e}),e}Object.defineProperty(e,"__esModule",{value:!0});var f=n(3),h=r(f),l=n(4),p=n(11),_=function(t){return t};e["default"]={isGetter:i,getComputeFn:o,getFlattenedDeps:a,getStoreDeps:c,getDeps:u,fromKeyPath:s},t.exports=e["default"]},function(t,e,n){function r(t){return t&&t.__esModule?t:{"default":t}}function i(t){return(0,s.isArray)(t)&&!(0,s.isFunction)(t[t.length-1])}function o(t,e){var n=a["default"].List(t),r=a["default"].List(e);return a["default"].is(n,r)}Object.defineProperty(e,"__esModule",{value:!0}),e.isKeyPath=i,e.isEqual=o;var u=n(3),a=r(u),s=n(4)},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var r=n(3),i=(0,r.Map)({logDispatches:!1,logAppState:!1,logDirtyStores:!1,throwOnUndefinedActionType:!1,throwOnUndefinedStoreReturnValue:!1,throwOnNonImmutableStore:!1,throwOnDispatchInDispatch:!1});e.PROD_OPTIONS=i;var o=(0,r.Map)({logDispatches:!0,logAppState:!0,logDirtyStores:!0,throwOnUndefinedActionType:!0,throwOnUndefinedStoreReturnValue:!0,throwOnNonImmutableStore:!0,throwOnDispatchInDispatch:!0});e.DEBUG_OPTIONS=o;var u=(0,r.Record)({dispatchId:0,state:(0,r.Map)(),stores:(0,r.Map)(),cache:(0,r.Map)(),storeStates:(0,r.Map)(),dirtyStores:(0,r.Set)(),debug:!1,options:i});e.ReactorState=u;var a=(0,r.Record)({any:(0,r.Set)(),stores:(0,r.Map)({}),observersMap:(0,r.Map)({}),nextId:1});e.ObserverState=a}])})}),Ce=t(De),ze=e(function(t){var e=function(t){var e,n={};if(!(t instanceof Object)||Array.isArray(t))throw new Error("keyMirror(...): Argument must be an object.");for(e in t)t.hasOwnProperty(e)&&(n[e]=e);return n};t.exports=e}),Re=t(ze),Le=Re({VALIDATING_AUTH_TOKEN:null,VALID_AUTH_TOKEN:null,INVALID_AUTH_TOKEN:null,LOG_OUT:null}),Me=Ce.Store,je=Ce.toImmutable,Ne=new Me({getInitialState:function(){return je({isValidating:!1,authToken:!1,host:null,isInvalid:!1,errorMessage:""})},initialize:function(){this.on(Le.VALIDATING_AUTH_TOKEN,n),this.on(Le.VALID_AUTH_TOKEN,r),this.on(Le.INVALID_AUTH_TOKEN,i)}}),ke=Ce.Store,Ue=Ce.toImmutable,Pe=new ke({getInitialState:function(){return Ue({authToken:null,host:""})},initialize:function(){this.on(Le.VALID_AUTH_TOKEN,o),this.on(Le.LOG_OUT,u)}}),He=Ce.Store,xe=new He({getInitialState:function(){return!0},initialize:function(){this.on(Le.VALID_AUTH_TOKEN,a)}}),Ve=Re({STREAM_START:null,STREAM_STOP:null,STREAM_ERROR:null}),qe="object"==typeof window&&"EventSource"in window,Fe=Ce.Store,Ge=Ce.toImmutable,Ke=new Fe({getInitialState:function(){return Ge({isSupported:qe,isStreaming:!1,useStreaming:!0,hasError:!1})},initialize:function(){this.on(Ve.STREAM_START,s),this.on(Ve.STREAM_STOP,c),this.on(Ve.STREAM_ERROR,f),this.on(Ve.LOG_OUT,h)}}),Ye=Re({API_FETCH_ALL_START:null,API_FETCH_ALL_SUCCESS:null,API_FETCH_ALL_FAIL:null,SYNC_SCHEDULED:null,SYNC_SCHEDULE_CANCELLED:null}),Be=Ce.Store,Je=new Be({getInitialState:function(){return!0},initialize:function(){this.on(Ye.API_FETCH_ALL_START,function(){return!0}),this.on(Ye.API_FETCH_ALL_SUCCESS,function(){return!1}),this.on(Ye.API_FETCH_ALL_FAIL,function(){return!1}),this.on(Ye.LOG_OUT,function(){return!1})}}),We=Ce.Store,Xe=new We({getInitialState:function(){return!1},initialize:function(){this.on(Ye.SYNC_SCHEDULED,function(){return!0}),this.on(Ye.SYNC_SCHEDULE_CANCELLED,function(){return!1}),this.on(Ye.LOG_OUT,function(){return!1})}}),Qe=Re({API_FETCH_SUCCESS:null,API_FETCH_START:null,API_FETCH_FAIL:null,API_SAVE_SUCCESS:null,API_SAVE_START:null,API_SAVE_FAIL:null,API_DELETE_SUCCESS:null,API_DELETE_START:null,API_DELETE_FAIL:null,LOG_OUT:null}),Ze=Ce.Store,$e=Ce.toImmutable,tn=new Ze({getInitialState:function(){return $e({})},initialize:function(){var t=this;this.on(Qe.API_FETCH_SUCCESS,l),this.on(Qe.API_SAVE_SUCCESS,l),this.on(Qe.API_DELETE_SUCCESS,p),this.on(Qe.LOG_OUT,function(){return t.getInitialState()})}}),en=e(function(t){function e(t){if(null===t||void 0===t)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(t)}function n(){try{if(!Object.assign)return!1;var t=new String("abc");if(t[5]="de","5"===Object.getOwnPropertyNames(t)[0])return!1;for(var e={},n=0;n<10;n++)e["_"+String.fromCharCode(n)]=n;var r=Object.getOwnPropertyNames(e).map(function(t){return e[t]});if("0123456789"!==r.join(""))return!1;var i={};return"abcdefghijklmnopqrst".split("").forEach(function(t){i[t]=t}),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},i)).join("")}catch(o){return!1}}var r=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable;t.exports=n()?Object.assign:function(t,n){for(var o,u,a=arguments,s=e(t),c=1;c199&&u.status<300?t(e):n(e)},u.onerror=function(){return n({})},r?(u.setRequestHeader("Content-Type","application/json;charset=UTF-8"),u.send(JSON.stringify(r))):u.send()})}function C(t,e){var n=e.message;return t.set(t.size,n)}function z(){return zn.getInitialState()}function R(t,e){t.dispatch(An.NOTIFICATION_CREATED,{message:e})}function M(t){t.registerStores({notifications:zn})}function L(t,e){if("lock"===t)return!0;if("garage_door"===t)return!0;var n=e.get(t);return!!n&&n.services.has("turn_on")}function j(t,e){return!!t&&("group"===t.domain?"on"===t.state||"off"===t.state:L(t.domain,e))}function N(t,e){return[rr(t),function(t){return!!t&&t.services.has(e)}]}function k(t){return[wn.byId(t),nr,j]}function U(t,e,n){function r(){var c=(new Date).getTime()-a;c0?i=setTimeout(r,e-c):(i=null,n||(s=t.apply(u,o),i||(u=o=null)))}var i,o,u,a,s;null==e&&(e=100);var c=function(){u=this,o=arguments,a=(new Date).getTime();var c=n&&!i;return i||(i=setTimeout(r,e)),c&&(s=t.apply(u,o),u=o=null),s};return c.clear=function(){i&&(clearTimeout(i),i=null)},c}function P(t,e){var n=e.component;return t.push(n)}function H(t,e){var n=e.components;return dr(n)}function x(){return vr.getInitialState()}function V(t,e){var n=e.latitude,r=e.longitude,i=e.location_name,o=e.temperature_unit,u=e.time_zone,a=e.version;return Sr({latitude:n,longitude:r,location_name:i,temperature_unit:o,time_zone:u,serverVersion:a})}function q(){return gr.getInitialState()}function F(t,e){t.dispatch(pr.SERVER_CONFIG_LOADED,e)}function G(t){ln(t,"GET","config").then(function(e){return F(t,e)})}function K(t,e){t.dispatch(pr.COMPONENT_LOADED,{component:e})}function Y(t){return[["serverComponent"],function(e){return e.contains(t)}]}function B(t){t.registerStores({serverComponent:vr,serverConfig:gr})}function J(t,e){var n=e.pane;return n}function W(){return zr.getInitialState()}function X(t,e){var n=e.panels;return Mr(n)}function Q(){return Lr.getInitialState()}function Z(t,e){var n=e.show;return!!n}function $(){return Nr.getInitialState()}function tt(t,e){t.dispatch(Dr.SHOW_SIDEBAR,{show:e})}function et(t,e){t.dispatch(Dr.NAVIGATE,{pane:e})}function nt(t,e){t.dispatch(Dr.PANELS_LOADED,{panels:e})}function rt(t,e){var n=e.entityId;return n}function it(){return Gr.getInitialState()}function ot(t,e){t.dispatch(qr.SELECT_ENTITY,{entityId:e})}function ut(t){t.dispatch(qr.SELECT_ENTITY,{entityId:null})}function at(t){return!t||(new Date).getTime()-t>6e4}function st(t,e){var n=e.date;return n.toISOString()}function ct(){return Jr.getInitialState()}function ft(t,e){var n=e.date,r=e.stateHistory;return 0===r.length?t.set(n,Xr({})):t.withMutations(function(t){r.forEach(function(e){return t.setIn([n,e[0].entity_id],Xr(e.map(yn.fromJSON)))})})}function ht(){return Qr.getInitialState()}function lt(t,e){var n=e.stateHistory;return t.withMutations(function(t){n.forEach(function(e){return t.set(e[0].entity_id,ei(e.map(yn.fromJSON)))})})}function pt(){return ni.getInitialState()}function _t(t,e){var n=e.stateHistory,r=(new Date).getTime();return t.withMutations(function(t){n.forEach(function(e){return t.set(e[0].entity_id,r)}),history.length>1&&t.set(oi,r)})}function dt(){return ui.getInitialState()}function vt(t,e){t.dispatch(Yr.ENTITY_HISTORY_DATE_SELECTED,{date:e})}function yt(t,e){void 0===e&&(e=null),t.dispatch(Yr.RECENT_ENTITY_HISTORY_FETCH_START,{});var n="history/period";return null!==e&&(n+="?filter_entity_id="+e),ln(t,"GET",n).then(function(e){return t.dispatch(Yr.RECENT_ENTITY_HISTORY_FETCH_SUCCESS,{stateHistory:e})},function(){return t.dispatch(Yr.RECENT_ENTITY_HISTORY_FETCH_ERROR,{})})}function St(t,e){return t.dispatch(Yr.ENTITY_HISTORY_FETCH_START,{date:e}),ln(t,"GET","history/period/"+e).then(function(n){return t.dispatch(Yr.ENTITY_HISTORY_FETCH_SUCCESS,{date:e,stateHistory:n})},function(){return t.dispatch(Yr.ENTITY_HISTORY_FETCH_ERROR,{})})}function gt(t){var e=t.evaluate(ci);return St(t,e)}function mt(t){t.registerStores({currentEntityHistoryDate:Jr,entityHistory:Qr,isLoadingEntityHistory:$r,recentEntityHistory:ni,recentEntityHistoryUpdated:ui})}function Et(t){t.registerStores({moreInfoEntityId:Gr})}function It(t,e){var n=e.model,r=e.result,i=e.params;if(null===t||"entity"!==n.entity||!i.replace)return t;for(var o=0;ouu}function se(t){t.registerStores({currentLogbookDate:Yo,isLoadingLogbookEntries:Jo,logbookEntries:tu,logbookEntriesUpdated:ru})}function ce(t){return t.set("active",!0)}function fe(t){return t.set("active",!1)}function he(){return Su.getInitialState()}function le(t){return navigator.serviceWorker.getRegistration().then(function(t){if(!t)throw new Error("No service worker registered.");return t.pushManager.subscribe({userVisibleOnly:!0})}).then(function(e){var n;return n=navigator.userAgent.toLowerCase().indexOf("firefox")>-1?"firefox":"chrome",ln(t,"POST","notify.html5",{subscription:e,browser:n}).then(function(){return t.dispatch(du.PUSH_NOTIFICATIONS_SUBSCRIBE,{})}).then(function(){return!0})}).catch(function(e){var n;return n=e.message&&e.message.indexOf("gcm_sender_id")!==-1?"Please setup the notify.html5 platform.":"Notification registration failed.",console.error(e),Nn.createNotification(t,n),!1})}function pe(t){return navigator.serviceWorker.getRegistration().then(function(t){if(!t)throw new Error("No service worker registered");return t.pushManager.subscribe({userVisibleOnly:!0})}).then(function(e){return ln(t,"DELETE","notify.html5",{subscription:e}).then(function(){return e.unsubscribe()}).then(function(){return t.dispatch(du.PUSH_NOTIFICATIONS_UNSUBSCRIBE,{})}).then(function(){return!0})}).catch(function(e){var n="Failed unsubscribing for push notifications.";return console.error(e),Nn.createNotification(t,n),!1})}function _e(t){t.registerStores({pushNotifications:Su})}function de(t,e){return ln(t,"POST","template",{template:e})}function ve(t){return t.set("isListening",!0)}function ye(t,e){var n=e.interimTranscript,r=e.finalTranscript;return t.withMutations(function(t){return t.set("isListening",!0).set("isTransmitting",!1).set("interimTranscript",n).set("finalTranscript",r)})}function Se(t,e){var n=e.finalTranscript;return t.withMutations(function(t){return t.set("isListening",!1).set("isTransmitting",!0).set("interimTranscript","").set("finalTranscript",n)})}function ge(){return ju.getInitialState()}function me(){return ju.getInitialState()}function Ee(){return ju.getInitialState()}function Ie(t){return Nu[t.hassId]}function be(t){var e=Ie(t);if(e){var n=e.finalTranscript||e.interimTranscript;t.dispatch(Ru.VOICE_TRANSMITTING,{finalTranscript:n}),ur.callService(t,"conversation","process",{text:n}).then(function(){t.dispatch(Ru.VOICE_DONE)},function(){t.dispatch(Ru.VOICE_ERROR)})}}function Oe(t){var e=Ie(t);e&&(e.recognition.stop(),Nu[t.hassId]=!1)}function we(t){be(t),Oe(t)}function Te(t){var e=we.bind(null,t);e();var n=new webkitSpeechRecognition;Nu[t.hassId]={recognition:n,interimTranscript:"",finalTranscript:""},n.interimResults=!0,n.onstart=function(){return t.dispatch(Ru.VOICE_START)},n.onerror=function(){return t.dispatch(Ru.VOICE_ERROR)},n.onend=e,n.onresult=function(e){var n=Ie(t);if(n){for(var r="",i="",o=e.resultIndex;o=n)}function c(t,e){return h(t,e,0)}function f(t,e){return h(t,e,e)}function h(t,e,n){return void 0===t?n:t<0?Math.max(0,e+t):void 0===e?t:Math.min(e,t)}function l(t){return v(t)?t:C(t)}function p(t){return y(t)?t:z(t)}function _(t){return S(t)?t:R(t)}function d(t){return v(t)&&!g(t)?t:M(t)}function v(t){return!(!t||!t[dn])}function y(t){return!(!t||!t[vn])}function S(t){return!(!t||!t[yn])}function g(t){return y(t)||S(t)}function m(t){return!(!t||!t[Sn])}function E(t){this.next=t}function I(t,e,n,r){var i=0===t?e:1===t?n:[e,n];return r?r.value=i:r={value:i,done:!1},r}function b(){return{value:void 0,done:!0}}function O(t){return!!A(t)}function w(t){return t&&"function"==typeof t.next}function T(t){var e=A(t);return e&&e.call(t)}function A(t){var e=t&&(In&&t[In]||t[bn]);if("function"==typeof e)return e}function D(t){return t&&"number"==typeof t.length}function C(t){return null===t||void 0===t?P():v(t)?t.toSeq():V(t)}function z(t){return null===t||void 0===t?P().toKeyedSeq():v(t)?y(t)?t.toSeq():t.fromEntrySeq():H(t)}function R(t){return null===t||void 0===t?P():v(t)?y(t)?t.entrySeq():t.toIndexedSeq():x(t)}function M(t){return(null===t||void 0===t?P():v(t)?y(t)?t.entrySeq():t:x(t)).toSetSeq()}function L(t){this._array=t,this.size=t.length}function j(t){var e=Object.keys(t);this._object=t,this._keys=e,this.size=e.length}function N(t){this._iterable=t,this.size=t.length||t.size}function k(t){this._iterator=t,this._iteratorCache=[]}function U(t){return!(!t||!t[wn])}function P(){return Tn||(Tn=new L([]))}function H(t){var e=Array.isArray(t)?new L(t).fromEntrySeq():w(t)?new k(t).fromEntrySeq():O(t)?new N(t).fromEntrySeq():"object"==typeof t?new j(t):void 0;if(!e)throw new TypeError("Expected Array or iterable object of [k, v] entries, or keyed object: "+t);return e}function x(t){var e=q(t);if(!e)throw new TypeError("Expected Array or iterable object of values: "+t);return e}function V(t){var e=q(t)||"object"==typeof t&&new j(t);if(!e)throw new TypeError("Expected Array or iterable object of values, or keyed object: "+t);return e}function q(t){return D(t)?new L(t):w(t)?new k(t):O(t)?new N(t):void 0}function F(t,e,n,r){var i=t._cache;if(i){for(var o=i.length-1,u=0;u<=o;u++){var a=i[n?o-u:u];if(e(a[1],r?a[0]:u,t)===!1)return u+1}return u}return t.__iterateUncached(e,n)}function G(t,e,n,r){var i=t._cache;if(i){var o=i.length-1,u=0;return new E(function(){var t=i[n?o-u:u];return u++>o?b():I(e,r?t[0]:u-1,t[1])})}return t.__iteratorUncached(e,n)}function K(){throw TypeError("Abstract")}function Y(){}function B(){}function J(){}function W(t,e){if(t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1;if("function"==typeof t.valueOf&&"function"==typeof e.valueOf){if(t=t.valueOf(),e=e.valueOf(),t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1}return!("function"!=typeof t.equals||"function"!=typeof e.equals||!t.equals(e))}function X(t,e){return e?Q(e,t,"",{"":t}):Z(t)}function Q(t,e,n,r){return Array.isArray(e)?t.call(r,n,R(e).map(function(n,r){return Q(t,n,r,e)})):$(e)?t.call(r,n,z(e).map(function(n,r){return Q(t,n,r,e)})):e}function Z(t){return Array.isArray(t)?R(t).map(Z).toList():$(t)?z(t).map(Z).toMap():t}function $(t){return t&&(t.constructor===Object||void 0===t.constructor)}function tt(t){return t>>>1&1073741824|3221225471&t}function et(t){if(t===!1||null===t||void 0===t)return 0;if("function"==typeof t.valueOf&&(t=t.valueOf(),t===!1||null===t||void 0===t))return 0;if(t===!0)return 1;var e=typeof t;if("number"===e){var n=0|t;for(n!==t&&(n^=4294967295*t);t>4294967295;)t/=4294967295,n^=t;return tt(n)}return"string"===e?t.length>jn?nt(t):rt(t):"function"==typeof t.hashCode?t.hashCode():it(t)}function nt(t){var e=Un[t];return void 0===e&&(e=rt(t),kn===Nn&&(kn=0,Un={}),kn++,Un[t]=e),e}function rt(t){for(var e=0,n=0;n0)switch(t.nodeType){case 1:return t.uniqueID;case 9:return t.documentElement&&t.documentElement.uniqueID}}function ut(t,e){if(!t)throw new Error(e)}function at(t){ut(t!==1/0,"Cannot perform this action with an infinite size.")}function st(t,e){this._iter=t,this._useKeys=e,this.size=t.size}function ct(t){this._iter=t,this.size=t.size}function ft(t){this._iter=t,this.size=t.size}function ht(t){this._iter=t,this.size=t.size}function lt(t){var e=Lt(t);return e._iter=t,e.size=t.size,e.flip=function(){return t},e.reverse=function(){var e=t.reverse.apply(this);return e.flip=function(){return t.reverse()},e},e.has=function(e){return t.includes(e)},e.includes=function(e){return t.has(e)},e.cacheResult=jt,e.__iterateUncached=function(e,n){var r=this;return t.__iterate(function(t,n){return e(n,t,r)!==!1},n)},e.__iteratorUncached=function(e,n){if(e===En){var r=t.__iterator(e,n);return new E(function(){var t=r.next();if(!t.done){var e=t.value[0];t.value[0]=t.value[1],t.value[1]=e}return t})}return t.__iterator(e===mn?gn:mn,n)},e}function pt(t,e,n){var r=Lt(t);return r.size=t.size,r.has=function(e){return t.has(e)},r.get=function(r,i){var o=t.get(r,ln);return o===ln?i:e.call(n,o,r,t)},r.__iterateUncached=function(r,i){var o=this;return t.__iterate(function(t,i,u){return r(e.call(n,t,i,u),i,o)!==!1},i)},r.__iteratorUncached=function(r,i){var o=t.__iterator(En,i);return new E(function(){var i=o.next();if(i.done)return i;var u=i.value,a=u[0];return I(r,a,e.call(n,u[1],a,t),i)})},r}function _t(t,e){var n=Lt(t);return n._iter=t,n.size=t.size,n.reverse=function(){return t},t.flip&&(n.flip=function(){var e=lt(t);return e.reverse=function(){return t.flip()},e}),n.get=function(n,r){return t.get(e?n:-1-n,r)},n.has=function(n){return t.has(e?n:-1-n)},n.includes=function(e){return t.includes(e)},n.cacheResult=jt,n.__iterate=function(e,n){var r=this;return t.__iterate(function(t,n){return e(t,n,r)},!n)},n.__iterator=function(e,n){return t.__iterator(e,!n)},n}function dt(t,e,n,r){var i=Lt(t);return r&&(i.has=function(r){var i=t.get(r,ln);return i!==ln&&!!e.call(n,i,r,t)},i.get=function(r,i){var o=t.get(r,ln);return o!==ln&&e.call(n,o,r,t)?o:i}),i.__iterateUncached=function(i,o){var u=this,a=0;return t.__iterate(function(t,o,s){if(e.call(n,t,o,s))return a++,i(t,r?o:a-1,u)},o),a},i.__iteratorUncached=function(i,o){var u=t.__iterator(En,o),a=0;return new E(function(){for(;;){var o=u.next();if(o.done)return o;var s=o.value,c=s[0],f=s[1];if(e.call(n,f,c,t))return I(i,r?c:a++,f,o)}})},i}function vt(t,e,n){var r=Ut().asMutable();return t.__iterate(function(i,o){r.update(e.call(n,i,o,t),0,function(t){return t+1})}),r.asImmutable()}function yt(t,e,n){var r=y(t),i=(m(t)?be():Ut()).asMutable();t.__iterate(function(o,u){i.update(e.call(n,o,u,t),function(t){return t=t||[],t.push(r?[u,o]:o),t})});var o=Mt(t);return i.map(function(e){return Ct(t,o(e))})}function St(t,e,n,r){var i=t.size;if(void 0!==e&&(e=0|e),void 0!==n&&(n=0|n),s(e,n,i))return t;var o=c(e,i),a=f(n,i);if(o!==o||a!==a)return St(t.toSeq().cacheResult(),e,n,r);var h,l=a-o;l===l&&(h=l<0?0:l);var p=Lt(t);return p.size=0===h?h:t.size&&h||void 0,!r&&U(t)&&h>=0&&(p.get=function(e,n){return e=u(this,e),e>=0&&eh)return b();var t=i.next();return r||e===mn?t:e===gn?I(e,a-1,void 0,t):I(e,a-1,t.value[1],t)})},p}function gt(t,e,n){var r=Lt(t);return r.__iterateUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterate(r,i);var u=0;return t.__iterate(function(t,i,a){return e.call(n,t,i,a)&&++u&&r(t,i,o)}),u},r.__iteratorUncached=function(r,i){var o=this;if(i)return this.cacheResult().__iterator(r,i);var u=t.__iterator(En,i),a=!0;return new E(function(){if(!a)return b();var t=u.next();if(t.done)return t;var i=t.value,s=i[0],c=i[1];return e.call(n,c,s,o)?r===En?t:I(r,s,c,t):(a=!1,b())})},r}function mt(t,e,n,r){var i=Lt(t);return i.__iterateUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterate(i,o);var a=!0,s=0;return t.__iterate(function(t,o,c){if(!a||!(a=e.call(n,t,o,c)))return s++,i(t,r?o:s-1,u)}),s},i.__iteratorUncached=function(i,o){var u=this;if(o)return this.cacheResult().__iterator(i,o);var a=t.__iterator(En,o),s=!0,c=0;return new E(function(){var t,o,f;do{if(t=a.next(),t.done)return r||i===mn?t:i===gn?I(i,c++,void 0,t):I(i,c++,t.value[1],t);var h=t.value;o=h[0],f=h[1],s&&(s=e.call(n,f,o,u))}while(s);return i===En?t:I(i,o,f,t)})},i}function Et(t,e){var n=y(t),r=[t].concat(e).map(function(t){return v(t)?n&&(t=p(t)):t=n?H(t):x(Array.isArray(t)?t:[t]),t}).filter(function(t){return 0!==t.size});if(0===r.length)return t;if(1===r.length){var i=r[0];if(i===t||n&&y(i)||S(t)&&S(i))return i}var o=new L(r);return n?o=o.toKeyedSeq():S(t)||(o=o.toSetSeq()),o=o.flatten(!0),o.size=r.reduce(function(t,e){if(void 0!==t){var n=e.size;if(void 0!==n)return t+n}},0),o}function It(t,e,n){var r=Lt(t);return r.__iterateUncached=function(r,i){function o(t,s){var c=this;t.__iterate(function(t,i){return(!e||s0}function Dt(t,e,n){var r=Lt(t);return r.size=new L(n).map(function(t){return t.size}).min(),r.__iterate=function(t,e){for(var n,r=this,i=this.__iterator(mn,e),o=0;!(n=i.next()).done&&t(n.value,o++,r)!==!1;);return o},r.__iteratorUncached=function(t,r){var i=n.map(function(t){return t=l(t),T(r?t.reverse():t)}),o=0,u=!1;return new E(function(){var n;return u||(n=i.map(function(t){return t.next()}),u=n.some(function(t){ +return t.done})),u?b():I(t,o++,e.apply(null,n.map(function(t){return t.value})))})},r}function Ct(t,e){return U(t)?e:t.constructor(e)}function zt(t){if(t!==Object(t))throw new TypeError("Expected [K, V] tuple: "+t)}function Rt(t){return at(t.size),o(t)}function Mt(t){return y(t)?p:S(t)?_:d}function Lt(t){return Object.create((y(t)?z:S(t)?R:M).prototype)}function jt(){return this._iter.cacheResult?(this._iter.cacheResult(),this.size=this._iter.size,this):C.prototype.cacheResult.call(this)}function Nt(t,e){return t>e?1:t>>n)&hn,a=(0===n?r:r>>>n)&hn,s=u===a?[Zt(t,e,n+cn,r,i)]:(o=new Ft(e,r,i),u>>=1)u[a]=1&n?e[o++]:void 0;return u[r]=i,new Vt(t,o+1,u)}function ne(t,e,n){for(var r=[],i=0;i>1&1431655765,t=(858993459&t)+(t>>2&858993459),t=t+(t>>4)&252645135,t+=t>>8,t+=t>>16,127&t}function ae(t,e,n,r){var o=r?t:i(t);return o[e]=n,o}function se(t,e,n,r){var i=t.length+1;if(r&&e+1===i)return t[e]=n,t;for(var o=new Array(i),u=0,a=0;a0&&ro?0:o-n,c=u-n;return c>fn&&(c=fn),function(){if(i===c)return Bn;var t=e?--c:i++;return r&&r[t]}}function i(t,r,i){var a,s=t&&t.array,c=i>o?0:o-i>>r,f=(u-i>>r)+1;return f>fn&&(f=fn),function(){for(;;){if(a){var t=a();if(t!==Bn)return t;a=null}if(c===f)return Bn;var o=e?--f:c++;a=n(s&&s[o],r-cn,i+(o<=t.size||n<0)return t.withMutations(function(t){n<0?me(t,n).set(0,r):me(t,0,n+1).set(n,r)});n+=t._origin;var i=t._tail,o=t._root,a=e(_n);return n>=Ie(t._capacity)?i=ye(i,t.__ownerID,0,n,r,a):o=ye(o,t.__ownerID,t._level,n,r,a),a.value?t.__ownerID?(t._root=o,t._tail=i,t.__hash=void 0,t.__altered=!0,t):_e(t._origin,t._capacity,t._level,o,i):t}function ye(t,e,r,i,o,u){var a=i>>>r&hn,s=t&&a0){var f=t&&t.array[a],h=ye(f,e,r-cn,i,o,u);return h===f?t:(c=Se(t,e),c.array[a]=h,c)}return s&&t.array[a]===o?t:(n(u),c=Se(t,e),void 0===o&&a===c.array.length-1?c.array.pop():c.array[a]=o,c)}function Se(t,e){return e&&t&&e===t.ownerID?t:new le(t?t.array.slice():[],e)}function ge(t,e){if(e>=Ie(t._capacity))return t._tail;if(e<1<0;)n=n.array[e>>>r&hn],r-=cn;return n}}function me(t,e,n){void 0!==e&&(e=0|e),void 0!==n&&(n=0|n);var i=t.__ownerID||new r,o=t._origin,u=t._capacity,a=o+e,s=void 0===n?u:n<0?u+n:o+n;if(a===o&&s===u)return t;if(a>=s)return t.clear();for(var c=t._level,f=t._root,h=0;a+h<0;)f=new le(f&&f.array.length?[void 0,f]:[],i),c+=cn,h+=1<=1<l?new le([],i):_;if(_&&p>l&&acn;y-=cn){var S=l>>>y&hn;v=v.array[S]=Se(v.array[S],i)}v.array[l>>>cn&hn]=_}if(s=p)a-=p,s-=p,c=cn,f=null,d=d&&d.removeBefore(i,0,a);else if(a>o||p>>c&hn;if(g!==p>>>c&hn)break;g&&(h+=(1<o&&(f=f.removeBefore(i,c,a-h)),f&&pi&&(i=a.size),v(u)||(a=a.map(function(t){return X(t)})),r.push(a)}return i>t.size&&(t=t.setSize(i)),ie(t,e,r)}function Ie(t){return t>>cn<=fn&&u.size>=2*o.size?(i=u.filter(function(t,e){return void 0!==t&&a!==e}),r=i.toKeyedSeq().map(function(t){return t[0]}).flip().toMap(),t.__ownerID&&(r.__ownerID=i.__ownerID=t.__ownerID)):(r=o.remove(e),i=a===u.size-1?u.pop():u.set(a,void 0))}else if(s){if(n===u.get(a)[1])return t;r=o,i=u.set(a,[e,n])}else r=o.set(e,u.size),i=u.set(u.size,[e,n]);return t.__ownerID?(t.size=r.size,t._map=r,t._list=i,t.__hash=void 0,t):we(r,i)}function De(t){return null===t||void 0===t?Re():Ce(t)?t:Re().unshiftAll(t)}function Ce(t){return!(!t||!t[Wn])}function ze(t,e,n,r){var i=Object.create(Xn);return i.size=t,i._head=e,i.__ownerID=n,i.__hash=r,i.__altered=!1,i}function Re(){return Qn||(Qn=ze(0))}function Me(t){return null===t||void 0===t?ke():Le(t)&&!m(t)?t:ke().withMutations(function(e){var n=d(t);at(n.size),n.forEach(function(t){return e.add(t)})})}function Le(t){return!(!t||!t[Zn])}function je(t,e){return t.__ownerID?(t.size=e.size,t._map=e,t):e===t._map?t:0===e.size?t.__empty():t.__make(e)}function Ne(t,e){var n=Object.create($n);return n.size=t?t.size:0,n._map=t,n.__ownerID=e,n}function ke(){return tr||(tr=Ne(Jt()))}function Ue(t){return null===t||void 0===t?xe():Pe(t)?t:xe().withMutations(function(e){var n=d(t);at(n.size),n.forEach(function(t){return e.add(t)})})}function Pe(t){return Le(t)&&m(t)}function He(t,e){var n=Object.create(er);return n.size=t?t.size:0,n._map=t,n.__ownerID=e,n}function xe(){return nr||(nr=He(Te()))}function Ve(t,e){var n,r=function(o){if(o instanceof r)return o;if(!(this instanceof r))return new r(o);if(!n){n=!0;var u=Object.keys(t);Ge(i,u),i.size=u.length,i._name=e,i._keys=u,i._defaultValues=t}this._map=Ut(o)},i=r.prototype=Object.create(rr);return i.constructor=r,r}function qe(t,e,n){var r=Object.create(Object.getPrototypeOf(t));return r._map=e,r.__ownerID=n,r}function Fe(t){return t._name||t.constructor.name||"Record"}function Ge(t,e){try{e.forEach(Ke.bind(void 0,t))}catch(t){}}function Ke(t,e){Object.defineProperty(t,e,{get:function(){return this.get(e)},set:function(t){ut(this.__ownerID,"Cannot set on an immutable record."),this.set(e,t)}})}function Ye(t,e){if(t===e)return!0;if(!v(e)||void 0!==t.size&&void 0!==e.size&&t.size!==e.size||void 0!==t.__hash&&void 0!==e.__hash&&t.__hash!==e.__hash||y(t)!==y(e)||S(t)!==S(e)||m(t)!==m(e))return!1;if(0===t.size&&0===e.size)return!0;var n=!g(t);if(m(t)){var r=t.entries();return e.every(function(t,e){var i=r.next().value;return i&&W(i[1],t)&&(n||W(i[0],e))})&&r.next().done}var i=!1;if(void 0===t.size)if(void 0===e.size)"function"==typeof t.cacheResult&&t.cacheResult();else{i=!0;var o=t;t=e,e=o}var u=!0,a=e.__iterate(function(e,r){if(n?!t.has(e):i?!W(e,t.get(r,ln)):!W(t.get(r,ln),e))return u=!1,!1});return u&&t.size===a}function Be(t,e,n){if(!(this instanceof Be))return new Be(t,e,n);if(ut(0!==n,"Cannot step a Range by 0"),t=t||0,void 0===e&&(e=1/0),n=void 0===n?1:Math.abs(n),ee?-1:0}function rn(t){if(t.size===1/0)return 0;var e=m(t),n=y(t),r=e?1:0,i=t.__iterate(n?e?function(t,e){r=31*r+un(et(t),et(e))|0}:function(t,e){r=r+un(et(t),et(e))|0}:e?function(t){r=31*r+et(t)|0}:function(t){r=r+et(t)|0});return on(i,r)}function on(t,e){return e=Dn(e,3432918353),e=Dn(e<<15|e>>>-15,461845907),e=Dn(e<<13|e>>>-13,5),e=(e+3864292196|0)^t,e=Dn(e^e>>>16,2246822507),e=Dn(e^e>>>13,3266489909),e=tt(e^e>>>16)}function un(t,e){return t^e+2654435769+(t<<6)+(t>>2)|0}var an=Array.prototype.slice,sn="delete",cn=5,fn=1<r?b():I(t,i,n[e?r-i++:i++])})},t(j,z),j.prototype.get=function(t,e){return void 0===e||this.has(t)?this._object[t]:e},j.prototype.has=function(t){return this._object.hasOwnProperty(t)},j.prototype.__iterate=function(t,e){for(var n=this,r=this._object,i=this._keys,o=i.length-1,u=0;u<=o;u++){var a=i[e?o-u:u];if(t(r[a],a,n)===!1)return u+1}return u},j.prototype.__iterator=function(t,e){var n=this._object,r=this._keys,i=r.length-1,o=0;return new E(function(){var u=r[e?i-o:o];return o++>i?b():I(t,u,n[u])})},j.prototype[Sn]=!0,t(N,R),N.prototype.__iterateUncached=function(t,e){var n=this;if(e)return this.cacheResult().__iterate(t,e);var r=this._iterable,i=T(r),o=0;if(w(i))for(var u;!(u=i.next()).done&&t(u.value,o++,n)!==!1;);return o},N.prototype.__iteratorUncached=function(t,e){if(e)return this.cacheResult().__iterator(t,e);var n=this._iterable,r=T(n);if(!w(r))return new E(b);var i=0;return new E(function(){var e=r.next();return e.done?e:I(t,i++,e.value)})},t(k,R),k.prototype.__iterateUncached=function(t,e){var n=this;if(e)return this.cacheResult().__iterate(t,e);for(var r=this._iterator,i=this._iteratorCache,o=0;o=r.length){var e=n.next();if(e.done)return e;r[i]=e.value}return I(t,i,r[i++])})};var Tn;t(K,l),t(Y,K),t(B,K),t(J,K),K.Keyed=Y,K.Indexed=B,K.Set=J;var An,Dn="function"==typeof Math.imul&&Math.imul(4294967295,2)===-2?Math.imul:function(t,e){t=0|t,e=0|e;var n=65535&t,r=65535&e;return n*r+((t>>>16)*r+n*(e>>>16)<<16>>>0)|0},Cn=Object.isExtensible,zn=function(){try{return Object.defineProperty({},"@",{}),!0}catch(t){return!1}}(),Rn="function"==typeof WeakMap;Rn&&(An=new WeakMap);var Mn=0,Ln="__immutablehash__";"function"==typeof Symbol&&(Ln=Symbol(Ln));var jn=16,Nn=255,kn=0,Un={};t(st,z),st.prototype.get=function(t,e){return this._iter.get(t,e)},st.prototype.has=function(t){return this._iter.has(t)},st.prototype.valueSeq=function(){return this._iter.valueSeq()},st.prototype.reverse=function(){var t=this,e=_t(this,!0);return this._useKeys||(e.valueSeq=function(){return t._iter.toSeq().reverse()}),e},st.prototype.map=function(t,e){var n=this,r=pt(this,t,e);return this._useKeys||(r.valueSeq=function(){return n._iter.toSeq().map(t,e)}),r},st.prototype.__iterate=function(t,e){var n,r=this;return this._iter.__iterate(this._useKeys?function(e,n){return t(e,n,r)}:(n=e?Rt(this):0,function(i){return t(i,e?--n:n++,r)}),e)},st.prototype.__iterator=function(t,e){if(this._useKeys)return this._iter.__iterator(t,e);var n=this._iter.__iterator(mn,e),r=e?Rt(this):0;return new E(function(){var i=n.next();return i.done?i:I(t,e?--r:r++,i.value,i)})},st.prototype[Sn]=!0,t(ct,R),ct.prototype.includes=function(t){return this._iter.includes(t)},ct.prototype.__iterate=function(t,e){var n=this,r=0;return this._iter.__iterate(function(e){return t(e,r++,n)},e)},ct.prototype.__iterator=function(t,e){var n=this._iter.__iterator(mn,e),r=0;return new E(function(){var e=n.next();return e.done?e:I(t,r++,e.value,e)})},t(ft,M),ft.prototype.has=function(t){return this._iter.includes(t)},ft.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate(function(e){return t(e,e,n)},e)},ft.prototype.__iterator=function(t,e){var n=this._iter.__iterator(mn,e);return new E(function(){var e=n.next();return e.done?e:I(t,e.value,e.value,e)})},t(ht,z),ht.prototype.entrySeq=function(){return this._iter.toSeq()},ht.prototype.__iterate=function(t,e){var n=this;return this._iter.__iterate(function(e){if(e){zt(e);var r=v(e);return t(r?e.get(1):e[1],r?e.get(0):e[0],n)}},e)},ht.prototype.__iterator=function(t,e){var n=this._iter.__iterator(mn,e);return new E(function(){for(;;){var e=n.next();if(e.done)return e;var r=e.value;if(r){zt(r);var i=v(r);return I(t,i?r.get(0):r[0],i?r.get(1):r[1],e)}}})},ct.prototype.cacheResult=st.prototype.cacheResult=ft.prototype.cacheResult=ht.prototype.cacheResult=jt,t(Ut,Y),Ut.prototype.toString=function(){return this.__toString("Map {","}")},Ut.prototype.get=function(t,e){return this._root?this._root.get(0,void 0,t,e):e},Ut.prototype.set=function(t,e){return Wt(this,t,e)},Ut.prototype.setIn=function(t,e){return this.updateIn(t,ln,function(){return e})},Ut.prototype.remove=function(t){return Wt(this,t,ln)},Ut.prototype.deleteIn=function(t){return this.updateIn(t,function(){return ln})},Ut.prototype.update=function(t,e,n){return 1===arguments.length?t(this):this.updateIn([t],e,n)},Ut.prototype.updateIn=function(t,e,n){n||(n=e,e=void 0);var r=oe(this,kt(t),e,n);return r===ln?void 0:r},Ut.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._root=null,this.__hash=void 0,this.__altered=!0,this):Jt()},Ut.prototype.merge=function(){return ne(this,void 0,arguments)},Ut.prototype.mergeWith=function(t){var e=an.call(arguments,1);return ne(this,t,e)},Ut.prototype.mergeIn=function(t){var e=an.call(arguments,1);return this.updateIn(t,Jt(),function(t){return"function"==typeof t.merge?t.merge.apply(t,e):e[e.length-1]})},Ut.prototype.mergeDeep=function(){return ne(this,re(void 0),arguments)},Ut.prototype.mergeDeepWith=function(t){var e=an.call(arguments,1);return ne(this,re(t),e)},Ut.prototype.mergeDeepIn=function(t){var e=an.call(arguments,1);return this.updateIn(t,Jt(),function(t){return"function"==typeof t.mergeDeep?t.mergeDeep.apply(t,e):e[e.length-1]})},Ut.prototype.sort=function(t){return be(wt(this,t))},Ut.prototype.sortBy=function(t,e){return be(wt(this,e,t))},Ut.prototype.withMutations=function(t){var e=this.asMutable();return t(e),e.wasAltered()?e.__ensureOwner(this.__ownerID):this},Ut.prototype.asMutable=function(){return this.__ownerID?this:this.__ensureOwner(new r)},Ut.prototype.asImmutable=function(){return this.__ensureOwner()},Ut.prototype.wasAltered=function(){return this.__altered},Ut.prototype.__iterator=function(t,e){return new Gt(this,t,e)},Ut.prototype.__iterate=function(t,e){var n=this,r=0;return this._root&&this._root.iterate(function(e){return r++,t(e[1],e[0],n)},e),r},Ut.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?Bt(this.size,this._root,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},Ut.isMap=Pt;var Pn="@@__IMMUTABLE_MAP__@@",Hn=Ut.prototype;Hn[Pn]=!0,Hn[sn]=Hn.remove,Hn.removeIn=Hn.deleteIn,Ht.prototype.get=function(t,e,n,r){for(var i=this.entries,o=0,u=i.length;o=Vn)return $t(t,f,o,u);var _=t&&t===this.ownerID,d=_?f:i(f);return p?c?h===l-1?d.pop():d[h]=d.pop():d[h]=[o,u]:d.push([o,u]),_?(this.entries=d,this):new Ht(t,d)}},xt.prototype.get=function(t,e,n,r){void 0===e&&(e=et(n));var i=1<<((0===t?e:e>>>t)&hn),o=this.bitmap;return 0===(o&i)?r:this.nodes[ue(o&i-1)].get(t+cn,e,n,r)},xt.prototype.update=function(t,e,n,r,i,o,u){void 0===n&&(n=et(r));var a=(0===e?n:n>>>e)&hn,s=1<=qn)return ee(t,l,c,a,_);if(f&&!_&&2===l.length&&Qt(l[1^h]))return l[1^h];if(f&&_&&1===l.length&&Qt(_))return _;var d=t&&t===this.ownerID,v=f?_?c:c^s:c|s,y=f?_?ae(l,h,_,d):ce(l,h,d):se(l,h,_,d);return d?(this.bitmap=v,this.nodes=y,this):new xt(t,v,y)},Vt.prototype.get=function(t,e,n,r){void 0===e&&(e=et(n));var i=(0===t?e:e>>>t)&hn,o=this.nodes[i];return o?o.get(t+cn,e,n,r):r},Vt.prototype.update=function(t,e,n,r,i,o,u){void 0===n&&(n=et(r));var a=(0===e?n:n>>>e)&hn,s=i===ln,c=this.nodes,f=c[a];if(s&&!f)return this;var h=Xt(f,t,e+cn,n,r,i,o,u);if(h===f)return this;var l=this.count;if(f){if(!h&&(l--,l=0&&t>>e&hn;if(r>=this.array.length)return new le([],t);var i,o=0===r;if(e>0){var u=this.array[r];if(i=u&&u.removeBefore(t,e-cn,n),i===u&&o)return this}if(o&&!i)return this;var a=Se(this,t);if(!o)for(var s=0;s>>e&hn;if(r>=this.array.length)return this;var i;if(e>0){var o=this.array[r];if(i=o&&o.removeAfter(t,e-cn,n),i===o&&r===this.array.length-1)return this}var u=Se(this,t);return u.array.splice(r+1),i&&(u.array[r]=i),u};var Yn,Bn={};t(be,Ut),be.of=function(){return this(arguments)},be.prototype.toString=function(){return this.__toString("OrderedMap {","}")},be.prototype.get=function(t,e){var n=this._map.get(t);return void 0!==n?this._list.get(n)[1]:e},be.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._map.clear(),this._list.clear(),this):Te()},be.prototype.set=function(t,e){return Ae(this,t,e)},be.prototype.remove=function(t){return Ae(this,t,ln)},be.prototype.wasAltered=function(){return this._map.wasAltered()||this._list.wasAltered()},be.prototype.__iterate=function(t,e){var n=this;return this._list.__iterate(function(e){return e&&t(e[1],e[0],n)},e)},be.prototype.__iterator=function(t,e){return this._list.fromEntrySeq().__iterator(t,e)},be.prototype.__ensureOwner=function(t){if(t===this.__ownerID)return this;var e=this._map.__ensureOwner(t),n=this._list.__ensureOwner(t);return t?we(e,n,t,this.__hash):(this.__ownerID=t,this._map=e,this._list=n,this)},be.isOrderedMap=Oe,be.prototype[Sn]=!0,be.prototype[sn]=be.prototype.remove;var Jn;t(De,B),De.of=function(){return this(arguments)},De.prototype.toString=function(){return this.__toString("Stack [","]")},De.prototype.get=function(t,e){var n=this._head;for(t=u(this,t);n&&t--;)n=n.next;return n?n.value:e},De.prototype.peek=function(){return this._head&&this._head.value},De.prototype.push=function(){var t=arguments;if(0===arguments.length)return this;for(var e=this.size+arguments.length,n=this._head,r=arguments.length-1;r>=0;r--)n={value:t[r],next:n};return this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):ze(e,n)},De.prototype.pushAll=function(t){if(t=_(t),0===t.size)return this;at(t.size);var e=this.size,n=this._head;return t.reverse().forEach(function(t){e++,n={value:t,next:n}}),this.__ownerID?(this.size=e,this._head=n,this.__hash=void 0,this.__altered=!0,this):ze(e,n)},De.prototype.pop=function(){return this.slice(1)},De.prototype.unshift=function(){return this.push.apply(this,arguments)},De.prototype.unshiftAll=function(t){return this.pushAll(t)},De.prototype.shift=function(){return this.pop.apply(this,arguments)},De.prototype.clear=function(){return 0===this.size?this:this.__ownerID?(this.size=0,this._head=void 0,this.__hash=void 0,this.__altered=!0,this):Re()},De.prototype.slice=function(t,e){if(s(t,e,this.size))return this;var n=c(t,this.size),r=f(e,this.size);if(r!==this.size)return B.prototype.slice.call(this,t,e);for(var i=this.size-n,o=this._head;n--;)o=o.next;return this.__ownerID?(this.size=i,this._head=o,this.__hash=void 0,this.__altered=!0,this):ze(i,o)},De.prototype.__ensureOwner=function(t){return t===this.__ownerID?this:t?ze(this.size,this._head,t,this.__hash):(this.__ownerID=t,this.__altered=!1,this)},De.prototype.__iterate=function(t,e){var n=this;if(e)return this.reverse().__iterate(t);for(var r=0,i=this._head;i&&t(i.value,r++,n)!==!1;)i=i.next;return r},De.prototype.__iterator=function(t,e){if(e)return this.reverse().__iterator(t);var n=0,r=this._head;return new E(function(){if(r){var e=r.value;return r=r.next,I(t,n++,e)}return b()})},De.isStack=Ce;var Wn="@@__IMMUTABLE_STACK__@@",Xn=De.prototype;Xn[Wn]=!0,Xn.withMutations=Hn.withMutations,Xn.asMutable=Hn.asMutable,Xn.asImmutable=Hn.asImmutable,Xn.wasAltered=Hn.wasAltered;var Qn;t(Me,J),Me.of=function(){return this(arguments)},Me.fromKeys=function(t){return this(p(t).keySeq())},Me.prototype.toString=function(){return this.__toString("Set {","}")},Me.prototype.has=function(t){return this._map.has(t)},Me.prototype.add=function(t){return je(this,this._map.set(t,!0))},Me.prototype.remove=function(t){return je(this,this._map.remove(t))},Me.prototype.clear=function(){return je(this,this._map.clear())},Me.prototype.union=function(){var t=an.call(arguments,0);return t=t.filter(function(t){return 0!==t.size}),0===t.length?this:0!==this.size||this.__ownerID||1!==t.length?this.withMutations(function(e){for(var n=0;n1?" by "+this._step:"")+" ]"},Be.prototype.get=function(t,e){return this.has(t)?this._start+u(this,t)*this._step:e},Be.prototype.includes=function(t){var e=(t-this._start)/this._step;return e>=0&&e=0&&nn?b():I(t,o++,u)})},Be.prototype.equals=function(t){return t instanceof Be?this._start===t._start&&this._end===t._end&&this._step===t._step:Ye(this,t)};var ir;t(Je,R),Je.prototype.toString=function(){return 0===this.size?"Repeat []":"Repeat [ "+this._value+" "+this.size+" times ]"},Je.prototype.get=function(t,e){return this.has(t)?this._value:e},Je.prototype.includes=function(t){return W(this._value,t)},Je.prototype.slice=function(t,e){var n=this.size;return s(t,e,n)?this:new Je(this._value,f(e,n)-c(t,n))},Je.prototype.reverse=function(){return this},Je.prototype.indexOf=function(t){return W(this._value,t)?0:-1},Je.prototype.lastIndexOf=function(t){return W(this._value,t)?this.size:-1},Je.prototype.__iterate=function(t,e){for(var n=this,r=0;rthis.size?e:this.find(function(e,n){return n===t},void 0,e)},has:function(t){return t=u(this,t),t>=0&&(void 0!==this.size?this.size===1/0||t-1&&t%1===0&&t<=Number.MAX_VALUE}var i=Function.prototype.bind;e.isString=function(t){return"string"==typeof t||"[object String]"===n(t)},e.isArray=Array.isArray||function(t){return"[object Array]"===n(t)},"function"!=typeof/./&&"object"!=typeof Int8Array?e.isFunction=function(t){return"function"==typeof t||!1}:e.isFunction=function(t){return"[object Function]"===toString.call(t)},e.isObject=function(t){var e=typeof t;return"function"===e||"object"===e&&!!t},e.extend=function(t){var e=arguments,n=arguments.length;if(!t||n<2)return t||{};for(var r=1;r0)){var e=this.reactorState.get("dirtyStores");if(0!==e.size){var n=c.default.Set().withMutations(function(n){n.union(t.observerState.get("any")),e.forEach(function(e){var r=t.observerState.getIn(["stores",e]);r&&n.union(r)})});n.forEach(function(e){var n=t.observerState.getIn(["observersMap",e]);if(n){var r=n.get("getter"),i=n.get("handler"),o=p.evaluate(t.prevReactorState,r),u=p.evaluate(t.reactorState,r);t.prevReactorState=o.reactorState,t.reactorState=u.reactorState;var a=o.result,s=u.result;c.default.is(a,s)||i.call(null,s)}});var r=p.resetDirtyStores(this.reactorState);this.prevReactorState=r,this.reactorState=r}}}},{key:"batchStart",value:function(){this.__batchDepth++}},{key:"batchEnd",value:function(){if(this.__batchDepth--,this.__batchDepth<=0){this.__isDispatching=!0;try{this.__notify()}catch(t){throw this.__isDispatching=!1,t}this.__isDispatching=!1}}}]),t}();e.default=(0,y.toFactory)(g),t.exports=e.default},function(t,e,n){function r(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function i(t,e){var n={};return(0,o.each)(e,function(e,r){n[r]=t.evaluate(e)}),n}Object.defineProperty(e,"__esModule",{value:!0});var o=n(4);e.default=function(t){return{getInitialState:function(){return i(t,this.getDataBindings())},componentDidMount:function(){var e=this;this.__unwatchFns=[],(0,o.each)(this.getDataBindings(),function(n,i){var o=t.observe(n,function(t){e.setState(r({},i,t))});e.__unwatchFns.push(o)})},componentWillUnmount:function(){for(var t=this;this.__unwatchFns.length;)t.__unwatchFns.shift()()}}},t.exports=e.default},function(t,e,n){function r(t){return t&&t.__esModule?t:{default:t}}function i(t,e){return new M({result:t,reactorState:e})}function o(t,e){return t.withMutations(function(t){(0,R.each)(e,function(e,n){t.getIn(["stores",n])&&console.warn("Store already defined for id = "+n);var r=e.getInitialState();if(void 0===r&&f(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store getInitialState() must return a value, did you forget a return statement");if(f(t,"throwOnNonImmutableStore")&&!(0,D.isImmutableValue)(r))throw new Error("Store getInitialState() must return an immutable value, did you forget to call toImmutable");t.update("stores",function(t){return t.set(n,e)}).update("state",function(t){return t.set(n,r)}).update("dirtyStores",function(t){return t.add(n)}).update("storeStates",function(t){return b(t,[n])})}),I(t)})}function u(t,e){return t.withMutations(function(t){(0,R.each)(e,function(e,n){t.update("stores",function(t){return t.set(n,e)})})})}function a(t,e,n){if(void 0===e&&f(t,"throwOnUndefinedActionType"))throw new Error("`dispatch` cannot be called with an `undefined` action type.");var r=t.get("state"),i=t.get("dirtyStores"),o=r.withMutations(function(r){A.default.dispatchStart(t,e,n),t.get("stores").forEach(function(o,u){var a=r.get(u),s=void 0;try{s=o.handle(a,e,n)}catch(e){throw A.default.dispatchError(t,e.message),e}if(void 0===s&&f(t,"throwOnUndefinedStoreReturnValue")){var c="Store handler must return a value, did you forget a return statement";throw A.default.dispatchError(t,c),new Error(c)}r.set(u,s),a!==s&&(i=i.add(u))}),A.default.dispatchEnd(t,r,i)}),u=t.set("state",o).set("dirtyStores",i).update("storeStates",function(t){return b(t,i)});return I(u)}function s(t,e){var n=[],r=(0,D.toImmutable)({}).withMutations(function(r){(0,R.each)(e,function(e,i){var o=t.getIn(["stores",i]);if(o){var u=o.deserialize(e);void 0!==u&&(r.set(i,u),n.push(i))}})}),i=w.default.Set(n);return t.update("state",function(t){return t.merge(r)}).update("dirtyStores",function(t){return t.union(i)}).update("storeStates",function(t){return b(t,n)})}function c(t,e,n){var r=e;(0,z.isKeyPath)(e)&&(e=(0,C.fromKeyPath)(e));var i=t.get("nextId"),o=(0,C.getStoreDeps)(e),u=w.default.Map({id:i,storeDeps:o,getterKey:r,getter:e,handler:n}),a=void 0;return a=0===o.size?t.update("any",function(t){return t.add(i)}):t.withMutations(function(t){o.forEach(function(e){var n=["stores",e];t.hasIn(n)||t.setIn(n,w.default.Set()),t.updateIn(["stores",e],function(t){return t.add(i)})})}),a=a.set("nextId",i+1).setIn(["observersMap",i],u),{observerState:a,entry:u}}function f(t,e){var n=t.getIn(["options",e]);if(void 0===n)throw new Error("Invalid option: "+e);return n}function h(t,e,n){var r=t.get("observersMap").filter(function(t){var r=t.get("getterKey"),i=!n||t.get("handler")===n;return!!i&&((0,z.isKeyPath)(e)&&(0,z.isKeyPath)(r)?(0,z.isEqual)(e,r):e===r)});return t.withMutations(function(t){r.forEach(function(e){return l(t,e)})})}function l(t,e){return t.withMutations(function(t){var n=e.get("id"),r=e.get("storeDeps");0===r.size?t.update("any",function(t){return t.remove(n)}):r.forEach(function(e){t.updateIn(["stores",e],function(t){return t?t.remove(n):t})}),t.removeIn(["observersMap",n])})}function p(t){var e=t.get("state");return t.withMutations(function(t){var n=t.get("stores"),r=n.keySeq().toJS();n.forEach(function(n,r){var i=e.get(r),o=n.handleReset(i);if(void 0===o&&f(t,"throwOnUndefinedStoreReturnValue"))throw new Error("Store handleReset() must return a value, did you forget a return statement");if(f(t,"throwOnNonImmutableStore")&&!(0,D.isImmutableValue)(o))throw new Error("Store reset state must be an immutable value, did you forget to call toImmutable");t.setIn(["state",r],o)}),t.update("storeStates",function(t){return b(t,r)}),v(t)})}function _(t,e){var n=t.get("state");if((0,z.isKeyPath)(e))return i(n.getIn(e),t);if(!(0,C.isGetter)(e))throw new Error("evaluate must be passed a keyPath or Getter");if(g(t,e))return i(E(t,e),t);var r=(0,C.getDeps)(e).map(function(e){return _(t,e).result}),o=(0,C.getComputeFn)(e).apply(null,r);return i(o,m(t,e,o))}function d(t){var e={};return t.get("stores").forEach(function(n,r){var i=t.getIn(["state",r]),o=n.serialize(i);void 0!==o&&(e[r]=o)}),e}function v(t){return t.set("dirtyStores",w.default.Set())}function y(t){return t}function S(t,e){var n=y(e);return t.getIn(["cache",n])}function g(t,e){var n=S(t,e);if(!n)return!1;var r=n.get("storeStates");return 0!==r.size&&r.every(function(e,n){return t.getIn(["storeStates",n])===e})}function m(t,e,n){var r=y(e),i=t.get("dispatchId"),o=(0,C.getStoreDeps)(e),u=(0,D.toImmutable)({}).withMutations(function(e){o.forEach(function(n){var r=t.getIn(["storeStates",n]);e.set(n,r)})});return t.setIn(["cache",r],w.default.Map({value:n,storeStates:u,dispatchId:i}))}function E(t,e){var n=y(e);return t.getIn(["cache",n,"value"])}function I(t){return t.update("dispatchId",function(t){return t+1})}function b(t,e){return t.withMutations(function(t){e.forEach(function(e){var n=t.has(e)?t.get(e)+1:1;t.set(e,n)})})}Object.defineProperty(e,"__esModule",{value:!0}),e.registerStores=o,e.replaceStores=u,e.dispatch=a,e.loadState=s,e.addObserver=c,e.getOption=f,e.removeObserver=h,e.removeObserverByEntry=l,e.reset=p,e.evaluate=_,e.serialize=d,e.resetDirtyStores=v;var O=n(3),w=r(O),T=n(9),A=r(T),D=n(5),C=n(10),z=n(11),R=n(4),M=w.default.Record({result:null,reactorState:null})},function(t,e,n){var r=n(8);e.dispatchStart=function(t,e,n){(0,r.getOption)(t,"logDispatches")&&console.group&&(console.groupCollapsed("Dispatch: %s",e),console.group("payload"),console.debug(n),console.groupEnd())},e.dispatchError=function(t,e){(0,r.getOption)(t,"logDispatches")&&console.group&&(console.debug("Dispatch error: "+e),console.groupEnd())},e.dispatchEnd=function(t,e,n){(0,r.getOption)(t,"logDispatches")&&console.group&&((0,r.getOption)(t,"logDirtyStores")&&console.log("Stores updated:",n.toList().toJS()),(0,r.getOption)(t,"logAppState")&&console.debug("Dispatch done, new state: ",e.toJS()),console.groupEnd())}},function(t,e,n){function r(t){return t&&t.__esModule?t:{default:t}}function i(t){return(0,l.isArray)(t)&&(0,l.isFunction)(t[t.length-1])}function o(t){return t[t.length-1]}function u(t){return t.slice(0,t.length-1)}function a(t,e){e||(e=h.default.Set());var n=h.default.Set().withMutations(function(e){if(!i(t))throw new Error("getFlattenedDeps must be passed a Getter");u(t).forEach(function(t){if((0,p.isKeyPath)(t))e.add((0,f.List)(t));else{if(!i(t))throw new Error("Invalid getter, each dependency must be a KeyPath or Getter");e.union(a(t))}})});return e.union(n)}function s(t){if(!(0,p.isKeyPath)(t))throw new Error("Cannot create Getter from KeyPath: "+t);return[t,_]}function c(t){if(t.hasOwnProperty("__storeDeps"))return t.__storeDeps;var e=a(t).map(function(t){return t.first()}).filter(function(t){return!!t});return Object.defineProperty(t,"__storeDeps",{enumerable:!1,configurable:!1,writable:!1,value:e}),e}Object.defineProperty(e,"__esModule",{value:!0});var f=n(3),h=r(f),l=n(4),p=n(11),_=function(t){return t};e.default={isGetter:i,getComputeFn:o,getFlattenedDeps:a,getStoreDeps:c,getDeps:u,fromKeyPath:s},t.exports=e.default},function(t,e,n){function r(t){return t&&t.__esModule?t:{default:t}}function i(t){return(0,s.isArray)(t)&&!(0,s.isFunction)(t[t.length-1])}function o(t,e){var n=a.default.List(t),r=a.default.List(e);return a.default.is(n,r)}Object.defineProperty(e,"__esModule",{value:!0}),e.isKeyPath=i,e.isEqual=o;var u=n(3),a=r(u),s=n(4)},function(t,e,n){Object.defineProperty(e,"__esModule",{value:!0});var r=n(3),i=(0,r.Map)({logDispatches:!1,logAppState:!1,logDirtyStores:!1,throwOnUndefinedActionType:!1,throwOnUndefinedStoreReturnValue:!1,throwOnNonImmutableStore:!1,throwOnDispatchInDispatch:!1});e.PROD_OPTIONS=i;var o=(0,r.Map)({logDispatches:!0,logAppState:!0,logDirtyStores:!0,throwOnUndefinedActionType:!0,throwOnUndefinedStoreReturnValue:!0,throwOnNonImmutableStore:!0,throwOnDispatchInDispatch:!0});e.DEBUG_OPTIONS=o;var u=(0,r.Record)({dispatchId:0,state:(0,r.Map)(),stores:(0,r.Map)(),cache:(0,r.Map)(),storeStates:(0,r.Map)(),dirtyStores:(0,r.Set)(),debug:!1,options:i});e.ReactorState=u;var a=(0,r.Record)({any:(0,r.Set)(),stores:(0,r.Map)({}),observersMap:(0,r.Map)({}),nextId:1});e.ObserverState=a}])})}),ze=t(Ce),Re=function(t){var e,n={};if(!(t instanceof Object)||Array.isArray(t))throw new Error("keyMirror(...): Argument must be an object.");for(e in t)t.hasOwnProperty(e)&&(n[e]=e);return n},Me=Re,Le=Me({VALIDATING_AUTH_TOKEN:null,VALID_AUTH_TOKEN:null,INVALID_AUTH_TOKEN:null,LOG_OUT:null}),je=ze.Store,Ne=ze.toImmutable,ke=new je({getInitialState:function(){return Ne({isValidating:!1,authToken:!1,host:null,isInvalid:!1,errorMessage:""})},initialize:function(){this.on(Le.VALIDATING_AUTH_TOKEN,n),this.on(Le.VALID_AUTH_TOKEN,r),this.on(Le.INVALID_AUTH_TOKEN,i)}}),Ue=ze.Store,Pe=ze.toImmutable,He=new Ue({getInitialState:function(){return Pe({authToken:null,host:""})},initialize:function(){this.on(Le.VALID_AUTH_TOKEN,o),this.on(Le.LOG_OUT,u)}}),xe=ze.Store,Ve=new xe({getInitialState:function(){return!0},initialize:function(){this.on(Le.VALID_AUTH_TOKEN,a)}}),qe=Me({STREAM_START:null,STREAM_STOP:null,STREAM_ERROR:null}),Fe="object"==typeof window&&"EventSource"in window,Ge=ze.Store,Ke=ze.toImmutable,Ye=new Ge({getInitialState:function(){return Ke({isSupported:Fe,isStreaming:!1,useStreaming:!0,hasError:!1})},initialize:function(){this.on(qe.STREAM_START,s),this.on(qe.STREAM_STOP,c),this.on(qe.STREAM_ERROR,f),this.on(qe.LOG_OUT,h)}}),Be=Me({API_FETCH_ALL_START:null,API_FETCH_ALL_SUCCESS:null,API_FETCH_ALL_FAIL:null,SYNC_SCHEDULED:null,SYNC_SCHEDULE_CANCELLED:null}),Je=ze.Store,We=new Je({getInitialState:function(){return!0},initialize:function(){this.on(Be.API_FETCH_ALL_START,function(){return!0}),this.on(Be.API_FETCH_ALL_SUCCESS,function(){return!1}),this.on(Be.API_FETCH_ALL_FAIL,function(){return!1}),this.on(Be.LOG_OUT,function(){return!1})}}),Xe=ze.Store,Qe=new Xe({getInitialState:function(){return!1},initialize:function(){this.on(Be.SYNC_SCHEDULED,function(){return!0}),this.on(Be.SYNC_SCHEDULE_CANCELLED,function(){return!1}),this.on(Be.LOG_OUT,function(){return!1})}}),Ze=Me({API_FETCH_SUCCESS:null,API_FETCH_START:null,API_FETCH_FAIL:null,API_SAVE_SUCCESS:null,API_SAVE_START:null,API_SAVE_FAIL:null,API_DELETE_SUCCESS:null,API_DELETE_START:null,API_DELETE_FAIL:null,LOG_OUT:null}),$e=ze.Store,tn=ze.toImmutable,en=new $e({getInitialState:function(){return tn({})},initialize:function(){var t=this;this.on(Ze.API_FETCH_SUCCESS,l),this.on(Ze.API_SAVE_SUCCESS,l),this.on(Ze.API_DELETE_SUCCESS,p),this.on(Ze.LOG_OUT,function(){return t.getInitialState()})}}),nn=Object.prototype.hasOwnProperty,rn=Object.prototype.propertyIsEnumerable,on=d()?Object.assign:function(t,e){for(var n,r,i=arguments,o=_(t),u=1;uJ})Y8 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

gx=ReGyI?@wMFZ;6fSh>qf(U3t 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 bJG_3EjNsG{0`D+-0|EJec=1I;%D@2t?~k=t literal 32161 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 o121`hYt#8XK2*D>oLjt5mQZjq2m

\ No newline at end of file +var r=t.propertyDataFromStyles(n._styles,this),i=!this.__notStyleScopeCacheable;i&&(r.key.customStyle=this.customStyle,e=n._styleCache.retrieve(this.is,r.key,this._styles));var a=Boolean(e);a?this._styleProperties=e._styleProperties:this._computeStyleProperties(r.properties),this._computeOwnStyleProperties(),a||(e=o.retrieve(this.is,this._ownStyleProperties,this._styles));var l=Boolean(e)&&!a,h=this._applyStyleProperties(e);a||(h=h&&s?h.cloneNode(!0):h,e={style:h,_scopeSelector:this._scopeSelector,_styleProperties:this._styleProperties},i&&(r.key.customStyle={},this.mixin(r.key.customStyle,this.customStyle),n._styleCache.store(this.is,e,r.key,this._styles)),l||o.store(this.is,Object.create(e),this._ownStyleProperties,this._styles))},_computeStyleProperties:function(e){var n=this._findStyleHost();n._styleProperties||n._computeStyleProperties();var r=Object.create(n._styleProperties),s=t.hostAndRootPropertiesForScope(this);this.mixin(r,s.hostProps),e=e||t.propertyDataFromStyles(n._styles,this).properties,this.mixin(r,e),this.mixin(r,s.rootProps),t.mixinCustomStyle(r,this.customStyle),t.reify(r),this._styleProperties=r},_computeOwnStyleProperties:function(){for(var e,t={},n=0;n0&&l.push(t);return[{removed:a,added:l}]}},Polymer.Collection.get=function(e){return Polymer._collections.get(e)||new Polymer.Collection(e)},Polymer.Collection.applySplices=function(e,t){var n=Polymer._collections.get(e);return n?n._applySplices(t):null},Polymer({is:"dom-repeat",extends:"template",_template:null,properties:{items:{type:Array},as:{type:String,value:"item"},indexAs:{type:String,value:"index"},sort:{type:Function,observer:"_sortChanged"},filter:{type:Function,observer:"_filterChanged"},observe:{type:String,observer:"_observeChanged"},delay:Number,renderedItemCount:{type:Number,notify:!0,readOnly:!0},initialCount:{type:Number,observer:"_initializeChunking"},targetFramerate:{type:Number,value:20},_targetFrameTime:{type:Number,computed:"_computeFrameTime(targetFramerate)"}},behaviors:[Polymer.Templatizer],observers:["_itemsChanged(items.*)"],created:function(){this._instances=[],this._pool=[],this._limit=1/0;var e=this;this._boundRenderChunk=function(){e._renderChunk()}},detached:function(){this.__isDetached=!0;for(var e=0;e=0;t--){var n=this._instances[t];n.isPlaceholder&&t=this._limit&&(n=this._downgradeInstance(t,n.__key__)),e[n.__key__]=t,n.isPlaceholder||n.__setProperty(this.indexAs,t,!0)}this._pool.length=0,this._setRenderedItemCount(this._instances.length),this.fire("dom-change"),this._tryRenderChunk()},_applyFullRefresh:function(){var e,t=this.collection;if(this._sortFn)e=t?t.getKeys():[];else{e=[];var n=this.items;if(n)for(var r=0;r=r;a--)this._detachAndRemoveInstance(a)},_numericSort:function(e,t){return e-t},_applySplicesUserSort:function(e){for(var t,n,r=this.collection,s={},i=0;i=0;i--){var h=a[i];void 0!==h&&this._detachAndRemoveInstance(h)}var c=this;if(l.length){this._filterFn&&(l=l.filter(function(e){return c._filterFn(r.getItem(e))})),l.sort(function(e,t){return c._sortFn(r.getItem(e),r.getItem(t))});var u=0;for(i=0;i>1,a=this._instances[o].__key__,l=this._sortFn(n.getItem(a),r);if(l<0)e=o+1;else{if(!(l>0)){i=o;break}s=o-1}}return i<0&&(i=s+1),this._insertPlaceholder(i,t),i},_applySplicesArrayOrder:function(e){for(var t,n=0;n=0?(e=this.as+"."+e.substring(n+1),i._notifyPath(e,t,!0)):i.__setProperty(this.as,t,!0))}},itemForElement:function(e){var t=this.modelForElement(e);return t&&t[this.as]},keyForElement:function(e){var t=this.modelForElement(e);return t&&t.__key__},indexForElement:function(e){var t=this.modelForElement(e);return t&&t[this.indexAs]}}),Polymer({is:"array-selector",_template:null,properties:{items:{type:Array,observer:"clearSelection"},multi:{type:Boolean,value:!1,observer:"clearSelection"},selected:{type:Object,notify:!0},selectedItem:{type:Object,notify:!0},toggle:{type:Boolean,value:!1}},clearSelection:function(){if(Array.isArray(this.selected))for(var e=0;e \ 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 3c052db8e0c1fec54a7e48a81272bcfe17c2fb65..3f49436d6c0f19e1b0d39dae0fa604cbb09f592d 100644 GIT binary patch delta 74616 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 a;T3QF_lxOg+=fuclm813dW+@X@&^E#F%4k= delta 74112 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 diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index bd6725d58d4..8394f10ca48 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit bd6725d58d4493651a02184778d9f5d0a611698c +Subproject commit 8394f10ca4854f7a494f9c51cb0590783d6ed5da diff --git a/homeassistant/components/frontend/www_static/mdi.html b/homeassistant/components/frontend/www_static/mdi.html index ff359e902b3..0f1aa609e46 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 09cadf26e6b345ed26285253c32ad7169877be33..2be36b99edb9d597b4ef8101533e4e5001e22582 100644 GIT binary patch delta 160165 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
FeF&T$t)*AqR3=63uNt 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 IQ|WdH0DHxs#{d8T delta 159582 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

u zaL7?&QqtH(DGuKuJc2$unLwz1ffYyS!+~5lcl69;{ABDjlv`k6=@SqY>9MpXi6r1{ z?TAGNXLV{s7ICsMbxn*bXJ<~P(PwrS7*V*@ECdE6nmCRme~7;eHZW#V(eXoEj3|Z5 zE*gx2kz@^V;&>RC`_T1hs|EJbV4bWr!g<56ofQR&+qQ|t_O2|UsA<{L%Mys%HfIs+ z%2;=bVsJm5X=FllO_`BGb6oSYpM`)Aj?%QyRnB06HIq}P*{zg>jtnQJ997=wq5pXE zmhAtFxE14^e~Ap4rrSUYtOJo-h-s|skfiLT%q-THywIR~0N2^h(H)~$RS+t|c6o;; z@3oKR%hnDPeu~vG3bn6})mN@d7wuV91{b>TBi>43!WX0|7OF#>92R=i7KNhcSf%oo zZ+N|=Ge&P!5w#(^c(5I_GLbiBV|&|X8aCur>0Y}sfA4+A4^JPLp!aOtpE2L{8kki; z7Qd0J7QwzOvAdPndDE#nl6pquiFYb6%B$k4wA;e&mtb4e{p&JjHidza8>Q?Oe65oH zzo^{ozqe4hiDJ=xt3EzIet7=t85xb5g}p@qm!UnAf@<-90P^rU;rDK zT`|c1e|@g@kS00ivk^1(XKaH6P_=u}N|aW)4I#;5It3txHW=Xc+}}_;m`iKULo;GY z06%ZP#=Oo+CnK1zu&vDQ&}|(~GV9dxj-1ZxI31-0{AY;(pD%9P>fGME@Wb=N=XYyk zCiU*C;i*jV-tlLLY1J-683=K5{bxaQg}Vf^ePegyrzA$TN>tQ<^)^)~JKRf> zB$Ie4eojmyj?s!e7BL-q@ZmCKk!XdsMF28fvESmT=7h(+Ru^x6czFBEmroCGH%NTY z*CMQn-kQQjb(hansAf~s32G*vZL`Ly*}eLzw+~CcBgzMfejzi8rV|)665)zmtI8#U zf0x*AQDV?Bf;(P+-F|!}zFRwyq^61x#yaug`pZ$WPL&}%kw_2%cPLl| zuZrQ}_&Muck5^^>WoQ|{Hn5E2ni>a5M2M1Hie*t!!(uxF8N`|G7T3J~5k5b@e|-JC zj;NJfG5TDax*Yh|=*kA%*G6^rxOD4rWk53;D`BmerW-yup8owmYb?b4!Hdj&=AA|${}2~BJ& zMh-oQUNOke#XWCOL|+M}YF6kT0p{|Kc0%mNO*K-M&8%V-MQubviDW4Cv`%}1QCV}I zmRU0oBJl5EHBLeBEN6!#e@*D^AXgjQ4m6-r9KI1EsHIG&t{WK0qSco(E(}554oWf5 z!Y=UiKc&9Nl1Y@idnuO;hC*dNI`?L!6d=G!m<3Hmcxw?_MVV2he>ByPs;cIx0Z$LEr$$nhRe;s?y9MUjg9PK$pOj0|<>=0*Kh)I+Qr)!SXte_JfrOmEVofM`z)R zfK$_w#g-W8M2R97W(ria#>5_Jfro!e7!*`4j>5o=XtNeYOMfJ^sL)=5)r$Vca+LF+ z9rz8#)+(%a!*N^;e`o`V2*wXDEqVTm^wYsj81UjI{#zXDRbmy^CCcQWkT`?|bD4Q8 zh)TIgRQV|BZbvg;GlD__HisbE+aqJ5*uV}wS(9CWZ+fIaJ-;-)V7WUU9W~`W5$8a^ zQ_)-w3&!q1a{$!N5+EX;o}U4y$mN?oZ;|RFAC-ft!lKa$f2Qrb0~|JQ1|drdP>IN&%vhu> zPpcsG)oNbCf*i$6dy!9ZV%zK=!X>edo^P+)&Dk#SnHv@wrZYl86N4}RgYy01hj$Mv zHrfH(3{nF%e{5<4_VtyT^X83{?yaI58FL^{Jhlo0Wujjq4a^>>oV z=^G|`uC4o9eSfRPFV(&tbGG(jj0ILh^kncw<43(>zBDqXWaLa_G9NC|VY2|Kc%hye z33jnSznH$-Bwto`KGU1G&mTWNzFjOo$F6VN7;Im(%L?4YFi&1zE8=6_@=imx@^04w z@Nf+Te`z!C&Unhk#q|klz+Av$aTq|;3r@jvAbJ*hh1xI#u4FmzAUj&*a1%%07gbn^ zcr=yGMl&^r2LfUCH}!GRil_w?mI zq7whTRTa!x9LKE~w~zpop0!{rc4(Xup;Ay~e~^o3g&ep(j^I!y0T?fo1*6y@KCYT9 z>i80_H^2fkz4!aR)~CmhYa-l#OM52+Hn(5@_Ah7qc^FbGGmBcp1=R+XI$n_Nr}n2{ z9@pdn?UgM7?pWtPeMLy`+m%F)VCId&x3#DxP!z}%vN+fYu3Qa0hQ$j(xl%!v;v20k ze_9qPkSun%cr~eIuF#^zP%AVG!qZ|rB5k*l(6u-M^#$F7%(*Gh&r~UhP{o%bWhE|q znz$KrA~z$|=M`*Hsp8lu?T6cfHabW~uDK}l2!k_y#Zt(PAo-*5olzh1V}{OMdRJ`4 zSZPkL#t>PEBQuVfjC=<2?t7ym$Hr1+e;b0NBqT7@j8r`G?jZ{QbL=sBT#gD5)8h-X z*sqMs+bhba^)LWzB#D?k%s%bwZQ;{OLh62M3=}~-WU`hScNLevP)r&Z7o4N zJp`f=n|)L!*bGE8fM(zsKfq?-Y{yaz{N;!QV+(NfeZsvt^KBZfK(N^as$@{CYe3^7F&z zWm9sursQT#$<3OQyEP?K4y&T9?~SQhW2)Ae@~tt&sWF9HWAeAg4;m2M3xS%XT}pwg{D@l2pRS|TE%vWWrR zYZq9f%*Y5n@$SZ&rnV6vn;h>uBu;B(zfGM86=Gk)- zYMQL?JiuCEjH!f`=_k&NBf}~uy;_K$Up~A~8+q8V^Du8}7}}K5f0h~GDhS8`dX9<# z(o6(gok037*k=G@3$H+BS-8SzLey4*YfH13FUduscNfCaLI9@$~o3%=x<%_xq8ZCn8`HR(3!!*MRFju2R85_e;f+T{8?%9Au$E6Agat2 zd!A$Pax!bG;+F+bcA=-kJ!M4I76l9*XiJDR8F>wA?2ws_Ua~DhUW@jx(Swz1PZ!c( z^>ks*yFtw*88`%5I7fLZ*qpf# zq$c&DnSDNDg%G0b;|E3SI+1v*)fRMbfzNp`S;P~=ECA7i1UIpss3%7^)uG5|0G2oI zseAb@a+MXzL{r3@rNqM-{2}lw^^v8-+w4f!^%2`tAhXFp9o?V_BT6os^_q#24`g6M zX?tZ-e>9oCXRbRm0vSy$-9;6crpB-%lU*}8@M2CTZ|5a{azJ{&Mkrl@=%0^+P2r_DlOyCoOwK%F$Ta&}$Fda(q@`xgsFJ_mHg|Q)4CDXQr}r6IKu{}TkeEANDJ*h zf0lgv6mq-6w=*tWYTlgZES_Og!3KZ}W_Ch-$pOn_4ID!xxWLh0(Cj2%-f8@b^@DML zRPMPNrMZ&#H`|>@OIyBdR*vSElFvu!(pfR_>6-N{rvCyWjXV4|F&tQ2KKbLQw%v?m z)~DP1dOj{nQgxK&gw~{pFuXQPs1NpSf9x_m7y!o0s+@<_z6Y`G&Phgk;QrY+CJ{@5 z*r3v5eSIYnPgB(c`*1J2IzrQSJRSgjy_mqeP zzsi-An^6Iq1Sk~{F;oRYB;z#SR(y!FqE;?`@Y`Cqt1>ZDvpXNv6-M6arZ%-fJJ^$^? za;q3O>U(_e>h_PLjD`_U%Jr_2D5d`gJWmwd@ zTX9_Esu(x{7p>p?f4wM+VB$V1IytghL|VgzuM*o<{q^J9pI79ZlwX7a^ju8cm7r1! zMvrIGphRjtdUx$I2W0E?*MCB}Q!2P_$5Zx?$1Rr*t6agvKp>CX7}}$tesY#mY#@G^ zvu=tw#;U6B92EB_`xO$>KN)E42%Lw+sEQl+bF&6QYhF8xe{AQBMI11XtIV7&-%&*P zfiAuzG{t$I(ePSEF|bGq+n>dLvDTy6bzSWwyPnYCH9W5!A)huo>cbXu`>Z{o@IG*XGzGTq}y6tg6zkV z6D|wC7R_R0pJJN`@tlN@J7;5Gf@w#)8W~qG69Qc{e?Z*Tb7CjA4aN~^=OyYDgBT-` zx6*Sb;3Io$Sgm?Nr7zOi}} z*_>&LkiaJIzVG9Tuyc_3E`wvxjL}KC#Prcx8u-&OqC1KlU?arut1B<6VO$6-rp|)$ zNq2zcf8)-~VRqdgTSkJ#5l}<4I1E#C2`WBqw*q$1dw*z#NXoy|lOg1u;YmwjncWN; zw11~%(9=GoyJ*;&`DV%1;-O(tfBXAc3OyU&DB(9>y8GS#s5j$TY5CQQ ziBFg1H{Z?+Pd|Kq`23Gm#_E!kGA%hN{esbQbyAu~a77&Hzk;TYqM^s-24W5cXTh>M z3v6tP*0@7h4LewLomL?AC|BV@mo&SLFM6d+Z?Nn8vJtXw#;{Jer*!R-g^4vqiT!Sl zfBp^Azc#?%qUC=r?;pN?d3w9qO27Uj3_e8>EL(18{~96nYu5rdO7G@iWMK*d5IHTE zll2d*rPu0U9Vu{#v6zP_0bf26f7BMUx0wR`Y-dhJR}>S+v=WuBajg^6HrKQPc4P*f-~&rl&isu00NOB#L4B|kFm!mFTQ~w02&=~2g%GqaFp#e*mDuh}bRZ zid|3)V^CqS@^F+CF|A`aTD6b>1sgpX*ex8C%?8p3LoB=arKXDgp0MVhSj=C0BLIbO~5 zBPot19@!AJ)x{)JEEx^`GG5@#{!{@l_55TxA37|gIPI74Na#luV~$Im#SP9RwT=uV zLDn7}w@U z>2EZ{!>ww+)#88iFvKyWSm^ZshHCZ(DW?de0#TiK^5o)`)QNKZ8#{gvcqql9450yI zFqqo_`oF2?7dQ$U=B7ZPYMKpfzp=A>1FJfUozi?v>PmL|K)9e#4MMiDq#|r=G&?KM z>vwjK$9GR3S7aeZe-x-7R4H%J<6N1!AQOF3nsEiK9}oRvi@Ch|X}RunDZ>`%t>oA6 zQ8At*DJMspkKFt@lEPVd`S_R%dfEw4Z5RgtD_szkEvXaBg0OK#tN2dggGb=-e7dl3we_ssuq9~#$XtKm1O2INI zAzracB|Ke*`3Fh4H1wuQ*0WwP2AuUXeRO*r@;UX;Gdtj>#dIt`H~ag9gaLzv1Y5 z_6$btj3+hG@z{zILG{F$Etf7eB?mrboQwzoV(LDKG;T!3A?HY<0--o7OF8f_8A|#g$7{wv3}D8cI-5k}#=jSHZxCMD zPX%2}8Iz!ggxi~D!ONnqsI4YL<-c2!% z^UH?2C7UlELn-h%TM-n$t_uPq2Lgnda;9Ua8^_us%18S4v5pUbl9g*-2@WmWc_y=^ zOv!VbERU!0=7niC>{Y~@=TDCx*IMS^Re)pXd=&?5FU9OaqT#mLSZ4K9zO#tiZ?vlX ze_#DLuaJ~X{#R6tTNT``D#ooU&V8$5cI|ywrxII%K!TwM(y_w~y~7Oi zsNi3F_a9%;w|-Rrl^tnguCB&Ix3mwsh4~RLSi!S~<-$sg$}7p-WrKo{%3ZxcPzJIM z1QRGA2a4eg${zn5g)ZlTM8l%D6f!a1e|8l=Tke#yF#4TDWZk34kp>iRTLC{kt@$(P zgT@^q*BY*wGnYWOR2t?L7s}O@CFyuveQzvLPIb|PgpN}jcez6E4Af(DhU2=k&_q6W z4PP6LTEoW}dXsh$nnbr!RY*KdXs57j6We?3*Ri#PuMBk*k@Pxgy}2TE~Mx)c;7#RT>|Z}1@rs3X}Hqx3N0Y7{Y$2ueeq?m zqC71qs6e;pJ~fhL34sT>#TWiz3yR?TEyIcm@8xRC+6#^i{$#rgVa z;|uyXE3WSCKX+AE!(A7wEj_fm^f2#YL)~^#{QBYVE8adtzpytMnRsG|e{T_b6X-6B z5R3&5fPTdw!aA4Fw?|wQ5~vHJ@vb$Rmpm1G&WWZU1cie@qzI>K=K~2Zrzc!=RKddx zBjr#?)ybQx&Vkql)!}#z7!_g`1W@7~p z`Pbxx63PhuH8QElSnNAB$*ss2a{Bb+0<%HoxV0*Pg_q@S7)<7lP)Lo@r45A$@t36E^(SG3J>0e+Ets%GdXf@nyo{X$Wr@AfL(|`LnpTF)f>^KVlBsf>7;~c1}Ci5H#DMC?DBpfhFr>ccJ zx}GGHMKzW`w36%&C69HMmdZTaPIhfs`=6S*hNht+-VRG7@{mXu8PM96A|~BFi%HXNf9@4 z?g57%)m$$|f#+~Jf#@C@Dv7D^FJG!2nm zpjR311Gqep*mqo zN<8xw7?yE?cB47tHj$pFR=04f*NgJz?bGMC@3%|hzaTWU?}WzH=>@Uav%G()|!A{A?f200)Ci_i$8pP1kRtMXrJ$1Q{^>u7bfLQS8EP z5{@#L!zqp?)$V%KFtPjVc~gX!K6EjT@~r}XegE?GX@Awk;%*`LGVkI}DR-;G9*T!k zTaX3C&Y1+u?yeoRNtTJ-i`)~Qe58w=DNC?kROOe!vsk<|l9>?o5QDZv!5^KRd$zcr>k+eFGuQb}S%;C_hZ4_RS52oH|J zngL`BMt^~1wBW|+oPTXVfmkH>L0V?W6O=l@1j8#4BkZEDjcihxZpM+W@bA#<+0-%A z9#c-!YCwUk;Y@@T+u@9rjV_AGwR-V-0q$n;H1cASc`1|s2DCj~#Ve=ZsO{~V^}nSz zO1zIuxYjcls$0L(OFuund-}RI^ms04$%BhU7=KC9*?^8>`H2?ytN)KmXFH>&cFekw z%zb_u(eu*jQCB{XVd*njoz9FcZsu8*8ghh~3Lg&;-?)(ULH2de39Ly65QOMmZWV#1 z##0;+m`JB55xnwwswjQggitX?L)B_!(NyPp716w}^RDXp+iI*Z29wV;ULw4T@h43= zK!4kpz`glpnOT-|`_(?L&DK#1xPquZkYzGOm2wi)jq7v5cFr0+7&vB~_=KuE-yUEh z-;7VCG;GJP#D(Nxvv}ATSn{HEnNe2AWui+=h0&Qzld)Q0qo?=S`(h{Tjt07}(SXA; z1ynNK%s9Oh8(d|*wu9(PaY^fYjQ$W^(wE730Sg!%XQ%zSaF5w|31Q4+-jru!cl3~! zY*dz)?0Eqte<@qwB;g--JAL=_FYliJwuC-mUtCY|TuAUUkk*6t?}5}G;Y={p_?Os) zO53esyq~ga9O};>p@I1P9N2(``Fy9R-V zuw$;Ge}+*IZ#WxOI_W8GyTl9C*|WMoo_1w;r<+_uf_d#UikGL43!xacULxL;kwMa~ zK=M*fQ<<@1)s*)feHrN*P669Wx5L%=;o;@+^V7roH(wVoQg|)m&lm|UMi~Ik$70IN z;?Imm-QbFZ_7cp=sk^~Wbk6tXQrzY}Zf04&OPy`Qr>9i!1o><}6O&ac0tRnr~ z6`)b#8KajF?A3&eqP3VD&z9kfaOTNw&{J^WTA9Id>^?I_BGoGn?^MUnZ{9wC`1J5) zIf0YN21MCFr%KVnrr>MjmWPu%K{CZaGGo!fRLJ-NoNs52CZiQO0 zf0x+2O>ZrK5$ZC7xCphw7b;^6m%h_(>YfqUq(& zd5I8Zt`TU&x#P>ktt@bk_GEl<=!vBD1WS2B3CFy!q-0QYqQWNdi{Qs%GC)bV*JkVB zoq77@^V8p#<1pP(e+wbFQ>Yf)k>esUb(^HK2o=OTP zOI!ddh^2OgY?8lV(HGeh;ys%!?KCc2g!mc^A><}qmODiDxXxtA^MFq|z%fbxUF;lo zAjYc|#Sj>99iI%MKv+a2z{rpt$uO0B@+$#F#xSv?Nb~AJPIVzNG7%Z^zEkQ&e_RSi zqB|<9Fv6UX`NSpyEqABwSiToUoaBm$eRqNoXSRZRWK$502#J4_s*a%KF;`i8%{GZa zk}Zv=)S%J4lbGw|rl1iUZcg3Cb2KC7ZCm*J+jq;kdO-6uWL?2XlLg_f)Hy*8Pp}n= zXQG0x(~E6Icb|UVNY)9G7W?(3a`gAb~6n0Gp;8Y4Ck(-bl zc7yb#d4!Jafx3=lb2>}A31s1fx8oYwaEVtegdwU>oAN)7+lvoNm^KDgOfj9nH`_io zAq$bVLJEaR3og*2oCq|I#aP~92t41Obh*Vac#FqnmOWhzB3FSmm^8RXe_`S@wt{{H zcV3z}7LIZx0dLFUx@2*5)4Sx^2vFjq00|hPysWU?T-33bV|f5NqfB|IKzy`EEEo8! zvHV7sGp8FY((uxY!1`P=oz)Gh8)tz^n{Pj}t=kXJt5|4y#S0yL63=9u=@n62lH%l+ zfIW#(sKMeqBcC*1S}DSwxi$~$a+Qw(^U#elcd5(L-oq?YFu z_0`Bw&iJ^F*g?0%!<=Gdwa$HY7i%wyWi zWsv7k#{bFdNcQ-+a5`v$WEGuZgM z{#r3MMnJ}8VIwurf6$kva=6raeCQ?jqza5h+{UVd3C2K?Bc*ZB=X*3j5bG7}sU+MGXr}zI@ zX`}BxV3UI_f1#qQ8X&Imn{V52n7TgLIgWPIKsXI;eT1P+nmJrGj6WoE8*0^mwzcMI zXp_cOFGZY}_#2sdtqc9LEji*I2J%W4ne?RNHge1`?f$T#luPbBBjn?GwQ2wL>D}Y= zW{^FLC51i&mNQbp0=1H#n>iV+KVGK;^d+d z&VlB-S^CV!UdZY}&UHU@(*JmzrIA-3`mCiWJs2mXyHQ9m+d9Q^o2SpyvoDNGs`O}a z|1eN*pr@&FK(C>+2Z+XPq-cavn`3ZgR(ST55Jgy}uqs1^NM7eVJ>`_-HcXVDmGvGN z>?PPkf4V0GLs5vsB7MF+_;N!s(Idbu6eHR#d5}7}7b!b=H&KFQfE{Fp(YjSm085;2 zkEQpLE+oEj6Th`h0u(rI=|~UQ!vcNKD$( z2SrHY!zgT$J41%0OE}0EYr{cJRF9fD7(5|m*Vq2HzehWGv ze_h`usCt%d#S3#UEE*-Rg#q@&=}GDX)&*8$@waL${wN9n+Qd`m$K`x?4?m9qKD{xt zus-hzAQNjZdjZ`IjkYAoJtX?+q*Bo+d$axiKr%z8lRkVE>sw!CC+bNxdNlVsEth$j z&0EH;BvM{ErDHGZtn{)ATI~d@9aBfpe+A8MVw<>7Fdg_VOL(%$0|ji^5*%geY}+z@{ubmczSHwgV3B>M(w`Ua_ggP=cyzCpNsgWSJC<~Oj&mf1JRfBhST z{tX6nCBQce&B_TYV$W4DeyUo+s#*{Pvn=+aS|A~?Iv>Dn5KlCok1zUGE$xKOv>BZs z&9&u}W(OF31P*y%L_Ie}B0X7%^@8jeH)1wvzLkw5x3Mo8Tdv z{jPj$J%Nvx_TLHulniraKmd54f0rGApFqzm(V$fIroGjRk~wfPu@Z6HFe5dqguu`)g8Wgb9dMQ-Zcxm0#OJl1*V zES`REd@!(@7EIJ(+ZH4ibSB04_ty7sYC(nUoz4)}+1lUbyBpJ3XY}|5e-pfl=9Dl> zZnetG&Ns)XK;I03!PKK~Cc;)3ltLerB$=`tQ`Kn5@wDAG@#XFFGW3Zt`kC{ylsJ!7 z^y;3>1)>vBYEC|<06^l(lNcu0@~PlO+Lx+eOh#W2P(TENm+!C?Ke%;27dxovWk&umeDk;7{~CGazd2y> zdr;fuZi-4QyCwYi`0}^s&ufZFTGM2)%c%0O3}!_r^D@BNA|TtG8Qw3lMUqmMRM{;lG0v_C zV%P_<_b6P?)Z=65LZ3-W!KMVJ1`xUTY^f{aMH7h4WX?|Ic3hV*2r!C4kRtS<)A365 zCeUId?kbQA@S4^cE2AxrL{g`8AYUKgA|zEJ5mu2je@#KmIk1|jUkH8IBDkjXWdOcZ zj3PtZCHI(!`%8jQ9+X5Rm|cLj3<)##GSVS7Hzjkh3vy@q!_Gl{r)HAqNTe;?4tgRe zNe<{u7;J12Oht+Lm@0NSOkWvfsxYP8=gyy0Y^gYuPBGXUw41)W(zBK!$Z|9y$#%ki z-kVedf6iFHcFGfriE#)9zoz_f4bI>LhQbi}8VIb@2S@^dRUOh=vJkXC<$W`*K7V|A zS=o-UF+PFy!4^@cF*l-qb23G{wUZ&(M!+NX(-Y&01*lZB=9>YuVj8uB8MVEP+HPm^ zR?c=~Ug#3N7TD&YD23niDa4cgzEG_dQRl$De-eI0Obc^Ilq9Dwrai%vVv`xK>-PzH zupk{td>?V4r{TIFmbjxV{J7ZM7UlEfX7#p>trCoReUy!OeXSXDcdiYJ+Sz-)-@Pa9 zJ>SNpoofT*Ep{HioBQ3&yWy2f@ob2noL~+NSXG+#%(!W0{4yZ7XDqwUNsBE+m?onb zf1oIRpt+p=)4Q#x85ge67Op{}X*+B_AD+jS_rudKkLze5>K+ziZ^um)bxtz{I>(^^ zN4>YG)4^=A<7S+}SmT{Uyp01O86(Ga!_f5_5zFN08*qSO1y;Y)s}8rfk~)TR3b5zF z&mPWFU>@f#IQBu|(oE=dAeL*;l7x$be-^~RtRxgX4!WXHz8Z=+_Gm=_dD4|;+8Cir zdna3%;jf|`0M(9iodMai1NU|qIxhF(Y@G>SKw`@w{mAbl7e`bYf zr<5$hsYZK*lHyIu#&tSy3DT?mBKSK-Y!GHfB@fQRkHJ1@$Z+UATJ$y$btZHa4L?7o zY^jW6Di|*;T@$-larw|>q0X8oECZGo57e#6N>KqjLLYaqhlqdB9e4#6jP?X3k5uW_ zQQEY0t&+UG8XZJI=vc9LOz*u|e}Fox!-)Z;*_7B^uaInhgMkdV%~?s^#29k0$DmTM zkX8L)_6WJxpSQ4sfh*{c?csKLvn6TIOwSnWrkH9lL6KrU%x!Al#-l%at@jZoZ(V4k}>2$e_pj7IXswU z@%EcodTa%wm_`PmE5^Mq@Y1$m1D89L`#s}f4r7(8U#2T z6=%>SDjftl?LE4XBcQYBe`8y(+BwcDtk)qT4aC>-TJ7ypT>*A5U@pPZO+vs#Pe;1Z z#?#}sEl2y^6@fDYVBNR`ik$5kdP|MLgPsCwL^OIn*p%mv1~~ENztL5|U2`)bvnUW* z!q!VM7fW!0lhgyOT64-9K;>q?D<2HdbTYq7WZ)O`iCkZWuJ_-pf3Dxr=)4fe`DoFM zzI)rl@5*B${Lh-&cYH8?8^EH^U|%4#`q&lqKwwLPY+j|Wccm}>EJ9Q2dj`nozXI{}<>~)D zu0nvXUVJbOHcv04f3(cB7sSnQ^;8ZfBRFm#$MyNKaNxkY4%-m8FFb6zM;^ri5{qc1 z4>=gbyqOLmuYe^4VN!Pr%roq@C(w$FHRD&lfO`pcVs~m6Q%w^FD0d~U;c7}KrDL(9 zkq;U|mDPob2#lCKCaO48oGUK&^xj83#P7JaEHBe1B@&;ykVSQ`tWe zNCQ{_+@<&n{}M2<@l+L9S%rvQewXrf9ys?4GnDoyEf5Yhmf_ zJyva%g(x$?v-EpdCB5)zqXjy_T8;)p{lSbrt6K1fFcIVq#a^2 z7JJ7xG?o~7;hZRH^C_oO_RWB>B!k!~`mkF|gGo{ldz{yv8nUUGDP@!P$eMZok956!W=Z^4@M9mx&U(<^D4}!4FO2s$jO@@ z-hX|3d47KRc@;L(^VFs+VRtK$1dsP~ITR1ZLJ-AI;T^B=6pQjw{gC8`<)B&Wi;py=-ptWA(Mm;(hu?wd@EJTW3qR&Zf$}q~6)0 zIvW>W*4xUhw`m{qjj`5ScZ;*`7H8e9f8DwpT+vf^OTD{!>uxeYw6~9~hENr7dTR)5B)!?(RtkNY&|8@oU&}RoBj;b9{{Hg# z`0+op#{pc!mBsgUzQ6tayjqw{dCl{zBByzt)7*1Pa-(%V=h>uJwZvI2rRy5ye=Yao zaJW^|zpS%;e0ce~@>p>~cLc~P+Ch40a($L;X^EIIi7*L;yfe&z1`a0YB#hHN$)1$y z8Eyt9AVPPbFOf*Lv6d#V?)Kv;*Ka;OKCirmam!gah)HO5jNyT+W8szSz|ZNY{41Fx z%`}xc?UcDr_taaNiEpq%$6KLke^DT<^bOL8#&?<@An6Kd<7YLLulBXt?Q1EE`dYV~ zZ!7VyPd{z<&10iEN7-_H!yx7u2!I|&!&|%4BB5Wgw;KNV`0)A7hsTwxnd~uRLkx;S zZRQ4XCQ(R$Jd6^!bo<1|cdJfH4qQ}x?Lx5}_H~%_$xG zAD+LwJiM$t!HuJ7V@z=kT^UoTo+vUCKzH0)tjf6Y z&fL41vgcXeC%p4W&JblKS@yUmU#N4Z-w%UC++()jzGPHO+fVejwOt6#n4_?6kum2Bt?ss#Fm8gq|I9}pGog>Rm~2CUUcMj z-BaTXXeqBv&3DEKKNu*r0uV!z|704lBy$&fuD}6blVKB*sqv!AVkV=aV=~nSVFt%! zImUaV{HHPqe;nQiLs%R;&pLtzN*0KNE?lIJd{?z)j4uFE|F zVzFJ7b^^7|)5pc})Uhf=P{diiB&(NTH!IX}N*?D&`PO{X$i-HI{IGZ0drZ|ZOYZdO zH!mx$fBgj`t{`oGB}f-nVtVWIBoP?Lw513ZNgF`V6R)Ch5PMO+6OF(!Tm%;`!bQYK zg3ZnRsgBNR4YBen%ITgu!%>At(`^D)c>OA3W!S5oJ-6eyd^;_b6de^xh<_SPS{lgW zp)YC{bz}jaj5BpzwuMWf38&Cw+jD=CJV@lmf63BxGeVzwW)uyj`E0Hx^~ozd3OjPd zwip2EvzJETo+|Nc%nBc$-#z}%n#$R+<4}m)vEMkZmDsf$sY?OB3mDVJ28?AaDb)~U zQ>4iN+bOnazKK}0V$?M!k8+RKdXAewwSW|v4{A|n$4<)MQC(@-#L!f;Cxa%lU{ zBo@pjah4?HST->hOHOsK1E$a0Z2FCYfAe+LULgzwVd7gEUvf}prZhMwWI89DE`t@h zaVp8EVtt&9wRE9vh|)DaiysdwY*-8rbs~v}MU0Pk6z$HU+B+~tgM<@jYiW+0gJcZ^ zu&kOyj;C4ES)v0~>>$l&y4a(w@ga19dy{uYS*$gRyw)^6ZXXS>f_ueK6g6($f1hnD zjmMIrK>U8}8v-V=$EXus#16^o<3cAp>2@kfdVAxZmo>6-vmPg^0fj7Ni&$?A!je3h zkh4k`vRx+m{AyRVi`-E~@#O=sKhBfBA0`j06ssi+poNSxlS1G^PzxL_*X*q%_evD5 ziK(9*h(-)-Wj#%`WZ^u?nQiJmezXX43^KLm-0Piuj_5bRf;gV`qloB} zCyRYqQMu=&2XIxyJQdQJ7kdfQO!6sM?Np)_16$14e6}O@%SIk!9PQDSf9csAF3{BZ zol%eUe1xqtTYiZh_kAR7L6eFa_hWqjNlvWxPnkVt{&hvYrdKkyk){1pbJgw)B$bF` zQ^+oucEzOx;Fqhf=5x0elVZe0xoi22$S<|bR{5op!L+4UZ0XwqRlfF(eY`4*dSH}h$#$Wwhlr4_;nQ~5ruC)2|zavB7af9_!PT>iFtJl zLk;92#({(D8hqv$rR0`uK2#itWMjN(mw@x-&UY@`z|BB&x&`Bqe?12~t#TK|W*^u2 z*~^f4@7;-|S)ehouRO_YljXNzLf2)nF%gGZ8EL3Ro3yeTLlL>0*u=!pb`HMC^%=GS ze0<#=`WBT@O3mmc@#4+b{`%qZ^V8d9aJ%DI?4eWSlLG`=p`0>|-H{cF-=6*Tw(Vei z6OoQ(v*l>l?+#P)f3inhog$SId(-%iqoVwpu-e*tF*XZf%zy+f_ibpUNAVw#o!S_< z{hf&qV*GifPTmZ^7hLxpuqU7~7f8!?T?)|~=5BsS7RC7y3Dn|YGO5u-*uGhwDO7Hk zI}dNYGU~oqVBdTL>nU|mmQ^qgi)B$hJYf;44R&Ctm=X}#e*h6(>|cS!O;R_7U2++2 zPRG_lZkjvY)&4gu;_vVviubZf60{b+T_=0%Gt*LLVq z22@3Hf_Suz3CpUa{Aum*cQ-o!YNOMTC~Eihe1Rwje}+NTE|I87qluB1bh?XaNRAno z{;anATJgeO%N<^kmL#Yt2=!u|1WahXa1_whnmbo28cq8+Ipe*bn)XrGNu}lE2xQFcEkE8407o^y1;d!vF#(mvKzVZYt8o0 z>Mq}|f2-Fz!*vsQKJ*W@8~2~8)UdM-CLObt2<2G>o69jp*G!{c<;{tiVXdG z=5~#M<(j4zoqF-p?;mmFzkHh4nokQ>Yj&EM$KS7HGVL^jHhufm$~kOC$KAoP{i~Wx z`tBi5szZGY)97q%yUafJf72M2er*gp3aZ=(ebTfsnpg%5S&ZaN~wIC6>K5`5i5LwLmRw7$iL43Z3*rth4l=w`R?+ zHgR`1wOrXR_Kh9i(+O4!Sd3%GW^IZanIdD#zq%`wKi7JsXxVn{%M<_!$~O!E<-6J} ze}j2!L`C;v$mM@v%PQY$S$j3}um1Y=&;I)R-Q^Ei_ilUCuzl{Z_uMw^!S;LLqT63x zxLGdUH!YX-cMQ1G_U2oo?;o<2+6>Wk`&hsGSc22if76g&zkf)-ec1ov7W=OqHQJv! zYRuJ>OaXAO=G?KFwXjKMbpB;uS!V8kf0l2S^4q=nwg{qP=w2slM{y)feP&u3d~+MH z---Y8I$C?Rqb*dh{`8OdukQl%@UId3zrW$Hx{m}@PIPgKN6Hrb0^66FLgoO9{8_fM zYBpQVDyZjBZ>~g{ghQe*&-n z1ltSTPgK2c#!^Hj-^{|o+p374brY@J&Z+k%!k0+|kuVBq((Mtud$ydp}k*Ta+4c!|rqF4e6 z(^yHw+zdBLe7sjEmW+383itf53QM^cF}5dwz7*m9dXV;@VA?{KU}^v*1|CKsOg)iw zg;>L&6&l%)Dp8}9vV^u3nF8qt01(5GxWS91x)IgZ0R0n$qEh6sgmgoh}=S;2{?0nVG9%~nG?nE6^Hj9yVonKq@7-W@KG^`t;boA#(43v zvt{vu&(Y_@%fkc4`04~eEQ{SCAd@>eNtkF z4|=1}E{b!YRz`7?8i=o*fBV|6636LCBd(CiN(pG(TZ}P$d}1bOlP}6WST;(Q2F-~f z^dis*qPtB9N~i=uC*n8DS5o{UBS~*Z$-JV3ke`&j4D&A9Ctd^NH zP@va!!f}#bjkMQFGrzior9cc<;c}ZCwM32{jbEJ6kI~;av3mtl?!>AU!1Z9YJ!=FP zM#m&ZqXZHt6p@@KwuH#w*MSu0iCFc6E^Lg>@%&39v65tf@jmfsGu_lb9P4 zHk2(xR+mT^O1ZW9gaUepC&js3aldNIG#2pVDPe^9C~x zK$4W#WLtKme^7QkrGN-xM$3dAtqk)^;@)uOyTOcb_M>th&2TlY9f7K%?2#_Xw#m0>dlt&p}?RlX9 znnwn!g1(77NJfQSoZXeXIBGj|7UXnb>_x;b7txox5ku!85ip+^Y|S?_j3wHTTgEq9 z7SSjXcZ8MwA>1o=1DY6oGw<^D*zu_2g$WHYkr9-|#Vo*wNm0rQTxKOMw<0U1*GUzp z9lf(se|vu`w)eMkdu58x^gW{_OC}|+YLKcWrCBb$yA|0Fw=(dKDOwM=Gp{#s8COSs_DL0tTdv-8b z_WZbX%a8)U!On~5C2MfrcL_6pYjAdNxmAH0oTnUhZ|7dD^_AQ576Hwdp3Lf;<_Pn>Yr)$*qbg$Z;8kO3fCjs-= z>|C^VrzMnK6K?P27NtMa_p0)IFRAz?t(&<-gTykw7DBI%Q?OX&C4&}#RQSAQZMbEv zSQL7bVGLpj)=@qk> zd%PKP&Q0xoO)AH@0gD)<2hJ+NTVQ>_RH!6ElPm&WG5J~IG!;<@8GI-T>`n>;e+(8d zD%uzBTo6)Z;n&p4i;NqGLKinLRnL^5l3rsqj_87;rGxbqMfOWVLNSaLq{R2+E8cv{ zkB{#@KmB#3v2OGyRfIVM~GuR%$_5qMBH%M@U$^e^<0y@n@59Ea0~Z+QWsvAbJ+u3KJ(*M$1A-xiR@* zYmrd0#&G$hC^=)LM>pV(B>S!@#bJ}_ah-waPK35kK@CH*1=*~&E|pWc=}=5;1k_SQ zm$nqks3e+|Z8S4t-Ex><2YgqizOb{trhJsN%lCQT3LIbKS7;&;V8p7eAF=Md%l9&b4CPKLUz9-5Q3$(ZIcc)f zF;m<+r9%t5txQUu|Bo7WO z%n3BUdIzb{o1X*PpbOE<-w^%jyllmL8GU-U%A=RcDfi(Nb;FH&hCQQi~?E-O$|OmUv2o5guMT7kxoa^Y;^ z=uy?`OOuKT7u}kXZAB?(Oh@ZkVsyzi+h@~0Jj(t@+X$`Pk(g_|W2eeKKdcqj@i|iI zQpr+PVCS-6zX9wZnCTJhj)&9O5+F$zWfg*AA{p292lMRFf1B(&w_q{M7EPcq;M&cZ z8l-DI%^R7lWS%BLjaD;LMlTXxM~3-VDr*!dk=~jkM`w~|y_cYjLjE~L4N>W%1~(qA z5^fk5FHAYaw<9HVoV`Y`-Bju4j}IRnKW=#2Ch-U&^Wt?O;R$vP3EY4%0-sDn6wH@Q z#F;van7S5fe=Qt4d2v$k{&+uThM`>ZLKig6w9v((!s?roQ$baZ7`+8$l~<4qsYkr$ z%#~rcE@YnwcjCT^Y&PVsC}amH)#%MG8p-pCb~jrRnK6%<_1=dD!MzVS5FhdZv^oxRt#8ML(frX$n^-u za!<8l>A8}Mhh%s8{`&wknY01WN+c{Q!4e!ZA3N?8X^coFCQI`{!e|-B-il~{jQ8Wv zM~@r}=$+XeaM$^kLku9*m@)E>Gs(*Tf9Bou-#%_D;ha=@*20-JBGyp!>a&)cggr=3 z)^pB0+xb~Noo{(N&T~8!Vk{K)-7Mj)$Wt|(r>Z&5stc93uI|_O2btR5_(rpDHs$cO zdinZkWq$eZ`SQQoj^%mPXY9Nw_>5`#jBU$4-YaHjwZbhhtk2ppVFaMkglr5(e|+4= zZwYwY5f(?7&JG+&Xkr29Tqe7+<6hw2k$(z95Bs@n?6%*KEto9q&dz&o8DP&M3l|>h z@u4yv3vov(xRD1DhGSSHhkBMC<+wqoV}o`OphG;@7MR0mR71(mBDGAU5@Bm{{b7>} zMsMa$|7;m@Bq8-SV2XN_R`!{Ff4G@_IJMiRFb3TVf3fK2>T%|n>6DJ9=Aydy_tM%P zhf|}Mg6~w>mz#dv@l&RQ#sr#D@jn|#zO(O+3oi0yxwBM3eIuD}Jtw*ruRO=SI})VF zZD*t0B&a~E8?EL-C;XIZ*OkvZ_nX1KE4ytVdA6e#ZZO8UO8LM*Yn7b-mrRfD?iSznPlX~h+>I?AD=#dS#f1{L;$yOv{9aQ zuyA8o?j&s{;hcJ&-9Y$5f2C-|HSfOe{loHY692R9D6Mg=T+|?UsjxH`%2__H*6o*w(k-xx5r-GR%byBs@ppV@!96%l`HF|?L4{eT;BGc z>t4AKHiVt}*kv)uJGD_?yAXAOEpEGO6WVlq&Fv?x%)3Dp?Ou%Ue_f15ic3{PAgK{x z(s$l)jMUmS(dJrj>78oCY8k4PlSZnRH>7+N^d-}K#f|qXjp@z9yT3kse7hPLJ)x!p z%)#DYy}#hVl@?=AwQ~DgX|(l5`O0ORCy`CPh6`EQ!$}-x{z8hzl;`TGoR7eDZ-#V? zL$2AMqy5S{mv!yze|CG|woo76J**{dXfKF&!S)70l=}sng3#^ofg97xq!_-!ntP0` z9y=7S2L=vQnley=LJl!?CgZ#I!eQQ8iL+L6`^{~5u!~8HH4yLPs~+h@=M8E~m_XR- zm)Rmd7Qjc3RU!YNDCCf4`KpBBNyj!G3zHQl&E;L@dI0)3e{KG|ZO#LIa<9Q77=N2{ z#ydIG&heSdt3984WWCik$@es2n^`a#6>O02y7mGUQhBnRq`_11 zryN|2TbYnBf7(cdZldO%srj5c!Mxa4IiGRbuA&&F+s%ttH2sARTl_b(Oo6ixCjDVK zyt>U%z2+~XNbS#X{rQF7KF{5B;d`}WEFvVO4T9^NT>C+HFTr%rmR0N_n-{dbJht5| z4*k+qG4=HQDsWbR=Sd-nU#|3;CQL5wqwXmfDf$5HEH4Y@U96>Rd)NanMl_47uC$o+`v`*&=f1gT(59rkhnz6g!3F>n zTQ)pZEYMMo?D$9>sM*R)6!icP5^^{v_?wzP8Yh4buEZ8HMNOiy(ysg^jYv|~GTXXu zR)n3(Q5@^?!^0B0PK>t2FOyf3RE=D^l~cN40$X|)$F3Sri=e2r<1$cQcpVf|U(3(9 z68!S><3HB2o_;uz5!(+EZj7{m`yEh0HUz&!N|cv@uK^VqQKXS##!sD^JGO!` zp6mQpQ6;=0sWO%D=a;Xq0U!YYm(Z^PCt?t=R3A4x^qCddC5c4W9Uy!+92K9(zqN{J z`^eYAT!8?9#zm4CBdhq1hh~RZ23(lBoCNPzaq&!N2q{&FXyyw^y#16fFS&`Uge!Ux zcwMv`N&BC<-^XxI1qz};YFDt!mszj@FBJ9h^UKrQWg8$shfQ7fc@VZr71WoUumKn$ zI_;|^<>Bzno~JAM65h(rfKNdxe@{~%1oQx8!I$g0Dwox;0V99kC=eXLm*f(es86E6 zbO-+A!e0dx_i(-A4d@4YZIY&Ha|erCIVMw39RM&TIts;zK-{QwET!JzfF2p4vIW z8ge&xl-Ikt=-2r1=1%lsbut1Sv~aV>#T8Hhp8tQ$>$6cV-DLq?Syez?M*nQi)1o}n zztmIW>Z-JQ%KCgT*`MW8)*r*Kp0Yka>^ASW@1H)c7W)uKrfp>|IH(oz{8%;A>~$I7 z!k7E|%|WcG5w(23DD9;FI?uapU!Ze4=#Eva4DYJzqjp!CDFUJ%W<4;Ta19fH2?gYbeynHPg40lEU-bX{_=lNqP%tx6NQbz>R%;tTv@SOmE!_V*o^kLB}8?xTl%1QUEMp=oF(%*y%9b z#8sDiI^pyHtS{}cPNGr4aJkMzhmNl2OD1ULRh}6GLg5*;maDdJ5biBd z6;ZKIK@vQQN5amHvsmd23EjY1rFdSB&9u*pVCtU8_H1`2ld+22Ins_AsbPNxr^Hy1 z65t91>dLr7xKNP^j%WlLsSrv-r;7BMs=|R|mnzh8sayt{6|+>Hw7*vDpYN5K`Dt$T zu+vfAJpcIP5{3}jL9xR|AJ*K?Fl+6|V3n#CAkXR_Sb^CVs$S5A|CoA}3B3SxT)E23 zP0M;m2eXR%{X;~6ovFy0OLu>6ri9?wQz0OX&;hUQ1Mnm$52f7xZ#4LKj|Q8T|M+nQ zP2%o%K+Fp296l~aLq(aDkc7E~7vSR%2^A<4t5C^&M_4@t#*)#aG@|yM#RWzh*^+iM z+;LkRe5CD~O;Sqik&PPiH#E^_M(uk=FwhB4!%`%Jg#bK2!@uJGD+SkS4s(ouWoYo* zc^bUICoa5Q`ep)4(pzKU`4yBMyQoUx&{es{Jf~AF18ZX!F@b$fN&%8@TNRId}MV4GI=oMKw z@|4UN3M4d5mmODJQo=h=FS>|@AW}PHtOlz+G{}fs2L*#DBP{Y zy%vu1s+m<-uf_9Jv&Ut1&pyJk!seATud~KJ42rUERoMIIx4*2=F5^yrWVuBpW7Bn^ zU5>r>FgrjBy}GbvKuLCK2Z0{o>d|~1Um8}Jh@zK5d3J-B(3Bw^(QqVfhyr!d@nnc* zq_2l5M;1dD%wouT@?KW?0emI*PR6x^B@f=FtSsGff-wmRaMhmQq+ek5#1`6L3b>s<%mH>X!32Ny26;+cVuFn&qzJe|$64I^7&VLhjU zq=gs-Q9B0?p?;h)#^Jir3p|i92T#W8gT}tG0u@Zx2vAdG=s2o>QoX*?kRQ*ti?{Xo z`RytetK2Q(TZX$b`-y%!`x#d!BZC>{_mk^l{ydn#=fC6TyrZ)Tu}9}5@8X%R6JXaE z>;w&1p5K(_=ZDW99^Nj4Y%_yIr77c#56<66ND&)FZb!g_Ls#E494Mx2)LzCmHe3rD zE9l5X_6MY%wy@lPAeSxo->09Sm&5859@~xzR}509W;ARfVzOf+L+t@riEu~e0s~VA znM9KqjgvC02*{EggQ5fKk-T%mn;}18Xr3J$2;mZOj1Am#`y}Y~%HlE9DCAz60f&_W zyW|fTu;xnG3^;VJ#<+Nl>P`XD{Fb+>*sZlUKfM3?`11UJ{POd1xnY2jTlwato>#`a zl4f;xtJ3qVs*m$eF#9sMOrEElAE(01%h$_1Rl}@emKJI(aG|q4p9|uw=7;_*Zj%Q( z!F6Fi9r|~@Fbe>PqN`)~5-GPnmj0rpRzDTksIi|YAj-trjB=la_lvFbp1Apt`f~`u zwL`%=#aP*Y;3A5Ykrrc$R6`|h%}|$&-}QFI9(-@T8#!}sVe#(x|M&&*Cxi0Infr3k%6b^r#>S`!k12QCgy_gH3l@x^@ww(>V6)=E zXqQF0*x|%Dc9O;#Z7VfMQM^XNv;{J05*&{rb3kH$Y&qddwnB663N;kk9pOa+Nb*P0 z?sOs}!T$eR3A{EE5>L!Uy~W>yrSY;py8&3)0g!{#dKgGzqc5P1!% z5(D`RSjkIvAT|bQO9v(nm+-m)I3WM_@cG^U4ROCV#5spx{>%{OYeaBPmDKBgz5e|0 z@u!zay8$HuvX^eV0g`_({ssFsE{k(eWL$JHSAT6rXyDRB-;(c$PI_gn@$~V{%ftI$ z)~*pm@y+1nsLX3yMxkTeU{Jg#@VqQg=htK;5hx_)svt1&|Ml@rAAjw;ynX(#^1jAJ zto=Xp&Of61%dGm#top-l_1CHT%O6zzA`Ty0JcI`BsnyQyQ8<4a#cUE20akafG=3N~ zmJtvzPyz%uwp|dhvJo56NoL^teYGH&dqrVJ9U^@!FGDIY@bwPRlFdxXo40;_Kzi3; zj%!;2HV}6)EWdEOuD)*kzaI9TJ?#DSPdkf`KY6nlf8%Dcp8tLOW^PBY-T)~RW(CL> zQ0DO>RROL7S1 z+P+w&@wUnpk>K215Rr#%ztS5g=etZ+4QZN3}hMYUYe}8tvz+moEPdzaJFcBCHpzt)}DNp zw)W&BZZp=b)u-NtHf+XwCBJ zeEI(N^UJ@dC%-#Afv*dnXrZDzWc$!uoC2-D@v(nz1Y~IcArr*(X`P!I>__$ABmK^L zSr9CWk>rbi=ea)rd&qfz$VqdvZOFL9k-OLsM2he36aDu1_BQth-?qL!BeMhECNDp+-`?dtV2q+929^ zUPON}7op7-?OaAbui=t%jh56!u*5BbCGHk1*$K!l;(f$&9id=WEWM$)e__-nj< z8H8_v`9-w;7Q;VX1N+k=*l*GQTQ0z2Wchy@BFonZ8RO5EVGu{&V$qB6^!05M6D!EJ zIiSrzWs=V!OYbO<=WmHcn3%%+F$^Ghp z+eP3%b#ukK5#bp2L^v!7G45Em?w?vET~arS9D6@d)?!n5diPwWTS$XSWW-Wpg3=4( zkQD}j6k_FJ9cyit~j^M$XOh_(TQ>OhzVC}`KM>9!BPVd>t`_X(QL84n-Iwg+-TF%LRa zaj==RA$rBb34tC3hwC-Z(iG?A8KuNgNG}Eb0QkVFQbl~5m6ZqFQu8$-D-D^tP#CPf*E2kFd^zC zsk20*&jYcO#x0d#~EhBlc+&Pm$( z&4n<8Q#(%Nb&(bvDj{X!5pI8N^FTZ_Ccw4@`1oWX>5)Vyj+Y9H(;*{Kw1z8{G@6zc zguiVQsQk$kwgwaHOG+xYO+U2_6a16&S!#wq+@=tMYy0ICX@cSgbEkFU7){gx40OrE z;Dg|ZdM9P`Y^Q%DLpz3GPtZ}L!E6WBPmWk3W%FPeusTs9IJYg#U2uK8_9kWKqs(Do zY~su(CYp54fxaQ2Wi_aJ%V*QtuA>N@00%Ca?z+1U z?D%Q0En|N_-77juIu7+ysV8ucs1ub!_#ku&pAmnSwYrCZ)>T*A3s(W38*9x9MZwMc z-gLTeQ@LzF;3=Dq7Do5XV7@{;bGw?F^jHDhn^zR&$7+Eskf~(G8dUXg&v{cAwFC1j zp2ID>X>P*TilKvSElzC^5v{q$pTP5>=8t$p{lG&o(0T6I?HM&PK(azV0cAu|AlvWY z?|)y*+PUSz4bx>0X#*0`*!@xK8#9{aY3M4Jf1{Z5yd0RI`cXhNL&pS(|K*o2#Q_$7 zBW*X}J;R|4h;DaBy`fxmgjkpNgpHmbI{Y#viUOpBYz!aoIjg|lz2E*Wq2o_*4+qM{ z^NoS=;kEw9eB*+Vo31PPYqx4U)brsg`>M&?@-yT1G`1mr9Ol!sKNG(jV^TQ=K z3sZ22pnzF9-5#yqKg~O!(KVQN44EFvyN;%udsPU$O6BU5d9p-5pCHm~JuQmadj!u< z^8ots^J7oiq0{bhy!yCfN1FfUyf{I?Gnt7!X7Xn_$D!hM6gtrf@wCw2L#NDtSCUWH zbKkAaCfmQfoRkxrt$kQlY*XuZudwb!z&1UO_Comi&u;}UX?{JpdbIEs#PLbtfOgr5 z#(9)>@xODW-@6s9aR#ns0)wOw*`UNs`6qAj_3_u2l_MZg0(gJNkqYdWt0X_Qe5Fy7 z)5)YBN2W)mOdL;F{l`M+l#{c6N;t{SA~0WByzLyD{-YMVOv$vJDpn`!asrap)1nUN z_LA*U?cL_Lw}-cqfEB={0PTxyv`U2T9n8#`9| zfC>X~^<>U)n#g=wmC3?rV(g5}(d^NR#oY3u`F!D;3r&9_$C9c6C=$zn>z!;H^!H6( zRLK)%*nqY`_v99E#mip5^ZnaqIh0#b*^0_mRBlCOoAE639i{$%le3=Py6sGH{!sP) zg|nV(tv>wv{rTpRn*#4wRn&H1DCPl?-=``K+l2akzFhAy$>VI)sT(NW?lO2iF9 zuTO(U@WUt>@X-VzFkygFfI^@M3(C5+9z@5Wx&m4IaQw(LA*FE>+bb8k2+JtbOBY9n|AKoDN7@Df zWy0ww-ISpkQEg_O=S5NwsypAqI%n7}vVi1%TWQ(xZPN^^ioFx_@b?T69^IHAwgZ$<3$A{;&^V^>(I3@*kWK$wu z=ny5w3Q+(=AQ3`BH_N~-xReK32YPy~5(nh%xWE<|w`i@31(FIBhUli}iR2!4p?j!i zLsz3|t2(N7$r4>KB&2M-hW~^4-mxx@mgLz^F+rDqW00G3Y^C!4I^)4%Wjr{pj0MBW zcyN7uyBK(Ret3Q9Ep6ou)h8v^W>f{keCo0iD=a`Fs39st0Xe;$OF#GPYPjJZng3xt z1YHl4EF-^X2;Fj~@3<%LmA5ys$qjgr1#@cihI2Kgis4xQM^`5;vN%A(kWc zu;B2($@<(&#$LUEorrISK%+@0;s2=p&VRB|Ox3j31uODAtzw}ZSNH=2j7?^&e*frq%h3UM1 zc~M07V*Gs~A88sTe~l$GPloRGyp^<@_9#>PgXhd$1rt;w+maLodDiXE`? z#pMPwtN_1b(^Gt8up@}R;#tB_sbB(ses17^DtIH%cNo3TCl|10f1V3$*Le;2Xc2yA~f<;6QZ~m9lLZZ<|1OKSiAT zz1#^9acv2aE5gWafzzC>Xmo`nb;JKU{AuP?@}@Gw9|FE$_(O45XH+|X#zCrou$CS0 z^$P-kfBh$FEP;o)sj(}CVmCLX;?>>I_S)aSeCoA}=;n5k7j8a|6dTt}3x?A9#7SI* z*M>g{^?IaU4(C|7O9Zwgqr>R~^>uVeiY-~k5Fa4`mc`N8;bf-8@kmoPwqe!+GwkL* zJ<=#7P#si9F~K}#JBD-4RQdyd!dayuPNTpcE`@`;&tW~<`n#=2)mHIp>uITBmri!L z1Jk=Y(~TLA#+$~fng%j^ZJd;dXuwx1} zIvt~Ey#0zlY6Y^qb}?S7RfP3GrO)OeKJY<0e$_m^vTAm4`$&IV%?=2EKZ-y(pr#BM z5-L!zs0EsU*;~%h=b2v9LT+-~x4&)8&vV&TWP#iZ^Q}lVhrsJhG`M2?D#)tgk0|+~ zyOgg(v3N!~-0E&$wmvDpch87`|GZk+<_p6ou*sRXfbA_4{G%{)PfUd{BOj@8;qaWX z#1kY4gcytg0ECfFewY7$wT7*M9nNJ5n%-@AuT~$=LZKr-`R<616AIV>NPY{!BIe zpiF4|y_#*ZcK`F$JCki<{1YdC@#f>2M4WX(i?7_v$ zU7@Vp6>9I!z^`X@d^9!yLwX2d)_Dw$OB%6*#g_q_gc@x^W*E=3J4Ln;t9;Y$EAshSRms{e#7

(kqR-#&bP`uX$LCu1C&vwP*9xd+k52ZC~dF`)%j=ADvuV8)AqgT^13=huYj!6pHa zmb-kSbZa>l1`jyq%0*)SoAa!s$V%kmW+cG~hnPKOwkuIco4yG9wmeoUlGzq{K%s+5feAc=xM@76KSv=eqT!wU4 zys3D*C(QpyD;@W7Ft0B^J%0N(k9ZHyA3i_+b?uSyZ)5V`HztpF{J4@4&GI6kTbGDO z*x=M7yvd2kM(6;@kPcRwV?TlkZauQ_Ga<{omMwssN!AkY!XOem_$n+bV|)U$3fL@v z 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 diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html index bdf7f206ba3..6babc87e2ef 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.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-event.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-dev-event.html.gz index 83efff860019c98f65e17c0be02770263ce1112b..09b6fecfc091602978733fc9912108ede41af8f7 100644 GIT binary patch delta 1125 zcmV-r1e*KL6x9?5ABzYGOFZ7O2Lc9v#?iEC?5Bo4XxRU0*ghHR|M2Pi;hEalL`tT> z`cIBkJ?Nn!J???Fx@tI_km(&WJz1F?)^q#$vpXc0$Kc#_=CUQYYN@4CT{hm(sv;|j z@#7vVBIn0M)6pr7dqsF;H~nVw0xtIoU3`>>fP-X253VEYUTW8h=$;Z{tx_ahpf+Vxe!^G;4DT@U^rurlb&7i-jpmM##=LjAFH+yqO-mZ{+y(PY5XU)@ zcX25h$lAwa=N&_n+*++@`@eM$1V zXX0k=4my$8>03{AJ|#8AhWsztZ(wa3*-60wpGisfk#1;j>jhkc@=8Gp-I{Rzn?vu)S?tt6g_617%?Fmxz5y zm!#%uc;7%wTy-ZJnS*+NuWh7>?u|Tgk2oA;fPG0D=E0i8aCk5#<#Zc&0d|&7 z3Rn@KTl0d~IYoX;qubSz(|hyPJr_f+=w@c4+#LrGkHaPz(kIL1a-ts|Ybb}-$dnZz zhVcFc{47qtd7eSEiGA2TJY1UofUn8=r-iHV@c#aNdEY{e0a9*%Y`(|Ryh;CnD3}`D zH&90uL<*-)G6P9$$n9IY2N4>>IS39VVg-4<0@vVg7Y9;r$?j?11CLSK-Ckj)bdQ6Btw#s# zoF`ZuVC;~ryX6ai4Ww0I^a*ia=e<_Pgq{xOuOWmz0$HIqgenC^o7zQIR|)k00iQjJ z;b7s}F&&lmbl^J^r2cWJ8{zbKKfLx?0;B41&gg{qqwzrgOQMJu{Nvp(ZzE5XI-Cg) zkL7A2SMFg_Gi!TpkQ0iO;119ZrGu`7Rp!LeH3;pbbmwJ%kEMGAA9@H+#-Bgi62^5~ z^af|?lvBO?uy!0b2OXimj*%b^wa47D(g1vLwXX5iFyn&s)a rHh1lJzo6+~Vp4bIbp=pJ_DRq5+#fb2^9%0?e-ZvKjGKVe7#;usM|3C> delta 1119 zcmV-l1fcuX6wed~ABzYGEhy8m2Lc9v<`EPAPh<7T`2L5_*$+?8#wJoS1t;>!v8o3> zG>*qT(AH56hqEwUVy0&(ldXC}KYvPxH1Zgnkj_!I1XnG!RH_Ta8(LLlMKMy`14QKf zm}ojWrExC7-`JQmizYfE#0)FF%MfXxdd^X6L}Yxl7Xy! zj0opBq~>RG4aN#eKtBeU3KJZ)#u`*v8+1Ql^C|Jxl0sTOF$D!?vpY-h4lg1QMDwOW zK!X3bus4}|)2bxJJODlPU$g4yy1O4Hxu$n$y#8VscJPtb!%bFg)0ZTFuX`qL=I)^9 zh@HOQROeGtV{FL(lKlqOwvn9)4DgwhWFP6)^|oHXg;x%O(uB|rHGcU!RZy6!1U})S z^M;hsDXnK`%6If*0An5uCC~T4Su*455T-KJAqd;swzb-I2QyFxMtq6bmvl*Lu7)=Y z#Kcv1qLDeM_u599=-$YG6ZeS2K?c}2vtb^rNeqVv15r-5aTicW34rkknWumi0lGCW zc%4(^w=}w4Ejhh6U){qn@9+$4k|BMvTrMa2iLr)qXpKx+0b&U6-@4D@ z^y}ssM4Q+r+`|*4=@0mBtbatf3J)*s-LfFe#D?6yntKqTF`R?oP$E{4*DG)h{z`Em^_J|O<~>je!7~Ew4^)C$SI1Btl_+5H zfgZgxqmTa5#h_ZlgrJEuSTEkdw|VCqa+@d94K!Axv)=6$W=i)AIM{l0(9U^m#R0~S z!@66(&_G%RMxPLW_jO)obxi2#VE!6H*dve?dPAsEP_(ICWObE5{~z$#qZkero*l?h zX-@~fGePQ~fw~b+fA?c*pCvG=4(E(ccs~RWs3{qi>QM5)7>@bFl!CUWH- zCN;CR=LR{UND1x$?NB=CN?2u199@IZPD*!P_E@^-?xBZ&@MQe?vn^p>$IS6){Dg=C-fOwavcQ!>Bhj_|kN{{m$Lk9Yze004GgEzbY| 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 21db62aab76..bd1998a7949 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 85ebe5f75f99ac94e2078f727cfd2c99c02f65e3..0683f460f7d099e070aaa73944c9ba27c7658622 100644 GIT binary patch delta 1304 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 GAOHYUOnILG 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 35caacb76b9..90a36bccc6a 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 edfb67103e0f9cc3f2cc7f7528f44eed4ebdef48..e40d047db4865f75a114c0381d7c665c20f7e0f8 100644 GIT binary patch literal 2776 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 literal 2772 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 ap82%1u6$UA+6;uhb^jO4qOyeY9smFcBUMoV 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 bf2ff97c528..b9aba112b63 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,2 +1,2 @@ -