Warn when casting of tts fails (#38603)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Erik Montnemery 2020-08-21 17:17:36 +02:00 committed by GitHub
parent ee043d8614
commit 91ba8c0ef0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 178 additions and 5 deletions

View File

@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/cast",
"requirements": ["pychromecast==7.2.1"],
"after_dependencies": ["cloud","zeroconf"],
"after_dependencies": ["cloud","tts","zeroconf"],
"zeroconf": ["_googlecast._tcp.local."],
"codeowners": ["@emontnemery"]
}

View File

@ -44,6 +44,7 @@ from homeassistant.core import callback
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.network import NoURLAvailableError, get_url
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
import homeassistant.util.dt as dt_util
from homeassistant.util.logging import async_create_catching_coro
@ -339,6 +340,48 @@ class CastDevice(MediaPlayerEntity):
def new_media_status(self, media_status):
"""Handle updates of the media status."""
if (
media_status
and media_status.player_is_idle
and media_status.idle_reason == "ERROR"
):
external_url = None
internal_url = None
tts_base_url = None
url_description = ""
if "tts" in self.hass.config.components:
try:
tts_base_url = self.hass.components.tts.get_base_url(self.hass)
except KeyError:
# base_url not configured, ignore
pass
try:
external_url = get_url(self.hass, allow_internal=False)
except NoURLAvailableError:
# external_url not configured, ignore
pass
try:
internal_url = get_url(self.hass, allow_external=False)
except NoURLAvailableError:
# internal_url not configured, ignore
pass
if media_status.content_id:
if tts_base_url and media_status.content_id.startswith(tts_base_url):
url_description = f" from tts.base_url ({tts_base_url})"
if external_url and media_status.content_id.startswith(external_url):
url_description = " from external_url ({external_url})"
if internal_url and media_status.content_id.startswith(internal_url):
url_description = " from internal_url ({internal_url})"
_LOGGER.error(
"Failed to cast media %s%s. Please make sure the URL is: "
"Reachable from the cast device and either a publicly resolvable "
"hostname or an IP address.",
media_status.content_id,
url_description,
)
self.media_status = media_status
self.media_status_received = dt_util.utcnow()
self.schedule_update_ha_state()
@ -387,7 +430,7 @@ class CastDevice(MediaPlayerEntity):
# ========== Service Calls ==========
def _media_controller(self):
"""
Return media status.
Return media controller.
First try from our own cast, then groups which our cast is a member in.
"""

View File

@ -46,6 +46,8 @@ ATTR_MESSAGE = "message"
ATTR_OPTIONS = "options"
ATTR_PLATFORM = "platform"
BASE_URL_KEY = "tts_base_url"
CONF_BASE_URL = "base_url"
CONF_CACHE = "cache"
CONF_CACHE_DIR = "cache_dir"
@ -115,10 +117,11 @@ async def async_setup(hass, config):
cache_dir = conf.get(CONF_CACHE_DIR, DEFAULT_CACHE_DIR)
time_memory = conf.get(CONF_TIME_MEMORY, DEFAULT_TIME_MEMORY)
base_url = conf.get(CONF_BASE_URL) or get_url(hass)
hass.data[BASE_URL_KEY] = base_url
await tts.async_init_cache(use_cache, cache_dir, time_memory, base_url)
except (HomeAssistantError, KeyError) as err:
_LOGGER.error("Error on cache init %s", err)
except (HomeAssistantError, KeyError):
_LOGGER.exception("Error on cache init")
return False
hass.http.register_view(TextToSpeechView(tts))
@ -592,3 +595,8 @@ class TextToSpeechView(HomeAssistantView):
return web.Response(status=HTTP_NOT_FOUND)
return web.Response(body=data, content_type=content)
def get_base_url(hass):
"""Get base URL."""
return hass.data[BASE_URL_KEY]

View File

