Home Assistant Unofficial Reference 2024.12.1
sensor.py
Go to the documentation of this file.
1 """Component providing sensors for UniFi Protect."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Callable, Sequence
6 from dataclasses import dataclass
7 from datetime import datetime
8 from functools import partial
9 import logging
10 from typing import Any
11 
12 from uiprotect.data import (
13  NVR,
14  Camera,
15  Light,
16  ModelType,
17  ProtectAdoptableDeviceModel,
18  ProtectDeviceModel,
19  Sensor,
20  SmartDetectObjectType,
21 )
22 
24  SensorDeviceClass,
25  SensorEntity,
26  SensorEntityDescription,
27  SensorStateClass,
28 )
29 from homeassistant.const import (
30  LIGHT_LUX,
31  PERCENTAGE,
32  SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
33  EntityCategory,
34  UnitOfDataRate,
35  UnitOfElectricPotential,
36  UnitOfInformation,
37  UnitOfTemperature,
38  UnitOfTime,
39 )
40 from homeassistant.core import HomeAssistant, callback
41 from homeassistant.helpers.entity_platform import AddEntitiesCallback
42 
43 from .data import ProtectData, ProtectDeviceType, UFPConfigEntry
44 from .entity import (
45  BaseProtectEntity,
46  EventEntityMixin,
47  PermRequired,
48  ProtectDeviceEntity,
49  ProtectEntityDescription,
50  ProtectEventMixin,
51  ProtectNVREntity,
52  T,
53  async_all_device_entities,
54 )
55 from .utils import async_get_light_motion_current
56 
57 _LOGGER = logging.getLogger(__name__)
58 OBJECT_TYPE_NONE = "none"
59 
60 
61 @dataclass(frozen=True, kw_only=True)
63  ProtectEntityDescription[T], SensorEntityDescription
64 ):
65  """Describes UniFi Protect Sensor entity."""
66 
67  precision: int | None = None
68 
69  def __post_init__(self) -> None:
70  """Ensure values are rounded if precision is set."""
71  super().__post_init__()
72  if precision := self.precision:
73  object.__setattr__(
74  self,
75  "get_ufp_value",
76  partial(self._rounded_value_rounded_value, precision, self.get_ufp_valueget_ufp_value),
77  )
78 
79  def _rounded_value(self, precision: int, getter: Callable[[T], Any], obj: T) -> Any:
80  """Round value to precision if set."""
81  return None if (v := getter(obj)) is None else round(v, precision)
82 
83 
84 @dataclass(frozen=True, kw_only=True)
86  ProtectEventMixin[T], SensorEntityDescription
87 ):
88  """Describes UniFi Protect Sensor entity."""
89 
90 
91 def _get_uptime(obj: ProtectDeviceModel) -> datetime | None:
92  if obj.up_since is None:
93  return None
94 
95  # up_since can vary slightly over time
96  # truncate to ensure no extra state_change events fire
97  return obj.up_since.replace(second=0, microsecond=0)
98 
99 
100 def _get_nvr_recording_capacity(obj: NVR) -> int:
101  if obj.storage_stats.capacity is None:
102  return 0
103 
104  return int(obj.storage_stats.capacity.total_seconds())
105 
106 
107 def _get_nvr_memory(obj: NVR) -> float | None:
108  memory = obj.system_info.memory
109  if memory.available is None or memory.total is None:
110  return None
111  return (1 - memory.available / memory.total) * 100
112 
113 
114 def _get_alarm_sound(obj: Sensor) -> str:
115  alarm_type = OBJECT_TYPE_NONE
116  if (
117  obj.is_alarm_detected
118  and obj.last_alarm_event is not None
119  and obj.last_alarm_event.metadata is not None
120  ):
121  alarm_type = obj.last_alarm_event.metadata.alarm_type or OBJECT_TYPE_NONE
122  return alarm_type.lower()
123 
124 
125 ALL_DEVICES_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
127  key="uptime",
128  name="Uptime",
129  icon="mdi:clock",
130  device_class=SensorDeviceClass.TIMESTAMP,
131  entity_category=EntityCategory.DIAGNOSTIC,
132  entity_registry_enabled_default=False,
133  ufp_value_fn=_get_uptime,
134  ),
136  key="ble_signal",
137  name="Bluetooth signal strength",
138  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
139  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
140  entity_category=EntityCategory.DIAGNOSTIC,
141  entity_registry_enabled_default=False,
142  state_class=SensorStateClass.MEASUREMENT,
143  ufp_value="bluetooth_connection_state.signal_strength",
144  ufp_required_field="bluetooth_connection_state.signal_strength",
145  ),
147  key="phy_rate",
148  name="Link speed",
149  device_class=SensorDeviceClass.DATA_RATE,
150  native_unit_of_measurement=UnitOfDataRate.MEGABITS_PER_SECOND,
151  entity_category=EntityCategory.DIAGNOSTIC,
152  entity_registry_enabled_default=False,
153  state_class=SensorStateClass.MEASUREMENT,
154  ufp_value="wired_connection_state.phy_rate",
155  ufp_required_field="wired_connection_state.phy_rate",
156  ),
158  key="wifi_signal",
159  name="WiFi signal strength",
160  native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
161  device_class=SensorDeviceClass.SIGNAL_STRENGTH,
162  entity_registry_enabled_default=False,
163  entity_category=EntityCategory.DIAGNOSTIC,
164  state_class=SensorStateClass.MEASUREMENT,
165  ufp_value="wifi_connection_state.signal_strength",
166  ufp_required_field="wifi_connection_state.signal_strength",
167  ),
168 )
169 
170 CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
172  key="oldest_recording",
173  name="Oldest recording",
174  device_class=SensorDeviceClass.TIMESTAMP,
175  entity_category=EntityCategory.DIAGNOSTIC,
176  entity_registry_enabled_default=False,
177  ufp_value="stats.video.recording_start",
178  ),
180  key="storage_used",
181  name="Storage used",
182  native_unit_of_measurement=UnitOfInformation.BYTES,
183  device_class=SensorDeviceClass.DATA_SIZE,
184  entity_category=EntityCategory.DIAGNOSTIC,
185  state_class=SensorStateClass.MEASUREMENT,
186  ufp_value="stats.storage.used",
187  suggested_unit_of_measurement=UnitOfInformation.MEGABYTES,
188  suggested_display_precision=2,
189  ),
191  key="write_rate",
192  name="Disk write rate",
193  device_class=SensorDeviceClass.DATA_RATE,
194  native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
195  entity_category=EntityCategory.DIAGNOSTIC,
196  state_class=SensorStateClass.MEASUREMENT,
197  ufp_value="stats.storage.rate_per_second",
198  precision=2,
199  suggested_unit_of_measurement=UnitOfDataRate.MEGABYTES_PER_SECOND,
200  suggested_display_precision=2,
201  ),
203  key="voltage",
204  name="Voltage",
205  device_class=SensorDeviceClass.VOLTAGE,
206  native_unit_of_measurement=UnitOfElectricPotential.VOLT,
207  entity_category=EntityCategory.DIAGNOSTIC,
208  state_class=SensorStateClass.MEASUREMENT,
209  ufp_value="voltage",
210  # no feature flag, but voltage will be null if device does not have
211  # voltage sensor (i.e. is not G4 Doorbell or not on 1.20.1+)
212  ufp_required_field="voltage",
213  precision=2,
214  ),
216  key="doorbell_last_trip_time",
217  name="Last doorbell ring",
218  device_class=SensorDeviceClass.TIMESTAMP,
219  icon="mdi:doorbell-video",
220  ufp_required_field="feature_flags.is_doorbell",
221  ufp_value="last_ring",
222  entity_registry_enabled_default=False,
223  ),
225  key="lens_type",
226  name="Lens type",
227  entity_category=EntityCategory.DIAGNOSTIC,
228  icon="mdi:camera-iris",
229  ufp_required_field="has_removable_lens",
230  ufp_value="feature_flags.lens_type",
231  ),
233  key="mic_level",
234  name="Microphone level",
235  icon="mdi:microphone",
236  native_unit_of_measurement=PERCENTAGE,
237  entity_category=EntityCategory.DIAGNOSTIC,
238  ufp_required_field="has_mic",
239  ufp_value="mic_volume",
240  ufp_enabled="feature_flags.has_mic",
241  ufp_perm=PermRequired.NO_WRITE,
242  ),
244  key="recording_mode",
245  name="Recording mode",
246  icon="mdi:video-outline",
247  entity_category=EntityCategory.DIAGNOSTIC,
248  ufp_value="recording_settings.mode.value",
249  ufp_perm=PermRequired.NO_WRITE,
250  ),
252  key="infrared",
253  name="Infrared mode",
254  icon="mdi:circle-opacity",
255  entity_category=EntityCategory.DIAGNOSTIC,
256  ufp_required_field="feature_flags.has_led_ir",
257  ufp_value="isp_settings.ir_led_mode.value",
258  ufp_perm=PermRequired.NO_WRITE,
259  ),
261  key="doorbell_text",
262  name="Doorbell text",
263  icon="mdi:card-text",
264  entity_category=EntityCategory.DIAGNOSTIC,
265  ufp_required_field="feature_flags.has_lcd_screen",
266  ufp_value="lcd_message.text",
267  ufp_perm=PermRequired.NO_WRITE,
268  ),
270  key="chime_type",
271  name="Chime type",
272  icon="mdi:bell",
273  entity_category=EntityCategory.DIAGNOSTIC,
274  entity_registry_enabled_default=False,
275  ufp_required_field="feature_flags.has_chime",
276  ufp_value="chime_type",
277  ),
278 )
279 
280 CAMERA_DISABLED_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
282  key="stats_rx",
283  name="Received data",
284  native_unit_of_measurement=UnitOfInformation.BYTES,
285  device_class=SensorDeviceClass.DATA_SIZE,
286  entity_registry_enabled_default=False,
287  entity_category=EntityCategory.DIAGNOSTIC,
288  state_class=SensorStateClass.TOTAL_INCREASING,
289  ufp_value="stats.rx_bytes",
290  suggested_unit_of_measurement=UnitOfInformation.MEGABYTES,
291  suggested_display_precision=2,
292  ),
294  key="stats_tx",
295  name="Transferred data",
296  native_unit_of_measurement=UnitOfInformation.BYTES,
297  device_class=SensorDeviceClass.DATA_SIZE,
298  entity_registry_enabled_default=False,
299  entity_category=EntityCategory.DIAGNOSTIC,
300  state_class=SensorStateClass.TOTAL_INCREASING,
301  ufp_value="stats.tx_bytes",
302  suggested_unit_of_measurement=UnitOfInformation.MEGABYTES,
303  suggested_display_precision=2,
304  ),
305 )
306 
307 SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
309  key="battery_level",
310  name="Battery level",
311  native_unit_of_measurement=PERCENTAGE,
312  device_class=SensorDeviceClass.BATTERY,
313  entity_category=EntityCategory.DIAGNOSTIC,
314  state_class=SensorStateClass.MEASUREMENT,
315  ufp_value="battery_status.percentage",
316  ),
318  key="light_level",
319  name="Light level",
320  native_unit_of_measurement=LIGHT_LUX,
321  device_class=SensorDeviceClass.ILLUMINANCE,
322  state_class=SensorStateClass.MEASUREMENT,
323  ufp_value="stats.light.value",
324  ufp_enabled="is_light_sensor_enabled",
325  ),
327  key="humidity_level",
328  name="Humidity level",
329  native_unit_of_measurement=PERCENTAGE,
330  device_class=SensorDeviceClass.HUMIDITY,
331  state_class=SensorStateClass.MEASUREMENT,
332  ufp_value="stats.humidity.value",
333  ufp_enabled="is_humidity_sensor_enabled",
334  ),
336  key="temperature_level",
337  name="Temperature",
338  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
339  device_class=SensorDeviceClass.TEMPERATURE,
340  state_class=SensorStateClass.MEASUREMENT,
341  ufp_value="stats.temperature.value",
342  ufp_enabled="is_temperature_sensor_enabled",
343  ),
344  ProtectSensorEntityDescription[Sensor](
345  key="alarm_sound",
346  name="Alarm sound detected",
347  ufp_value_fn=_get_alarm_sound,
348  ufp_enabled="is_alarm_sensor_enabled",
349  ),
351  key="door_last_trip_time",
352  name="Last open",
353  device_class=SensorDeviceClass.TIMESTAMP,
354  ufp_value="open_status_changed_at",
355  entity_registry_enabled_default=False,
356  ),
358  key="motion_last_trip_time",
359  name="Last motion detected",
360  device_class=SensorDeviceClass.TIMESTAMP,
361  ufp_value="motion_detected_at",
362  entity_registry_enabled_default=False,
363  ),
365  key="tampering_last_trip_time",
366  name="Last tampering detected",
367  device_class=SensorDeviceClass.TIMESTAMP,
368  ufp_value="tampering_detected_at",
369  entity_registry_enabled_default=False,
370  ),
372  key="sensitivity",
373  name="Motion sensitivity",
374  icon="mdi:walk",
375  native_unit_of_measurement=PERCENTAGE,
376  entity_category=EntityCategory.DIAGNOSTIC,
377  ufp_value="motion_settings.sensitivity",
378  ufp_perm=PermRequired.NO_WRITE,
379  ),
381  key="mount_type",
382  name="Mount type",
383  icon="mdi:screwdriver",
384  entity_category=EntityCategory.DIAGNOSTIC,
385  ufp_value="mount_type",
386  ufp_perm=PermRequired.NO_WRITE,
387  ),
389  key="paired_camera",
390  name="Paired camera",
391  icon="mdi:cctv",
392  entity_category=EntityCategory.DIAGNOSTIC,
393  ufp_value="camera.display_name",
394  ufp_perm=PermRequired.NO_WRITE,
395  ),
396 )
397 
398 DOORLOCK_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
400  key="battery_level",
401  name="Battery level",
402  native_unit_of_measurement=PERCENTAGE,
403  device_class=SensorDeviceClass.BATTERY,
404  entity_category=EntityCategory.DIAGNOSTIC,
405  state_class=SensorStateClass.MEASUREMENT,
406  ufp_value="battery_status.percentage",
407  ),
409  key="paired_camera",
410  name="Paired camera",
411  icon="mdi:cctv",
412  entity_category=EntityCategory.DIAGNOSTIC,
413  ufp_value="camera.display_name",
414  ufp_perm=PermRequired.NO_WRITE,
415  ),
416 )
417 
418 NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
420  key="uptime",
421  name="Uptime",
422  icon="mdi:clock",
423  device_class=SensorDeviceClass.TIMESTAMP,
424  entity_category=EntityCategory.DIAGNOSTIC,
425  ufp_value_fn=_get_uptime,
426  ),
428  key="storage_utilization",
429  name="Storage utilization",
430  native_unit_of_measurement=PERCENTAGE,
431  icon="mdi:harddisk",
432  entity_category=EntityCategory.DIAGNOSTIC,
433  state_class=SensorStateClass.MEASUREMENT,
434  ufp_value="storage_stats.utilization",
435  precision=2,
436  ),
438  key="record_rotating",
439  name="Type: timelapse video",
440  native_unit_of_measurement=PERCENTAGE,
441  icon="mdi:server",
442  entity_category=EntityCategory.DIAGNOSTIC,
443  state_class=SensorStateClass.MEASUREMENT,
444  ufp_value="storage_stats.storage_distribution.timelapse_recordings.percentage",
445  precision=2,
446  ),
448  key="record_timelapse",
449  name="Type: continuous video",
450  native_unit_of_measurement=PERCENTAGE,
451  icon="mdi:server",
452  entity_category=EntityCategory.DIAGNOSTIC,
453  state_class=SensorStateClass.MEASUREMENT,
454  ufp_value="storage_stats.storage_distribution.continuous_recordings.percentage",
455  precision=2,
456  ),
458  key="record_detections",
459  name="Type: detections video",
460  native_unit_of_measurement=PERCENTAGE,
461  icon="mdi:server",
462  entity_category=EntityCategory.DIAGNOSTIC,
463  state_class=SensorStateClass.MEASUREMENT,
464  ufp_value="storage_stats.storage_distribution.detections_recordings.percentage",
465  precision=2,
466  ),
468  key="resolution_HD",
469  name="Resolution: HD video",
470  native_unit_of_measurement=PERCENTAGE,
471  icon="mdi:cctv",
472  entity_category=EntityCategory.DIAGNOSTIC,
473  state_class=SensorStateClass.MEASUREMENT,
474  ufp_value="storage_stats.storage_distribution.hd_usage.percentage",
475  precision=2,
476  ),
478  key="resolution_4K",
479  name="Resolution: 4K video",
480  native_unit_of_measurement=PERCENTAGE,
481  icon="mdi:cctv",
482  entity_category=EntityCategory.DIAGNOSTIC,
483  state_class=SensorStateClass.MEASUREMENT,
484  ufp_value="storage_stats.storage_distribution.uhd_usage.percentage",
485  precision=2,
486  ),
488  key="resolution_free",
489  name="Resolution: free space",
490  native_unit_of_measurement=PERCENTAGE,
491  icon="mdi:cctv",
492  entity_category=EntityCategory.DIAGNOSTIC,
493  state_class=SensorStateClass.MEASUREMENT,
494  ufp_value="storage_stats.storage_distribution.free.percentage",
495  precision=2,
496  ),
497  ProtectSensorEntityDescription[NVR](
498  key="record_capacity",
499  name="Recording capacity",
500  native_unit_of_measurement=UnitOfTime.SECONDS,
501  icon="mdi:record-rec",
502  entity_category=EntityCategory.DIAGNOSTIC,
503  state_class=SensorStateClass.MEASUREMENT,
504  ufp_value_fn=_get_nvr_recording_capacity,
505  ),
506 )
507 
508 NVR_DISABLED_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
510  key="cpu_utilization",
511  name="CPU utilization",
512  native_unit_of_measurement=PERCENTAGE,
513  icon="mdi:speedometer",
514  entity_registry_enabled_default=False,
515  entity_category=EntityCategory.DIAGNOSTIC,
516  state_class=SensorStateClass.MEASUREMENT,
517  ufp_value="system_info.cpu.average_load",
518  ),
520  key="cpu_temperature",
521  name="CPU temperature",
522  native_unit_of_measurement=UnitOfTemperature.CELSIUS,
523  device_class=SensorDeviceClass.TEMPERATURE,
524  entity_registry_enabled_default=False,
525  entity_category=EntityCategory.DIAGNOSTIC,
526  state_class=SensorStateClass.MEASUREMENT,
527  ufp_value="system_info.cpu.temperature",
528  ),
529  ProtectSensorEntityDescription[NVR](
530  key="memory_utilization",
531  name="Memory utilization",
532  native_unit_of_measurement=PERCENTAGE,
533  icon="mdi:memory",
534  entity_registry_enabled_default=False,
535  entity_category=EntityCategory.DIAGNOSTIC,
536  state_class=SensorStateClass.MEASUREMENT,
537  ufp_value_fn=_get_nvr_memory,
538  precision=2,
539  ),
540 )
541 
542 LICENSE_PLATE_EVENT_SENSORS: tuple[ProtectSensorEventEntityDescription, ...] = (
544  key="smart_obj_licenseplate",
545  name="License plate detected",
546  icon="mdi:car",
547  translation_key="license_plate",
548  ufp_obj_type=SmartDetectObjectType.LICENSE_PLATE,
549  ufp_required_field="can_detect_license_plate",
550  ufp_event_obj="last_license_plate_detect_event",
551  ),
552 )
553 
554 
555 LIGHT_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
557  key="motion_last_trip_time",
558  name="Last motion detected",
559  device_class=SensorDeviceClass.TIMESTAMP,
560  ufp_value="last_motion",
561  entity_registry_enabled_default=False,
562  ),
564  key="sensitivity",
565  name="Motion sensitivity",
566  icon="mdi:walk",
567  native_unit_of_measurement=PERCENTAGE,
568  entity_category=EntityCategory.DIAGNOSTIC,
569  ufp_value="light_device_settings.pir_sensitivity",
570  ufp_perm=PermRequired.NO_WRITE,
571  ),
572  ProtectSensorEntityDescription[Light](
573  key="light_motion",
574  name="Light mode",
575  icon="mdi:spotlight",
576  entity_category=EntityCategory.DIAGNOSTIC,
577  ufp_value_fn=async_get_light_motion_current,
578  ufp_perm=PermRequired.NO_WRITE,
579  ),
581  key="paired_camera",
582  name="Paired camera",
583  icon="mdi:cctv",
584  entity_category=EntityCategory.DIAGNOSTIC,
585  ufp_value="camera.display_name",
586  ufp_perm=PermRequired.NO_WRITE,
587  ),
588 )
589 
590 MOTION_TRIP_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
592  key="motion_last_trip_time",
593  name="Last motion detected",
594  device_class=SensorDeviceClass.TIMESTAMP,
595  ufp_value="last_motion",
596  entity_registry_enabled_default=False,
597  ),
598 )
599 
600 CHIME_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
602  key="last_ring",
603  name="Last ring",
604  device_class=SensorDeviceClass.TIMESTAMP,
605  icon="mdi:bell",
606  ufp_value="last_ring",
607  ),
609  key="volume",
610  name="Volume",
611  icon="mdi:speaker",
612  native_unit_of_measurement=PERCENTAGE,
613  entity_category=EntityCategory.DIAGNOSTIC,
614  ufp_value="volume",
615  ufp_perm=PermRequired.NO_WRITE,
616  ),
617 )
618 
619 VIEWER_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
621  key="viewer",
622  name="Liveview",
623  icon="mdi:view-dashboard",
624  entity_category=EntityCategory.DIAGNOSTIC,
625  ufp_value="liveview.name",
626  ufp_perm=PermRequired.NO_WRITE,
627  ),
628 )
629 
630 _MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = {
631  ModelType.CAMERA: CAMERA_SENSORS + CAMERA_DISABLED_SENSORS,
632  ModelType.SENSOR: SENSE_SENSORS,
633  ModelType.LIGHT: LIGHT_SENSORS,
634  ModelType.DOORLOCK: DOORLOCK_SENSORS,
635  ModelType.CHIME: CHIME_SENSORS,
636  ModelType.VIEWPORT: VIEWER_SENSORS,
637 }
638 
639 
641  hass: HomeAssistant,
642  entry: UFPConfigEntry,
643  async_add_entities: AddEntitiesCallback,
644 ) -> None:
645  """Set up sensors for UniFi Protect integration."""
646  data = entry.runtime_data
647 
648  @callback
649  def _add_new_device(device: ProtectAdoptableDeviceModel) -> None:
650  entities = async_all_device_entities(
651  data,
652  ProtectDeviceSensor,
653  all_descs=ALL_DEVICES_SENSORS,
654  model_descriptions=_MODEL_DESCRIPTIONS,
655  ufp_device=device,
656  )
657  if device.is_adopted_by_us and isinstance(device, Camera):
658  entities += _async_event_entities(data, ufp_device=device)
659  async_add_entities(entities)
660 
661  data.async_subscribe_adopt(_add_new_device)
662  entities = async_all_device_entities(
663  data,
664  ProtectDeviceSensor,
665  all_descs=ALL_DEVICES_SENSORS,
666  model_descriptions=_MODEL_DESCRIPTIONS,
667  )
668  entities += _async_event_entities(data)
669  entities += _async_nvr_entities(data)
670 
671  async_add_entities(entities)
672 
673 
674 @callback
676  data: ProtectData,
677  ufp_device: Camera | None = None,
678 ) -> list[ProtectDeviceEntity]:
679  entities: list[ProtectDeviceEntity] = []
680  cameras = data.get_cameras() if ufp_device is None else [ufp_device]
681  for camera in cameras:
682  for description in MOTION_TRIP_SENSORS:
683  entities.append(ProtectDeviceSensor(data, camera, description))
684  _LOGGER.debug(
685  "Adding trip sensor entity %s for %s",
686  description.name,
687  camera.display_name,
688  )
689 
690  if not camera.feature_flags.has_smart_detect:
691  continue
692 
693  for event_desc in LICENSE_PLATE_EVENT_SENSORS:
694  if not event_desc.has_required(camera):
695  continue
696 
697  entities.append(ProtectLicensePlateEventSensor(data, camera, event_desc))
698  _LOGGER.debug(
699  "Adding sensor entity %s for %s",
700  description.name,
701  camera.display_name,
702  )
703 
704  return entities
705 
706 
707 @callback
709  data: ProtectData,
710 ) -> list[BaseProtectEntity]:
711  entities: list[BaseProtectEntity] = []
712  device = data.api.bootstrap.nvr
713  for description in NVR_SENSORS + NVR_DISABLED_SENSORS:
714  entities.append(ProtectNVRSensor(data, device, description))
715  _LOGGER.debug("Adding NVR sensor entity %s", description.name)
716 
717  return entities
718 
719 
721  """A UniFi Protect Sensor Entity."""
722 
723  entity_description: ProtectSensorEntityDescription
724  _state_attrs = ("_attr_available", "_attr_native_value")
725 
726  def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
727  super()._async_update_device_from_protect(device)
728  self._attr_native_value_attr_native_value = self.entity_descriptionentity_description.get_ufp_value(self.devicedevice)
729 
730 
732  """A Ubiquiti UniFi Protect Sensor."""
733 
734 
735 class ProtectNVRSensor(BaseProtectSensor, ProtectNVREntity):
736  """A Ubiquiti UniFi Protect Sensor."""
737 
738 
740  """A UniFi Protect Device Sensor with access tokens."""
741 
742  entity_description: ProtectSensorEventEntityDescription
743  _state_attrs = (
744  "_attr_available",
745  "_attr_native_value",
746  "_attr_extra_state_attributes",
747  )
748 
749 
751  """A UniFi Protect license plate sensor."""
752 
753  device: Camera
754 
755  @callback
756  def _set_event_done(self) -> None:
757  self._attr_native_value_attr_native_value = OBJECT_TYPE_NONE
758  self._attr_extra_state_attributes_attr_extra_state_attributes_attr_extra_state_attributes = {}
759 
760  @callback
761  def _async_update_device_from_protect(self, device: ProtectDeviceType) -> None:
762  description = self.entity_descriptionentity_description
763 
764  prev_event = self._event_event
765  prev_event_end = self._event_end_event_end
766  super()._async_update_device_from_protect(device)
767  if event := description.get_event_obj(device):
768  self._event_event = event
769  self._event_end_event_end = event.end
770 
771  if not (
772  event
773  and (metadata := event.metadata)
774  and (license_plate := metadata.license_plate)
775  and description.has_matching_smart(event)
776  and not self._event_already_ended_event_already_ended(prev_event, prev_event_end)
777  ):
778  self._set_event_done_set_event_done_set_event_done()
779  return
780 
781  self._attr_native_value_attr_native_value = license_plate.name
782  self._set_event_attrs_set_event_attrs(event)
783  if event.end:
784  self._async_event_with_immediate_end_async_event_with_immediate_end()
bool _event_already_ended(self, Event|None prev_event, datetime|None prev_event_end)
Definition: entity.py:359
None _async_update_device_from_protect(self, ProtectDeviceType device)
Definition: sensor.py:726
None _async_update_device_from_protect(self, ProtectDeviceType device)
Definition: sensor.py:761
Any _rounded_value(self, int precision, Callable[[T], Any] getter, T obj)
Definition: sensor.py:79
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
list[ProtectDeviceEntity] _async_event_entities(ProtectData data, Camera|None ufp_device=None)
Definition: sensor.py:678
list[BaseProtectEntity] _async_nvr_entities(ProtectData data)
Definition: sensor.py:710
datetime|None _get_uptime(ProtectDeviceModel obj)
Definition: sensor.py:91
None async_setup_entry(HomeAssistant hass, UFPConfigEntry entry, AddEntitiesCallback async_add_entities)
Definition: sensor.py:644