Change dynamic segment handling of WLED (#52018)

This commit is contained in:
Franck Nijhof 2021-06-23 23:43:24 +02:00 committed by GitHub
parent cc00617cd5
commit a67ca08124
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 93 additions and 97 deletions

View file

@ -5,7 +5,6 @@ from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import DOMAIN from .const import DOMAIN
@ -16,7 +15,7 @@ PLATFORMS = (LIGHT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up WLED from a config entry.""" """Set up WLED from a config entry."""
coordinator = WLEDDataUpdateCoordinator(hass, host=entry.data[CONF_HOST]) coordinator = WLEDDataUpdateCoordinator(hass, entry=entry)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {}) hass.data.setdefault(DOMAIN, {})

View file

@ -6,25 +6,37 @@ from typing import Callable
from wled import WLED, Device as WLEDDevice, WLEDConnectionClosed, WLEDError from wled import WLED, Device as WLEDDevice, WLEDConnectionClosed, WLEDError
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, LOGGER, SCAN_INTERVAL from .const import (
CONF_KEEP_MASTER_LIGHT,
DEFAULT_KEEP_MASTER_LIGHT,
DOMAIN,
LOGGER,
SCAN_INTERVAL,
)
class WLEDDataUpdateCoordinator(DataUpdateCoordinator[WLEDDevice]): class WLEDDataUpdateCoordinator(DataUpdateCoordinator[WLEDDevice]):
"""Class to manage fetching WLED data from single endpoint.""" """Class to manage fetching WLED data from single endpoint."""
keep_master_light: bool
def __init__( def __init__(
self, self,
hass: HomeAssistant, hass: HomeAssistant,
*, *,
host: str, entry: ConfigEntry,
) -> None: ) -> None:
"""Initialize global WLED data updater.""" """Initialize global WLED data updater."""
self.wled = WLED(host, session=async_get_clientsession(hass)) self.keep_master_light = entry.options.get(
CONF_KEEP_MASTER_LIGHT, DEFAULT_KEEP_MASTER_LIGHT
)
self.wled = WLED(entry.data[CONF_HOST], session=async_get_clientsession(hass))
self.unsub: Callable | None = None self.unsub: Callable | None = None
super().__init__( super().__init__(
@ -34,6 +46,13 @@ class WLEDDataUpdateCoordinator(DataUpdateCoordinator[WLEDDevice]):
update_interval=SCAN_INTERVAL, update_interval=SCAN_INTERVAL,
) )
@property
def has_master_light(self) -> bool:
"""Return if the coordinated device has an master light."""
return self.keep_master_light or (
self.data is not None and len(self.data.state.segments) > 1
)
def update_listeners(self) -> None: def update_listeners(self) -> None:
"""Call update on all listeners.""" """Call update on all listeners."""
for update_callback in self._listeners: for update_callback in self._listeners:

View file

@ -24,9 +24,6 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity_registry import (
async_get_registry as async_get_entity_registry,
)
from .const import ( from .const import (
ATTR_COLOR_PRIMARY, ATTR_COLOR_PRIMARY,
@ -38,8 +35,6 @@ from .const import (
ATTR_REVERSE, ATTR_REVERSE,
ATTR_SEGMENT_ID, ATTR_SEGMENT_ID,
ATTR_SPEED, ATTR_SPEED,
CONF_KEEP_MASTER_LIGHT,
DEFAULT_KEEP_MASTER_LIGHT,
DOMAIN, DOMAIN,
SERVICE_EFFECT, SERVICE_EFFECT,
SERVICE_PRESET, SERVICE_PRESET,
@ -87,17 +82,13 @@ async def async_setup_entry(
"async_preset", "async_preset",
) )
keep_master_light = entry.options.get( if coordinator.keep_master_light:
CONF_KEEP_MASTER_LIGHT, DEFAULT_KEEP_MASTER_LIGHT
)
if keep_master_light:
async_add_entities([WLEDMasterLight(coordinator=coordinator)]) async_add_entities([WLEDMasterLight(coordinator=coordinator)])
update_segments = partial( update_segments = partial(
async_update_segments, async_update_segments,
entry, entry,
coordinator, coordinator,
keep_master_light,
{}, {},
async_add_entities, async_add_entities,
) )
@ -130,6 +121,11 @@ class WLEDMasterLight(WLEDEntity, LightEntity):
"""Return the state of the light.""" """Return the state of the light."""
return bool(self.coordinator.data.state.on) return bool(self.coordinator.data.state.on)
@property
def available(self) -> bool:
"""Return if this master light is available or not."""
return self.coordinator.has_master_light and super().available
@wled_exception_handler @wled_exception_handler
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the light.""" """Turn off the light."""
@ -182,18 +178,17 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
self, self,
coordinator: WLEDDataUpdateCoordinator, coordinator: WLEDDataUpdateCoordinator,
segment: int, segment: int,
keep_master_light: bool,
) -> None: ) -> None:
"""Initialize WLED segment light.""" """Initialize WLED segment light."""
super().__init__(coordinator=coordinator) super().__init__(coordinator=coordinator)
self._keep_master_light = keep_master_light
self._rgbw = coordinator.data.info.leds.rgbw self._rgbw = coordinator.data.info.leds.rgbw
self._wv = coordinator.data.info.leds.wv self._wv = coordinator.data.info.leds.wv
self._segment = segment self._segment = segment
# If this is the one and only segment, use a simpler name # Segment 0 uses a simpler name, which is more natural for when using
# a single segment / using WLED with one big LED strip.
self._attr_name = f"{coordinator.data.info.name} Segment {segment}" self._attr_name = f"{coordinator.data.info.name} Segment {segment}"
if len(coordinator.data.state.segments) == 1: if segment == 0:
self._attr_name = coordinator.data.info.name self._attr_name = coordinator.data.info.name
self._attr_unique_id = ( self._attr_unique_id = (
@ -264,7 +259,7 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
# If this is the one and only segment, calculate brightness based # If this is the one and only segment, calculate brightness based
# on the master and segment brightness # on the master and segment brightness
if not self._keep_master_light and len(state.segments) == 1: if not self.coordinator.has_master_light:
return int( return int(
(state.segments[self._segment].brightness * state.brightness) / 255 (state.segments[self._segment].brightness * state.brightness) / 255
) )
@ -281,8 +276,9 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
"""Return the state of the light.""" """Return the state of the light."""
state = self.coordinator.data.state state = self.coordinator.data.state
# If there is a single segment, take master into account # If there is no master, we take the master state into account
if len(state.segments) == 1 and not state.on: # on the segment level.
if not self.coordinator.has_master_light and not state.on:
return False return False
return bool(state.segments[self._segment].on) return bool(state.segments[self._segment].on)
@ -295,11 +291,8 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
# WLED uses 100ms per unit, so 10 = 1 second. # WLED uses 100ms per unit, so 10 = 1 second.
transition = round(kwargs[ATTR_TRANSITION] * 10) transition = round(kwargs[ATTR_TRANSITION] * 10)
# If there is a single segment, control via the master # If there is no master control, and only 1 segment, handle the
if ( if not self.coordinator.has_master_light:
not self._keep_master_light
and len(self.coordinator.data.state.segments) == 1
):
await self.coordinator.wled.master(on=False, transition=transition) await self.coordinator.wled.master(on=False, transition=transition)
return return
@ -331,12 +324,8 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
if ATTR_EFFECT in kwargs: if ATTR_EFFECT in kwargs:
data[ATTR_EFFECT] = kwargs[ATTR_EFFECT] data[ATTR_EFFECT] = kwargs[ATTR_EFFECT]
# When only 1 segment is present, switch along the master, and use # If there is no master control, and only 1 segment, handle the master
# the master for power/brightness control. if not self.coordinator.has_master_light:
if (
not self._keep_master_light
and len(self.coordinator.data.state.segments) == 1
):
master_data = {ATTR_ON: True} master_data = {ATTR_ON: True}
if ATTR_BRIGHTNESS in data: if ATTR_BRIGHTNESS in data:
master_data[ATTR_BRIGHTNESS] = data[ATTR_BRIGHTNESS] master_data[ATTR_BRIGHTNESS] = data[ATTR_BRIGHTNESS]
@ -384,56 +373,28 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
def async_update_segments( def async_update_segments(
entry: ConfigEntry, entry: ConfigEntry,
coordinator: WLEDDataUpdateCoordinator, coordinator: WLEDDataUpdateCoordinator,
keep_master_light: bool,
current: dict[int, WLEDSegmentLight | WLEDMasterLight], current: dict[int, WLEDSegmentLight | WLEDMasterLight],
async_add_entities, async_add_entities,
) -> None: ) -> None:
"""Update segments.""" """Update segments."""
segment_ids = {light.segment_id for light in coordinator.data.state.segments} segment_ids = {light.segment_id for light in coordinator.data.state.segments}
current_ids = set(current) current_ids = set(current)
new_entities = []
# Discard master (if present) # Discard master (if present)
current_ids.discard(-1) current_ids.discard(-1)
new_entities = []
# Process new segments, add them to Home Assistant # Process new segments, add them to Home Assistant
for segment_id in segment_ids - current_ids: for segment_id in segment_ids - current_ids:
current[segment_id] = WLEDSegmentLight( current[segment_id] = WLEDSegmentLight(coordinator, segment_id)
coordinator, segment_id, keep_master_light
)
new_entities.append(current[segment_id]) new_entities.append(current[segment_id])
# More than 1 segment now? Add master controls # More than 1 segment now? No master? Add master controls
if not keep_master_light and (len(current_ids) < 2 and len(segment_ids) > 1): if not coordinator.keep_master_light and (
len(current_ids) < 2 and len(segment_ids) > 1
):
current[-1] = WLEDMasterLight(coordinator) current[-1] = WLEDMasterLight(coordinator)
new_entities.append(current[-1]) new_entities.append(current[-1])
if new_entities: if new_entities:
async_add_entities(new_entities) async_add_entities(new_entities)
# Process deleted segments, remove them from Home Assistant
for segment_id in current_ids - segment_ids:
coordinator.hass.async_create_task(
async_remove_entity(segment_id, coordinator, current)
)
# Remove master if there is only 1 segment left
if not keep_master_light and len(current_ids) > 1 and len(segment_ids) < 2:
coordinator.hass.async_create_task(
async_remove_entity(-1, coordinator, current)
)
async def async_remove_entity(
index: int,
coordinator: WLEDDataUpdateCoordinator,
current: dict[int, WLEDSegmentLight | WLEDMasterLight],
) -> None:
"""Remove WLED segment light from Home Assistant."""
entity = current[index]
await entity.async_remove(force_remove=True)
registry = await async_get_entity_registry(coordinator.hass)
if entity.entity_id in registry.entities:
registry.async_remove(entity.entity_id)
del current[index]

View file

@ -50,7 +50,7 @@ async def test_rgb_light_state(
entity_registry = er.async_get(hass) entity_registry = er.async_get(hass)
# First segment of the strip # First segment of the strip
state = hass.states.get("light.wled_rgb_light_segment_0") state = hass.states.get("light.wled_rgb_light")
assert state assert state
assert state.attributes.get(ATTR_BRIGHTNESS) == 127 assert state.attributes.get(ATTR_BRIGHTNESS) == 127
assert state.attributes.get(ATTR_EFFECT) == "Solid" assert state.attributes.get(ATTR_EFFECT) == "Solid"
@ -64,7 +64,7 @@ async def test_rgb_light_state(
assert state.attributes.get(ATTR_SPEED) == 32 assert state.attributes.get(ATTR_SPEED) == 32
assert state.state == STATE_ON assert state.state == STATE_ON
entry = entity_registry.async_get("light.wled_rgb_light_segment_0") entry = entity_registry.async_get("light.wled_rgb_light")
assert entry assert entry
assert entry.unique_id == "aabbccddeeff_0" assert entry.unique_id == "aabbccddeeff_0"
@ -107,7 +107,7 @@ async def test_segment_change_state(
await hass.services.async_call( await hass.services.async_call(
LIGHT_DOMAIN, LIGHT_DOMAIN,
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_TRANSITION: 5}, {ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_TRANSITION: 5},
blocking=True, blocking=True,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
@ -124,7 +124,7 @@ async def test_segment_change_state(
{ {
ATTR_BRIGHTNESS: 42, ATTR_BRIGHTNESS: 42,
ATTR_EFFECT: "Chase", ATTR_EFFECT: "Chase",
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_ENTITY_ID: "light.wled_rgb_light",
ATTR_RGB_COLOR: [255, 0, 0], ATTR_RGB_COLOR: [255, 0, 0],
ATTR_TRANSITION: 5, ATTR_TRANSITION: 5,
}, },
@ -211,36 +211,53 @@ async def test_master_change_state(
) )
@pytest.mark.parametrize("mock_wled", ["wled/rgb_single_segment.json"], indirect=True)
async def test_dynamically_handle_segments( async def test_dynamically_handle_segments(
hass: HomeAssistant, hass: HomeAssistant,
init_integration: MockConfigEntry, init_integration: MockConfigEntry,
mock_wled: MagicMock, mock_wled: MagicMock,
) -> None: ) -> None:
"""Test if a new/deleted segment is dynamically added/removed.""" """Test if a new/deleted segment is dynamically added/removed."""
assert hass.states.get("light.wled_rgb_light_master") master = hass.states.get("light.wled_rgb_light_master")
assert hass.states.get("light.wled_rgb_light_segment_0") segment0 = hass.states.get("light.wled_rgb_light")
assert hass.states.get("light.wled_rgb_light_segment_1") segment1 = hass.states.get("light.wled_rgb_light_segment_1")
assert segment0
assert segment0.state == STATE_ON
assert not master
assert not segment1
return_value = mock_wled.update.return_value return_value = mock_wled.update.return_value
mock_wled.update.return_value = WLEDDevice( mock_wled.update.return_value = WLEDDevice(
json.loads(load_fixture("wled/rgb_single_segment.json")) json.loads(load_fixture("wled/rgb.json"))
) )
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("light.wled_rgb_light_segment_0") master = hass.states.get("light.wled_rgb_light_master")
assert not hass.states.get("light.wled_rgb_light_segment_1") segment0 = hass.states.get("light.wled_rgb_light")
assert not hass.states.get("light.wled_rgb_light_master") segment1 = hass.states.get("light.wled_rgb_light_segment_1")
assert master
assert master.state == STATE_ON
assert segment0
assert segment0.state == STATE_ON
assert segment1
assert segment1.state == STATE_ON
# Test adding if segment shows up again, including the master entity # Test adding if segment shows up again, including the master entity
mock_wled.update.return_value = return_value mock_wled.update.return_value = return_value
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL) async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
await hass.async_block_till_done() await hass.async_block_till_done()
assert hass.states.get("light.wled_rgb_light_master") master = hass.states.get("light.wled_rgb_light_master")
assert hass.states.get("light.wled_rgb_light_segment_0") segment0 = hass.states.get("light.wled_rgb_light")
assert hass.states.get("light.wled_rgb_light_segment_1") segment1 = hass.states.get("light.wled_rgb_light_segment_1")
assert master
assert master.state == STATE_UNAVAILABLE
assert segment0
assert segment0.state == STATE_ON
assert segment1
assert segment1.state == STATE_UNAVAILABLE
@pytest.mark.parametrize("mock_wled", ["wled/rgb_single_segment.json"], indirect=True) @pytest.mark.parametrize("mock_wled", ["wled/rgb_single_segment.json"], indirect=True)
@ -320,12 +337,12 @@ async def test_light_error(
await hass.services.async_call( await hass.services.async_call(
LIGHT_DOMAIN, LIGHT_DOMAIN,
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0"}, {ATTR_ENTITY_ID: "light.wled_rgb_light"},
blocking=True, blocking=True,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get("light.wled_rgb_light_segment_0") state = hass.states.get("light.wled_rgb_light")
assert state assert state
assert state.state == STATE_ON assert state.state == STATE_ON
assert "Invalid response from API" in caplog.text assert "Invalid response from API" in caplog.text
@ -345,12 +362,12 @@ async def test_light_connection_error(
await hass.services.async_call( await hass.services.async_call(
LIGHT_DOMAIN, LIGHT_DOMAIN,
SERVICE_TURN_OFF, SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0"}, {ATTR_ENTITY_ID: "light.wled_rgb_light"},
blocking=True, blocking=True,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get("light.wled_rgb_light_segment_0") state = hass.states.get("light.wled_rgb_light")
assert state assert state
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
assert "Error communicating with API" in caplog.text assert "Error communicating with API" in caplog.text
@ -395,7 +412,7 @@ async def test_effect_service(
SERVICE_EFFECT, SERVICE_EFFECT,
{ {
ATTR_EFFECT: "Rainbow", ATTR_EFFECT: "Rainbow",
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_ENTITY_ID: "light.wled_rgb_light",
ATTR_INTENSITY: 200, ATTR_INTENSITY: 200,
ATTR_PALETTE: "Tiamat", ATTR_PALETTE: "Tiamat",
ATTR_REVERSE: True, ATTR_REVERSE: True,
@ -417,7 +434,7 @@ async def test_effect_service(
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_EFFECT, SERVICE_EFFECT,
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_EFFECT: 9}, {ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_EFFECT: 9},
blocking=True, blocking=True,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
@ -435,7 +452,7 @@ async def test_effect_service(
DOMAIN, DOMAIN,
SERVICE_EFFECT, SERVICE_EFFECT,
{ {
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_ENTITY_ID: "light.wled_rgb_light",
ATTR_INTENSITY: 200, ATTR_INTENSITY: 200,
ATTR_REVERSE: True, ATTR_REVERSE: True,
ATTR_SPEED: 100, ATTR_SPEED: 100,
@ -458,7 +475,7 @@ async def test_effect_service(
SERVICE_EFFECT, SERVICE_EFFECT,
{ {
ATTR_EFFECT: "Rainbow", ATTR_EFFECT: "Rainbow",
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_ENTITY_ID: "light.wled_rgb_light",
ATTR_PALETTE: "Tiamat", ATTR_PALETTE: "Tiamat",
ATTR_REVERSE: True, ATTR_REVERSE: True,
ATTR_SPEED: 100, ATTR_SPEED: 100,
@ -481,7 +498,7 @@ async def test_effect_service(
SERVICE_EFFECT, SERVICE_EFFECT,
{ {
ATTR_EFFECT: "Rainbow", ATTR_EFFECT: "Rainbow",
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_ENTITY_ID: "light.wled_rgb_light",
ATTR_INTENSITY: 200, ATTR_INTENSITY: 200,
ATTR_SPEED: 100, ATTR_SPEED: 100,
}, },
@ -503,7 +520,7 @@ async def test_effect_service(
SERVICE_EFFECT, SERVICE_EFFECT,
{ {
ATTR_EFFECT: "Rainbow", ATTR_EFFECT: "Rainbow",
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_ENTITY_ID: "light.wled_rgb_light",
ATTR_INTENSITY: 200, ATTR_INTENSITY: 200,
ATTR_REVERSE: True, ATTR_REVERSE: True,
}, },
@ -533,12 +550,12 @@ async def test_effect_service_error(
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_EFFECT, SERVICE_EFFECT,
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_EFFECT: 9}, {ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_EFFECT: 9},
blocking=True, blocking=True,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get("light.wled_rgb_light_segment_0") state = hass.states.get("light.wled_rgb_light")
assert state assert state
assert state.state == STATE_ON assert state.state == STATE_ON
assert "Invalid response from API" in caplog.text assert "Invalid response from API" in caplog.text
@ -556,7 +573,7 @@ async def test_preset_service(
DOMAIN, DOMAIN,
SERVICE_PRESET, SERVICE_PRESET,
{ {
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_ENTITY_ID: "light.wled_rgb_light",
ATTR_PRESET: 1, ATTR_PRESET: 1,
}, },
blocking=True, blocking=True,
@ -591,12 +608,12 @@ async def test_preset_service_error(
await hass.services.async_call( await hass.services.async_call(
DOMAIN, DOMAIN,
SERVICE_PRESET, SERVICE_PRESET,
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_PRESET: 1}, {ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_PRESET: 1},
blocking=True, blocking=True,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get("light.wled_rgb_light_segment_0") state = hass.states.get("light.wled_rgb_light")
assert state assert state
assert state.state == STATE_ON assert state.state == STATE_ON
assert "Invalid response from API" in caplog.text assert "Invalid response from API" in caplog.text