@ -6,15 +6,17 @@ from uuid import UUID
import attr
import pytest
from homeassistant.components import tts
from homeassistant.components.cast import media_player as cast
from homeassistant.components.cast.media_player import ChromecastInfo
from homeassistant.config import async_process_ha_core_config
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.setup import async_setup_component
from tests.async_mock import AsyncMock, MagicMock, Mock, patch
from tests.common import MockConfigEntry
from tests.common import MockConfigEntry, assert_setup_component
@pytest.fixture(autouse=True)
@ -577,6 +579,126 @@ async def test_group_media_control(hass: HomeAssistantType):
assert chromecast.media_controller.play_media.called
async def test_failed_cast_on_idle(hass, caplog):
"""Test no warning when unless player went idle with reason "ERROR"."""
info = get_fake_chromecast_info()
chromecast, entity = await async_setup_media_player_cast(hass, info)
media_status = MagicMock(images=None)
media_status.player_is_idle = False
media_status.idle_reason = "ERROR"
media_status.content_id = "http://example.com:8123/tts.mp3"
entity.new_media_status(media_status)
assert "Failed to cast media" not in caplog.text
media_status = MagicMock(images=None)
media_status.player_is_idle = True
media_status.idle_reason = "Other"
media_status.content_id = "http://example.com:8123/tts.mp3"
entity.new_media_status(media_status)
assert "Failed to cast media" not in caplog.text
media_status = MagicMock(images=None)
media_status.player_is_idle = True
media_status.idle_reason = "ERROR"
media_status.content_id = "http://example.com:8123/tts.mp3"
entity.new_media_status(media_status)
assert "Failed to cast media http://example.com:8123/tts.mp3." in caplog.text
async def test_failed_cast_other_url(hass, caplog):
"""Test warning when casting from internal_url fails."""
with assert_setup_component(1, tts.DOMAIN):
assert await async_setup_component(
hass,
tts.DOMAIN,
{tts.DOMAIN: {"platform": "demo", "base_url": "http://example.local:8123"}},
)
info = get_fake_chromecast_info()
chromecast, entity = await async_setup_media_player_cast(hass, info)
media_status = MagicMock(images=None)
media_status.player_is_idle = True
media_status.idle_reason = "ERROR"
media_status.content_id = "http://example.com:8123/tts.mp3"
entity.new_media_status(media_status)
assert "Failed to cast media http://example.com:8123/tts.mp3." in caplog.text
async def test_failed_cast_internal_url(hass, caplog):
"""Test warning when casting from internal_url fails."""
await async_process_ha_core_config(
hass, {"internal_url": "http://example.local:8123"},
)
with assert_setup_component(1, tts.DOMAIN):
assert await async_setup_component(
hass, tts.DOMAIN, {tts.DOMAIN: {"platform": "demo"}}
)
info = get_fake_chromecast_info()
chromecast, entity = await async_setup_media_player_cast(hass, info)
media_status = MagicMock(images=None)
media_status.player_is_idle = True
media_status.idle_reason = "ERROR"
media_status.content_id = "http://example.local:8123/tts.mp3"
entity.new_media_status(media_status)
assert (
"Failed to cast media http://example.local:8123/tts.mp3 from internal_url"
in caplog.text
)
async def test_failed_cast_external_url(hass, caplog):
"""Test warning when casting from external_url fails."""
await async_process_ha_core_config(
hass, {"external_url": "http://example.com:8123"},
)
with assert_setup_component(1, tts.DOMAIN):
assert await async_setup_component(
hass,
tts.DOMAIN,
{tts.DOMAIN: {"platform": "demo", "base_url": "http://example.com:8123"}},
)
info = get_fake_chromecast_info()
chromecast, entity = await async_setup_media_player_cast(hass, info)
media_status = MagicMock(images=None)
media_status.player_is_idle = True
media_status.idle_reason = "ERROR"
media_status.content_id = "http://example.com:8123/tts.mp3"
entity.new_media_status(media_status)
assert (
"Failed to cast media http://example.com:8123/tts.mp3 from external_url"
in caplog.text
)
async def test_failed_cast_tts_base_url(hass, caplog):
"""Test warning when casting from tts.base_url fails."""
with assert_setup_component(1, tts.DOMAIN):
assert await async_setup_component(
hass,
tts.DOMAIN,
{tts.DOMAIN: {"platform": "demo", "base_url": "http://example.local:8123"}},
)
info = get_fake_chromecast_info()
chromecast, entity = await async_setup_media_player_cast(hass, info)
media_status = MagicMock(images=None)
media_status.player_is_idle = True
media_status.idle_reason = "ERROR"
media_status.content_id = "http://example.local:8123/tts.mp3"
entity.new_media_status(media_status)
assert (
"Failed to cast media http://example.local:8123/tts.mp3 from tts.base_url"
in caplog.text
)
async def test_disconnect_on_stop(hass: HomeAssistantType):
"""Test cast device disconnects socket on stop."""
info = get_fake_chromecast_info()