Zeigt strip.current_ma und strip.power_w aus system/info als Sensoren und macht max_milliamps aus system/settings ueber ein Zahlenfeld einstellbar. Schreiben liest wie bei sACN zurueck statt den geschriebenen Wert anzunehmen.
170 lines
6.1 KiB
Python
170 lines
6.1 KiB
Python
"""Zahlenfelder eines ninstrip-Geraets: Uebergangszeiten und sACN."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from homeassistant.components.number import (
|
|
NumberDeviceClass,
|
|
NumberEntity,
|
|
NumberEntityDescription,
|
|
NumberMode,
|
|
)
|
|
from homeassistant.const import EntityCategory, UnitOfElectricCurrent, UnitOfTime
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
|
|
|
from .const import MAX_FADE_MS, MAX_TRANSITION_MS
|
|
from .coordinator import NinstripConfigEntry, NinstripCoordinator
|
|
from .entity import NinstripEntity, 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
|
|
|
|
|
|
@dataclass(frozen=True, kw_only=True)
|
|
class NinstripZeitDescription(NumberEntityDescription):
|
|
"""Ein Millisekundenfeld aus device/state."""
|
|
|
|
entity_category: EntityCategory = EntityCategory.CONFIG
|
|
device_class: NumberDeviceClass = NumberDeviceClass.DURATION
|
|
native_unit_of_measurement: str = UnitOfTime.MILLISECONDS
|
|
native_min_value: float = 0
|
|
native_step: float = 50
|
|
mode: NumberMode = NumberMode.BOX
|
|
|
|
|
|
# Grenzen wie in der Firmware (nl_api_device.c). Feldnamen wie in ninlight.
|
|
ZEITEN = (
|
|
NinstripZeitDescription(key="fade_in", translation_key="fade_in", native_max_value=MAX_FADE_MS),
|
|
NinstripZeitDescription(
|
|
key="fade_out", translation_key="fade_out", native_max_value=MAX_FADE_MS
|
|
),
|
|
NinstripZeitDescription(
|
|
key="transition_default",
|
|
translation_key="transition_default",
|
|
native_max_value=MAX_TRANSITION_MS,
|
|
),
|
|
)
|
|
|
|
|
|
async def async_setup_entry(
|
|
hass: HomeAssistant,
|
|
entry: NinstripConfigEntry,
|
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
|
) -> None:
|
|
coordinator = entry.runtime_data
|
|
entitaeten: list[NumberEntity] = [NinstripZeit(coordinator, d) for d in ZEITEN]
|
|
entitaeten.append(NinstripSacnUniverse(coordinator))
|
|
entitaeten.append(NinstripSacnStartSlot(coordinator))
|
|
entitaeten.append(NinstripStrombudget(coordinator))
|
|
async_add_entities(entitaeten)
|
|
|
|
|
|
class NinstripZeit(NinstripEntity, NumberEntity):
|
|
"""Ein Millisekundenfeld: Einblenden, Ausblenden, Vorgabe fuer Uebergaenge."""
|
|
|
|
entity_description: NinstripZeitDescription
|
|
|
|
def __init__(
|
|
self, coordinator: NinstripCoordinator, description: NinstripZeitDescription
|
|
) -> None:
|
|
super().__init__(coordinator, description.key)
|
|
self.entity_description = description
|
|
|
|
@property
|
|
def native_value(self) -> float | None:
|
|
wert = self.coordinator.data.state.get(self.entity_description.key)
|
|
return float(wert) if isinstance(wert, (int, float)) else None
|
|
|
|
async def async_set_native_value(self, value: float) -> None:
|
|
await self.coordinator.async_set_state(**{self.entity_description.key: int(value)})
|
|
|
|
|
|
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))
|
|
|
|
|
|
class NinstripStrombudget(NinstripEntity, NumberEntity):
|
|
"""Was das Netzteil hergibt. Die Firmware regelt daran."""
|
|
|
|
_attr_entity_category = EntityCategory.CONFIG
|
|
_attr_translation_key = "max_milliamps"
|
|
_attr_native_min_value = 100
|
|
# Die Firmware nimmt fuer das Budget hoechstens 65535 mA an (uint16).
|
|
_attr_native_max_value = 65535
|
|
_attr_native_step = 100
|
|
_attr_mode = NumberMode.BOX
|
|
_attr_native_unit_of_measurement = UnitOfElectricCurrent.MILLIAMPERE
|
|
|
|
def __init__(self, coordinator: NinstripCoordinator) -> None:
|
|
super().__init__(coordinator, "max_milliamps")
|
|
|
|
@property
|
|
def native_value(self) -> float | None:
|
|
strip = self.coordinator.data.settings.get("strip")
|
|
wert = strip.get("max_milliamps") if isinstance(strip, dict) else None
|
|
return float(wert) if isinstance(wert, (int, float)) else None
|
|
|
|
async def async_set_native_value(self, value: float) -> None:
|
|
await self.coordinator.async_set_strip(max_milliamps=int(value))
|