From 3de701a9abfd421b06bfeccd1657c615837575c9 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Fri, 12 Sep 2025 13:40:16 -0500 Subject: [PATCH] Acknowledge if targets in same area (#150655) Co-authored-by: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com> --- .../assist_pipeline/acknowledge.mp3 | Bin 0 -> 50991 bytes .../components/assist_pipeline/const.py | 4 + .../components/assist_pipeline/pipeline.py | 95 +++++- .../assist_pipeline/snapshots/test_init.ambr | 4 + .../snapshots/test_pipeline.ambr | 3 + .../snapshots/test_websocket.ambr | 4 + .../assist_pipeline/test_pipeline.py | 309 +++++++++++++++++- 7 files changed, 405 insertions(+), 14 deletions(-) create mode 100644 homeassistant/components/assist_pipeline/acknowledge.mp3 diff --git a/homeassistant/components/assist_pipeline/acknowledge.mp3 b/homeassistant/components/assist_pipeline/acknowledge.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..1709ff20bc2b7c49bb8335fbc5a444b7c1f3d67e GIT binary patch literal 50991 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 str: - """Run intent recognition portion of pipeline. Returns text to speak.""" + ) -> tuple[str, bool]: + """Run intent recognition portion of pipeline. + + Returns (speech, all_targets_in_satellite_area). + """ if self.intent_agent is None or self._conversation_data is None: raise RuntimeError("Recognize intent was not prepared") @@ -1116,6 +1126,7 @@ class PipelineRun: agent_id = self.intent_agent.id processed_locally = agent_id == conversation.HOME_ASSISTANT_AGENT + all_targets_in_satellite_area = False intent_response: intent.IntentResponse | None = None if not processed_locally and not self._intent_agent_only: # Sentence triggers override conversation agent @@ -1290,6 +1301,17 @@ class PipelineRun: if tts_input_stream and self._streamed_response_text: tts_input_stream.put_nowait(None) + if agent_id == conversation.HOME_ASSISTANT_AGENT: + # Check if all targeted entities were in the same area as + # the satellite device. + # If so, the satellite should respond with an acknowledge beep + # instead of a full response. + all_targets_in_satellite_area = ( + self._get_all_targets_in_satellite_area( + conversation_result.response, self._device_id + ) + ) + except Exception as src_error: _LOGGER.exception("Unexpected error during intent recognition") raise IntentRecognitionError( @@ -1312,7 +1334,45 @@ class PipelineRun: if conversation_result.continue_conversation: self._conversation_data.continue_conversation_agent = agent_id - return speech + return (speech, all_targets_in_satellite_area) + + def _get_all_targets_in_satellite_area( + self, intent_response: intent.IntentResponse, device_id: str | None + ) -> bool: + """Return true if all targeted entities were in the same area as the device.""" + if ( + (intent_response.response_type != intent.IntentResponseType.ACTION_DONE) + or (not intent_response.matched_states) + or (not device_id) + ): + return False + + device_registry = dr.async_get(self.hass) + + if (not (device := device_registry.async_get(device_id))) or ( + not device.area_id + ): + return False + + entity_registry = er.async_get(self.hass) + for state in intent_response.matched_states: + entity = entity_registry.async_get(state.entity_id) + if not entity: + return False + + if (entity_area_id := entity.area_id) is None: + if (entity.device_id is None) or ( + (entity_device := device_registry.async_get(entity.device_id)) + is None + ): + return False + + entity_area_id = entity_device.area_id + + if entity_area_id != device.area_id: + return False + + return True async def prepare_text_to_speech(self) -> None: """Prepare text-to-speech.""" @@ -1350,7 +1410,9 @@ class PipelineRun: ), ) from err - async def text_to_speech(self, tts_input: str) -> None: + async def text_to_speech( + self, tts_input: str, override_media_path: Path | None = None + ) -> None: """Run text-to-speech portion of pipeline.""" assert self.tts_stream is not None @@ -1362,11 +1424,14 @@ class PipelineRun: "language": self.pipeline.tts_language, "voice": self.pipeline.tts_voice, "tts_input": tts_input, + "acknowledge_override": override_media_path is not None, }, ) ) - if not self._streamed_response_text: + if override_media_path: + self.tts_stream.async_override_result(override_media_path) + elif not self._streamed_response_text: self.tts_stream.async_set_message(tts_input) tts_output = { @@ -1664,16 +1729,20 @@ class PipelineInput: if self.run.end_stage != PipelineStage.STT: tts_input = self.tts_input + all_targets_in_satellite_area = False if current_stage == PipelineStage.INTENT: # intent-recognition assert intent_input is not None - tts_input = await self.run.recognize_intent( + ( + tts_input, + all_targets_in_satellite_area, + ) = await self.run.recognize_intent( intent_input, self.session.conversation_id, self.conversation_extra_system_prompt, ) - if tts_input.strip(): + if all_targets_in_satellite_area or tts_input.strip(): current_stage = PipelineStage.TTS else: # Skip TTS @@ -1682,8 +1751,14 @@ class PipelineInput: if self.run.end_stage != PipelineStage.INTENT: # text-to-speech if current_stage == PipelineStage.TTS: - assert tts_input is not None - await self.run.text_to_speech(tts_input) + if all_targets_in_satellite_area: + # Use acknowledge media instead of full response + await self.run.text_to_speech( + tts_input or "", override_media_path=ACKNOWLEDGE_PATH + ) + else: + assert tts_input is not None + await self.run.text_to_speech(tts_input) except PipelineError as err: self.run.process_event( diff --git a/tests/components/assist_pipeline/snapshots/test_init.ambr b/tests/components/assist_pipeline/snapshots/test_init.ambr index 56ca8bde0ba..5e77b7e9291 100644 --- a/tests/components/assist_pipeline/snapshots/test_init.ambr +++ b/tests/components/assist_pipeline/snapshots/test_init.ambr @@ -76,6 +76,7 @@ }), dict({ 'data': dict({ + 'acknowledge_override': False, 'engine': 'tts.test', 'language': 'en_US', 'tts_input': "Sorry, I couldn't understand that", @@ -177,6 +178,7 @@ }), dict({ 'data': dict({ + 'acknowledge_override': False, 'engine': 'test', 'language': 'en-US', 'tts_input': "Sorry, I couldn't understand that", @@ -278,6 +280,7 @@ }), dict({ 'data': dict({ + 'acknowledge_override': False, 'engine': 'test', 'language': 'en-US', 'tts_input': "Sorry, I couldn't understand that", @@ -403,6 +406,7 @@ }), dict({ 'data': dict({ + 'acknowledge_override': False, 'engine': 'tts.test', 'language': 'en_US', 'tts_input': "Sorry, I couldn't understand that", diff --git a/tests/components/assist_pipeline/snapshots/test_pipeline.ambr b/tests/components/assist_pipeline/snapshots/test_pipeline.ambr index 7a51eddf8d6..e92f3aec3fb 100644 --- a/tests/components/assist_pipeline/snapshots/test_pipeline.ambr +++ b/tests/components/assist_pipeline/snapshots/test_pipeline.ambr @@ -131,6 +131,7 @@ }), dict({ 'data': dict({ + 'acknowledge_override': False, 'engine': 'tts.test', 'language': 'en_US', 'tts_input': 'hello, how are you?', @@ -365,6 +366,7 @@ }), dict({ 'data': dict({ + 'acknowledge_override': False, 'engine': 'tts.test', 'language': 'en_US', 'tts_input': "hello, how are you? I'm doing well, thank you. What about you?!", @@ -595,6 +597,7 @@ }), dict({ 'data': dict({ + 'acknowledge_override': False, 'engine': 'tts.test', 'language': 'en_US', 'tts_input': "I'm doing well, thank you.", diff --git a/tests/components/assist_pipeline/snapshots/test_websocket.ambr b/tests/components/assist_pipeline/snapshots/test_websocket.ambr index 5e0d915a77e..5b5ed44e24d 100644 --- a/tests/components/assist_pipeline/snapshots/test_websocket.ambr +++ b/tests/components/assist_pipeline/snapshots/test_websocket.ambr @@ -73,6 +73,7 @@ # --- # name: test_audio_pipeline.5 dict({ + 'acknowledge_override': False, 'engine': 'tts.test', 'language': 'en_US', 'tts_input': "Sorry, I couldn't understand that", @@ -166,6 +167,7 @@ # --- # name: test_audio_pipeline_debug.5 dict({ + 'acknowledge_override': False, 'engine': 'tts.test', 'language': 'en_US', 'tts_input': "Sorry, I couldn't understand that", @@ -271,6 +273,7 @@ # --- # name: test_audio_pipeline_with_enhancements.5 dict({ + 'acknowledge_override': False, 'engine': 'tts.test', 'language': 'en_US', 'tts_input': "Sorry, I couldn't understand that", @@ -386,6 +389,7 @@ # --- # name: test_audio_pipeline_with_wake_word_no_timeout.7 dict({ + 'acknowledge_override': False, 'engine': 'tts.test', 'language': 'en_US', 'tts_input': "Sorry, I couldn't understand that", diff --git a/tests/components/assist_pipeline/test_pipeline.py b/tests/components/assist_pipeline/test_pipeline.py index 75234122368..fe82f693fde 100644 --- a/tests/components/assist_pipeline/test_pipeline.py +++ b/tests/components/assist_pipeline/test_pipeline.py @@ -16,13 +16,14 @@ from homeassistant.components import ( stt, tts, ) -from homeassistant.components.assist_pipeline.const import DOMAIN +from homeassistant.components.assist_pipeline.const import ACKNOWLEDGE_PATH, DOMAIN from homeassistant.components.assist_pipeline.pipeline import ( STORAGE_KEY, STORAGE_VERSION, STORAGE_VERSION_MINOR, Pipeline, PipelineData, + PipelineEventType, PipelineStorageCollection, PipelineStore, _async_local_fallback_intent_filter, @@ -31,9 +32,16 @@ from homeassistant.components.assist_pipeline.pipeline import ( async_get_pipelines, async_update_pipeline, ) -from homeassistant.const import MATCH_ALL +from homeassistant.const import ATTR_FRIENDLY_NAME, MATCH_ALL from homeassistant.core import Context, HomeAssistant -from homeassistant.helpers import chat_session, intent, llm +from homeassistant.helpers import ( + area_registry as ar, + chat_session, + device_registry as dr, + entity_registry as er, + intent, + llm, +) from homeassistant.setup import async_setup_component from . import MANY_LANGUAGES, process_events @@ -46,7 +54,7 @@ from .conftest import ( make_10ms_chunk, ) -from tests.common import flush_store +from tests.common import MockConfigEntry, async_mock_service, flush_store from tests.typing import ClientSessionGenerator, WebSocketGenerator @@ -1787,3 +1795,296 @@ async def test_chat_log_tts_streaming( assert "".join(received_tts) == chunk_text assert process_events(events) == snapshot + + +async def test_acknowledge( + hass: HomeAssistant, + init_components, + pipeline_data: assist_pipeline.pipeline.PipelineData, + mock_chat_session: chat_session.ChatSession, + entity_registry: er.EntityRegistry, + area_registry: ar.AreaRegistry, + device_registry: dr.DeviceRegistry, +) -> None: + """Test that acknowledge sound is played when targets are in the same area.""" + area_1 = area_registry.async_get_or_create("area_1") + + light_1 = entity_registry.async_get_or_create("light", "demo", "1234") + hass.states.async_set(light_1.entity_id, "off", {ATTR_FRIENDLY_NAME: "light 1"}) + light_1 = entity_registry.async_update_entity(light_1.entity_id, area_id=area_1.id) + + light_2 = entity_registry.async_get_or_create("light", "demo", "5678") + hass.states.async_set(light_2.entity_id, "off", {ATTR_FRIENDLY_NAME: "light 2"}) + light_2 = entity_registry.async_update_entity(light_2.entity_id, area_id=area_1.id) + + entry = MockConfigEntry() + entry.add_to_hass(hass) + satellite = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + connections=set(), + identifiers={("demo", "id-1234")}, + ) + device_registry.async_update_device(satellite.id, area_id=area_1.id) + + events: list[assist_pipeline.PipelineEvent] = [] + turn_on = async_mock_service(hass, "light", "turn_on") + + pipeline_store = pipeline_data.pipeline_store + pipeline_id = pipeline_store.async_get_preferred_item() + pipeline = assist_pipeline.pipeline.async_get_pipeline(hass, pipeline_id) + + async def _run(text: str) -> None: + pipeline_input = assist_pipeline.pipeline.PipelineInput( + intent_input=text, + session=mock_chat_session, + device_id=satellite.id, + run=assist_pipeline.pipeline.PipelineRun( + hass, + context=Context(), + pipeline=pipeline, + start_stage=assist_pipeline.PipelineStage.INTENT, + end_stage=assist_pipeline.PipelineStage.TTS, + event_callback=events.append, + ), + ) + await pipeline_input.validate() + await pipeline_input.execute() + + with patch( + "homeassistant.components.assist_pipeline.PipelineRun.text_to_speech" + ) as text_to_speech: + + def _reset() -> None: + events.clear() + text_to_speech.reset_mock() + turn_on.clear() + + # 1. All targets in same area + await _run("turn on the lights") + + # Acknowledgment sound should be played (same area) + text_to_speech.assert_called_once() + assert ( + text_to_speech.call_args.kwargs["override_media_path"] == ACKNOWLEDGE_PATH + ) + assert len(turn_on) == 2 + + # 2. One light in a different area + area_2 = area_registry.async_get_or_create("area_2") + light_2 = entity_registry.async_update_entity( + light_2.entity_id, area_id=area_2.id + ) + + _reset() + await _run("turn on light 2") + + # Acknowledgment sound should be not played (different area) + text_to_speech.assert_called_once() + assert text_to_speech.call_args.kwargs.get("override_media_path") is None + assert len(turn_on) == 1 + + # Restore + light_2 = entity_registry.async_update_entity( + light_2.entity_id, area_id=area_1.id + ) + + # 3. Remove satellite device area + device_registry.async_update_device(satellite.id, area_id=None) + + _reset() + await _run("turn on light 1") + + # Acknowledgment sound should be not played (no satellite area) + text_to_speech.assert_called_once() + assert text_to_speech.call_args.kwargs.get("override_media_path") is None + assert len(turn_on) == 1 + + # Restore + device_registry.async_update_device(satellite.id, area_id=area_1.id) + + # 4. Check device area instead of entity area + light_device = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + connections=set(), + identifiers={("demo", "id-5678")}, + ) + device_registry.async_update_device(light_device.id, area_id=area_1.id) + light_2 = entity_registry.async_update_entity( + light_2.entity_id, area_id=None, device_id=light_device.id + ) + + _reset() + await _run("turn on the lights") + + # Acknowledgment sound should be played (same area) + text_to_speech.assert_called_once() + assert ( + text_to_speech.call_args.kwargs["override_media_path"] == ACKNOWLEDGE_PATH + ) + assert len(turn_on) == 2 + + # 5. Move device to different area + device_registry.async_update_device(light_device.id, area_id=area_2.id) + + _reset() + await _run("turn on light 2") + + # Acknowledgment sound should be not played (different device area) + text_to_speech.assert_called_once() + assert text_to_speech.call_args.kwargs.get("override_media_path") is None + assert len(turn_on) == 1 + + # 6. No device or area + light_2 = entity_registry.async_update_entity( + light_2.entity_id, area_id=None, device_id=None + ) + + _reset() + await _run("turn on light 2") + + # Acknowledgment sound should be not played (no area) + text_to_speech.assert_called_once() + assert text_to_speech.call_args.kwargs.get("override_media_path") is None + assert len(turn_on) == 1 + + # 7. Not in entity registry + hass.states.async_set("light.light_3", "off", {ATTR_FRIENDLY_NAME: "light 3"}) + + _reset() + await _run("turn on light 3") + + # Acknowledgment sound should be not played (not in entity registry) + text_to_speech.assert_called_once() + assert text_to_speech.call_args.kwargs.get("override_media_path") is None + assert len(turn_on) == 1 + + # Check TTS event + events.clear() + await _run("turn on light 1") + + has_acknowledge_override: bool | None = None + for event in events: + if event.type == PipelineEventType.TTS_START: + assert event.data + has_acknowledge_override = event.data["acknowledge_override"] + break + + assert has_acknowledge_override + + +async def test_acknowledge_other_agents( + hass: HomeAssistant, + init_components, + pipeline_data: assist_pipeline.pipeline.PipelineData, + mock_chat_session: chat_session.ChatSession, + entity_registry: er.EntityRegistry, + area_registry: ar.AreaRegistry, + device_registry: dr.DeviceRegistry, +) -> None: + """Test that acknowledge sound is only played when intents are processed locally for other agents.""" + area_1 = area_registry.async_get_or_create("area_1") + + light_1 = entity_registry.async_get_or_create("light", "demo", "1234") + hass.states.async_set(light_1.entity_id, "off", {ATTR_FRIENDLY_NAME: "light 1"}) + light_1 = entity_registry.async_update_entity(light_1.entity_id, area_id=area_1.id) + + light_2 = entity_registry.async_get_or_create("light", "demo", "5678") + hass.states.async_set(light_2.entity_id, "off", {ATTR_FRIENDLY_NAME: "light 2"}) + light_2 = entity_registry.async_update_entity(light_2.entity_id, area_id=area_1.id) + + entry = MockConfigEntry() + entry.add_to_hass(hass) + satellite = device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + connections=set(), + identifiers={("demo", "id-1234")}, + ) + device_registry.async_update_device(satellite.id, area_id=area_1.id) + + events: list[assist_pipeline.PipelineEvent] = [] + async_mock_service(hass, "light", "turn_on") + + pipeline_store = pipeline_data.pipeline_store + pipeline = await pipeline_store.async_create_item( + { + "name": "Test 1", + "language": "en-US", + "conversation_engine": "test agent", + "conversation_language": "en-US", + "tts_engine": "test tts", + "tts_language": "en-US", + "tts_voice": "test voice", + "stt_engine": "test stt", + "stt_language": "en-US", + "wake_word_entity": None, + "wake_word_id": None, + "prefer_local_intents": True, + } + ) + + with ( + patch( + "homeassistant.components.assist_pipeline.pipeline.conversation.async_get_agent_info", + return_value=conversation.AgentInfo( + id="test-agent", + name="Test Agent", + supports_streaming=False, + ), + ), + patch( + "homeassistant.components.assist_pipeline.PipelineRun.prepare_text_to_speech" + ), + patch( + "homeassistant.components.assist_pipeline.PipelineRun.text_to_speech" + ) as text_to_speech, + patch( + "homeassistant.components.conversation.async_converse", return_value=None + ) as async_converse, + patch( + "homeassistant.components.assist_pipeline.PipelineRun._get_all_targets_in_satellite_area" + ) as get_all_targets_in_satellite_area, + ): + pipeline_input = assist_pipeline.pipeline.PipelineInput( + intent_input="turn on the lights", + session=mock_chat_session, + device_id=satellite.id, + run=assist_pipeline.pipeline.PipelineRun( + hass, + context=Context(), + pipeline=pipeline, + start_stage=assist_pipeline.PipelineStage.INTENT, + end_stage=assist_pipeline.PipelineStage.TTS, + event_callback=events.append, + ), + ) + await pipeline_input.validate() + await pipeline_input.execute() + + # Processed locally + async_converse.assert_not_called() + + # Not processed locally + text_to_speech.reset_mock() + get_all_targets_in_satellite_area.reset_mock() + + pipeline_input = assist_pipeline.pipeline.PipelineInput( + intent_input="not processed locally", + session=mock_chat_session, + device_id=satellite.id, + run=assist_pipeline.pipeline.PipelineRun( + hass, + context=Context(), + pipeline=pipeline, + start_stage=assist_pipeline.PipelineStage.INTENT, + end_stage=assist_pipeline.PipelineStage.TTS, + event_callback=events.append, + ), + ) + await pipeline_input.validate() + await pipeline_input.execute() + + # The acknowledgment should not have even been checked for because the + # default agent didn't handle the intent. + text_to_speech.assert_not_called() + async_converse.assert_called_once() + get_all_targets_in_satellite_area.assert_not_called()