diff --git a/homeassistant/components/esphome/entry_data.py b/homeassistant/components/esphome/entry_data.py index cf8378008f6..61d6262250c 100644 --- a/homeassistant/components/esphome/entry_data.py +++ b/homeassistant/components/esphome/entry_data.py @@ -204,6 +204,7 @@ class RuntimeEntryData: if self.device_info is not None and self.device_info.voice_assistant_version: needed_platforms.add(Platform.BINARY_SENSOR) + needed_platforms.add(Platform.SELECT) for info in infos: for info_type, platform in INFO_TYPE_TO_PLATFORM.items(): diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index 4c48fba1add..8250746e9a0 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -1,10 +1,10 @@ { "domain": "esphome", "name": "ESPHome", - "after_dependencies": ["zeroconf", "tag", "assist_pipeline"], + "after_dependencies": ["zeroconf", "tag"], "codeowners": ["@OttoWinter", "@jesserockz"], "config_flow": true, - "dependencies": ["bluetooth"], + "dependencies": ["assist_pipeline", "bluetooth"], "dhcp": [ { "registered_devices": true diff --git a/homeassistant/components/esphome/select.py b/homeassistant/components/esphome/select.py index 79af0455346..e4cac21dbc8 100644 --- a/homeassistant/components/esphome/select.py +++ b/homeassistant/components/esphome/select.py @@ -3,12 +3,20 @@ from __future__ import annotations from aioesphomeapi import SelectInfo, SelectState +from homeassistant.components.assist_pipeline.select import AssistPipelineSelect from homeassistant.components.select import SelectEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import EsphomeEntity, esphome_state_property, platform_async_setup_entry +from . import ( + EsphomeAssistEntity, + EsphomeEntity, + esphome_state_property, + platform_async_setup_entry, +) +from .domain_data import DomainData +from .entry_data import RuntimeEntryData async def async_setup_entry( @@ -27,6 +35,11 @@ async def async_setup_entry( state_type=SelectState, ) + entry_data = DomainData.get(hass).get_entry_data(entry) + assert entry_data.device_info is not None + if entry_data.device_info.voice_assistant_version: + async_add_entities([EsphomeAssistPipelineSelect(hass, entry_data)]) + class EsphomeSelect(EsphomeEntity[SelectInfo, SelectState], SelectEntity): """A select implementation for esphome.""" @@ -47,3 +60,12 @@ class EsphomeSelect(EsphomeEntity[SelectInfo, SelectState], SelectEntity): async def async_select_option(self, option: str) -> None: """Change the selected option.""" await self._client.select_command(self._static_info.key, option) + + +class EsphomeAssistPipelineSelect(EsphomeAssistEntity, AssistPipelineSelect): + """Pipeline selector for esphome devices.""" + + def __init__(self, hass: HomeAssistant, entry_data: RuntimeEntryData) -> None: + """Initialize a pipeline selector.""" + EsphomeAssistEntity.__init__(self, entry_data) + AssistPipelineSelect.__init__(self, hass, self._device_info.mac_address) diff --git a/homeassistant/components/esphome/strings.json b/homeassistant/components/esphome/strings.json index 0486da8d204..68ff5e508de 100644 --- a/homeassistant/components/esphome/strings.json +++ b/homeassistant/components/esphome/strings.json @@ -51,6 +51,14 @@ "call_active": { "name": "Call Active" } + }, + "select": { + "pipeline": { + "name": "[%key:component::assist_pipeline::entity::select::pipeline::name%]", + "state": { + "preferred": "[%key:component::assist_pipeline::entity::select::pipeline::state::preferred%]" + } + } } }, "issues": { diff --git a/tests/components/esphome/conftest.py b/tests/components/esphome/conftest.py index c1d9f72dd31..f5362b1fb3d 100644 --- a/tests/components/esphome/conftest.py +++ b/tests/components/esphome/conftest.py @@ -122,3 +122,38 @@ async def mock_dashboard(hass): hass, DASHBOARD_SLUG, DASHBOARD_HOST, DASHBOARD_PORT ) yield data + + +@pytest.fixture +async def mock_voice_assistant_v1_entry( + hass: HomeAssistant, + mock_client, +) -> MockConfigEntry: + """Set up an ESPHome entry with voice assistant.""" + entry = MockConfigEntry( + domain=DOMAIN, + data={ + CONF_HOST: "test.local", + CONF_PORT: 6053, + CONF_PASSWORD: "", + }, + ) + entry.add_to_hass(hass) + + device_info = DeviceInfo( + name="test", + friendly_name="Test", + voice_assistant_version=1, + mac_address="11:22:33:44:55:aa", + esphome_version="1.0.0", + ) + + mock_client.device_info = AsyncMock(return_value=device_info) + mock_client.subscribe_voice_assistant = AsyncMock(return_value=Mock()) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + await hass.async_block_till_done() + await hass.async_block_till_done() + + return entry diff --git a/tests/components/esphome/test_binary_sensor.py b/tests/components/esphome/test_binary_sensor.py index f91da878d03..90cf99747f0 100644 --- a/tests/components/esphome/test_binary_sensor.py +++ b/tests/components/esphome/test_binary_sensor.py @@ -1,43 +1,17 @@ """Test ESPHome binary sensors.""" -from unittest.mock import AsyncMock, Mock -from aioesphomeapi import DeviceInfo -from homeassistant.components.esphome import DOMAIN, DomainData -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT +from homeassistant.components.esphome import DomainData from homeassistant.core import HomeAssistant -from tests.common import MockConfigEntry - async def test_call_active( hass: HomeAssistant, - mock_client, + mock_voice_assistant_v1_entry, ) -> None: """Test call active binary sensor.""" - entry = MockConfigEntry( - domain=DOMAIN, - data={CONF_HOST: "test.local", CONF_PORT: 6053, CONF_PASSWORD: ""}, - ) - entry.add_to_hass(hass) - device_info = DeviceInfo( - name="test", - friendly_name="Test", - voice_assistant_version=1, - mac_address="11:22:33:44:55:aa", - esphome_version="1.0.0", - ) - - mock_client.device_info = AsyncMock(return_value=device_info) - mock_client.subscribe_voice_assistant = AsyncMock(return_value=Mock()) - - await hass.config_entries.async_setup(entry.entry_id) - await hass.async_block_till_done() - await hass.async_block_till_done() - await hass.async_block_till_done() - - entry_data = DomainData.get(hass).get_entry_data(entry) + entry_data = DomainData.get(hass).get_entry_data(mock_voice_assistant_v1_entry) state = hass.states.get("binary_sensor.test_call_active") assert state is not None diff --git a/tests/components/esphome/test_select.py b/tests/components/esphome/test_select.py new file mode 100644 index 00000000000..dec321ced86 --- /dev/null +++ b/tests/components/esphome/test_select.py @@ -0,0 +1,15 @@ +"""Test ESPHome selects.""" + + +from homeassistant.core import HomeAssistant + + +async def test_pipeline_selector( + hass: HomeAssistant, + mock_voice_assistant_v1_entry, +) -> None: + """Test assist pipeline selector.""" + + state = hass.states.get("select.test_assist_pipeline") + assert state is not None + assert state.state == "preferred"