Skip to content

Commit 681eb6b

Browse files
Add LED control for supported UniFi network devices (home-assistant#152649)
1 parent 1d6c662 commit 681eb6b

File tree

6 files changed

+574
-0
lines changed

6 files changed

+574
-0
lines changed

homeassistant/components/unifi/const.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
Platform.BUTTON,
1414
Platform.DEVICE_TRACKER,
1515
Platform.IMAGE,
16+
Platform.LIGHT,
1617
Platform.SENSOR,
1718
Platform.SWITCH,
1819
Platform.UPDATE,
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
"""Light platform for UniFi Network integration."""
2+
3+
from __future__ import annotations
4+
5+
from collections.abc import Callable, Coroutine
6+
from dataclasses import dataclass
7+
from typing import TYPE_CHECKING, Any, cast
8+
9+
from aiounifi.interfaces.api_handlers import APIHandler, ItemEvent
10+
from aiounifi.interfaces.devices import Devices
11+
from aiounifi.models.api import ApiItem
12+
from aiounifi.models.device import Device, DeviceSetLedStatus
13+
14+
from homeassistant.components.light import (
15+
ATTR_BRIGHTNESS,
16+
ATTR_RGB_COLOR,
17+
ColorMode,
18+
LightEntity,
19+
LightEntityDescription,
20+
LightEntityFeature,
21+
)
22+
from homeassistant.core import HomeAssistant, callback
23+
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
24+
from homeassistant.util.color import rgb_hex_to_rgb_list
25+
26+
from . import UnifiConfigEntry
27+
from .entity import (
28+
UnifiEntity,
29+
UnifiEntityDescription,
30+
async_device_available_fn,
31+
async_device_device_info_fn,
32+
)
33+
34+
if TYPE_CHECKING:
35+
from .hub import UnifiHub
36+
37+
38+
@callback
39+
def async_device_led_supported_fn(hub: UnifiHub, obj_id: str) -> bool:
40+
"""Check if device supports LED control."""
41+
device: Device = hub.api.devices[obj_id]
42+
return device.supports_led_ring
43+
44+
45+
@callback
46+
def async_device_led_is_on_fn(hub: UnifiHub, device: Device) -> bool:
47+
"""Check if device LED is on."""
48+
return device.led_override == "on"
49+
50+
51+
async def async_device_led_control_fn(
52+
hub: UnifiHub, obj_id: str, turn_on: bool, **kwargs: Any
53+
) -> None:
54+
"""Control device LED."""
55+
device = hub.api.devices[obj_id]
56+
57+
status = "on" if turn_on else "off"
58+
59+
brightness = (
60+
int((kwargs[ATTR_BRIGHTNESS] / 255) * 100)
61+
if ATTR_BRIGHTNESS in kwargs
62+
else device.led_override_color_brightness
63+
)
64+
65+
color = (
66+
f"#{kwargs[ATTR_RGB_COLOR][0]:02x}{kwargs[ATTR_RGB_COLOR][1]:02x}{kwargs[ATTR_RGB_COLOR][2]:02x}"
67+
if ATTR_RGB_COLOR in kwargs
68+
else device.led_override_color
69+
)
70+
71+
await hub.api.request(
72+
DeviceSetLedStatus.create(
73+
device=device,
74+
status=status,
75+
brightness=brightness,
76+
color=color,
77+
)
78+
)
79+
80+
81+
@dataclass(frozen=True, kw_only=True)
82+
class UnifiLightEntityDescription[HandlerT: APIHandler, ApiItemT: ApiItem](
83+
LightEntityDescription, UnifiEntityDescription[HandlerT, ApiItemT]
84+
):
85+
"""Class describing UniFi light entity."""
86+
87+
control_fn: Callable[[UnifiHub, str, bool], Coroutine[Any, Any, None]]
88+
is_on_fn: Callable[[UnifiHub, ApiItemT], bool]
89+
90+
91+
ENTITY_DESCRIPTIONS: tuple[UnifiLightEntityDescription, ...] = (
92+
UnifiLightEntityDescription[Devices, Device](
93+
key="LED control",
94+
translation_key="led_control",
95+
allowed_fn=lambda hub, obj_id: True,
96+
api_handler_fn=lambda api: api.devices,
97+
available_fn=async_device_available_fn,
98+
control_fn=async_device_led_control_fn,
99+
device_info_fn=async_device_device_info_fn,
100+
is_on_fn=async_device_led_is_on_fn,
101+
name_fn=lambda device: "LED",
102+
object_fn=lambda api, obj_id: api.devices[obj_id],
103+
supported_fn=async_device_led_supported_fn,
104+
unique_id_fn=lambda hub, obj_id: f"led-{obj_id}",
105+
),
106+
)
107+
108+
109+
async def async_setup_entry(
110+
hass: HomeAssistant,
111+
config_entry: UnifiConfigEntry,
112+
async_add_entities: AddConfigEntryEntitiesCallback,
113+
) -> None:
114+
"""Set up lights for UniFi Network integration."""
115+
config_entry.runtime_data.entity_loader.register_platform(
116+
async_add_entities,
117+
UnifiLightEntity,
118+
ENTITY_DESCRIPTIONS,
119+
requires_admin=True,
120+
)
121+
122+
123+
class UnifiLightEntity[HandlerT: APIHandler, ApiItemT: ApiItem](
124+
UnifiEntity[HandlerT, ApiItemT], LightEntity
125+
):
126+
"""Base representation of a UniFi light."""
127+
128+
entity_description: UnifiLightEntityDescription[HandlerT, ApiItemT]
129+
_attr_supported_features = LightEntityFeature(0)
130+
_attr_color_mode = ColorMode.RGB
131+
_attr_supported_color_modes = {ColorMode.RGB}
132+
133+
@callback
134+
def async_initiate_state(self) -> None:
135+
"""Initiate entity state."""
136+
self.async_update_state(ItemEvent.ADDED, self._obj_id)
137+
138+
async def async_turn_on(self, **kwargs: Any) -> None:
139+
"""Turn on light."""
140+
await self.entity_description.control_fn(self.hub, self._obj_id, True, **kwargs)
141+
142+
async def async_turn_off(self, **kwargs: Any) -> None:
143+
"""Turn off light."""
144+
await self.entity_description.control_fn(
145+
self.hub, self._obj_id, False, **kwargs
146+
)
147+
148+
@callback
149+
def async_update_state(self, event: ItemEvent, obj_id: str) -> None:
150+
"""Update entity state."""
151+
description = self.entity_description
152+
device_obj = description.object_fn(self.api, self._obj_id)
153+
154+
device = cast(Device, device_obj)
155+
156+
self._attr_is_on = description.is_on_fn(self.hub, device_obj)
157+
158+
brightness = device.led_override_color_brightness
159+
self._attr_brightness = (
160+
int((int(brightness) / 100) * 255) if brightness is not None else None
161+
)
162+
163+
hex_color = (
164+
device.led_override_color.lstrip("#")
165+
if self._attr_is_on and device.led_override_color
166+
else None
167+
)
168+
if hex_color and len(hex_color) == 6:
169+
rgb_list = rgb_hex_to_rgb_list(hex_color)
170+
self._attr_rgb_color = (rgb_list[0], rgb_list[1], rgb_list[2])
171+
else:
172+
self._attr_rgb_color = None

homeassistant/components/unifi/strings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
}
3535
},
3636
"entity": {
37+
"light": {
38+
"led_control": {
39+
"name": "LED"
40+
}
41+
},
3742
"sensor": {
3843
"device_state": {
3944
"state": {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# serializer version: 1
2+
# name: test_light_platform_snapshot[device_payload0][light.device_with_led_led-entry]
3+
EntityRegistryEntrySnapshot({
4+
'aliases': set({
5+
}),
6+
'area_id': None,
7+
'capabilities': dict({
8+
'supported_color_modes': list([
9+
<ColorMode.RGB: 'rgb'>,
10+
]),
11+
}),
12+
'config_entry_id': <ANY>,
13+
'config_subentry_id': <ANY>,
14+
'device_class': None,
15+
'device_id': <ANY>,
16+
'disabled_by': None,
17+
'domain': 'light',
18+
'entity_category': None,
19+
'entity_id': 'light.device_with_led_led',
20+
'has_entity_name': True,
21+
'hidden_by': None,
22+
'icon': None,
23+
'id': <ANY>,
24+
'labels': set({
25+
}),
26+
'name': None,
27+
'options': dict({
28+
}),
29+
'original_device_class': None,
30+
'original_icon': None,
31+
'original_name': 'LED',
32+
'platform': 'unifi',
33+
'previous_unique_id': None,
34+
'suggested_object_id': None,
35+
'supported_features': 0,
36+
'translation_key': 'led_control',
37+
'unique_id': 'led-10:00:00:00:01:01',
38+
'unit_of_measurement': None,
39+
})
40+
# ---
41+
# name: test_light_platform_snapshot[device_payload0][light.device_with_led_led-state]
42+
StateSnapshot({
43+
'attributes': ReadOnlyDict({
44+
'brightness': 204,
45+
'color_mode': <ColorMode.RGB: 'rgb'>,
46+
'friendly_name': 'Device with LED LED',
47+
'hs_color': tuple(
48+
240.0,
49+
100.0,
50+
),
51+
'rgb_color': tuple(
52+
0,
53+
0,
54+
255,
55+
),
56+
'supported_color_modes': list([
57+
<ColorMode.RGB: 'rgb'>,
58+
]),
59+
'supported_features': <LightEntityFeature: 0>,
60+
'xy_color': tuple(
61+
0.136,
62+
0.04,
63+
),
64+
}),
65+
'context': <ANY>,
66+
'entity_id': 'light.device_with_led_led',
67+
'last_changed': <ANY>,
68+
'last_reported': <ANY>,
69+
'last_updated': <ANY>,
70+
'state': 'on',
71+
})
72+
# ---

tests/components/unifi/test_hub.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ async def test_hub_setup(
4141
Platform.BUTTON,
4242
Platform.DEVICE_TRACKER,
4343
Platform.IMAGE,
44+
Platform.LIGHT,
4445
Platform.SENSOR,
4546
Platform.SWITCH,
4647
Platform.UPDATE,

0 commit comments

Comments
 (0)