mirror of
https://github.com/home-assistant/core.git
synced 2025-07-18 18:57:06 +00:00
Add preannounce media id support for ESPHome (#141474)
* Working on preannounce media id support for ESPHome * Fix test * Update tests
This commit is contained in:
parent
febc455bc5
commit
220aaf93c6
@ -370,8 +370,10 @@ class EsphomeAssistSatellite(
|
|||||||
announcement.media_id,
|
announcement.media_id,
|
||||||
)
|
)
|
||||||
media_id = announcement.media_id
|
media_id = announcement.media_id
|
||||||
if announcement.media_id_source != "tts":
|
is_media_tts = announcement.media_id_source == "tts"
|
||||||
# Route non-TTS media through the proxy
|
preannounce_media_id = announcement.preannounce_media_id
|
||||||
|
if (not is_media_tts) or preannounce_media_id:
|
||||||
|
# Route media through the proxy
|
||||||
format_to_use: MediaPlayerSupportedFormat | None = None
|
format_to_use: MediaPlayerSupportedFormat | None = None
|
||||||
for supported_format in chain(
|
for supported_format in chain(
|
||||||
*self.entry_data.media_player_formats.values()
|
*self.entry_data.media_player_formats.values()
|
||||||
@ -384,22 +386,33 @@ class EsphomeAssistSatellite(
|
|||||||
assert (self.registry_entry is not None) and (
|
assert (self.registry_entry is not None) and (
|
||||||
self.registry_entry.device_id is not None
|
self.registry_entry.device_id is not None
|
||||||
)
|
)
|
||||||
proxy_url = async_create_proxy_url(
|
|
||||||
self.hass,
|
make_proxy_url = partial(
|
||||||
self.registry_entry.device_id,
|
async_create_proxy_url,
|
||||||
media_id,
|
hass=self.hass,
|
||||||
|
device_id=self.registry_entry.device_id,
|
||||||
media_format=format_to_use.format,
|
media_format=format_to_use.format,
|
||||||
rate=format_to_use.sample_rate or None,
|
rate=format_to_use.sample_rate or None,
|
||||||
channels=format_to_use.num_channels or None,
|
channels=format_to_use.num_channels or None,
|
||||||
width=format_to_use.sample_bytes or None,
|
width=format_to_use.sample_bytes or None,
|
||||||
)
|
)
|
||||||
media_id = async_process_play_media_url(self.hass, proxy_url)
|
|
||||||
|
if not is_media_tts:
|
||||||
|
media_id = async_process_play_media_url(
|
||||||
|
self.hass, make_proxy_url(media_url=media_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
if preannounce_media_id:
|
||||||
|
preannounce_media_id = async_process_play_media_url(
|
||||||
|
self.hass, make_proxy_url(media_url=preannounce_media_id)
|
||||||
|
)
|
||||||
|
|
||||||
await self.cli.send_voice_assistant_announcement_await_response(
|
await self.cli.send_voice_assistant_announcement_await_response(
|
||||||
media_id,
|
media_id,
|
||||||
_ANNOUNCEMENT_TIMEOUT_SEC,
|
_ANNOUNCEMENT_TIMEOUT_SEC,
|
||||||
announcement.message,
|
announcement.message,
|
||||||
start_conversation=run_pipeline_after,
|
start_conversation=run_pipeline_after,
|
||||||
|
preannounce_media_id=preannounce_media_id or "",
|
||||||
)
|
)
|
||||||
|
|
||||||
async def handle_pipeline_start(
|
async def handle_pipeline_start(
|
||||||
|
@ -1212,12 +1212,17 @@ async def test_announce_message(
|
|||||||
done = asyncio.Event()
|
done = asyncio.Event()
|
||||||
|
|
||||||
async def send_voice_assistant_announcement_await_response(
|
async def send_voice_assistant_announcement_await_response(
|
||||||
media_id: str, timeout: float, text: str, start_conversation: bool
|
media_id: str,
|
||||||
|
timeout: float,
|
||||||
|
text: str,
|
||||||
|
start_conversation: bool,
|
||||||
|
preannounce_media_id: str | None = None,
|
||||||
):
|
):
|
||||||
assert satellite.state == AssistSatelliteState.RESPONDING
|
assert satellite.state == AssistSatelliteState.RESPONDING
|
||||||
assert media_id == "http://10.10.10.10:8123/api/tts_proxy/test-token"
|
assert media_id == "http://10.10.10.10:8123/api/tts_proxy/test-token"
|
||||||
assert text == "test-text"
|
assert text == "test-text"
|
||||||
assert not start_conversation
|
assert not start_conversation
|
||||||
|
assert not preannounce_media_id
|
||||||
|
|
||||||
done.set()
|
done.set()
|
||||||
|
|
||||||
@ -1302,11 +1307,16 @@ async def test_announce_media_id(
|
|||||||
done = asyncio.Event()
|
done = asyncio.Event()
|
||||||
|
|
||||||
async def send_voice_assistant_announcement_await_response(
|
async def send_voice_assistant_announcement_await_response(
|
||||||
media_id: str, timeout: float, text: str, start_conversation: bool
|
media_id: str,
|
||||||
|
timeout: float,
|
||||||
|
text: str,
|
||||||
|
start_conversation: bool,
|
||||||
|
preannounce_media_id: str | None = None,
|
||||||
):
|
):
|
||||||
assert satellite.state == AssistSatelliteState.RESPONDING
|
assert satellite.state == AssistSatelliteState.RESPONDING
|
||||||
assert media_id == "https://www.home-assistant.io/proxied.flac"
|
assert media_id == "https://www.home-assistant.io/proxied.flac"
|
||||||
assert not start_conversation
|
assert not start_conversation
|
||||||
|
assert not preannounce_media_id
|
||||||
|
|
||||||
done.set()
|
done.set()
|
||||||
|
|
||||||
@ -1335,9 +1345,9 @@ async def test_announce_media_id(
|
|||||||
assert satellite.state == AssistSatelliteState.IDLE
|
assert satellite.state == AssistSatelliteState.IDLE
|
||||||
|
|
||||||
mock_async_create_proxy_url.assert_called_once_with(
|
mock_async_create_proxy_url.assert_called_once_with(
|
||||||
hass,
|
hass=hass,
|
||||||
dev.id,
|
device_id=dev.id,
|
||||||
"https://www.home-assistant.io/resolved.mp3",
|
media_url="https://www.home-assistant.io/resolved.mp3",
|
||||||
media_format="flac",
|
media_format="flac",
|
||||||
rate=48000,
|
rate=48000,
|
||||||
channels=2,
|
channels=2,
|
||||||
@ -1345,6 +1355,83 @@ async def test_announce_media_id(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_announce_message_with_preannounce(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_client: APIClient,
|
||||||
|
mock_esphome_device: Callable[
|
||||||
|
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
|
||||||
|
Awaitable[MockESPHomeDevice],
|
||||||
|
],
|
||||||
|
) -> None:
|
||||||
|
"""Test announcement with message and preannounce media id."""
|
||||||
|
mock_device: MockESPHomeDevice = await mock_esphome_device(
|
||||||
|
mock_client=mock_client,
|
||||||
|
entity_info=[],
|
||||||
|
user_service=[],
|
||||||
|
states=[],
|
||||||
|
device_info={
|
||||||
|
"voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
|
||||||
|
| VoiceAssistantFeature.SPEAKER
|
||||||
|
| VoiceAssistantFeature.API_AUDIO
|
||||||
|
| VoiceAssistantFeature.ANNOUNCE
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
|
||||||
|
assert satellite is not None
|
||||||
|
|
||||||
|
done = asyncio.Event()
|
||||||
|
|
||||||
|
async def send_voice_assistant_announcement_await_response(
|
||||||
|
media_id: str,
|
||||||
|
timeout: float,
|
||||||
|
text: str,
|
||||||
|
start_conversation: bool,
|
||||||
|
preannounce_media_id: str | None = None,
|
||||||
|
):
|
||||||
|
assert satellite.state == AssistSatelliteState.RESPONDING
|
||||||
|
assert media_id == "http://10.10.10.10:8123/api/tts_proxy/test-token"
|
||||||
|
assert text == "test-text"
|
||||||
|
assert not start_conversation
|
||||||
|
assert preannounce_media_id == "test-preannounce"
|
||||||
|
|
||||||
|
done.set()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.tts.generate_media_source_id",
|
||||||
|
return_value="media-source://bla",
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.tts.async_resolve_engine",
|
||||||
|
return_value="tts.cloud_tts",
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.tts.async_create_stream",
|
||||||
|
return_value=MockResultStream(hass, "wav", b""),
|
||||||
|
),
|
||||||
|
patch.object(
|
||||||
|
mock_client,
|
||||||
|
"send_voice_assistant_announcement_await_response",
|
||||||
|
new=send_voice_assistant_announcement_await_response,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
async with asyncio.timeout(1):
|
||||||
|
await hass.services.async_call(
|
||||||
|
assist_satellite.DOMAIN,
|
||||||
|
"announce",
|
||||||
|
{
|
||||||
|
"entity_id": satellite.entity_id,
|
||||||
|
"message": "test-text",
|
||||||
|
"preannounce_media_id": "test-preannounce",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await done.wait()
|
||||||
|
assert satellite.state == AssistSatelliteState.IDLE
|
||||||
|
|
||||||
|
|
||||||
async def test_start_conversation_supported_features(
|
async def test_start_conversation_supported_features(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_client: APIClient,
|
mock_client: APIClient,
|
||||||
@ -1417,12 +1504,17 @@ async def test_start_conversation_message(
|
|||||||
done = asyncio.Event()
|
done = asyncio.Event()
|
||||||
|
|
||||||
async def send_voice_assistant_announcement_await_response(
|
async def send_voice_assistant_announcement_await_response(
|
||||||
media_id: str, timeout: float, text: str, start_conversation: bool
|
media_id: str,
|
||||||
|
timeout: float,
|
||||||
|
text: str,
|
||||||
|
start_conversation: bool,
|
||||||
|
preannounce_media_id: str,
|
||||||
):
|
):
|
||||||
assert satellite.state == AssistSatelliteState.RESPONDING
|
assert satellite.state == AssistSatelliteState.RESPONDING
|
||||||
assert media_id == "http://10.10.10.10:8123/api/tts_proxy/test-token"
|
assert media_id == "http://10.10.10.10:8123/api/tts_proxy/test-token"
|
||||||
assert text == "test-text"
|
assert text == "test-text"
|
||||||
assert start_conversation
|
assert start_conversation
|
||||||
|
assert not preannounce_media_id
|
||||||
|
|
||||||
done.set()
|
done.set()
|
||||||
|
|
||||||
@ -1526,11 +1618,16 @@ async def test_start_conversation_media_id(
|
|||||||
done = asyncio.Event()
|
done = asyncio.Event()
|
||||||
|
|
||||||
async def send_voice_assistant_announcement_await_response(
|
async def send_voice_assistant_announcement_await_response(
|
||||||
media_id: str, timeout: float, text: str, start_conversation: bool
|
media_id: str,
|
||||||
|
timeout: float,
|
||||||
|
text: str,
|
||||||
|
start_conversation: bool,
|
||||||
|
preannounce_media_id: str,
|
||||||
):
|
):
|
||||||
assert satellite.state == AssistSatelliteState.RESPONDING
|
assert satellite.state == AssistSatelliteState.RESPONDING
|
||||||
assert media_id == "https://www.home-assistant.io/proxied.flac"
|
assert media_id == "https://www.home-assistant.io/proxied.flac"
|
||||||
assert start_conversation
|
assert start_conversation
|
||||||
|
assert not preannounce_media_id
|
||||||
|
|
||||||
done.set()
|
done.set()
|
||||||
|
|
||||||
@ -1563,9 +1660,9 @@ async def test_start_conversation_media_id(
|
|||||||
assert satellite.state == AssistSatelliteState.IDLE
|
assert satellite.state == AssistSatelliteState.IDLE
|
||||||
|
|
||||||
mock_async_create_proxy_url.assert_called_once_with(
|
mock_async_create_proxy_url.assert_called_once_with(
|
||||||
hass,
|
hass=hass,
|
||||||
dev.id,
|
device_id=dev.id,
|
||||||
"https://www.home-assistant.io/resolved.mp3",
|
media_url="https://www.home-assistant.io/resolved.mp3",
|
||||||
media_format="flac",
|
media_format="flac",
|
||||||
rate=48000,
|
rate=48000,
|
||||||
channels=2,
|
channels=2,
|
||||||
@ -1573,6 +1670,102 @@ async def test_start_conversation_media_id(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_start_conversation_message_with_preannounce(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_client: APIClient,
|
||||||
|
mock_esphome_device: Callable[
|
||||||
|
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
|
||||||
|
Awaitable[MockESPHomeDevice],
|
||||||
|
],
|
||||||
|
) -> None:
|
||||||
|
"""Test start conversation with message and preannounce media id."""
|
||||||
|
mock_device: MockESPHomeDevice = await mock_esphome_device(
|
||||||
|
mock_client=mock_client,
|
||||||
|
entity_info=[],
|
||||||
|
user_service=[],
|
||||||
|
states=[],
|
||||||
|
device_info={
|
||||||
|
"voice_assistant_feature_flags": VoiceAssistantFeature.VOICE_ASSISTANT
|
||||||
|
| VoiceAssistantFeature.SPEAKER
|
||||||
|
| VoiceAssistantFeature.API_AUDIO
|
||||||
|
| VoiceAssistantFeature.ANNOUNCE
|
||||||
|
| VoiceAssistantFeature.START_CONVERSATION
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
|
||||||
|
assert satellite is not None
|
||||||
|
|
||||||
|
pipeline = assist_pipeline.Pipeline(
|
||||||
|
conversation_engine="test engine",
|
||||||
|
conversation_language="en",
|
||||||
|
language="en",
|
||||||
|
name="test pipeline",
|
||||||
|
stt_engine="test stt",
|
||||||
|
stt_language="en",
|
||||||
|
tts_engine="test tts",
|
||||||
|
tts_language="en",
|
||||||
|
tts_voice=None,
|
||||||
|
wake_word_entity=None,
|
||||||
|
wake_word_id=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
done = asyncio.Event()
|
||||||
|
|
||||||
|
async def send_voice_assistant_announcement_await_response(
|
||||||
|
media_id: str,
|
||||||
|
timeout: float,
|
||||||
|
text: str,
|
||||||
|
start_conversation: bool,
|
||||||
|
preannounce_media_id: str,
|
||||||
|
):
|
||||||
|
assert satellite.state == AssistSatelliteState.RESPONDING
|
||||||
|
assert media_id == "http://10.10.10.10:8123/api/tts_proxy/test-token"
|
||||||
|
assert text == "test-text"
|
||||||
|
assert start_conversation
|
||||||
|
assert preannounce_media_id == "test-preannounce"
|
||||||
|
|
||||||
|
done.set()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.tts.generate_media_source_id",
|
||||||
|
return_value="media-source://bla",
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.tts.async_resolve_engine",
|
||||||
|
return_value="tts.cloud_tts",
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.tts.async_create_stream",
|
||||||
|
return_value=MockResultStream(hass, "wav", b""),
|
||||||
|
),
|
||||||
|
patch.object(
|
||||||
|
mock_client,
|
||||||
|
"send_voice_assistant_announcement_await_response",
|
||||||
|
new=send_voice_assistant_announcement_await_response,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.assist_satellite.entity.async_get_pipeline",
|
||||||
|
return_value=pipeline,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
async with asyncio.timeout(1):
|
||||||
|
await hass.services.async_call(
|
||||||
|
assist_satellite.DOMAIN,
|
||||||
|
"start_conversation",
|
||||||
|
{
|
||||||
|
"entity_id": satellite.entity_id,
|
||||||
|
"start_message": "test-text",
|
||||||
|
"preannounce_media_id": "test-preannounce",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await done.wait()
|
||||||
|
assert satellite.state == AssistSatelliteState.IDLE
|
||||||
|
|
||||||
|
|
||||||
async def test_satellite_unloaded_on_disconnect(
|
async def test_satellite_unloaded_on_disconnect(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_client: APIClient,
|
mock_client: APIClient,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user