Add Balboa spa temperature range state control (high/low) (#115285)

* Add temperature range switch (high/low) to Balboa spa integration.

* Change Balboa spa integration temperature range control from switch to select

* Balboa spa integration: Fix ruff formatting

* Balboa spa integration: increase test coverage

* Balboa spa integration review fixes: Move instance attributes as class attributes. Fix code comments.
This commit is contained in:
Toni Korhonen 2024-04-13 11:44:02 +03:00 committed by GitHub
parent 127c27c9a7
commit 84a975b61e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 160 additions and 2 deletions

View file

@ -18,7 +18,13 @@ from .const import CONF_SYNC_TIME, DEFAULT_SYNC_TIME, DOMAIN
_LOGGER = logging.getLogger(__name__) _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) KEEP_ALIVE_INTERVAL = timedelta(minutes=1)

View file

@ -27,6 +27,11 @@
"off": "mdi:pump-off" "off": "mdi:pump-off"
} }
} }
},
"select": {
"temperature_range": {
"default": "mdi:thermometer-lines"
}
} }
} }
} }

View file

@ -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)

View file

@ -65,6 +65,15 @@
"only_light": { "only_light": {
"name": "Light" "name": "Light"
} }
},
"select": {
"temperature_range": {
"name": "Temperature range",
"state": {
"low": "Low",
"high": "High"
}
}
} }
} }
} }

View file

@ -5,7 +5,7 @@ from __future__ import annotations
from collections.abc import Callable, Generator from collections.abc import Callable, Generator
from unittest.mock import AsyncMock, MagicMock, patch from unittest.mock import AsyncMock, MagicMock, patch
from pybalboa.enums import HeatMode from pybalboa.enums import HeatMode, LowHighRange
import pytest import pytest
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -60,5 +60,6 @@ def client_fixture() -> Generator[MagicMock, None, None]:
client.heat_state = 2 client.heat_state = 2
client.lights = [] client.lights = []
client.pumps = [] client.pumps = []
client.temperature_range.state = LowHighRange.LOW
yield client yield client

View file

@ -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()