Home Assistant Unofficial Reference 2024.12.1
select.py
Go to the documentation of this file.
1 """Component providing support for Reolink select entities."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable
6 from dataclasses import dataclass
7 import logging
8 from typing import Any
9 
10 from reolink_aio.api import (
11  BinningModeEnum,
12  Chime,
13  ChimeToneEnum,
14  DayNightEnum,
15  HDREnum,
16  Host,
17  HubToneEnum,
18  SpotlightModeEnum,
19  StatusLedEnum,
20  TrackMethodEnum,
21 )
22 from reolink_aio.exceptions import InvalidParameterError, ReolinkError
23 
24 from homeassistant.components.select import SelectEntity, SelectEntityDescription
25 from homeassistant.const import EntityCategory, UnitOfDataRate, UnitOfFrequency
26 from homeassistant.core import HomeAssistant
27 from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
28 from homeassistant.helpers.entity_platform import AddEntitiesCallback
29 
30 from .entity import (
31  ReolinkChannelCoordinatorEntity,
32  ReolinkChannelEntityDescription,
33  ReolinkChimeCoordinatorEntity,
34  ReolinkChimeEntityDescription,
35 )
36 from .util import ReolinkConfigEntry, ReolinkData
37 
38 _LOGGER = logging.getLogger(__name__)
39 PARALLEL_UPDATES = 0
40 
41 
42 @dataclass(frozen=True, kw_only=True)
44  SelectEntityDescription,
45  ReolinkChannelEntityDescription,
46 ):
47  """A class that describes select entities."""
48 
49  get_options: list[str] | Callable[[Host, int], list[str]]
50  method: Callable[[Host, int, str], Any]
51  value: Callable[[Host, int], str] | None = None
52 
53 
54 @dataclass(frozen=True, kw_only=True)
56  SelectEntityDescription,
57  ReolinkChimeEntityDescription,
58 ):
59  """A class that describes select entities for a chime."""
60 
61  get_options: list[str]
62  method: Callable[[Chime, str], Any]
63  value: Callable[[Chime], str]
64 
65 
66 def _get_quick_reply_id(api: Host, ch: int, mess: str) -> int:
67  """Get the quick reply file id from the message string."""
68  return [k for k, v in api.quick_reply_dict(ch).items() if v == mess][0]
69 
70 
71 SELECT_ENTITIES = (
73  key="floodlight_mode",
74  cmd_key="GetWhiteLed",
75  translation_key="floodlight_mode",
76  entity_category=EntityCategory.CONFIG,
77  get_options=lambda api, ch: api.whiteled_mode_list(ch),
78  supported=lambda api, ch: api.supported(ch, "floodLight"),
79  value=lambda api, ch: SpotlightModeEnum(api.whiteled_mode(ch)).name,
80  method=lambda api, ch, name: api.set_whiteled(ch, mode=name),
81  ),
83  key="day_night_mode",
84  cmd_key="GetIsp",
85  translation_key="day_night_mode",
86  entity_category=EntityCategory.CONFIG,
87  get_options=[mode.name for mode in DayNightEnum],
88  supported=lambda api, ch: api.supported(ch, "dayNight"),
89  value=lambda api, ch: DayNightEnum(api.daynight_state(ch)).name,
90  method=lambda api, ch, name: api.set_daynight(ch, DayNightEnum[name].value),
91  ),
93  key="ptz_preset",
94  translation_key="ptz_preset",
95  get_options=lambda api, ch: list(api.ptz_presets(ch)),
96  supported=lambda api, ch: api.supported(ch, "ptz_presets"),
97  method=lambda api, ch, name: api.set_ptz_command(ch, preset=name),
98  ),
100  key="play_quick_reply_message",
101  translation_key="play_quick_reply_message",
102  get_options=lambda api, ch: list(api.quick_reply_dict(ch).values())[1:],
103  supported=lambda api, ch: api.supported(ch, "play_quick_reply"),
104  method=lambda api, ch, mess: (
105  api.play_quick_reply(ch, file_id=_get_quick_reply_id(api, ch, mess))
106  ),
107  ),
109  key="auto_quick_reply_message",
110  cmd_key="GetAutoReply",
111  translation_key="auto_quick_reply_message",
112  entity_category=EntityCategory.CONFIG,
113  get_options=lambda api, ch: list(api.quick_reply_dict(ch).values()),
114  supported=lambda api, ch: api.supported(ch, "quick_reply"),
115  value=lambda api, ch: api.quick_reply_dict(ch)[api.quick_reply_file(ch)],
116  method=lambda api, ch, mess: (
117  api.set_quick_reply(ch, file_id=_get_quick_reply_id(api, ch, mess))
118  ),
119  ),
121  key="hub_alarm_ringtone",
122  cmd_key="GetDeviceAudioCfg",
123  translation_key="hub_alarm_ringtone",
124  entity_category=EntityCategory.CONFIG,
125  get_options=[mode.name for mode in HubToneEnum],
126  supported=lambda api, ch: api.supported(ch, "hub_audio"),
127  value=lambda api, ch: HubToneEnum(api.hub_alarm_tone_id(ch)).name,
128  method=lambda api, ch, name: (
129  api.set_hub_audio(ch, alarm_tone_id=HubToneEnum[name].value)
130  ),
131  ),
133  key="hub_visitor_ringtone",
134  cmd_key="GetDeviceAudioCfg",
135  translation_key="hub_visitor_ringtone",
136  entity_category=EntityCategory.CONFIG,
137  get_options=[mode.name for mode in HubToneEnum],
138  supported=lambda api, ch: (
139  api.supported(ch, "hub_audio") and api.is_doorbell(ch)
140  ),
141  value=lambda api, ch: HubToneEnum(api.hub_visitor_tone_id(ch)).name,
142  method=lambda api, ch, name: (
143  api.set_hub_audio(ch, visitor_tone_id=HubToneEnum[name].value)
144  ),
145  ),
147  key="auto_track_method",
148  cmd_key="GetAiCfg",
149  translation_key="auto_track_method",
150  entity_category=EntityCategory.CONFIG,
151  get_options=[method.name for method in TrackMethodEnum],
152  supported=lambda api, ch: api.supported(ch, "auto_track_method"),
153  value=lambda api, ch: TrackMethodEnum(api.auto_track_method(ch)).name,
154  method=lambda api, ch, name: api.set_auto_tracking(ch, method=name),
155  ),
157  key="status_led",
158  cmd_key="GetPowerLed",
159  translation_key="doorbell_led",
160  entity_category=EntityCategory.CONFIG,
161  get_options=lambda api, ch: api.doorbell_led_list(ch),
162  supported=lambda api, ch: api.supported(ch, "doorbell_led"),
163  value=lambda api, ch: StatusLedEnum(api.doorbell_led(ch)).name,
164  method=lambda api, ch, name: (
165  api.set_status_led(ch, StatusLedEnum[name].value, doorbell=True)
166  ),
167  ),
169  key="hdr",
170  cmd_key="GetIsp",
171  translation_key="hdr",
172  entity_category=EntityCategory.CONFIG,
173  entity_registry_enabled_default=False,
174  get_options=[method.name for method in HDREnum],
175  supported=lambda api, ch: api.supported(ch, "HDR"),
176  value=lambda api, ch: HDREnum(api.HDR_state(ch)).name,
177  method=lambda api, ch, name: api.set_HDR(ch, HDREnum[name].value),
178  ),
180  key="binning_mode",
181  cmd_key="GetIsp",
182  translation_key="binning_mode",
183  entity_category=EntityCategory.CONFIG,
184  entity_registry_enabled_default=False,
185  get_options=[method.name for method in BinningModeEnum],
186  supported=lambda api, ch: api.supported(ch, "binning_mode"),
187  value=lambda api, ch: BinningModeEnum(api.binning_mode(ch)).name,
188  method=lambda api, ch, name: api.set_binning_mode(
189  ch, BinningModeEnum[name].value
190  ),
191  ),
193  key="main_frame_rate",
194  cmd_key="GetEnc",
195  translation_key="main_frame_rate",
196  entity_category=EntityCategory.CONFIG,
197  entity_registry_enabled_default=False,
198  unit_of_measurement=UnitOfFrequency.HERTZ,
199  get_options=lambda api, ch: [str(v) for v in api.frame_rate_list(ch, "main")],
200  supported=lambda api, ch: api.supported(ch, "frame_rate"),
201  value=lambda api, ch: str(api.frame_rate(ch, "main")),
202  method=lambda api, ch, value: api.set_frame_rate(ch, int(value), "main"),
203  ),
205  key="sub_frame_rate",
206  cmd_key="GetEnc",
207  translation_key="sub_frame_rate",
208  entity_category=EntityCategory.CONFIG,
209  entity_registry_enabled_default=False,
210  unit_of_measurement=UnitOfFrequency.HERTZ,
211  get_options=lambda api, ch: [str(v) for v in api.frame_rate_list(ch, "sub")],
212  supported=lambda api, ch: api.supported(ch, "frame_rate"),
213  value=lambda api, ch: str(api.frame_rate(ch, "sub")),
214  method=lambda api, ch, value: api.set_frame_rate(ch, int(value), "sub"),
215  ),
217  key="main_bit_rate",
218  cmd_key="GetEnc",
219  translation_key="main_bit_rate",
220  entity_category=EntityCategory.CONFIG,
221  entity_registry_enabled_default=False,
222  unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
223  get_options=lambda api, ch: [str(v) for v in api.bit_rate_list(ch, "main")],
224  supported=lambda api, ch: api.supported(ch, "bit_rate"),
225  value=lambda api, ch: str(api.bit_rate(ch, "main")),
226  method=lambda api, ch, value: api.set_bit_rate(ch, int(value), "main"),
227  ),
229  key="sub_bit_rate",
230  cmd_key="GetEnc",
231  translation_key="sub_bit_rate",
232  entity_category=EntityCategory.CONFIG,
233  entity_registry_enabled_default=False,
234  unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
235  get_options=lambda api, ch: [str(v) for v in api.bit_rate_list(ch, "sub")],
236  supported=lambda api, ch: api.supported(ch, "bit_rate"),
237  value=lambda api, ch: str(api.bit_rate(ch, "sub")),
238  method=lambda api, ch, value: api.set_bit_rate(ch, int(value), "sub"),
239  ),
240 )
241 
242 CHIME_SELECT_ENTITIES = (
244  key="motion_tone",
245  cmd_key="GetDingDongCfg",
246  translation_key="motion_tone",
247  entity_category=EntityCategory.CONFIG,
248  supported=lambda chime: "md" in chime.chime_event_types,
249  get_options=[method.name for method in ChimeToneEnum],
250  value=lambda chime: ChimeToneEnum(chime.tone("md")).name,
251  method=lambda chime, name: chime.set_tone("md", ChimeToneEnum[name].value),
252  ),
254  key="people_tone",
255  cmd_key="GetDingDongCfg",
256  translation_key="people_tone",
257  entity_category=EntityCategory.CONFIG,
258  get_options=[method.name for method in ChimeToneEnum],
259  supported=lambda chime: "people" in chime.chime_event_types,
260  value=lambda chime: ChimeToneEnum(chime.tone("people")).name,
261  method=lambda chime, name: chime.set_tone("people", ChimeToneEnum[name].value),
262  ),
264  key="vehicle_tone",
265  cmd_key="GetDingDongCfg",
266  translation_key="vehicle_tone",
267  entity_category=EntityCategory.CONFIG,
268  get_options=[method.name for method in ChimeToneEnum],
269  supported=lambda chime: "vehicle" in chime.chime_event_types,
270  value=lambda chime: ChimeToneEnum(chime.tone("vehicle")).name,
271  method=lambda chime, name: chime.set_tone("vehicle", ChimeToneEnum[name].value),
272  ),
274  key="visitor_tone",
275  cmd_key="GetDingDongCfg",
276  translation_key="visitor_tone",
277  entity_category=EntityCategory.CONFIG,
278  get_options=[method.name for method in ChimeToneEnum],
279  supported=lambda chime: "visitor" in chime.chime_event_types,
280  value=lambda chime: ChimeToneEnum(chime.tone("visitor")).name,
281  method=lambda chime, name: chime.set_tone("visitor", ChimeToneEnum[name].value),
282  ),
284  key="package_tone",
285  cmd_key="GetDingDongCfg",
286  translation_key="package_tone",
287  entity_category=EntityCategory.CONFIG,
288  get_options=[method.name for method in ChimeToneEnum],
289  supported=lambda chime: "package" in chime.chime_event_types,
290  value=lambda chime: ChimeToneEnum(chime.tone("package")).name,
291  method=lambda chime, name: chime.set_tone("package", ChimeToneEnum[name].value),
292  ),
293 )
294 
295 
297  hass: HomeAssistant,
298  config_entry: ReolinkConfigEntry,
299  async_add_entities: AddEntitiesCallback,
300 ) -> None:
301  """Set up a Reolink select entities."""
302  reolink_data: ReolinkData = config_entry.runtime_data
303 
304  entities: list[ReolinkSelectEntity | ReolinkChimeSelectEntity] = [
305  ReolinkSelectEntity(reolink_data, channel, entity_description)
306  for entity_description in SELECT_ENTITIES
307  for channel in reolink_data.host.api.channels
308  if entity_description.supported(reolink_data.host.api, channel)
309  ]
310  entities.extend(
311  ReolinkChimeSelectEntity(reolink_data, chime, entity_description)
312  for entity_description in CHIME_SELECT_ENTITIES
313  for chime in reolink_data.host.api.chime_list
314  if entity_description.supported(chime)
315  )
316  async_add_entities(entities)
317 
318 
320  """Base select entity class for Reolink IP cameras."""
321 
322  entity_description: ReolinkSelectEntityDescription
323 
324  def __init__(
325  self,
326  reolink_data: ReolinkData,
327  channel: int,
328  entity_description: ReolinkSelectEntityDescription,
329  ) -> None:
330  """Initialize Reolink select entity."""
331  self.entity_descriptionentity_description = entity_description
332  super().__init__(reolink_data, channel)
333  self._log_error_log_error = True
334 
335  if callable(entity_description.get_options):
336  self._attr_options_attr_options = entity_description.get_options(self._host_host.api, channel)
337  else:
338  self._attr_options_attr_options = entity_description.get_options
339 
340  @property
341  def current_option(self) -> str | None:
342  """Return the current option."""
343  if self.entity_descriptionentity_description.value is None:
344  return None
345 
346  try:
347  option = self.entity_descriptionentity_description.value(self._host_host.api, self._channel_channel)
348  except (ValueError, KeyError):
349  if self._log_error_log_error:
350  _LOGGER.exception("Reolink '%s' has an unknown value", self.namenamename)
351  self._log_error_log_error = False
352  return None
353 
354  self._log_error_log_error = True
355  return option
356 
357  async def async_select_option(self, option: str) -> None:
358  """Change the selected option."""
359  try:
360  await self.entity_descriptionentity_description.method(self._host_host.api, self._channel_channel, option)
361  except InvalidParameterError as err:
362  raise ServiceValidationError(err) from err
363  except ReolinkError as err:
364  raise HomeAssistantError(err) from err
365  self.async_write_ha_stateasync_write_ha_state()
366 
367 
369  """Base select entity class for Reolink IP cameras."""
370 
371  entity_description: ReolinkChimeSelectEntityDescription
372 
373  def __init__(
374  self,
375  reolink_data: ReolinkData,
376  chime: Chime,
377  entity_description: ReolinkChimeSelectEntityDescription,
378  ) -> None:
379  """Initialize Reolink select entity for a chime."""
380  self.entity_descriptionentity_description = entity_description
381  super().__init__(reolink_data, chime)
382  self._log_error_log_error = True
383  self._attr_options_attr_options = entity_description.get_options
384 
385  @property
386  def current_option(self) -> str | None:
387  """Return the current option."""
388  try:
389  option = self.entity_descriptionentity_description.value(self._chime_chime)
390  except (ValueError, KeyError):
391  if self._log_error_log_error:
392  _LOGGER.exception("Reolink '%s' has an unknown value", self.namenamename)
393  self._log_error_log_error = False
394  return None
395 
396  self._log_error_log_error = True
397  return option
398 
399  async def async_select_option(self, option: str) -> None:
400  """Change the selected option."""
401  try:
402  await self.entity_descriptionentity_description.method(self._chime_chime, option)
403  except InvalidParameterError as err:
404  raise ServiceValidationError(err) from err
405  except ReolinkError as err:
406  raise HomeAssistantError(err) from err
407  self.async_write_ha_stateasync_write_ha_state()
str|UndefinedType|None name(self)
Definition: entity.py:738