Compare commits
8 commits
dev
...
frenck-202
Author | SHA1 | Date | |
---|---|---|---|
|
734331b4fb | ||
|
3b981a15d1 | ||
|
abdf230649 | ||
|
81ab5489dd | ||
|
93f9c0f811 | ||
|
3eafc30617 | ||
|
e35aa20b33 | ||
|
aaa7e55e86 |
8 changed files with 116 additions and 87 deletions
|
@ -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.
|
||||||
|
|
|
@ -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})
|
||||||
|
|
||||||
|
|
|
@ -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],
|
||||||
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue