mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 18:27:09 +00:00
Add ESPHome Assist satellite configuration (#126085)
* Basic implementation * Add websocket commands * Clean up * Add callback to other signatures * Remove unused constant * Re-add callback * Add callback to test * Implement get/set configuration * Add tests * Re-add constant * Bump aioesphomeapi --------- Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
dde989685c
commit
6eab5e3e14
@ -79,6 +79,7 @@ _TIMER_EVENT_TYPES: EsphomeEnumMapper[VoiceAssistantTimerEventType, TimerEventTy
|
|||||||
)
|
)
|
||||||
|
|
||||||
_ANNOUNCEMENT_TIMEOUT_SEC = 5 * 60 # 5 minutes
|
_ANNOUNCEMENT_TIMEOUT_SEC = 5 * 60 # 5 minutes
|
||||||
|
_CONFIG_TIMEOUT_SEC = 5
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@ -128,6 +129,11 @@ class EsphomeAssistSatellite(
|
|||||||
self._tts_streaming_task: asyncio.Task | None = None
|
self._tts_streaming_task: asyncio.Task | None = None
|
||||||
self._udp_server: VoiceAssistantUDPServer | None = None
|
self._udp_server: VoiceAssistantUDPServer | None = None
|
||||||
|
|
||||||
|
# Empty config. Updated when added to HA.
|
||||||
|
self._satellite_config = assist_satellite.AssistSatelliteConfiguration(
|
||||||
|
available_wake_words=[], active_wake_words=[], max_active_wake_words=0
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pipeline_entity_id(self) -> str | None:
|
def pipeline_entity_id(self) -> str | None:
|
||||||
"""Return the entity ID of the pipeline to use for the next conversation."""
|
"""Return the entity ID of the pipeline to use for the next conversation."""
|
||||||
@ -155,13 +161,33 @@ class EsphomeAssistSatellite(
|
|||||||
self,
|
self,
|
||||||
) -> assist_satellite.AssistSatelliteConfiguration:
|
) -> assist_satellite.AssistSatelliteConfiguration:
|
||||||
"""Get the current satellite configuration."""
|
"""Get the current satellite configuration."""
|
||||||
raise NotImplementedError
|
return self._satellite_config
|
||||||
|
|
||||||
async def async_set_configuration(
|
async def async_set_configuration(
|
||||||
self, config: assist_satellite.AssistSatelliteConfiguration
|
self, config: assist_satellite.AssistSatelliteConfiguration
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set the current satellite configuration."""
|
"""Set the current satellite configuration."""
|
||||||
raise NotImplementedError
|
await self.cli.set_voice_assistant_configuration(
|
||||||
|
active_wake_words=config.active_wake_words
|
||||||
|
)
|
||||||
|
_LOGGER.debug("Set active wake words: %s", config.active_wake_words)
|
||||||
|
|
||||||
|
async def _update_satellite_config(self) -> None:
|
||||||
|
"""Get the latest satellite configuration from the device."""
|
||||||
|
config = await self.cli.get_voice_assistant_configuration(_CONFIG_TIMEOUT_SEC)
|
||||||
|
|
||||||
|
# Update available/active wake words
|
||||||
|
self._satellite_config.available_wake_words = [
|
||||||
|
assist_satellite.AssistSatelliteWakeWord(
|
||||||
|
id=model.id,
|
||||||
|
wake_word=model.wake_word,
|
||||||
|
trained_languages=list(model.trained_languages),
|
||||||
|
)
|
||||||
|
for model in config.available_wake_words
|
||||||
|
]
|
||||||
|
self._satellite_config.active_wake_words = list(config.active_wake_words)
|
||||||
|
self._satellite_config.max_active_wake_words = config.max_active_wake_words
|
||||||
|
_LOGGER.debug("Received satellite configuration: %s", self._satellite_config)
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Run when entity about to be added to hass."""
|
"""Run when entity about to be added to hass."""
|
||||||
@ -214,6 +240,11 @@ class EsphomeAssistSatellite(
|
|||||||
# Will use media player for TTS/announcements
|
# Will use media player for TTS/announcements
|
||||||
self._update_tts_format()
|
self._update_tts_format()
|
||||||
|
|
||||||
|
# Fetch latest config in the background
|
||||||
|
self.config_entry.async_create_background_task(
|
||||||
|
self.hass, self._update_satellite_config(), "esphome_voice_assistant_config"
|
||||||
|
)
|
||||||
|
|
||||||
async def async_will_remove_from_hass(self) -> None:
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
"""Run when entity will be removed from hass."""
|
"""Run when entity will be removed from hass."""
|
||||||
await super().async_will_remove_from_hass()
|
await super().async_will_remove_from_hass()
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"mqtt": ["esphome/discover/#"],
|
"mqtt": ["esphome/discover/#"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"aioesphomeapi==26.0.0",
|
"aioesphomeapi==27.0.0",
|
||||||
"esphome-dashboard-api==1.2.3",
|
"esphome-dashboard-api==1.2.3",
|
||||||
"bleak-esphome==1.0.0"
|
"bleak-esphome==1.0.0"
|
||||||
],
|
],
|
||||||
|
@ -240,7 +240,7 @@ aioelectricitymaps==0.4.0
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==26.0.0
|
aioesphomeapi==27.0.0
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
|
@ -228,7 +228,7 @@ aioelectricitymaps==0.4.0
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==26.0.0
|
aioesphomeapi==27.0.0
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
|
@ -27,8 +27,10 @@ import pytest
|
|||||||
from homeassistant.components import assist_satellite, tts
|
from homeassistant.components import assist_satellite, tts
|
||||||
from homeassistant.components.assist_pipeline import PipelineEvent, PipelineEventType
|
from homeassistant.components.assist_pipeline import PipelineEvent, PipelineEventType
|
||||||
from homeassistant.components.assist_satellite import (
|
from homeassistant.components.assist_satellite import (
|
||||||
|
AssistSatelliteConfiguration,
|
||||||
AssistSatelliteEntity,
|
AssistSatelliteEntity,
|
||||||
AssistSatelliteEntityFeature,
|
AssistSatelliteEntityFeature,
|
||||||
|
AssistSatelliteWakeWord,
|
||||||
)
|
)
|
||||||
|
|
||||||
# pylint: disable-next=hass-component-root-import
|
# pylint: disable-next=hass-component-root-import
|
||||||
@ -1380,3 +1382,50 @@ async def test_pipeline_abort(
|
|||||||
|
|
||||||
# Only first chunk
|
# Only first chunk
|
||||||
assert chunks == [b"before-abort"]
|
assert chunks == [b"before-abort"]
|
||||||
|
|
||||||
|
|
||||||
|
async def test_get_set_configuration(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_client: APIClient,
|
||||||
|
mock_esphome_device: Callable[
|
||||||
|
[APIClient, list[EntityInfo], list[UserService], list[EntityState]],
|
||||||
|
Awaitable[MockESPHomeDevice],
|
||||||
|
],
|
||||||
|
) -> None:
|
||||||
|
"""Test getting and setting the satellite configuration."""
|
||||||
|
expected_config = AssistSatelliteConfiguration(
|
||||||
|
available_wake_words=[
|
||||||
|
AssistSatelliteWakeWord("1234", "okay nabu", ["en"]),
|
||||||
|
AssistSatelliteWakeWord("5678", "hey jarvis", ["en"]),
|
||||||
|
],
|
||||||
|
active_wake_words=["1234"],
|
||||||
|
max_active_wake_words=1,
|
||||||
|
)
|
||||||
|
mock_client.get_voice_assistant_configuration.return_value = expected_config
|
||||||
|
|
||||||
|
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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
satellite = get_satellite_entity(hass, mock_device.device_info.mac_address)
|
||||||
|
assert satellite is not None
|
||||||
|
|
||||||
|
# HA should have been updated
|
||||||
|
actual_config = satellite.async_get_configuration()
|
||||||
|
assert actual_config == expected_config
|
||||||
|
|
||||||
|
# Change active wake words
|
||||||
|
actual_config.active_wake_words = ["5678"]
|
||||||
|
await satellite.async_set_configuration(actual_config)
|
||||||
|
|
||||||
|
# Device should have been updated
|
||||||
|
mock_client.set_voice_assistant_configuration.assert_called_once_with(
|
||||||
|
active_wake_words=["5678"]
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user