mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Warn when casting of tts fails (#38603)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
ee043d8614
commit
91ba8c0ef0
@ -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"]
|
||||
}
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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]
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user