Source code for aioafero.v1.models.light

"""Representation of an Afero Light and its corresponding updates."""

from dataclasses import dataclass, field
import re

from aioafero.v1.models import features

from .resource import ResourceTypes
from .standard_mixin import StandardMixin

rgbw_name_search = re.compile(r"RGB\w*W")


[docs] @dataclass class LightChannel: """Per-channel brightness/on for a dual-channel light.""" brightness: int | None = None on: bool | None = None
[docs] @dataclass(kw_only=True) class Light(StandardMixin): """Representation of an Afero Light.""" type: ResourceTypes = ResourceTypes.LIGHT on: features.OnFeature | None = None color: features.ColorFeature | None = None color_mode: features.ColorModeFeature | None = None color_modes: list[str] | None = None color_mode_hints: dict[str, list[str]] | None = None color_temperature: features.ColorTemperatureFeature | None = None dimming: features.DimmingFeature | None = None effect: features.EffectFeature | None = None supports_white: bool = False channels: dict[str, LightChannel] = field(default_factory=dict)
[docs] def __post_init__(self): """Determine if white only is supported.""" model = getattr(getattr(self, "device_information", None), "model", None) if model is not None and rgbw_name_search.search(model): self.supports_white = True
@property def supports_color(self) -> bool: """Return if this light supports color control.""" return self.color is not None @property def supports_color_temperature(self) -> bool: """Return if this light supports color_temperature control.""" return self.color_temperature is not None @property def supports_color_white(self) -> bool: """Return if this light supports setting white.""" return self.color_modes is not None and "white" in self.color_modes
[docs] def color_mode_has_hint(self, mode: str, hint: str) -> bool: """Return True when ``mode`` carries the given API category hint.""" if not self.color_mode_hints: return False return hint in self.color_mode_hints.get(mode, [])
@property def supports_dimming(self) -> bool: """Return if this light supports brightness control.""" return self.dimming is not None @property def supports_effects(self) -> bool: """Return if this light supports brightness control.""" return self.effect is not None @property def supports_on(self): """If the light can be toggled on or off.""" return self.on is not None @property def is_on(self) -> bool: """Return bool if light is currently powered on.""" if self.on is not None: return self.on.on return False @property def brightness(self) -> float: """Return current brightness of light.""" if self.dimming is not None: return self.dimming.brightness return 100.0 if self.is_on else 0.0 @property def is_dual_channel(self) -> bool: """Return True when color and white channels are both present.""" return {"color", "white"}.issubset(self.channels)
[docs] def channel_brightness(self, channel: str) -> float | None: """Return brightness for a channel, if known.""" entry = self.channels.get(channel) if entry is None or entry.brightness is None: return None return float(entry.brightness)
[docs] def channel_on(self, channel: str) -> bool | None: """Return on/off for a channel toggle, if known.""" entry = self.channels.get(channel) if entry is None: return None return entry.on
[docs] def feature_for_update_comparison( self, field_name: str, put_feature: object | None ) -> object | None: """Compare channel toggles/dimming against ``channels``, not primary.""" instance = ( getattr(put_feature, "function_instance", None) if put_feature else None ) # Primary power/dimming often use function_instance=None; never treat that # as a channel. if not instance or instance not in self.channels: return super().feature_for_update_comparison(field_name, put_feature) channel = self.channels[instance] if field_name == "on": # Dual-channel power uses toggle/<color|white>, not primary power. if getattr(put_feature, "function_class", None) != "toggle": return super().feature_for_update_comparison(field_name, put_feature) if channel.on is None: # Unknown channel state: do not suppress (caller may still PUT). return None return features.OnFeature( on=channel.on, function_class="toggle", function_instance=instance, ) if field_name == "dimming": if channel.brightness is None or self.dimming is None: # Unknown channel brightness: do not suppress. return None return features.DimmingFeature( brightness=channel.brightness, # Required by the dataclass; features_equivalent_for_update ignores it. supported=self.dimming.supported, function_class=self.dimming.function_class, function_instance=instance, ) return super().feature_for_update_comparison(field_name, put_feature)
[docs] @dataclass class LightPut[AferoResource]: """States that can be updated for a light.""" on: features.OnFeature | None = None color: features.ColorFeature | None = None color_mode: features.ColorModeFeature | None = None color_temperature: features.ColorTemperatureFeature | None = None dimming: features.DimmingFeature | None = None effect: features.EffectFeature | None = None numbers: dict[tuple[str, str | None], features.NumbersFeature] | None = field( default_factory=dict, repr=False, init=False )