Allow keeping master light in WLED ()

pull/51779/head
Franck Nijhof 2021-06-12 13:33:23 +02:00 committed by GitHub
parent 779ef3c8e1
commit cfce71d7df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 155 additions and 12 deletions
homeassistant/components/wled

View File

@ -31,6 +31,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Set up all platforms for this device/entry.
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
# Reload entry when its updated.
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
return True
@ -48,3 +51,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
del hass.data[DOMAIN][entry.entry_id]
return unload_ok
async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Reload the config entry when it changed."""
await hass.config_entries.async_reload(entry.entry_id)

View File

@ -6,13 +6,19 @@ from typing import Any
import voluptuous as vol
from wled import WLED, WLEDConnectionError
from homeassistant.config_entries import SOURCE_ZEROCONF, ConfigFlow
from homeassistant.config_entries import (
SOURCE_ZEROCONF,
ConfigEntry,
ConfigFlow,
OptionsFlow,
)
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import DiscoveryInfoType
from .const import DOMAIN
from .const import CONF_KEEP_MASTER_LIGHT, DEFAULT_KEEP_MASTER_LIGHT, DOMAIN
class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
@ -20,6 +26,12 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
VERSION = 1
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> WLEDOptionsFlowHandler:
"""Get the options flow for this handler."""
return WLEDOptionsFlowHandler(config_entry)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
@ -115,3 +127,32 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
description_placeholders={"name": name},
errors=errors or {},
)
class WLEDOptionsFlowHandler(OptionsFlow):
"""Handle WLED options."""
def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize WLED options flow."""
self.config_entry = config_entry
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage WLED options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Optional(
CONF_KEEP_MASTER_LIGHT,
default=self.config_entry.options.get(
CONF_KEEP_MASTER_LIGHT, DEFAULT_KEEP_MASTER_LIGHT
),
): bool,
}
),
)

View File

@ -8,6 +8,10 @@ DOMAIN = "wled"
LOGGER = logging.getLogger(__package__)
SCAN_INTERVAL = timedelta(seconds=10)
# Options
CONF_KEEP_MASTER_LIGHT = "keep_master_light"
DEFAULT_KEEP_MASTER_LIGHT = False
# Attributes
ATTR_COLOR_PRIMARY = "color_primary"
ATTR_DURATION = "duration"

View File

@ -37,6 +37,8 @@ from .const import (
ATTR_REVERSE,
ATTR_SEGMENT_ID,
ATTR_SPEED,
CONF_KEEP_MASTER_LIGHT,
DEFAULT_KEEP_MASTER_LIGHT,
DOMAIN,
SERVICE_EFFECT,
SERVICE_PRESET,
@ -84,8 +86,19 @@ async def async_setup_entry(
"async_preset",
)
keep_master_light = entry.options.get(
CONF_KEEP_MASTER_LIGHT, DEFAULT_KEEP_MASTER_LIGHT
)
if keep_master_light:
async_add_entities([WLEDMasterLight(coordinator=coordinator)])
update_segments = partial(
async_update_segments, entry, coordinator, {}, async_add_entities
async_update_segments,
entry,
coordinator,
keep_master_light,
{},
async_add_entities,
)
coordinator.async_add_listener(update_segments)
@ -169,9 +182,15 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
_attr_supported_features = SUPPORT_EFFECT | SUPPORT_TRANSITION
_attr_icon = "mdi:led-strip-variant"
def __init__(self, coordinator: WLEDDataUpdateCoordinator, segment: int) -> None:
def __init__(
self,
coordinator: WLEDDataUpdateCoordinator,
segment: int,
keep_master_light: bool,
) -> None:
"""Initialize WLED segment light."""
super().__init__(coordinator=coordinator)
self._keep_master_light = keep_master_light
self._rgbw = coordinator.data.info.leds.rgbw
self._wv = coordinator.data.info.leds.wv
self._segment = segment
@ -247,7 +266,7 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
# If this is the one and only segment, calculate brightness based
# on the master and segment brightness
if len(state.segments) == 1:
if not self._keep_master_light and len(state.segments) == 1:
return int(
(state.segments[self._segment].brightness * state.brightness) / 255
)
@ -280,7 +299,10 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10)
# If there is a single segment, control via the master
if len(self.coordinator.data.state.segments) == 1:
if (
not self._keep_master_light
and len(self.coordinator.data.state.segments) == 1
):
await self.coordinator.wled.master(**data) # type: ignore[arg-type]
return
@ -313,7 +335,10 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
# 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:
if (
not self._keep_master_light
and len(self.coordinator.data.state.segments) == 1
):
master_data = {ATTR_ON: True}
if ATTR_BRIGHTNESS in data:
master_data[ATTR_BRIGHTNESS] = data[ATTR_BRIGHTNESS]
@ -373,6 +398,7 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
def async_update_segments(
entry: ConfigEntry,
coordinator: WLEDDataUpdateCoordinator,
keep_master_light: bool,
current: dict[int, WLEDSegmentLight | WLEDMasterLight],
async_add_entities,
) -> None:
@ -383,14 +409,17 @@ def async_update_segments(
# Discard master (if present)
current_ids.discard(-1)
# Process new segments, add them to Home Assistant
new_entities = []
# Process new segments, add them to Home Assistant
for segment_id in segment_ids - current_ids:
current[segment_id] = WLEDSegmentLight(coordinator, segment_id)
current[segment_id] = WLEDSegmentLight(
coordinator, segment_id, keep_master_light
)
new_entities.append(current[segment_id])
# More than 1 segment now? Add master controls
if len(current_ids) < 2 and len(segment_ids) > 1:
if not keep_master_light and (len(current_ids) < 2 and len(segment_ids) > 1):
current[-1] = WLEDMasterLight(coordinator)
new_entities.append(current[-1])
@ -404,7 +433,7 @@ def async_update_segments(
)
# Remove master if there is only 1 segment left
if len(current_ids) > 1 and len(segment_ids) < 2:
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)
)

