Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion homeassistant/components/wled/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ def __init__(self, coordinator: WLEDDataUpdateCoordinator) -> None:
"""Initialize WLED main light."""
super().__init__(coordinator=coordinator)
self._attr_unique_id = coordinator.data.info.mac_address
if coordinator.keep_main_light:
self._attr_name = None
Comment on lines +69 to +70
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is expected. I covered this scenario in PR description. Migration would be too risky.

Comment on lines +69 to +70
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, it's too risky to change an entity ID that's already assigned or to delete entities that have already been created. If the keep main light option is enabled, the entity will be marked as unavailable and the user can manually delete it from the UI.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides, I think we should discourage disabling the "Keep Main Light" option and instead describe how to achieve a similar effect using automation. I even have such automations for multi-segment installations, and now there will also be a recommendation to use it for single-segment installations too.

One day, we might even retire the "Keep Main Light" option and leave it on by default but that's another thread.


@property
def brightness(self) -> int | None:
Expand Down Expand Up @@ -124,7 +126,7 @@ def __init__(

# Segment 0 uses a simpler name, which is more natural for when using
# a single segment / using WLED with one big LED strip.
if segment == 0:
if segment == 0 and not coordinator.keep_main_light:
self._attr_name = None
else:
self._attr_translation_placeholders = {"segment": str(segment)}
Expand Down
7 changes: 5 additions & 2 deletions homeassistant/components/wled/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ class WLEDNumberEntityDescription(NumberEntityDescription):
"""Class describing WLED number entities."""

value_fn: Callable[[Segment], int | None]
segment_translation_key: str


NUMBERS = [
WLEDNumberEntityDescription(
key=ATTR_SPEED,
translation_key="speed",
segment_translation_key="segment_speed",
entity_category=EntityCategory.CONFIG,
native_step=1,
native_min_value=0,
Expand All @@ -57,6 +59,7 @@ class WLEDNumberEntityDescription(NumberEntityDescription):
WLEDNumberEntityDescription(
key=ATTR_INTENSITY,
translation_key="intensity",
segment_translation_key="segment_intensity",
entity_category=EntityCategory.CONFIG,
native_step=1,
native_min_value=0,
Expand All @@ -83,8 +86,8 @@ def __init__(

# Segment 0 uses a simpler name, which is more natural for when using
# a single segment / using WLED with one big LED strip.
if segment != 0:
self._attr_translation_key = f"segment_{description.translation_key}"
if segment != 0 or coordinator.keep_main_light:
self._attr_translation_key = description.segment_translation_key
self._attr_translation_placeholders = {"segment": str(segment)}

self._attr_unique_id = (
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/wled/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def __init__(self, coordinator: WLEDDataUpdateCoordinator, segment: int) -> None

# Segment 0 uses a simpler name, which is more natural for when using
# a single segment / using WLED with one big LED strip.
if segment != 0:
if segment != 0 or coordinator.keep_main_light:
self._attr_translation_key = "segment_color_palette"
self._attr_translation_placeholders = {"segment": str(segment)}

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/wled/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def __init__(

# Segment 0 uses a simpler name, which is more natural for when using
# a single segment / using WLED with one big LED strip.
if segment != 0:
if segment != 0 or coordinator.keep_main_light:
self._attr_translation_key = description.segment_translation_key
self._attr_translation_placeholders = {"segment": str(segment)}
else:
Expand Down
58 changes: 50 additions & 8 deletions tests/components/wled/test_light.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,23 +340,65 @@ async def test_rgbw_light(hass: HomeAssistant, mock_wled: MagicMock) -> None:
)


@pytest.mark.parametrize("device_fixture", ["rgb_single_segment"])
async def test_single_segment_with_keep_main_light(
@pytest.mark.parametrize(
("device_fixture", "keep_main_light", "expected_entities"),
[
(
"rgb_single_segment",
False,
{"light.wled_rgb_light": "WLED RGB Light"},
),
(
"rgb_single_segment",
True,
{
# Segment 0 keeps its existing entity_id (registry preserved).
# Main light is registered for the first time with _attr_name=None,
# whose slug collides with segment 0 → gets _2 suffix.
"light.wled_rgb_light": "WLED RGB Light Segment 0",
"light.wled_rgb_light_2": "WLED RGB Light",
},
),
(
"rgb",
False,
{
"light.wled_rgb_light_main": "WLED RGB Light Main",
"light.wled_rgb_light": "WLED RGB Light",
"light.wled_rgb_light_segment_1": "WLED RGB Light Segment 1",
},
),
(
"rgb",
True,
{
# Entity ids are preserved by the registry; only names change.
"light.wled_rgb_light_main": "WLED RGB Light",
"light.wled_rgb_light": "WLED RGB Light Segment 0",
"light.wled_rgb_light_segment_1": "WLED RGB Light Segment 1",
},
),
],
)
async def test_keep_main_light_entity_names(
hass: HomeAssistant,
init_integration: MockConfigEntry,
mock_wled: MagicMock,
keep_main_light: bool,
expected_entities: dict[str, str],
) -> None:
"""Test the behavior of the integration with a single segment."""
assert not hass.states.get("light.wled_rgb_light_main")

"""Test entity friendly names with and without the keep_main_light option."""
hass.config_entries.async_update_entry(
init_integration, options={CONF_KEEP_MAIN_LIGHT: True}
init_integration, options={CONF_KEEP_MAIN_LIGHT: keep_main_light}
)
await hass.config_entries.async_reload(init_integration.entry_id)
await hass.async_block_till_done()

assert (state := hass.states.get("light.wled_rgb_light_main"))
assert state.state == STATE_ON
actual_entities = {
state.entity_id: state.attributes.get("friendly_name")
for state in hass.states.async_all("light")
}
assert actual_entities == expected_entities


@pytest.mark.parametrize("device_fixture", ["cct"])
Expand Down
67 changes: 66 additions & 1 deletion tests/components/wled/test_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
DOMAIN as NUMBER_DOMAIN,
SERVICE_SET_VALUE,
)
from homeassistant.components.wled.const import DOMAIN, SCAN_INTERVAL
from homeassistant.components.wled.const import (
CONF_KEEP_MAIN_LIGHT,
DOMAIN,
SCAN_INTERVAL,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
Expand Down Expand Up @@ -173,3 +177,64 @@ async def test_speed_dynamically_handle_segments(
assert segment0.state == state_segment0
assert (segment1 := hass.states.get(entity_id_segment1))
assert segment1.state == STATE_UNAVAILABLE


@pytest.mark.parametrize(
("device_fixture", "keep_main_light", "expected_entities"),
[
(
"rgb_single_segment",
False,
{
"number.wled_rgb_light_speed": "WLED RGB Light Speed",
"number.wled_rgb_light_intensity": "WLED RGB Light Intensity",
},
),
(
"rgb_single_segment",
True,
{
"number.wled_rgb_light_speed": "WLED RGB Light Segment 0 speed",
"number.wled_rgb_light_intensity": "WLED RGB Light Segment 0 intensity",
},
),
(
"rgb",
False,
{
"number.wled_rgb_light_speed": "WLED RGB Light Speed",
"number.wled_rgb_light_intensity": "WLED RGB Light Intensity",
"number.wled_rgb_light_segment_1_speed": "WLED RGB Light Segment 1 speed",
"number.wled_rgb_light_segment_1_intensity": "WLED RGB Light Segment 1 intensity",
},
),
(
"rgb",
True,
{
"number.wled_rgb_light_speed": "WLED RGB Light Segment 0 speed",
"number.wled_rgb_light_intensity": "WLED RGB Light Segment 0 intensity",
"number.wled_rgb_light_segment_1_speed": "WLED RGB Light Segment 1 speed",
"number.wled_rgb_light_segment_1_intensity": "WLED RGB Light Segment 1 intensity",
},
),
],
)
async def test_keep_main_light_entity_names(
hass: HomeAssistant,
init_integration: MockConfigEntry,
keep_main_light: bool,
expected_entities: dict[str, str],
) -> None:
"""Test entity friendly names with and without the keep_main_light option."""
hass.config_entries.async_update_entry(
init_integration, options={CONF_KEEP_MAIN_LIGHT: keep_main_light}
)
await hass.config_entries.async_reload(init_integration.entry_id)
await hass.async_block_till_done()

actual_entities = {
state.entity_id: state.attributes.get("friendly_name")
for state in hass.states.async_all("number")
}
assert actual_entities == expected_entities
73 changes: 72 additions & 1 deletion tests/components/wled/test_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@
)

from homeassistant.components.select import ATTR_OPTION, DOMAIN as SELECT_DOMAIN
from homeassistant.components.wled.const import DOMAIN, SCAN_INTERVAL
from homeassistant.components.wled.const import (
CONF_KEEP_MAIN_LIGHT,
DOMAIN,
SCAN_INTERVAL,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_SELECT_OPTION,
Expand Down Expand Up @@ -269,3 +273,70 @@ async def test_select_load_new_options_after_update(

assert (state := hass.states.get(entity_id))
assert state.attributes["options"] == new_options


@pytest.mark.parametrize(
("device_fixture", "keep_main_light", "expected_entities"),
[
(
"rgb_single_segment",
False,
{
"select.wled_rgb_light_color_palette": "WLED RGB Light Color palette",
"select.wled_rgb_light_live_override": "WLED RGB Light Live override",
"select.wled_rgb_light_playlist": "WLED RGB Light Playlist",
"select.wled_rgb_light_preset": "WLED RGB Light Preset",
},
),
(
"rgb_single_segment",
True,
{
"select.wled_rgb_light_color_palette": "WLED RGB Light Segment 0 color palette",
"select.wled_rgb_light_live_override": "WLED RGB Light Live override",
"select.wled_rgb_light_playlist": "WLED RGB Light Playlist",
"select.wled_rgb_light_preset": "WLED RGB Light Preset",
},
),
(
"rgb",
False,
{
"select.wled_rgb_light_color_palette": "WLED RGB Light Color palette",
"select.wled_rgb_light_live_override": "WLED RGB Light Live override",
"select.wled_rgb_light_playlist": "WLED RGB Light Playlist",
"select.wled_rgb_light_preset": "WLED RGB Light Preset",
"select.wled_rgb_light_segment_1_color_palette": "WLED RGB Light Segment 1 color palette",
},
),
(
"rgb",
True,
{
"select.wled_rgb_light_color_palette": "WLED RGB Light Segment 0 color palette",
"select.wled_rgb_light_live_override": "WLED RGB Light Live override",
"select.wled_rgb_light_playlist": "WLED RGB Light Playlist",
"select.wled_rgb_light_preset": "WLED RGB Light Preset",
"select.wled_rgb_light_segment_1_color_palette": "WLED RGB Light Segment 1 color palette",
},
),
],
)
async def test_keep_main_light_entity_names(
hass: HomeAssistant,
init_integration: MockConfigEntry,
keep_main_light: bool,
expected_entities: dict[str, str],
) -> None:
"""Test entity friendly names with and without the keep_main_light option."""
hass.config_entries.async_update_entry(
init_integration, options={CONF_KEEP_MAIN_LIGHT: keep_main_light}
)
await hass.config_entries.async_reload(init_integration.entry_id)
await hass.async_block_till_done()

actual_entities = {
state.entity_id: state.attributes.get("friendly_name")
for state in hass.states.async_all("select")
}
assert actual_entities == expected_entities
79 changes: 78 additions & 1 deletion tests/components/wled/test_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@
from wled import Device as WLEDDevice, WLEDConnectionError, WLEDError

from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.wled.const import DOMAIN, SCAN_INTERVAL
from homeassistant.components.wled.const import (
CONF_KEEP_MAIN_LIGHT,
DOMAIN,
SCAN_INTERVAL,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
Expand Down Expand Up @@ -207,3 +211,76 @@ async def test_switch_dynamically_handle_segments(
segment1_reverse := hass.states.get("switch.wled_rgb_light_segment_1_reverse")
)
assert segment1_reverse.state == STATE_UNAVAILABLE


@pytest.mark.parametrize(
("device_fixture", "keep_main_light", "expected_entities"),
[
(
"rgb_single_segment",
False,
{
"switch.wled_rgb_light_nightlight": "WLED RGB Light Nightlight",
"switch.wled_rgb_light_freeze": "WLED RGB Light Freeze",
"switch.wled_rgb_light_reverse": "WLED RGB Light Reverse",
"switch.wled_rgb_light_sync_receive": "WLED RGB Light Sync receive",
"switch.wled_rgb_light_sync_send": "WLED RGB Light Sync send",
},
),
(
"rgb_single_segment",
True,
{
"switch.wled_rgb_light_nightlight": "WLED RGB Light Nightlight",
"switch.wled_rgb_light_freeze": "WLED RGB Light Segment 0 freeze",
"switch.wled_rgb_light_reverse": "WLED RGB Light Segment 0 reverse",
"switch.wled_rgb_light_sync_receive": "WLED RGB Light Sync receive",
"switch.wled_rgb_light_sync_send": "WLED RGB Light Sync send",
},
),
(
"rgb",
False,
{
"switch.wled_rgb_light_nightlight": "WLED RGB Light Nightlight",
"switch.wled_rgb_light_freeze": "WLED RGB Light Freeze",
"switch.wled_rgb_light_reverse": "WLED RGB Light Reverse",
"switch.wled_rgb_light_segment_1_freeze": "WLED RGB Light Segment 1 freeze",
"switch.wled_rgb_light_segment_1_reverse": "WLED RGB Light Segment 1 reverse",
"switch.wled_rgb_light_sync_receive": "WLED RGB Light Sync receive",
"switch.wled_rgb_light_sync_send": "WLED RGB Light Sync send",
},
),
(
"rgb",
True,
{
"switch.wled_rgb_light_nightlight": "WLED RGB Light Nightlight",
"switch.wled_rgb_light_freeze": "WLED RGB Light Segment 0 freeze",
"switch.wled_rgb_light_reverse": "WLED RGB Light Segment 0 reverse",
"switch.wled_rgb_light_segment_1_freeze": "WLED RGB Light Segment 1 freeze",
"switch.wled_rgb_light_segment_1_reverse": "WLED RGB Light Segment 1 reverse",
"switch.wled_rgb_light_sync_receive": "WLED RGB Light Sync receive",
"switch.wled_rgb_light_sync_send": "WLED RGB Light Sync send",
},
),
],
)
async def test_keep_main_light_entity_names(
hass: HomeAssistant,
init_integration: MockConfigEntry,
keep_main_light: bool,
expected_entities: dict[str, str],
) -> None:
"""Test entity friendly names with and without the keep_main_light option."""
hass.config_entries.async_update_entry(
init_integration, options={CONF_KEEP_MAIN_LIGHT: keep_main_light}
)
await hass.config_entries.async_reload(init_integration.entry_id)
await hass.async_block_till_done()

actual_entities = {
state.entity_id: state.attributes.get("friendly_name")
for state in hass.states.async_all("switch")
}
assert actual_entities == expected_entities
Loading