Add relative color temperature dimming

This commit is contained in:
2026-02-15 21:20:49 +01:00
parent a9fb58c87a
commit 9825e748cf
47 changed files with 276 additions and 90 deletions

View File

@@ -44,6 +44,7 @@ from .const import (
CONF_LIGHT_COLOR_TEMPERATURE_ADDRESS,
CONF_LIGHT_COLOR_TEMPERATURE_STATE_ADDRESS,
CONF_LIGHT_COLOR_TEMPERATURE_MODE,
CONF_LIGHT_RELATIVE_COLOR_TEMPERATURE_ADDRESS,
CONF_LIGHT_RELATIVE_DIMMING_ADDRESS,
CONF_LIGHT_MIN_KELVIN,
CONF_LIGHT_MAX_KELVIN,
@@ -145,6 +146,7 @@ class LightPort:
color_temperature_mode: str
min_kelvin: int | None
max_kelvin: int | None
relative_color_temperature_address: str | None
relative_dimming_address: str | None
red_address: str | None
red_state_address: str | None
@@ -183,6 +185,7 @@ class BridgeManager:
self._address_options: dict[str, AddressOptions] = {}
self._light_components: dict[str, dict[str, int]] = {}
self._light_dimming_tasks: dict[str, asyncio.Task] = {}
self._light_ct_tasks: dict[str, asyncio.Task] = {}
async def async_setup(self) -> None:
if not self.hass.services.has_service(KNX_DOMAIN, "send"):
@@ -204,6 +207,9 @@ class BridgeManager:
for task in self._light_dimming_tasks.values():
task.cancel()
self._light_dimming_tasks.clear()
for task in self._light_ct_tasks.values():
task.cancel()
self._light_ct_tasks.clear()
await self._unregister_knx_events()
@@ -344,6 +350,9 @@ class BridgeManager:
color_temperature_mode=color_temp_mode,
min_kelvin=_clean_int(data.get(CONF_LIGHT_MIN_KELVIN)),
max_kelvin=_clean_int(data.get(CONF_LIGHT_MAX_KELVIN)),
relative_color_temperature_address=_clean_address(
data.get(CONF_LIGHT_RELATIVE_COLOR_TEMPERATURE_ADDRESS)
),
relative_dimming_address=_clean_address(
data.get(CONF_LIGHT_RELATIVE_DIMMING_ADDRESS)
),
@@ -667,6 +676,12 @@ class BridgeManager:
port,
"color_temperature",
)
self._register_knx_light_command(
port.relative_color_temperature_address,
None,
port,
"relative_color_temperature",
)
self._register_knx_light_command(
port.relative_dimming_address,
None,
@@ -1172,6 +1187,22 @@ class BridgeManager:
self._start_light_dimming(port.entity_id, direction, percent)
return
if action == "relative_color_temperature":
value = _extract_event_value(event)
if value is None:
return
if value in (0, 8):
self._stop_light_ct_adjust(port.entity_id)
return
step = _relative_dimming_step(value)
if step is None:
return
direction, percent = step
if percent <= 0:
return
self._start_light_ct_adjust(port, direction, percent)
return
if action.startswith("red_"):
await self._handle_light_component_action(port, "red", action, event)
elif action.startswith("green_"):
@@ -1422,6 +1453,43 @@ class BridgeManager:
if task is not None:
task.cancel()
def _start_light_ct_adjust(
self, port: LightPort, direction: str, percent: int
) -> None:
self._stop_light_ct_adjust(port.entity_id)
async def _runner() -> None:
while True:
current_kelvin = _current_color_temp_kelvin(self.hass, port)
if current_kelvin is None:
current_kelvin = _light_min_kelvin(port)
delta = (_light_max_kelvin(port) - _light_min_kelvin(port)) * (
percent / 100
)
if direction == "up":
next_kelvin = current_kelvin + delta
else:
next_kelvin = current_kelvin - delta
next_kelvin = min(
max(next_kelvin, _light_min_kelvin(port)),
_light_max_kelvin(port),
)
await self._call_light_service(
port.entity_id,
"turn_on",
{"color_temp": _kelvin_to_mireds(next_kelvin)},
)
await asyncio.sleep(0.3)
self._light_ct_tasks[port.entity_id] = self.hass.async_create_task(
_runner()
)
def _stop_light_ct_adjust(self, entity_id: str) -> None:
task = self._light_ct_tasks.pop(entity_id, None)
if task is not None:
task.cancel()
def _extract_event_value(event: Event) -> int | None:
if "value" in event.data:
@@ -1631,6 +1699,26 @@ def _current_hs_color(
return None
def _current_color_temp_kelvin(
hass: HomeAssistant, port: LightPort
) -> float | None:
state = hass.states.get(port.entity_id)
if state is None:
return None
if ATTR_COLOR_TEMP_KELVIN in state.attributes:
try:
return float(state.attributes[ATTR_COLOR_TEMP_KELVIN])
except (TypeError, ValueError):
return None
mireds = state.attributes.get(ATTR_COLOR_TEMP)
if mireds is None:
return None
try:
return _mireds_to_kelvin(float(mireds))
except (TypeError, ValueError):
return None
def _light_color_temperature_event_type(mode: str) -> str | None:
if mode == "relative":
return "percent"