View File

@ -20,5 +20,14 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
}
},
"options": {
"step": {
"init": {
"data": {
"keep_master_light": "Keep master light, even with 1 LED segment."
}
}
}
}
}

View File

@ -20,5 +20,14 @@
"title": "Discovered WLED device"
}
}
},
"options": {
"step": {
"init": {
"data": {
"keep_master_light": "Keep master light, even with 1 LED segment."
}
}
}
}
}

View File

@ -3,7 +3,7 @@ from unittest.mock import MagicMock
from wled import WLEDConnectionError
from homeassistant.components.wled.const import DOMAIN
from homeassistant.components.wled.const import CONF_KEEP_MASTER_LIGHT, DOMAIN
from homeassistant.config_entries import SOURCE_USER, SOURCE_ZEROCONF
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
from homeassistant.core import HomeAssistant
@ -177,3 +177,26 @@ async def test_zeroconf_with_mac_device_exists_abort(
assert result.get("type") == RESULT_TYPE_ABORT
assert result.get("reason") == "already_configured"
async def test_options_flow(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None:
"""Test options config flow."""
mock_config_entry.add_to_hass(hass)
result = await hass.config_entries.options.async_init(mock_config_entry.entry_id)
assert result.get("type") == RESULT_TYPE_FORM
assert result.get("step_id") == "init"
assert "flow_id" in result
result2 = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={CONF_KEEP_MASTER_LIGHT: True},
)
assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY
assert result2.get("data") == {
CONF_KEEP_MASTER_LIGHT: True,
}

View File

@ -21,6 +21,7 @@ from homeassistant.components.wled.const import (
ATTR_PRESET,
ATTR_REVERSE,
ATTR_SPEED,
CONF_KEEP_MASTER_LIGHT,
DOMAIN,
SCAN_INTERVAL,
SERVICE_EFFECT,
@ -588,3 +589,22 @@ async def test_preset_service_error(
assert "Invalid response from API" in caplog.text
assert mock_wled.preset.call_count == 1
mock_wled.preset.assert_called_with(preset=1)
@pytest.mark.parametrize("mock_wled", ["wled/rgb_single_segment.json"], indirect=True)
async def test_single_segment_with_keep_master_light(
hass: HomeAssistant,
init_integration: MockConfigEntry,
mock_wled: MagicMock,
) -> None:
"""Test the behavior of the integration with a single segment."""
assert not hass.states.get("light.wled_rgb_light_master")
hass.config_entries.async_update_entry(
init_integration, options={CONF_KEEP_MASTER_LIGHT: True}
)
await hass.async_block_till_done()
state = hass.states.get("light.wled_rgb_light_master")
assert state
assert state.state == STATE_ON