feat(light): Effekte zur Auswahl

Die neun Effekte der Firmware waren in Home Assistant nicht erreichbar -
das Auswahlfeld, das es schon gab, sind die Szenen. Jetzt bietet die
Lichtentitaet sie an:

* Gewaehlt wird ueber den NAMEN aus der Firmware ("Regenbogen"),
  geschickt wird die Kennung ("rainbow"). Eine Liste englischer
  Kennungen waere in einer deutschen Oberflaeche unnoetig sperrig.
* Ein Effekt laeuft wie eine Farbe in ALLEN Segmenten; angezeigt wird er
  nur, wenn alle denselben fahren.
* Eine Kennung, die nicht in der Liste steht (neuere Firmware, aeltere
  Liste), wird roh angezeigt statt verschluckt.
* Ein unbekannter Name wird abgelehnt, statt stillschweigend nichts zu
  tun.

Die Liste steht fest in der Firmware und wird deshalb einmal geholt,
nicht je Abfragezyklus - erneut erst, wenn sich die Version aendert. Eine
sechste Anfrage je Zyklus fuer etwas, das sich nie aendert, waere
Verschwendung.

Effekt-Parameter (Geschwindigkeit, Intensitaet, Palette) fehlen noch;
dafuer braucht es Entitaeten je Segment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Michael Sauer
2026-09-23 11:15:36 +02:00
co-authored by Claude Opus 5
parent 9eb7adcdd7
commit ffcbbdfe4a
10 changed files with 235 additions and 7 deletions
+43 -3
View File
@@ -14,6 +14,7 @@ from typing import Any
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
ATTR_EFFECT,
ATTR_RGB_COLOR,
ATTR_TRANSITION,
ColorMode,
@@ -21,9 +22,10 @@ from homeassistant.components.light import (
LightEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import MAX_TRANSITION_MS
from .const import DOMAIN, MAX_TRANSITION_MS
from .coordinator import NinstripConfigEntry, NinstripCoordinator
from .entity import NinstripEntity
@@ -56,7 +58,7 @@ class NinstripLight(NinstripEntity, LightEntity):
_attr_name = None
_attr_supported_color_modes = {ColorMode.RGB, ColorMode.COLOR_TEMP}
_attr_supported_features = LightEntityFeature.TRANSITION
_attr_supported_features = LightEntityFeature.TRANSITION | LightEntityFeature.EFFECT
_attr_min_color_temp_kelvin = MIN_KELVIN
_attr_max_color_temp_kelvin = MAX_KELVIN
@@ -117,6 +119,35 @@ class NinstripLight(NinstripEntity, LightEntity):
wert = self._einig("color_temp")
return int(wert) if isinstance(wert, (int, float)) else None
# --- Effekte -------------------------------------------------------
@property
def effect_list(self) -> list[str]:
"""Die Namen aus der Firmware, nicht die Kennungen.
Angezeigt wird "Regenbogen", geschickt wird "rainbow" - eine Liste
voller englischer Kennungen waere in einer deutschen Oberflaeche
unnoetig sperrig.
"""
return [str(e["name"]) for e in self.coordinator.effects if e.get("name") and e.get("id")]
def _effekt_id(self, name: str) -> str | None:
for e in self.coordinator.effects:
if e.get("name") == name:
return str(e["id"])
return None
@property
def effect(self) -> str | None:
"""Nur, wenn alle Segmente denselben Effekt fahren."""
if self._einig("mode") != "effect":
return None
kennung = self._einig("effect")
for e in self.coordinator.effects:
if e.get("id") == kennung:
return str(e.get("name") or kennung)
return str(kennung) if kennung else None
@property
def extra_state_attributes(self) -> dict[str, Any]:
state = self.coordinator.data.state
@@ -132,7 +163,16 @@ class NinstripLight(NinstripEntity, LightEntity):
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:
if ATTR_EFFECT in kwargs:
kennung = self._effekt_id(kwargs[ATTR_EFFECT])
if kennung is None:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="unknown_effect",
translation_placeholders={"effect": str(kwargs[ATTR_EFFECT])},
)
await self.coordinator.async_set_all_segments(mode="effect", effect=kennung)
elif 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)}