From 61fc4ca8fe03e246811e5d73c65f82ff24cf4767 Mon Sep 17 00:00:00 2001 From: pvizeli Date: Wed, 21 Dec 2016 11:47:38 +0100 Subject: [PATCH 1/5] Version bump to 0.35.3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 6b295a9be5ee03..c48ff84f3a1193 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 35 -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 35b4da0aa2c1113d21a3588f7aaafe9ae67bbf71 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 21 Dec 2016 10:22:12 +0100 Subject: [PATCH 2/5] Bugfix voicerss post api (#5021) * Bugfix voicerss post api * fix unittest * Add cache to service description --- homeassistant/components/tts/services.yaml | 8 +++- homeassistant/components/tts/voicerss.py | 27 +++++++++++-- tests/components/tts/test_voicerss.py | 45 +++++++++++++++++++--- 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/tts/services.yaml b/homeassistant/components/tts/services.yaml index aba1334da8718c..5cb146950b475b 100644 --- a/homeassistant/components/tts/services.yaml +++ b/homeassistant/components/tts/services.yaml @@ -3,12 +3,16 @@ say: fields: entity_id: - description: Name(s) of media player entities + description: Name(s) of media player entities. example: 'media_player.floor' message: - description: Text to speak on devices + description: Text to speak on devices. example: 'My name is hanna' + cache: + description: Control file cache of this message. + example: 'true' + clear_cache: description: Remove cache files and RAM cache. diff --git a/homeassistant/components/tts/voicerss.py b/homeassistant/components/tts/voicerss.py index 728a1996a5d006..fdbe8a8d806d97 100644 --- a/homeassistant/components/tts/voicerss.py +++ b/homeassistant/components/tts/voicerss.py @@ -21,6 +21,18 @@ VOICERSS_API_URL = "https://api.voicerss.org/" +ERROR_MSG = [ + b'Error description', + b'The subscription is expired or requests count limitation is exceeded!', + b'The request content length is too large!', + b'The language does not support!', + b'The language is not specified!', + b'The text is not specified!', + b'The API key is not available!', + b'The API key is not specified!', + b'The subscription does not support SSML!', +] + SUPPORT_LANGUAGES = [ 'ca-es', 'zh-cn', 'zh-hk', 'zh-tw', 'da-dk', 'nl-nl', 'en-au', 'en-ca', 'en-gb', 'en-in', 'en-us', 'fi-fi', 'fr-ca', 'fr-fr', 'de-de', 'it-it', @@ -83,7 +95,7 @@ def __init__(self, hass, conf): self.hass = hass self.extension = conf.get(CONF_CODEC) - self.params = { + self.form_data = { 'key': conf.get(CONF_API_KEY), 'hl': conf.get(CONF_LANG), 'c': (conf.get(CONF_CODEC)).upper(), @@ -94,21 +106,28 @@ def __init__(self, hass, conf): def async_get_tts_audio(self, message): """Load TTS from voicerss.""" websession = async_get_clientsession(self.hass) + form_data = self.form_data.copy() + + form_data['src'] = message request = None try: with async_timeout.timeout(10, loop=self.hass.loop): request = yield from websession.post( - VOICERSS_API_URL, params=self.params, - data=bytes(message, 'utf-8') + VOICERSS_API_URL, data=form_data ) if request.status != 200: - _LOGGER.error("Error %d on load url %s", + _LOGGER.error("Error %d on load url %s.", request.status, request.url) return (None, None) data = yield from request.read() + if data in ERROR_MSG: + _LOGGER.error( + "Error receive %s from voicerss.", str(data, 'utf-8')) + return (None, None) + except (asyncio.TimeoutError, aiohttp.errors.ClientError): _LOGGER.error("Timeout for voicerss api.") return (None, None) diff --git a/tests/components/tts/test_voicerss.py b/tests/components/tts/test_voicerss.py index 44ce0d6739fba0..ea1263b189ec08 100644 --- a/tests/components/tts/test_voicerss.py +++ b/tests/components/tts/test_voicerss.py @@ -20,11 +20,12 @@ def setup_method(self): self.hass = get_test_home_assistant() self.url = "https://api.voicerss.org/" - self.url_param = { + self.form_data = { 'key': '1234567xx', 'hl': 'en-us', 'c': 'MP3', 'f': '8khz_8bit_mono', + 'src': "I person is on front of your door.", } def teardown_method(self): @@ -63,7 +64,7 @@ def test_service_say(self, aioclient_mock): calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) aioclient_mock.post( - self.url, params=self.url_param, status=200, content=b'test') + self.url, data=self.form_data, status=200, content=b'test') config = { tts.DOMAIN: { @@ -82,15 +83,16 @@ def test_service_say(self, aioclient_mock): assert len(calls) == 1 assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2] == self.form_data assert calls[0].data[ATTR_MEDIA_CONTENT_ID].find(".mp3") != -1 def test_service_say_german(self, aioclient_mock): """Test service call say with german code.""" calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) - self.url_param['hl'] = 'de-de' + self.form_data['hl'] = 'de-de' aioclient_mock.post( - self.url, params=self.url_param, status=200, content=b'test') + self.url, data=self.form_data, status=200, content=b'test') config = { tts.DOMAIN: { @@ -110,13 +112,14 @@ def test_service_say_german(self, aioclient_mock): assert len(calls) == 1 assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2] == self.form_data def test_service_say_error(self, aioclient_mock): """Test service call say with http response 400.""" calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) aioclient_mock.post( - self.url, params=self.url_param, status=400, content=b'test') + self.url, data=self.form_data, status=400, content=b'test') config = { tts.DOMAIN: { @@ -135,13 +138,14 @@ def test_service_say_error(self, aioclient_mock): assert len(calls) == 0 assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2] == self.form_data def test_service_say_timeout(self, aioclient_mock): """Test service call say with http timeout.""" calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) aioclient_mock.post( - self.url, params=self.url_param, exc=asyncio.TimeoutError()) + self.url, data=self.form_data, exc=asyncio.TimeoutError()) config = { tts.DOMAIN: { @@ -160,3 +164,32 @@ def test_service_say_timeout(self, aioclient_mock): assert len(calls) == 0 assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2] == self.form_data + + def test_service_say_error_msg(self, aioclient_mock): + """Test service call say with http error api message.""" + calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) + + aioclient_mock.post( + self.url, data=self.form_data, status=200, + content=b'The subscription does not support SSML!' + ) + + config = { + tts.DOMAIN: { + 'platform': 'voicerss', + 'api_key': '1234567xx', + } + } + + with assert_setup_component(1, tts.DOMAIN): + setup_component(self.hass, tts.DOMAIN, config) + + self.hass.services.call(tts.DOMAIN, 'voicerss_say', { + tts.ATTR_MESSAGE: "I person is on front of your door.", + }) + self.hass.block_till_done() + + assert len(calls) == 0 + assert len(aioclient_mock.mock_calls) == 1 + assert aioclient_mock.mock_calls[0][2] == self.form_data From f18a88c2d4ea786d87b873632a1cbb9781d8b9c6 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Wed, 21 Dec 2016 15:11:14 +0100 Subject: [PATCH 3/5] Bugfix create a task from a task in component update (#5033) --- homeassistant/components/alarm_control_panel/__init__.py | 2 +- homeassistant/components/light/__init__.py | 2 +- homeassistant/components/remote/__init__.py | 2 +- homeassistant/components/switch/__init__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 1b64431c7a106b..54be6aa4d0b444 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -115,7 +115,7 @@ def async_alarm_service_handler(service): update_coro = hass.loop.create_task( alarm.async_update_ha_state(True)) if hasattr(alarm, 'async_update'): - update_tasks.append(hass.loop.create_task(update_coro)) + update_tasks.append(update_coro) else: yield from update_coro diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py index 869bbd90e7d00d..d98d8b0d5fc5a5 100644 --- a/homeassistant/components/light/__init__.py +++ b/homeassistant/components/light/__init__.py @@ -253,7 +253,7 @@ def async_handle_light_service(service): update_coro = hass.loop.create_task( light.async_update_ha_state(True)) if hasattr(light, 'async_update'): - update_tasks.append(hass.loop.create_task(update_coro)) + update_tasks.append(update_coro) else: yield from update_coro diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py index 3a481e8383073a..2baef2011fc5e7 100755 --- a/homeassistant/components/remote/__init__.py +++ b/homeassistant/components/remote/__init__.py @@ -115,7 +115,7 @@ def async_handle_remote_service(service): update_coro = hass.loop.create_task( remote.async_update_ha_state(True)) if hasattr(remote, 'async_update'): - update_tasks.append(hass.loop.create_task(update_coro)) + update_tasks.append(update_coro) else: yield from update_coro diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py index 846a87f5067933..fe74711dff0354 100644 --- a/homeassistant/components/switch/__init__.py +++ b/homeassistant/components/switch/__init__.py @@ -98,7 +98,7 @@ def async_handle_switch_service(service): update_coro = hass.loop.create_task( switch.async_update_ha_state(True)) if hasattr(switch, 'async_update'): - update_tasks.append(hass.loop.create_task(update_coro)) + update_tasks.append(update_coro) else: yield from update_coro From 9bf4a53fbb7562217e7841fcee47c1e759a27014 Mon Sep 17 00:00:00 2001 From: Pascal Vizeli Date: Thu, 22 Dec 2016 16:08:01 +0100 Subject: [PATCH 4/5] Bugfix async log handle re-close bug (#5034) * Bugfix async log handle re-close bug * Check on running thread on async_close * Fix now on right place --- homeassistant/util/logging.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index 0a1218f5796ca9..736fee0d1b316c 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -55,7 +55,9 @@ def async_close(self, blocking=False): When blocking=True, will wait till closed. """ - self.close() + if not self._thread.is_alive(): + return + yield from self._queue.put(None) if blocking: # Python 3.4.4+ From 203c1cfc96a2e400d37c282edd363f326effa7f5 Mon Sep 17 00:00:00 2001 From: Josh Nichols Date: Thu, 22 Dec 2016 14:22:07 -0500 Subject: [PATCH 5/5] Nest fixes (#5011) * Updated Nest API to have logical names * Fix NoneType not having replace method in NestSensor constructor * Move name setting to constructor, in case zone.name causes IO. * normalize is_online to online * Updated python-nest API * push is_* helpers down to python-nest, and use inheritence to implement rather than checking class name * Update python-nest --- .../components/binary_sensor/nest.py | 18 ++++----- homeassistant/components/camera/nest.py | 6 +-- homeassistant/components/climate/nest.py | 2 +- homeassistant/components/nest.py | 37 ++++++------------- homeassistant/components/sensor/nest.py | 21 +++-------- requirements_all.txt | 2 +- 6 files changed, 31 insertions(+), 55 deletions(-) diff --git a/homeassistant/components/binary_sensor/nest.py b/homeassistant/components/binary_sensor/nest.py index 070703df32a85d..c66373bc58a1c3 100644 --- a/homeassistant/components/binary_sensor/nest.py +++ b/homeassistant/components/binary_sensor/nest.py @@ -13,8 +13,7 @@ BinarySensorDevice, PLATFORM_SCHEMA) from homeassistant.components.sensor.nest import NestSensor from homeassistant.const import (CONF_SCAN_INTERVAL, CONF_MONITORED_CONDITIONS) -from homeassistant.components.nest import ( - DATA_NEST, is_thermostat, is_camera) +from homeassistant.components.nest import DATA_NEST import homeassistant.helpers.config_validation as cv DEPENDENCIES = ['nest'] @@ -76,9 +75,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error(wstr) sensors = [] - device_chain = chain(nest.devices(), - nest.protect_devices(), - nest.camera_devices()) + device_chain = chain(nest.thermostats(), + nest.smoke_co_alarms(), + nest.cameras()) for structure, device in device_chain: sensors += [NestBinarySensor(structure, device, variable) for variable in conf @@ -86,9 +85,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None): sensors += [NestBinarySensor(structure, device, variable) for variable in conf if variable in CLIMATE_BINARY_TYPES - and is_thermostat(device)] + and device.is_thermostat] - if is_camera(device): + if device.is_camera: sensors += [NestBinarySensor(structure, device, variable) for variable in conf if variable in CAMERA_BINARY_TYPES] @@ -118,13 +117,14 @@ class NestActivityZoneSensor(NestBinarySensor): def __init__(self, structure, device, zone): """Initialize the sensor.""" - super(NestActivityZoneSensor, self).__init__(structure, device, None) + super(NestActivityZoneSensor, self).__init__(structure, device, "") self.zone = zone + self._name = "{} {} activity".format(self._name, self.zone.name) @property def name(self): """Return the name of the nest, if any.""" - return "{} {} activity".format(self._name, self.zone.name) + return self._name def update(self): """Retrieve latest state.""" diff --git a/homeassistant/components/camera/nest.py b/homeassistant/components/camera/nest.py index aa2041e07a676e..6ffb7ef85619f8 100644 --- a/homeassistant/components/camera/nest.py +++ b/homeassistant/components/camera/nest.py @@ -27,7 +27,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): if discovery_info is None: return - camera_devices = hass.data[nest.DATA_NEST].camera_devices() + camera_devices = hass.data[nest.DATA_NEST].cameras() cameras = [NestCamera(structure, device) for structure, device in camera_devices] add_devices(cameras, True) @@ -43,7 +43,7 @@ def __init__(self, structure, device): self.device = device self._location = None self._name = None - self._is_online = None + self._online = None self._is_streaming = None self._is_video_history_enabled = False # Default to non-NestAware subscribed, but will be fixed during update @@ -76,7 +76,7 @@ def update(self): """Cache value from Python-nest.""" self._location = self.device.where self._name = self.device.name - self._is_online = self.device.is_online + self._online = self.device.online self._is_streaming = self.device.is_streaming self._is_video_history_enabled = self.device.is_video_history_enabled diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/climate/nest.py index e098c3c3709269..32cae5c4cece9b 100644 --- a/homeassistant/components/climate/nest.py +++ b/homeassistant/components/climate/nest.py @@ -40,7 +40,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None): add_devices( [NestThermostat(structure, device, temp_unit) - for structure, device in hass.data[DATA_NEST].devices()], + for structure, device in hass.data[DATA_NEST].thermostats()], True ) diff --git a/homeassistant/components/nest.py b/homeassistant/components/nest.py index cd871c8e03982e..30a256f1c37b21 100644 --- a/homeassistant/components/nest.py +++ b/homeassistant/components/nest.py @@ -19,8 +19,8 @@ REQUIREMENTS = [ 'http://github.com/technicalpickles/python-nest' - '/archive/b8391d2b3cb8682f8b0c2bdff477179983609f39.zip' # nest-cam branch - '#python-nest==3.0.2'] + '/archive/e6c9d56a8df455d4d7746389811f2c1387e8cb33.zip' # nest-cam branch + '#python-nest==3.0.3'] DOMAIN = 'nest' @@ -132,12 +132,12 @@ def __init__(self, hass, conf, nest): self._structure = conf[CONF_STRUCTURE] _LOGGER.debug("Structures to include: %s", self._structure) - def devices(self): - """Generator returning list of devices and their location.""" + def thermostats(self): + """Generator returning list of thermostats and their location.""" try: for structure in self.nest.structures: if structure.name in self._structure: - for device in structure.devices: + for device in structure.thermostats: yield (structure, device) else: _LOGGER.debug("Ignoring structure %s, not in %s", @@ -146,12 +146,12 @@ def devices(self): _LOGGER.error( "Connection error logging into the nest web service.") - def protect_devices(self): - """Generator returning list of protect devices.""" + def smoke_co_alarms(self): + """Generator returning list of smoke co alarams.""" try: for structure in self.nest.structures: if structure.name in self._structure: - for device in structure.protectdevices: + for device in structure.smoke_co_alarms: yield(structure, device) else: _LOGGER.info("Ignoring structure %s, not in %s", @@ -160,12 +160,12 @@ def protect_devices(self): _LOGGER.error( "Connection error logging into the nest web service.") - def camera_devices(self): - """Generator returning list of camera devices.""" + def cameras(self): + """Generator returning list of cameras.""" try: for structure in self.nest.structures: if structure.name in self._structure: - for device in structure.cameradevices: + for device in structure.cameras: yield(structure, device) else: _LOGGER.info("Ignoring structure %s, not in %s", @@ -173,18 +173,3 @@ def camera_devices(self): except socket.error: _LOGGER.error( "Connection error logging into the nest web service.") - - -def is_thermostat(device): - """Target devices that are Nest Thermostats.""" - return bool(device.__class__.__name__ == 'Device') - - -def is_protect(device): - """Target devices that are Nest Protect Smoke Alarms.""" - return bool(device.__class__.__name__ == 'ProtectDevice') - - -def is_camera(device): - """Target devices that are Nest Protect Smoke Alarms.""" - return bool(device.__class__.__name__ == 'CameraDevice') diff --git a/homeassistant/components/sensor/nest.py b/homeassistant/components/sensor/nest.py index f7bbf41cff9d31..a074dcc310d42a 100644 --- a/homeassistant/components/sensor/nest.py +++ b/homeassistant/components/sensor/nest.py @@ -9,7 +9,8 @@ import voluptuous as vol -from homeassistant.components.nest import DATA_NEST, DOMAIN +from homeassistant.components.nest import ( + DATA_NEST, DOMAIN) from homeassistant.helpers.entity import Entity from homeassistant.const import ( TEMP_CELSIUS, TEMP_FAHRENHEIT, CONF_PLATFORM, @@ -93,31 +94,21 @@ def setup_platform(hass, config, add_devices, discovery_info=None): _LOGGER.error(wstr) all_sensors = [] - for structure, device in chain(nest.devices(), nest.protect_devices()): + for structure, device in chain(nest.thermostats(), nest.smoke_co_alarms()): sensors = [NestBasicSensor(structure, device, variable) for variable in conf - if variable in SENSOR_TYPES and is_thermostat(device)] + if variable in SENSOR_TYPES and device.is_thermostat] sensors += [NestTempSensor(structure, device, variable) for variable in conf - if variable in SENSOR_TEMP_TYPES and is_thermostat(device)] + if variable in SENSOR_TEMP_TYPES and device.is_thermostat] sensors += [NestProtectSensor(structure, device, variable) for variable in conf - if variable in PROTECT_VARS and is_protect(device)] + if variable in PROTECT_VARS and device.is_smoke_co_alarm] all_sensors.extend(sensors) add_devices(all_sensors, True) -def is_thermostat(device): - """Target devices that are Nest Thermostats.""" - return bool(device.__class__.__name__ == 'Device') - - -def is_protect(device): - """Target devices that are Nest Protect Smoke Alarms.""" - return bool(device.__class__.__name__ == 'ProtectDevice') - - class NestSensor(Entity): """Representation of a Nest sensor.""" diff --git a/requirements_all.txt b/requirements_all.txt index f39a560f2f8660..e9347202f62a49 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -174,7 +174,7 @@ hikvision==0.4 # http://github.com/adafruit/Adafruit_Python_DHT/archive/310c59b0293354d07d94375f1365f7b9b9110c7d.zip#Adafruit_DHT==1.3.0 # homeassistant.components.nest -http://github.com/technicalpickles/python-nest/archive/b8391d2b3cb8682f8b0c2bdff477179983609f39.zip#python-nest==3.0.2 +http://github.com/technicalpickles/python-nest/archive/e6c9d56a8df455d4d7746389811f2c1387e8cb33.zip#python-nest==3.0.3 # homeassistant.components.light.flux_led https://github.com/Danielhiversen/flux_led/archive/0.10.zip#flux_led==0.10