mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 09:17:10 +00:00
Fix WLED power and brightness with WLED 0.10+ (#36529)
This commit is contained in:
parent
233284056a
commit
215c7e0e14
@ -83,20 +83,78 @@ async def async_setup_entry(
|
|||||||
update_segments()
|
update_segments()
|
||||||
|
|
||||||
|
|
||||||
class WLEDLight(LightEntity, WLEDDeviceEntity):
|
class WLEDMasterLight(LightEntity, WLEDDeviceEntity):
|
||||||
"""Defines a WLED light."""
|
"""Defines a WLED master light."""
|
||||||
|
|
||||||
|
def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator):
|
||||||
|
"""Initialize WLED master light."""
|
||||||
|
super().__init__(
|
||||||
|
entry_id=entry_id,
|
||||||
|
coordinator=coordinator,
|
||||||
|
name=f"{coordinator.data.info.name} Master",
|
||||||
|
icon="mdi:led-strip-variant",
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unique_id(self) -> str:
|
||||||
|
"""Return the unique ID for this sensor."""
|
||||||
|
return f"{self.coordinator.data.info.mac_address}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def supported_features(self) -> int:
|
||||||
|
"""Flag supported features."""
|
||||||
|
return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION
|
||||||
|
|
||||||
|
@property
|
||||||
|
def brightness(self) -> Optional[int]:
|
||||||
|
"""Return the brightness of this light between 1..255."""
|
||||||
|
return self.coordinator.data.state.brightness
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_on(self) -> bool:
|
||||||
|
"""Return the state of the light."""
|
||||||
|
return bool(self.coordinator.data.state.on)
|
||||||
|
|
||||||
|
@wled_exception_handler
|
||||||
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn off the light."""
|
||||||
|
data = {ATTR_ON: False}
|
||||||
|
|
||||||
|
if ATTR_TRANSITION in kwargs:
|
||||||
|
# WLED uses 100ms per unit, so 10 = 1 second.
|
||||||
|
data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10)
|
||||||
|
|
||||||
|
await self.coordinator.wled.master(**data)
|
||||||
|
|
||||||
|
@wled_exception_handler
|
||||||
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
|
"""Turn on the light."""
|
||||||
|
data = {ATTR_ON: True}
|
||||||
|
|
||||||
|
if ATTR_TRANSITION in kwargs:
|
||||||
|
# WLED uses 100ms per unit, so 10 = 1 second.
|
||||||
|
data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10)
|
||||||
|
|
||||||
|
if ATTR_BRIGHTNESS in kwargs:
|
||||||
|
data[ATTR_BRIGHTNESS] = kwargs[ATTR_BRIGHTNESS]
|
||||||
|
|
||||||
|
await self.coordinator.wled.master(**data)
|
||||||
|
|
||||||
|
|
||||||
|
class WLEDSegmentLight(LightEntity, WLEDDeviceEntity):
|
||||||
|
"""Defines a WLED light based on a segment."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, entry_id: str, coordinator: WLEDDataUpdateCoordinator, segment: int
|
self, entry_id: str, coordinator: WLEDDataUpdateCoordinator, segment: int
|
||||||
):
|
):
|
||||||
"""Initialize WLED light."""
|
"""Initialize WLED segment light."""
|
||||||
self._rgbw = coordinator.data.info.leds.rgbw
|
self._rgbw = coordinator.data.info.leds.rgbw
|
||||||
self._segment = segment
|
self._segment = segment
|
||||||
|
|
||||||
# Only apply the segment ID if it is not the first segment
|
# If this is the one and only segment, use a simpler name
|
||||||
name = coordinator.data.info.name
|
name = f"{coordinator.data.info.name} Segment {self._segment}"
|
||||||
if segment != 0:
|
if len(coordinator.data.state.segments) == 1:
|
||||||
name += f" {segment}"
|
name = coordinator.data.info.name
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
entry_id=entry_id,
|
entry_id=entry_id,
|
||||||
@ -155,7 +213,16 @@ class WLEDLight(LightEntity, WLEDDeviceEntity):
|
|||||||
@property
|
@property
|
||||||
def brightness(self) -> Optional[int]:
|
def brightness(self) -> Optional[int]:
|
||||||
"""Return the brightness of this light between 1..255."""
|
"""Return the brightness of this light between 1..255."""
|
||||||
return self.coordinator.data.state.brightness
|
state = self.coordinator.data.state
|
||||||
|
|
||||||
|
# If this is the one and only segment, calculate brightness based
|
||||||
|
# on the master and segment brightness
|
||||||
|
if len(state.segments) == 1:
|
||||||
|
return int(
|
||||||
|
(state.segments[self._segment].brightness * state.brightness) / 255
|
||||||
|
)
|
||||||
|
|
||||||
|
return state.segments[self._segment].brightness
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def white_value(self) -> Optional[int]:
|
def white_value(self) -> Optional[int]:
|
||||||
@ -187,18 +254,30 @@ class WLEDLight(LightEntity, WLEDDeviceEntity):
|
|||||||
@property
|
@property
|
||||||
def is_on(self) -> bool:
|
def is_on(self) -> bool:
|
||||||
"""Return the state of the light."""
|
"""Return the state of the light."""
|
||||||
return bool(self.coordinator.data.state.on)
|
state = self.coordinator.data.state
|
||||||
|
|
||||||
|
# If there is a single segment, take master into account
|
||||||
|
if len(state.segments) == 1 and not state.on:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return bool(state.segments[self._segment].on)
|
||||||
|
|
||||||
@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."""
|
||||||
data = {ATTR_ON: False, ATTR_SEGMENT_ID: self._segment}
|
data = {ATTR_ON: False}
|
||||||
|
|
||||||
if ATTR_TRANSITION in kwargs:
|
if ATTR_TRANSITION in kwargs:
|
||||||
# WLED uses 100ms per unit, so 10 = 1 second.
|
# WLED uses 100ms per unit, so 10 = 1 second.
|
||||||
data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10)
|
data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10)
|
||||||
|
|
||||||
await self.coordinator.wled.light(**data)
|
# If there is a single segment, control via the master
|
||||||
|
if len(self.coordinator.data.state.segments) == 1:
|
||||||
|
await self.coordinator.wled.master(**data)
|
||||||
|
return
|
||||||
|
|
||||||
|
data[ATTR_SEGMENT_ID] = self._segment
|
||||||
|
await self.coordinator.wled.segment(**data)
|
||||||
|
|
||||||
@wled_exception_handler
|
@wled_exception_handler
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
@ -248,7 +327,23 @@ class WLEDLight(LightEntity, WLEDDeviceEntity):
|
|||||||
else:
|
else:
|
||||||
data[ATTR_COLOR_PRIMARY] += (self.white_value,)
|
data[ATTR_COLOR_PRIMARY] += (self.white_value,)
|
||||||
|
|
||||||
await self.coordinator.wled.light(**data)
|
# When only 1 segment is present, switch along the master, and use
|
||||||
|
# the master for power/brightness control.
|
||||||
|
if len(self.coordinator.data.state.segments) == 1:
|
||||||
|
master_data = {ATTR_ON: True}
|
||||||
|
if ATTR_BRIGHTNESS in data:
|
||||||
|
master_data[ATTR_BRIGHTNESS] = data[ATTR_BRIGHTNESS]
|
||||||
|
data[ATTR_BRIGHTNESS] = 255
|
||||||
|
|
||||||
|
if ATTR_TRANSITION in data:
|
||||||
|
master_data[ATTR_TRANSITION] = data[ATTR_TRANSITION]
|
||||||
|
del data[ATTR_TRANSITION]
|
||||||
|
|
||||||
|
await self.coordinator.wled.segment(**data)
|
||||||
|
await self.coordinator.wled.master(**master_data)
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.coordinator.wled.segment(**data)
|
||||||
|
|
||||||
@wled_exception_handler
|
@wled_exception_handler
|
||||||
async def async_effect(
|
async def async_effect(
|
||||||
@ -273,45 +368,59 @@ class WLEDLight(LightEntity, WLEDDeviceEntity):
|
|||||||
if speed is not None:
|
if speed is not None:
|
||||||
data[ATTR_SPEED] = speed
|
data[ATTR_SPEED] = speed
|
||||||
|
|
||||||
await self.coordinator.wled.light(**data)
|
await self.coordinator.wled.segment(**data)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_update_segments(
|
def async_update_segments(
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
coordinator: WLEDDataUpdateCoordinator,
|
coordinator: WLEDDataUpdateCoordinator,
|
||||||
current: Dict[int, WLEDLight],
|
current: Dict[int, WLEDSegmentLight],
|
||||||
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)
|
||||||
|
|
||||||
# Process new segments, add them to Home Assistant
|
# Discard master (if present)
|
||||||
new_segments = []
|
current_ids.discard(-1)
|
||||||
for segment_id in segment_ids - current_ids:
|
|
||||||
current[segment_id] = WLEDLight(entry.entry_id, coordinator, segment_id)
|
|
||||||
new_segments.append(current[segment_id])
|
|
||||||
|
|
||||||
if new_segments:
|
# Process new segments, add them to Home Assistant
|
||||||
async_add_entities(new_segments)
|
new_entities = []
|
||||||
|
for segment_id in segment_ids - current_ids:
|
||||||
|
current[segment_id] = WLEDSegmentLight(entry.entry_id, coordinator, segment_id)
|
||||||
|
new_entities.append(current[segment_id])
|
||||||
|
|
||||||
|
# More than 1 segment now? Add master controls
|
||||||
|
if len(current_ids) < 2 and len(segment_ids) > 1:
|
||||||
|
current[-1] = WLEDMasterLight(entry.entry_id, coordinator)
|
||||||
|
new_entities.append(current[-1])
|
||||||
|
|
||||||
|
if new_entities:
|
||||||
|
async_add_entities(new_entities)
|
||||||
|
|
||||||
# Process deleted segments, remove them from Home Assistant
|
# Process deleted segments, remove them from Home Assistant
|
||||||
for segment_id in current_ids - segment_ids:
|
for segment_id in current_ids - segment_ids:
|
||||||
coordinator.hass.async_create_task(
|
coordinator.hass.async_create_task(
|
||||||
async_remove_segment(segment_id, coordinator, current)
|
async_remove_entity(segment_id, coordinator, current)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove master if there is only 1 segment left
|
||||||
|
if len(current_ids) > 1 and len(segment_ids) < 2:
|
||||||
|
coordinator.hass.async_create_task(
|
||||||
|
async_remove_entity(-1, coordinator, current)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_remove_segment(
|
async def async_remove_entity(
|
||||||
segment_id: int,
|
index: int,
|
||||||
coordinator: WLEDDataUpdateCoordinator,
|
coordinator: WLEDDataUpdateCoordinator,
|
||||||
current: Dict[int, WLEDLight],
|
current: Dict[int, WLEDSegmentLight],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Remove WLED segment light from Home Assistant."""
|
"""Remove WLED segment light from Home Assistant."""
|
||||||
entity = current[segment_id]
|
entity = current[index]
|
||||||
await entity.async_remove()
|
await entity.async_remove()
|
||||||
registry = await async_get_entity_registry(coordinator.hass)
|
registry = await async_get_entity_registry(coordinator.hass)
|
||||||
if entity.entity_id in registry.entities:
|
if entity.entity_id in registry.entities:
|
||||||
registry.async_remove(entity.entity_id)
|
registry.async_remove(entity.entity_id)
|
||||||
del current[segment_id]
|
del current[index]
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "WLED",
|
"name": "WLED",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/wled",
|
"documentation": "https://www.home-assistant.io/integrations/wled",
|
||||||
"requirements": ["wled==0.4.1"],
|
"requirements": ["wled==0.4.2"],
|
||||||
"zeroconf": ["_wled._tcp.local."],
|
"zeroconf": ["_wled._tcp.local."],
|
||||||
"codeowners": ["@frenck"],
|
"codeowners": ["@frenck"],
|
||||||
"quality_scale": "platinum"
|
"quality_scale": "platinum"
|
||||||
|
@ -2201,7 +2201,7 @@ wirelesstagpy==0.4.0
|
|||||||
withings-api==2.1.3
|
withings-api==2.1.3
|
||||||
|
|
||||||
# homeassistant.components.wled
|
# homeassistant.components.wled
|
||||||
wled==0.4.1
|
wled==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.xbee
|
# homeassistant.components.xbee
|
||||||
xbee-helper==0.0.7
|
xbee-helper==0.0.7
|
||||||
|
@ -910,7 +910,7 @@ wiffi==1.0.0
|
|||||||
withings-api==2.1.3
|
withings-api==2.1.3
|
||||||
|
|
||||||
# homeassistant.components.wled
|
# homeassistant.components.wled
|
||||||
wled==0.4.1
|
wled==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.bluesound
|
# homeassistant.components.bluesound
|
||||||
# homeassistant.components.rest
|
# homeassistant.components.rest
|
||||||
|
@ -29,6 +29,7 @@ from homeassistant.const import (
|
|||||||
ATTR_ICON,
|
ATTR_ICON,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
STATE_UNAVAILABLE,
|
STATE_UNAVAILABLE,
|
||||||
)
|
)
|
||||||
@ -50,7 +51,7 @@ async def test_rgb_light_state(
|
|||||||
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
entity_registry = await hass.helpers.entity_registry.async_get_registry()
|
||||||
|
|
||||||
# First segment of the strip
|
# First segment of the strip
|
||||||
state = hass.states.get("light.wled_rgb_light")
|
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||||
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,12 +65,12 @@ 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")
|
entry = entity_registry.async_get("light.wled_rgb_light_segment_0")
|
||||||
assert entry
|
assert entry
|
||||||
assert entry.unique_id == "aabbccddeeff_0"
|
assert entry.unique_id == "aabbccddeeff_0"
|
||||||
|
|
||||||
# Second segment of the strip
|
# Second segment of the strip
|
||||||
state = hass.states.get("light.wled_rgb_light_1")
|
state = hass.states.get("light.wled_rgb_light_segment_1")
|
||||||
assert state
|
assert state
|
||||||
assert state.attributes.get(ATTR_BRIGHTNESS) == 127
|
assert state.attributes.get(ATTR_BRIGHTNESS) == 127
|
||||||
assert state.attributes.get(ATTR_EFFECT) == "Blink"
|
assert state.attributes.get(ATTR_EFFECT) == "Blink"
|
||||||
@ -83,22 +84,32 @@ async def test_rgb_light_state(
|
|||||||
assert state.attributes.get(ATTR_SPEED) == 16
|
assert state.attributes.get(ATTR_SPEED) == 16
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
entry = entity_registry.async_get("light.wled_rgb_light_1")
|
entry = entity_registry.async_get("light.wled_rgb_light_segment_1")
|
||||||
assert entry
|
assert entry
|
||||||
assert entry.unique_id == "aabbccddeeff_1"
|
assert entry.unique_id == "aabbccddeeff_1"
|
||||||
|
|
||||||
|
# Test master control of the lightstrip
|
||||||
|
state = hass.states.get("light.wled_rgb_light_master")
|
||||||
|
assert state
|
||||||
|
assert state.attributes.get(ATTR_BRIGHTNESS) == 127
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
async def test_switch_change_state(
|
entry = entity_registry.async_get("light.wled_rgb_light_master")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == "aabbccddeeff"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_segment_change_state(
|
||||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the change of state of the WLED switches."""
|
"""Test the change of state of the WLED segments."""
|
||||||
await init_integration(hass, aioclient_mock)
|
await init_integration(hass, aioclient_mock)
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
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", ATTR_TRANSITION: 5},
|
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_TRANSITION: 5},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -106,14 +117,14 @@ async def test_switch_change_state(
|
|||||||
on=False, segment_id=0, transition=50,
|
on=False, segment_id=0, transition=50,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
{
|
{
|
||||||
ATTR_BRIGHTNESS: 42,
|
ATTR_BRIGHTNESS: 42,
|
||||||
ATTR_EFFECT: "Chase",
|
ATTR_EFFECT: "Chase",
|
||||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||||
ATTR_RGB_COLOR: [255, 0, 0],
|
ATTR_RGB_COLOR: [255, 0, 0],
|
||||||
ATTR_TRANSITION: 5,
|
ATTR_TRANSITION: 5,
|
||||||
},
|
},
|
||||||
@ -129,11 +140,11 @@ async def test_switch_change_state(
|
|||||||
transition=50,
|
transition=50,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
{ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_COLOR_TEMP: 400},
|
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_COLOR_TEMP: 400},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -142,33 +153,178 @@ async def test_switch_change_state(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_master_change_state(
|
||||||
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog
|
||||||
|
) -> None:
|
||||||
|
"""Test the change of state of the WLED master light control."""
|
||||||
|
await init_integration(hass, aioclient_mock)
|
||||||
|
|
||||||
|
with patch("wled.WLED.master") as light_mock:
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{ATTR_ENTITY_ID: "light.wled_rgb_light_master", ATTR_TRANSITION: 5},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
light_mock.assert_called_once_with(
|
||||||
|
on=False, transition=50,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("wled.WLED.master") as light_mock:
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{
|
||||||
|
ATTR_BRIGHTNESS: 42,
|
||||||
|
ATTR_ENTITY_ID: "light.wled_rgb_light_master",
|
||||||
|
ATTR_TRANSITION: 5,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
light_mock.assert_called_once_with(
|
||||||
|
brightness=42, on=True, transition=50,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("wled.WLED.master") as light_mock:
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{ATTR_ENTITY_ID: "light.wled_rgb_light_master", ATTR_TRANSITION: 5},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
light_mock.assert_called_once_with(
|
||||||
|
on=False, transition=50,
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch("wled.WLED.master") as light_mock:
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{
|
||||||
|
ATTR_BRIGHTNESS: 42,
|
||||||
|
ATTR_ENTITY_ID: "light.wled_rgb_light_master",
|
||||||
|
ATTR_TRANSITION: 5,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
light_mock.assert_called_once_with(
|
||||||
|
brightness=42, on=True, transition=50,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_dynamically_handle_segments(
|
async def test_dynamically_handle_segments(
|
||||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test if a new/deleted segment is dynamically added/removed."""
|
"""Test if a new/deleted segment is dynamically added/removed."""
|
||||||
await init_integration(hass, aioclient_mock)
|
await init_integration(hass, aioclient_mock)
|
||||||
|
|
||||||
assert hass.states.get("light.wled_rgb_light")
|
assert hass.states.get("light.wled_rgb_light_master")
|
||||||
assert hass.states.get("light.wled_rgb_light_1")
|
assert hass.states.get("light.wled_rgb_light_segment_0")
|
||||||
|
assert hass.states.get("light.wled_rgb_light_segment_1")
|
||||||
|
|
||||||
data = json.loads(load_fixture("wled/rgb_single_segment.json"))
|
data = json.loads(load_fixture("wled/rgb_single_segment.json"))
|
||||||
device = WLEDDevice(data)
|
device = WLEDDevice(data)
|
||||||
|
|
||||||
# Test removal if segment went missing
|
# Test removal if segment went missing, including the master entity
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.wled.WLED.update", return_value=device,
|
"homeassistant.components.wled.WLED.update", return_value=device,
|
||||||
):
|
):
|
||||||
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")
|
assert hass.states.get("light.wled_rgb_light_segment_0")
|
||||||
assert not hass.states.get("light.wled_rgb_light_1")
|
assert not hass.states.get("light.wled_rgb_light_segment_1")
|
||||||
|
assert not hass.states.get("light.wled_rgb_light_master")
|
||||||
|
|
||||||
# Test adding if segment shows up again
|
# Test adding if segment shows up again, including the master entity
|
||||||
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")
|
assert hass.states.get("light.wled_rgb_light_master")
|
||||||
assert hass.states.get("light.wled_rgb_light_1")
|
assert hass.states.get("light.wled_rgb_light_segment_0")
|
||||||
|
assert hass.states.get("light.wled_rgb_light_segment_1")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_single_segment_behavior(
|
||||||
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, caplog
|
||||||
|
) -> None:
|
||||||
|
"""Test the behavior of the integration with a single segment."""
|
||||||
|
await init_integration(hass, aioclient_mock)
|
||||||
|
|
||||||
|
data = json.loads(load_fixture("wled/rgb_single_segment.json"))
|
||||||
|
device = WLEDDevice(data)
|
||||||
|
|
||||||
|
# Test absent master
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.wled.WLED.update", return_value=device,
|
||||||
|
):
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert not hass.states.get("light.wled_rgb_light_master")
|
||||||
|
|
||||||
|
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
# Test segment brightness takes master into account
|
||||||
|
device.state.brightness = 100
|
||||||
|
device.state.segments[0].brightness = 255
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.wled.WLED.update", return_value=device,
|
||||||
|
):
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||||
|
assert state
|
||||||
|
assert state.attributes.get(ATTR_BRIGHTNESS) == 100
|
||||||
|
|
||||||
|
# Test segment is off when master is off
|
||||||
|
device.state.on = False
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.wled.WLED.update", return_value=device,
|
||||||
|
):
|
||||||
|
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
|
||||||
|
# Test master is turned off when turning off a single segment
|
||||||
|
with patch("wled.WLED.master") as master_mock:
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
SERVICE_TURN_OFF,
|
||||||
|
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_TRANSITION: 5},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
master_mock.assert_called_once_with(
|
||||||
|
on=False, transition=50,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Test master is turned on when turning on a single segment, and segment
|
||||||
|
# brightness is set to 255.
|
||||||
|
with patch("wled.WLED.master") as master_mock, patch(
|
||||||
|
"wled.WLED.segment"
|
||||||
|
) as segment_mock:
|
||||||
|
await hass.services.async_call(
|
||||||
|
LIGHT_DOMAIN,
|
||||||
|
SERVICE_TURN_ON,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||||
|
ATTR_TRANSITION: 5,
|
||||||
|
ATTR_BRIGHTNESS: 42,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
master_mock.assert_called_once_with(on=True, transition=50, brightness=42)
|
||||||
|
segment_mock.assert_called_once_with(on=True, segment_id=0, brightness=255)
|
||||||
|
|
||||||
|
|
||||||
async def test_light_error(
|
async def test_light_error(
|
||||||
@ -182,12 +338,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"},
|
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get("light.wled_rgb_light")
|
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||||
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
|
||||||
|
|
||||||
@ -199,17 +355,17 @@ async def test_light_connection_error(
|
|||||||
await init_integration(hass, aioclient_mock)
|
await init_integration(hass, aioclient_mock)
|
||||||
|
|
||||||
with patch("homeassistant.components.wled.WLED.update"), patch(
|
with patch("homeassistant.components.wled.WLED.update"), patch(
|
||||||
"homeassistant.components.wled.WLED.light", side_effect=WLEDConnectionError
|
"homeassistant.components.wled.WLED.segment", side_effect=WLEDConnectionError
|
||||||
):
|
):
|
||||||
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"},
|
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
state = hass.states.get("light.wled_rgb_light")
|
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||||
assert state.state == STATE_UNAVAILABLE
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
@ -224,7 +380,7 @@ async def test_rgbw_light(
|
|||||||
assert state.attributes.get(ATTR_HS_COLOR) == (0.0, 100.0)
|
assert state.attributes.get(ATTR_HS_COLOR) == (0.0, 100.0)
|
||||||
assert state.attributes.get(ATTR_WHITE_VALUE) == 139
|
assert state.attributes.get(ATTR_WHITE_VALUE) == 139
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
@ -236,7 +392,7 @@ async def test_rgbw_light(
|
|||||||
on=True, segment_id=0, color_primary=(255, 159, 70, 139),
|
on=True, segment_id=0, color_primary=(255, 159, 70, 139),
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
@ -248,7 +404,7 @@ async def test_rgbw_light(
|
|||||||
color_primary=(255, 0, 0, 100), on=True, segment_id=0,
|
color_primary=(255, 0, 0, 100), on=True, segment_id=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
LIGHT_DOMAIN,
|
LIGHT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
@ -271,13 +427,13 @@ async def test_effect_service(
|
|||||||
"""Test the effect service of a WLED light."""
|
"""Test the effect service of a WLED light."""
|
||||||
await init_integration(hass, aioclient_mock)
|
await init_integration(hass, aioclient_mock)
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
SERVICE_EFFECT,
|
||||||
{
|
{
|
||||||
ATTR_EFFECT: "Rainbow",
|
ATTR_EFFECT: "Rainbow",
|
||||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||||
ATTR_INTENSITY: 200,
|
ATTR_INTENSITY: 200,
|
||||||
ATTR_REVERSE: True,
|
ATTR_REVERSE: True,
|
||||||
ATTR_SPEED: 100,
|
ATTR_SPEED: 100,
|
||||||
@ -289,11 +445,11 @@ async def test_effect_service(
|
|||||||
effect="Rainbow", intensity=200, reverse=True, segment_id=0, speed=100,
|
effect="Rainbow", intensity=200, reverse=True, segment_id=0, speed=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
SERVICE_EFFECT,
|
||||||
{ATTR_ENTITY_ID: "light.wled_rgb_light", ATTR_EFFECT: 9},
|
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", ATTR_EFFECT: 9},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -301,12 +457,12 @@ async def test_effect_service(
|
|||||||
segment_id=0, effect=9,
|
segment_id=0, effect=9,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
SERVICE_EFFECT,
|
||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||||
ATTR_INTENSITY: 200,
|
ATTR_INTENSITY: 200,
|
||||||
ATTR_REVERSE: True,
|
ATTR_REVERSE: True,
|
||||||
ATTR_SPEED: 100,
|
ATTR_SPEED: 100,
|
||||||
@ -318,13 +474,13 @@ async def test_effect_service(
|
|||||||
intensity=200, reverse=True, segment_id=0, speed=100,
|
intensity=200, reverse=True, segment_id=0, speed=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
SERVICE_EFFECT,
|
||||||
{
|
{
|
||||||
ATTR_EFFECT: "Rainbow",
|
ATTR_EFFECT: "Rainbow",
|
||||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||||
ATTR_REVERSE: True,
|
ATTR_REVERSE: True,
|
||||||
ATTR_SPEED: 100,
|
ATTR_SPEED: 100,
|
||||||
},
|
},
|
||||||
@ -335,13 +491,13 @@ async def test_effect_service(
|
|||||||
effect="Rainbow", reverse=True, segment_id=0, speed=100,
|
effect="Rainbow", reverse=True, segment_id=0, speed=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
SERVICE_EFFECT,
|
||||||
{
|
{
|
||||||
ATTR_EFFECT: "Rainbow",
|
ATTR_EFFECT: "Rainbow",
|
||||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||||
ATTR_INTENSITY: 200,
|
ATTR_INTENSITY: 200,
|
||||||
ATTR_SPEED: 100,
|
ATTR_SPEED: 100,
|
||||||
},
|
},
|
||||||
@ -352,13 +508,13 @@ async def test_effect_service(
|
|||||||
effect="Rainbow", intensity=200, segment_id=0, speed=100,
|
effect="Rainbow", intensity=200, segment_id=0, speed=100,
|
||||||
)
|
)
|
||||||
|
|
||||||
with patch("wled.WLED.light") as light_mock:
|
with patch("wled.WLED.segment") as light_mock:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_EFFECT,
|
SERVICE_EFFECT,
|
||||||
{
|
{
|
||||||
ATTR_EFFECT: "Rainbow",
|
ATTR_EFFECT: "Rainbow",
|
||||||
ATTR_ENTITY_ID: "light.wled_rgb_light",
|
ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0",
|
||||||
ATTR_INTENSITY: 200,
|
ATTR_INTENSITY: 200,
|
||||||
ATTR_REVERSE: True,
|
ATTR_REVERSE: True,
|
||||||
},
|
},
|
||||||
@ -381,11 +537,11 @@ 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", ATTR_EFFECT: 9},
|
{ATTR_ENTITY_ID: "light.wled_rgb_light_segment_0", 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")
|
state = hass.states.get("light.wled_rgb_light_segment_0")
|
||||||
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user