feat(light): Farbe und Farbtemperatur ueber alle Segmente

In Home Assistant liessen sich nur Szenen und Helligkeit stellen, keine
Farben - device/state traegt naemlich keine. Die steht je Segment, und die
Segmente hat der Koordinator bisher gar nicht gelesen.

Jetzt holt er device/segments mit (fuenfte Anfrage je Zyklus), und die
Lichtentitaet bietet rgb_color und color_temp_kelvin an:

* Setzen schreibt in ALLE Segmente, aber nur die Farbfelder - Grenzen,
  Namen und Helligkeit bleiben stehen. Die Einteilung eines Strips ist von
  Hand gemacht, die reisst eine Farbwahl nicht ein. Geschrieben wird die
  ganze Liste auf einmal, danach zurueckgelesen statt angenommen.
* Angezeigt wird nur, worauf sich alle Segmente einigen. Zwei Farben und
  eine Entitaet - da waere jede Angabe gelogen, also gibt es keine.
* Farbtemperatur nur, wenn alle auf "white" stehen; sonst RGB, sonst
  blendete HA den Farbkreis aus, sobald ein Segment einen Effekt faehrt.
* Die Farbe geht VOR dem Einschalten raus, sonst leuchtet der Strip kurz
  in der alten auf.

Die Spanne 2700-6500 K ist die, die die Firmware auf die Weisskanaele
umrechnet (nl_ct.h); ohne Weisskanaele naehert sie sie in RGB an.

Dazu: das Markenbild ist jetzt dasselbe wie bei ninlight - Michaels
Wunsch, die beiden Integrationen gehoeren zusammen. Das Skript fuer ein
eigenes Motiv bleibt als Ausgangspunkt liegen.
This commit is contained in:
Michael Sauer
2026-09-23 00:20:23 +02:00
parent 2c340ea11f
commit c40c68b537
11 changed files with 361 additions and 22 deletions
+15
View File
@@ -20,6 +20,7 @@ from tests_unit.samples import (
INFO_K1Y,
MAC_K1Y,
PRESETS_K1Y,
SEGMENTS_K1Y,
SETTINGS_COMPILED,
STATE_K1Y,
TOKEN,
@@ -57,12 +58,18 @@ def strip_presets() -> dict[str, Any]:
return deepcopy(PRESETS_K1Y)
@pytest.fixture
def strip_segments() -> dict[str, Any]:
return deepcopy(SEGMENTS_K1Y)
@pytest.fixture
def mock_client(
strip_state: dict[str, Any],
strip_info: dict[str, Any],
strip_settings: dict[str, Any],
strip_presets: dict[str, Any],
strip_segments: dict[str, Any],
) -> Generator[MagicMock]:
"""Ein Geraet, dessen Antworten den veraenderbaren strip_*-Fixtures folgen."""
client = MagicMock(spec=NinstripClient)
@@ -71,6 +78,14 @@ def mock_client(
client.get_info = AsyncMock(side_effect=lambda: deepcopy(strip_info))
client.get_settings = AsyncMock(side_effect=lambda: deepcopy(strip_settings))
client.get_presets = AsyncMock(side_effect=lambda: deepcopy(strip_presets))
client.get_segments = AsyncMock(side_effect=lambda: deepcopy(strip_segments))
async def put_segments(segments: list[dict[str, Any]]) -> None:
# Wie das Geraet: es nimmt die Liste als Ganzes an und antwortet nur
# mit "ok"; der Aufrufer liest danach zurueck.
strip_segments["segments"] = deepcopy(segments)
client.put_segments = AsyncMock(side_effect=put_segments)
async def set_state(**fields: Any) -> dict[str, Any]:
strip_state.update(fields)