Home Assistant Unofficial Reference 2024.12.1
switch.py
Go to the documentation of this file.
1 """Component providing support for Reolink switch 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 Chime, Host
10 from reolink_aio.exceptions import ReolinkError
11 
12 from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
13 from homeassistant.const import EntityCategory
14 from homeassistant.core import HomeAssistant
15 from homeassistant.exceptions import HomeAssistantError
16 from homeassistant.helpers import entity_registry as er, issue_registry as ir
17 from homeassistant.helpers.entity_platform import AddEntitiesCallback
18 
19 from .const import DOMAIN
20 from .entity import (
21  ReolinkChannelCoordinatorEntity,
22  ReolinkChannelEntityDescription,
23  ReolinkChimeCoordinatorEntity,
24  ReolinkChimeEntityDescription,
25  ReolinkHostCoordinatorEntity,
26  ReolinkHostEntityDescription,
27 )
28 from .util import ReolinkConfigEntry, ReolinkData
29 
30 PARALLEL_UPDATES = 0
31 
32 
33 @dataclass(frozen=True, kw_only=True)
35  SwitchEntityDescription,
36  ReolinkChannelEntityDescription,
37 ):
38  """A class that describes switch entities."""
39 
40  method: Callable[[Host, int, bool], Any]
41  value: Callable[[Host, int], bool | None]
42 
43 
44 @dataclass(frozen=True, kw_only=True)
46  SwitchEntityDescription,
47  ReolinkHostEntityDescription,
48 ):
49  """A class that describes NVR switch entities."""
50 
51  method: Callable[[Host, bool], Any]
52  value: Callable[[Host], bool]
53 
54 
55 @dataclass(frozen=True, kw_only=True)
57  SwitchEntityDescription,
58  ReolinkChimeEntityDescription,
59 ):
60  """A class that describes switch entities for a chime."""
61 
62  method: Callable[[Chime, bool], Any]
63  value: Callable[[Chime], bool | None]
64 
65 
66 SWITCH_ENTITIES = (
68  key="ir_lights",
69  cmd_key="GetIrLights",
70  translation_key="ir_lights",
71  entity_category=EntityCategory.CONFIG,
72  supported=lambda api, ch: api.supported(ch, "ir_lights"),
73  value=lambda api, ch: api.ir_enabled(ch),
74  method=lambda api, ch, value: api.set_ir_lights(ch, value),
75  ),
77  key="record_audio",
78  cmd_key="GetEnc",
79  translation_key="record_audio",
80  entity_category=EntityCategory.CONFIG,
81  supported=lambda api, ch: api.supported(ch, "audio"),
82  value=lambda api, ch: api.audio_record(ch),
83  method=lambda api, ch, value: api.set_audio(ch, value),
84  ),
86  key="siren_on_event",
87  cmd_key="GetAudioAlarm",
88  translation_key="siren_on_event",
89  entity_category=EntityCategory.CONFIG,
90  supported=lambda api, ch: api.supported(ch, "siren"),
91  value=lambda api, ch: api.audio_alarm_enabled(ch),
92  method=lambda api, ch, value: api.set_audio_alarm(ch, value),
93  ),
95  key="auto_tracking",
96  cmd_key="GetAiCfg",
97  translation_key="auto_tracking",
98  entity_category=EntityCategory.CONFIG,
99  supported=lambda api, ch: api.supported(ch, "auto_track"),
100  value=lambda api, ch: api.auto_track_enabled(ch),
101  method=lambda api, ch, value: api.set_auto_tracking(ch, value),
102  ),
104  key="auto_focus",
105  cmd_key="GetAutoFocus",
106  translation_key="auto_focus",
107  entity_category=EntityCategory.CONFIG,
108  supported=lambda api, ch: api.supported(ch, "auto_focus"),
109  value=lambda api, ch: api.autofocus_enabled(ch),
110  method=lambda api, ch, value: api.set_autofocus(ch, value),
111  ),
113  key="gaurd_return",
114  cmd_key="GetPtzGuard",
115  translation_key="guard_return",
116  entity_category=EntityCategory.CONFIG,
117  supported=lambda api, ch: api.supported(ch, "ptz_guard"),
118  value=lambda api, ch: api.ptz_guard_enabled(ch),
119  method=lambda api, ch, value: api.set_ptz_guard(ch, enable=value),
120  ),
122  key="ptz_patrol",
123  translation_key="ptz_patrol",
124  supported=lambda api, ch: api.supported(ch, "ptz_patrol"),
125  value=lambda api, ch: None,
126  method=lambda api, ch, value: api.ctrl_ptz_patrol(ch, value),
127  ),
129  key="email",
130  cmd_key="GetEmail",
131  translation_key="email",
132  entity_category=EntityCategory.CONFIG,
133  supported=lambda api, ch: api.supported(ch, "email") and api.is_nvr,
134  value=lambda api, ch: api.email_enabled(ch),
135  method=lambda api, ch, value: api.set_email(ch, value),
136  ),
138  key="ftp_upload",
139  cmd_key="GetFtp",
140  translation_key="ftp_upload",
141  entity_category=EntityCategory.CONFIG,
142  supported=lambda api, ch: api.supported(ch, "ftp") and api.is_nvr,
143  value=lambda api, ch: api.ftp_enabled(ch),
144  method=lambda api, ch, value: api.set_ftp(ch, value),
145  ),
147  key="push_notifications",
148  cmd_key="GetPush",
149  translation_key="push_notifications",
150  entity_category=EntityCategory.CONFIG,
151  supported=lambda api, ch: api.supported(ch, "push") and api.is_nvr,
152  value=lambda api, ch: api.push_enabled(ch),
153  method=lambda api, ch, value: api.set_push(ch, value),
154  ),
156  key="record",
157  cmd_key="GetRec",
158  translation_key="record",
159  entity_category=EntityCategory.CONFIG,
160  supported=lambda api, ch: api.supported(ch, "recording") and api.is_nvr,
161  value=lambda api, ch: api.recording_enabled(ch),
162  method=lambda api, ch, value: api.set_recording(ch, value),
163  ),
165  key="manual_record",
166  cmd_key="GetManualRec",
167  translation_key="manual_record",
168  entity_category=EntityCategory.CONFIG,
169  supported=lambda api, ch: api.supported(ch, "manual_record"),
170  value=lambda api, ch: api.manual_record_enabled(ch),
171  method=lambda api, ch, value: api.set_manual_record(ch, value),
172  ),
174  key="buzzer",
175  cmd_key="GetBuzzerAlarmV20",
176  translation_key="hub_ringtone_on_event",
177  entity_category=EntityCategory.CONFIG,
178  supported=lambda api, ch: api.supported(ch, "buzzer") and api.is_nvr,
179  value=lambda api, ch: api.buzzer_enabled(ch),
180  method=lambda api, ch, value: api.set_buzzer(ch, value),
181  ),
183  key="doorbell_button_sound",
184  cmd_key="GetAudioCfg",
185  translation_key="doorbell_button_sound",
186  entity_category=EntityCategory.CONFIG,
187  supported=lambda api, ch: api.supported(ch, "doorbell_button_sound"),
188  value=lambda api, ch: api.doorbell_button_sound(ch),
189  method=lambda api, ch, value: api.set_volume(ch, doorbell_button_sound=value),
190  ),
192  key="pir_enabled",
193  cmd_key="GetPirInfo",
194  translation_key="pir_enabled",
195  entity_category=EntityCategory.CONFIG,
196  entity_registry_enabled_default=False,
197  supported=lambda api, ch: api.supported(ch, "PIR"),
198  value=lambda api, ch: api.pir_enabled(ch) is True,
199  method=lambda api, ch, value: api.set_pir(ch, enable=value),
200  ),
202  key="pir_reduce_alarm",
203  cmd_key="GetPirInfo",
204  translation_key="pir_reduce_alarm",
205  entity_category=EntityCategory.CONFIG,
206  entity_registry_enabled_default=False,
207  supported=lambda api, ch: api.supported(ch, "PIR"),
208  value=lambda api, ch: api.pir_reduce_alarm(ch) is True,
209  method=lambda api, ch, value: api.set_pir(ch, reduce_alarm=value),
210  ),
211 )
212 
213 NVR_SWITCH_ENTITIES = (
215  key="email",
216  cmd_key="GetEmail",
217  translation_key="email",
218  entity_category=EntityCategory.CONFIG,
219  supported=lambda api: api.supported(None, "email") and not api.is_hub,
220  value=lambda api: api.email_enabled(),
221  method=lambda api, value: api.set_email(None, value),
222  ),
224  key="ftp_upload",
225  cmd_key="GetFtp",
226  translation_key="ftp_upload",
227  entity_category=EntityCategory.CONFIG,
228  supported=lambda api: api.supported(None, "ftp") and not api.is_hub,
229  value=lambda api: api.ftp_enabled(),
230  method=lambda api, value: api.set_ftp(None, value),
231  ),
233  key="push_notifications",
234  cmd_key="GetPush",
235  translation_key="push_notifications",
236  entity_category=EntityCategory.CONFIG,
237  supported=lambda api: api.supported(None, "push") and not api.is_hub,
238  value=lambda api: api.push_enabled(),
239  method=lambda api, value: api.set_push(None, value),
240  ),
242  key="record",
243  cmd_key="GetRec",
244  translation_key="record",
245  entity_category=EntityCategory.CONFIG,
246  supported=lambda api: api.supported(None, "recording") and not api.is_hub,
247  value=lambda api: api.recording_enabled(),
248  method=lambda api, value: api.set_recording(None, value),
249  ),
251  key="buzzer",
252  cmd_key="GetBuzzerAlarmV20",
253  translation_key="hub_ringtone_on_event",
254  entity_category=EntityCategory.CONFIG,
255  supported=lambda api: api.supported(None, "buzzer") and not api.is_hub,
256  value=lambda api: api.buzzer_enabled(),
257  method=lambda api, value: api.set_buzzer(None, value),
258  ),
259 )
260 
261 CHIME_SWITCH_ENTITIES = (
263  key="chime_led",
264  cmd_key="DingDongOpt",
265  translation_key="led",
266  entity_category=EntityCategory.CONFIG,
267  value=lambda chime: chime.led_state,
268  method=lambda chime, value: chime.set_option(led=value),
269  ),
270 )
271 
272 # Can be removed in HA 2025.2.0
274  key="hdr",
275  cmd_key="GetIsp",
276  translation_key="hdr",
277  entity_category=EntityCategory.CONFIG,
278  entity_registry_enabled_default=False,
279  supported=lambda api, ch: api.supported(ch, "HDR"),
280  value=lambda api, ch: api.HDR_on(ch) is True,
281  method=lambda api, ch, value: api.set_HDR(ch, value),
282 )
283 
284 # Can be removed in HA 2025.4.0
285 DEPRECATED_NVR_SWITCHES = [
287  key="email",
288  cmd_key="GetEmail",
289  translation_key="email",
290  entity_category=EntityCategory.CONFIG,
291  supported=lambda api: api.is_hub,
292  value=lambda api: api.email_enabled(),
293  method=lambda api, value: api.set_email(None, value),
294  ),
296  key="ftp_upload",
297  cmd_key="GetFtp",
298  translation_key="ftp_upload",
299  entity_category=EntityCategory.CONFIG,
300  supported=lambda api: api.is_hub,
301  value=lambda api: api.ftp_enabled(),
302  method=lambda api, value: api.set_ftp(None, value),
303  ),
305  key="push_notifications",
306  cmd_key="GetPush",
307  translation_key="push_notifications",
308  entity_category=EntityCategory.CONFIG,
309  supported=lambda api: api.is_hub,
310  value=lambda api: api.push_enabled(),
311  method=lambda api, value: api.set_push(None, value),
312  ),
314  key="record",
315  cmd_key="GetRec",
316  translation_key="record",
317  entity_category=EntityCategory.CONFIG,
318  supported=lambda api: api.is_hub,
319  value=lambda api: api.recording_enabled(),
320  method=lambda api, value: api.set_recording(None, value),
321  ),
323  key="buzzer",
324  cmd_key="GetBuzzerAlarmV20",
325  translation_key="hub_ringtone_on_event",
326  icon="mdi:room-service",
327  entity_category=EntityCategory.CONFIG,
328  supported=lambda api: api.is_hub,
329  value=lambda api: api.buzzer_enabled(),
330  method=lambda api, value: api.set_buzzer(None, value),
331  ),
332 ]
333 
334 
336  hass: HomeAssistant,
337  config_entry: ReolinkConfigEntry,
338  async_add_entities: AddEntitiesCallback,
339 ) -> None:
340  """Set up a Reolink switch entities."""
341  reolink_data: ReolinkData = config_entry.runtime_data
342 
343  entities: list[
344  ReolinkSwitchEntity | ReolinkNVRSwitchEntity | ReolinkChimeSwitchEntity
345  ] = [
346  ReolinkSwitchEntity(reolink_data, channel, entity_description)
347  for entity_description in SWITCH_ENTITIES
348  for channel in reolink_data.host.api.channels
349  if entity_description.supported(reolink_data.host.api, channel)
350  ]
351  entities.extend(
352  ReolinkNVRSwitchEntity(reolink_data, entity_description)
353  for entity_description in NVR_SWITCH_ENTITIES
354  if entity_description.supported(reolink_data.host.api)
355  )
356  entities.extend(
357  ReolinkChimeSwitchEntity(reolink_data, chime, entity_description)
358  for entity_description in CHIME_SWITCH_ENTITIES
359  for chime in reolink_data.host.api.chime_list
360  )
361 
362  # Can be removed in HA 2025.4.0
363  depricated_dict = {}
364  for desc in DEPRECATED_NVR_SWITCHES:
365  if not desc.supported(reolink_data.host.api):
366  continue
367  depricated_dict[f"{reolink_data.host.unique_id}_{desc.key}"] = desc
368 
369  entity_reg = er.async_get(hass)
370  reg_entities = er.async_entries_for_config_entry(entity_reg, config_entry.entry_id)
371  for entity in reg_entities:
372  # Can be removed in HA 2025.2.0
373  if entity.domain == "switch" and entity.unique_id.endswith("_hdr"):
374  if entity.disabled:
375  entity_reg.async_remove(entity.entity_id)
376  continue
377 
378  ir.async_create_issue(
379  hass,
380  DOMAIN,
381  "hdr_switch_deprecated",
382  is_fixable=False,
383  severity=ir.IssueSeverity.WARNING,
384  translation_key="hdr_switch_deprecated",
385  )
386  entities.extend(
387  ReolinkSwitchEntity(reolink_data, channel, DEPRECATED_HDR)
388  for channel in reolink_data.host.api.channels
389  if DEPRECATED_HDR.supported(reolink_data.host.api, channel)
390  )
391 
392  # Can be removed in HA 2025.4.0
393  if entity.domain == "switch" and entity.unique_id in depricated_dict:
394  if entity.disabled:
395  entity_reg.async_remove(entity.entity_id)
396  continue
397 
398  ir.async_create_issue(
399  hass,
400  DOMAIN,
401  "hub_switch_deprecated",
402  is_fixable=False,
403  severity=ir.IssueSeverity.WARNING,
404  translation_key="hub_switch_deprecated",
405  )
406  entities.append(
407  ReolinkNVRSwitchEntity(reolink_data, depricated_dict[entity.unique_id])
408  )
409 
410  async_add_entities(entities)
411 
412 
414  """Base switch entity class for Reolink IP cameras."""
415 
416  entity_description: ReolinkSwitchEntityDescription
417 
418  def __init__(
419  self,
420  reolink_data: ReolinkData,
421  channel: int,
422  entity_description: ReolinkSwitchEntityDescription,
423  ) -> None:
424  """Initialize Reolink switch entity."""
425  self.entity_descriptionentity_description = entity_description
426  super().__init__(reolink_data, channel)
427 
428  @property
429  def is_on(self) -> bool | None:
430  """Return true if switch is on."""
431  return self.entity_descriptionentity_description.value(self._host_host.api, self._channel_channel)
432 
433  async def async_turn_on(self, **kwargs: Any) -> None:
434  """Turn the entity on."""
435  try:
436  await self.entity_descriptionentity_description.method(self._host_host.api, self._channel_channel, True)
437  except ReolinkError as err:
438  raise HomeAssistantError(err) from err
439  self.async_write_ha_stateasync_write_ha_state()
440 
441  async def async_turn_off(self, **kwargs: Any) -> None:
442  """Turn the entity off."""
443  try:
444  await self.entity_descriptionentity_description.method(self._host_host.api, self._channel_channel, False)
445  except ReolinkError as err:
446  raise HomeAssistantError(err) from err
447  self.async_write_ha_stateasync_write_ha_state()
448 
449 
451  """Switch entity class for Reolink NVR features."""
452 
453  entity_description: ReolinkNVRSwitchEntityDescription
454 
455  def __init__(
456  self,
457  reolink_data: ReolinkData,
458  entity_description: ReolinkNVRSwitchEntityDescription,
459  ) -> None:
460  """Initialize Reolink switch entity."""
461  self.entity_descriptionentity_description = entity_description
462  super().__init__(reolink_data)
463 
464  @property
465  def is_on(self) -> bool:
466  """Return true if switch is on."""
467  return self.entity_descriptionentity_description.value(self._host_host.api)
468 
469  async def async_turn_on(self, **kwargs: Any) -> None:
470  """Turn the entity on."""
471  try:
472  await self.entity_descriptionentity_description.method(self._host_host.api, True)
473  except ReolinkError as err:
474  raise HomeAssistantError(err) from err
475  self.async_write_ha_stateasync_write_ha_state()
476 
477  async def async_turn_off(self, **kwargs: Any) -> None:
478  """Turn the entity off."""
479  try:
480  await self.entity_descriptionentity_description.method(self._host_host.api, False)
481  except ReolinkError as err:
482  raise HomeAssistantError(err) from err
483  self.async_write_ha_stateasync_write_ha_state()
484 
485 
487  """Base switch entity class for a chime."""
488 
489  entity_description: ReolinkChimeSwitchEntityDescription
490 
491  def __init__(
492  self,
493  reolink_data: ReolinkData,
494  chime: Chime,
495  entity_description: ReolinkChimeSwitchEntityDescription,
496  ) -> None:
497  """Initialize Reolink switch entity."""
498  self.entity_descriptionentity_description = entity_description
499  super().__init__(reolink_data, chime)
500 
501  @property
502  def is_on(self) -> bool | None:
503  """Return true if switch is on."""
504  return self.entity_descriptionentity_description.value(self._chime_chime)
505 
506  async def async_turn_on(self, **kwargs: Any) -> None:
507  """Turn the entity on."""
508  try:
509  await self.entity_descriptionentity_description.method(self._chime_chime, True)
510  except ReolinkError as err:
511  raise HomeAssistantError(err) from err
512  self.async_write_ha_stateasync_write_ha_state()
513 
514  async def async_turn_off(self, **kwargs: Any) -> None:
515  """Turn the entity off."""
516  try:
517  await self.entity_descriptionentity_description.method(self._chime_chime, False)
518  except ReolinkError as err:
519  raise HomeAssistantError(err) from err
520  self.async_write_ha_stateasync_write_ha_state()