feat: sACN-Einstellungen und -Status als Entitaeten
Die Firmware kann sACN/E1.31 - jetzt auch in Home Assistant, aufgebaut wie in
ha-ninlight: Schalter fuer den Empfang, Zahlen fuer Universe und Startslot,
Auswahl fuer das Verhalten nach Stromende, zwei Diagnose-Binaersensoren.
Die Einstellungen liegen hinter system/settings, der Koordinator liest sie
deshalb mit - eine vierte Anfrage je Zyklus. Nach dem Schreiben wird
zurueckgelesen statt angenommen, wie in ha-ninlight.
Zwei Unterschiede zu ninlight:
- Kein Layout-Feld: ninstrip nutzt fest drei Kanaele je Pixel.
- Der Startslot gilt in JEDEM belegten Universe. Die Obergrenze ist deshalb
513 minus Kanaele je Pixel, also 510 - ein festes 512 boete Werte an, bei
denen kein einziges Pixel mehr ins Universe passt.
Aeltere Firmware ohne sacn-Block: die Entitaeten entstehen trotzdem und
melden sich nicht verfuegbar, bis ein Update kommt.
In ha-dev an der Stehlampe geprueft: Universe, Startslot, on_timeout und der
Schalter kommen jeweils auf dem Geraet an; der Aktiv-Sensor steht waehrend
eines Stroms auf an, danach auf aus. 511 als Startslot weist Home Assistant
selbst ab ("outside valid range 1 - 510"). Einstellungen der Lampe danach
zurueckgestellt.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""sACN-Zahlenfelder eines ninstrip-Geraets: Universe und Startslot."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.number import NumberEntity, NumberMode
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import NinstripConfigEntry, NinstripCoordinator
|
||||
from .entity import NinstripSacnEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
# Kanaele je Pixel, falls das Geraet sie nicht meldet. Die Firmware stellt fest
|
||||
# drei ein (R, G, B) - so senden LedFx, xLights und Resolume.
|
||||
_KANAELE_VORGABE = 3
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: NinstripConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
[NinstripSacnUniverse(coordinator), NinstripSacnStartSlot(coordinator)]
|
||||
)
|
||||
|
||||
|
||||
class NinstripSacnUniverse(NinstripSacnEntity, NumberEntity):
|
||||
"""Das erste Universe. Braucht der Strip mehr, folgen die naechsten."""
|
||||
|
||||
_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: NinstripCoordinator) -> 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 NinstripSacnStartSlot(NinstripSacnEntity, NumberEntity):
|
||||
"""Der erste Slot - er gilt in JEDEM belegten Universe, nicht nur im ersten.
|
||||
|
||||
Anders als bei ninlight, wo eine Lampe ein einzelner Slotsatz ist: ein
|
||||
Strip verteilt sich ueber mehrere Universes, und in jedem beginnt er an
|
||||
derselben Stelle.
|
||||
"""
|
||||
|
||||
_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: NinstripCoordinator) -> None:
|
||||
super().__init__(coordinator, "sacn_start_slot")
|
||||
|
||||
@property
|
||||
def native_max_value(self) -> float:
|
||||
"""Der hoechste Startslot, hinter dem noch ein ganzes Pixel Platz hat.
|
||||
|
||||
Bei drei Kanaelen sind das 510. Ein festes 512 wuerde Werte anbieten,
|
||||
bei denen kein einziges Pixel mehr ins Universe passt.
|
||||
"""
|
||||
kanaele = self.sacn.get("channels_per_pixel")
|
||||
if not isinstance(kanaele, int) or not 1 <= kanaele <= 512:
|
||||
kanaele = _KANAELE_VORGABE
|
||||
return 513 - kanaele
|
||||
|
||||
@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