diff --git a/homeassistant/components/tts/services.yaml b/homeassistant/components/tts/services.yaml index aba1334da87..5cb146950b4 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 728a1996a5d..fdbe8a8d806 100644 --- a/homeassistant/components/tts/voicerss.py +++ b/homeassistant/components/tts/voicerss.py @@ -21,6 +21,18 @@ _LOGGER = logging.getLogger(__name__) 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 @@ class VoiceRSSProvider(Provider): 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 @@ class VoiceRSSProvider(Provider): 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 44ce0d6739f..ea1263b189e 100644 --- a/tests/components/tts/test_voicerss.py +++ b/tests/components/tts/test_voicerss.py @@ -20,11 +20,12 @@ class TestTTSVoiceRSSPlatform(object): 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 @@ class TestTTSVoiceRSSPlatform(object): 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 @@ class TestTTSVoiceRSSPlatform(object): 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 @@ class TestTTSVoiceRSSPlatform(object): 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 @@ class TestTTSVoiceRSSPlatform(object): 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 @@ class TestTTSVoiceRSSPlatform(object): 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