From ff3a4637a4fa8f2da4ae73e38849e8900e05700d Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Thu, 10 Aug 2017 23:28:04 +0200 Subject: [PATCH 01/93] Version bump to 0.52.0.dev0 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 85846e32f59..978dafa457b 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,7 +1,7 @@ # coding: utf-8 """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 -MINOR_VERSION = 51 +MINOR_VERSION = 52 PATCH_VERSION = '0.dev0' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) From d427063acd29a6dc71f0a86596e23c430b3b32fc Mon Sep 17 00:00:00 2001 From: William Scanlon Date: Fri, 11 Aug 2017 02:35:45 -0400 Subject: [PATCH 02/93] Update python-wink version to fix Dome water valve bug. (#8923) --- homeassistant/components/switch/wink.py | 3 ++- homeassistant/components/wink.py | 2 +- requirements_all.txt | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/switch/wink.py index ec9311ac9e9..aa33c2f7132 100644 --- a/homeassistant/components/switch/wink.py +++ b/homeassistant/components/switch/wink.py @@ -65,7 +65,8 @@ class WinkToggleDevice(WinkDevice, ToggleEntity): attributes = super(WinkToggleDevice, self).device_state_attributes try: event = self.wink.last_event() - attributes["last_event"] = event + if event is not None: + attributes["last_event"] = event except AttributeError: pass return attributes diff --git a/homeassistant/components/wink.py b/homeassistant/components/wink.py index 316c939492b..8d40f5dad48 100644 --- a/homeassistant/components/wink.py +++ b/homeassistant/components/wink.py @@ -25,7 +25,7 @@ from homeassistant.const import ( from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-wink==1.4.2', 'pubnubsub-handler==1.0.2'] +REQUIREMENTS = ['python-wink==1.5.1', 'pubnubsub-handler==1.0.2'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 91be5eb7be3..b071f7ad98c 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -775,7 +775,7 @@ python-velbus==2.0.11 python-vlc==1.1.2 # homeassistant.components.wink -python-wink==1.4.2 +python-wink==1.5.1 # homeassistant.components.zwave python_openzwave==0.4.0.31 From 0999e2ddc476f4bddf710005168b082f03a7cdc0 Mon Sep 17 00:00:00 2001 From: Philipp Schmitt Date: Fri, 11 Aug 2017 11:22:22 +0200 Subject: [PATCH 03/93] Update roombapy to 1.3.1 to avoid installing all the mapping dependencies (#8925) --- homeassistant/components/vacuum/roomba.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/vacuum/roomba.py b/homeassistant/components/vacuum/roomba.py index b6dcda4bcaa..cf9ee064283 100644 --- a/homeassistant/components/vacuum/roomba.py +++ b/homeassistant/components/vacuum/roomba.py @@ -17,7 +17,7 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['roombapy==1.3.0'] +REQUIREMENTS = ['roombapy==1.3.1'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index b071f7ad98c..07bb6846c61 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -838,7 +838,7 @@ rflink==0.0.34 ring_doorbell==0.1.4 # homeassistant.components.vacuum.roomba -roombapy==1.3.0 +roombapy==1.3.1 # homeassistant.components.switch.rpi_rf # rpi-rf==0.9.6 From 49733b7fdfaa568c3536012f2761069f0addbcb5 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 12 Aug 2017 04:55:57 +0200 Subject: [PATCH 04/93] Remove not needed call to update (#8930) * This will ensure no I/O in entity properties. --- homeassistant/components/switch/rachio.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/switch/rachio.py b/homeassistant/components/switch/rachio.py index 547442a4233..c9b6011bcbd 100644 --- a/homeassistant/components/switch/rachio.py +++ b/homeassistant/components/switch/rachio.py @@ -199,7 +199,6 @@ class RachioZone(SwitchDevice): @property def is_on(self): """Whether the zone is currently running.""" - self._device.update() schedule = self._device.current_schedule return self.zone_id == schedule.get('zoneId') From c4550d02c530a53b0adbb617eb11aaea32e2545e Mon Sep 17 00:00:00 2001 From: Fabian Affolter Date: Sat, 12 Aug 2017 08:52:56 +0200 Subject: [PATCH 05/93] Add version sensor (#8912) * Add version sensor * Set version directly * Rework tests and fix typo * Remove additional blank line --- homeassistant/components/sensor/version.py | 55 ++++++++++++++++++++++ tests/components/sensor/test_version.py | 50 ++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 homeassistant/components/sensor/version.py create mode 100644 tests/components/sensor/test_version.py diff --git a/homeassistant/components/sensor/version.py b/homeassistant/components/sensor/version.py new file mode 100644 index 00000000000..c19d2743563 --- /dev/null +++ b/homeassistant/components/sensor/version.py @@ -0,0 +1,55 @@ +""" +Support for displaying the current version of Home Assistant. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.version/ +""" +import asyncio +import logging + +import voluptuous as vol + +import homeassistant.helpers.config_validation as cv +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.const import __version__, CONF_NAME +from homeassistant.helpers.entity import Entity + +_LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = "Current Version" + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, +}) + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up the Version sensor platform.""" + name = config.get(CONF_NAME) + + async_add_devices([VersionSensor(name)]) + + +class VersionSensor(Entity): + """Representation of a Home Assistant version sensor.""" + + def __init__(self, name): + """Initialize the Version sensor.""" + self._name = name + self._state = __version__ + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def state(self): + """Return the state of the sensor.""" + return self._state diff --git a/tests/components/sensor/test_version.py b/tests/components/sensor/test_version.py new file mode 100644 index 00000000000..270cfd1709d --- /dev/null +++ b/tests/components/sensor/test_version.py @@ -0,0 +1,50 @@ +"""The test for the version sensor platform.""" +import asyncio +import unittest +from unittest.mock import patch + +from homeassistant.setup import setup_component + +from tests.common import get_test_home_assistant + +MOCK_VERSION = '10.0' + + +class TestVersionSensor(unittest.TestCase): + """Test the Version sensor.""" + + def setup_method(self, method): + """Set up things to be run when tests are started.""" + self.hass = get_test_home_assistant() + + def teardown_method(self, method): + """Stop everything that was started.""" + self.hass.stop() + + def test_version_sensor(self): + """Test the Version sensor.""" + config = { + 'sensor': { + 'platform': 'version', + } + } + + assert setup_component(self.hass, 'sensor', config) + + @asyncio.coroutine + def test_version(self): + """Test the Version sensor.""" + config = { + 'sensor': { + 'platform': 'version', + 'name': 'test', + } + } + + with patch('homeassistant.const.__version__', MOCK_VERSION): + assert setup_component(self.hass, 'sensor', config) + self.hass.block_till_done() + + state = self.hass.states.get('sensor.test') + + self.assertEqual(state.state, '10.0') From 489a02b2c267aef025fe985450816eb7b6880669 Mon Sep 17 00:00:00 2001 From: groth-its Date: Sat, 12 Aug 2017 17:38:12 +0200 Subject: [PATCH 06/93] Fix hue lights for Philips and non-philips lights (#8905) --- homeassistant/components/light/hue.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py index cdbea7d2194..27c3b43e926 100644 --- a/homeassistant/components/light/hue.py +++ b/homeassistant/components/light/hue.py @@ -384,7 +384,6 @@ class HueLight(Light): hue, sat = color_util.color_xy_to_hs(*kwargs[ATTR_XY_COLOR]) command['hue'] = hue command['sat'] = sat - command['bri'] = self.info['bri'] else: command['xy'] = kwargs[ATTR_XY_COLOR] elif ATTR_RGB_COLOR in kwargs: @@ -399,14 +398,13 @@ class HueLight(Light): *(int(val) for val in kwargs[ATTR_RGB_COLOR])) command['xy'] = xyb[0], xyb[1] command['bri'] = xyb[2] + elif ATTR_COLOR_TEMP in kwargs: + temp = kwargs[ATTR_COLOR_TEMP] + command['ct'] = max(self.min_mireds, min(temp, self.max_mireds)) if ATTR_BRIGHTNESS in kwargs: command['bri'] = kwargs[ATTR_BRIGHTNESS] - if ATTR_COLOR_TEMP in kwargs: - temp = kwargs[ATTR_COLOR_TEMP] - command['ct'] = max(self.min_mireds, min(temp, self.max_mireds)) - flash = kwargs.get(ATTR_FLASH) if flash == FLASH_LONG: @@ -425,9 +423,9 @@ class HueLight(Light): elif effect == EFFECT_RANDOM: command['hue'] = random.randrange(0, 65535) command['sat'] = random.randrange(150, 254) - elif self.bridge_type == 'hue': - if self.info.get('manufacturername') != "OSRAM": - command['effect'] = 'none' + elif (self.bridge_type == 'hue' and + self.info.get('manufacturername') == 'Philips'): + command['effect'] = 'none' self._command_func(self.light_id, command) From 369caeedbd2eefb4497877d07596e2ac747dfc4d Mon Sep 17 00:00:00 2001 From: cribbstechnologies Date: Sat, 12 Aug 2017 11:50:02 -0400 Subject: [PATCH 07/93] fixing emulated hue issue and testing it (#8928) * fixing emulated hue issue and testing it * fixing hound issues * I should probably stop using vim * Check against dict directly instead of items. --- .../components/emulated_hue/__init__.py | 4 +- tests/components/emulated_hue/test_init.py | 59 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py index 315fc564999..ae0a26aaea4 100644 --- a/homeassistant/components/emulated_hue/__init__.py +++ b/homeassistant/components/emulated_hue/__init__.py @@ -193,7 +193,9 @@ class Config(object): if entity_id == ent_id: return number - number = str(max(int(k) for k in self.numbers) + 1) + number = '1' + if self.numbers: + number = str(max(int(k) for k in self.numbers) + 1) self.numbers[number] = entity_id self._save_numbers_json() return number diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py index 8c0a6dc4f60..2dcb9ecbf21 100755 --- a/tests/components/emulated_hue/test_init.py +++ b/tests/components/emulated_hue/test_init.py @@ -36,6 +36,65 @@ def test_config_google_home_entity_id_to_number(): assert entity_id == 'light.test2' +def test_config_google_home_entity_id_to_number_altered(): + """Test config adheres to the type.""" + conf = Config(Mock(), { + 'type': 'google_home' + }) + + mop = mock_open(read_data=json.dumps({'21': 'light.test2'})) + handle = mop() + + with patch('homeassistant.components.emulated_hue.open', mop, create=True): + number = conf.entity_id_to_number('light.test') + assert number == '22' + assert handle.write.call_count == 1 + assert json.loads(handle.write.mock_calls[0][1][0]) == { + '21': 'light.test2', + '22': 'light.test', + } + + number = conf.entity_id_to_number('light.test') + assert number == '22' + assert handle.write.call_count == 1 + + number = conf.entity_id_to_number('light.test2') + assert number == '21' + assert handle.write.call_count == 1 + + entity_id = conf.number_to_entity_id('21') + assert entity_id == 'light.test2' + + +def test_config_google_home_entity_id_to_number_empty(): + """Test config adheres to the type.""" + conf = Config(Mock(), { + 'type': 'google_home' + }) + + mop = mock_open(read_data='') + handle = mop() + + with patch('homeassistant.components.emulated_hue.open', mop, create=True): + number = conf.entity_id_to_number('light.test') + assert number == '1' + assert handle.write.call_count == 1 + assert json.loads(handle.write.mock_calls[0][1][0]) == { + '1': 'light.test', + } + + number = conf.entity_id_to_number('light.test') + assert number == '1' + assert handle.write.call_count == 1 + + number = conf.entity_id_to_number('light.test2') + assert number == '2' + assert handle.write.call_count == 2 + + entity_id = conf.number_to_entity_id('2') + assert entity_id == 'light.test2' + + def test_config_alexa_entity_id_to_number(): """Test config adheres to the type.""" conf = Config(None, { From fbb67820813b1bd674162339ad71ea253c18f261 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sat, 12 Aug 2017 18:39:05 +0200 Subject: [PATCH 08/93] Fix SET_TEMPERATURE_SCHEMA in climate component (#8879) * Require either temperature or high/low target temperatures. * Add tests. --- homeassistant/components/climate/__init__.py | 18 +++++---- tests/common.py | 5 ++- tests/components/climate/test_init.py | 40 ++++++++++++++++++++ 3 files changed, 54 insertions(+), 9 deletions(-) create mode 100644 tests/components/climate/test_init.py diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index 6dd66817d43..1f919301254 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -86,13 +86,17 @@ SET_AUX_HEAT_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_AUX_HEAT): cv.boolean, }) -SET_TEMPERATURE_SCHEMA = vol.Schema({ - vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float), - vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float), - vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float), - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Optional(ATTR_OPERATION_MODE): cv.string, -}) +SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All( + cv.has_at_least_one_key( + ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW), + { + vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float), + vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float), + vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float), + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_OPERATION_MODE): cv.string, + } +)) SET_FAN_MODE_SCHEMA = vol.Schema({ vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_FAN_MODE): cv.string, diff --git a/tests/common.py b/tests/common.py index 5e328959a7a..5fdec2fc411 100644 --- a/tests/common.py +++ b/tests/common.py @@ -174,7 +174,7 @@ def get_test_instance_port(): @ha.callback -def async_mock_service(hass, domain, service): +def async_mock_service(hass, domain, service, schema=None): """Set up a fake service & return a calls log list to this service.""" calls = [] @@ -183,7 +183,8 @@ def async_mock_service(hass, domain, service): """Mock service call.""" calls.append(call) - hass.services.async_register(domain, service, mock_service_log) + hass.services.async_register( + domain, service, mock_service_log, schema=schema) return calls diff --git a/tests/components/climate/test_init.py b/tests/components/climate/test_init.py new file mode 100644 index 00000000000..2e942c5988c --- /dev/null +++ b/tests/components/climate/test_init.py @@ -0,0 +1,40 @@ +"""The tests for the climate component.""" +import asyncio + +from homeassistant.components.climate import SET_TEMPERATURE_SCHEMA +from tests.common import async_mock_service + + +@asyncio.coroutine +def test_set_temp_schema_no_req(hass, caplog): + """Test the set temperature schema with missing required data.""" + domain = 'climate' + service = 'test_set_temperature' + schema = SET_TEMPERATURE_SCHEMA + calls = async_mock_service(hass, domain, service, schema) + + data = {'operation_mode': 'test', 'entity_id': ['climate.test_id']} + yield from hass.services.async_call(domain, service, data) + yield from hass.async_block_till_done() + + assert len(calls) == 0 + assert 'ERROR' in caplog.text + assert 'Invalid service data' in caplog.text + + +@asyncio.coroutine +def test_set_temp_schema(hass, caplog): + """Test the set temperature schema with ok required data.""" + domain = 'climate' + service = 'test_set_temperature' + schema = SET_TEMPERATURE_SCHEMA + calls = async_mock_service(hass, domain, service, schema) + + data = { + 'temperature': 20.0, 'operation_mode': 'test', + 'entity_id': ['climate.test_id']} + yield from hass.services.async_call(domain, service, data) + yield from hass.async_block_till_done() + + assert len(calls) == 1 + assert calls[-1].data == data From 4a98b32a03986e9633402142313359b45e94fb8f Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 12 Aug 2017 14:54:50 -0700 Subject: [PATCH 09/93] Update frontend --- homeassistant/components/frontend/version.py | 2 +- .../www_static/home-assistant-polymer | 2 +- .../www_static/panels/ha-panel-config.html | 2 +- .../www_static/panels/ha-panel-config.html.gz | Bin 20661 -> 20665 bytes 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 001e369602b..59b09aa4ca1 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -6,7 +6,7 @@ FINGERPRINTS = { "frontend.html": "fb225cfababf965f8e19a8eb5c5a2a7e", "mdi.html": "e91f61a039ed0a9936e7ee5360da3870", "micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a", - "panels/ha-panel-config.html": "878fd176dad70fe5cb8fc3c4ca72145c", + "panels/ha-panel-config.html": "ec48185c79000d0cfe5bbf38c7974944", "panels/ha-panel-dev-event.html": "d409e7ab537d9fe629126d122345279c", "panels/ha-panel-dev-info.html": "b0e55eb657fd75f21aba2426ac0cedc0", "panels/ha-panel-dev-mqtt.html": "94b222b013a98583842de3e72d5888c6", diff --git a/homeassistant/components/frontend/www_static/home-assistant-polymer b/homeassistant/components/frontend/www_static/home-assistant-polymer index a51b9c1eb5a..823a6996708 160000 --- a/homeassistant/components/frontend/www_static/home-assistant-polymer +++ b/homeassistant/components/frontend/www_static/home-assistant-polymer @@ -1 +1 @@ -Subproject commit a51b9c1eb5aecd119706a64c353b5df1de0af8cd +Subproject commit 823a6996708d5b85097fb1db0defbc03516e77bf diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-config.html b/homeassistant/components/frontend/www_static/panels/ha-panel-config.html index 98742c6ac29..d645ff87f8e 100644 --- a/homeassistant/components/frontend/www_static/panels/ha-panel-config.html +++ b/homeassistant/components/frontend/www_static/panels/ha-panel-config.html @@ -1,4 +1,4 @@ - \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-config.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-config.html.gz index 775e05b4a0207047491e526b6172a078c99639a2..e794d3735977c39139d3b34360a40ad42c371c37 100644 GIT binary patch delta 7808 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 UCzFzFZvFj#^Hk2+ntDbC05f1k-T(jq From 79f45b51763a59c27a4ac26457b3b047e9cd39b8 Mon Sep 17 00:00:00 2001 From: Alok Saboo Date: Sun, 13 Aug 2017 02:49:15 -0400 Subject: [PATCH 10/93] Fixed cert_expiry sensor to delay firing on HA startup (#8920) * Fixed cert_expiry sensor to delay firing on HA startup * Addressed Travis complaints * Added imports * Fixed cert_expiry sensor to delay firing on HA startup * Changed comment --- .../components/sensor/cert_expiry.py | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/sensor/cert_expiry.py b/homeassistant/components/sensor/cert_expiry.py index dfc15510d6f..1ccaf2f6925 100644 --- a/homeassistant/components/sensor/cert_expiry.py +++ b/homeassistant/components/sensor/cert_expiry.py @@ -4,16 +4,17 @@ Counter for the days till a HTTPS (TLS) certificate will expire. For more details about this sensor please refer to the documentation at https://home-assistant.io/components/sensor.cert_expiry/ """ -import datetime import logging import socket import ssl +from datetime import datetime, timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT) +from homeassistant.const import (CONF_NAME, CONF_HOST, CONF_PORT, + EVENT_HOMEASSISTANT_START) from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) @@ -21,7 +22,7 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = 'SSL Certificate Expiry' DEFAULT_PORT = 443 -SCAN_INTERVAL = datetime.timedelta(hours=12) +SCAN_INTERVAL = timedelta(hours=12) TIMEOUT = 10.0 @@ -34,11 +35,20 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up certificate expiry sensor.""" - server_name = config.get(CONF_HOST) - server_port = config.get(CONF_PORT) - sensor_name = config.get(CONF_NAME) + def run_setup(event): + """Wait until Home Assistant is fully initialized before creating. - add_devices([SSLCertificate(sensor_name, server_name, server_port)], True) + Delay the setup until Home Assistant is fully initialized. + """ + server_name = config.get(CONF_HOST) + server_port = config.get(CONF_PORT) + sensor_name = config.get(CONF_NAME) + + add_devices([SSLCertificate(sensor_name, server_name, server_port)], + True) + + # To allow checking of the HA certificate we must first be running. + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup) class SSLCertificate(Entity): @@ -97,6 +107,6 @@ class SSLCertificate(Entity): return ts_seconds = ssl.cert_time_to_seconds(cert['notAfter']) - timestamp = datetime.datetime.fromtimestamp(ts_seconds) - expiry = timestamp - datetime.datetime.today() + timestamp = datetime.fromtimestamp(ts_seconds) + expiry = timestamp - datetime.today() self._state = expiry.days From 73d6227021633cd78841be25922b740e32724842 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Sun, 13 Aug 2017 09:54:43 +0200 Subject: [PATCH 11/93] Remove spaces from Xiami switch attributes (#8952) * Attributes of the xiaomi zigbee plug changed. * Reformat. --- homeassistant/components/switch/xiaomi.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/switch/xiaomi.py b/homeassistant/components/switch/xiaomi.py index 3e4ea4f6d72..767043a8bc9 100644 --- a/homeassistant/components/switch/xiaomi.py +++ b/homeassistant/components/switch/xiaomi.py @@ -6,9 +6,13 @@ from homeassistant.components.xiaomi import (PY_XIAOMI_GATEWAY, XiaomiDevice) _LOGGER = logging.getLogger(__name__) -ATTR_LOAD_POWER = 'Load power' # Load power in watts (W) -ATTR_POWER_CONSUMED = 'Power consumed' -ATTR_IN_USE = 'In use' +# Load power in watts (W) +ATTR_LOAD_POWER = 'load_power' + +# Total (lifetime) power consumption in watts +ATTR_POWER_CONSUMED = 'power_consumed' +ATTR_IN_USE = 'in_use' + LOAD_POWER = 'load_power' POWER_CONSUMED = 'power_consumed' IN_USE = 'inuse' From c92e5c147a360175b964121525d20bc03cf4ef18 Mon Sep 17 00:00:00 2001 From: Eugenio Panadero Date: Sun, 13 Aug 2017 15:02:48 +0200 Subject: [PATCH 12/93] fix DeviceException handling when updating xiaomi vacuum (#8954) * Fix DeviceException handling when updating entity * add DeviceException error handling to generic request --- homeassistant/components/vacuum/xiaomi.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/vacuum/xiaomi.py b/homeassistant/components/vacuum/xiaomi.py index c5fb0d3c003..5e5081a2aa8 100644 --- a/homeassistant/components/vacuum/xiaomi.py +++ b/homeassistant/components/vacuum/xiaomi.py @@ -219,12 +219,12 @@ class MiroboVacuum(VacuumDevice): @asyncio.coroutine def _try_command(self, mask_error, func, *args, **kwargs): """Call a vacuum command handling error messages.""" - from mirobo import VacuumException + from mirobo import DeviceException, VacuumException try: yield from self.hass.async_add_job(partial(func, *args, **kwargs)) return True - except VacuumException as ex: - _LOGGER.error(mask_error, ex) + except (DeviceException, VacuumException) as exc: + _LOGGER.error(mask_error, exc) return False @asyncio.coroutine @@ -341,7 +341,7 @@ class MiroboVacuum(VacuumDevice): @asyncio.coroutine def async_update(self): """Fetch state from the device.""" - from mirobo import VacuumException + from mirobo import DeviceException try: state = yield from self.hass.async_add_job(self._vacuum.status) _LOGGER.debug("Got new state from the vacuum: %s", state.data) @@ -351,6 +351,6 @@ class MiroboVacuum(VacuumDevice): except OSError as exc: _LOGGER.error("Got OSError while fetching the state: %s", exc) # self._available = False - except VacuumException as exc: + except DeviceException as exc: _LOGGER.warning("Got exception while fetching the state: %s", exc) # self._available = False From 811fdc5533db53170733a08e587e6798bd1becc6 Mon Sep 17 00:00:00 2001 From: Matt Schmitt Date: Sun, 13 Aug 2017 18:57:48 +0100 Subject: [PATCH 13/93] Add service to alarm control panel for night mode arming (#8614) * Update const.py * Update __init__.py * Update services.yaml * Update totalconnect.py * Update manual.py Add night arm service for manual alarm control panel * Update test_manual.py Add tests for night mode arming * Update manual.py Fix docstring --- .../alarm_control_panel/__init__.py | 27 ++++++- .../components/alarm_control_panel/manual.py | 24 +++++- .../alarm_control_panel/services.yaml | 11 +++ .../alarm_control_panel/totalconnect.py | 14 +++- homeassistant/const.py | 4 + .../alarm_control_panel/test_manual.py | 80 ++++++++++++++++++- 6 files changed, 152 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 39c86f3215f..005048ba8c1 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -13,7 +13,8 @@ import voluptuous as vol from homeassistant.const import ( ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER, - SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY) + SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY, + SERVICE_ALARM_ARM_NIGHT) from homeassistant.config import load_yaml_config_file from homeassistant.loader import bind_hass from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa @@ -31,6 +32,7 @@ SERVICE_TO_METHOD = { SERVICE_ALARM_DISARM: 'alarm_disarm', SERVICE_ALARM_ARM_HOME: 'alarm_arm_home', SERVICE_ALARM_ARM_AWAY: 'alarm_arm_away', + SERVICE_ALARM_ARM_NIGHT: 'alarm_arm_night', SERVICE_ALARM_TRIGGER: 'alarm_trigger' } @@ -81,6 +83,18 @@ def alarm_arm_away(hass, code=None, entity_id=None): hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data) +@bind_hass +def alarm_arm_night(hass, code=None, entity_id=None): + """Send the alarm the command for arm night.""" + data = {} + if code: + data[ATTR_CODE] = code + if entity_id: + data[ATTR_ENTITY_ID] = entity_id + + hass.services.call(DOMAIN, SERVICE_ALARM_ARM_NIGHT, data) + + @bind_hass def alarm_trigger(hass, code=None, entity_id=None): """Send the alarm the command for trigger.""" @@ -187,6 +201,17 @@ class AlarmControlPanel(Entity): """ return self.hass.async_add_job(self.alarm_arm_away, code) + def alarm_arm_night(self, code=None): + """Send arm night command.""" + raise NotImplementedError() + + def async_alarm_arm_night(self, code=None): + """Send arm night command. + + This method must be run in the event loop and returns a coroutine. + """ + return self.hass.async_add_job(self.alarm_arm_night, code) + def alarm_trigger(self, code=None): """Send alarm trigger command.""" raise NotImplementedError() diff --git a/homeassistant/components/alarm_control_panel/manual.py b/homeassistant/components/alarm_control_panel/manual.py index c87aea862d5..97820ab4b2b 100644 --- a/homeassistant/components/alarm_control_panel/manual.py +++ b/homeassistant/components/alarm_control_panel/manual.py @@ -12,9 +12,10 @@ import voluptuous as vol import homeassistant.components.alarm_control_panel as alarm import homeassistant.util.dt as dt_util from homeassistant.const import ( - STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, - STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, CONF_PLATFORM, CONF_NAME, - CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME, CONF_DISARM_AFTER_TRIGGER) + STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_DISARMED, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, + CONF_PLATFORM, CONF_NAME, CONF_CODE, CONF_PENDING_TIME, CONF_TRIGGER_TIME, + CONF_DISARM_AFTER_TRIGGER) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_point_in_time @@ -87,7 +88,8 @@ class ManualAlarm(alarm.AlarmControlPanel): def state(self): """Return the state of the device.""" if self._state in (STATE_ALARM_ARMED_HOME, - STATE_ALARM_ARMED_AWAY) and \ + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_NIGHT) and \ self._pending_time and self._state_ts + self._pending_time > \ dt_util.utcnow(): return STATE_ALARM_PENDING @@ -145,6 +147,20 @@ class ManualAlarm(alarm.AlarmControlPanel): self._hass, self.async_update_ha_state, self._state_ts + self._pending_time) + def alarm_arm_night(self, code=None): + """Send arm night command.""" + if not self._validate_code(code, STATE_ALARM_ARMED_NIGHT): + return + + self._state = STATE_ALARM_ARMED_NIGHT + self._state_ts = dt_util.utcnow() + self.schedule_update_ha_state() + + if self._pending_time: + track_point_in_time( + self._hass, self.async_update_ha_state, + self._state_ts + self._pending_time) + def alarm_trigger(self, code=None): """Send alarm trigger command. No code needed.""" self._pre_trigger_state = self._state diff --git a/homeassistant/components/alarm_control_panel/services.yaml b/homeassistant/components/alarm_control_panel/services.yaml index 6cc3946ca66..19c3ca0233d 100644 --- a/homeassistant/components/alarm_control_panel/services.yaml +++ b/homeassistant/components/alarm_control_panel/services.yaml @@ -31,6 +31,17 @@ alarm_arm_away: description: An optional code to arm away the alarm control panel with example: 1234 +alarm_arm_night: + description: Send the alarm the command for arm night + + fields: + entity_id: + description: Name of alarm control panel to arm night + example: 'alarm_control_panel.downstairs' + code: + description: An optional code to arm night the alarm control panel with + example: 1234 + alarm_trigger: description: Send the alarm the command for trigger diff --git a/homeassistant/components/alarm_control_panel/totalconnect.py b/homeassistant/components/alarm_control_panel/totalconnect.py index 9c0b5108fee..05dc8aeef20 100644 --- a/homeassistant/components/alarm_control_panel/totalconnect.py +++ b/homeassistant/components/alarm_control_panel/totalconnect.py @@ -13,8 +13,8 @@ import homeassistant.components.alarm_control_panel as alarm from homeassistant.components.alarm_control_panel import PLATFORM_SCHEMA from homeassistant.const import ( CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY, - STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED, STATE_UNKNOWN, - CONF_NAME) + STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED, + STATE_ALARM_ARMING, STATE_ALARM_DISARMING, STATE_UNKNOWN, CONF_NAME) REQUIREMENTS = ['total_connect_client==0.11'] @@ -74,6 +74,12 @@ class TotalConnect(alarm.AlarmControlPanel): state = STATE_ALARM_ARMED_HOME elif status == self._client.ARMED_AWAY: state = STATE_ALARM_ARMED_AWAY + elif status == self._client.ARMED_STAY_NIGHT: + state = STATE_ALARM_ARMED_NIGHT + elif status == self._client.ARMING: + state = STATE_ALARM_ARMING + elif status == self._client.DISARMING: + state = STATE_ALARM_DISARMING else: state = STATE_UNKNOWN @@ -90,3 +96,7 @@ class TotalConnect(alarm.AlarmControlPanel): def alarm_arm_away(self, code=None): """Send arm away command.""" self._client.arm_away() + + def alarm_arm_night(self, code=None): + """Send arm night command.""" + self._client.arm_stay_night() diff --git a/homeassistant/const.py b/homeassistant/const.py index 978dafa457b..19ce7e470c2 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -199,7 +199,10 @@ STATE_STANDBY = 'standby' STATE_ALARM_DISARMED = 'disarmed' STATE_ALARM_ARMED_HOME = 'armed_home' STATE_ALARM_ARMED_AWAY = 'armed_away' +STATE_ALARM_ARMED_NIGHT = 'armed_night' STATE_ALARM_PENDING = 'pending' +STATE_ALARM_ARMING = 'arming' +STATE_ALARM_DISARMING = 'disarming' STATE_ALARM_TRIGGERED = 'triggered' STATE_LOCKED = 'locked' STATE_UNLOCKED = 'unlocked' @@ -347,6 +350,7 @@ SERVICE_SHUFFLE_SET = 'shuffle_set' SERVICE_ALARM_DISARM = 'alarm_disarm' SERVICE_ALARM_ARM_HOME = 'alarm_arm_home' SERVICE_ALARM_ARM_AWAY = 'alarm_arm_away' +SERVICE_ALARM_ARM_NIGHT = 'alarm_arm_night' SERVICE_ALARM_TRIGGER = 'alarm_trigger' SERVICE_LOCK = 'lock' diff --git a/tests/components/alarm_control_panel/test_manual.py b/tests/components/alarm_control_panel/test_manual.py index 7bd89d12a0a..e5d819bc815 100644 --- a/tests/components/alarm_control_panel/test_manual.py +++ b/tests/components/alarm_control_panel/test_manual.py @@ -6,7 +6,7 @@ from unittest.mock import patch from homeassistant.setup import setup_component from homeassistant.const import ( STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY, - STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED) + STATE_ALARM_ARMED_NIGHT, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED) from homeassistant.components import alarm_control_panel import homeassistant.util.dt as dt_util @@ -182,6 +182,84 @@ class TestAlarmControlPanelManual(unittest.TestCase): self.assertEqual(STATE_ALARM_DISARMED, self.hass.states.get(entity_id).state) + def test_arm_night_no_pending(self): + """Test arm night method.""" + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { + 'platform': 'manual', + 'name': 'test', + 'code': CODE, + 'pending_time': 0, + 'disarm_after_trigger': False + }})) + + entity_id = 'alarm_control_panel.test' + + self.assertEqual(STATE_ALARM_DISARMED, + self.hass.states.get(entity_id).state) + + alarm_control_panel.alarm_arm_night(self.hass, CODE) + self.hass.block_till_done() + + self.assertEqual(STATE_ALARM_ARMED_NIGHT, + self.hass.states.get(entity_id).state) + + def test_arm_night_with_pending(self): + """Test arm night method.""" + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { + 'platform': 'manual', + 'name': 'test', + 'code': CODE, + 'pending_time': 1, + 'disarm_after_trigger': False + }})) + + entity_id = 'alarm_control_panel.test' + + self.assertEqual(STATE_ALARM_DISARMED, + self.hass.states.get(entity_id).state) + + alarm_control_panel.alarm_arm_night(self.hass, CODE, 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=1) + 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_ARMED_NIGHT, + self.hass.states.get(entity_id).state) + + def test_arm_night_with_invalid_code(self): + """Attempt to night home without a valid code.""" + self.assertTrue(setup_component( + self.hass, alarm_control_panel.DOMAIN, + {'alarm_control_panel': { + 'platform': 'manual', + 'name': 'test', + 'code': CODE, + 'pending_time': 1, + 'disarm_after_trigger': False + }})) + + entity_id = 'alarm_control_panel.test' + + self.assertEqual(STATE_ALARM_DISARMED, + self.hass.states.get(entity_id).state) + + alarm_control_panel.alarm_arm_night(self.hass, CODE + '2') + self.hass.block_till_done() + + self.assertEqual(STATE_ALARM_DISARMED, + self.hass.states.get(entity_id).state) + def test_trigger_no_pending(self): """Test triggering when no pending submitted method.""" self.assertTrue(setup_component( From cbe5225e04c9354fd9929a5a2dd94b677596bec4 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Sun, 13 Aug 2017 20:28:33 +0200 Subject: [PATCH 14/93] Fix call to ha_send_commands (#8956) * Name keyword arguments correctly according to dependency lib. * Only pass keyword arguments that are not None. --- homeassistant/components/remote/harmony.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/remote/harmony.py b/homeassistant/components/remote/harmony.py index 19dcc10c545..b25741207de 100755 --- a/homeassistant/components/remote/harmony.py +++ b/homeassistant/components/remote/harmony.py @@ -214,14 +214,15 @@ class HarmonyRemote(remote.RemoteDevice): if device is None: _LOGGER.error("Missing required argument: device") return + params = {} num_repeats = kwargs.pop(ATTR_NUM_REPEATS, None) if num_repeats is not None: - kwargs[ATTR_NUM_REPEATS] = num_repeats + params['repeat_num'] = num_repeats delay_secs = kwargs.pop(ATTR_DELAY_SECS, None) if delay_secs is not None: - kwargs[ATTR_DELAY_SECS] = delay_secs + params['delay_secs'] = delay_secs pyharmony.ha_send_commands( - self._token, self.host, self._port, device, command, **kwargs) + self._token, self.host, self._port, device, command, **params) def sync(self): """Sync the Harmony device with the web service.""" From 4b3a932d88b19a6af92013d376a95153e44abfbe Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 13 Aug 2017 11:29:48 -0700 Subject: [PATCH 15/93] Sabnzbd: do not assume discovery info is a dict (#8951) --- homeassistant/components/sensor/sabnzbd.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/sensor/sabnzbd.py b/homeassistant/components/sensor/sabnzbd.py index dd02ce389f3..e2b7584d865 100644 --- a/homeassistant/components/sensor/sabnzbd.py +++ b/homeassistant/components/sensor/sabnzbd.py @@ -130,15 +130,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None): """Set up the SABnzbd platform.""" from pysabnzbd import SabnzbdApi - host = config.get(CONF_HOST) or discovery_info.get(CONF_HOST) - port = config.get(CONF_PORT) or discovery_info.get(CONF_PORT) - name = config.get(CONF_NAME, DEFAULT_NAME) - use_ssl = DEFAULT_SSL - - if config.get(CONF_SSL): - use_ssl = True - elif discovery_info.get('properties', {}).get('https', '0') == '1': - use_ssl = True + if discovery_info is not None: + host = discovery_info.get(CONF_HOST) + port = discovery_info.get(CONF_PORT) + name = DEFAULT_NAME + use_ssl = discovery_info.get('properties', {}).get('https', '0') == '1' + else: + host = config.get(CONF_HOST) + port = config.get(CONF_PORT) + name = config.get(CONF_NAME, DEFAULT_NAME) + use_ssl = config.get(CONF_SSL) uri_scheme = 'https://' if use_ssl else 'http://' base_url = "{}{}:{}/".format(uri_scheme, host, port) From 74adebc2fdce0abb2b3b4651c9cd4c1e387eef9d Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 13 Aug 2017 13:28:36 -0700 Subject: [PATCH 16/93] fix issue #8948 in pushbullet (#8965) * fix issue #8948 in pushbullet * pushbullet --- homeassistant/components/notify/pushbullet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index f3bb5a119d0..8ac2bd06dad 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -91,7 +91,7 @@ class PushBulletNotificationService(BaseNotificationService): # Backward compatibility, notify all devices in own account if url: self.pushbullet.push_link(title, url, body=message) - if self.hass.config.is_allowed_path(filepath): + if filepath and self.hass.config.is_allowed_path(filepath): with open(filepath, "rb") as fileh: filedata = self.pushbullet.upload_file(fileh, filepath) self.pushbullet.push_file(title=title, body=message, @@ -115,7 +115,7 @@ class PushBulletNotificationService(BaseNotificationService): if url: self.pushbullet.push_link( title, url, body=message, email=tname) - if self.hass.config.is_allowed_path(filepath): + if filepath and self.hass.config.is_allowed_path(filepath): with open(filepath, "rb") as fileh: filedata = self.pushbullet.upload_file(fileh, filepath) self.pushbullet.push_file(title=title, body=message, From 23273d3e882fe434e8edb7f16622b35ed8e95300 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 14 Aug 2017 04:15:59 +0300 Subject: [PATCH 17/93] Fix zwave power_consumption attribute (#8968) --- homeassistant/components/zwave/util.py | 7 ++-- tests/components/zwave/test_init.py | 49 ++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/zwave/util.py b/homeassistant/components/zwave/util.py index 6abdc180742..8c74b731ad6 100644 --- a/homeassistant/components/zwave/util.py +++ b/homeassistant/components/zwave/util.py @@ -54,9 +54,12 @@ def check_value_schema(value, schema): value.instance, schema[const.DISC_INSTANCE]) return False if const.DISC_SCHEMAS in schema: + found = False for schema_item in schema[const.DISC_SCHEMAS]: - if not check_value_schema(value, schema_item): - return False + found = found or check_value_schema(value, schema_item) + if not found: + return False + return True diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py index 19335caa47e..2fa4dd0b929 100644 --- a/tests/components/zwave/test_init.py +++ b/tests/components/zwave/test_init.py @@ -3,6 +3,9 @@ import asyncio from collections import OrderedDict from datetime import datetime +import unittest +from unittest.mock import patch, MagicMock + from homeassistant.bootstrap import async_setup_component from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_START from homeassistant.components import zwave @@ -12,8 +15,6 @@ from homeassistant.components.zwave import ( from homeassistant.setup import setup_component import pytest -import unittest -from unittest.mock import patch, MagicMock from tests.common import ( get_test_home_assistant, async_fire_time_changed) @@ -312,6 +313,50 @@ def test_value_discovery_existing_entity(hass, mock_openzwave): 'current_temperature'] == 23.5 +@asyncio.coroutine +def test_power_schemes(hass, mock_openzwave): + """Test power attribute.""" + mock_receivers = [] + + def mock_connect(receiver, signal, *args, **kwargs): + if signal == MockNetwork.SIGNAL_VALUE_ADDED: + mock_receivers.append(receiver) + + with patch('pydispatch.dispatcher.connect', new=mock_connect): + yield from async_setup_component(hass, 'zwave', {'zwave': { + 'new_entity_ids': True, + }}) + + assert len(mock_receivers) == 1 + + node = MockNode(node_id=11, generic=const.GENERIC_TYPE_SWITCH_BINARY) + switch = MockValue( + data=True, node=node, index=12, instance=13, + command_class=const.COMMAND_CLASS_SWITCH_BINARY, + genre=const.GENRE_USER, type=const.TYPE_BOOL) + hass.async_add_job(mock_receivers[0], node, switch) + + yield from hass.async_block_till_done() + + assert hass.states.get('switch.mock_node_mock_value').state == 'on' + assert 'power_consumption' not in hass.states.get( + 'switch.mock_node_mock_value').attributes + + def mock_update(self): + self.hass.async_add_job(self.async_update_ha_state) + + with patch.object(zwave.node_entity.ZWaveBaseEntity, + 'maybe_schedule_update', new=mock_update): + power = MockValue( + data=23.5, node=node, index=const.INDEX_SENSOR_MULTILEVEL_POWER, + instance=13, command_class=const.COMMAND_CLASS_SENSOR_MULTILEVEL) + hass.async_add_job(mock_receivers[0], node, power) + yield from hass.async_block_till_done() + + assert hass.states.get('switch.mock_node_mock_value').attributes[ + 'power_consumption'] == 23.5 + + @asyncio.coroutine def test_network_ready(hass, mock_openzwave): """Test Node network ready event.""" From a0ddb24245cd751b3fec6de047e1536c51e4acc9 Mon Sep 17 00:00:00 2001 From: Andrey Date: Mon, 14 Aug 2017 04:16:38 +0300 Subject: [PATCH 18/93] Turn foscam verbose mode off (#8967) --- homeassistant/components/camera/foscam.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/camera/foscam.py b/homeassistant/components/camera/foscam.py index 6e9f036074a..15138e2c253 100644 --- a/homeassistant/components/camera/foscam.py +++ b/homeassistant/components/camera/foscam.py @@ -56,7 +56,7 @@ class FoscamCam(Camera): from foscam import FoscamCamera self._foscam_session = FoscamCamera(ip_address, port, self._username, - self._password) + self._password, verbose=False) def camera_image(self): """Return a still image reponse from the camera.""" From 8fcec03adf0ebb14377d729c1879990fd3400f18 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sun, 13 Aug 2017 21:52:36 -0700 Subject: [PATCH 19/93] Update frontend --- homeassistant/components/frontend/version.py | 2 +- .../frontend/www_static/frontend.html | 8 ++++---- .../frontend/www_static/frontend.html.gz | Bin 165967 -> 165958 bytes .../www_static/home-assistant-polymer | 2 +- .../frontend/www_static/service_worker.js | 2 +- .../frontend/www_static/service_worker.js.gz | Bin 5139 -> 5141 bytes 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 59b09aa4ca1..07cd39ca581 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -3,7 +3,7 @@ FINGERPRINTS = { "compatibility.js": "1686167ff210e001f063f5c606b2e74b", "core.js": "2a7d01e45187c7d4635da05065b5e54e", - "frontend.html": "fb225cfababf965f8e19a8eb5c5a2a7e", + "frontend.html": "5a2a3d6181cc820f5b3e94d1a50def74", "mdi.html": "e91f61a039ed0a9936e7ee5360da3870", "micromarkdown-js.html": "93b5ec4016f0bba585521cf4d18dec1a", "panels/ha-panel-config.html": "ec48185c79000d0cfe5bbf38c7974944", diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 1281ef93b0d..71c4381108a 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -50,7 +50,7 @@ .input-content.is-invalid ::slotted(textarea), .input-content.is-invalid ::slotted(iron-autogrow-textarea), .input-content.is-invalid ::slotted(.paper-input-input){@apply --paper-input-container-input-invalid;}.prefix ::slotted(*){display:inline-block;@apply --paper-font-subhead;@apply --layout-flex-none;@apply --paper-input-prefix;}.suffix ::slotted(*){display:inline-block;@apply --paper-font-subhead;@apply --layout-flex-none;@apply --paper-input-suffix;}.input-content ::slotted(input){min-width:0;}.input-content ::slotted(textarea){resize:none;}.add-on-content{position:relative;}.add-on-content.is-invalid ::slotted(*){color:var(--paper-input-container-invalid-color, var(--error-color));}.add-on-content.is-highlighted ::slotted(*){color:var(--paper-input-container-focus-color, var(--primary-color));}
\ No newline at end of file + ha-automation-editor{height:100%;} \ No newline at end of file diff --git a/homeassistant/components/frontend/www_static/panels/ha-panel-config.html.gz b/homeassistant/components/frontend/www_static/panels/ha-panel-config.html.gz index e794d3735977c39139d3b34360a40ad42c371c37..9c8b72d6bccbb68931d502f47b45899b3c0387f8 100644 GIT binary patch literal 26600 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 XwcA-wb6s2YfBSXoMQ1X9Vq^dS=D@;S literal 20665 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�)w>30;TOSh2!JliMD+ClA?UT%x{b$B@!Tg1yF z`@WV*t(hKEzhi-pmEnfIuUd+4ws`DJi_@PFu`NCE?X~nQ?FLDAegE(4?@yTMXg)h? z5!bbRcdwhXDj9hjCzbs4FPzUFZ!@=BGe+{)!%sf8tkzshMa++;A6{`ZS@Of%ma@4! z3-9e}Q@i8ml{DLbk&EO$#qM8Qtj)e@-+#2^#HD{z4l{cGn_%p&Jb!V0QQthtDxI0l z)seC<4vJ>)&R#LisyW-ex+FL&wy4Unw?8gfJ^$M$-Mbfc8}Eo6IkhCm>fOzs z8eV!w^|Bv4He5Hg_nhI1>2q^;*NC+HWcPg%aj2BP=#vUu=c4p(o!j?FJc);ism43wiQD0dEoPf;=km?dT_Bju zZt~h9(`eVyTb5z+%dV#t{+lAemkYCEtkIkdvp5l+8XcEC$rwHKJCr6Z(CZFk&WWc zBgd|;yxVT{Y2TS5z0f$XOT2$Wm)rbWnQ(YvCd-63vkc{Z-b~fdxiIxfZS(W|;J@cq zxLlufJX`JWx`Q8?=G_Umtz=c6koGL|>9dc&_E&U%Z{)k`KEGP7<5h6o>=V_yel9Gz z^izMH;Kdo=Jz0K8@;`aMPeAs5?M46NYYlt2PlVs$FMML!8{HF7EW1EjTP@I#C)RoA zuHE`6W<}pwW!-s_ZcF?A?s@Pb`at<@p7Y`7(}fGpI_|MLzg^w@F5``R*G!!`-_BXS zxNiIOzDsME`?{`Y#V&byv}0>ROmuvH{_ne$(KE6*7nja!dT#%0!LwVpl9$*VOnDC8 z`C+@Vuj&8O3;#PK(#+a6_9@!=tIxf5E?%HR>s3a<(x_wN!VN3*nMIEG_3Fe-yxx5IpON`3E5TFGOVrx`XSR~2pKj2pJhq2efB;`vVzucZnCBhuri)=)W3zk?clAqM` z!}GH1WTB`#rk%;XyIQ|ZyVktm^p3om`F(lEbv~yVw(xB2GMw=t+Ae8Yd73kiZd%DN z)ARfdGnW{O>+f%zm>6ipUsMo&uPNu&d)JJO8=13at;jwo6u(nSUCH#?vuV3`ZhO}j z>$bx+^7w_1Mk%Z-DsHlS99Pu-dVEFtl#8O8HIGbx%c_UJifEc9e9c2R(9; zeU$btJ+S!s@*O9CiT1Rw6Is3Ft!K@xqqiS_+pM!I_qgTXuM+omH{W;K6O!{TI^;HI zmPgFOh4$tzWC{$)@T%{wgJwB&BWef9@;$Z7D-oz}A^l7ptdrWW5l}_#HM;{*?yL^jS^6(ydsX zc?xfuIM(tV)H0ptETQ&n*{4q`qH||#d;Y7uoIz0JQ?9i1k+#z(OuF-LJectL;g3bV zZ8|d~y)M6~G@W%xNH0{P_Tl-@3l@pwoDQAWB~Z-F_WsSzcblhIB=Q{DI`6)w;R4gjr4c$*YI)1zURMoN3TRijn+N|3dz8g~&=N#ZWQ+zP_ z*|spfYaetid30VbjGpVm8oA~C<$t-=XFl2NYWQ$c=wW4ogq%0e*STp;+7qheyDF}T znXa%-SzoXywNOFTDdXIB*aFn}Mdv#L89D7Bzi2f#(8r``@e-f6=`Z)RHWSii3 zhoa~rJ$+Ztq?h{37#~l6DOz4Gy)ysF^ebmO0(ac)jT8B#bhP7sUDOR&zAt?b9a({8}^bso>l^7wyJM$|i8*kz-%(pJ=Y6Ap>H)UrHG*d^-(MHK z_{$-sPx;p$czdkl}l!ZJ^|346sKdhOYEhv9E?(3H4C;!4fQ&o=ZdRa}?j zrE+HzNB$d@$168EOG-C*$_5>_|ByaYVN#~qMCD5s>(d`RY0x>%-r0BarNF$|2Nn5E zmQAg24dK0Ot|@rAQJinf&Ajb*7aY6z|J_x?A3x`4FVIK|FMD=b=*5+?N#cbH2U*`Q z+~K$R^!Z8rx7GT$tY6%-O!T5KBbVl0g-c($)8)IqFx?lJpA@x8F?jvwd$;epa(=1W z8uCh(yv&yRFe^ka6bozN;!*1y&ci zZ{>KX-SPPB7M-cDjz;nyUvF3*tH0sv;ee0XJDtRAo_ZMzJmT87S>^5K&2zpklQikq z-plu}wb(`fMD5zl2B~!m|L&D8`*EUX;UR&;4jT5mmK}6o-0<$#a_YPNAH({#1{|Oepzf>u=ZHsrSfwA7u$;u)Z0t;dY!csoGIrg zccEjkapsD${q6BVntCc{oo6kX8nDZE*T$lyZt_NQWsB$ggcWaSNxOder2C7V7oRMD zU16nh?m%U7LSp}en?D)n&tG?W*G~UysT~fyX7+9I{&v^GCi7pl{9g7w_kH=y%khWS zWZo;2<4|Ay=+|_H7U|Ob@WSf{=WTt&vb=NE%NNJL-M+ob^e1n)UB^|U@1?KKE_mBg zz53@%PJhMu=L+7jpJ(*_C41{^*+kdvZL?!`vn@JTuIB$~tK;&i$^MN(Z1b&nd@ot- zJiFPtP3EN*5VZ7rRck$d)Gyx$SF%HF1# zF1#9VGoM<&7M`o5cyiuO+2gfgGm0Nheb=g#{PKKwfR@YNKc9UVzeujEvlBP|&2RPb zNMpyG_5U+p-fgJc{%NBA1>yQX>tZTBym~H7%I|T_c>1(6MsU@iYdNY4-$NtpKQ(Sx z70D7Xb#3o{zPF_uKO=XpTho!e_*A!UcM@w%>fuGok{qFvYSap}vsBJG1@T@CbDO%P zL4H9=%BEQj@-8;+J@++Kb41n#+A~#uz5VKMkSh1Q){H-;ezjIYvR~HB`}K5b(bll0 zZPP891EoZDC%@s#H7N`zT+m(2d_-~9gue9aA#B}yj{UL_^frEdxa3N@FS@Sj-{Q;b$=PVcCCHKFHnDNZK^yrjDJA*!Qo%Z ze~qtyj8osiv;X)7iIe>sE54uMJ@EUG>fvkd6(7$WwLCACw?2Dvj~Nd4xRT|HXPs5}J_dJ(buF83g#Ivy``R79f5$SvAK4y0S8iu0Wp6c| zb9@e`QAya>$trh`?^)D0zo2MV%@{~_#>Yo*Ilio!h^}RG-Yu_cQWT)*< zZSHvp>=0#d?EZ0Uv4gM8yMQ&@mihE^EewzpsEU^4E;gE{^rt)_!)R9C$)C&4SUh*S zHc_+T=@M^2wyot6$4~OkWWC(0?JF3vp+@@dgKdtgUxX~(nb@~x)ak1m%(DIND&iL; z{7m{ndJ*HJX0?(N7v@P?t+}{=*)!k!x1{bIxcR4amRRhCb$=S&q}6%)gWJ7y3bvSj zwcq~k!%fS@?nz!BqVMe5vnJYK$e!bMegBWf>oL}K7fx+a?BLKjrF*tKa%t(K)hgS= z=J`6VTw_v`eqBpbh9P>=(O*{$LU-i9%?jNSTDa!?*8J;h_UVMKVfephk&#nYW=NQ# zTl4#~N7Ck9+Tihg_Ysz)z~xy>O|+ioeG;p=8FSquzv$lejgnjPvp7pl0t?H_na)KV zx)XdgtMhnRcBqR-xYDw&~0P9r?Y>k9FeGh3_xbvYa{IFe63s@ZGF4O!K|v z>*j8HBc?xpXF`}qOsUPQ=E9EL3b`4kEk{G`^OoM2A*rCV{PDtxrOmYl{+G3~9(mgB z67mT)=I7Sn?K_zLsIwsb^n%P^*A6}T7uxBVc;O3|<2uH7QN?Hm60cT8K3VW{ik6J!MWyYxr%t`+HA{0va8LGX@%pQp(fbzt=znrZ-CyrRqd@yx z9ZtS=i@lFoKHeA1Y%j?2+%$;&hWj~wl_y6fjal|AeOk;cBe!(B&FR7bj%Sw-D(pL0 z>u!1Y?&6($10EHs@inxcw>Z^X$Z-4$4@-}o&@T5i1r;kzYySRTD)VFK-ii%E5!rkC zKV00zFY~FIv*M@?OZuU$p9Pj4vx_RyIOnY!E+n}0edFin<`6&72l<{B>z2Nk#O=u_IWmrgFLk^dqe+9boZ{CW>!=5$??sr8fUGy{1bnK zC~egEs$puB_}y0TVRQe}KaVaOwkDsR9=zUKzTDSzR>8fO#)oyxB#I5LPUQV%|_>w;^PGPy0&q z*+LUTRd?-JqUyGK85{qbRlbuR@9tGO*v%)k?J29N&8{g;s}1Un&FsUbO}&#oV`^p2 zs|eT2=DfWR>!urBvO1F)cm0oKs$XTiwDIy~TSBKLcDK&5*>wFc-}cm?%&A)huUXIb zJC`JQwe!yF+Er|-E(}}Q7j8;Fuy|pc!-n9>^863SP6=2`tUc?Ue?{fh5liP%&DFp5 ztqI!n*l+S_mYn^5i^C3`m*SD%!NeLqmF4TH>_oljH*)%C9#1c+;;fw(if7HwLVyw|C~ zzKBiqV^mA-eg2rk#!=EY76sX#YE8MowkzV@Bs=9Rr49Gy&!57cHqWti(~8J_J32on zUd*wR+Pq4uGAdc(@H7jNRY}oTrnes9@b_y@Em@mhx5e(+#n5HNLGjE<2Yzuq_6lLd9?q!fs>5G@|W7}+Dm_5{21?)SNAB+S>cAk*;KEjxdsc`cJ01>L&IzCO6_ck z=dO{5zb+Hjo&P$nIP_P*uH*ApcfHxU%HwRP>#>-rwMI{pTz5|3{Zo4W+KeXtJBRuH z8@|76@xJ(cri{wk!DyV0-m2$_*9JWexxV?~p@l-v`SiZ;np#yYb$qu&j>Z(AC$Ma3cR?QzpRxf^8ueDJZnym?vv-Ur`in$|q|mFipZ!twNsxoZzi z2tTj)ebdpO7wQ*(Hh)yp{tyMSR*Ejm`OU&Vh$HZBO%wJi_ ze!eg2zsxZI)Wbhn-ABBbtGE1ry!w-(DfbHAZLIwoPMYw)?l--w;FbZe@nUcaP=YI{M7K=i1rRMca^bmz)$D*H>s)O;qho@f=e;#`dBESq9#}t@&#BIo>vv{;*YPQzyj5?k7L<+3 zQq#NrN+7c`mgTBSy;{gTUxmE)2l{h&g>I@h=y5+3{~*+*c6I+Y`-3Z==-+-Dc%z+9 zqGHX{NE{b@SuL4hMy8Wk68 zuHDwS?yS3g1ylYB0m5@M?yGcw60(2em;dliyrr;M;EPY5C-F{Hf1ei)tR7zp0V#r@Lm| zza=WBTaI3ORMx%flY^G0mEm2z6xVy3Ql>HV?>+4qo)@&qfBNMqN#(_JK3Ka;Ik$Y0 zHQvbSwdsgV|6;B!e^*^qSk#vgp1kf(RxISv7?Mj(vR{oLbv=7WmIAYrdl$ zzK*HoT)et@-;Tnd!%z0*iyBq!Tl;)p?6$Rk_Uh!W+MIkn^Rz+oT80)sl~8fFsJIoq z%`>ty{__4xyLb6U%5>?D^Tow;F3a0wKAO_LC2f_Xu*Z2bp2+t?kJ+mpBr;9kab4VV z)<(N!(@)o#%js?W{i?0tp}4{A>kqW@T^aH}J@Q=k=-9$6h6MWyCG>nmt=gn4g$(CV7Ho%z(;bnlRur+n|*x>py> z+*P~tt=@i{16EGXQ#R;*6niT3Fvdcu{ecVLl%w7+SeNBKuQ*Yp#((ML&#m`F)?Uc1 zbA1)Ffyvb0YkG}(Vc;Draml|ACo@(?uUoFqo-j32^W~f=lXa7{;$LoJ-r3(ZUpR;H z>H3^ccdJ=<%B)Xiim!gUvQRT8)*>R3^RL&GAn|~ibJy+`E?ahJrJC*5tT&sTZZuin zy_??sKIBkP#e=4KHWO-BWpDYnYB^{vL0`+|?>1*^KRk5bA-ImK7n z_k9ZY;{*BgPELytjJrDHYq|KeFSZUVujNDtrp&z^SKc3}oD;V|@AW#rE5u}i%=>zN z%$@suvUJR@W?qXgL_aj4&*@u_4Oi zq58YB>Q}oPeL_x~2doIJeYAT5+XLav|C(POJ@2a=A^+4(mHVx~gY~SA8l6Xi8~R=L zN*%Gc`gtJGvx`0Gt?i+TsHB-0>--mARSV7b%eG{x_1R?+SuxGJtaj_-JE$U+_R*A^8}WtpDg!;-bWwZ zDPX)t{`lU6q)D>PvlcGYC**0Ce{$TyQJGUpyuy1o(9i8x1V!Bl4lvSnjg6jR7 z*KbnIzF!!^UfQedV!`BJbN*sgxq@wza$LRXr9-RtX{_1wNSkvS*Yy0hRW8@qHXP^E zKI3hnHEE;Wy}lhs4(y3^``Ws{a+chO%m3sSvp?!^`Fy4DD)YZ*nN;`Y_retU0M|$BawIi zS=?E>PXFC$-_Ce(uCwLOTsmvDu~1T$gOBS8&yH6Q&8Ake*In5>rzGNahxFzTd;NDs z7MC3nVlef-RMCCA>h!Zu=Q?jc_kbk zSt)x|eqPmA71QN6&MhiZ)-KZ#eej2^yYFy7^!{+GAB%!I z_{75U=cje=*?LrV{`pIvCr5E$JGS`h(W#!dV^&*dGuAi~Z_vRtyz9;|s zqMw#Wy34Ixy8N5ziY;r``YpVAzirm_du_8$XZzd#Km50GPWA)kurLLqx5u7}t6e%~ z`S0s=h8x`X&T8jqZ#&+Q`rE1ZdtgLZ-QVOLSxZy$ZU%0!ZQO5rE z2F?CoE7H49a=&g;^-EKJ`BIPN>TW;RCs))noc%qn-;3yz?^%87<8vE>EdhTU4NCLw z)apq!Ec-HxRoMQ(a*xfo&Fa2-X&1Y!TeHb7T}UDM0kIK+LPc_Ve_=DmNH zU-IJHpe1nS(te4@7n$9r3ro07Q(d_Gs-wWV!;>d%z9}hs@ZzG&y^AZB+?PA7UnjPa zY47i*3lf{_WVU*=TYSlXQT5nDkh@^c+_&qushjURy?^V8_(K!Q%?`JU$)rf0>zXkC z@wH28C)_{m|Ev*xgCX$3iSGGxGK98#R95}a@Un1Xl=t+*{v^`Xpfx zt$Rx&!{w7#iPLwjbB~LoY!)tlTsMXPd~nRkg$eEJsv>>WujsG2vx+feTT}O9mq+I^ z-1e86ZhsNDK9X_so=V=!IrC$9H)l?)iIcXuYOMJE(ix_n{qGsYS1aDI$=|4R*zkR7 zPvjLNw&`6h@2Y=zaG!WtDy`D4@A1g*yUF7vOUt|pRMUmz*UgRCSmKhLq`L5GsMZG8 zqd%sp27hfjwlkAm@j266rHhXgPiNUp%wKobrcbf+RqWeUm%W-Enoj+svad0@;gS7| zKAqKDDi1hINd$h6yQQh}t@Pov?%Vq#q^CPL`DiG~eAG*B{=vqixANH0D;Adea~`BZ$F`Wv|`Ke|BPP+r^q$bGco`GhF}_P diff --git a/homeassistant/components/frontend/www_static/service_worker.js b/homeassistant/components/frontend/www_static/service_worker.js index 21824a12326..a5544a8b165 100644 --- a/homeassistant/components/frontend/www_static/service_worker.js +++ b/homeassistant/components/frontend/www_static/service_worker.js @@ -37,7 +37,7 @@ /* eslint-disable indent, no-unused-vars, no-multiple-empty-lines, max-nested-callbacks, space-before-function-paren, quotes, comma-spacing */ 'use strict'; -var precacheConfig = [["/","07ae53d16e9e97de8c721f5032cf79bf"],["/frontend/panels/dev-event-d409e7ab537d9fe629126d122345279c.html","936814991f2a5e23d61d29f0d40f81b8"],["/frontend/panels/dev-info-b0e55eb657fd75f21aba2426ac0cedc0.html","1fa953b0224470f70d4e87bbe4dff191"],["/frontend/panels/dev-mqtt-94b222b013a98583842de3e72d5888c6.html","dc3ddfac58397feda97317358f0aecbb"],["/frontend/panels/dev-service-422b2c181ee0713fa31d45a64e605baf.html","ae7d26b1c8c3309fd3c65944f89ea03f"],["/frontend/panels/dev-state-7948d3dba058f31517d880df8ed0e857.html","ff8156bb1a52490fcc07466556fce0e1"],["/frontend/panels/dev-template-f47b6910d8e4880e22cc508ca452f9b6.html","9aa0675e01373c6bc2737438bb84a9ec"],["/frontend/panels/map-c2544fff3eedb487d44105cf94b335ec.html","113c5bf9a68a74c62e50cd354034e78b"],["/static/compatibility-1686167ff210e001f063f5c606b2e74b.js","6ee7b5e2dd82b510c3bd92f7e215988e"],["/static/core-2a7d01e45187c7d4635da05065b5e54e.js","90a0a8a6a6dd0ca41b16f40e7d23924d"],["/static/frontend-5a2a3d6181cc820f5b3e94d1a50def74.html","6cd425233aeb180178dccae238533d65"],["/static/mdi-e91f61a039ed0a9936e7ee5360da3870.html","5e587bc82719b740a4f0798722a83aee"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"]]; +var precacheConfig = [["/","535d629ec4d3936dba0ca4ca84dabeb2"],["/frontend/panels/dev-event-d409e7ab537d9fe629126d122345279c.html","936814991f2a5e23d61d29f0d40f81b8"],["/frontend/panels/dev-info-b0e55eb657fd75f21aba2426ac0cedc0.html","1fa953b0224470f70d4e87bbe4dff191"],["/frontend/panels/dev-mqtt-94b222b013a98583842de3e72d5888c6.html","dc3ddfac58397feda97317358f0aecbb"],["/frontend/panels/dev-service-422b2c181ee0713fa31d45a64e605baf.html","ae7d26b1c8c3309fd3c65944f89ea03f"],["/frontend/panels/dev-state-7948d3dba058f31517d880df8ed0e857.html","ff8156bb1a52490fcc07466556fce0e1"],["/frontend/panels/dev-template-f47b6910d8e4880e22cc508ca452f9b6.html","9aa0675e01373c6bc2737438bb84a9ec"],["/frontend/panels/map-c2544fff3eedb487d44105cf94b335ec.html","113c5bf9a68a74c62e50cd354034e78b"],["/static/compatibility-1686167ff210e001f063f5c606b2e74b.js","6ee7b5e2dd82b510c3bd92f7e215988e"],["/static/core-2a7d01e45187c7d4635da05065b5e54e.js","90a0a8a6a6dd0ca41b16f40e7d23924d"],["/static/frontend-6c8192a4393c9e83516dc8177b75c23d.html","56d5bfe9e11a8b81a686f20aeae3c359"],["/static/mdi-e91f61a039ed0a9936e7ee5360da3870.html","5e587bc82719b740a4f0798722a83aee"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/icons/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/icons/favicon.ico","04235bda7843ec2fceb1cbe2bc696cf4"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"]]; var cacheName = 'sw-precache-v3--' + (self.registration ? self.registration.scope : ''); diff --git a/homeassistant/components/frontend/www_static/service_worker.js.gz b/homeassistant/components/frontend/www_static/service_worker.js.gz index 980bc5d26a82fa3ea0c79973de2f85b2116ab20e..97665c7f31e22c8c6a05b60599ba2bd3ff53391e 100644 GIT binary patch literal 5137 zcmb2|=3oE;Cg!cy*~PcrCf)z4eQek537b3x-nW<9P1n)x6ml=}{IqLZP26<`i3uAV z8yFn|_iX+DyYxJ>{-jOi+5S^+FkQI({@u%WlIQ2y*(&*;tlW5O+RK_i;b8NaPu_uN zjNSA;dnl(zy8T$*HDyiGQ`>ISh^E!|mI$8K7Moluw|UYPZntw`CQbXFOy~Tmt-X57 ziO!WTQ+gPVf4cnfq~@XpP!!^66W{%F?pxN^3`z_xkVa_STiV z-4Ow&1%ahxXo-b46i6SFEZky5@yViI1?HcpXRzK8Y6lum=;04X#;TIq)!DiRHX_ z>c*7KS;nlNU0Fktgs&^Nie^TtwdgI(CDIV5iJJN5KG3oR;__N;NwiT6# zzog&e!o5NJag##!owS?NiccM|br5#?9P&bY`bN*}D^`0PoGfoonBcX1B9{x#(Oq$C zUvuvC*K}WJ@Q;V%eo)8#`pA-2&d6xSWtsj3nQtolmLF5Y0n^jA@4kNzi*7s=gAru>>_lIGN|*%oHyf06f>))UR{$q{0P zM?`)|JX3$W;^Rl>bMAGEzlZ$bPG~f_W1wNd#xC=536KAS&$Ap(v`8RVVN{GlL zvxh4Kf9oW3l^pC1Ub9S6LnZqV$3?##553LWOO7bqSA52DMo}S5B$d6y^`!IM%M9$D zrf1YSb{}6+oOdPT_=?R2+J|mVQd$^!X4+96rrd8kXL-i@wCXKunmI{bQlP20#nQZe z!?FeWZA>~{yyg)s>qWmGvQUcK5s|Q4;=udNnPRWARuo@wjSJ71ySybda@O_WY4^=v zIyiYJc;x6xJjvT9z2t?}ga4I=vCmgb)10vC$LFHCS2aIavGT9n9vpE@tK?$4<{u_L zgWim3ttP*i^0jZwxM7l_pgLRawT;usz10{ls3JjYk@@NfpZ+pqKB1M zxi5Ejci8!H)@LREOO}<6b~P6BWc~YOPj|)|Crp%$ZhU%7@v3r``<3mp zn4qFoIwSMwI>FhFiw^%fV(#?cV6KUOSLO=inmpu9-WL|dl zOuTSlqtjiJ+}oFh`9B!n=XOpz?wQ1LEYaw+{p%{J8w)dMEzFBB`5=4$Q?7BE*yl4h zKGxb7iO#gx^vGhNU;|6sp(Lq_X%8d&=D(QYnpiQTrzk`E#Egj#rF&k^OH{pLlyrOH zy0bBNIWre&1U@^uOj>H9eB?Aam5ri1{St0>${VbTKB+tWgKaZ(H*cUTi#{ zv*4hQahJ?p_VS`LDjd>JS*N^O_uSyZCiY7$6JJKo%l(=7;Si9dS&0%Zy{qh3;+@QZFhsH<_8=r0b5WZW=(4TDFXntX(U+t?P^FBkq-U|i7 z(eCmam@fBKS4?eNmiI)u(KDChfkwiuvPE%;9kIU3s)H`2rXBjKl!@FT4T$ zvu8ZmI3;r8Qx@Adr!D3hafDxIFy}rHoxxyu#^#vJ>8r_EKjVZ;6&P6}pUj>o*fMWz zu=T~g2fq7E{w0>`B-@ppuxr*-*N7`e-?aTVtqo1t7 zH#%wd_6p>Dv<=DM;5l)}0T<5~zdIA#c)lb?ck;z$woGJo%$xD;d%@p>k4pAVIU$r5 zamLK1}4VlarE_`_5LjTPC zXtygzr#amXJS(IT`f$2t#4pBw*Ta6we7NBz@Is8GFO$hxMCQVE$3qiXcO^>9D;DTy zt3Ob6*GyNvXC-@xWYJ~;60K3;NCRWc{#HWcqP2#`O_g+|EJpDXUzUY`1G+B64-Z~tELN4ay~kxINi`)_Tm zzWrG{C5!gUg1xVSUv2dHFH`@9-i1N-ZaDEbMODiO;xea zWY+2BRi3qeRx#CHboX)^|TQnd@y3Sr@o9O8Na{MZ>j6Z+L09R&uu{oN`>Hv^#xwj~1&&Sv{MN z=SK&bx#5M1rx)Dl<_=SgzgpEAE2y6-{VGu~f1_AZNoTHw*TyB$t^TF0A0ofB1YBm| zJQSWdpJi*t&ZBkhlfnZQXZ5}nblTMK*VC^Y$oJhiQ5Bi@v>?+|C7jBvn8eYEa4l}!~x@oy==KY)B z3Z|?#$WO?dwEy4KwCrn}Ha%xwwCGty{Myx01ty%!h0~wUv04$F#vRl$Us3JC@fWod zk1f)Fa<;r(`fYoVulh9pO^cZJBwd*D;E%fG5#!bk3>P|`jycOsQvPMa-^1>)OR3P` z*`d!Ouy3l@=D8vTGrHHuOS{^=T<+#qTN5!={clL5VU!!U&y>gK9(x=8%2fHr{AtZX z7F!#W!|(I-&M&;8UBvORFgc|`d9nD8r!q~CzP(pp^nTf+Er%qJ|7I<@DEV!E;rY8} zuWU zrzduQns1_4cH1Ke$l1@W4lO#BKViP?N`b{C8}@$qeTjA3`PJTWK1cRuJxX{c zu*)u{?vL?Q<`sY0w#}3=gFZ|baFWqKt$ZGrsC6^<@DqI**=y=Xbt zeXe;;@B$x3J3YB462|_or=+EPDN@}y5lm_#=( zn!Ch8=`Y_;_cbdYG8EokC%)k00Y>W`^GX!A%nCKV@Ml52NYdRcAK5=@C2_Q5WJ$l< z8wSz>pc$6?>5Q*`diwD^XogUz?J3UAFZdbUYftlW8&-Mdz=!d zFMFaK?DKqsNy^tY!-LoPmn6s?c=0+vbe8+_|5rHsCcbb^s4NX%F>Apj#h>gMyar$0 z(`sX#gd)ox?@W=D-?sm8VQ>7wP0P=wOP2rHd$IDqXZ_rg3yx-jzW!k@dmdV5PIt>y zyu3bVmu2at>rEANCbXpa=IV<6T)27F;)|Zn?Zv#`j#e!`Ss&mVqdv#r*@>((jjeGi zasS+ylfxfRcI@nWo*}?G_0r;)g>m0S_3po0QuZ^B+56k39}^^^svJ13)}GG#=&566 z+mzd%Eu%G`Tc%*&!L>^5E{0yk8YY`_OfoP1pV9BY8uoAXwuPC0`K&r}&Ttk!)aI?a zQgdg^9_MpbR<8}V$8Y{U?bG{erRcA$2R`qZ+7+7G8TU8~}#e0$?m%kEI`8jskcvgZ4F=1G6N zqSLgEC5f5+A>8Ebhd128b4nx( z_eOMYe#|@1=o-uN-wU*Zub=$-zM^F#zxe$GDfat~4{im$$v8T_Rz@(OmQ&V9>-i!b z?t`^uE+l6$jbw|Lnt zsCl{Nx9U69c~^J6<~{ti+Tu>7dCljyD}Jx7e37$Hi=TH!{?6RlpXPTP38~(DTOH>* zT_mgOoxz_azPkk4E`8b0*;^3)Kr8)l4x{GkUz66|3$A;ZaA!mDl@FaKc_)8w{Bmp} z^CRY;z0;4(n5-ukRx_czb~Tf%4kfA{9ciFJqMg6_?^$5S@_efqJ7r|QIH zYGi-O^&gXE{U~A<(HVVpUe2}2I%$$3v#m}3@3-0b+BI<6r_JXz7Qg&tXm9nz!=_g5 zgVL2rA6TbeTd*QdWTQm;zoJ6(S6}vTe{|?L+jqVf1y`+j9{#X>A~z@LBVWWlai?8O zUi02f(-+mMJ9N*Mr+?$L_+>L*t;+3Vzf`$#-G@~yCLIlJ^S7Kgyv1cF^!S9RS*L7z zQw6)iqr(A}3v|4SCnh;Y*~Rlj-e%~I)=|2Abot$#YJJRJ7e6oD@n=)V3yE8j-m2%< zEu7`!WflKPIjyF3ucG|REjIT~pXR&K`8cLXH`|T3{Ozh46D+6o`u~#QJm~0nql0I; zKYR3}M&Ii-5&t}tb>^({|8aDK#Pf}t<@k2|>b~$M;!w9b={@)H)f^AKi&9Gy-?NVcT8>UB^%!fr=D~D z+9v$=$;*1V`Z*KkdRR(I#g{fZJGQNTu;BgQ?Nx``wDyY&bME;xHUA{ntshao@{Ma3 zZwr!L_RRD7IqCf-+qHsa8oJFG%HEZk+*H1sl`Ni_JfnVT(T$tQM{NpLEZVsu)vs>L zJ9kFD?<~HPwHWx`-(M9l-SyYptQohQEh>w7c@C#EHZDBzKH*t$?v>TNyG*s;{1cqM z!7VTM{ARp=QvZkh>aU)^gMRN>+5XwbaPh-5 zi-4*!*9I%IU!OdT53{>{s4nMqzs_OxNF!p+omIC_m4rL4jCn9q>;D<^<+cWQcEsos;WT;8!M?!#LC*J}cUTwZsbpTK&+z}@^a`@UJu{kvyASnyC@vndKVg7~aD^W{I6aoYK z-tH`M{b*vD_4xqfelZ1`_BV|gMqLFrr!7>T=f-!m%R}palw^5D+{N6m3l&ZRBCH4M zHy$`Xd+M)n=KXv32OIlxuDvwl#jWc6iBDcm)Y`Nwe%9rfK);W%+jIK=Jh7c}kE<}% zVe-K`-FZI*mT;F{vVM3};e}yKw9b{18}0=hjmvH}m$?d5cAU^HHfdf|ek^qN)7(De z;OkS}Ux{_Rc&Rvf&)$7|Cs!`nDcbb(?fOTFfB0`a`pA`RyO;TPZ$j&z?fQ%-2Ho&9{`%j( MQ*-+}W>W?R0Hnd}vj6}9 literal 5141 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 HrVI=KjaJc} From 3342db33e46ecfa917ad35670b6599afb9bcd4fb Mon Sep 17 00:00:00 2001 From: timstanley1985 Date: Wed, 16 Aug 2017 07:04:57 +0100 Subject: [PATCH 29/93] MQTT Switch - Add configurable availability payload (#8934) * Add configurable availabilty payload * Fix * Fix * Lint fixes * Fix tests * Fix tests * Move from const.py to mqtt switch * New test * Fix flake* --- homeassistant/components/switch/mqtt.py | 20 ++++++++--- tests/components/switch/test_mqtt.py | 48 ++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py index 95f9a779327..308cce4de46 100644 --- a/homeassistant/components/switch/mqtt.py +++ b/homeassistant/components/switch/mqtt.py @@ -24,17 +24,25 @@ _LOGGER = logging.getLogger(__name__) DEPENDENCIES = ['mqtt'] +CONF_PAYLOAD_AVAILABLE = 'payload_available' +CONF_PAYLOAD_NOT_AVAILABLE = 'payload_not_available' + DEFAULT_NAME = 'MQTT Switch' DEFAULT_PAYLOAD_ON = 'ON' DEFAULT_PAYLOAD_OFF = 'OFF' DEFAULT_OPTIMISTIC = False +DEFAULT_PAYLOAD_AVAILABLE = 'ON' +DEFAULT_PAYLOAD_NOT_AVAILABLE = 'OFF' PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_AVAILABILITY_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_PAYLOAD_AVAILABLE, + default=DEFAULT_PAYLOAD_AVAILABLE): cv.string, + vol.Optional(CONF_PAYLOAD_NOT_AVAILABLE, + default=DEFAULT_PAYLOAD_NOT_AVAILABLE): cv.string, }) @@ -58,6 +66,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): config.get(CONF_PAYLOAD_ON), config.get(CONF_PAYLOAD_OFF), config.get(CONF_OPTIMISTIC), + config.get(CONF_PAYLOAD_AVAILABLE), + config.get(CONF_PAYLOAD_NOT_AVAILABLE), value_template, )]) @@ -67,7 +77,7 @@ class MqttSwitch(SwitchDevice): def __init__(self, name, state_topic, command_topic, availability_topic, qos, retain, payload_on, payload_off, optimistic, - value_template): + payload_available, payload_not_available, value_template): """Initialize the MQTT switch.""" self._state = False self._name = name @@ -81,6 +91,8 @@ class MqttSwitch(SwitchDevice): self._payload_off = payload_off self._optimistic = optimistic self._template = value_template + self._payload_available = payload_available + self._payload_not_available = payload_not_available @asyncio.coroutine def async_added_to_hass(self): @@ -104,9 +116,9 @@ class MqttSwitch(SwitchDevice): @callback def availability_message_received(topic, payload, qos): """Handle new MQTT availability messages.""" - if payload == self._payload_on: + if payload == self._payload_available: self._available = True - elif payload == self._payload_off: + elif payload == self._payload_not_available: self._available = False self.hass.async_add_job(self.async_update_ha_state()) diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py index 133978a7bd8..cc97fe1c9c3 100644 --- a/tests/components/switch/test_mqtt.py +++ b/tests/components/switch/test_mqtt.py @@ -122,7 +122,9 @@ class TestSensorMQTT(unittest.TestCase): 'command_topic': 'command-topic', 'availability_topic': 'availability_topic', 'payload_on': 1, - 'payload_off': 0 + 'payload_off': 0, + 'payload_available': 1, + 'payload_not_available': 0 } }) @@ -153,3 +155,47 @@ class TestSensorMQTT(unittest.TestCase): state = self.hass.states.get('switch.test') self.assertEqual(STATE_ON, state.state) + + def test_custom_availability_payload(self): + """Test the availability payload.""" + assert setup_component(self.hass, switch.DOMAIN, { + switch.DOMAIN: { + 'platform': 'mqtt', + 'name': 'test', + 'state_topic': 'state-topic', + 'command_topic': 'command-topic', + 'availability_topic': 'availability_topic', + 'payload_on': 1, + 'payload_off': 0, + 'payload_available': 'online', + 'payload_not_available': 'offline' + } + }) + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability_topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_OFF, state.state) + self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE)) + + fire_mqtt_message(self.hass, 'availability_topic', 'offline') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'state-topic', '1') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_UNAVAILABLE, state.state) + + fire_mqtt_message(self.hass, 'availability_topic', 'online') + self.hass.block_till_done() + + state = self.hass.states.get('switch.test') + self.assertEqual(STATE_ON, state.state) From e7ce110dc6aa2ac48b49b50c54d4cf098ddc4604 Mon Sep 17 00:00:00 2001 From: mjj4791 Date: Wed, 16 Aug 2017 08:07:04 +0200 Subject: [PATCH 30/93] Buienradar newconditions (#8897) * new monitored conditions and support for new weathercard * new monitored conditions and support for new weathercard * minor changes --- homeassistant/components/sensor/buienradar.py | 177 +++++++++++++++--- .../components/weather/buienradar.py | 100 ++++++++-- requirements_all.txt | 2 +- 3 files changed, 241 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/sensor/buienradar.py b/homeassistant/components/sensor/buienradar.py index 755d88bb443..8961fa1dc74 100755 --- a/homeassistant/components/sensor/buienradar.py +++ b/homeassistant/components/sensor/buienradar.py @@ -23,12 +23,14 @@ from homeassistant.helpers.event import ( async_track_point_in_utc_time) from homeassistant.util import dt as dt_util -REQUIREMENTS = ['buienradar==0.8'] +REQUIREMENTS = ['buienradar==0.9'] _LOGGER = logging.getLogger(__name__) MEASURED_LABEL = 'Measured' TIMEFRAME_LABEL = 'Timeframe' +SYMBOL = 'symbol' + # Schedule next call after (minutes): SCHEDULE_OK = 10 # When an error occurred, new call after (minutes): @@ -38,6 +40,10 @@ SCHEDULE_NOK = 2 # Key: ['label', unit, icon] SENSOR_TYPES = { 'stationname': ['Stationname', None, None], + 'condition': ['Condition', None, None], + 'conditioncode': ['Condition code', None, None], + 'conditiondetailed': ['Detailed condition', None, None], + 'conditionexact': ['Full condition', None, None], 'symbol': ['Symbol', None, None], 'humidity': ['Humidity', '%', 'mdi:water-percent'], 'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer'], @@ -55,7 +61,67 @@ SENSOR_TYPES = { 'precipitation_forecast_average': ['Precipitation forecast average', 'mm/h', 'mdi:weather-pouring'], 'precipitation_forecast_total': ['Precipitation forecast total', - 'mm', 'mdi:weather-pouring'] + 'mm', 'mdi:weather-pouring'], + 'temperature_1d': ['Temperature 1d', TEMP_CELSIUS, 'mdi:thermometer'], + 'temperature_2d': ['Temperature 2d', TEMP_CELSIUS, 'mdi:thermometer'], + 'temperature_3d': ['Temperature 3d', TEMP_CELSIUS, 'mdi:thermometer'], + 'temperature_4d': ['Temperature 4d', TEMP_CELSIUS, 'mdi:thermometer'], + 'temperature_5d': ['Temperature 5d', TEMP_CELSIUS, 'mdi:thermometer'], + 'mintemp_1d': ['Minimum temperature 1d', TEMP_CELSIUS, 'mdi:thermometer'], + 'mintemp_2d': ['Minimum temperature 2d', TEMP_CELSIUS, 'mdi:thermometer'], + 'mintemp_3d': ['Minimum temperature 3d', TEMP_CELSIUS, 'mdi:thermometer'], + 'mintemp_4d': ['Minimum temperature 4d', TEMP_CELSIUS, 'mdi:thermometer'], + 'mintemp_5d': ['Minimum temperature 5d', TEMP_CELSIUS, 'mdi:thermometer'], + 'rain_1d': ['Rain 1d', 'mm', 'mdi:weather-pouring'], + 'rain_2d': ['Rain 2d', 'mm', 'mdi:weather-pouring'], + 'rain_3d': ['Rain 3d', 'mm', 'mdi:weather-pouring'], + 'rain_4d': ['Rain 4d', 'mm', 'mdi:weather-pouring'], + 'rain_5d': ['Rain 5d', 'mm', 'mdi:weather-pouring'], + 'snow_1d': ['Snow 1d', 'cm', 'mdi:snowflake'], + 'snow_2d': ['Snow 2d', 'cm', 'mdi:snowflake'], + 'snow_3d': ['Snow 3d', 'cm', 'mdi:snowflake'], + 'snow_4d': ['Snow 4d', 'cm', 'mdi:snowflake'], + 'snow_5d': ['Snow 5d', 'cm', 'mdi:snowflake'], + 'rainchance_1d': ['Rainchance 1d', '%', 'mdi:weather-pouring'], + 'rainchance_2d': ['Rainchance 2d', '%', 'mdi:weather-pouring'], + 'rainchance_3d': ['Rainchance 3d', '%', 'mdi:weather-pouring'], + 'rainchance_4d': ['Rainchance 4d', '%', 'mdi:weather-pouring'], + 'rainchance_5d': ['Rainchance 5d', '%', 'mdi:weather-pouring'], + 'sunchance_1d': ['Sunchance 1d', '%', 'mdi:weather-partlycloudy'], + 'sunchance_2d': ['Sunchance 2d', '%', 'mdi:weather-partlycloudy'], + 'sunchance_3d': ['Sunchance 3d', '%', 'mdi:weather-partlycloudy'], + 'sunchance_4d': ['Sunchance 4d', '%', 'mdi:weather-partlycloudy'], + 'sunchance_5d': ['Sunchance 5d', '%', 'mdi:weather-partlycloudy'], + 'windforce_1d': ['Wind force 1d', 'Bft', 'mdi:weather-windy'], + 'windforce_2d': ['Wind force 2d', 'Bft', 'mdi:weather-windy'], + 'windforce_3d': ['Wind force 3d', 'Bft', 'mdi:weather-windy'], + 'windforce_4d': ['Wind force 4d', 'Bft', 'mdi:weather-windy'], + 'windforce_5d': ['Wind force 5d', 'Bft', 'mdi:weather-windy'], + 'condition_1d': ['Condition 1d', None, None], + 'condition_2d': ['Condition 2d', None, None], + 'condition_3d': ['Condition 3d', None, None], + 'condition_4d': ['Condition 4d', None, None], + 'condition_5d': ['Condition 5d', None, None], + 'conditioncode_1d': ['Condition code 1d', None, None], + 'conditioncode_2d': ['Condition code 2d', None, None], + 'conditioncode_3d': ['Condition code 3d', None, None], + 'conditioncode_4d': ['Condition code 4d', None, None], + 'conditioncode_5d': ['Condition code 5d', None, None], + 'conditiondetailed_1d': ['Detailed condition 1d', None, None], + 'conditiondetailed_2d': ['Detailed condition 2d', None, None], + 'conditiondetailed_3d': ['Detailed condition 3d', None, None], + 'conditiondetailed_4d': ['Detailed condition 4d', None, None], + 'conditiondetailed_5d': ['Detailed condition 5d', None, None], + 'conditionexact_1d': ['Full condition 1d', None, None], + 'conditionexact_2d': ['Full condition 2d', None, None], + 'conditionexact_3d': ['Full condition 3d', None, None], + 'conditionexact_4d': ['Full condition 4d', None, None], + 'conditionexact_5d': ['Full condition 5d', None, None], + 'symbol_1d': ['Symbol 1d', None, None], + 'symbol_2d': ['Symbol 2d', None, None], + 'symbol_3d': ['Symbol 3d', None, None], + 'symbol_4d': ['Symbol 4d', None, None], + 'symbol_5d': ['Symbol 5d', None, None], } CONF_TIMEFRAME = 'timeframe' @@ -126,23 +192,86 @@ class BrSensor(Entity): def load_data(self, data): """Load the sensor with relevant data.""" # Find sensor - from buienradar.buienradar import (ATTRIBUTION, IMAGE, MEASURED, + from buienradar.buienradar import (ATTRIBUTION, CONDITION, CONDCODE, + DETAILED, EXACT, EXACTNL, FORECAST, + IMAGE, MEASURED, PRECIPITATION_FORECAST, STATIONNAME, - SYMBOL, TIMEFRAME) + TIMEFRAME) self._attribution = data.get(ATTRIBUTION) self._stationname = data.get(STATIONNAME) self._measured = data.get(MEASURED) - if self.type == SYMBOL: - # update weather symbol & status text - new_state = data.get(self.type) - img = data.get(IMAGE) - # pylint: disable=protected-access - if new_state != self._state or img != self._entity_picture: - self._state = new_state - self._entity_picture = img - return True + if self.type.endswith('_1d') or \ + self.type.endswith('_2d') or \ + self.type.endswith('_3d') or \ + self.type.endswith('_4d') or \ + self.type.endswith('_5d'): + + fcday = 0 + if self.type.endswith('_2d'): + fcday = 1 + if self.type.endswith('_3d'): + fcday = 2 + if self.type.endswith('_4d'): + fcday = 3 + if self.type.endswith('_5d'): + fcday = 4 + + # update all other sensors + if self.type.startswith(SYMBOL) or self.type.startswith(CONDITION): + condition = data.get(FORECAST)[fcday].get(CONDITION) + if condition: + new_state = condition.get(CONDITION, None) + if self.type.startswith(SYMBOL): + new_state = condition.get(EXACTNL, None) + if self.type.startswith('conditioncode'): + new_state = condition.get(CONDCODE, None) + if self.type.startswith('conditiondetailed'): + new_state = condition.get(DETAILED, None) + if self.type.startswith('conditionexact'): + new_state = condition.get(EXACT, None) + + img = condition.get(IMAGE, None) + + if new_state != self._state or img != self._entity_picture: + self._state = new_state + self._entity_picture = img + return True + return False + else: + new_state = data.get(FORECAST)[fcday].get(self.type[:-3]) + + if new_state != self._state: + self._state = new_state + return True + return False + + return False + + if self.type == SYMBOL or self.type.startswith(CONDITION): + # update weather symbol & status text + condition = data.get(CONDITION, None) + if condition: + if self.type == SYMBOL: + new_state = condition.get(EXACTNL, None) + if self.type == CONDITION: + new_state = condition.get(CONDITION, None) + if self.type == 'conditioncode': + new_state = condition.get(CONDCODE, None) + if self.type == 'conditiondetailed': + new_state = condition.get(DETAILED, None) + if self.type == 'conditionexact': + new_state = condition.get(EXACT, None) + + img = condition.get(IMAGE, None) + + # pylint: disable=protected-access + if new_state != self._state or img != self._entity_picture: + self._state = new_state + self._entity_picture = img + return True + return False if self.type.startswith(PRECIPITATION_FORECAST): @@ -187,11 +316,6 @@ class BrSensor(Entity): @property def entity_picture(self): """Weather symbol if type is symbol.""" - from buienradar.buienradar import SYMBOL - - if self.type != SYMBOL: - return None - return self._entity_picture @property @@ -360,8 +484,8 @@ class BrData(object): @property def condition(self): """Return the condition.""" - from buienradar.buienradar import SYMBOL - return self.data.get(SYMBOL) + from buienradar.buienradar import CONDITION + return self.data.get(CONDITION) @property def temperature(self): @@ -390,6 +514,15 @@ class BrData(object): except (ValueError, TypeError): return None + @property + def visibility(self): + """Return the visibility, or None.""" + from buienradar.buienradar import VISIBILITY + try: + return int(self.data.get(VISIBILITY)) + except (ValueError, TypeError): + return None + @property def wind_speed(self): """Return the windspeed, or None.""" @@ -402,9 +535,9 @@ class BrData(object): @property def wind_bearing(self): """Return the wind bearing, or None.""" - from buienradar.buienradar import WINDDIRECTION + from buienradar.buienradar import WINDAZIMUTH try: - return int(self.data.get(WINDDIRECTION)) + return int(self.data.get(WINDAZIMUTH)) except (ValueError, TypeError): return None diff --git a/homeassistant/components/weather/buienradar.py b/homeassistant/components/weather/buienradar.py index bca50182a16..f37914b3b0f 100755 --- a/homeassistant/components/weather/buienradar.py +++ b/homeassistant/components/weather/buienradar.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/weather.buienradar/ import logging import asyncio from homeassistant.components.weather import ( - WeatherEntity, PLATFORM_SCHEMA) + WeatherEntity, PLATFORM_SCHEMA, ATTR_FORECAST_TEMP, ATTR_FORECAST_TIME) from homeassistant.const import \ CONF_NAME, TEMP_CELSIUS, CONF_LATITUDE, CONF_LONGITUDE from homeassistant.helpers import config_validation as cv @@ -16,14 +16,37 @@ from homeassistant.components.sensor.buienradar import ( BrData) import voluptuous as vol -REQUIREMENTS = ['buienradar==0.8'] +REQUIREMENTS = ['buienradar==0.9'] _LOGGER = logging.getLogger(__name__) +DATA_CONDITION = 'buienradar_condition' + DEFAULT_TIMEFRAME = 60 CONF_FORECAST = 'forecast' +ATTR_FORECAST_CONDITION = 'condition' +ATTR_FORECAST_TEMP_LOW = 'templow' + + +CONDITION_CLASSES = { + 'cloudy': ['c', 'p'], + 'fog': ['d', 'n'], + 'hail': [], + 'lightning': ['g'], + 'lightning-rainy': ['s'], + 'partlycloudy': ['b', 'j', 'o', 'r'], + 'pouring': ['l', 'q'], + 'rainy': ['f', 'h', 'k', 'm'], + 'snowy': ['u', 'i', 'v', 't'], + 'snowy-rainy': ['w'], + 'sunny': ['a'], + 'windy': [], + 'windy-variant': [], + 'exceptional': [], +} + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_LATITUDE): cv.latitude, @@ -50,8 +73,16 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): # create weather device: _LOGGER.debug("Initializing buienradar weather: coordinates %s", coordinates) - async_add_devices([BrWeather(data, config.get(CONF_FORECAST, True), - config.get(CONF_NAME, None))]) + + # create condition helper + if DATA_CONDITION not in hass.data: + cond_keys = [str(chr(x)) for x in range(97, 123)] + hass.data[DATA_CONDITION] = dict.fromkeys(cond_keys) + for cond, condlst in CONDITION_CLASSES.items(): + for condi in condlst: + hass.data[DATA_CONDITION][condi] = cond + + async_add_devices([BrWeather(data, config)]) # schedule the first update in 1 minute from now: yield from data.schedule_update(1) @@ -60,10 +91,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): class BrWeather(WeatherEntity): """Representation of a weather condition.""" - def __init__(self, data, forecast, stationname=None): + def __init__(self, data, config): """Initialise the platform with a data instance and station name.""" - self._stationname = stationname - self._forecast = forecast + self._stationname = config.get(CONF_NAME, None) + self._forecast = config.get(CONF_FORECAST) self._data = data @property @@ -79,17 +110,32 @@ class BrWeather(WeatherEntity): @property def condition(self): - """Return the name of the sensor.""" - return self._data.condition + """Return the current condition.""" + from buienradar.buienradar import (CONDCODE) + if self._data and self._data.condition: + ccode = self._data.condition.get(CONDCODE) + if ccode: + conditions = self.hass.data.get(DATA_CONDITION) + if conditions: + return conditions.get(ccode) + + @property + def entity_picture(self): + """Return the entity picture to use in the frontend, if any.""" + from buienradar.buienradar import (IMAGE) + + if self._data and self._data.condition: + return self._data.condition.get(IMAGE, None) + return None @property def temperature(self): - """Return the name of the sensor.""" + """Return the current temperature.""" return self._data.temperature @property def pressure(self): - """Return the name of the sensor.""" + """Return the current pressure.""" return self._data.pressure @property @@ -97,14 +143,19 @@ class BrWeather(WeatherEntity): """Return the name of the sensor.""" return self._data.humidity + @property + def visibility(self): + """Return the current visibility.""" + return self._data.visibility + @property def wind_speed(self): - """Return the name of the sensor.""" + """Return the current windspeed.""" return self._data.wind_speed @property def wind_bearing(self): - """Return the name of the sensor.""" + """Return the current wind bearing (degrees).""" return self._data.wind_bearing @property @@ -114,6 +165,25 @@ class BrWeather(WeatherEntity): @property def forecast(self): - """Return the forecast.""" + """Return the forecast array.""" + from buienradar.buienradar import (CONDITION, CONDCODE, DATETIME, + MIN_TEMP, MAX_TEMP) + if self._forecast: - return self._data.forecast + fcdata_out = [] + cond = self.hass.data[DATA_CONDITION] + if self._data.forecast: + for data_in in self._data.forecast: + # remap keys from external library to + # keys understood by the weather component: + data_out = {} + condcode = data_in.get(CONDITION, []).get(CONDCODE) + + data_out[ATTR_FORECAST_TIME] = data_in.get(DATETIME) + data_out[ATTR_FORECAST_CONDITION] = cond[condcode] + data_out[ATTR_FORECAST_TEMP_LOW] = data_in.get(MIN_TEMP) + data_out[ATTR_FORECAST_TEMP] = data_in.get(MAX_TEMP) + + fcdata_out.append(data_out) + + return fcdata_out diff --git a/requirements_all.txt b/requirements_all.txt index 0d8d5f55843..59ca0a67bb1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -128,7 +128,7 @@ broadlink==0.5 # homeassistant.components.sensor.buienradar # homeassistant.components.weather.buienradar -buienradar==0.8 +buienradar==0.9 # homeassistant.components.notify.ciscospark ciscosparkapi==0.4.2 From f1142638455dd7f4409c4971ae47ba559708189f Mon Sep 17 00:00:00 2001 From: karlkar Date: Wed, 16 Aug 2017 09:29:42 +0200 Subject: [PATCH 31/93] Pushbullet, fix multiple messages sent when url param is set (#9006) --- homeassistant/components/notify/pushbullet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index 8ac2bd06dad..bbc7c18ffab 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -91,7 +91,7 @@ class PushBulletNotificationService(BaseNotificationService): # Backward compatibility, notify all devices in own account if url: self.pushbullet.push_link(title, url, body=message) - if filepath and self.hass.config.is_allowed_path(filepath): + elif filepath and self.hass.config.is_allowed_path(filepath): with open(filepath, "rb") as fileh: filedata = self.pushbullet.upload_file(fileh, filepath) self.pushbullet.push_file(title=title, body=message, From 95663f8126f13d4ec39affcc6f3d5048ead5e63d Mon Sep 17 00:00:00 2001 From: Dan Cinnamon Date: Wed, 16 Aug 2017 05:08:15 -0500 Subject: [PATCH 32/93] =?UTF-8?q?Update=20to=20pyenvisalink=202.2,=20and?= =?UTF-8?q?=20remove=20range=20validation=20on=20zonedump=20i=E2=80=A6=20(?= =?UTF-8?q?#8981)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update to pyenvisalink 2.2, and remove range validation on zonedump interval. * Keep using default timer dump variable, only remove minimum check. * Fix lint issue * Indentation issue --- homeassistant/components/envisalink.py | 8 ++++---- requirements_all.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/envisalink.py b/homeassistant/components/envisalink.py index 87b6163282a..5ffd97ef0e3 100644 --- a/homeassistant/components/envisalink.py +++ b/homeassistant/components/envisalink.py @@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.discovery import async_load_platform from homeassistant.helpers.dispatcher import async_dispatcher_send -REQUIREMENTS = ['pyenvisalink==2.1'] +REQUIREMENTS = ['pyenvisalink==2.2'] _LOGGER = logging.getLogger(__name__) @@ -74,9 +74,9 @@ CONFIG_SCHEMA = vol.Schema({ vol.All(vol.Coerce(int), vol.Range(min=3, max=4)), vol.Optional(CONF_EVL_KEEPALIVE, default=DEFAULT_KEEPALIVE): vol.All(vol.Coerce(int), vol.Range(min=15)), - vol.Optional(CONF_ZONEDUMP_INTERVAL, - default=DEFAULT_ZONEDUMP_INTERVAL): - vol.All(vol.Coerce(int), vol.Range(min=15)), + vol.Optional( + CONF_ZONEDUMP_INTERVAL, + default=DEFAULT_ZONEDUMP_INTERVAL): vol.Coerce(int), }), }, extra=vol.ALLOW_EXTRA) diff --git a/requirements_all.txt b/requirements_all.txt index 59ca0a67bb1..0a14bc0d38f 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -576,7 +576,7 @@ pyeight==0.0.7 pyemby==1.4 # homeassistant.components.envisalink -pyenvisalink==2.1 +pyenvisalink==2.2 # homeassistant.components.sensor.fido pyfido==1.0.1 From b75ce4f1b2efde2152b8960e5edf090e0916f807 Mon Sep 17 00:00:00 2001 From: Michael Hertig Date: Wed, 16 Aug 2017 21:28:51 +0200 Subject: [PATCH 33/93] Fix #9010 - Swiss Public Transportation shows departure time in the past (#9011) --- homeassistant/components/sensor/swiss_public_transport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/swiss_public_transport.py b/homeassistant/components/sensor/swiss_public_transport.py index aa0be36b075..0febd8c95bc 100644 --- a/homeassistant/components/sensor/swiss_public_transport.py +++ b/homeassistant/components/sensor/swiss_public_transport.py @@ -136,7 +136,7 @@ class PublicTransportData(object): 'fields[]=connections/from/departureTimestamp/&' + 'fields[]=connections/', timeout=10) - connections = response.json()['connections'][:2] + connections = response.json()['connections'][1:3] try: self.times = [ From 3765f882c771979e6f6356b93ed9ed5e47586ad9 Mon Sep 17 00:00:00 2001 From: BioSehnsucht Date: Wed, 16 Aug 2017 18:26:30 -0500 Subject: [PATCH 34/93] Add HipChat notify service. (#8918) * Add HipChat notify service. * Change HipChat notify service to use python-simple-hipchat-v2. * Change HipChat notify service to use hipnotify * Change HipChat notify service to remove redundant validation --- .coveragerc | 1 + homeassistant/components/notify/hipchat.py | 102 +++++++++++++++++++++ requirements_all.txt | 3 + 3 files changed, 106 insertions(+) create mode 100644 homeassistant/components/notify/hipchat.py diff --git a/.coveragerc b/.coveragerc index 3cbb942508b..8297f7823d5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -384,6 +384,7 @@ omit = homeassistant/components/notify/free_mobile.py homeassistant/components/notify/gntp.py homeassistant/components/notify/group.py + homeassistant/components/notify/hipchat.py homeassistant/components/notify/instapush.py homeassistant/components/notify/kodi.py homeassistant/components/notify/lannouncer.py diff --git a/homeassistant/components/notify/hipchat.py b/homeassistant/components/notify/hipchat.py new file mode 100644 index 00000000000..5d69ce97d61 --- /dev/null +++ b/homeassistant/components/notify/hipchat.py @@ -0,0 +1,102 @@ +""" +HipChat platform for notify component. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/notify.hipchat/ +""" +import logging + +import voluptuous as vol + +from homeassistant.components.notify import ( + ATTR_TARGET, ATTR_DATA, + PLATFORM_SCHEMA, BaseNotificationService) +from homeassistant.const import ( + CONF_TOKEN) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['hipnotify==1.0.8'] + +_LOGGER = logging.getLogger(__name__) + +CONF_COLOR = 'color' +CONF_ROOM = 'room' +CONF_NOTIFY = 'notify' +CONF_FORMAT = 'format' +CONF_HOST = 'host' + +VALID_COLORS = {'yellow', 'green', 'red', 'purple', 'gray', 'random'} +VALID_FORMATS = {'text', 'html'} + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_TOKEN): cv.string, + vol.Required(CONF_ROOM): vol.Coerce(int), + vol.Optional(CONF_COLOR, default='yellow'): vol.In(VALID_COLORS), + vol.Optional(CONF_NOTIFY, default=False): cv.boolean, + vol.Optional(CONF_FORMAT, default='text'): vol.In(VALID_FORMATS), + vol.Optional(CONF_HOST, default='https://api.hipchat.com/'): cv.string, +}) + + +def get_service(hass, config, discovery_info=None): + """Get the HipChat notification service.""" + return HipchatNotificationService( + config[CONF_TOKEN], + config[CONF_ROOM], + config[CONF_COLOR], + config[CONF_NOTIFY], + config[CONF_FORMAT], + config[CONF_HOST]) + + +class HipchatNotificationService(BaseNotificationService): + """Implement the notification service for HipChat.""" + + def __init__(self, token, default_room, default_color, default_notify, + default_format, host): + """Initialize the service.""" + self._token = token + self._default_room = default_room + self._default_color = default_color + self._default_notify = default_notify + self._default_format = default_format + self._host = host + + self._rooms = {} + self._get_room(self._default_room) + + def _get_room(self, room): + """Get Room object, creating it if necessary.""" + from hipnotify import Room + if room not in self._rooms: + self._rooms[room] = Room(token=self._token, + room_id=room, + endpoint_url=self._host) + return self._rooms[room] + + def send_message(self, message="", **kwargs): + """Send a message.""" + color = self._default_color + notify = self._default_notify + message_format = self._default_format + + if kwargs.get(ATTR_DATA) is not None: + data = kwargs.get(ATTR_DATA) + if ((data.get(CONF_COLOR) is not None) + and (data.get(CONF_COLOR) in VALID_COLORS)): + color = data.get(CONF_COLOR) + if ((data.get(CONF_NOTIFY) is not None) + and isinstance(data.get(CONF_NOTIFY), bool)): + notify = data.get(CONF_NOTIFY) + if ((data.get(CONF_FORMAT) is not None) + and (data.get(CONF_FORMAT) in VALID_FORMATS)): + message_format = data.get(CONF_FORMAT) + + targets = kwargs.get(ATTR_TARGET, [self._default_room]) + + for target in targets: + room = self._get_room(target) + room.notify(msg=message, + color=color, + notify=notify, + message_format=message_format) diff --git a/requirements_all.txt b/requirements_all.txt index 0a14bc0d38f..34a1482508b 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -283,6 +283,9 @@ heatmiserV3==0.9.1 # homeassistant.components.switch.hikvisioncam hikvision==0.4 +# homeassistant.components.notify.hipchat +hipnotify==1.0.8 + # homeassistant.components.binary_sensor.workday holidays==0.8.1 From 55234a7fa32c6229df2b51debe7e8ce955e4be38 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 17 Aug 2017 00:51:03 -0400 Subject: [PATCH 35/93] Update onkyo-eiscp to 1.2.3 (#9019) --- homeassistant/components/media_player/onkyo.py | 2 +- requirements_all.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/onkyo.py b/homeassistant/components/media_player/onkyo.py index 927de799ae9..c49df1405d5 100644 --- a/homeassistant/components/media_player/onkyo.py +++ b/homeassistant/components/media_player/onkyo.py @@ -14,7 +14,7 @@ from homeassistant.components.media_player import ( from homeassistant.const import (STATE_OFF, STATE_ON, CONF_HOST, CONF_NAME) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['onkyo-eiscp==1.1'] +REQUIREMENTS = ['onkyo-eiscp==1.2.3'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 34a1482508b..9cfaed98866 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -435,7 +435,7 @@ oauth2client==4.0.0 oemthermostat==1.1 # homeassistant.components.media_player.onkyo -onkyo-eiscp==1.1 +onkyo-eiscp==1.2.3 # homeassistant.components.camera.onvif onvif-py3==0.1.3 From 427d7ee1fc430e94041a5888b47ff8e4867f41ba Mon Sep 17 00:00:00 2001 From: Tom Matheussen Date: Thu, 17 Aug 2017 22:39:20 +0200 Subject: [PATCH 36/93] Check if album image(s) exist in spotify (#9024) * Check if album image(s) exist in spotify * Actually set the image to None * Simplified using ternary operator --- homeassistant/components/media_player/spotify.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/spotify.py b/homeassistant/components/media_player/spotify.py index 80d18b8eea8..239b13a6292 100644 --- a/homeassistant/components/media_player/spotify.py +++ b/homeassistant/components/media_player/spotify.py @@ -185,7 +185,8 @@ class SpotifyMediaPlayer(MediaPlayerDevice): self._artist = ', '.join([artist.get('name') for artist in item.get('artists')]) self._uri = current.get('uri') - self._image_url = item.get('album').get('images')[0].get('url') + images = item.get('album').get('images') + self._image_url = images[0].get('url') if images else None # Playing state self._state = STATE_PAUSED if current.get('is_playing'): From c278209c7b70024ae8ad4780f6f3fd7d39c8849d Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 18 Aug 2017 00:51:52 +0200 Subject: [PATCH 37/93] Update ffmpeg to 1.7 to fix severals problems (#9029) * Update ffmpeg to 1.7 to fix severals problems * Update ffmpeg.py * Update requirements_test_all.txt --- homeassistant/components/ffmpeg.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/ffmpeg.py b/homeassistant/components/ffmpeg.py index 45bd651ad95..887d07e5855 100644 --- a/homeassistant/components/ffmpeg.py +++ b/homeassistant/components/ffmpeg.py @@ -19,7 +19,7 @@ from homeassistant.helpers.dispatcher import ( import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['ha-ffmpeg==1.5'] +REQUIREMENTS = ['ha-ffmpeg==1.7'] DOMAIN = 'ffmpeg' diff --git a/requirements_all.txt b/requirements_all.txt index 9cfaed98866..0bb785cc0ef 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -269,7 +269,7 @@ gps3==0.33.3 gstreamer-player==1.1.0 # homeassistant.components.ffmpeg -ha-ffmpeg==1.5 +ha-ffmpeg==1.7 # homeassistant.components.media_player.philips_js ha-philipsjs==0.0.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 68ce17c04f2..74717aa7d7b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -49,7 +49,7 @@ fuzzywuzzy==0.15.1 gTTS-token==1.1.1 # homeassistant.components.ffmpeg -ha-ffmpeg==1.5 +ha-ffmpeg==1.7 # homeassistant.components.mqtt.server hbmqtt==0.8 From b282167f264535971247ca9dc639af7262499059 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 17 Aug 2017 23:19:35 -0700 Subject: [PATCH 38/93] Add state_with_unit property to state objects in templates (#9014) * Wrap state objects in templates * Fix tests * Fix bugs * Lint * Remove invalid state warning --- homeassistant/helpers/template.py | 50 +++++++++++++++++++++++++++---- tests/helpers/test_template.py | 36 ++++++++++++++++++++-- 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index 6c74c49424e..aa6ca186a8e 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -10,7 +10,8 @@ from jinja2 import contextfilter from jinja2.sandbox import ImmutableSandboxedEnvironment from homeassistant.const import ( - STATE_UNKNOWN, ATTR_LATITUDE, ATTR_LONGITUDE, MATCH_ALL) + STATE_UNKNOWN, ATTR_LATITUDE, ATTR_LONGITUDE, MATCH_ALL, + ATTR_UNIT_OF_MEASUREMENT) from homeassistant.core import State from homeassistant.exceptions import TemplateError from homeassistant.helpers import location as loc_helper @@ -181,8 +182,10 @@ class AllStates(object): def __iter__(self): """Return all states.""" - return iter(sorted(self._hass.states.async_all(), - key=lambda state: state.entity_id)) + return iter( + _wrap_state(state) for state in + sorted(self._hass.states.async_all(), + key=lambda state: state.entity_id)) def __call__(self, entity_id): """Return the states.""" @@ -200,7 +203,8 @@ class DomainStates(object): def __getattr__(self, name): """Return the states.""" - return self._hass.states.get('{}.{}'.format(self._domain, name)) + return _wrap_state( + self._hass.states.get('{}.{}'.format(self._domain, name))) def __iter__(self): """Return the iteration over all the states.""" @@ -210,6 +214,42 @@ class DomainStates(object): key=lambda state: state.entity_id)) +class TemplateState(State): + """Class to represent a state object in a template.""" + + # Inheritance is done so functions that check against State keep working + # pylint: disable=super-init-not-called + def __init__(self, state): + """Initialize template state.""" + self._state = state + + @property + def state_with_unit(self): + """Return the state concatenated with the unit if available.""" + state = object.__getattribute__(self, '_state') + unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + if unit is None: + return state.state + return "{} {}".format(state.state, unit) + + def __getattribute__(self, name): + """Return an attribute of the state.""" + if name in TemplateState.__dict__: + return object.__getattribute__(self, name) + else: + return getattr(object.__getattribute__(self, '_state'), name) + + def __repr__(self): + """Representation of Template State.""" + rep = object.__getattribute__(self, '_state').__repr__() + return '