feat: sACN-Einstellungen und -Status als Entitaeten

Die Firmware kann seit dieser Woche sACN/E1.31. Einstellen liess sich das nur
per REST - jetzt steht es in Home Assistant: Schalter fuer den Empfang, Zahlen
fuer Universe und Startslot, Auswahlen fuer das Verhalten nach dem Timeout und
die Slotbelegung, dazu zwei Diagnose-Binaersensoren.

Die sACN-Einstellungen liegen hinter system/settings, nicht in device/state -
der Koordinator liest sie deshalb jetzt mit, eine dritte Anfrage je Zyklus. Der
Schreibweg liest die Einstellungen danach frisch zurueck statt den gesetzten
Wert anzunehmen: die Lampe validiert selbst (die Startslot-Grenze haengt an der
Slotbelegung), und eine Entitaet, die das Gewuenschte statt des Gespeicherten
zeigt, luegt in genau dem Moment, in dem es darauf ankommt.

Aeltere Firmware antwortet ohne den sacn-Block. Die Entitaeten entstehen
trotzdem und melden sich als nicht verfuegbar - so werden sie nach einem Update
der Lampe beim naechsten Poll von selbst lebendig, ohne Neuladen.

Die Obergrenze des Startslots kommt aus dem "slots"-Feld der Lampe, nicht aus
einer festen 512: sie haengt an Board UND Slotbelegung, und eine feste Grenze
wuerde Werte anbieten, die die Lampe dann ablehnt.

Der Multicast-Sensor ist ab Werk deaktiviert wie die uebrigen Diagnosewerte. Er
meldet im Normalbetrieb schlicht "beigetreten" - aber eine falsche
Gruppenadresse hat zwei Abende gekostet, und genau dort waere sie aufgefallen.

test_update.py misst seine Zusage jetzt um den Rescan herum statt nach dem
Install: dort pollt der Koordinator ohnehin wieder und liest seitdem auch die
Einstellungen mit.
This commit is contained in:
Michael Sauer
2026-09-21 23:35:54 +02:00
parent 5781e723ab
commit ac60428fdb
15 changed files with 791 additions and 85 deletions
@@ -0,0 +1,64 @@
"""Diagnostic binary sensors of a ninlight lamp: the sACN receiver's state."""
from __future__ import annotations
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import NinlightConfigEntry, NinlightCoordinator
from .entity import NinlightEntity
PARALLEL_UPDATES = 0
async def async_setup_entry(
hass: HomeAssistant,
entry: NinlightConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
async_add_entities(
[NinlightSacnActive(entry.runtime_data), NinlightSacnMulticast(entry.runtime_data)]
)
class _NinlightSacnInfo(NinlightEntity, BinarySensorEntity):
"""Base for the two flags the lamp reports under system/info -> sacn."""
_attr_entity_category = EntityCategory.DIAGNOSTIC
_field: str
@property
def available(self) -> bool:
return super().available and self.coordinator.data.sacn_info is not None
@property
def is_on(self) -> bool:
return bool((self.coordinator.data.sacn_info or {}).get(self._field))
class NinlightSacnActive(_NinlightSacnInfo):
"""True while a controller is driving the lamp."""
_attr_translation_key = "sacn_active"
_field = "active"
def __init__(self, coordinator: NinlightCoordinator) -> None:
super().__init__(coordinator, "sacn_active")
class NinlightSacnMulticast(_NinlightSacnInfo):
"""True when the lamp joined the multicast group of its universe.
Off by default like the other diagnostics: it reads "true" in normal operation and
only earns its place when multicast does not arrive. It is worth having - a wrong
group address once cost two evenings of searching, and this flag is where that shows.
"""
_attr_translation_key = "sacn_multicast"
_attr_entity_registry_enabled_default = False
_field = "multicast"
def __init__(self, coordinator: NinlightCoordinator) -> None:
super().__init__(coordinator, "sacn_multicast")