diff --git a/homeassistant/components/balboa/__init__.py b/homeassistant/components/balboa/__init__.py index d6a80e8fa8f..7e220bd46f8 100644 --- a/homeassistant/components/balboa/__init__.py +++ b/homeassistant/components/balboa/__init__.py @@ -18,7 +18,13 @@ from .const import CONF_SYNC_TIME, DEFAULT_SYNC_TIME, DOMAIN _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.FAN, Platform.LIGHT] +PLATFORMS = [ + Platform.BINARY_SENSOR, + Platform.CLIMATE, + Platform.FAN, + Platform.LIGHT, + Platform.SELECT, +] KEEP_ALIVE_INTERVAL = timedelta(minutes=1) diff --git a/homeassistant/components/balboa/icons.json b/homeassistant/components/balboa/icons.json index 7261f71bd00..7454366f692 100644 --- a/homeassistant/components/balboa/icons.json +++ b/homeassistant/components/balboa/icons.json @@ -27,6 +27,11 @@ "off": "mdi:pump-off" } } + }, + "select": { + "temperature_range": { + "default": "mdi:thermometer-lines" + } } } } diff --git a/homeassistant/components/balboa/select.py b/homeassistant/components/balboa/select.py new file mode 100644 index 00000000000..3fdd8c4d014 --- /dev/null +++ b/homeassistant/components/balboa/select.py @@ -0,0 +1,52 @@ +"""Support for Spa Client selects.""" + +from pybalboa import SpaClient, SpaControl +from pybalboa.enums import LowHighRange + +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 .const import DOMAIN +from .entity import BalboaEntity + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up the spa select entity.""" + spa: SpaClient = hass.data[DOMAIN][entry.entry_id] + async_add_entities([BalboaTempRangeSelectEntity(spa.temperature_range)]) + + +class BalboaTempRangeSelectEntity(BalboaEntity, SelectEntity): + """Representation of a Temperature Range select.""" + + _attr_icon = "mdi:thermometer-lines" + _attr_name = "Temperature range" + _attr_unique_id = "temperature_range" + _attr_translation_key = "temperature_range" + _attr_options = [ + LowHighRange.LOW.name.lower(), + LowHighRange.HIGH.name.lower(), + ] + + def __init__(self, control: SpaControl) -> None: + """Initialise the select.""" + super().__init__(control.client, "TempHiLow") + self._control = control + + @property + def current_option(self) -> str | None: + """Return current select option.""" + if self._control.state == LowHighRange.HIGH: + return LowHighRange.HIGH.name.lower() + return LowHighRange.LOW.name.lower() + + async def async_select_option(self, option: str) -> None: + """Select temperature range high/low mode.""" + if option == LowHighRange.HIGH.name.lower(): + await self._client.set_temperature_range(LowHighRange.HIGH) + else: + await self._client.set_temperature_range(LowHighRange.LOW) diff --git a/homeassistant/components/balboa/strings.json b/homeassistant/components/balboa/strings.json index 3c8f82764d4..6ced7dfd8c3 100644 --- a/homeassistant/components/balboa/strings.json +++ b/homeassistant/components/balboa/strings.json @@ -65,6 +65,15 @@ "only_light": { "name": "Light" } + }, + "select": { + "temperature_range": { + "name": "Temperature range", + "state": { + "low": "Low", + "high": "High" + } + } } } } diff --git a/tests/components/balboa/conftest.py b/tests/components/balboa/conftest.py index fce022572c3..7f679773f93 100644 --- a/tests/components/balboa/conftest.py +++ b/tests/components/balboa/conftest.py @@ -5,7 +5,7 @@ from __future__ import annotations from collections.abc import Callable, Generator from unittest.mock import AsyncMock, MagicMock, patch -from pybalboa.enums import HeatMode +from pybalboa.enums import HeatMode, LowHighRange import pytest from homeassistant.core import HomeAssistant @@ -60,5 +60,6 @@ def client_fixture() -> Generator[MagicMock, None, None]: client.heat_state = 2 client.lights = [] client.pumps = [] + client.temperature_range.state = LowHighRange.LOW yield client diff --git a/tests/components/balboa/test_select.py b/tests/components/balboa/test_select.py new file mode 100644 index 00000000000..bd79f024817 --- /dev/null +++ b/tests/components/balboa/test_select.py @@ -0,0 +1,85 @@ +"""Tests of the select entity of the balboa integration.""" + +from __future__ import annotations + +from unittest.mock import MagicMock, call + +from pybalboa import SpaControl +from pybalboa.enums import LowHighRange +import pytest + +from homeassistant.components.select import ( + ATTR_OPTION, + DOMAIN as SELECT_DOMAIN, + SERVICE_SELECT_OPTION, +) +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import HomeAssistant + +from . import client_update, init_integration + +ENTITY_SELECT = "select.fakespa_temperature_range" + + +@pytest.fixture +def mock_select(client: MagicMock): + """Return a mock switch.""" + select = MagicMock(SpaControl) + + async def set_state(state: LowHighRange): + select.state = state # mock the spacontrol state + + select.client = client + select.state = LowHighRange.LOW + select.set_state = set_state + client.temperature_range = select + return select + + +async def test_select(hass: HomeAssistant, client: MagicMock, mock_select) -> None: + """Test spa temperature range select.""" + await init_integration(hass) + + # check if the initial state is off + state = hass.states.get(ENTITY_SELECT) + assert state.state == LowHighRange.LOW.name.lower() + + # test high state + await _select_option_and_wait(hass, ENTITY_SELECT, LowHighRange.HIGH.name.lower()) + assert client.set_temperature_range.call_count == 1 + assert client.set_temperature_range.call_args == call(LowHighRange.HIGH) + + # test back to low state + await _select_option_and_wait(hass, ENTITY_SELECT, LowHighRange.LOW.name.lower()) + assert client.set_temperature_range.call_count == 2 # total call count + assert client.set_temperature_range.call_args == call(LowHighRange.LOW) + + +async def test_selected_option( + hass: HomeAssistant, client: MagicMock, mock_select +) -> None: + """Test spa temperature range selected option.""" + + await init_integration(hass) + + # ensure initial low state + state = hass.states.get(ENTITY_SELECT) + assert state.state == LowHighRange.LOW.name.lower() + + # ensure high state + mock_select.state = LowHighRange.HIGH + state = await client_update(hass, client, ENTITY_SELECT) + assert state.state == LowHighRange.HIGH.name.lower() + + +async def _select_option_and_wait(hass: HomeAssistant | None, entity, option): + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: entity, + ATTR_OPTION: option, + }, + blocking=True, + ) + await hass.async_block_till_done()