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:
@@ -1,4 +1,4 @@
|
||||
"""Fade and transition settings of a ninlight lamp."""
|
||||
"""Number settings of a ninlight lamp: fades, transition and sACN addressing."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -15,7 +15,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import NinlightConfigEntry, NinlightCoordinator
|
||||
from .entity import NinlightEntity
|
||||
from .entity import NinlightEntity, NinlightSacnEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
@@ -46,7 +46,12 @@ async def async_setup_entry(
|
||||
entry: NinlightConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
async_add_entities(NinlightNumber(entry.runtime_data, description) for description in NUMBERS)
|
||||
entities: list[NumberEntity] = [
|
||||
NinlightNumber(entry.runtime_data, description) for description in NUMBERS
|
||||
]
|
||||
entities.append(NinlightSacnUniverse(entry.runtime_data))
|
||||
entities.append(NinlightSacnStartSlot(entry.runtime_data))
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class NinlightNumber(NinlightEntity, NumberEntity):
|
||||
@@ -67,3 +72,58 @@ class NinlightNumber(NinlightEntity, NumberEntity):
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
await self.coordinator.async_set_state(**{self.entity_description.key: int(value)})
|
||||
|
||||
|
||||
class NinlightSacnUniverse(NinlightSacnEntity, NumberEntity):
|
||||
"""The DMX universe the lamp listens on."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_translation_key = "sacn_universe"
|
||||
_attr_native_min_value = 1
|
||||
_attr_native_max_value = 63_999
|
||||
_attr_native_step = 1
|
||||
_attr_mode = NumberMode.BOX
|
||||
|
||||
def __init__(self, coordinator: NinlightCoordinator) -> None:
|
||||
super().__init__(coordinator, "sacn_universe")
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
value = self.sacn.get("universe")
|
||||
return float(value) if isinstance(value, (int, float)) else None
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
await self.coordinator.async_set_sacn(universe=int(value))
|
||||
|
||||
|
||||
class NinlightSacnStartSlot(NinlightSacnEntity, NumberEntity):
|
||||
"""The first slot of the lamp inside its universe."""
|
||||
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
_attr_translation_key = "sacn_start_slot"
|
||||
_attr_native_min_value = 1
|
||||
_attr_native_step = 1
|
||||
_attr_mode = NumberMode.BOX
|
||||
|
||||
def __init__(self, coordinator: NinlightCoordinator) -> None:
|
||||
super().__init__(coordinator, "sacn_start_slot")
|
||||
|
||||
@property
|
||||
def native_max_value(self) -> float:
|
||||
"""The highest start slot that still leaves room for all of the lamp's slots.
|
||||
|
||||
The lamp reports how many it needs, and that number depends on both the board and
|
||||
the slot layout - a fixed 512 here would offer settings the lamp then rejects.
|
||||
"""
|
||||
slots = self.sacn.get("slots")
|
||||
if not isinstance(slots, int) or not 1 <= slots <= 512:
|
||||
return 512
|
||||
return 512 - slots + 1
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
value = self.sacn.get("start_slot")
|
||||
return float(value) if isinstance(value, (int, float)) else None
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
await self.coordinator.async_set_sacn(start_slot=int(value))
|
||||
|
||||
Reference in New Issue
Block a user