Compare commits

...
Sign in to create a new pull request.

8 commits

Author SHA1 Message Date
Franck Nijhof
734331b4fb
Use different fixture, less patching 2022-12-28 10:02:21 +01:00
Franck Nijhof
3b981a15d1
Correctly determine color modes 2022-12-28 09:56:16 +01:00
Franck Nijhof
abdf230649
Adjusts tests based on WLED testing results 2022-12-28 00:07:12 +01:00
Franck Nijhof
81ab5489dd
Not correct, but a start for having the inital color mode 2022-12-27 22:28:13 +01:00
Franck Nijhof
93f9c0f811
Handle manual white light capability 2022-12-27 22:12:02 +01:00
Franck Nijhof
3eafc30617
Update config flow tests in user step with cct 2022-12-27 22:04:21 +01:00
Franck Nijhof
e35aa20b33
Remove CCT check from user flow 2022-12-27 22:02:45 +01:00
Franck Nijhof
aaa7e55e86
Add WLED Segments Light Capb ilities support 2022-12-27 22:02:41 +01:00
8 changed files with 116 additions and 87 deletions

View file

@ -5,7 +5,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .const import DOMAIN, LOGGER from .const import DOMAIN
from .coordinator import WLEDDataUpdateCoordinator from .coordinator import WLEDDataUpdateCoordinator
PLATFORMS = ( PLATFORMS = (
@ -25,16 +25,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = WLEDDataUpdateCoordinator(hass, entry=entry) coordinator = WLEDDataUpdateCoordinator(hass, entry=entry)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
if coordinator.data.info.leds.cct:
LOGGER.error(
(
"WLED device '%s' has a CCT channel, which is not supported by "
"this integration"
),
entry.title,
)
return False
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
# Set up all platforms for this device/entry. # Set up all platforms for this device/entry.

View file

@ -41,8 +41,6 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
except WLEDConnectionError: except WLEDConnectionError:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
else: else:
if device.info.leds.cct:
return self.async_abort(reason="cct_unsupported")
await self.async_set_unique_id(device.info.mac_address) await self.async_set_unique_id(device.info.mac_address)
self._abort_if_unique_id_configured( self._abort_if_unique_id_configured(
updates={CONF_HOST: user_input[CONF_HOST]} updates={CONF_HOST: user_input[CONF_HOST]}
@ -79,9 +77,6 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
except WLEDConnectionError: except WLEDConnectionError:
return self.async_abort(reason="cannot_connect") return self.async_abort(reason="cannot_connect")
if self.discovered_device.info.leds.cct:
return self.async_abort(reason="cct_unsupported")
await self.async_set_unique_id(self.discovered_device.info.mac_address) await self.async_set_unique_id(self.discovered_device.info.mac_address)
self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.host}) self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.host})

View file

@ -2,6 +2,10 @@
from datetime import timedelta from datetime import timedelta
import logging import logging
from wled import LightCapability
from homeassistant.components.light import ColorMode
# Integration domain # Integration domain
DOMAIN = "wled" DOMAIN = "wled"
@ -23,3 +27,36 @@ ATTR_SOFTWARE_VERSION = "sw_version"
ATTR_SPEED = "speed" ATTR_SPEED = "speed"
ATTR_TARGET_BRIGHTNESS = "target_brightness" ATTR_TARGET_BRIGHTNESS = "target_brightness"
ATTR_UDP_PORT = "udp_port" ATTR_UDP_PORT = "udp_port"
LIGHT_CAPABILITIES_COLOR_MODE_MAPPING: dict[LightCapability, list[ColorMode]] = {
LightCapability.NONE: [ColorMode.ONOFF],
LightCapability.RGB_COLOR: [ColorMode.RGB],
LightCapability.WHITE_CHANNEL: [ColorMode.BRIGHTNESS],
LightCapability.RGB_COLOR | LightCapability.WHITE_CHANNEL: [ColorMode.RGB],
LightCapability.COLOR_TEMPERATURE: [ColorMode.COLOR_TEMP],
LightCapability.RGB_COLOR | LightCapability.COLOR_TEMPERATURE: [ColorMode.RGBWW],
LightCapability.WHITE_CHANNEL
| LightCapability.COLOR_TEMPERATURE: [ColorMode.COLOR_TEMP],
LightCapability.RGB_COLOR
| LightCapability.WHITE_CHANNEL
| LightCapability.COLOR_TEMPERATURE: [ColorMode.RGB, ColorMode.COLOR_TEMP],
LightCapability.MANUAL_WHITE: [ColorMode.BRIGHTNESS],
LightCapability.RGB_COLOR | LightCapability.MANUAL_WHITE: [ColorMode.RGBW],
LightCapability.WHITE_CHANNEL
| LightCapability.MANUAL_WHITE: [ColorMode.BRIGHTNESS],
LightCapability.RGB_COLOR
| LightCapability.WHITE_CHANNEL
| LightCapability.MANUAL_WHITE: [ColorMode.RGBW],
LightCapability.COLOR_TEMPERATURE
| LightCapability.MANUAL_WHITE: [ColorMode.COLOR_TEMP, ColorMode.WHITE],
LightCapability.RGB_COLOR
| LightCapability.COLOR_TEMPERATURE
| LightCapability.MANUAL_WHITE: [ColorMode.RGBW, ColorMode.COLOR_TEMP],
LightCapability.WHITE_CHANNEL
| LightCapability.COLOR_TEMPERATURE
| LightCapability.MANUAL_WHITE: [ColorMode.COLOR_TEMP, ColorMode.WHITE],
LightCapability.RGB_COLOR
| LightCapability.WHITE_CHANNEL
| LightCapability.COLOR_TEMPERATURE
| LightCapability.MANUAL_WHITE: [ColorMode.RGBW, ColorMode.COLOR_TEMP],
}

