Home Assistant Unofficial Reference 2024.12.1
binary_sensor.py
Go to the documentation of this file.
1 """Component providing binary sensors for UniFi Protect."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Sequence
6 import dataclasses
7 
8 from uiprotect.data import (
9  NVR,
10  Camera,
11  ModelType,
12  MountType,
13  ProtectAdoptableDeviceModel,
14  Sensor,
15  SmartDetectObjectType,
16 )
17 from uiprotect.data.nvr import UOSDisk
18 
20  BinarySensorDeviceClass,
21  BinarySensorEntity,
22  BinarySensorEntityDescription,
23 )
24 from homeassistant.const import EntityCategory
25 from homeassistant.core import HomeAssistant, callback
26 from homeassistant.helpers.entity_platform import AddEntitiesCallback
27 
28 from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
29 from .entity import (
30  BaseProtectEntity,
31  EventEntityMixin,
32  PermRequired,
33  ProtectDeviceEntity,
34  ProtectEntityDescription,
35  ProtectEventMixin,
36  ProtectIsOnEntity,
37  ProtectNVREntity,
38  async_all_device_entities,
39 )
40 
41 _KEY_DOOR = "door"
42 
43 
44 @dataclasses.dataclass(frozen=True, kw_only=True)
46  ProtectEntityDescription, BinarySensorEntityDescription
47 ):
48  """Describes UniFi Protect Binary Sensor entity."""
49 
50 
51 @dataclasses.dataclass(frozen=True, kw_only=True)
53  ProtectEventMixin, BinarySensorEntityDescription
54 ):
55  """Describes UniFi Protect Binary Sensor entity."""
56 
57 
58 MOUNT_DEVICE_CLASS_MAP = {
59  MountType.GARAGE: BinarySensorDeviceClass.GARAGE_DOOR,
60  MountType.WINDOW: BinarySensorDeviceClass.WINDOW,
61  MountType.DOOR: BinarySensorDeviceClass.DOOR,
62 }
63 
64 
65 CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
67  key="dark",
68  name="Is dark",
69  icon="mdi:brightness-6",
70  ufp_value="is_dark",
71  ),
73  key="ssh",
74  name="SSH enabled",
75  icon="mdi:lock",
76  entity_registry_enabled_default=False,
77  entity_category=EntityCategory.DIAGNOSTIC,
78  ufp_value="is_ssh_enabled",
79  ufp_perm=PermRequired.NO_WRITE,
80  ),
82  key="status_light",
83  name="Status light on",
84  icon="mdi:led-on",
85  entity_category=EntityCategory.DIAGNOSTIC,
86  ufp_required_field="feature_flags.has_led_status",
87  ufp_value="led_settings.is_enabled",
88  ufp_perm=PermRequired.NO_WRITE,
89  ),
91  key="hdr_mode",
92  name="HDR mode",
93  icon="mdi:brightness-7",
94  entity_category=EntityCategory.DIAGNOSTIC,
95  ufp_required_field="feature_flags.has_hdr",
96  ufp_value="hdr_mode",
97  ufp_perm=PermRequired.NO_WRITE,
98  ),
100  key="high_fps",
101  name="High FPS",
102  icon="mdi:video-high-definition",
103  entity_category=EntityCategory.DIAGNOSTIC,
104  ufp_required_field="feature_flags.has_highfps",
105  ufp_value="is_high_fps_enabled",
106  ufp_perm=PermRequired.NO_WRITE,
107  ),
109  key="system_sounds",
110  name="System sounds",
111  icon="mdi:speaker",
112  entity_category=EntityCategory.DIAGNOSTIC,
113  ufp_required_field="has_speaker",
114  ufp_value="speaker_settings.are_system_sounds_enabled",
115  ufp_enabled="feature_flags.has_speaker",
116  ufp_perm=PermRequired.NO_WRITE,
117  ),
119  key="osd_name",
120  name="Overlay: show name",
121  icon="mdi:fullscreen",
122  entity_category=EntityCategory.DIAGNOSTIC,
123  ufp_value="osd_settings.is_name_enabled",
124  ufp_perm=PermRequired.NO_WRITE,
125  ),
127  key="osd_date",
128  name="Overlay: show date",
129  icon="mdi:fullscreen",
130  entity_category=EntityCategory.DIAGNOSTIC,
131  ufp_value="osd_settings.is_date_enabled",
132  ufp_perm=PermRequired.NO_WRITE,
133  ),
135  key="osd_logo",
136  name="Overlay: show logo",
137  icon="mdi:fullscreen",
138  entity_category=EntityCategory.DIAGNOSTIC,
139  ufp_value="osd_settings.is_logo_enabled",
140  ufp_perm=PermRequired.NO_WRITE,
141  ),
143  key="osd_bitrate",
144  name="Overlay: show bitrate",
145  icon="mdi:fullscreen",
146  entity_category=EntityCategory.DIAGNOSTIC,
147  ufp_value="osd_settings.is_debug_enabled",
148  ufp_perm=PermRequired.NO_WRITE,
149  ),
151  key="motion_enabled",
152  name="Detections: motion",
153  icon="mdi:run-fast",
154  ufp_value="recording_settings.enable_motion_detection",
155  ufp_perm=PermRequired.NO_WRITE,
156  ),
158  key="smart_person",
159  name="Detections: person",
160  icon="mdi:walk",
161  entity_category=EntityCategory.DIAGNOSTIC,
162  ufp_required_field="can_detect_person",
163  ufp_value="is_person_detection_on",
164  ufp_perm=PermRequired.NO_WRITE,
165  ),
167  key="smart_vehicle",
168  name="Detections: vehicle",
169  icon="mdi:car",
170  entity_category=EntityCategory.DIAGNOSTIC,
171  ufp_required_field="can_detect_vehicle",
172  ufp_value="is_vehicle_detection_on",
173  ufp_perm=PermRequired.NO_WRITE,
174  ),
176  key="smart_animal",
177  name="Detections: animal",
178  icon="mdi:paw",
179  entity_category=EntityCategory.DIAGNOSTIC,
180  ufp_required_field="can_detect_animal",
181  ufp_value="is_animal_detection_on",
182  ufp_perm=PermRequired.NO_WRITE,
183  ),
185  key="smart_package",
186  name="Detections: package",
187  icon="mdi:package-variant-closed",
188  entity_category=EntityCategory.DIAGNOSTIC,
189  ufp_required_field="can_detect_package",
190  ufp_value="is_package_detection_on",
191  ufp_perm=PermRequired.NO_WRITE,
192  ),
194  key="smart_licenseplate",
195  name="Detections: license plate",
196  icon="mdi:car",
197  entity_category=EntityCategory.DIAGNOSTIC,
198  ufp_required_field="can_detect_license_plate",
199  ufp_value="is_license_plate_detection_on",
200  ufp_perm=PermRequired.NO_WRITE,
201  ),
203  key="smart_smoke",
204  name="Detections: smoke",
205  icon="mdi:fire",
206  entity_category=EntityCategory.DIAGNOSTIC,
207  ufp_required_field="can_detect_smoke",
208  ufp_value="is_smoke_detection_on",
209  ufp_perm=PermRequired.NO_WRITE,
210  ),
212  key="smart_cmonx",
213  name="Detections: CO",
214  icon="mdi:molecule-co",
215  entity_category=EntityCategory.DIAGNOSTIC,
216  ufp_required_field="can_detect_co",
217  ufp_value="is_co_detection_on",
218  ufp_perm=PermRequired.NO_WRITE,
219  ),
221  key="smart_siren",
222  name="Detections: siren",
223  icon="mdi:alarm-bell",
224  entity_category=EntityCategory.DIAGNOSTIC,
225  ufp_required_field="can_detect_siren",
226  ufp_value="is_siren_detection_on",
227  ufp_perm=PermRequired.NO_WRITE,
228  ),
230  key="smart_baby_cry",
231  name="Detections: baby cry",
232  icon="mdi:cradle",
233  entity_category=EntityCategory.DIAGNOSTIC,
234  ufp_required_field="can_detect_baby_cry",
235  ufp_value="is_baby_cry_detection_on",
236  ufp_perm=PermRequired.NO_WRITE,
237  ),
239  key="smart_speak",
240  name="Detections: speaking",
241  icon="mdi:account-voice",
242  entity_category=EntityCategory.DIAGNOSTIC,
243  ufp_required_field="can_detect_speaking",
244  ufp_value="is_speaking_detection_on",
245  ufp_perm=PermRequired.NO_WRITE,
246  ),
248  key="smart_bark",
249  name="Detections: barking",
250  icon="mdi:dog",
251  entity_category=EntityCategory.DIAGNOSTIC,
252  ufp_required_field="can_detect_bark",
253  ufp_value="is_bark_detection_on",
254  ufp_perm=PermRequired.NO_WRITE,
255  ),
257  key="smart_car_alarm",
258  name="Detections: car alarm",
259  icon="mdi:car",
260  entity_category=EntityCategory.DIAGNOSTIC,
261  ufp_required_field="can_detect_car_alarm",
262  ufp_value="is_car_alarm_detection_on",
263  ufp_perm=PermRequired.NO_WRITE,
264  ),
266  key="smart_car_horn",
267  name="Detections: car horn",
268  icon="mdi:bugle",
269  entity_category=EntityCategory.DIAGNOSTIC,
270  ufp_required_field="can_detect_car_horn",
271  ufp_value="is_car_horn_detection_on",
272  ufp_perm=PermRequired.NO_WRITE,
273  ),
275  key="smart_glass_break",
276  name="Detections: glass break",
277  icon="mdi:glass-fragile",
278  entity_category=EntityCategory.DIAGNOSTIC,
279  ufp_required_field="can_detect_glass_break",
280  ufp_value="is_glass_break_detection_on",
281  ufp_perm=PermRequired.NO_WRITE,
282  ),
284  key="track_person",
285  name="Tracking: person",
286  icon="mdi:walk",
287  entity_category=EntityCategory.DIAGNOSTIC,
288  ufp_required_field="feature_flags.is_ptz",
289  ufp_value="is_person_tracking_enabled",
290  ufp_perm=PermRequired.NO_WRITE,
291  ),
292 )
293 
294 LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
296  key="dark",
297  name="Is dark",
298  icon="mdi:brightness-6",
299  ufp_value="is_dark",
300  ),
302  key="motion",
303  name="Motion detected",
304  device_class=BinarySensorDeviceClass.MOTION,
305  ufp_value="is_pir_motion_detected",
306  ),
308  key="light",
309  name="Flood light",
310  icon="mdi:spotlight-beam",
311  entity_category=EntityCategory.DIAGNOSTIC,
312  ufp_value="is_light_on",
313  ufp_perm=PermRequired.NO_WRITE,
314  ),
316  key="ssh",
317  name="SSH enabled",
318  icon="mdi:lock",
319  entity_registry_enabled_default=False,
320  entity_category=EntityCategory.DIAGNOSTIC,
321  ufp_value="is_ssh_enabled",
322  ufp_perm=PermRequired.NO_WRITE,
323  ),
325  key="status_light",
326  name="Status light on",
327  icon="mdi:led-on",
328  entity_category=EntityCategory.DIAGNOSTIC,
329  ufp_value="light_device_settings.is_indicator_enabled",
330  ufp_perm=PermRequired.NO_WRITE,
331  ),
332 )
333 
334 # The mountable sensors can be remounted at run-time which
335 # means they can change their device class at run-time.
336 MOUNTABLE_SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
338  key=_KEY_DOOR,
339  name="Contact",
340  device_class=BinarySensorDeviceClass.DOOR,
341  ufp_value="is_opened",
342  ufp_enabled="is_contact_sensor_enabled",
343  ),
344 )
345 
346 SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
348  key="leak",
349  name="Leak",
350  device_class=BinarySensorDeviceClass.MOISTURE,
351  ufp_value="is_leak_detected",
352  ufp_enabled="is_leak_sensor_enabled",
353  ),
355  key="battery_low",
356  name="Battery low",
357  device_class=BinarySensorDeviceClass.BATTERY,
358  entity_category=EntityCategory.DIAGNOSTIC,
359  ufp_value="battery_status.is_low",
360  ),
362  key="motion",
363  name="Motion detected",
364  device_class=BinarySensorDeviceClass.MOTION,
365  ufp_value="is_motion_detected",
366  ufp_enabled="is_motion_sensor_enabled",
367  ),
369  key="tampering",
370  name="Tampering detected",
371  device_class=BinarySensorDeviceClass.TAMPER,
372  ufp_value="is_tampering_detected",
373  ),
375  key="status_light",
376  name="Status light on",
377  icon="mdi:led-on",
378  entity_category=EntityCategory.DIAGNOSTIC,
379  ufp_value="led_settings.is_enabled",
380  ufp_perm=PermRequired.NO_WRITE,
381  ),
383  key="motion_enabled",
384  name="Motion detection",
385  icon="mdi:walk",
386  entity_category=EntityCategory.DIAGNOSTIC,
387  ufp_value="motion_settings.is_enabled",
388  ufp_perm=PermRequired.NO_WRITE,
389  ),
391  key="temperature",
392  name="Temperature sensor",
393  icon="mdi:thermometer",
394  entity_category=EntityCategory.DIAGNOSTIC,
395  ufp_value="temperature_settings.is_enabled",
396  ufp_perm=PermRequired.NO_WRITE,
397  ),
399  key="humidity",
400  name="Humidity sensor",
401  icon="mdi:water-percent",
402  entity_category=EntityCategory.DIAGNOSTIC,
403  ufp_value="humidity_settings.is_enabled",
404  ufp_perm=PermRequired.NO_WRITE,
405  ),
407  key="light",
408  name="Light sensor",
409  icon="mdi:brightness-5",
410  entity_category=EntityCategory.DIAGNOSTIC,
411  ufp_value="light_settings.is_enabled",
412  ufp_perm=PermRequired.NO_WRITE,
413  ),
415  key="alarm",
416  name="Alarm sound detection",
417  entity_category=EntityCategory.DIAGNOSTIC,
418  ufp_value="alarm_settings.is_enabled",
419  ufp_perm=PermRequired.NO_WRITE,
420  ),
421 )
422 
423 EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
425  key="doorbell",
426  name="Doorbell",
427  device_class=BinarySensorDeviceClass.OCCUPANCY,
428  icon="mdi:doorbell-video",
429  ufp_required_field="feature_flags.is_doorbell",
430  ufp_event_obj="last_ring_event",
431  ),
433  key="motion",
434  name="Motion",
435  device_class=BinarySensorDeviceClass.MOTION,
436  ufp_enabled="is_motion_detection_on",
437  ufp_event_obj="last_motion_event",
438  ),
440  key="smart_obj_any",
441  name="Object detected",
442  icon="mdi:eye",
443  ufp_required_field="feature_flags.has_smart_detect",
444  ufp_event_obj="last_smart_detect_event",
445  entity_registry_enabled_default=False,
446  ),
448  key="smart_obj_person",
449  name="Person detected",
450  icon="mdi:walk",
451  ufp_obj_type=SmartDetectObjectType.PERSON,
452  ufp_required_field="can_detect_person",
453  ufp_enabled="is_person_detection_on",
454  ufp_event_obj="last_person_detect_event",
455  ),
457  key="smart_obj_vehicle",
458  name="Vehicle detected",
459  icon="mdi:car",
460  ufp_obj_type=SmartDetectObjectType.VEHICLE,
461  ufp_required_field="can_detect_vehicle",
462  ufp_enabled="is_vehicle_detection_on",
463  ufp_event_obj="last_vehicle_detect_event",
464  ),
466  key="smart_obj_animal",
467  name="Animal detected",
468  icon="mdi:paw",
469  ufp_obj_type=SmartDetectObjectType.ANIMAL,
470  ufp_required_field="can_detect_animal",
471  ufp_enabled="is_animal_detection_on",
472  ufp_event_obj="last_animal_detect_event",
473  ),
475  key="smart_obj_package",
476  name="Package detected",
477  icon="mdi:package-variant-closed",
478  entity_registry_enabled_default=False,
479  ufp_obj_type=SmartDetectObjectType.PACKAGE,
480  ufp_required_field="can_detect_package",
481  ufp_enabled="is_package_detection_on",
482  ufp_event_obj="last_package_detect_event",
483  ),
485  key="smart_audio_any",
486  name="Audio object detected",
487  icon="mdi:eye",
488  ufp_required_field="feature_flags.has_smart_detect",
489  ufp_event_obj="last_smart_audio_detect_event",
490  entity_registry_enabled_default=False,
491  ),
493  key="smart_audio_smoke",
494  name="Smoke alarm detected",
495  icon="mdi:fire",
496  ufp_obj_type=SmartDetectObjectType.SMOKE,
497  ufp_required_field="can_detect_smoke",
498  ufp_enabled="is_smoke_detection_on",
499  ufp_event_obj="last_smoke_detect_event",
500  ),
502  key="smart_audio_cmonx",
503  name="CO alarm detected",
504  icon="mdi:molecule-co",
505  ufp_required_field="can_detect_co",
506  ufp_enabled="is_co_detection_on",
507  ufp_event_obj="last_cmonx_detect_event",
508  ufp_obj_type=SmartDetectObjectType.CMONX,
509  ),
511  key="smart_audio_siren",
512  name="Siren detected",
513  icon="mdi:alarm-bell",
514  ufp_obj_type=SmartDetectObjectType.SIREN,
515  ufp_required_field="can_detect_siren",
516  ufp_enabled="is_siren_detection_on",
517  ufp_event_obj="last_siren_detect_event",
518  ),
520  key="smart_audio_baby_cry",
521  name="Baby cry detected",
522  icon="mdi:cradle",
523  ufp_obj_type=SmartDetectObjectType.BABY_CRY,
524  ufp_required_field="can_detect_baby_cry",
525  ufp_enabled="is_baby_cry_detection_on",
526  ufp_event_obj="last_baby_cry_detect_event",
527  ),
529  key="smart_audio_speak",
530  name="Speaking detected",
531  icon="mdi:account-voice",
532  ufp_obj_type=SmartDetectObjectType.SPEAK,
533  ufp_required_field="can_detect_speaking",
534  ufp_enabled="is_speaking_detection_on",
535  ufp_event_obj="last_speaking_detect_event",
536  ),
538  key="smart_audio_bark",
539  name="Barking detected",
540  icon="mdi:dog",
541  ufp_obj_type=SmartDetectObjectType.BARK,
542  ufp_required_field="can_detect_bark",
543  ufp_enabled="is_bark_detection_on",
544  ufp_event_obj="last_bark_detect_event",
545  ),
547  key="smart_audio_car_alarm",
548  name="Car alarm detected",
549  icon="mdi:car",
550  ufp_obj_type=SmartDetectObjectType.BURGLAR,
551  ufp_required_field="can_detect_car_alarm",
552  ufp_enabled="is_car_alarm_detection_on",
553  ufp_event_obj="last_car_alarm_detect_event",
554  ),
556  key="smart_audio_car_horn",
557  name="Car horn detected",
558  icon="mdi:bugle",
559  ufp_obj_type=SmartDetectObjectType.CAR_HORN,
560  ufp_required_field="can_detect_car_horn",
561  ufp_enabled="is_car_horn_detection_on",
562  ufp_event_obj="last_car_horn_detect_event",
563  ),
565  key="smart_audio_glass_break",
566  name="Glass break detected",
567  icon="mdi:glass-fragile",
568  ufp_obj_type=SmartDetectObjectType.GLASS_BREAK,
569  ufp_required_field="can_detect_glass_break",
570  ufp_enabled="is_glass_break_detection_on",
571  ufp_event_obj="last_glass_break_detect_event",
572  ),
573 )
574 
575 DOORLOCK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
577  key="battery_low",
578  name="Battery low",
579  device_class=BinarySensorDeviceClass.BATTERY,
580  entity_category=EntityCategory.DIAGNOSTIC,
581  ufp_value="battery_status.is_low",
582  ),
584  key="status_light",
585  name="Status light on",
586  icon="mdi:led-on",
587  entity_category=EntityCategory.DIAGNOSTIC,
588  ufp_value="led_settings.is_enabled",
589  ufp_perm=PermRequired.NO_WRITE,
590  ),
591 )
592 
593 VIEWER_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
595  key="ssh",
596  name="SSH enabled",
597  icon="mdi:lock",
598  entity_registry_enabled_default=False,
599  entity_category=EntityCategory.DIAGNOSTIC,
600  ufp_value="is_ssh_enabled",
601  ufp_perm=PermRequired.NO_WRITE,
602  ),
603 )
604 
605 
606 DISK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
608  key="disk_health",
609  device_class=BinarySensorDeviceClass.PROBLEM,
610  entity_category=EntityCategory.DIAGNOSTIC,
611  ),
612 )
613 
614 _MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = {
615  ModelType.CAMERA: CAMERA_SENSORS,
616  ModelType.LIGHT: LIGHT_SENSORS,
617  ModelType.SENSOR: SENSE_SENSORS,
618  ModelType.DOORLOCK: DOORLOCK_SENSORS,
619  ModelType.VIEWPORT: VIEWER_SENSORS,
620 }
621 
622 _MOUNTABLE_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = {
623  ModelType.SENSOR: MOUNTABLE_SENSE_SENSORS,
624 }
625 
626 
628  ProtectIsOnEntity, ProtectDeviceEntity, BinarySensorEntity
629 ):
630  """A UniFi Protect Device Binary Sensor."""
631 
632  entity_description: ProtectBinaryEntityDescription
633 
634 
636  """A UniFi Protect Device Binary Sensor that can change device class at runtime."""
637 
638  device: Sensor
639  _state_attrs = ("_attr_available", "_attr_is_on", "_attr_device_class")
640 
641  @callback
642  def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
643  super()._async_update_device_from_protect(device)
644  # UP Sense can be any of the 3 contact sensor device classes
645  self._attr_device_class_attr_device_class = MOUNT_DEVICE_CLASS_MAP.get(
646  self.devicedevice.mount_type, BinarySensorDeviceClass.DOOR
647  )
648 
649 
651  """A UniFi Protect NVR Disk Binary Sensor."""
652 
653  _disk: UOSDisk
654  entity_description: ProtectBinaryEntityDescription
655  _state_attrs = ("_attr_available", "_attr_is_on")
656 
657  def __init__(
658  self,
659  data: ProtectData,
660  device: NVR,
661  description: ProtectBinaryEntityDescription,
662  disk: UOSDisk,
663  ) -> None:
664  """Initialize the Binary Sensor."""
665  self._disk_disk = disk
666  # backwards compat with old unique IDs
667  index = self._disk_disk.slot - 1
668  description = dataclasses.replace(
669  description,
670  key=f"{description.key}_{index}",
671  name=f"{disk.type} {disk.slot}",
672  )
673  super().__init__(data, device, description)
674 
675  @callback
676  def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
677  super()._async_update_device_from_protect(device)
678  slot = self._disk_disk.slot
679  self._attr_available_attr_available_attr_available = False
680  available = self.datadata.last_update_success
681 
682  # should not be possible since it would require user to
683  # _downgrade_ to make ustorage disppear
684  assert self.devicedevice.system_info.ustorage is not None
685  for disk in self.devicedevice.system_info.ustorage.disks:
686  if disk.slot == slot:
687  self._disk_disk = disk
688  self._attr_available_attr_available_attr_available = available
689  break
690 
691  self._attr_is_on_attr_is_on = not self._disk_disk.is_healthy
692 
693 
695  """A UniFi Protect Device Binary Sensor for events."""
696 
697  entity_description: ProtectBinaryEventEntityDescription
698  _state_attrs = ("_attr_available", "_attr_is_on", "_attr_extra_state_attributes")
699 
700  @callback
701  def _set_event_done(self) -> None:
702  self._attr_is_on_attr_is_on = False
703  self._attr_extra_state_attributes_attr_extra_state_attributes_attr_extra_state_attributes = {}
704 
705  @callback
706  def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
707  description = self.entity_descriptionentity_description
708 
709  prev_event = self._event_event
710  prev_event_end = self._event_end_event_end
711  super()._async_update_device_from_protect(device)
712  if event := description.get_event_obj(device):
713  self._event_event = event
714  self._event_end_event_end = event.end if event else None
715 
716  if not (
717  event
718  and (
719  description.ufp_obj_type is None
720  or description.has_matching_smart(event)
721  )
722  and not self._event_already_ended_event_already_ended(prev_event, prev_event_end)
723  ):
724  self._set_event_done_set_event_done_set_event_done()
725  return
726 
727  self._attr_is_on_attr_is_on = True
728  self._set_event_attrs_set_event_attrs(event)
729  if event.end:
730  self._async_event_with_immediate_end_async_event_with_immediate_end()
731 
732 
733 MODEL_DESCRIPTIONS_WITH_CLASS = (
734  (_MODEL_DESCRIPTIONS, ProtectDeviceBinarySensor),
735  (_MOUNTABLE_MODEL_DESCRIPTIONS, MountableProtectDeviceBinarySensor),
736 )
737 
738 
739 @callback
741  data: ProtectData,
742  ufp_device: ProtectAdoptableDeviceModel | None = None,
743 ) -> list[ProtectDeviceEntity]:
744  entities: list[ProtectDeviceEntity] = []
745  for device in data.get_cameras() if ufp_device is None else [ufp_device]:
746  entities.extend(
747  ProtectEventBinarySensor(data, device, description)
748  for description in EVENT_SENSORS
749  if description.has_required(device)
750  )
751  return entities
752 
753 
754 @callback
756  data: ProtectData,
757 ) -> list[BaseProtectEntity]:
758  device = data.api.bootstrap.nvr
759  if (ustorage := device.system_info.ustorage) is None:
760  return []
761  return [
762  ProtectDiskBinarySensor(data, device, description, disk)
763  for disk in ustorage.disks
764  for description in DISK_SENSORS
765  if disk.has_disk
766  ]
767 
768 
770  hass: HomeAssistant,
771  entry: UFPConfigEntry,
772  async_add_entities: AddEntitiesCallback,
773 ) -> None:
774  """Set up binary sensors for UniFi Protect integration."""
775  data = entry.runtime_data
776 
777  @callback
778  def _add_new_device(device: ProtectAdoptableDeviceModel) -> None:
779  entities: list[BaseProtectEntity] = []
780  for model_descriptions, klass in MODEL_DESCRIPTIONS_WITH_CLASS:
781  entities += async_all_device_entities(
782  data, klass, model_descriptions=model_descriptions, ufp_device=device
783  )
784  if device.is_adopted and isinstance(device, Camera):
785  entities += _async_event_entities(data, ufp_device=device)
786  async_add_entities(entities)
787 
788  data.async_subscribe_adopt(_add_new_device)
789  entities: list[BaseProtectEntity] = []
790  for model_descriptions, klass in MODEL_DESCRIPTIONS_WITH_CLASS:
791  entities += async_all_device_entities(
792  data, klass, model_descriptions=model_descriptions
793  )
794  entities += _async_event_entities(data)
795  entities += _async_nvr_entities(data)
796  async_add_entities(entities)
None __init__(self, ProtectData data, NVR device, ProtectBinaryEntityDescription description, UOSDisk disk)
bool _event_already_ended(self, Event|None prev_event, datetime|None prev_event_end)
Definition: entity.py:359
None async_setup_entry(HomeAssistant hass, UFPConfigEntry entry, AddEntitiesCallback async_add_entities)
list[ProtectDeviceEntity] _async_event_entities(ProtectData data, ProtectAdoptableDeviceModel|None ufp_device=None)
list[BaseProtectEntity] _async_nvr_entities(ProtectData data)
list[BaseProtectEntity] async_all_device_entities(ProtectData data, type[BaseProtectEntity] klass, dict[ModelType, Sequence[ProtectEntityDescription]]|None model_descriptions=None, Sequence[ProtectEntityDescription]|None all_descs=None, list[ProtectEntityDescription]|None unadopted_descs=None, ProtectAdoptableDeviceModel|None ufp_device=None)
Definition: entity.py:153