Home Assistant Unofficial Reference 2024.12.1
switch.py
Go to the documentation of this file.
1 """Component providing Switches for UniFi Protect."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Sequence
6 from dataclasses import dataclass
7 from functools import partial
8 from typing import Any
9 
10 from uiprotect.data import (
11  Camera,
12  ModelType,
13  ProtectAdoptableDeviceModel,
14  RecordingMode,
15  VideoMode,
16 )
17 
18 from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
19 from homeassistant.const import EntityCategory
20 from homeassistant.core import HomeAssistant, callback
21 from homeassistant.helpers.entity_platform import AddEntitiesCallback
22 from homeassistant.helpers.restore_state import RestoreEntity
23 
24 from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
25 from .entity import (
26  BaseProtectEntity,
27  PermRequired,
28  ProtectDeviceEntity,
29  ProtectEntityDescription,
30  ProtectIsOnEntity,
31  ProtectNVREntity,
32  ProtectSetableKeysMixin,
33  T,
34  async_all_device_entities,
35 )
36 
37 ATTR_PREV_MIC = "prev_mic_level"
38 ATTR_PREV_RECORD = "prev_record_mode"
39 
40 
41 @dataclass(frozen=True, kw_only=True)
43  ProtectSetableKeysMixin[T], SwitchEntityDescription
44 ):
45  """Describes UniFi Protect Switch entity."""
46 
47 
48 async def _set_highfps(obj: Camera, value: bool) -> None:
49  await obj.set_video_mode(VideoMode.HIGH_FPS if value else VideoMode.DEFAULT)
50 
51 
52 CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
54  key="ssh",
55  name="SSH enabled",
56  icon="mdi:lock",
57  entity_registry_enabled_default=False,
58  entity_category=EntityCategory.CONFIG,
59  ufp_value="is_ssh_enabled",
60  ufp_set_method="set_ssh",
61  ufp_perm=PermRequired.WRITE,
62  ),
64  key="status_light",
65  name="Status light on",
66  icon="mdi:led-on",
67  entity_category=EntityCategory.CONFIG,
68  ufp_required_field="feature_flags.has_led_status",
69  ufp_value="led_settings.is_enabled",
70  ufp_set_method="set_status_light",
71  ufp_perm=PermRequired.WRITE,
72  ),
74  key="hdr_mode",
75  name="HDR mode",
76  icon="mdi:brightness-7",
77  entity_category=EntityCategory.CONFIG,
78  entity_registry_enabled_default=False,
79  ufp_required_field="feature_flags.has_hdr",
80  ufp_value="hdr_mode",
81  ufp_set_method="set_hdr",
82  ufp_perm=PermRequired.WRITE,
83  ),
84  ProtectSwitchEntityDescription[Camera](
85  key="high_fps",
86  name="High FPS",
87  icon="mdi:video-high-definition",
88  entity_category=EntityCategory.CONFIG,
89  ufp_required_field="feature_flags.has_highfps",
90  ufp_value="is_high_fps_enabled",
91  ufp_set_method_fn=_set_highfps,
92  ufp_perm=PermRequired.WRITE,
93  ),
95  key="system_sounds",
96  name="System sounds",
97  icon="mdi:speaker",
98  entity_category=EntityCategory.CONFIG,
99  ufp_required_field="has_speaker",
100  ufp_value="speaker_settings.are_system_sounds_enabled",
101  ufp_enabled="feature_flags.has_speaker",
102  ufp_set_method="set_system_sounds",
103  ufp_perm=PermRequired.WRITE,
104  ),
106  key="osd_name",
107  name="Overlay: show name",
108  icon="mdi:fullscreen",
109  entity_category=EntityCategory.CONFIG,
110  ufp_value="osd_settings.is_name_enabled",
111  ufp_set_method="set_osd_name",
112  ufp_perm=PermRequired.WRITE,
113  ),
115  key="osd_date",
116  name="Overlay: show date",
117  icon="mdi:fullscreen",
118  entity_category=EntityCategory.CONFIG,
119  ufp_value="osd_settings.is_date_enabled",
120  ufp_set_method="set_osd_date",
121  ufp_perm=PermRequired.WRITE,
122  ),
124  key="osd_logo",
125  name="Overlay: show logo",
126  icon="mdi:fullscreen",
127  entity_category=EntityCategory.CONFIG,
128  ufp_value="osd_settings.is_logo_enabled",
129  ufp_set_method="set_osd_logo",
130  ufp_perm=PermRequired.WRITE,
131  ),
133  key="osd_bitrate",
134  name="Overlay: show nerd mode",
135  icon="mdi:fullscreen",
136  entity_category=EntityCategory.CONFIG,
137  ufp_value="osd_settings.is_debug_enabled",
138  ufp_set_method="set_osd_bitrate",
139  ufp_perm=PermRequired.WRITE,
140  ),
142  key="color_night_vision",
143  name="Color night vision",
144  icon="mdi:light-flood-down",
145  entity_category=EntityCategory.CONFIG,
146  ufp_required_field="has_color_night_vision",
147  ufp_value="isp_settings.is_color_night_vision_enabled",
148  ufp_set_method="set_color_night_vision",
149  ufp_perm=PermRequired.WRITE,
150  ),
152  key="motion",
153  name="Detections: motion",
154  icon="mdi:run-fast",
155  entity_category=EntityCategory.CONFIG,
156  ufp_value="recording_settings.enable_motion_detection",
157  ufp_enabled="is_recording_enabled",
158  ufp_set_method="set_motion_detection",
159  ufp_perm=PermRequired.WRITE,
160  ),
162  key="smart_person",
163  name="Detections: person",
164  icon="mdi:walk",
165  entity_category=EntityCategory.CONFIG,
166  ufp_required_field="can_detect_person",
167  ufp_value="is_person_detection_on",
168  ufp_enabled="is_recording_enabled",
169  ufp_set_method="set_person_detection",
170  ufp_perm=PermRequired.WRITE,
171  ),
173  key="smart_vehicle",
174  name="Detections: vehicle",
175  icon="mdi:car",
176  entity_category=EntityCategory.CONFIG,
177  ufp_required_field="can_detect_vehicle",
178  ufp_value="is_vehicle_detection_on",
179  ufp_enabled="is_recording_enabled",
180  ufp_set_method="set_vehicle_detection",
181  ufp_perm=PermRequired.WRITE,
182  ),
184  key="smart_animal",
185  name="Detections: animal",
186  icon="mdi:paw",
187  entity_category=EntityCategory.CONFIG,
188  ufp_required_field="can_detect_animal",
189  ufp_value="is_animal_detection_on",
190  ufp_enabled="is_recording_enabled",
191  ufp_set_method="set_animal_detection",
192  ufp_perm=PermRequired.WRITE,
193  ),
195  key="smart_package",
196  name="Detections: package",
197  icon="mdi:package-variant-closed",
198  entity_category=EntityCategory.CONFIG,
199  ufp_required_field="can_detect_package",
200  ufp_value="is_package_detection_on",
201  ufp_enabled="is_recording_enabled",
202  ufp_set_method="set_package_detection",
203  ufp_perm=PermRequired.WRITE,
204  ),
206  key="smart_licenseplate",
207  name="Detections: license plate",
208  icon="mdi:car",
209  entity_category=EntityCategory.CONFIG,
210  ufp_required_field="can_detect_license_plate",
211  ufp_value="is_license_plate_detection_on",
212  ufp_enabled="is_recording_enabled",
213  ufp_set_method="set_license_plate_detection",
214  ufp_perm=PermRequired.WRITE,
215  ),
217  key="smart_smoke",
218  name="Detections: smoke",
219  icon="mdi:fire",
220  entity_category=EntityCategory.CONFIG,
221  ufp_required_field="can_detect_smoke",
222  ufp_value="is_smoke_detection_on",
223  ufp_enabled="is_recording_enabled",
224  ufp_set_method="set_smoke_detection",
225  ufp_perm=PermRequired.WRITE,
226  ),
228  key="smart_cmonx",
229  name="Detections: CO",
230  icon="mdi:molecule-co",
231  entity_category=EntityCategory.CONFIG,
232  ufp_required_field="can_detect_co",
233  ufp_value="is_co_detection_on",
234  ufp_enabled="is_recording_enabled",
235  ufp_set_method="set_cmonx_detection",
236  ufp_perm=PermRequired.WRITE,
237  ),
239  key="smart_siren",
240  name="Detections: siren",
241  icon="mdi:alarm-bell",
242  entity_category=EntityCategory.CONFIG,
243  ufp_required_field="can_detect_siren",
244  ufp_value="is_siren_detection_on",
245  ufp_enabled="is_recording_enabled",
246  ufp_set_method="set_siren_detection",
247  ufp_perm=PermRequired.WRITE,
248  ),
250  key="smart_baby_cry",
251  name="Detections: baby cry",
252  icon="mdi:cradle",
253  entity_category=EntityCategory.CONFIG,
254  ufp_required_field="can_detect_baby_cry",
255  ufp_value="is_baby_cry_detection_on",
256  ufp_enabled="is_recording_enabled",
257  ufp_set_method="set_baby_cry_detection",
258  ufp_perm=PermRequired.WRITE,
259  ),
261  key="smart_speak",
262  name="Detections: speaking",
263  icon="mdi:account-voice",
264  entity_category=EntityCategory.CONFIG,
265  ufp_required_field="can_detect_speaking",
266  ufp_value="is_speaking_detection_on",
267  ufp_enabled="is_recording_enabled",
268  ufp_set_method="set_speaking_detection",
269  ufp_perm=PermRequired.WRITE,
270  ),
272  key="smart_bark",
273  name="Detections: barking",
274  icon="mdi:dog",
275  entity_category=EntityCategory.CONFIG,
276  ufp_required_field="can_detect_bark",
277  ufp_value="is_bark_detection_on",
278  ufp_enabled="is_recording_enabled",
279  ufp_set_method="set_bark_detection",
280  ufp_perm=PermRequired.WRITE,
281  ),
283  key="smart_car_alarm",
284  name="Detections: car alarm",
285  icon="mdi:car",
286  entity_category=EntityCategory.CONFIG,
287  ufp_required_field="can_detect_car_alarm",
288  ufp_value="is_car_alarm_detection_on",
289  ufp_enabled="is_recording_enabled",
290  ufp_set_method="set_car_alarm_detection",
291  ufp_perm=PermRequired.WRITE,
292  ),
294  key="smart_car_horn",
295  name="Detections: car horn",
296  icon="mdi:bugle",
297  entity_category=EntityCategory.CONFIG,
298  ufp_required_field="can_detect_car_horn",
299  ufp_value="is_car_horn_detection_on",
300  ufp_enabled="is_recording_enabled",
301  ufp_set_method="set_car_horn_detection",
302  ufp_perm=PermRequired.WRITE,
303  ),
305  key="smart_glass_break",
306  name="Detections: glass break",
307  icon="mdi:glass-fragile",
308  entity_category=EntityCategory.CONFIG,
309  ufp_required_field="can_detect_glass_break",
310  ufp_value="is_glass_break_detection_on",
311  ufp_enabled="is_recording_enabled",
312  ufp_set_method="set_glass_break_detection",
313  ufp_perm=PermRequired.WRITE,
314  ),
316  key="track_person",
317  name="Tracking: person",
318  icon="mdi:walk",
319  entity_category=EntityCategory.CONFIG,
320  ufp_required_field="feature_flags.is_ptz",
321  ufp_value="is_person_tracking_enabled",
322  ufp_set_method="set_person_track",
323  ufp_perm=PermRequired.WRITE,
324  ),
325 )
326 
327 PRIVACY_MODE_SWITCH = ProtectSwitchEntityDescription[Camera](
328  key="privacy_mode",
329  name="Privacy mode",
330  icon="mdi:eye-settings",
331  entity_category=EntityCategory.CONFIG,
332  ufp_required_field="feature_flags.has_privacy_mask",
333  ufp_value="is_privacy_on",
334  ufp_perm=PermRequired.WRITE,
335 )
336 
337 SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
339  key="status_light",
340  name="Status light on",
341  icon="mdi:led-on",
342  entity_category=EntityCategory.CONFIG,
343  ufp_value="led_settings.is_enabled",
344  ufp_set_method="set_status_light",
345  ufp_perm=PermRequired.WRITE,
346  ),
348  key="motion",
349  name="Motion detection",
350  icon="mdi:walk",
351  entity_category=EntityCategory.CONFIG,
352  ufp_value="motion_settings.is_enabled",
353  ufp_set_method="set_motion_status",
354  ufp_perm=PermRequired.WRITE,
355  ),
357  key="temperature",
358  name="Temperature sensor",
359  icon="mdi:thermometer",
360  entity_category=EntityCategory.CONFIG,
361  ufp_value="temperature_settings.is_enabled",
362  ufp_set_method="set_temperature_status",
363  ufp_perm=PermRequired.WRITE,
364  ),
366  key="humidity",
367  name="Humidity sensor",
368  icon="mdi:water-percent",
369  entity_category=EntityCategory.CONFIG,
370  ufp_value="humidity_settings.is_enabled",
371  ufp_set_method="set_humidity_status",
372  ufp_perm=PermRequired.WRITE,
373  ),
375  key="light",
376  name="Light sensor",
377  icon="mdi:brightness-5",
378  entity_category=EntityCategory.CONFIG,
379  ufp_value="light_settings.is_enabled",
380  ufp_set_method="set_light_status",
381  ufp_perm=PermRequired.WRITE,
382  ),
384  key="alarm",
385  name="Alarm sound detection",
386  entity_category=EntityCategory.CONFIG,
387  ufp_value="alarm_settings.is_enabled",
388  ufp_set_method="set_alarm_status",
389  ufp_perm=PermRequired.WRITE,
390  ),
391 )
392 
393 
394 LIGHT_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
396  key="ssh",
397  name="SSH enabled",
398  icon="mdi:lock",
399  entity_registry_enabled_default=False,
400  entity_category=EntityCategory.CONFIG,
401  ufp_value="is_ssh_enabled",
402  ufp_set_method="set_ssh",
403  ufp_perm=PermRequired.WRITE,
404  ),
406  key="status_light",
407  name="Status light on",
408  icon="mdi:led-on",
409  entity_category=EntityCategory.CONFIG,
410  ufp_value="light_device_settings.is_indicator_enabled",
411  ufp_set_method="set_status_light",
412  ufp_perm=PermRequired.WRITE,
413  ),
414 )
415 
416 DOORLOCK_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
418  key="status_light",
419  name="Status light on",
420  icon="mdi:led-on",
421  entity_category=EntityCategory.CONFIG,
422  ufp_value="led_settings.is_enabled",
423  ufp_set_method="set_status_light",
424  ufp_perm=PermRequired.WRITE,
425  ),
426 )
427 
428 VIEWER_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
430  key="ssh",
431  name="SSH enabled",
432  icon="mdi:lock",
433  entity_registry_enabled_default=False,
434  entity_category=EntityCategory.CONFIG,
435  ufp_value="is_ssh_enabled",
436  ufp_set_method="set_ssh",
437  ufp_perm=PermRequired.WRITE,
438  ),
439 )
440 
441 NVR_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
443  key="analytics_enabled",
444  name="Analytics enabled",
445  icon="mdi:google-analytics",
446  entity_category=EntityCategory.CONFIG,
447  ufp_value="is_analytics_enabled",
448  ufp_set_method="set_anonymous_analytics",
449  ),
451  key="insights_enabled",
452  name="Insights enabled",
453  icon="mdi:magnify",
454  entity_category=EntityCategory.CONFIG,
455  ufp_value="is_insights_enabled",
456  ufp_set_method="set_insights",
457  ),
458 )
459 
460 _MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = {
461  ModelType.CAMERA: CAMERA_SWITCHES,
462  ModelType.LIGHT: LIGHT_SWITCHES,
463  ModelType.SENSOR: SENSE_SWITCHES,
464  ModelType.DOORLOCK: DOORLOCK_SWITCHES,
465  ModelType.VIEWPORT: VIEWER_SWITCHES,
466 }
467 
468 _PRIVACY_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = {
469  ModelType.CAMERA: [PRIVACY_MODE_SWITCH]
470 }
471 
472 
474  """Base class for UniFi Protect Switch."""
475 
476  entity_description: ProtectSwitchEntityDescription
477 
478  async def async_turn_on(self, **kwargs: Any) -> None:
479  """Turn the device on."""
480  await self.entity_descriptionentity_description.ufp_set(self.devicedevice, True)
481 
482  async def async_turn_off(self, **kwargs: Any) -> None:
483  """Turn the device off."""
484  await self.entity_descriptionentity_description.ufp_set(self.devicedevice, False)
485 
486 
488  """A UniFi Protect Switch."""
489 
490  entity_description: ProtectSwitchEntityDescription
491 
492 
494  """A UniFi Protect NVR Switch."""
495 
496  entity_description: ProtectSwitchEntityDescription
497 
498 
500  """A UniFi Protect Switch."""
501 
502  device: Camera
503  entity_description: ProtectSwitchEntityDescription
504 
505  def __init__(
506  self,
507  data: ProtectData,
508  device: Camera,
509  description: ProtectSwitchEntityDescription,
510  ) -> None:
511  """Initialize an UniFi Protect Switch."""
512  super().__init__(data, device, description)
513  if device.is_privacy_on:
514  extra_state = self.extra_state_attributesextra_state_attributes or {}
515  self._previous_mic_level_previous_mic_level = extra_state.get(ATTR_PREV_MIC, 100)
516  self._previous_record_mode_previous_record_mode = extra_state.get(
517  ATTR_PREV_RECORD, RecordingMode.ALWAYS
518  )
519  else:
520  self._previous_mic_level_previous_mic_level = device.mic_volume
521  self._previous_record_mode_previous_record_mode = device.recording_settings.mode
522 
523  @callback
524  def _update_previous_attr(self) -> None:
525  if self.is_onis_on:
526  self._attr_extra_state_attributes_attr_extra_state_attributes = {
527  ATTR_PREV_MIC: self._previous_mic_level_previous_mic_level,
528  ATTR_PREV_RECORD: self._previous_record_mode_previous_record_mode,
529  }
530  else:
531  self._attr_extra_state_attributes_attr_extra_state_attributes = {}
532 
533  @callback
534  def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
535  super()._async_update_device_from_protect(device)
536  # do not add extra state attribute on initialize
537  if self.entity_identity_id:
538  self._update_previous_attr_update_previous_attr()
539 
540  async def async_turn_on(self, **kwargs: Any) -> None:
541  """Turn the device on."""
542  self._previous_mic_level_previous_mic_level = self.devicedevice.mic_volume
543  self._previous_record_mode_previous_record_mode = self.devicedevice.recording_settings.mode
544  await self.devicedevice.set_privacy(True, 0, RecordingMode.NEVER)
545 
546  async def async_turn_off(self, **kwargs: Any) -> None:
547  """Turn the device off."""
548  extra_state = self.extra_state_attributesextra_state_attributes or {}
549  prev_mic = extra_state.get(ATTR_PREV_MIC, self._previous_mic_level_previous_mic_level)
550  prev_record = extra_state.get(ATTR_PREV_RECORD, self._previous_record_mode_previous_record_mode)
551  await self.devicedevice.set_privacy(False, prev_mic, prev_record)
552 
553  async def async_added_to_hass(self) -> None:
554  """Restore extra state attributes on startp up."""
555  await super().async_added_to_hass()
556  if not (last_state := await self.async_get_last_stateasync_get_last_state()):
557  return
558  last_attrs = last_state.attributes
559  self._previous_mic_level_previous_mic_level = last_attrs.get(
560  ATTR_PREV_MIC, self._previous_mic_level_previous_mic_level
561  )
562  self._previous_record_mode_previous_record_mode = last_attrs.get(
563  ATTR_PREV_RECORD, self._previous_record_mode_previous_record_mode
564  )
565  self._update_previous_attr_update_previous_attr()
566 
567 
569  hass: HomeAssistant,
570  entry: UFPConfigEntry,
571  async_add_entities: AddEntitiesCallback,
572 ) -> None:
573  """Set up sensors for UniFi Protect integration."""
574  data = entry.runtime_data
575 
576  @callback
577  def _add_new_device(device: ProtectAdoptableDeviceModel) -> None:
578  _make_entities = partial(async_all_device_entities, data, ufp_device=device)
579  entities: list[BaseProtectEntity] = []
580  entities += _make_entities(ProtectSwitch, _MODEL_DESCRIPTIONS)
581  entities += _make_entities(ProtectPrivacyModeSwitch, _PRIVACY_DESCRIPTIONS)
582  async_add_entities(entities)
583 
584  _make_entities = partial(async_all_device_entities, data)
585  data.async_subscribe_adopt(_add_new_device)
586  entities: list[BaseProtectEntity] = []
587  entities += _make_entities(ProtectSwitch, _MODEL_DESCRIPTIONS)
588  entities += _make_entities(ProtectPrivacyModeSwitch, _PRIVACY_DESCRIPTIONS)
589  bootstrap = data.api.bootstrap
590  nvr = bootstrap.nvr
591  if nvr.can_write(bootstrap.auth_user) and nvr.is_insights_enabled is not None:
592  entities.extend(
593  ProtectNVRSwitch(data, device=nvr, description=switch)
594  for switch in NVR_SWITCHES
595  )
596  async_add_entities(entities)
None _async_update_device_from_protect(self, ProtectDeviceType device)
Definition: switch.py:534
None __init__(self, ProtectData data, Camera device, ProtectSwitchEntityDescription description)
Definition: switch.py:510
Mapping[str, Any]|None extra_state_attributes(self)
Definition: entity.py:787
None async_turn_off(self, **Any kwargs)
Definition: switch.py:90
None async_setup_entry(HomeAssistant hass, UFPConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: switch.py:572
None _set_highfps(Camera obj, bool value)
Definition: switch.py:48