1 """Component providing binary sensors for UniFi Protect."""
3 from __future__
import annotations
5 from collections.abc
import Sequence
8 from uiprotect.data
import (
13 ProtectAdoptableDeviceModel,
15 SmartDetectObjectType,
17 from uiprotect.data.nvr
import UOSDisk
20 BinarySensorDeviceClass,
22 BinarySensorEntityDescription,
28 from .data
import ProtectData, ProtectDeviceType, UFPConfigEntry
34 ProtectEntityDescription,
38 async_all_device_entities,
44 @dataclasses.dataclass(frozen=True, kw_only=True)
46 ProtectEntityDescription, BinarySensorEntityDescription
48 """Describes UniFi Protect Binary Sensor entity."""
51 @dataclasses.dataclass(frozen=True, kw_only=True)
53 ProtectEventMixin, BinarySensorEntityDescription
55 """Describes UniFi Protect Binary Sensor entity."""
58 MOUNT_DEVICE_CLASS_MAP = {
59 MountType.GARAGE: BinarySensorDeviceClass.GARAGE_DOOR,
60 MountType.WINDOW: BinarySensorDeviceClass.WINDOW,
61 MountType.DOOR: BinarySensorDeviceClass.DOOR,
65 CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
69 icon=
"mdi:brightness-6",
76 entity_registry_enabled_default=
False,
77 entity_category=EntityCategory.DIAGNOSTIC,
78 ufp_value=
"is_ssh_enabled",
79 ufp_perm=PermRequired.NO_WRITE,
83 name=
"Status light 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,
93 icon=
"mdi:brightness-7",
94 entity_category=EntityCategory.DIAGNOSTIC,
95 ufp_required_field=
"feature_flags.has_hdr",
97 ufp_perm=PermRequired.NO_WRITE,
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,
110 name=
"System sounds",
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,
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,
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,
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,
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,
151 key=
"motion_enabled",
152 name=
"Detections: motion",
154 ufp_value=
"recording_settings.enable_motion_detection",
155 ufp_perm=PermRequired.NO_WRITE,
159 name=
"Detections: person",
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,
168 name=
"Detections: vehicle",
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,
177 name=
"Detections: animal",
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,
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,
194 key=
"smart_licenseplate",
195 name=
"Detections: license plate",
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,
204 name=
"Detections: smoke",
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,
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,
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,
230 key=
"smart_baby_cry",
231 name=
"Detections: baby cry",
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,
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,
249 name=
"Detections: barking",
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,
257 key=
"smart_car_alarm",
258 name=
"Detections: car alarm",
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,
266 key=
"smart_car_horn",
267 name=
"Detections: car horn",
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,
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,
285 name=
"Tracking: person",
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,
294 LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
298 icon=
"mdi:brightness-6",
303 name=
"Motion detected",
304 device_class=BinarySensorDeviceClass.MOTION,
305 ufp_value=
"is_pir_motion_detected",
310 icon=
"mdi:spotlight-beam",
311 entity_category=EntityCategory.DIAGNOSTIC,
312 ufp_value=
"is_light_on",
313 ufp_perm=PermRequired.NO_WRITE,
319 entity_registry_enabled_default=
False,
320 entity_category=EntityCategory.DIAGNOSTIC,
321 ufp_value=
"is_ssh_enabled",
322 ufp_perm=PermRequired.NO_WRITE,
326 name=
"Status light on",
328 entity_category=EntityCategory.DIAGNOSTIC,
329 ufp_value=
"light_device_settings.is_indicator_enabled",
330 ufp_perm=PermRequired.NO_WRITE,
336 MOUNTABLE_SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
340 device_class=BinarySensorDeviceClass.DOOR,
341 ufp_value=
"is_opened",
342 ufp_enabled=
"is_contact_sensor_enabled",
346 SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
350 device_class=BinarySensorDeviceClass.MOISTURE,
351 ufp_value=
"is_leak_detected",
352 ufp_enabled=
"is_leak_sensor_enabled",
357 device_class=BinarySensorDeviceClass.BATTERY,
358 entity_category=EntityCategory.DIAGNOSTIC,
359 ufp_value=
"battery_status.is_low",
363 name=
"Motion detected",
364 device_class=BinarySensorDeviceClass.MOTION,
365 ufp_value=
"is_motion_detected",
366 ufp_enabled=
"is_motion_sensor_enabled",
370 name=
"Tampering detected",
371 device_class=BinarySensorDeviceClass.TAMPER,
372 ufp_value=
"is_tampering_detected",
376 name=
"Status light on",
378 entity_category=EntityCategory.DIAGNOSTIC,
379 ufp_value=
"led_settings.is_enabled",
380 ufp_perm=PermRequired.NO_WRITE,
383 key=
"motion_enabled",
384 name=
"Motion detection",
386 entity_category=EntityCategory.DIAGNOSTIC,
387 ufp_value=
"motion_settings.is_enabled",
388 ufp_perm=PermRequired.NO_WRITE,
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,
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,
409 icon=
"mdi:brightness-5",
410 entity_category=EntityCategory.DIAGNOSTIC,
411 ufp_value=
"light_settings.is_enabled",
412 ufp_perm=PermRequired.NO_WRITE,
416 name=
"Alarm sound detection",
417 entity_category=EntityCategory.DIAGNOSTIC,
418 ufp_value=
"alarm_settings.is_enabled",
419 ufp_perm=PermRequired.NO_WRITE,
423 EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
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",
435 device_class=BinarySensorDeviceClass.MOTION,
436 ufp_enabled=
"is_motion_detection_on",
437 ufp_event_obj=
"last_motion_event",
441 name=
"Object detected",
443 ufp_required_field=
"feature_flags.has_smart_detect",
444 ufp_event_obj=
"last_smart_detect_event",
445 entity_registry_enabled_default=
False,
448 key=
"smart_obj_person",
449 name=
"Person detected",
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",
457 key=
"smart_obj_vehicle",
458 name=
"Vehicle detected",
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",
466 key=
"smart_obj_animal",
467 name=
"Animal detected",
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",
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",
485 key=
"smart_audio_any",
486 name=
"Audio object detected",
488 ufp_required_field=
"feature_flags.has_smart_detect",
489 ufp_event_obj=
"last_smart_audio_detect_event",
490 entity_registry_enabled_default=
False,
493 key=
"smart_audio_smoke",
494 name=
"Smoke alarm detected",
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",
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,
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",
520 key=
"smart_audio_baby_cry",
521 name=
"Baby cry detected",
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",
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",
538 key=
"smart_audio_bark",
539 name=
"Barking detected",
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",
547 key=
"smart_audio_car_alarm",
548 name=
"Car alarm detected",
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",
556 key=
"smart_audio_car_horn",
557 name=
"Car horn detected",
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",
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",
575 DOORLOCK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
579 device_class=BinarySensorDeviceClass.BATTERY,
580 entity_category=EntityCategory.DIAGNOSTIC,
581 ufp_value=
"battery_status.is_low",
585 name=
"Status light on",
587 entity_category=EntityCategory.DIAGNOSTIC,
588 ufp_value=
"led_settings.is_enabled",
589 ufp_perm=PermRequired.NO_WRITE,
593 VIEWER_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
598 entity_registry_enabled_default=
False,
599 entity_category=EntityCategory.DIAGNOSTIC,
600 ufp_value=
"is_ssh_enabled",
601 ufp_perm=PermRequired.NO_WRITE,
606 DISK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
609 device_class=BinarySensorDeviceClass.PROBLEM,
610 entity_category=EntityCategory.DIAGNOSTIC,
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,
622 _MOUNTABLE_MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = {
623 ModelType.SENSOR: MOUNTABLE_SENSE_SENSORS,
628 ProtectIsOnEntity, ProtectDeviceEntity, BinarySensorEntity
630 """A UniFi Protect Device Binary Sensor."""
632 entity_description: ProtectBinaryEntityDescription
636 """A UniFi Protect Device Binary Sensor that can change device class at runtime."""
639 _state_attrs = (
"_attr_available",
"_attr_is_on",
"_attr_device_class")
646 self.
devicedevice.mount_type, BinarySensorDeviceClass.DOOR
651 """A UniFi Protect NVR Disk Binary Sensor."""
654 entity_description: ProtectBinaryEntityDescription
655 _state_attrs = (
"_attr_available",
"_attr_is_on")
661 description: ProtectBinaryEntityDescription,
664 """Initialize the Binary Sensor."""
667 index = self.
_disk_disk.slot - 1
668 description = dataclasses.replace(
670 key=f
"{description.key}_{index}",
671 name=f
"{disk.type} {disk.slot}",
673 super().
__init__(data, device, description)
678 slot = self.
_disk_disk.slot
680 available = self.
datadata.last_update_success
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
695 """A UniFi Protect Device Binary Sensor for events."""
697 entity_description: ProtectBinaryEventEntityDescription
698 _state_attrs = (
"_attr_available",
"_attr_is_on",
"_attr_extra_state_attributes")
709 prev_event = self.
_event_event
712 if event := description.get_event_obj(device):
719 description.ufp_obj_type
is None
720 or description.has_matching_smart(event)
733 MODEL_DESCRIPTIONS_WITH_CLASS = (
734 (_MODEL_DESCRIPTIONS, ProtectDeviceBinarySensor),
735 (_MOUNTABLE_MODEL_DESCRIPTIONS, MountableProtectDeviceBinarySensor),
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]:
748 for description
in EVENT_SENSORS
749 if description.has_required(device)
757 ) -> list[BaseProtectEntity]:
758 device = data.api.bootstrap.nvr
759 if (ustorage := device.system_info.ustorage)
is None:
763 for disk
in ustorage.disks
764 for description
in DISK_SENSORS
771 entry: UFPConfigEntry,
772 async_add_entities: AddEntitiesCallback,
774 """Set up binary sensors for UniFi Protect integration."""
775 data = entry.runtime_data
778 def _add_new_device(device: ProtectAdoptableDeviceModel) ->
None:
779 entities: list[BaseProtectEntity] = []
780 for model_descriptions, klass
in MODEL_DESCRIPTIONS_WITH_CLASS:
782 data, klass, model_descriptions=model_descriptions, ufp_device=device
784 if device.is_adopted
and isinstance(device, Camera):
788 data.async_subscribe_adopt(_add_new_device)
789 entities: list[BaseProtectEntity] = []
790 for model_descriptions, klass
in MODEL_DESCRIPTIONS_WITH_CLASS:
792 data, klass, model_descriptions=model_descriptions
None _async_update_device_from_protect(self, ProtectDeviceType device)
None _async_update_device_from_protect(self, ProtectDeviceType device)
None __init__(self, ProtectData data, NVR device, ProtectBinaryEntityDescription description, UOSDisk disk)
None _set_event_done(self)
_attr_extra_state_attributes
None _async_update_device_from_protect(self, ProtectDeviceType device)
None _async_event_with_immediate_end(self)
_attr_extra_state_attributes
None _set_event_done(self)
bool _event_already_ended(self, Event|None prev_event, datetime|None prev_event_end)
None _set_event_attrs(self, Event event)
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)