1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-21 11:30:03 +00:00
Files
core/homeassistant/components/unifi/light.py
2025-12-06 07:51:09 +01:00

208 lines
6.9 KiB
Python

"""Light platform for UniFi Network integration."""
from __future__ import annotations
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, cast
from aiounifi.interfaces.api_handlers import APIHandler, ItemEvent
from aiounifi.interfaces.devices import Devices
from aiounifi.models.api import ApiItem
from aiounifi.models.device import Device, DeviceSetLedStatus
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_RGB_COLOR,
ColorMode,
LightEntity,
LightEntityDescription,
LightEntityFeature,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.color import rgb_hex_to_rgb_list
from . import UnifiConfigEntry
from .entity import (
UnifiEntity,
UnifiEntityDescription,
async_device_available_fn,
async_device_device_info_fn,
)
if TYPE_CHECKING:
from .hub import UnifiHub
def convert_brightness_to_unifi(ha_brightness: int) -> int:
"""Convert Home Assistant brightness (0-255) to UniFi brightness (0-100)."""
return round((ha_brightness / 255) * 100)
def convert_brightness_to_ha(
unifi_brightness: int,
) -> int:
"""Convert UniFi brightness (0-100) to Home Assistant brightness (0-255)."""
return round((unifi_brightness / 100) * 255)
def get_device_brightness_or_default(device: Device) -> int:
"""Get device's current LED brightness. Defaults to 100 (full brightness) if not set."""
value = device.led_override_color_brightness
return value if value is not None else 100
@callback
def async_device_led_supported_fn(hub: UnifiHub, obj_id: str) -> bool:
"""Check if device supports LED control."""
device: Device = hub.api.devices[obj_id]
return device.led_override is not None or device.supports_led_ring
@callback
def async_device_led_is_on_fn(hub: UnifiHub, device: Device) -> bool:
"""Check if device LED is on."""
return device.led_override == "on"
async def async_device_led_control_fn(
hub: UnifiHub, obj_id: str, turn_on: bool, **kwargs: Any
) -> None:
"""Control device LED."""
device = hub.api.devices[obj_id]
status = "on" if turn_on else "off"
# Only send brightness and RGB if device has LED_RING hardware support
if device.supports_led_ring:
# Use provided brightness or fall back to device's current brightness
if ATTR_BRIGHTNESS in kwargs:
brightness = convert_brightness_to_unifi(kwargs[ATTR_BRIGHTNESS])
else:
brightness = get_device_brightness_or_default(device)
# Use provided RGB color or fall back to device's current color
color: str | None
if ATTR_RGB_COLOR in kwargs:
rgb = kwargs[ATTR_RGB_COLOR]
color = f"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}"
else:
color = device.led_override_color
else:
brightness = None
color = None
await hub.api.request(
DeviceSetLedStatus.create(
device=device,
status=status,
brightness=brightness,
color=color,
)
)
@dataclass(frozen=True, kw_only=True)
class UnifiLightEntityDescription[HandlerT: APIHandler, ApiItemT: ApiItem](
LightEntityDescription, UnifiEntityDescription[HandlerT, ApiItemT]
):
"""Class describing UniFi light entity."""
control_fn: Callable[[UnifiHub, str, bool], Coroutine[Any, Any, None]]
is_on_fn: Callable[[UnifiHub, ApiItemT], bool]
ENTITY_DESCRIPTIONS: tuple[UnifiLightEntityDescription, ...] = (
UnifiLightEntityDescription[Devices, Device](
key="LED control",
translation_key="led_control",
entity_category=EntityCategory.CONFIG,
allowed_fn=lambda hub, obj_id: True,
api_handler_fn=lambda api: api.devices,
available_fn=async_device_available_fn,
control_fn=async_device_led_control_fn,
device_info_fn=async_device_device_info_fn,
is_on_fn=async_device_led_is_on_fn,
name_fn=lambda device: "LED",
object_fn=lambda api, obj_id: api.devices[obj_id],
supported_fn=async_device_led_supported_fn,
unique_id_fn=lambda hub, obj_id: f"led-{obj_id}",
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: UnifiConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up lights for UniFi Network integration."""
config_entry.runtime_data.entity_loader.register_platform(
async_add_entities,
UnifiLightEntity,
ENTITY_DESCRIPTIONS,
requires_admin=True,
)
class UnifiLightEntity[HandlerT: APIHandler, ApiItemT: ApiItem](
UnifiEntity[HandlerT, ApiItemT], LightEntity
):
"""Base representation of a UniFi light."""
entity_description: UnifiLightEntityDescription[HandlerT, ApiItemT]
_attr_supported_features = LightEntityFeature(0)
@callback
def async_initiate_state(self) -> None:
"""Initiate entity state."""
device = cast(Device, self.entity_description.object_fn(self.api, self._obj_id))
if device.supports_led_ring:
self._attr_supported_color_modes = {ColorMode.RGB}
self._attr_color_mode = ColorMode.RGB
else:
self._attr_supported_color_modes = {ColorMode.ONOFF}
self._attr_color_mode = ColorMode.ONOFF
self.async_update_state(ItemEvent.ADDED, self._obj_id)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on light."""
await self.entity_description.control_fn(self.hub, self._obj_id, True, **kwargs)
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off light."""
await self.entity_description.control_fn(
self.hub, self._obj_id, False, **kwargs
)
@callback
def async_update_state(self, event: ItemEvent, obj_id: str) -> None:
"""Update entity state."""
description = self.entity_description
device_obj = description.object_fn(self.api, self._obj_id)
device = cast(Device, device_obj)
self._attr_is_on = description.is_on_fn(self.hub, device_obj)
# Only set brightness and RGB if device has LED_RING hardware support
if device.supports_led_ring:
self._attr_brightness = convert_brightness_to_ha(
get_device_brightness_or_default(device)
)
# Parse hex color from device and convert to RGB tuple
hex_color = (
device.led_override_color.lstrip("#")
if self._attr_is_on and device.led_override_color
else None
)
if hex_color and len(hex_color) == 6:
rgb_list = rgb_hex_to_rgb_list(hex_color)
self._attr_rgb_color = (rgb_list[0], rgb_list[1], rgb_list[2])
else:
self._attr_rgb_color = None