From a046e2ed2052421967208a27ee98993e0c82b843 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Sat, 17 Feb 2018 13:39:30 -0800 Subject: [PATCH 1/8] Version bump to 0.63.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 917292648c9..eb90a19c778 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 63 -PATCH_VERSION = '2' +PATCH_VERSION = '3' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 4, 2) From de7a4b9501eb8cb0eff678d30427b370ef208adb Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Wed, 14 Feb 2018 00:17:47 +0100 Subject: [PATCH 2/8] python-miio version bumped. (Closes: #12389, Closes: #12298) (#12392) --- homeassistant/components/fan/xiaomi_miio.py | 2 +- homeassistant/components/light/xiaomi_miio.py | 2 +- homeassistant/components/remote/xiaomi_miio.py | 2 +- homeassistant/components/switch/xiaomi_miio.py | 2 +- homeassistant/components/vacuum/xiaomi_miio.py | 2 +- requirements_all.txt | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/fan/xiaomi_miio.py b/homeassistant/components/fan/xiaomi_miio.py index 942aff4ec57..6938926a19b 100644 --- a/homeassistant/components/fan/xiaomi_miio.py +++ b/homeassistant/components/fan/xiaomi_miio.py @@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) -REQUIREMENTS = ['python-miio==0.3.5'] +REQUIREMENTS = ['python-miio==0.3.6'] ATTR_TEMPERATURE = 'temperature' ATTR_HUMIDITY = 'humidity' diff --git a/homeassistant/components/light/xiaomi_miio.py b/homeassistant/components/light/xiaomi_miio.py index a3c5fa9f62e..f2d327575b1 100644 --- a/homeassistant/components/light/xiaomi_miio.py +++ b/homeassistant/components/light/xiaomi_miio.py @@ -30,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) -REQUIREMENTS = ['python-miio==0.3.5'] +REQUIREMENTS = ['python-miio==0.3.6'] # The light does not accept cct values < 1 CCT_MIN = 1 diff --git a/homeassistant/components/remote/xiaomi_miio.py b/homeassistant/components/remote/xiaomi_miio.py index 32fde57b61a..aa9f0c95a7c 100644 --- a/homeassistant/components/remote/xiaomi_miio.py +++ b/homeassistant/components/remote/xiaomi_miio.py @@ -21,7 +21,7 @@ from homeassistant.const import ( import homeassistant.helpers.config_validation as cv from homeassistant.util.dt import utcnow -REQUIREMENTS = ['python-miio==0.3.5'] +REQUIREMENTS = ['python-miio==0.3.6'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/switch/xiaomi_miio.py b/homeassistant/components/switch/xiaomi_miio.py index 87871079a9c..ad71b3944cf 100644 --- a/homeassistant/components/switch/xiaomi_miio.py +++ b/homeassistant/components/switch/xiaomi_miio.py @@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) -REQUIREMENTS = ['python-miio==0.3.5'] +REQUIREMENTS = ['python-miio==0.3.6'] ATTR_POWER = 'power' ATTR_TEMPERATURE = 'temperature' diff --git a/homeassistant/components/vacuum/xiaomi_miio.py b/homeassistant/components/vacuum/xiaomi_miio.py index d64f7a754ee..bcfb3b6738e 100644 --- a/homeassistant/components/vacuum/xiaomi_miio.py +++ b/homeassistant/components/vacuum/xiaomi_miio.py @@ -19,7 +19,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN, STATE_OFF, STATE_ON) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-miio==0.3.5'] +REQUIREMENTS = ['python-miio==0.3.6'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 6c1282ce8a7..19e6f5cb6e6 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -922,7 +922,7 @@ python-juicenet==0.0.5 # homeassistant.components.remote.xiaomi_miio # homeassistant.components.switch.xiaomi_miio # homeassistant.components.vacuum.xiaomi_miio -python-miio==0.3.5 +python-miio==0.3.6 # homeassistant.components.media_player.mpd python-mpd2==0.5.5 From c9300a98e00d9849e89850ea86c9ed68b89397db Mon Sep 17 00:00:00 2001 From: Greg Laabs Date: Wed, 14 Feb 2018 21:58:49 -0800 Subject: [PATCH 3/8] Fixed 3 small issues in isy994 component (#12421) 1. FanLincs have two nodes: one light and one fan motor. In order for each node to get detected as different Hass entity types, I removed the device-type check for FanLinc. The logic will now fall back on the uom checks which should work just fine. (An alternative approach here would be to special case FanLincs and handle them directly - but seeing as the newer 5.x ISY firmware already handles this much better using NodeDefs, I think this quick and dirty approach is fine for the older firmware.) Fixes #12030 2. Some non-dimming switches were appearing as `light`s in Hass due to an duplicate NodeDef being in the light domain filter. Removed! Fixes #12340 3. The `unqiue_id` property was throwing an error for certain entity types that don't have an `_id` property from the ISY. This issue has always been present, but was exposed by the entity registry which seems to be the first thing to actually try reading the `unique_id` property from the isy994 component. --- homeassistant/components/isy994.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py index d85883e472a..df93bf51250 100644 --- a/homeassistant/components/isy994.py +++ b/homeassistant/components/isy994.py @@ -83,9 +83,9 @@ NODE_FILTERS = { }, 'fan': { 'uom': [], - 'states': ['on', 'off', 'low', 'medium', 'high'], + 'states': ['off', 'low', 'medium', 'high'], 'node_def_id': ['FanLincMotor'], - 'insteon_type': ['1.46.'] + 'insteon_type': [] }, 'cover': { 'uom': ['97'], @@ -99,7 +99,7 @@ NODE_FILTERS = { 'node_def_id': ['DimmerLampSwitch', 'DimmerLampSwitch_ADV', 'DimmerSwitchOnly', 'DimmerSwitchOnly_ADV', 'DimmerLampOnly', 'BallastRelayLampSwitch', - 'BallastRelayLampSwitch_ADV', 'RelayLampSwitch', + 'BallastRelayLampSwitch_ADV', 'RemoteLinc2', 'RemoteLinc2_ADV'], 'insteon_type': ['1.'] }, @@ -431,7 +431,10 @@ class ISYDevice(Entity): def unique_id(self) -> str: """Get the unique identifier of the device.""" # pylint: disable=protected-access - return self._node._id + if hasattr(self._node, '_id'): + return self._node._id + + return None @property def name(self) -> str: From 6838de37866920428c56b52117af2f3741183894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Sat, 17 Feb 2018 22:33:41 +0100 Subject: [PATCH 4/8] Reduce the load on met.no servers, yr.no sensor (#12435) * Spread the load more for the yr.no sensor * Spread the load more for the yr.no sensor * Spread the load more for the yr.no sensor * Spread the load more for the yr.no sensor * Spread the load more for the yr.no sensor * Spread the load more for the yr.no sensor * Spread the load more for the yr.no sensor * Spread the load more for the yr.no sensor * Spread the load more for the yr.no sensor * fix comment * Spread the load more for the yr.no sensor * Spread the load more for the yr.no sensor * Spread the load more for the yr.no sensor * Update yr.py --- homeassistant/components/sensor/yr.py | 80 +++++++++++++-------------- 1 file changed, 38 insertions(+), 42 deletions(-) diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index 71a4e60e8a6..3f9579c1f13 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -7,7 +7,6 @@ https://home-assistant.io/components/sensor.yr/ import asyncio import logging -from datetime import timedelta from random import randrange from xml.parsers.expat import ExpatError @@ -22,16 +21,17 @@ from homeassistant.const import ( ATTR_ATTRIBUTION, CONF_NAME) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import Entity -from homeassistant.helpers.event import ( - async_track_point_in_utc_time, async_track_utc_time_change) +from homeassistant.helpers.event import (async_track_utc_time_change, + async_call_later) from homeassistant.util import dt as dt_util REQUIREMENTS = ['xmltodict==0.11.0'] _LOGGER = logging.getLogger(__name__) -CONF_ATTRIBUTION = "Weather forecast from yr.no, delivered by the Norwegian " \ - "Meteorological Institute and the NRK." +CONF_ATTRIBUTION = "Weather forecast from met.no, delivered " \ + "by the Norwegian Meteorological Institute." +# https://api.met.no/license_data.html SENSOR_TYPES = { 'symbol': ['Symbol', None], @@ -91,11 +91,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): async_add_devices(dev) weather = YrData(hass, coordinates, forecast, dev) - # Update weather on the hour, spread seconds - async_track_utc_time_change( - hass, weather.async_update, minute=randrange(1, 10), - second=randrange(0, 59)) - yield from weather.async_update() + async_track_utc_time_change(hass, weather.updating_devices, minute=31) + yield from weather.fetching_data() class YrSensor(Entity): @@ -153,50 +150,49 @@ class YrData(object): self._url = 'https://aa015h6buqvih86i1.api.met.no/'\ 'weatherapi/locationforecast/1.9/' self._urlparams = coordinates - self._nextrun = None self._forecast = forecast self.devices = devices self.data = {} self.hass = hass @asyncio.coroutine - def async_update(self, *_): + def fetching_data(self, *_): """Get the latest data from yr.no.""" import xmltodict def try_again(err: str): - """Retry in 15 minutes.""" - _LOGGER.warning("Retrying in 15 minutes: %s", err) - self._nextrun = None - nxt = dt_util.utcnow() + timedelta(minutes=15) - if nxt.minute >= 15: - async_track_point_in_utc_time(self.hass, self.async_update, - nxt) - - if self._nextrun is None or dt_util.utcnow() >= self._nextrun: - try: - websession = async_get_clientsession(self.hass) - with async_timeout.timeout(10, loop=self.hass.loop): - resp = yield from websession.get( - self._url, params=self._urlparams) - if resp.status != 200: - try_again('{} returned {}'.format(resp.url, resp.status)) - return - text = yield from resp.text() - - except (asyncio.TimeoutError, aiohttp.ClientError) as err: - try_again(err) + """Retry in 15 to 20 minutes.""" + minutes = 15 + randrange(6) + _LOGGER.error("Retrying in %i minutes: %s", minutes, err) + async_call_later(self.hass, minutes*60, self.fetching_data) + try: + websession = async_get_clientsession(self.hass) + with async_timeout.timeout(10, loop=self.hass.loop): + resp = yield from websession.get( + self._url, params=self._urlparams) + if resp.status != 200: + try_again('{} returned {}'.format(resp.url, resp.status)) return + text = yield from resp.text() - try: - self.data = xmltodict.parse(text)['weatherdata'] - model = self.data['meta']['model'] - if '@nextrun' not in model: - model = model[0] - self._nextrun = dt_util.parse_datetime(model['@nextrun']) - except (ExpatError, IndexError) as err: - try_again(err) - return + except (asyncio.TimeoutError, aiohttp.ClientError) as err: + try_again(err) + return + + try: + self.data = xmltodict.parse(text)['weatherdata'] + except (ExpatError, IndexError) as err: + try_again(err) + return + + yield from self.updating_devices() + async_call_later(self.hass, 60*60, self.fetching_data) + + @asyncio.coroutine + def updating_devices(self, *_): + """Find the current data from self.data.""" + if not self.data: + return now = dt_util.utcnow() forecast_time = now + dt_util.dt.timedelta(hours=self._forecast) From b651cdd8f2a627ee56e1c0372787202c7ac1275a Mon Sep 17 00:00:00 2001 From: Ryan McLean Date: Fri, 16 Feb 2018 19:51:19 +0000 Subject: [PATCH 5/8] Fix for contentRating error (#12445) * Fix for contentRating * Use getattr instead of hasattr * Lint --- homeassistant/components/media_player/plex.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/media_player/plex.py b/homeassistant/components/media_player/plex.py index b2a89341cf0..dc38bb17dd3 100644 --- a/homeassistant/components/media_player/plex.py +++ b/homeassistant/components/media_player/plex.py @@ -370,7 +370,8 @@ class PlexClient(MediaPlayerDevice): self._is_player_available = False self._media_position = self._session.viewOffset self._media_content_id = self._session.ratingKey - self._media_content_rating = self._session.contentRating + self._media_content_rating = getattr( + self._session, 'contentRating', None) self._set_player_state() From fcf97524a2dfbe5cb76c8f301ddab68f0d8275db Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 16 Feb 2018 19:32:11 +0200 Subject: [PATCH 6/8] Fix light template to return brightness as int (#12447) --- homeassistant/components/light/template.py | 3 +-- tests/components/light/test_template.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/light/template.py b/homeassistant/components/light/template.py index d4f2b93e6b5..cfd050f54f2 100644 --- a/homeassistant/components/light/template.py +++ b/homeassistant/components/light/template.py @@ -237,7 +237,6 @@ class LightTemplate(Light): @asyncio.coroutine def async_update(self): """Update the state from the template.""" - print("ASYNC UPDATE") if self._template is not None: try: state = self._template.async_render().lower() @@ -262,7 +261,7 @@ class LightTemplate(Light): self._state = None if 0 <= int(brightness) <= 255: - self._brightness = brightness + self._brightness = int(brightness) else: _LOGGER.error( 'Received invalid brightness : %s' + diff --git a/tests/components/light/test_template.py b/tests/components/light/test_template.py index aaac0617590..2d45ad1bf94 100644 --- a/tests/components/light/test_template.py +++ b/tests/components/light/test_template.py @@ -586,7 +586,7 @@ class TestTemplateLight: state = self.hass.states.get('light.test_template_light') assert state is not None - assert state.attributes.get('brightness') == '42' + assert state.attributes.get('brightness') == 42 def test_friendly_name(self): """Test the accessibility of the friendly_name attribute.""" From bf67d0e650541232bbaa66aa459fbbd9bf1a5cd4 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Fri, 16 Feb 2018 05:22:57 +0100 Subject: [PATCH 7/8] Optimize recorder purge (#12448) --- homeassistant/components/recorder/purge.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/recorder/purge.py b/homeassistant/components/recorder/purge.py index 06bd81c2309..3fffb521d5a 100644 --- a/homeassistant/components/recorder/purge.py +++ b/homeassistant/components/recorder/purge.py @@ -12,8 +12,7 @@ _LOGGER = logging.getLogger(__name__) def purge_old_data(instance, purge_days): """Purge events and states older than purge_days ago.""" from .models import States, Events - from sqlalchemy import orm - from sqlalchemy.sql import exists + from sqlalchemy import func purge_before = dt_util.utcnow() - timedelta(days=purge_days) @@ -21,18 +20,10 @@ def purge_old_data(instance, purge_days): # For each entity, the most recent state is protected from deletion # s.t. we can properly restore state even if the entity has not been # updated in a long time - states_alias = orm.aliased(States, name='StatesAlias') - protected_states = session.query(States.state_id, States.event_id)\ - .filter(~exists() - .where(States.entity_id == - states_alias.entity_id) - .where(states_alias.last_updated > - States.last_updated))\ - .all() + protected_states = session.query(func.max(States.state_id)) \ + .group_by(States.entity_id).all() protected_state_ids = tuple((state[0] for state in protected_states)) - protected_event_ids = tuple((state[1] for state in protected_states - if state[1] is not None)) deleted_rows = session.query(States) \ .filter((States.last_updated < purge_before)) \ @@ -45,6 +36,13 @@ def purge_old_data(instance, purge_days): # Otherwise, if the SQL server has "ON DELETE CASCADE" as default, it # will delete the protected state when deleting its associated # event. Also, we would be producing NULLed foreign keys otherwise. + protected_events = session.query(States.event_id) \ + .filter(States.state_id.in_(protected_state_ids)) \ + .filter(States.event_id.isnot(None)) \ + .all() + + protected_event_ids = tuple((state[0] for state in protected_events)) + deleted_rows = session.query(Events) \ .filter((Events.time_fired < purge_before)) \ .filter(~Events.event_id.in_( From 6ce9b35e81f2d916b32fec52a3e19ebc574a64fb Mon Sep 17 00:00:00 2001 From: Diogo Gomes Date: Fri, 16 Feb 2018 04:20:45 +0000 Subject: [PATCH 8/8] [SQL Sensor] always close session (#12452) * close aborted session * blank line --- homeassistant/components/sensor/sql.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/sensor/sql.py b/homeassistant/components/sensor/sql.py index 99da8c3c680..395c082f9d2 100644 --- a/homeassistant/components/sensor/sql.py +++ b/homeassistant/components/sensor/sql.py @@ -126,6 +126,8 @@ class SQLSensor(Entity): except sqlalchemy.exc.SQLAlchemyError as err: _LOGGER.error("Error executing query %s: %s", self._query, err) return + finally: + sess.close() for res in result: _LOGGER.debug(res.items()) @@ -141,5 +143,3 @@ class SQLSensor(Entity): data, None) else: self._state = data - - sess.close()