View file

@ -18,7 +18,13 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ATTR_COLOR_PRIMARY, ATTR_ON, ATTR_SEGMENT_ID, DOMAIN from .const import (
ATTR_COLOR_PRIMARY,
ATTR_ON,
ATTR_SEGMENT_ID,
DOMAIN,
LIGHT_CAPABILITIES_COLOR_MODE_MAPPING,
)
from .coordinator import WLEDDataUpdateCoordinator from .coordinator import WLEDDataUpdateCoordinator
from .helpers import wled_exception_handler from .helpers import wled_exception_handler
from .models import WLEDEntity from .models import WLEDEntity
@ -112,8 +118,6 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
) -> None: ) -> None:
"""Initialize WLED segment light.""" """Initialize WLED segment light."""
super().__init__(coordinator=coordinator) super().__init__(coordinator=coordinator)
self._rgbw = coordinator.data.info.leds.rgbw
self._wv = coordinator.data.info.leds.wv
self._segment = segment self._segment = segment
# Segment 0 uses a simpler name, which is more natural for when using # Segment 0 uses a simpler name, which is more natural for when using
@ -125,11 +129,26 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
f"{self.coordinator.data.info.mac_address}_{self._segment}" f"{self.coordinator.data.info.mac_address}_{self._segment}"
) )
self._attr_color_mode = ColorMode.RGB # WLED >= v0.13.1, light capabilities per segment
self._attr_supported_color_modes = {ColorMode.RGB} if (
if self._rgbw and self._wv: coordinator.data.info.leds.segment_light_capabilities is not None
self._attr_color_mode = ColorMode.RGBW and (
self._attr_supported_color_modes = {ColorMode.RGBW} color_modes := LIGHT_CAPABILITIES_COLOR_MODE_MAPPING.get(
coordinator.data.info.leds.segment_light_capabilities[segment]
)
)
is not None
):
self._attr_color_mode = color_modes[0]
self._attr_supported_color_modes = set(color_modes)
# WLED < v0.13.1 or unknown color mode combination
else:
self._attr_color_mode = ColorMode.RGB
self._attr_supported_color_modes = {ColorMode.RGB}
if coordinator.data.info.leds.rgbw and coordinator.data.info.leds.wv:
self._attr_color_mode = ColorMode.RGBW
self._attr_supported_color_modes = {ColorMode.RGBW}
@property @property
def available(self) -> bool: def available(self) -> bool:

View file

