diff --git a/homeassistant/components/wled/const.py b/homeassistant/components/wled/const.py index 1d76d6633dd..25c1fea8f9a 100644 --- a/homeassistant/components/wled/const.py +++ b/homeassistant/components/wled/const.py @@ -1,6 +1,7 @@ """Constants for the WLED integration.""" from datetime import timedelta import logging +from typing import Final # Integration domain DOMAIN = "wled" @@ -30,3 +31,6 @@ ATTR_UDP_PORT = "udp_port" # Services SERVICE_EFFECT = "effect" SERVICE_PRESET = "preset" + +# Device classes +DEVICE_CLASS_WLED_LIVE_OVERRIDE: Final = "wled__live_override" diff --git a/homeassistant/components/wled/select.py b/homeassistant/components/wled/select.py index 47942359b0f..d82f12cffd7 100644 --- a/homeassistant/components/wled/select.py +++ b/homeassistant/components/wled/select.py @@ -3,7 +3,7 @@ from __future__ import annotations from functools import partial -from wled import Playlist, Preset +from wled import Live, Playlist, Preset from homeassistant.components.select import SelectEntity from homeassistant.config_entries import ConfigEntry @@ -11,7 +11,7 @@ from homeassistant.const import ENTITY_CATEGORY_CONFIG from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .const import DOMAIN +from .const import DEVICE_CLASS_WLED_LIVE_OVERRIDE, DOMAIN from .coordinator import WLEDDataUpdateCoordinator from .helpers import wled_exception_handler from .models import WLEDEntity @@ -27,7 +27,13 @@ async def async_setup_entry( """Set up WLED select based on a config entry.""" coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] - async_add_entities([WLEDPlaylistSelect(coordinator), WLEDPresetSelect(coordinator)]) + async_add_entities( + [ + WLEDLiveOverrideSelect(coordinator), + WLEDPlaylistSelect(coordinator), + WLEDPresetSelect(coordinator), + ] + ) update_segments = partial( async_update_segments, @@ -39,6 +45,32 @@ async def async_setup_entry( update_segments() +class WLEDLiveOverrideSelect(WLEDEntity, SelectEntity): + """Defined a WLED Live Override select.""" + + _attr_device_class = DEVICE_CLASS_WLED_LIVE_OVERRIDE + _attr_entity_category = ENTITY_CATEGORY_CONFIG + _attr_icon = "mdi:theater" + + def __init__(self, coordinator: WLEDDataUpdateCoordinator) -> None: + """Initialize WLED .""" + super().__init__(coordinator=coordinator) + + self._attr_name = f"{coordinator.data.info.name} Live Override" + self._attr_unique_id = f"{coordinator.data.info.mac_address}_live_override" + self._attr_options = [str(live.value) for live in Live] + + @property + def current_option(self) -> str: + """Return the current selected live override.""" + return str(self.coordinator.data.state.lor.value) + + @wled_exception_handler + async def async_select_option(self, option: str) -> None: + """Set WLED state to the selected live override state.""" + await self.coordinator.wled.live(live=Live(int(option))) + + class WLEDPresetSelect(WLEDEntity, SelectEntity): """Defined a WLED Preset select.""" diff --git a/homeassistant/components/wled/strings.select.json b/homeassistant/components/wled/strings.select.json new file mode 100644 index 00000000000..9f678e380b4 --- /dev/null +++ b/homeassistant/components/wled/strings.select.json @@ -0,0 +1,9 @@ +{ + "state": { + "wled__live_override": { + "0": "[%key:common::state::off%]", + "1": "[%key:common::state::on%]", + "2": "Until device restarts" + } + } +} diff --git a/homeassistant/components/wled/translations/select.en.json b/homeassistant/components/wled/translations/select.en.json new file mode 100644 index 00000000000..0aafa31c0f5 --- /dev/null +++ b/homeassistant/components/wled/translations/select.en.json @@ -0,0 +1,9 @@ +{ + "state": { + "wled__live_override": { + "0": "Off", + "1": "On", + "2": "Until device restart" + } + } +} \ No newline at end of file diff --git a/tests/components/wled/test_select.py b/tests/components/wled/test_select.py index 96ea07f52c4..345c0c632fe 100644 --- a/tests/components/wled/test_select.py +++ b/tests/components/wled/test_select.py @@ -451,3 +451,91 @@ async def test_playlist_select_connection_error( assert "Error communicating with API" in caplog.text assert mock_wled.playlist.call_count == 1 mock_wled.playlist.assert_called_with(playlist="Playlist 2") + + +async def test_live_override( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_wled: MagicMock, +) -> None: + """Test the creation and values of the WLED selects.""" + entity_registry = er.async_get(hass) + + state = hass.states.get("select.wled_rgb_light_live_override") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:theater" + assert state.attributes.get(ATTR_OPTIONS) == ["0", "1", "2"] + assert state.state == "0" + + entry = entity_registry.async_get("select.wled_rgb_light_live_override") + assert entry + assert entry.unique_id == "aabbccddeeff_live_override" + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.wled_rgb_light_live_override", + ATTR_OPTION: "2", + }, + blocking=True, + ) + await hass.async_block_till_done() + assert mock_wled.live.call_count == 1 + mock_wled.live.assert_called_with(live=2) + + +async def test_live_select_error( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_wled: MagicMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test error handling of the WLED selects.""" + mock_wled.live.side_effect = WLEDError + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.wled_rgb_light_live_override", + ATTR_OPTION: "1", + }, + blocking=True, + ) + await hass.async_block_till_done() + + state = hass.states.get("select.wled_rgb_light_live_override") + assert state + assert state.state == "0" + assert "Invalid response from API" in caplog.text + assert mock_wled.live.call_count == 1 + mock_wled.live.assert_called_with(live=1) + + +async def test_live_select_connection_error( + hass: HomeAssistant, + init_integration: MockConfigEntry, + mock_wled: MagicMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test error handling of the WLED selects.""" + mock_wled.live.side_effect = WLEDConnectionError + + await hass.services.async_call( + SELECT_DOMAIN, + SERVICE_SELECT_OPTION, + { + ATTR_ENTITY_ID: "select.wled_rgb_light_live_override", + ATTR_OPTION: "2", + }, + blocking=True, + ) + await hass.async_block_till_done() + + state = hass.states.get("select.wled_rgb_light_live_override") + assert state + assert state.state == STATE_UNAVAILABLE + assert "Error communicating with API" in caplog.text + assert mock_wled.live.call_count == 1 + mock_wled.live.assert_called_with(live=2)