Compare commits

...

14 Commits

Author SHA1 Message Date
Paulus Schoutsen 308d71c448 Merge pull request #5032 from home-assistant/release-0-35-3
0.35.3
2016-12-23 07:05:54 +01:00
Josh Nichols 203c1cfc96 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
2016-12-22 20:25:43 +01:00
Pascal Vizeli 9bf4a53fbb 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
2016-12-22 16:09:16 +01:00
Pascal Vizeli f18a88c2d4 Bugfix create a task from a task in component update (#5033) 2016-12-21 15:12:26 +01:00
Pascal Vizeli 35b4da0aa2 Bugfix voicerss post api (#5021)
* Bugfix voicerss post api

* fix unittest

* Add cache to service description
2016-12-21 11:49:56 +01:00
pvizeli 61fc4ca8fe Version bump to 0.35.3 2016-12-21 11:47:38 +01:00
Paulus Schoutsen 3ea984ca25 Version bump to 0.35.2 2016-12-18 15:00:37 -08:00
Paulus Schoutsen 1258c4c680 Base url: Fix external port different from internal port (#4990)
* Base url: Fix external port different from internal port

* Add base_url example to new config
2016-12-18 15:00:16 -08:00
Paulus Schoutsen 75dd391118 Merge pull request #4988 from home-assistant/release-0-35-1
0.35.1
2016-12-18 14:11:10 -08:00
Paulus Schoutsen 76a9eba744 Version bump to 0.35.1 2016-12-18 13:00:35 -08:00
Paulus Schoutsen 31fe1d28e8 Gracefully exit with async logger (#4965)
* Gracefully exit with async logger

* Lint
2016-12-18 13:00:21 -08:00
Pascal Vizeli a4a38c8a00 Bugfix TTS clear cache (#4974)
* Bugfix TTS base url with certificate

* fix lint

* remove base_url stuff fix only clear_cache stuff

* cleanup
2016-12-18 13:00:21 -08:00
Paulus Schoutsen 3b74cc606e Allow setting base url (#4985) 2016-12-18 13:00:21 -08:00
Pascal Vizeli b750319de4 Bugfix wait in automation (#4984) 2016-12-18 13:00:21 -08:00
23 changed files with 251 additions and 115 deletions
@@ -115,7 +115,7 @@ def async_setup(hass, config):
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
@@ -166,7 +166,9 @@ def async_setup(hass, config):
for entity in component.async_extract_from_service(service_call):
tasks.append(entity.async_trigger(
service_call.data.get(ATTR_VARIABLES), True))
yield from asyncio.wait(tasks, loop=hass.loop)
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
@asyncio.coroutine
def turn_onoff_service_handler(service_call):
@@ -175,7 +177,9 @@ def async_setup(hass, config):
method = 'async_{}'.format(service_call.service)
for entity in component.async_extract_from_service(service_call):
tasks.append(getattr(entity, method)())
yield from asyncio.wait(tasks, loop=hass.loop)
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
@asyncio.coroutine
def toggle_service_handler(service_call):
@@ -186,7 +190,9 @@ def async_setup(hass, config):
tasks.append(entity.async_turn_off())
else:
tasks.append(entity.async_turn_on())
yield from asyncio.wait(tasks, loop=hass.loop)
if tasks:
yield from asyncio.wait(tasks, loop=hass.loop)
@asyncio.coroutine
def reload_service_handler(service_call):
@@ -13,8 +13,7 @@ from homeassistant.components.binary_sensor import (
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."""
+3 -3
View File
@@ -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 @@ class NestCamera(Camera):
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 @@ class NestCamera(Camera):
"""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
+1 -1
View File
@@ -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
)
+16 -4
View File
@@ -18,7 +18,7 @@ from aiohttp.web_exceptions import HTTPUnauthorized, HTTPMovedPermanently
import homeassistant.helpers.config_validation as cv
import homeassistant.remote as rem
from homeassistant.util import get_local_ip
import homeassistant.util as hass_util
from homeassistant.components import persistent_notification
from homeassistant.const import (
SERVER_PORT, CONTENT_TYPE_JSON, ALLOWED_CORS_HEADERS,
@@ -41,6 +41,7 @@ REQUIREMENTS = ('aiohttp_cors==0.5.0',)
CONF_API_PASSWORD = 'api_password'
CONF_SERVER_HOST = 'server_host'
CONF_SERVER_PORT = 'server_port'
CONF_BASE_URL = 'base_url'
CONF_DEVELOPMENT = 'development'
CONF_SSL_CERTIFICATE = 'ssl_certificate'
CONF_SSL_KEY = 'ssl_key'
@@ -84,6 +85,7 @@ HTTP_SCHEMA = vol.Schema({
vol.Optional(CONF_SERVER_HOST, default=DEFAULT_SERVER_HOST): cv.string,
vol.Optional(CONF_SERVER_PORT, default=SERVER_PORT):
vol.All(vol.Coerce(int), vol.Range(min=1, max=65535)),
vol.Optional(CONF_BASE_URL): cv.string,
vol.Optional(CONF_DEVELOPMENT, default=DEFAULT_DEVELOPMENT): cv.string,
vol.Optional(CONF_SSL_CERTIFICATE, default=None): cv.isfile,
vol.Optional(CONF_SSL_KEY, default=None): cv.isfile,
@@ -155,9 +157,19 @@ def async_setup(hass, config):
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, start_server)
hass.http = server
hass.config.api = rem.API(server_host if server_host != '0.0.0.0'
else get_local_ip(),
api_password, server_port,
host = conf.get(CONF_BASE_URL)
if host:
port = None
elif server_host != DEFAULT_SERVER_HOST:
host = server_host
port = server_port
else:
host = hass_util.get_local_ip()
port = server_port
hass.config.api = rem.API(host, api_password, port,
ssl_certificate is not None)
return True
+1 -1
View File
@@ -253,7 +253,7 @@ def async_setup(hass, config):
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
+11 -26
View File
@@ -19,8 +19,8 @@ _LOGGER = logging.getLogger(__name__)
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 @@ class NestDevice(object):
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 @@ class NestDevice(object):
_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 @@ class NestDevice(object):
_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 @@ class NestDevice(object):
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')
+1 -1
View File
@@ -115,7 +115,7 @@ def async_setup(hass, config):
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
+6 -15
View File
@@ -9,7 +9,8 @@ import logging
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."""
+1 -1
View File
@@ -98,7 +98,7 @@ def async_setup(hass, config):
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
+6 -5
View File
@@ -157,7 +157,8 @@ def async_setup(hass, config):
hass.services.async_register(
DOMAIN, SERVICE_CLEAR_CACHE, async_clear_cache_handle,
descriptions.get(SERVICE_CLEAR_CACHE), schema=SERVICE_CLEAR_CACHE)
descriptions.get(SERVICE_CLEAR_CACHE),
schema=SCHEMA_SERVICE_CLEAR_CACHE)
return True
@@ -170,9 +171,9 @@ class SpeechManager(object):
self.hass = hass
self.providers = {}
self.use_cache = True
self.cache_dir = None
self.time_memory = None
self.use_cache = DEFAULT_CACHE
self.cache_dir = DEFAULT_CACHE_DIR
self.time_memory = DEFAULT_TIME_MEMORY
self.file_cache = {}
self.mem_cache = {}
@@ -229,7 +230,7 @@ class SpeechManager(object):
"""Remove files from filesystem."""
for _, filename in self.file_cache.items():
try:
os.remove(os.path.join(self.cache_dir), filename)
os.remove(os.path.join(self.cache_dir, filename))
except OSError:
pass
+6 -2
View File
@@ -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.
+23 -4
View File
@@ -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)
+7
View File
@@ -54,6 +54,8 @@ frontend:
http:
# Uncomment this to add a password (recommended!)
# api_password: PASSWORD
# Uncomment this if you are using SSL or running in Docker etc
# base_url: example.duckdns.org:8123
# Checks for available updates
updater:
@@ -76,6 +78,11 @@ sun:
# Weather Prediction
sensor:
platform: yr
# Text to speech
tts:
platform: google
"""
+1 -1
View File
@@ -2,7 +2,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 35
PATCH_VERSION = '0'
PATCH_VERSION = '3'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 4, 2)
+2 -3
View File
@@ -298,9 +298,8 @@ class HomeAssistant(object):
# cleanup async layer from python logging
if self.data.get(DATA_ASYNCHANDLER):
handler = self.data.pop(DATA_ASYNCHANDLER)
logger = logging.getLogger('')
handler.close()
logger.removeHandler(handler)
logging.getLogger('').removeHandler(handler)
yield from handler.async_close(blocking=True)
self.loop.stop()
+11 -6
View File
@@ -55,15 +55,20 @@ class API(object):
"""Object to pass around Home Assistant API location and credentials."""
def __init__(self, host: str, api_password: Optional[str]=None,
port: Optional[int]=None, use_ssl: bool=False) -> None:
port: Optional[int]=SERVER_PORT, use_ssl: bool=False) -> None:
"""Initalize the API."""
self.host = host
self.port = port or SERVER_PORT
self.port = port
self.api_password = api_password
if use_ssl:
self.base_url = "https://{}:{}".format(host, self.port)
self.base_url = "https://{}".format(host)
else:
self.base_url = "http://{}:{}".format(host, self.port)
self.base_url = "http://{}".format(host)
if port is not None:
self.base_url += ':{}'.format(port)
self.status = None
self._headers = {
HTTP_HEADER_CONTENT_TYPE: CONTENT_TYPE_JSON,
@@ -106,8 +111,8 @@ class API(object):
def __repr__(self) -> str:
"""Return the representation of the API."""
return "API({}, {}, {})".format(
self.host, self.api_password, self.port)
return "<API({}, password: {})>".format(
self.base_url, 'yes' if self.api_password is not None else 'no')
class HomeAssistant(ha.HomeAssistant):
+27
View File
@@ -49,6 +49,25 @@ class AsyncHandler(object):
"""Wrap close to handler."""
self.emit(None)
@asyncio.coroutine
def async_close(self, blocking=False):
"""Close the handler.
When blocking=True, will wait till closed.
"""
if not self._thread.is_alive():
return
yield from self._queue.put(None)
if blocking:
# Python 3.4.4+
# pylint: disable=no-member
if hasattr(self._queue, 'join'):
yield from self._queue.join()
else:
while not self._queue.empty():
yield from asyncio.sleep(0, loop=self.loop)
def emit(self, record):
"""Process a record."""
ident = self.loop.__dict__.get("_thread_ident")
@@ -66,15 +85,23 @@ class AsyncHandler(object):
def _process(self):
"""Process log in a thread."""
support_join = hasattr(self._queue, 'task_done')
while True:
record = run_coroutine_threadsafe(
self._queue.get(), self.loop).result()
# pylint: disable=no-member
if record is None:
self.handler.close()
if support_join:
self.loop.call_soon_threadsafe(self._queue.task_done)
return
self.handler.emit(record)
if support_join:
self.loop.call_soon_threadsafe(self._queue.task_done)
def createLock(self):
"""Ignore lock stuff."""
+1 -1
View File
@@ -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
+47
View File
@@ -1,6 +1,7 @@
"""The tests for the Home Assistant HTTP component."""
import asyncio
import requests
from unittest.mock import MagicMock
from homeassistant import bootstrap, const
import homeassistant.components.http as http
@@ -154,3 +155,49 @@ def test_registering_view_while_running(hass, test_client):
text = yield from resp.text()
assert text == 'hello'
def test_api_base_url(loop):
"""Test setting api url."""
hass = MagicMock()
hass.loop = loop
assert loop.run_until_complete(
bootstrap.async_setup_component(hass, 'http', {
'http': {
'base_url': 'example.com'
}
})
)
assert hass.config.api.base_url == 'http://example.com'
assert loop.run_until_complete(
bootstrap.async_setup_component(hass, 'http', {
'http': {
'server_host': '1.1.1.1'
}
})
)
assert hass.config.api.base_url == 'http://1.1.1.1:8123'
assert loop.run_until_complete(
bootstrap.async_setup_component(hass, 'http', {
'http': {
'server_host': '1.1.1.1'
}
})
)
assert hass.config.api.base_url == 'http://1.1.1.1:8123'
assert loop.run_until_complete(
bootstrap.async_setup_component(hass, 'http', {
'http': {
}
})
)
assert hass.config.api.base_url == 'http://127.0.0.1:8123'
+22 -22
View File
@@ -88,35 +88,35 @@ class TestTTS(object):
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_demo.mp3"))
def test_setup_component_and_test_service_clear_cache(self):
"""Setup the demo platform and call service clear cache."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
def test_setup_component_and_test_service_clear_cache(self):
"""Setup the demo platform and call service clear cache."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
tts.DOMAIN: {
'platform': 'demo',
}
config = {
tts.DOMAIN: {
'platform': 'demo',
}
}
with assert_setup_component(1, tts.DOMAIN):
setup_component(self.hass, tts.DOMAIN, config)
with assert_setup_component(1, tts.DOMAIN):
setup_component(self.hass, tts.DOMAIN, config)
self.hass.services.call(tts.DOMAIN, 'demo_say', {
tts.ATTR_MESSAGE: "I person is on front of your door.",
})
self.hass.block_till_done()
self.hass.services.call(tts.DOMAIN, 'demo_say', {
tts.ATTR_MESSAGE: "I person is on front of your door.",
})
self.hass.block_till_done()
assert len(calls) == 1
assert os.path.isfile(os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_demo.mp3"))
assert len(calls) == 1
assert os.path.isfile(os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_demo.mp3"))
self.hass.services.call(tts.DOMAIN, tts.SERVICE_CLEAR_CACHE, {})
self.hass.block_till_done()
self.hass.services.call(tts.DOMAIN, tts.SERVICE_CLEAR_CACHE, {})
self.hass.block_till_done()
assert not os.path.isfile(os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_demo.mp3"))
assert not os.path.isfile(os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_demo.mp3"))
def test_setup_component_and_test_service_with_receive_voice(self):
"""Setup the demo platform and call service and receive voice."""
+39 -6
View File
@@ -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