From 58600f25b3d0f70e469ccf72b537de5cf6454009 Mon Sep 17 00:00:00 2001 From: Lewis Juggins Date: Wed, 9 Nov 2016 02:57:56 +0000 Subject: [PATCH 01/15] Fix OWM async I/O (#4298) --- homeassistant/components/weather/openweathermap.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/weather/openweathermap.py b/homeassistant/components/weather/openweathermap.py index b029b4d44bb..a93b0142d90 100644 --- a/homeassistant/components/weather/openweathermap.py +++ b/homeassistant/components/weather/openweathermap.py @@ -67,7 +67,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): data = WeatherData(owm, latitude, longitude) add_devices([OpenWeatherMapWeather( - name, data, hass.config.units.temperature_unit)]) + name, data, hass.config.units.temperature_unit)], True) class OpenWeatherMapWeather(WeatherEntity): @@ -78,8 +78,7 @@ class OpenWeatherMapWeather(WeatherEntity): self._name = name self._owm = owm self._temperature_unit = temperature_unit - self.date = None - self.update() + self.data = None @property def name(self): From a18fdbfbb8590b12c732d62703ca978343e5337b Mon Sep 17 00:00:00 2001 From: Jesse Newland Date: Tue, 8 Nov 2016 19:57:46 -0800 Subject: [PATCH 02/15] Fix alarm.com I/O inside properties (#4307) * Fix alarm.com I/O inside properties * First line should end with a period * Not needed * Fetch state on init --- .../components/alarm_control_panel/alarmdotcom.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/alarmdotcom.py b/homeassistant/components/alarm_control_panel/alarmdotcom.py index 8bf36e176e5..cd37fc6a828 100644 --- a/homeassistant/components/alarm_control_panel/alarmdotcom.py +++ b/homeassistant/components/alarm_control_panel/alarmdotcom.py @@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) - add_devices([AlarmDotCom(hass, name, code, username, password)]) + add_devices([AlarmDotCom(hass, name, code, username, password)], True) class AlarmDotCom(alarm.AlarmControlPanel): @@ -54,12 +54,17 @@ class AlarmDotCom(alarm.AlarmControlPanel): self._code = str(code) if code else None self._username = username self._password = password + self._state = STATE_UNKNOWN @property def should_poll(self): """No polling needed.""" return True + def update(self): + """Fetch the latest state.""" + self._state = self._alarm.state + @property def name(self): """Return the name of the alarm.""" @@ -73,11 +78,11 @@ class AlarmDotCom(alarm.AlarmControlPanel): @property def state(self): """Return the state of the device.""" - if self._alarm.state == 'Disarmed': + if self._state == 'Disarmed': return STATE_ALARM_DISARMED - elif self._alarm.state == 'Armed Stay': + elif self._state == 'Armed Stay': return STATE_ALARM_ARMED_HOME - elif self._alarm.state == 'Armed Away': + elif self._state == 'Armed Away': return STATE_ALARM_ARMED_AWAY else: return STATE_UNKNOWN From ffe4c425af25235ab3ee9338ceea1dcae331554b Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 8 Nov 2016 20:25:19 -0800 Subject: [PATCH 03/15] Fix Tellstick doing I/O inside event loop (#4268) --- homeassistant/components/sensor/tellstick.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/tellstick.py b/homeassistant/components/sensor/tellstick.py index 464e3554324..08e15cd332f 100644 --- a/homeassistant/components/sensor/tellstick.py +++ b/homeassistant/components/sensor/tellstick.py @@ -98,6 +98,7 @@ class TellstickSensor(Entity): self.datatype = datatype self.sensor = sensor self._unit_of_measurement = sensor_info.unit or None + self._value = None self._name = '{} {}'.format(name, sensor_info.name) @@ -109,9 +110,13 @@ class TellstickSensor(Entity): @property def state(self): """Return the state of the sensor.""" - return self.sensor.value(self.datatype).value + return self._value @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" return self._unit_of_measurement + + def update(self): + """Update tellstick sensor.""" + self._value = self.sensor.value(self.datatype).value From eb17ba970cbd67e8b48b3bfbeb570b68df004fc9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 9 Nov 2016 07:21:58 -0800 Subject: [PATCH 04/15] Increase update delay (#4321) --- homeassistant/helpers/entity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index ecb04aca9d9..ac058f89143 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -242,7 +242,7 @@ class Entity(object): end = timer() - if end - start > 0.2: + if end - start > 0.4: _LOGGER.warning('Updating state for %s took %.3f seconds. ' 'Please report platform to the developers at ' 'https://goo.gl/Nvioub', self.entity_id, From 200bdb30ff9cc2024118ecd6611f6d257ac75f92 Mon Sep 17 00:00:00 2001 From: Jan Losinski Date: Thu, 10 Nov 2016 22:14:40 +0100 Subject: [PATCH 05/15] Change pilight systemcode validation to integer (#4286) * Change pilight systemcode validation to integer According to the pilight code the systemcode should be an integer and not a string (it is an int in the pilight code). Passing this as a string caused errors from pilight: "ERROR: elro_800_switch: insufficient number of arguments" This fixes #4282 * Change pilight unit-id to positive integer According to the pilight code the unit of an entity is also evrywhere handled as an integer. So converting and passing this as string causes pilight not to work. This fixes #4282 Signed-off-by: Jan Losinski --- homeassistant/components/switch/pilight.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/switch/pilight.py b/homeassistant/components/switch/pilight.py index f07d91ca9fb..80a36756d79 100644 --- a/homeassistant/components/switch/pilight.py +++ b/homeassistant/components/switch/pilight.py @@ -27,10 +27,10 @@ DEPENDENCIES = ['pilight'] COMMAND_SCHEMA = pilight.RF_CODE_SCHEMA.extend({ vol.Optional('on'): cv.positive_int, vol.Optional('off'): cv.positive_int, - vol.Optional(CONF_UNIT): cv.string, + vol.Optional(CONF_UNIT): cv.positive_int, vol.Optional(CONF_ID): cv.positive_int, vol.Optional(CONF_STATE): cv.string, - vol.Optional(CONF_SYSTEMCODE): cv.string, + vol.Optional(CONF_SYSTEMCODE): cv.positive_int, }) SWITCHES_SCHEMA = vol.Schema({ From 3e1cc4282ea39586950e51fd79bf3543b554589b Mon Sep 17 00:00:00 2001 From: Sean Dague Date: Thu, 10 Nov 2016 23:44:38 -0500 Subject: [PATCH 06/15] Fix "argument of type 'NoneType' is not iterable" during discovery (#4279) * Fix "argument of type 'NoneType' is not iterable" during discovery When yamaha receivers are dynamically discovered, there config is empty, which means that we need to set zone_ignore to [] otherwise the iteration over receivers fails. * Bump rxv library version to fix play_status bug rxv version 0.3 will issue the play_status command even for sources that don't support it, causing stack traces during updates when receivers are on HDMI inputs. This was fixed in rxv 0.3.1. Bump to fix bug #4226. * Don't discovery receivers that we've already configured The discovery component doesn't know anything about already configured receivers. This means that specifying a receiver manually will make it show up twice if you have the discovery component enabled. This puts a platform specific work around here that ensures that if the media_player is found, we ignore the discovery system. --- homeassistant/components/media_player/yamaha.py | 14 +++++++++++++- requirements_all.txt | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/yamaha.py b/homeassistant/components/media_player/yamaha.py index 94191862f44..0e265199fce 100644 --- a/homeassistant/components/media_player/yamaha.py +++ b/homeassistant/components/media_player/yamaha.py @@ -18,7 +18,7 @@ from homeassistant.const import (CONF_NAME, CONF_HOST, STATE_OFF, STATE_ON, STATE_PLAYING, STATE_IDLE) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['rxv==0.3.0'] +REQUIREMENTS = ['rxv==0.3.1'] _LOGGER = logging.getLogger(__name__) @@ -35,6 +35,7 @@ CONF_SOURCE_IGNORE = 'source_ignore' CONF_ZONE_IGNORE = 'zone_ignore' DEFAULT_NAME = 'Yamaha Receiver' +KNOWN = 'yamaha_known_receivers' PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -50,6 +51,11 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ def setup_platform(hass, config, add_devices, discovery_info=None): """Setup the Yamaha platform.""" import rxv + # keep track of configured receivers so that we don't end up + # discovering a receiver dynamically that we have static config + # for. + if hass.data.get(KNOWN, None) is None: + hass.data[KNOWN] = set() name = config.get(CONF_NAME) host = config.get(CONF_HOST) @@ -62,12 +68,17 @@ def setup_platform(hass, config, add_devices, discovery_info=None): model = discovery_info[1] ctrl_url = discovery_info[2] desc_url = discovery_info[3] + if ctrl_url in hass.data[KNOWN]: + _LOGGER.info("%s already manually configured", ctrl_url) + return receivers = rxv.RXV( ctrl_url, model_name=model, friendly_name=name, unit_desc_url=desc_url).zone_controllers() _LOGGER.info("Receivers: %s", receivers) + # when we are dynamically discovered config is empty + zone_ignore = [] elif host is None: receivers = [] for recv in rxv.find(): @@ -78,6 +89,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): for receiver in receivers: if receiver.zone not in zone_ignore: + hass.data[KNOWN].add(receiver.ctrl_url) add_devices([ YamahaDevice(name, receiver, source_ignore, source_names)]) diff --git a/requirements_all.txt b/requirements_all.txt index af5519f1d15..39e037cdf89 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -464,7 +464,7 @@ radiotherm==1.2 # rpi-rf==0.9.5 # homeassistant.components.media_player.yamaha -rxv==0.3.0 +rxv==0.3.1 # homeassistant.components.media_player.samsungtv samsungctl==0.5.1 From 6860d9b096d2b260cdf3575e534fd06319f0f9f1 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 11 Nov 2016 06:01:42 +0100 Subject: [PATCH 07/15] Update SoCo to 0.12 (#4337) * Update SoCo to 0.12 * fix req --- homeassistant/components/media_player/sonos.py | 4 +--- requirements_all.txt | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index 89f5d7b07ed..ebc8d58874a 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -21,9 +21,7 @@ from homeassistant.const import ( from homeassistant.config import load_yaml_config_file import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['https://github.com/SoCo/SoCo/archive/' - 'cf8c2701165562eccbf1ecc879bf7060ceb0993e.zip#' - 'SoCo==0.12'] +REQUIREMENTS = ['SoCo==0.12'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index 39e037cdf89..e6ccf11cbed 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -24,6 +24,9 @@ PyMata==2.13 # homeassistant.components.rpi_gpio # RPi.GPIO==0.6.1 +# homeassistant.components.media_player.sonos +SoCo==0.12 + # homeassistant.components.notify.twitter TwitterAPI==2.4.2 @@ -157,9 +160,6 @@ https://github.com/GadgetReactor/pyHS100/archive/1f771b7d8090a91c6a58931532e4273 # homeassistant.components.switch.dlink https://github.com/LinuxChristian/pyW215/archive/v0.3.5.zip#pyW215==0.3.5 -# homeassistant.components.media_player.sonos -https://github.com/SoCo/SoCo/archive/cf8c2701165562eccbf1ecc879bf7060ceb0993e.zip#SoCo==0.12 - # homeassistant.components.media_player.webostv # homeassistant.components.notify.webostv https://github.com/TheRealLink/pylgtv/archive/v0.1.2.zip#pylgtv==0.1.2 From 55ddaf1ee7b6d05145803f37f2c96174578f0445 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 11 Nov 2016 06:04:47 +0100 Subject: [PATCH 08/15] Synology SSL fix & Error handling (#4325) * Synology SSL fix & Error handling * change handling for cookies/ssl * fix use not deprecated functions * fix lint * change verify * fix connector close to coro * fix force close * not needed since websession close connector too * fix params * fix lint --- homeassistant/components/camera/synology.py | 113 ++++++++++---------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/camera/synology.py index 4ca63c16d7d..bbca25fd6b6 100644 --- a/homeassistant/components/camera/synology.py +++ b/homeassistant/components/camera/synology.py @@ -9,13 +9,14 @@ import logging import voluptuous as vol +import aiohttp from aiohttp import web from aiohttp.web_exceptions import HTTPGatewayTimeout import async_timeout from homeassistant.const import ( CONF_NAME, CONF_USERNAME, CONF_PASSWORD, - CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL) + CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP) from homeassistant.components.camera import ( Camera, PLATFORM_SCHEMA) import homeassistant.helpers.config_validation as cv @@ -57,6 +58,16 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @asyncio.coroutine def async_setup_platform(hass, config, async_add_devices, discovery_info=None): """Setup a Synology IP Camera.""" + if not config.get(CONF_VERIFY_SSL): + connector = aiohttp.TCPConnector(verify_ssl=False) + else: + connector = None + + websession_init = aiohttp.ClientSession( + loop=hass.loop, + connector=connector + ) + # Determine API to use for authentication syno_api_url = SYNO_API_URL.format( config.get(CONF_URL), WEBAPI_PATH, QUERY_CGI) @@ -69,13 +80,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): } try: with async_timeout.timeout(TIMEOUT, loop=hass.loop): - query_req = yield from hass.websession.get( + query_req = yield from websession_init.get( syno_api_url, - params=query_payload, - verify_ssl=config.get(CONF_VERIFY_SSL) + params=query_payload ) - except asyncio.TimeoutError: - _LOGGER.error("Timeout on %s", syno_api_url) + except (asyncio.TimeoutError, aiohttp.errors.ClientError): + _LOGGER.exception("Error on %s", syno_api_url) return False query_resp = yield from query_req.json() @@ -93,12 +103,26 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): session_id = yield from get_session_id( hass, + websession_init, config.get(CONF_USERNAME), config.get(CONF_PASSWORD), - syno_auth_url, - config.get(CONF_VERIFY_SSL) + syno_auth_url ) + websession_init.detach() + + # init websession + websession = aiohttp.ClientSession( + loop=hass.loop, connector=connector, cookies={'id': session_id}) + + @asyncio.coroutine + def _async_close_websession(event): + """Close webssesion on shutdown.""" + yield from websession.close() + + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, _async_close_websession) + # Use SessionID to get cameras in system syno_camera_url = SYNO_API_URL.format( config.get(CONF_URL), WEBAPI_PATH, camera_api) @@ -110,14 +134,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): } try: with async_timeout.timeout(TIMEOUT, loop=hass.loop): - camera_req = yield from hass.websession.get( + camera_req = yield from websession.get( syno_camera_url, - params=camera_payload, - verify_ssl=config.get(CONF_VERIFY_SSL), - cookies={'id': session_id} + params=camera_payload ) - except asyncio.TimeoutError: - _LOGGER.error("Timeout on %s", syno_camera_url) + except (asyncio.TimeoutError, aiohttp.errors.ClientError): + _LOGGER.exception("Error on %s", syno_camera_url) return False camera_resp = yield from camera_req.json() @@ -126,13 +148,14 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): # add cameras devices = [] - tasks = [] for camera in cameras: if not config.get(CONF_WHITELIST): camera_id = camera['id'] snapshot_path = camera['snapshot_path'] device = SynologyCamera( + hass, + websession, config, camera_id, camera['name'], @@ -141,15 +164,13 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): camera_path, auth_path ) - tasks.append(device.async_read_sid()) devices.append(device) - yield from asyncio.gather(*tasks, loop=hass.loop) - hass.loop.create_task(async_add_devices(devices)) + yield from async_add_devices(devices) @asyncio.coroutine -def get_session_id(hass, username, password, login_url, valid_cert): +def get_session_id(hass, websession, username, password, login_url): """Get a session id.""" auth_payload = { 'api': AUTH_API, @@ -162,13 +183,12 @@ def get_session_id(hass, username, password, login_url, valid_cert): } try: with async_timeout.timeout(TIMEOUT, loop=hass.loop): - auth_req = yield from hass.websession.get( + auth_req = yield from websession.get( login_url, - params=auth_payload, - verify_ssl=valid_cert + params=auth_payload ) - except asyncio.TimeoutError: - _LOGGER.error("Timeout on %s", login_url) + except (asyncio.TimeoutError, aiohttp.errors.ClientError): + _LOGGER.exception("Error on %s", login_url) return False auth_resp = yield from auth_req.json() @@ -180,36 +200,22 @@ def get_session_id(hass, username, password, login_url, valid_cert): class SynologyCamera(Camera): """An implementation of a Synology NAS based IP camera.""" - def __init__(self, config, camera_id, camera_name, - snapshot_path, streaming_path, camera_path, auth_path): + def __init__(self, hass, websession, config, camera_id, + camera_name, snapshot_path, streaming_path, camera_path, + auth_path): """Initialize a Synology Surveillance Station camera.""" super().__init__() + self.hass = hass + self._websession = websession self._name = camera_name - self._username = config.get(CONF_USERNAME) - self._password = config.get(CONF_PASSWORD) self._synology_url = config.get(CONF_URL) - self._api_url = config.get(CONF_URL) + 'webapi/' - self._login_url = config.get(CONF_URL) + '/webapi/' + 'auth.cgi' self._camera_name = config.get(CONF_CAMERA_NAME) self._stream_id = config.get(CONF_STREAM_ID) - self._valid_cert = config.get(CONF_VERIFY_SSL) self._camera_id = camera_id self._snapshot_path = snapshot_path self._streaming_path = streaming_path self._camera_path = camera_path self._auth_path = auth_path - self._session_id = None - - @asyncio.coroutine - def async_read_sid(self): - """Get a session id.""" - self._session_id = yield from get_session_id( - self.hass, - self._username, - self._password, - self._login_url, - self._valid_cert - ) def camera_image(self): """Return bytes of camera image.""" @@ -230,14 +236,12 @@ class SynologyCamera(Camera): } try: with async_timeout.timeout(TIMEOUT, loop=self.hass.loop): - response = yield from self.hass.websession.get( + response = yield from self._websession.get( image_url, - params=image_payload, - verify_ssl=self._valid_cert, - cookies={'id': self._session_id} + params=image_payload ) - except asyncio.TimeoutError: - _LOGGER.error("Timeout on %s", image_url) + except (asyncio.TimeoutError, aiohttp.errors.ClientError): + _LOGGER.exception("Error on %s", image_url) return None image = yield from response.read() @@ -260,13 +264,12 @@ class SynologyCamera(Camera): } try: with async_timeout.timeout(TIMEOUT, loop=self.hass.loop): - stream = yield from self.hass.websession.get( + stream = yield from self._websession.get( streaming_url, - payload=streaming_payload, - verify_ssl=self._valid_cert, - cookies={'id': self._session_id} + params=streaming_payload ) - except asyncio.TimeoutError: + except (asyncio.TimeoutError, aiohttp.errors.ClientError): + _LOGGER.exception("Error on %s", streaming_url) raise HTTPGatewayTimeout() response = web.StreamResponse() @@ -281,7 +284,7 @@ class SynologyCamera(Camera): break response.write(data) finally: - self.hass.loop.create_task(stream.release()) + self.hass.async_add_job(stream.release()) yield from response.write_eof() @property From 6e6b1ef7ab137f6fa9f4592e45075188e9c3c58c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 10 Nov 2016 21:17:44 -0800 Subject: [PATCH 09/15] fix panasonic viera doing I/O in event loop (#4341) --- .../components/media_player/panasonic_viera.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/media_player/panasonic_viera.py b/homeassistant/components/media_player/panasonic_viera.py index d1a971eb91e..987364bbf63 100644 --- a/homeassistant/components/media_player/panasonic_viera.py +++ b/homeassistant/components/media_player/panasonic_viera.py @@ -79,16 +79,16 @@ class PanasonicVieraTVDevice(MediaPlayerDevice): self._playing = True self._state = STATE_UNKNOWN self._remote = remote + self._volume = 0 def update(self): """Retrieve the latest data.""" try: self._muted = self._remote.get_mute() + self._volume = self._remote.get_volume() / 100 self._state = STATE_ON except OSError: self._state = STATE_OFF - return False - return True def send_key(self, key): """Send a key to the tv and handles exceptions.""" @@ -113,13 +113,7 @@ class PanasonicVieraTVDevice(MediaPlayerDevice): @property def volume_level(self): """Volume level of the media player (0..1).""" - volume = 0 - try: - volume = self._remote.get_volume() / 100 - self._state = STATE_ON - except OSError: - self._state = STATE_OFF - return volume + return self._volume @property def is_volume_muted(self): From 2c39c39d5276f9fae3456c2e09e9145352fef6a3 Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Fri, 11 Nov 2016 07:28:22 +0200 Subject: [PATCH 10/15] Improve async generic camera's error handling (#4316) * Handle errors * Feedback * DisconnectedError --- homeassistant/components/camera/generic.py | 16 +++++++++------- homeassistant/components/sensor/yr.py | 14 ++++++-------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py index c6664ed70b2..e4d97987dbf 100644 --- a/homeassistant/components/camera/generic.py +++ b/homeassistant/components/camera/generic.py @@ -91,7 +91,7 @@ class GenericCamera(Camera): if url == self._last_url and self._limit_refetch: return self._last_image - # aiohttp don't support DigestAuth jet + # aiohttp don't support DigestAuth yet if self._authentication == HTTP_DIGEST_AUTHENTICATION: def fetch(): """Read image from a URL.""" @@ -109,15 +109,17 @@ class GenericCamera(Camera): else: try: with async_timeout.timeout(10, loop=self.hass.loop): - respone = yield from self.hass.websession.get( - url, - auth=self._auth - ) - self._last_image = yield from respone.read() - yield from respone.release() + response = yield from self.hass.websession.get( + url, auth=self._auth) + self._last_image = yield from response.read() + yield from response.release() except asyncio.TimeoutError: _LOGGER.error('Timeout getting camera image') return self._last_image + except (aiohttp.errors.ClientError, + aiohttp.errors.ClientDisconnectedError) as err: + _LOGGER.error('Error getting new camera image: %s', err) + return self._last_image self._last_url = url return self._last_image diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index 51616062475..3436288b627 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -10,7 +10,7 @@ import logging from xml.parsers.expat import ExpatError import async_timeout -from aiohttp.web import HTTPException +import aiohttp import voluptuous as vol import homeassistant.helpers.config_validation as cv @@ -154,12 +154,9 @@ class YrData(object): try_again('{} returned {}'.format(self._url, resp.status)) return text = yield from resp.text() - self.hass.loop.create_task(resp.release()) - except asyncio.TimeoutError as err: - try_again(err) - return - except HTTPException as err: - resp.close() + self.hass.async_add_job(resp.release()) + except (asyncio.TimeoutError, aiohttp.errors.ClientError, + aiohttp.errors.ClientDisconnectedError) as err: try_again(err) return @@ -218,4 +215,5 @@ class YrData(object): dev._state = new_state tasks.append(dev.async_update_ha_state()) - yield from asyncio.gather(*tasks, loop=self.hass.loop) + if tasks: + yield from asyncio.wait(tasks, loop=self.hass.loop) From 2feea1d1eba4dada7d66ac3fcc21a29fc9fda790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20H=C3=B8yer=20Iversen?= Date: Fri, 11 Nov 2016 06:30:52 +0100 Subject: [PATCH 11/15] Add support for rgb light in led flux, fixes issue #4303 (#4332) --- homeassistant/components/light/flux_led.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py index 095733afd8e..9de4aa6b0fc 100644 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -23,6 +23,7 @@ REQUIREMENTS = ['https://github.com/Danielhiversen/flux_led/archive/0.8.zip' _LOGGER = logging.getLogger(__name__) CONF_AUTOMATIC_ADD = 'automatic_add' +ATTR_MODE = 'mode' DOMAIN = 'flux_led' @@ -31,6 +32,8 @@ SUPPORT_FLUX_LED = (SUPPORT_BRIGHTNESS | SUPPORT_EFFECT | DEVICE_SCHEMA = vol.Schema({ vol.Optional(CONF_NAME): cv.string, + vol.Optional(ATTR_MODE, default='rgbw'): + vol.All(cv.string, vol.In(['rgbw', 'rgb'])), }) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ @@ -48,6 +51,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): device = {} device['name'] = device_config[CONF_NAME] device['ipaddr'] = ipaddr + device[ATTR_MODE] = device_config[ATTR_MODE] light = FluxLight(device) if light.is_valid: lights.append(light) @@ -65,6 +69,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if ipaddr in light_ips: continue device['name'] = device['id'] + " " + ipaddr + device[ATTR_MODE] = 'rgbw' light = FluxLight(device) if light.is_valid: lights.append(light) @@ -82,6 +87,7 @@ class FluxLight(Light): self._name = device['name'] self._ipaddr = device['ipaddr'] + self._mode = device[ATTR_MODE] self.is_valid = True self._bulb = None try: @@ -132,7 +138,11 @@ class FluxLight(Light): if rgb: self._bulb.setRgb(*tuple(rgb)) elif brightness: - self._bulb.setWarmWhite255(brightness) + if self._mode == 'rgbw': + self._bulb.setWarmWhite255(brightness) + elif self._mode == 'rgb': + (red, green, blue) = self._bulb.getRgb() + self._bulb.setRgb(red, green, blue, brightness=brightness) elif effect == EFFECT_RANDOM: self._bulb.setRgb(random.randrange(0, 255), random.randrange(0, 255), From cc5233103cf211a64d2d07c619e2fc4d7ad244af Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Fri, 11 Nov 2016 06:32:08 +0100 Subject: [PATCH 12/15] Fix rest switch default template (#4331) --- homeassistant/components/switch/rest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/switch/rest.py b/homeassistant/components/switch/rest.py index 056bcef0281..36674c16d16 100644 --- a/homeassistant/components/switch/rest.py +++ b/homeassistant/components/switch/rest.py @@ -12,11 +12,12 @@ import voluptuous as vol from homeassistant.components.switch import (SwitchDevice, PLATFORM_SCHEMA) from homeassistant.const import (CONF_NAME, CONF_RESOURCE, CONF_TIMEOUT) import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.template import Template CONF_BODY_OFF = 'body_off' CONF_BODY_ON = 'body_on' -DEFAULT_BODY_OFF = 'OFF' -DEFAULT_BODY_ON = 'ON' +DEFAULT_BODY_OFF = Template('OFF') +DEFAULT_BODY_ON = Template('ON') DEFAULT_NAME = 'REST Switch' DEFAULT_TIMEOUT = 10 CONF_IS_ON_TEMPLATE = 'is_on_template' From 1b79722b6973babddd3b0d86d5028f5750da5cfa Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 8 Nov 2016 21:00:33 -0800 Subject: [PATCH 13/15] Fix KNX async I/O (#4267) --- homeassistant/components/climate/knx.py | 19 +++++++++++++------ homeassistant/components/knx.py | 2 -- homeassistant/components/sensor/knx.py | 15 ++++++++++++--- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/climate/knx.py b/homeassistant/components/climate/knx.py index ef7445c35fd..888a217d90c 100644 --- a/homeassistant/components/climate/knx.py +++ b/homeassistant/components/climate/knx.py @@ -56,6 +56,8 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice): self._unit_of_measurement = TEMP_CELSIUS # KNX always used celsius self._away = False # not yet supported self._is_fan_on = False # not yet supported + self._current_temp = None + self._target_temp = None @property def should_poll(self): @@ -70,16 +72,12 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice): @property def current_temperature(self): """Return the current temperature.""" - from knxip.conversion import knx2_to_float - - return knx2_to_float(self.value('temperature')) + return self._current_temp @property def target_temperature(self): """Return the temperature we try to reach.""" - from knxip.conversion import knx2_to_float - - return knx2_to_float(self.value('setpoint')) + return self._target_temp def set_temperature(self, **kwargs): """Set new target temperature.""" @@ -94,3 +92,12 @@ class KNXThermostat(KNXMultiAddressDevice, ClimateDevice): def set_operation_mode(self, operation_mode): """Set operation mode.""" raise NotImplementedError() + + def update(self): + """Update KNX climate.""" + from knxip.conversion import knx2_to_float + + super().update() + + self._current_temp = knx2_to_float(self.value('temperature')) + self._target_temp = knx2_to_float(self.value('setpoint')) diff --git a/homeassistant/components/knx.py b/homeassistant/components/knx.py index 5d096b30ee0..8653f33c663 100644 --- a/homeassistant/components/knx.py +++ b/homeassistant/components/knx.py @@ -161,8 +161,6 @@ class KNXGroupAddress(Entity): @property def is_on(self): """Return True if the value is not 0 is on, else False.""" - if self.should_poll: - self.update() return self._state != 0 @property diff --git a/homeassistant/components/sensor/knx.py b/homeassistant/components/sensor/knx.py index 007291f5fb1..1f5c9a76520 100644 --- a/homeassistant/components/sensor/knx.py +++ b/homeassistant/components/sensor/knx.py @@ -113,15 +113,24 @@ class KNXSensorFloatClass(KNXGroupAddress, KNXSensorBaseClass): self._unit_of_measurement = unit_of_measurement self._minimum_value = minimum_sensor_value self._maximum_value = maximum_sensor_value + self._value = None KNXGroupAddress.__init__(self, hass, config) @property def state(self): """Return the Value of the KNX Sensor.""" + return self._value + + def update(self): + """Update KNX sensor.""" + from knxip.conversion import knx2_to_float + + super().update() + + self._value = None + if self._data: - from knxip.conversion import knx2_to_float value = knx2_to_float(self._data) if self._minimum_value <= value <= self._maximum_value: - return value - return None + self._value = value From 72407c2f95817e8e6a0139e923de0126726490bc Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 10 Nov 2016 21:49:56 -0800 Subject: [PATCH 14/15] Make yr compatible with 0.32 --- homeassistant/components/sensor/yr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/sensor/yr.py b/homeassistant/components/sensor/yr.py index 3436288b627..ab1e2da5852 100644 --- a/homeassistant/components/sensor/yr.py +++ b/homeassistant/components/sensor/yr.py @@ -154,7 +154,7 @@ class YrData(object): try_again('{} returned {}'.format(self._url, resp.status)) return text = yield from resp.text() - self.hass.async_add_job(resp.release()) + self.hass.async_add_job(resp.release) except (asyncio.TimeoutError, aiohttp.errors.ClientError, aiohttp.errors.ClientDisconnectedError) as err: try_again(err) From 173e15e73360cf0661650fef017d7d62e1753c35 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 10 Nov 2016 21:50:05 -0800 Subject: [PATCH 15/15] Version bump to 0.32.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 0eb2384517d..cdc846c49d3 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 32 -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)