"""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))