mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Add CCT support to WLED (#122488)
This commit is contained in:
parent
d7c713d18d
commit
34b32ced25
@ -19,6 +19,7 @@ CONF_KEEP_MAIN_LIGHT = "keep_master_light"
|
||||
DEFAULT_KEEP_MAIN_LIGHT = False
|
||||
|
||||
# Attributes
|
||||
ATTR_CCT = "cct"
|
||||
ATTR_COLOR_PRIMARY = "color_primary"
|
||||
ATTR_DURATION = "duration"
|
||||
ATTR_FADE = "fade"
|
||||
@ -30,6 +31,10 @@ ATTR_SPEED = "speed"
|
||||
ATTR_TARGET_BRIGHTNESS = "target_brightness"
|
||||
ATTR_UDP_PORT = "udp_port"
|
||||
|
||||
# Static values
|
||||
COLOR_TEMP_K_MIN = 2000
|
||||
COLOR_TEMP_K_MAX = 6535
|
||||
|
||||
|
||||
LIGHT_CAPABILITIES_COLOR_MODE_MAPPING: dict[LightCapability, list[ColorMode]] = {
|
||||
LightCapability.NONE: [
|
||||
@ -56,8 +61,8 @@ LIGHT_CAPABILITIES_COLOR_MODE_MAPPING: dict[LightCapability, list[ColorMode]] =
|
||||
LightCapability.RGB_COLOR
|
||||
| LightCapability.WHITE_CHANNEL
|
||||
| LightCapability.COLOR_TEMPERATURE: [
|
||||
ColorMode.RGB,
|
||||
ColorMode.COLOR_TEMP,
|
||||
ColorMode.RGBW,
|
||||
],
|
||||
LightCapability.MANUAL_WHITE: [
|
||||
ColorMode.BRIGHTNESS,
|
||||
|
@ -35,3 +35,13 @@ def wled_exception_handler[_WLEDEntityT: WLEDEntity, **_P](
|
||||
raise HomeAssistantError("Invalid response from WLED API") from error
|
||||
|
||||
return handler
|
||||
|
||||
|
||||
def kelvin_to_255(k: int, min_k: int, max_k: int) -> int:
|
||||
"""Map color temperature in K from minK-maxK to 0-255."""
|
||||
return int((k - min_k) / (max_k - min_k) * 255)
|
||||
|
||||
|
||||
def kelvin_to_255_reverse(v: int, min_k: int, max_k: int) -> int:
|
||||
"""Map color temperature from 0-255 to minK-maxK K."""
|
||||
return int(v / 255 * (max_k - min_k) + min_k)
|
||||
|
@ -7,6 +7,7 @@ from typing import Any, cast
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
ATTR_EFFECT,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
@ -20,14 +21,17 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import WLEDConfigEntry
|
||||
from .const import (
|
||||
ATTR_CCT,
|
||||
ATTR_COLOR_PRIMARY,
|
||||
ATTR_ON,
|
||||
ATTR_SEGMENT_ID,
|
||||
COLOR_TEMP_K_MAX,
|
||||
COLOR_TEMP_K_MIN,
|
||||
LIGHT_CAPABILITIES_COLOR_MODE_MAPPING,
|
||||
)
|
||||
from .coordinator import WLEDDataUpdateCoordinator
|
||||
from .entity import WLEDEntity
|
||||
from .helpers import wled_exception_handler
|
||||
from .helpers import kelvin_to_255, kelvin_to_255_reverse, wled_exception_handler
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@ -109,6 +113,8 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
|
||||
|
||||
_attr_supported_features = LightEntityFeature.EFFECT | LightEntityFeature.TRANSITION
|
||||
_attr_translation_key = "segment"
|
||||
_attr_min_color_temp_kelvin = COLOR_TEMP_K_MIN
|
||||
_attr_max_color_temp_kelvin = COLOR_TEMP_K_MAX
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -166,6 +172,12 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
|
||||
return None
|
||||
return cast(tuple[int, int, int, int], color.primary)
|
||||
|
||||
@property
|
||||
def color_temp_kelvin(self) -> int | None:
|
||||
"""Return the CT color value in K."""
|
||||
cct = self.coordinator.data.state.segments[self._segment].cct
|
||||
return kelvin_to_255_reverse(cct, COLOR_TEMP_K_MIN, COLOR_TEMP_K_MAX)
|
||||
|
||||
@property
|
||||
def effect(self) -> str | None:
|
||||
"""Return the current effect of the light."""
|
||||
@ -235,6 +247,11 @@ class WLEDSegmentLight(WLEDEntity, LightEntity):
|
||||
if ATTR_RGBW_COLOR in kwargs:
|
||||
data[ATTR_COLOR_PRIMARY] = kwargs[ATTR_RGBW_COLOR]
|
||||
|
||||
if ATTR_COLOR_TEMP_KELVIN in kwargs:
|
||||
data[ATTR_CCT] = kelvin_to_255(
|
||||
kwargs[ATTR_COLOR_TEMP_KELVIN], COLOR_TEMP_K_MIN, COLOR_TEMP_K_MAX
|
||||
)
|
||||
|
||||
if ATTR_TRANSITION in kwargs:
|
||||
# WLED uses 100ms per unit, so 10 = 1 second.
|
||||
data[ATTR_TRANSITION] = round(kwargs[ATTR_TRANSITION] * 10)
|
||||
|
383
tests/components/wled/fixtures/cct.json
Normal file
383
tests/components/wled/fixtures/cct.json
Normal file
@ -0,0 +1,383 @@
|
||||
{
|
||||
"state": {
|
||||
"on": true,
|
||||
"bri": 255,
|
||||
"transition": 7,
|
||||
"ps": 2,
|
||||
"pl": -1,
|
||||
"nl": {
|
||||
"on": false,
|
||||
"dur": 60,
|
||||
"mode": 1,
|
||||
"tbri": 0,
|
||||
"rem": -1
|
||||
},
|
||||
"udpn": {
|
||||
"send": false,
|
||||
"recv": true,
|
||||
"sgrp": 1,
|
||||
"rgrp": 1
|
||||
},
|
||||
"lor": 0,
|
||||
"mainseg": 0,
|
||||
"seg": [
|
||||
{
|
||||
"id": 0,
|
||||
"start": 0,
|
||||
"stop": 178,
|
||||
"len": 178,
|
||||
"grp": 1,
|
||||
"spc": 0,
|
||||
"of": 0,
|
||||
"on": true,
|
||||
"frz": false,
|
||||
"bri": 255,
|
||||
"cct": 53,
|
||||
"set": 0,
|
||||
"col": [
|
||||
[0, 0, 0, 255],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 0]
|
||||
],
|
||||
"fx": 0,
|
||||
"sx": 128,
|
||||
"ix": 128,
|
||||
"pal": 0,
|
||||
"c1": 128,
|
||||
"c2": 128,
|
||||
"c3": 16,
|
||||
"sel": true,
|
||||
"rev": false,
|
||||
"mi": false,
|
||||
"o1": false,
|
||||
"o2": false,
|
||||
"o3": false,
|
||||
"si": 0,
|
||||
"m12": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"info": {
|
||||
"ver": "0.15.0-b3",
|
||||
"vid": 2405180,
|
||||
"cn": "Kōsen",
|
||||
"release": "ESP32",
|
||||
"leds": {
|
||||
"count": 178,
|
||||
"pwr": 0,
|
||||
"fps": 0,
|
||||
"maxpwr": 0,
|
||||
"maxseg": 32,
|
||||
"bootps": 1,
|
||||
"seglc": [7],
|
||||
"lc": 7,
|
||||
"rgbw": true,
|
||||
"wv": 2,
|
||||
"cct": 4
|
||||
},
|
||||
"str": false,
|
||||
"name": "WLED CCT light",
|
||||
"udpport": 21324,
|
||||
"simplifiedui": false,
|
||||
"live": false,
|
||||
"liveseg": -1,
|
||||
"lm": "",
|
||||
"lip": "",
|
||||
"ws": 1,
|
||||
"fxcount": 187,
|
||||
"palcount": 75,
|
||||
"cpalcount": 4,
|
||||
"maps": [
|
||||
{
|
||||
"id": 0
|
||||
}
|
||||
],
|
||||
"wifi": {
|
||||
"bssid": "AA:AA:AA:AA:AA:BB",
|
||||
"rssi": -44,
|
||||
"signal": 100,
|
||||
"channel": 11
|
||||
},
|
||||
"fs": {
|
||||
"u": 20,
|
||||
"t": 983,
|
||||
"pmt": 1721752272
|
||||
},
|
||||
"ndc": 1,
|
||||
"arch": "esp32",
|
||||
"core": "v3.3.6-16-gcc5440f6a2",
|
||||
"clock": 240,
|
||||
"flash": 4,
|
||||
"lwip": 0,
|
||||
"freeheap": 164804,
|
||||
"uptime": 79769,
|
||||
"time": "2024-7-24, 14:34:00",
|
||||
"opt": 79,
|
||||
"brand": "WLED",
|
||||
"product": "FOSS",
|
||||
"mac": "aabbccddeeff",
|
||||
"ip": "127.0.0.1"
|
||||
},
|
||||
"effects": [
|
||||
"Solid",
|
||||
"Blink",
|
||||
"Breathe",
|
||||
"Wipe",
|
||||
"Wipe Random",
|
||||
"Random Colors",
|
||||
"Sweep",
|
||||
"Dynamic",
|
||||
"Colorloop",
|
||||
"Rainbow",
|
||||
"Scan",
|
||||
"Scan Dual",
|
||||
"Fade",
|
||||
"Theater",
|
||||
"Theater Rainbow",
|
||||
"Running",
|
||||
"Saw",
|
||||
"Twinkle",
|
||||
"Dissolve",
|
||||
"Dissolve Rnd",
|
||||
"Sparkle",
|
||||
"Sparkle Dark",
|
||||
"Sparkle+",
|
||||
"Strobe",
|
||||
"Strobe Rainbow",
|
||||
"Strobe Mega",
|
||||
"Blink Rainbow",
|
||||
"Android",
|
||||
"Chase",
|
||||
"Chase Random",
|
||||
"Chase Rainbow",
|
||||
"Chase Flash",
|
||||
"Chase Flash Rnd",
|
||||
"Rainbow Runner",
|
||||
"Colorful",
|
||||
"Traffic Light",
|
||||
"Sweep Random",
|
||||
"Chase 2",
|
||||
"Aurora",
|
||||
"Stream",
|
||||
"Scanner",
|
||||
"Lighthouse",
|
||||
"Fireworks",
|
||||
"Rain",
|
||||
"Tetrix",
|
||||
"Fire Flicker",
|
||||
"Gradient",
|
||||
"Loading",
|
||||
"Rolling Balls",
|
||||
"Fairy",
|
||||
"Two Dots",
|
||||
"Fairytwinkle",
|
||||
"Running Dual",
|
||||
"RSVD",
|
||||
"Chase 3",
|
||||
"Tri Wipe",
|
||||
"Tri Fade",
|
||||
"Lightning",
|
||||
"ICU",
|
||||
"Multi Comet",
|
||||
"Scanner Dual",
|
||||
"Stream 2",
|
||||
"Oscillate",
|
||||
"Pride 2015",
|
||||
"Juggle",
|
||||
"Palette",
|
||||
"Fire 2012",
|
||||
"Colorwaves",
|
||||
"Bpm",
|
||||
"Fill Noise",
|
||||
"Noise 1",
|
||||
"Noise 2",
|
||||
"Noise 3",
|
||||
"Noise 4",
|
||||
"Colortwinkles",
|
||||
"Lake",
|
||||
"Meteor",
|
||||
"Meteor Smooth",
|
||||
"Railway",
|
||||
"Ripple",
|
||||
"Twinklefox",
|
||||
"Twinklecat",
|
||||
"Halloween Eyes",
|
||||
"Solid Pattern",
|
||||
"Solid Pattern Tri",
|
||||
"Spots",
|
||||
"Spots Fade",
|
||||
"Glitter",
|
||||
"Candle",
|
||||
"Fireworks Starburst",
|
||||
"Fireworks 1D",
|
||||
"Bouncing Balls",
|
||||
"Sinelon",
|
||||
"Sinelon Dual",
|
||||
"Sinelon Rainbow",
|
||||
"Popcorn",
|
||||
"Drip",
|
||||
"Plasma",
|
||||
"Percent",
|
||||
"Ripple Rainbow",
|
||||
"Heartbeat",
|
||||
"Pacifica",
|
||||
"Candle Multi",
|
||||
"Solid Glitter",
|
||||
"Sunrise",
|
||||
"Phased",
|
||||
"Twinkleup",
|
||||
"Noise Pal",
|
||||
"Sine",
|
||||
"Phased Noise",
|
||||
"Flow",
|
||||
"Chunchun",
|
||||
"Dancing Shadows",
|
||||
"Washing Machine",
|
||||
"Rotozoomer",
|
||||
"Blends",
|
||||
"TV Simulator",
|
||||
"Dynamic Smooth",
|
||||
"Spaceships",
|
||||
"Crazy Bees",
|
||||
"Ghost Rider",
|
||||
"Blobs",
|
||||
"Scrolling Text",
|
||||
"Drift Rose",
|
||||
"Distortion Waves",
|
||||
"Soap",
|
||||
"Octopus",
|
||||
"Waving Cell",
|
||||
"Pixels",
|
||||
"Pixelwave",
|
||||
"Juggles",
|
||||
"Matripix",
|
||||
"Gravimeter",
|
||||
"Plasmoid",
|
||||
"Puddles",
|
||||
"Midnoise",
|
||||
"Noisemeter",
|
||||
"Freqwave",
|
||||
"Freqmatrix",
|
||||
"GEQ",
|
||||
"Waterfall",
|
||||
"Freqpixels",
|
||||
"RSVD",
|
||||
"Noisefire",
|
||||
"Puddlepeak",
|
||||
"Noisemove",
|
||||
"Noise2D",
|
||||
"Perlin Move",
|
||||
"Ripple Peak",
|
||||
"Firenoise",
|
||||
"Squared Swirl",
|
||||
"RSVD",
|
||||
"DNA",
|
||||
"Matrix",
|
||||
"Metaballs",
|
||||
"Freqmap",
|
||||
"Gravcenter",
|
||||
"Gravcentric",
|
||||
"Gravfreq",
|
||||
"DJ Light",
|
||||
"Funky Plank",
|
||||
"RSVD",
|
||||
"Pulser",
|
||||
"Blurz",
|
||||
"Drift",
|
||||
"Waverly",
|
||||
"Sun Radiation",
|
||||
"Colored Bursts",
|
||||
"Julia",
|
||||
"RSVD",
|
||||
"RSVD",
|
||||
"RSVD",
|
||||
"Game Of Life",
|
||||
"Tartan",
|
||||
"Polar Lights",
|
||||
"Swirl",
|
||||
"Lissajous",
|
||||
"Frizzles",
|
||||
"Plasma Ball",
|
||||
"Flow Stripe",
|
||||
"Hiphotic",
|
||||
"Sindots",
|
||||
"DNA Spiral",
|
||||
"Black Hole",
|
||||
"Wavesins",
|
||||
"Rocktaves",
|
||||
"Akemi"
|
||||
],
|
||||
"palettes": [
|
||||
"Default",
|
||||
"* Random Cycle",
|
||||
"* Color 1",
|
||||
"* Colors 1&2",
|
||||
"* Color Gradient",
|
||||
"* Colors Only",
|
||||
"Party",
|
||||
"Cloud",
|
||||
"Lava",
|
||||
"Ocean",
|
||||
"Forest",
|
||||
"Rainbow",
|
||||
"Rainbow Bands",
|
||||
"Sunset",
|
||||
"Rivendell",
|
||||
"Breeze",
|
||||
"Red & Blue",
|
||||
"Yellowout",
|
||||
"Analogous",
|
||||
"Splash",
|
||||
"Pastel",
|
||||
"Sunset 2",
|
||||
"Beach",
|
||||
"Vintage",
|
||||
"Departure",
|
||||
"Landscape",
|
||||
"Beech",
|
||||
"Sherbet",
|
||||
"Hult",
|
||||
"Hult 64",
|
||||
"Drywet",
|
||||
"Jul",
|
||||
"Grintage",
|
||||
"Rewhi",
|
||||
"Tertiary",
|
||||
"Fire",
|
||||
"Icefire",
|
||||
"Cyane",
|
||||
"Light Pink",
|
||||
"Autumn",
|
||||
"Magenta",
|
||||
"Magred",
|
||||
"Yelmag",
|
||||
"Yelblu",
|
||||
"Orange & Teal",
|
||||
"Tiamat",
|
||||
"April Night",
|
||||
"Orangery",
|
||||
"C9",
|
||||
"Sakura",
|
||||
"Aurora",
|
||||
"Atlantica",
|
||||
"C9 2",
|
||||
"C9 New",
|
||||
"Temperature",
|
||||
"Aurora 2",
|
||||
"Retro Clown",
|
||||
"Candy",
|
||||
"Toxy Reaf",
|
||||
"Fairy Reaf",
|
||||
"Semi Blue",
|
||||
"Pink Candy",
|
||||
"Red Reaf",
|
||||
"Aqua Flash",
|
||||
"Yelblu Hot",
|
||||
"Lite Light",
|
||||
"Red Flash",
|
||||
"Blink Red",
|
||||
"Red Shift",
|
||||
"Red Tide",
|
||||
"Candy2"
|
||||
]
|
||||
}
|
@ -9,8 +9,11 @@ from wled import Device as WLEDDevice, WLEDConnectionError, WLEDError
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_MODE,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
ATTR_EFFECT,
|
||||
ATTR_HS_COLOR,
|
||||
ATTR_MAX_COLOR_TEMP_KELVIN,
|
||||
ATTR_MIN_COLOR_TEMP_KELVIN,
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
ATTR_SUPPORTED_COLOR_MODES,
|
||||
@ -374,3 +377,34 @@ async def test_single_segment_with_keep_main_light(
|
||||
|
||||
assert (state := hass.states.get("light.wled_rgb_light_main"))
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_fixture", ["cct"])
|
||||
async def test_cct_light(hass: HomeAssistant, mock_wled: MagicMock) -> None:
|
||||
"""Test CCT support for WLED."""
|
||||
assert (state := hass.states.get("light.wled_cct_light"))
|
||||
assert state.state == STATE_ON
|
||||
assert state.attributes.get(ATTR_SUPPORTED_COLOR_MODES) == [
|
||||
ColorMode.COLOR_TEMP,
|
||||
ColorMode.RGBW,
|
||||
]
|
||||
assert state.attributes.get(ATTR_COLOR_MODE) == ColorMode.COLOR_TEMP
|
||||
assert state.attributes.get(ATTR_MIN_COLOR_TEMP_KELVIN) == 2000
|
||||
assert state.attributes.get(ATTR_MAX_COLOR_TEMP_KELVIN) == 6535
|
||||
assert state.attributes.get(ATTR_COLOR_TEMP_KELVIN) == 2942
|
||||
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
{
|
||||
ATTR_ENTITY_ID: "light.wled_cct_light",
|
||||
ATTR_COLOR_TEMP_KELVIN: 4321,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert mock_wled.segment.call_count == 1
|
||||
mock_wled.segment.assert_called_with(
|
||||
cct=130,
|
||||
on=True,
|
||||
segment_id=0,
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user