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.
138 lines
5.0 KiB
Python
138 lines
5.0 KiB
Python
"""Hauptschalter, Helligkeit und Farbe eines ninstrip.
|
|
|
|
Bewusst eine Entitaet je Geraet, nicht je Segment: device/state ist der
|
|
Hauptschalter. Farbe und Farbtemperatur stehen dagegen NICHT dort, sondern je
|
|
Segment - die Entitaet schreibt sie deshalb in alle Segmente zugleich und
|
|
laesst deren Grenzen und Namen stehen. Einzelne Segmententitaeten koennen
|
|
spaeter dazukommen.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from homeassistant.components.light import (
|
|
ATTR_BRIGHTNESS,
|
|
ATTR_COLOR_TEMP_KELVIN,
|
|
ATTR_RGB_COLOR,
|
|
ColorMode,
|
|
LightEntity,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
|
|
|
from .coordinator import NinstripConfigEntry, NinstripCoordinator
|
|
from .entity import NinstripEntity
|
|
|
|
# Die Firmware rechnet Farbtemperaturen ausserhalb dieser Spanne nicht auf die
|
|
# Weisskanaele um (nl_ct.h). Strips ohne Weisskanaele bekommen dieselbe Spanne
|
|
# als RGB-Naeherung, damit "warmweiss" auf jedem Chip leuchtet.
|
|
MIN_KELVIN = 2700
|
|
MAX_KELVIN = 6500
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: NinstripConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
async_add_entities([NinstripLight(entry.runtime_data)])
|
|
|
|
|
|
class NinstripLight(NinstripEntity, LightEntity):
|
|
"""Der Strip als Ganzes."""
|
|
|
|
_attr_name = None
|
|
_attr_supported_color_modes = {ColorMode.RGB, ColorMode.COLOR_TEMP}
|
|
_attr_min_color_temp_kelvin = MIN_KELVIN
|
|
_attr_max_color_temp_kelvin = MAX_KELVIN
|
|
|
|
def __init__(self, coordinator: NinstripCoordinator) -> None:
|
|
super().__init__(coordinator, "light")
|
|
|
|
# --- Zustand -------------------------------------------------------
|
|
|
|
def _segments(self) -> list[dict[str, Any]]:
|
|
return self.coordinator.data.segments.get("segments") or []
|
|
|
|
def _einig(self, feld: str) -> Any:
|
|
"""Der Wert, wenn alle Segmente sich einig sind - sonst None.
|
|
|
|
Ein Strip kann segmentweise verschiedene Farben zeigen. Home Assistant
|
|
kennt nur eine je Entitaet; in dem Fall waere jede Angabe gelogen, also
|
|
gibt es keine.
|
|
"""
|
|
segmente = self._segments()
|
|
if not segmente:
|
|
return None
|
|
werte = [seg.get(feld) for seg in segmente]
|
|
return werte[0] if all(w == werte[0] and w is not None for w in werte) else None
|
|
|
|
@property
|
|
def is_on(self) -> bool:
|
|
return bool(self.coordinator.data.state.get("on"))
|
|
|
|
@property
|
|
def brightness(self) -> int | None:
|
|
"""Das Geraet rechnet in Prozent, Home Assistant in 0..255."""
|
|
value = self.coordinator.data.state.get("brightness")
|
|
if value is None:
|
|
return None
|
|
return round(max(0, min(100, int(value))) * 255 / 100)
|
|
|
|
@property
|
|
def color_mode(self) -> ColorMode:
|
|
"""Weiss nur, wenn sich alle Segmente auf 'white' einigen.
|
|
|
|
Sonst RGB: ein Strip mit Effekten oder gemischten Segmenten hat keine
|
|
eine Farbtemperatur, und HA wuerde den Farbkreis sonst ausblenden.
|
|
"""
|
|
return ColorMode.COLOR_TEMP if self._einig("mode") == "white" else ColorMode.RGB
|
|
|
|
@property
|
|
def rgb_color(self) -> tuple[int, int, int] | None:
|
|
farbe = self._einig("color")
|
|
if not isinstance(farbe, dict):
|
|
return None
|
|
try:
|
|
return (int(farbe["r"]), int(farbe["g"]), int(farbe["b"]))
|
|
except KeyError, TypeError, ValueError:
|
|
return None
|
|
|
|
@property
|
|
def color_temp_kelvin(self) -> int | None:
|
|
wert = self._einig("color_temp")
|
|
return int(wert) if isinstance(wert, (int, float)) else None
|
|
|
|
@property
|
|
def extra_state_attributes(self) -> dict[str, Any]:
|
|
state = self.coordinator.data.state
|
|
return {
|
|
"segment_count": state.get("segment_count"),
|
|
"pixel_count": state.get("pixel_count"),
|
|
"preset": state.get("preset"),
|
|
"preset_modified": state.get("preset_modified"),
|
|
}
|
|
|
|
# --- Schalten ------------------------------------------------------
|
|
|
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
|
# Erst die Farbe in die Segmente, dann der Hauptschalter: so leuchtet
|
|
# nichts kurz in der alten Farbe auf.
|
|
if ATTR_RGB_COLOR in kwargs:
|
|
r, g, b = kwargs[ATTR_RGB_COLOR]
|
|
await self.coordinator.async_set_all_segments(
|
|
mode="solid", color={"r": int(r), "g": int(g), "b": int(b)}
|
|
)
|
|
elif ATTR_COLOR_TEMP_KELVIN in kwargs:
|
|
kelvin = max(MIN_KELVIN, min(MAX_KELVIN, int(kwargs[ATTR_COLOR_TEMP_KELVIN])))
|
|
await self.coordinator.async_set_all_segments(mode="white", color_temp=kelvin)
|
|
|
|
fields: dict[str, Any] = {"on": True}
|
|
if ATTR_BRIGHTNESS in kwargs:
|
|
fields["brightness"] = round(int(kwargs[ATTR_BRIGHTNESS]) * 100 / 255)
|
|
await self.coordinator.async_set_state(**fields)
|
|
|
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
|
await self.coordinator.async_set_state(on=False)
|