Home Assistant Unofficial Reference 2024.12.1
light.py
Go to the documentation of this file.
1 """Component providing support for Reolink light entities."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 from typing import Any
8 
9 from reolink_aio.api import Host
10 from reolink_aio.exceptions import InvalidParameterError, ReolinkError
11 
13  ATTR_BRIGHTNESS,
14  ColorMode,
15  LightEntity,
16  LightEntityDescription,
17 )
18 from homeassistant.const import EntityCategory
19 from homeassistant.core import HomeAssistant
20 from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
21 from homeassistant.helpers.entity_platform import AddEntitiesCallback
22 
23 from .entity import (
24  ReolinkChannelCoordinatorEntity,
25  ReolinkChannelEntityDescription,
26  ReolinkHostCoordinatorEntity,
27  ReolinkHostEntityDescription,
28 )
29 from .util import ReolinkConfigEntry, ReolinkData
30 
31 PARALLEL_UPDATES = 0
32 
33 
34 @dataclass(frozen=True, kw_only=True)
36  LightEntityDescription,
37  ReolinkChannelEntityDescription,
38 ):
39  """A class that describes light entities."""
40 
41  get_brightness_fn: Callable[[Host, int], int | None] | None = None
42  is_on_fn: Callable[[Host, int], bool]
43  set_brightness_fn: Callable[[Host, int, int], Any] | None = None
44  turn_on_off_fn: Callable[[Host, int, bool], Any]
45 
46 
47 @dataclass(frozen=True, kw_only=True)
49  LightEntityDescription,
50  ReolinkHostEntityDescription,
51 ):
52  """A class that describes host light entities."""
53 
54  is_on_fn: Callable[[Host], bool]
55  turn_on_off_fn: Callable[[Host, bool], Any]
56 
57 
58 LIGHT_ENTITIES = (
60  key="floodlight",
61  cmd_key="GetWhiteLed",
62  cmd_id=291,
63  translation_key="floodlight",
64  supported=lambda api, ch: api.supported(ch, "floodLight"),
65  is_on_fn=lambda api, ch: api.whiteled_state(ch),
66  turn_on_off_fn=lambda api, ch, value: api.set_whiteled(ch, state=value),
67  get_brightness_fn=lambda api, ch: api.whiteled_brightness(ch),
68  set_brightness_fn=lambda api, ch, value: api.set_whiteled(ch, brightness=value),
69  ),
71  key="status_led",
72  cmd_key="GetPowerLed",
73  translation_key="status_led",
74  entity_category=EntityCategory.CONFIG,
75  supported=lambda api, ch: api.supported(ch, "power_led"),
76  is_on_fn=lambda api, ch: api.status_led_enabled(ch),
77  turn_on_off_fn=lambda api, ch, value: api.set_status_led(ch, value),
78  ),
79 )
80 
81 HOST_LIGHT_ENTITIES = (
83  key="hub_status_led",
84  cmd_key="GetStateLight",
85  translation_key="status_led",
86  entity_category=EntityCategory.CONFIG,
87  supported=lambda api: api.supported(None, "state_light"),
88  is_on_fn=lambda api: api.state_light,
89  turn_on_off_fn=lambda api, value: api.set_state_light(value),
90  ),
91 )
92 
93 
95  hass: HomeAssistant,
96  config_entry: ReolinkConfigEntry,
97  async_add_entities: AddEntitiesCallback,
98 ) -> None:
99  """Set up a Reolink light entities."""
100  reolink_data: ReolinkData = config_entry.runtime_data
101 
102  entities: list[ReolinkLightEntity | ReolinkHostLightEntity] = [
103  ReolinkLightEntity(reolink_data, channel, entity_description)
104  for entity_description in LIGHT_ENTITIES
105  for channel in reolink_data.host.api.channels
106  if entity_description.supported(reolink_data.host.api, channel)
107  ]
108  entities.extend(
109  ReolinkHostLightEntity(reolink_data, entity_description)
110  for entity_description in HOST_LIGHT_ENTITIES
111  if entity_description.supported(reolink_data.host.api)
112  )
113 
114  async_add_entities(entities)
115 
116 
118  """Base light entity class for Reolink IP cameras."""
119 
120  entity_description: ReolinkLightEntityDescription
121 
122  def __init__(
123  self,
124  reolink_data: ReolinkData,
125  channel: int,
126  entity_description: ReolinkLightEntityDescription,
127  ) -> None:
128  """Initialize Reolink light entity."""
129  self.entity_descriptionentity_description = entity_description
130  super().__init__(reolink_data, channel)
131 
132  if entity_description.set_brightness_fn is None:
133  self._attr_supported_color_modes_attr_supported_color_modes = {ColorMode.ONOFF}
134  self._attr_color_mode_attr_color_mode = ColorMode.ONOFF
135  else:
136  self._attr_supported_color_modes_attr_supported_color_modes = {ColorMode.BRIGHTNESS}
137  self._attr_color_mode_attr_color_mode = ColorMode.BRIGHTNESS
138 
139  @property
140  def is_on(self) -> bool:
141  """Return true if light is on."""
142  return self.entity_descriptionentity_description.is_on_fn(self._host_host.api, self._channel_channel)
143 
144  @property
145  def brightness(self) -> int | None:
146  """Return the brightness of this light between 0.255."""
147  assert self.entity_descriptionentity_description.get_brightness_fn is not None
148 
149  bright_pct = self.entity_descriptionentity_description.get_brightness_fn(
150  self._host_host.api, self._channel_channel
151  )
152  if bright_pct is None:
153  return None
154 
155  return round(255 * bright_pct / 100.0)
156 
157  async def async_turn_off(self, **kwargs: Any) -> None:
158  """Turn light off."""
159  try:
160  await self.entity_descriptionentity_description.turn_on_off_fn(
161  self._host_host.api, self._channel_channel, False
162  )
163  except ReolinkError as err:
164  raise HomeAssistantError(err) from err
165  self.async_write_ha_stateasync_write_ha_state()
166 
167  async def async_turn_on(self, **kwargs: Any) -> None:
168  """Turn light on."""
169  if (
170  brightness := kwargs.get(ATTR_BRIGHTNESS)
171  ) is not None and self.entity_descriptionentity_description.set_brightness_fn is not None:
172  brightness_pct = int(brightness / 255.0 * 100)
173  try:
174  await self.entity_descriptionentity_description.set_brightness_fn(
175  self._host_host.api, self._channel_channel, brightness_pct
176  )
177  except InvalidParameterError as err:
178  raise ServiceValidationError(err) from err
179  except ReolinkError as err:
180  raise HomeAssistantError(err) from err
181 
182  try:
183  await self.entity_descriptionentity_description.turn_on_off_fn(
184  self._host_host.api, self._channel_channel, True
185  )
186  except ReolinkError as err:
187  raise HomeAssistantError(err) from err
188  self.async_write_ha_stateasync_write_ha_state()
189 
190 
192  """Base host light entity class for Reolink IP cameras."""
193 
194  entity_description: ReolinkHostLightEntityDescription
195  _attr_supported_color_modes = {ColorMode.ONOFF}
196  _attr_color_mode = ColorMode.ONOFF
197 
198  def __init__(
199  self,
200  reolink_data: ReolinkData,
201  entity_description: ReolinkHostLightEntityDescription,
202  ) -> None:
203  """Initialize Reolink host light entity."""
204  self.entity_descriptionentity_description = entity_description
205  super().__init__(reolink_data)
206 
207  @property
208  def is_on(self) -> bool:
209  """Return true if light is on."""
210  return self.entity_descriptionentity_description.is_on_fn(self._host_host.api)
211 
212  async def async_turn_off(self, **kwargs: Any) -> None:
213  """Turn light off."""
214  try:
215  await self.entity_descriptionentity_description.turn_on_off_fn(self._host_host.api, False)
216  except ReolinkError as err:
217  raise HomeAssistantError(err) from err
218  self.async_write_ha_stateasync_write_ha_state()
219 
220  async def async_turn_on(self, **kwargs: Any) -> None:
221  """Turn light on."""
222  try:
223  await self.entity_descriptionentity_description.turn_on_off_fn(self._host_host.api, True)
224  except ReolinkError as err:
225  raise HomeAssistantError(err) from err
226  self.async_write_ha_stateasync_write_ha_state()