mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Add preset support to WLED (#52170)
This commit is contained in:
parent
0730b375f3
commit
f9d65b9196
@ -5,7 +5,6 @@ from functools import partial
|
||||
from typing import Any, Tuple, cast
|
||||
|
||||
import voluptuous as vol
|
||||
from wled import Preset
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
@ -218,18 +217,11 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
|
||||
if playlist == -1:
|
||||
playlist = None
|
||||
|
||||
preset: int | None = None
|
||||
if isinstance(self.coordinator.data.state.preset, Preset):
|
||||
preset = self.coordinator.data.state.preset.preset_id
|
||||
elif self.coordinator.data.state.preset != -1:
|
||||
preset = self.coordinator.data.state.preset
|
||||
|
||||
segment = self.coordinator.data.state.segments[self._segment]
|
||||
return {
|
||||
ATTR_INTENSITY: segment.intensity,
|
||||
ATTR_PALETTE: segment.palette.name,
|
||||
ATTR_PLAYLIST: playlist,
|
||||
ATTR_PRESET: preset,
|
||||
ATTR_REVERSE: segment.reverse,
|
||||
ATTR_SPEED: segment.speed,
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ from __future__ import annotations
|
||||
|
||||
from functools import partial
|
||||
|
||||
from wled import Preset
|
||||
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@ -23,6 +25,9 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up WLED select based on a config entry."""
|
||||
coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
|
||||
async_add_entities([WLEDPresetSelect(coordinator)])
|
||||
|
||||
update_segments = partial(
|
||||
async_update_segments,
|
||||
coordinator,
|
||||
@ -33,6 +38,37 @@ async def async_setup_entry(
|
||||
update_segments()
|
||||
|
||||
|
||||
class WLEDPresetSelect(WLEDEntity, SelectEntity):
|
||||
"""Defined a WLED Preset select."""
|
||||
|
||||
_attr_icon = "mdi:playlist-play"
|
||||
|
||||
def __init__(self, coordinator: WLEDDataUpdateCoordinator) -> None:
|
||||
"""Initialize WLED ."""
|
||||
super().__init__(coordinator=coordinator)
|
||||
|
||||
self._attr_name = f"{coordinator.data.info.name} Preset"
|
||||
self._attr_unique_id = f"{coordinator.data.info.mac_address}_preset"
|
||||
self._attr_options = [preset.name for preset in self.coordinator.data.presets]
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
return len(self.coordinator.data.presets) > 0 and super().available
|
||||
|
||||
@property
|
||||
def current_option(self) -> str | None:
|
||||
"""Return the current selected preset."""
|
||||
if not isinstance(self.coordinator.data.state.preset, Preset):
|
||||
return None
|
||||
return self.coordinator.data.state.preset.name
|
||||
|
||||
@wled_exception_handler
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Set WLED segment to the selected preset."""
|
||||
await self.coordinator.wled.preset(preset=option)
|
||||
|
||||
|
||||
class WLEDPaletteSelect(WLEDEntity, SelectEntity):
|
||||
"""Defines a WLED Palette select."""
|
||||
|
||||
|
@ -13,6 +13,7 @@ from homeassistant.const import (
|
||||
ATTR_ICON,
|
||||
SERVICE_SELECT_OPTION,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@ -44,7 +45,7 @@ async def enable_all(hass: HomeAssistant) -> None:
|
||||
)
|
||||
|
||||
|
||||
async def test_select_state(
|
||||
async def test_color_palette_state(
|
||||
hass: HomeAssistant, enable_all: None, init_integration: MockConfigEntry
|
||||
) -> None:
|
||||
"""Test the creation and values of the WLED selects."""
|
||||
@ -113,7 +114,7 @@ async def test_select_state(
|
||||
assert entry.unique_id == "aabbccddeeff_palette_1"
|
||||
|
||||
|
||||
async def test_segment_change_state(
|
||||
async def test_color_palette_segment_change_state(
|
||||
hass: HomeAssistant,
|
||||
enable_all: None,
|
||||
init_integration: MockConfigEntry,
|
||||
@ -138,7 +139,7 @@ async def test_segment_change_state(
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_wled", ["wled/rgb_single_segment.json"], indirect=True)
|
||||
async def test_dynamically_handle_segments(
|
||||
async def test_color_palette_dynamically_handle_segments(
|
||||
hass: HomeAssistant,
|
||||
enable_all: None,
|
||||
init_integration: MockConfigEntry,
|
||||
@ -179,7 +180,7 @@ async def test_dynamically_handle_segments(
|
||||
assert segment1.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_select_error(
|
||||
async def test_color_palette_select_error(
|
||||
hass: HomeAssistant,
|
||||
enable_all: None,
|
||||
init_integration: MockConfigEntry,
|
||||
@ -208,7 +209,7 @@ async def test_select_error(
|
||||
mock_wled.segment.assert_called_with(segment_id=1, palette="Whatever")
|
||||
|
||||
|
||||
async def test_select_connection_error(
|
||||
async def test_color_palette_select_connection_error(
|
||||
hass: HomeAssistant,
|
||||
enable_all: None,
|
||||
init_integration: MockConfigEntry,
|
||||
@ -237,6 +238,126 @@ async def test_select_connection_error(
|
||||
mock_wled.segment.assert_called_with(segment_id=1, palette="Whatever")
|
||||
|
||||
|
||||
async def test_preset_unavailable_without_presets(
|
||||
hass: HomeAssistant,
|
||||
init_integration: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test WLED preset entity is unavailable when presets are not available."""
|
||||
state = hass.states.get("select.wled_rgb_light_preset")
|
||||
assert state
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_wled", ["wled/rgbw.json"], indirect=True)
|
||||
async def test_preset_state(
|
||||
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_rgbw_light_preset")
|
||||
assert state
|
||||
assert state.attributes.get(ATTR_ICON) == "mdi:playlist-play"
|
||||
assert state.attributes.get(ATTR_OPTIONS) == ["Preset 1", "Preset 2"]
|
||||
assert state.state == "Preset 1"
|
||||
|
||||
entry = entity_registry.async_get("select.wled_rgbw_light_preset")
|
||||
assert entry
|
||||
assert entry.unique_id == "aabbccddee11_preset"
|
||||
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: "select.wled_rgbw_light_preset",
|
||||
ATTR_OPTION: "Preset 2",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert mock_wled.preset.call_count == 1
|
||||
mock_wled.preset.assert_called_with(preset="Preset 2")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_wled", ["wled/rgbw.json"], indirect=True)
|
||||
async def test_old_style_preset_active(
|
||||
hass: HomeAssistant,
|
||||
init_integration: MockConfigEntry,
|
||||
mock_wled: MagicMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test unknown preset returned (when old style/unknown) preset is active."""
|
||||
# Set device preset state to a random number
|
||||
mock_wled.update.return_value.state.preset = 99
|
||||
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("select.wled_rgbw_light_preset")
|
||||
assert state
|
||||
assert state.state == STATE_UNKNOWN
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_wled", ["wled/rgbw.json"], indirect=True)
|
||||
async def test_preset_select_error(
|
||||
hass: HomeAssistant,
|
||||
init_integration: MockConfigEntry,
|
||||
mock_wled: MagicMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test error handling of the WLED selects."""
|
||||
mock_wled.preset.side_effect = WLEDError
|
||||
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: "select.wled_rgbw_light_preset",
|
||||
ATTR_OPTION: "Preset 2",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("select.wled_rgbw_light_preset")
|
||||
assert state
|
||||
assert state.state == "Preset 1"
|
||||
assert "Invalid response from API" in caplog.text
|
||||
assert mock_wled.preset.call_count == 1
|
||||
mock_wled.preset.assert_called_with(preset="Preset 2")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mock_wled", ["wled/rgbw.json"], indirect=True)
|
||||
async def test_preset_select_connection_error(
|
||||
hass: HomeAssistant,
|
||||
init_integration: MockConfigEntry,
|
||||
mock_wled: MagicMock,
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""Test error handling of the WLED selects."""
|
||||
mock_wled.preset.side_effect = WLEDConnectionError
|
||||
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{
|
||||
ATTR_ENTITY_ID: "select.wled_rgbw_light_preset",
|
||||
ATTR_OPTION: "Preset 2",
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("select.wled_rgbw_light_preset")
|
||||
assert state
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
assert "Error communicating with API" in caplog.text
|
||||
assert mock_wled.preset.call_count == 1
|
||||
mock_wled.preset.assert_called_with(preset="Preset 2")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"entity_id",
|
||||
(
|
||||
|
155
tests/fixtures/wled/rgbw.json
vendored
155
tests/fixtures/wled/rgbw.json
vendored
@ -3,7 +3,7 @@
|
||||
"on": true,
|
||||
"bri": 140,
|
||||
"transition": 7,
|
||||
"ps": -1,
|
||||
"ps": 1,
|
||||
"pl": -1,
|
||||
"nl": {
|
||||
"on": false,
|
||||
@ -200,5 +200,158 @@
|
||||
"Orangery",
|
||||
"C9",
|
||||
"Sakura"
|
||||
],
|
||||
"presets": {
|
||||
"0": {},
|
||||
"1": {
|
||||
"on": false,
|
||||
"bri": 255,
|
||||
"transition": 7,
|
||||
"mainseg": 0,
|
||||
"seg": [
|
||||
{
|
||||
"id": 0,
|
||||
"start": 0,
|
||||
"stop": 13,
|
||||
"grp": 1,
|
||||
"spc": 0,
|
||||
"on": true,
|
||||
"bri": 255,
|
||||
"col": [
|
||||
[
|
||||
97,
|
||||
144,
|
||||
255
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
],
|
||||
"fx": 9,
|
||||
"sx": 183,
|
||||
"ix": 255,
|
||||
"pal": 1,
|
||||
"sel": true,
|
||||
"rev": false,
|
||||
"mi": false
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
}
|
||||
],
|
||||
"n": "Preset 1"
|
||||
},
|
||||
"2": {
|
||||
"on": false,
|
||||
"bri": 255,
|
||||
"transition": 7,
|
||||
"mainseg": 0,
|
||||
"seg": [
|
||||
{
|
||||
"id": 0,
|
||||
"start": 0,
|
||||
"stop": 13,
|
||||
"grp": 1,
|
||||
"spc": 0,
|
||||
"on": true,
|
||||
"bri": 255,
|
||||
"col": [
|
||||
[
|
||||
97,
|
||||
144,
|
||||
255
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
],
|
||||
"fx": 9,
|
||||
"sx": 183,
|
||||
"ix": 255,
|
||||
"pal": 1,
|
||||
"sel": true,
|
||||
"rev": false,
|
||||
"mi": false
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
},
|
||||
{
|
||||
"stop": 0
|
||||
}
|
||||
],
|
||||
"n": "Preset 2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user