@ -18,8 +18,7 @@
}, },
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
"cct_unsupported": "This WLED device uses CCT channels, which is not supported by this integration"
} }
}, },
"options": { "options": {

View file

@ -169,23 +169,6 @@ async def test_user_device_exists_abort(
assert result.get("reason") == "already_configured" assert result.get("reason") == "already_configured"
async def test_user_with_cct_channel_abort(
hass: HomeAssistant,
mock_wled_config_flow: MagicMock,
) -> None:
"""Test we abort user flow if WLED device uses a CCT channel."""
mock_wled_config_flow.update.return_value.info.leds.cct = True
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
data={CONF_HOST: "192.168.1.123"},
)
assert result.get("type") == FlowResultType.ABORT
assert result.get("reason") == "cct_unsupported"
async def test_zeroconf_without_mac_device_exists_abort( async def test_zeroconf_without_mac_device_exists_abort(
hass: HomeAssistant, hass: HomeAssistant,
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
@ -236,31 +219,6 @@ async def test_zeroconf_with_mac_device_exists_abort(
assert result.get("reason") == "already_configured" assert result.get("reason") == "already_configured"
async def test_zeroconf_with_cct_channel_abort(
hass: HomeAssistant,
mock_wled_config_flow: MagicMock,
) -> None:
"""Test we abort zeroconf flow if WLED device uses a CCT channel."""
mock_wled_config_flow.update.return_value.info.leds.cct = True
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo(
host="192.168.1.123",
addresses=["192.168.1.123"],
hostname="example.local.",
name="mock_name",
port=None,
properties={CONF_MAC: "aabbccddeeff"},
type="mock_type",
),
)
assert result.get("type") == FlowResultType.ABORT
assert result.get("reason") == "cct_unsupported"
async def test_options_flow( async def test_options_flow(
hass: HomeAssistant, mock_config_entry: MockConfigEntry hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> None: ) -> None:

View file

@ -68,21 +68,3 @@ async def test_setting_unique_id(
"""Test we set unique ID if not set yet.""" """Test we set unique ID if not set yet."""
assert hass.data[DOMAIN] assert hass.data[DOMAIN]
assert init_integration.unique_id == "aabbccddeeff" assert init_integration.unique_id == "aabbccddeeff"
async def test_error_config_entry_with_cct_channel(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_wled: AsyncMock,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test the WLED fails entry setup with a CCT channel."""
mock_wled.update.return_value.info.leds.cct = True
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
# Ensure config entry is errored and are connected and disconnected
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
assert "has a CCT channel, which is not supported" in caplog.text

View file

@ -3,16 +3,20 @@ import json
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
from wled import Device as WLEDDevice, WLEDConnectionError, WLEDError from pytest_unordered import unordered
from wled import Device as WLEDDevice, LightCapability, WLEDConnectionError, WLEDError
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_BRIGHTNESS,
ATTR_COLOR_MODE,
ATTR_EFFECT, ATTR_EFFECT,
ATTR_HS_COLOR, ATTR_HS_COLOR,
ATTR_RGB_COLOR, ATTR_RGB_COLOR,
ATTR_RGBW_COLOR, ATTR_RGBW_COLOR,
ATTR_SUPPORTED_COLOR_MODES,
ATTR_TRANSITION, ATTR_TRANSITION,
DOMAIN as LIGHT_DOMAIN, DOMAIN as LIGHT_DOMAIN,
ColorMode,
) )
from homeassistant.components.wled.const import CONF_KEEP_MASTER_LIGHT, SCAN_INTERVAL from homeassistant.components.wled.const import CONF_KEEP_MASTER_LIGHT, SCAN_INTERVAL
from homeassistant.const import ( from homeassistant.const import (
@ -395,3 +399,48 @@ async def test_single_segment_with_keep_master_light(
state = hass.states.get("light.wled_rgb_light_master") state = hass.states.get("light.wled_rgb_light_master")
assert state assert state
assert state.state == STATE_ON assert state.state == STATE_ON
@pytest.mark.parametrize("mock_wled", ["wled/rgbw.json"], indirect=True)
@pytest.mark.parametrize(
"capabilities,color_modes",
[
(0, [ColorMode.ONOFF]),
(1, [ColorMode.RGB]),
(2, [ColorMode.BRIGHTNESS]),
(3, [ColorMode.RGB]),
(4, [ColorMode.COLOR_TEMP]),
(5, [ColorMode.RGBWW]),
(6, [ColorMode.COLOR_TEMP]),
(7, [ColorMode.RGB, ColorMode.COLOR_TEMP]),
(8, [ColorMode.BRIGHTNESS]),
(9, [ColorMode.RGBW]),
(10, [ColorMode.BRIGHTNESS]),
(11, [ColorMode.RGBW]),
(12, [ColorMode.COLOR_TEMP, ColorMode.WHITE]),
(13, [ColorMode.RGBW, ColorMode.COLOR_TEMP]),
(14, [ColorMode.COLOR_TEMP, ColorMode.WHITE]),
(15, [ColorMode.RGBW, ColorMode.COLOR_TEMP]),
],
)
async def test_segment_light_capabilities(
hass: HomeAssistant,
mock_wled: MagicMock,
mock_config_entry: MockConfigEntry,
capabilities: LightCapability,
color_modes: list[ColorMode],
) -> None:
"""Test segment light capabilities of WLED lights."""
update: WLEDDevice = mock_wled.update.return_value
update.info.leds.segment_light_capabilities = [LightCapability(capabilities)]
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("light.wled_rgbw_light")
assert state
assert state.state == STATE_ON
assert state.attributes.get(ATTR_COLOR_MODE) == color_modes[0]
assert state.attributes.get(ATTR_SUPPORTED_COLOR_MODES) == unordered(color_modes)