diff --git a/homeassistant/components/balboa/__init__.py b/homeassistant/components/balboa/__init__.py index a86cd8d9507..1d2cd042918 100644 --- a/homeassistant/components/balboa/__init__.py +++ b/homeassistant/components/balboa/__init__.py @@ -17,7 +17,7 @@ from .const import CONF_SYNC_TIME, DEFAULT_SYNC_TIME, DOMAIN _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.FAN] +PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.FAN, Platform.LIGHT] KEEP_ALIVE_INTERVAL = timedelta(minutes=1) diff --git a/homeassistant/components/balboa/light.py b/homeassistant/components/balboa/light.py new file mode 100644 index 00000000000..00b8eb979a2 --- /dev/null +++ b/homeassistant/components/balboa/light.py @@ -0,0 +1,56 @@ +"""Support for Balboa Spa lights.""" +from __future__ import annotations + +from typing import Any, cast + +from pybalboa import SpaClient, SpaControl +from pybalboa.enums import OffOnState, UnknownState + +from homeassistant.components.light import ColorMode, LightEntity +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's lights.""" + spa: SpaClient = hass.data[DOMAIN][entry.entry_id] + async_add_entities(BalboaLightEntity(control) for control in spa.lights) + + +class BalboaLightEntity(BalboaEntity, LightEntity): + """Representation of a Balboa Spa light entity.""" + + _attr_color_mode = ColorMode.ONOFF + _attr_supported_color_modes = {ColorMode.ONOFF} + + def __init__(self, control: SpaControl) -> None: + """Initialize a Balboa Spa light entity.""" + super().__init__(control.client, control.name) + self._control = control + self._attr_translation_key = ( + "light_of_n" if len(control.client.lights) > 1 else "only_light" + ) + self._attr_translation_placeholders = { + "index": f"{cast(int, control.index) + 1}" + } + + async def async_turn_off(self, **kwargs: Any) -> None: + """Turn the light off.""" + await self._control.set_state(OffOnState.OFF) + + async def async_turn_on(self, **kwargs: Any) -> None: + """Turn the light on.""" + await self._control.set_state(OffOnState.ON) + + @property + def is_on(self) -> bool | None: + """Return true if the light is on.""" + if self._control.state == UnknownState.UNKNOWN: + return None + return self._control.state != OffOnState.OFF diff --git a/homeassistant/components/balboa/strings.json b/homeassistant/components/balboa/strings.json index 1975a5bc505..3c8f82764d4 100644 --- a/homeassistant/components/balboa/strings.json +++ b/homeassistant/components/balboa/strings.json @@ -57,6 +57,14 @@ "pump": { "name": "Pump {index}" } + }, + "light": { + "light_of_n": { + "name": "Light {index}" + }, + "only_light": { + "name": "Light" + } } } } diff --git a/tests/components/balboa/conftest.py b/tests/components/balboa/conftest.py index 422023d1d9d..fa2787dd6e2 100644 --- a/tests/components/balboa/conftest.py +++ b/tests/components/balboa/conftest.py @@ -57,6 +57,7 @@ def client_fixture() -> Generator[MagicMock, None, None]: client.heat_mode.set_state = AsyncMock() client.heat_mode.options = list(HeatMode)[:2] client.heat_state = 2 + client.lights = [] client.pumps = [] yield client diff --git a/tests/components/balboa/test_light.py b/tests/components/balboa/test_light.py new file mode 100644 index 00000000000..e20e55c5980 --- /dev/null +++ b/tests/components/balboa/test_light.py @@ -0,0 +1,65 @@ +"""Tests of the light entity of the balboa integration.""" +from __future__ import annotations + +from unittest.mock import MagicMock + +from pybalboa import SpaControl +from pybalboa.enums import OffOnState, UnknownState +import pytest + +from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN +from homeassistant.core import HomeAssistant + +from . import client_update, init_integration + +from tests.components.light import common + +ENTITY_LIGHT = "light.fakespa_light" + + +@pytest.fixture +def mock_light(client: MagicMock): + """Return a mock light.""" + light = MagicMock(SpaControl) + + async def set_state(state: OffOnState): + light.state = state + + light.client = client + light.index = 0 + light.state = OffOnState.OFF + light.set_state = set_state + light.options = list(OffOnState) + client.lights.append(light) + + return light + + +async def test_light(hass: HomeAssistant, client: MagicMock, mock_light) -> None: + """Test spa light.""" + await init_integration(hass) + + # check if the initial state is off + state = hass.states.get(ENTITY_LIGHT) + assert state.state == STATE_OFF + + # test calling turn on + await common.async_turn_on(hass, ENTITY_LIGHT) + state = await client_update(hass, client, ENTITY_LIGHT) + assert state.state == STATE_ON + + # test calling turn off + await common.async_turn_off(hass, ENTITY_LIGHT) + state = await client_update(hass, client, ENTITY_LIGHT) + assert state.state == STATE_OFF + + +async def test_light_unknown_state( + hass: HomeAssistant, client: MagicMock, mock_light +) -> None: + """Tests spa light with unknown state.""" + await init_integration(hass) + + mock_light.state = UnknownState.UNKNOWN + state = await client_update(hass, client, ENTITY_LIGHT) + assert state.state == STATE_UNKNOWN