Add new features from new UniFi Protect (#82892)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Christopher Bailey 2022-11-29 04:44:31 -05:00 committed by GitHub
parent a5890b2374
commit 596016c2ac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 166 additions and 17 deletions

View file

@ -14,6 +14,7 @@ from pyunifiprotect.data import (
ProtectAdoptableDeviceModel,
ProtectModelWithId,
Sensor,
SmartDetectAudioType,
SmartDetectObjectType,
)
from pyunifiprotect.data.nvr import UOSDisk
@ -201,6 +202,24 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ufp_value="is_package_detection_on",
ufp_perm=PermRequired.NO_WRITE,
),
ProtectBinaryEntityDescription(
key="smart_licenseplate",
name="Detections: License Plate",
icon="mdi:car",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_license_plate",
ufp_value="is_license_plate_detection_on",
ufp_perm=PermRequired.NO_WRITE,
),
ProtectBinaryEntityDescription(
key="smart_smoke",
name="Detections: Smoke/CO",
icon="mdi:fire",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_smoke",
ufp_value="is_smoke_detection_on",
ufp_perm=PermRequired.NO_WRITE,
),
)
LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
@ -381,6 +400,37 @@ MOTION_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ufp_event_obj="last_smart_detect_event",
ufp_smart_type=SmartDetectObjectType.PACKAGE,
),
ProtectBinaryEventEntityDescription(
key="smart_audio_any",
name="Audio Detected",
icon="mdi:eye",
device_class=DEVICE_CLASS_DETECTION,
ufp_value="is_smart_detected",
ufp_required_field="feature_flags.has_smart_detect",
ufp_event_obj="last_smart_audio_detect_event",
),
ProtectBinaryEventEntityDescription(
key="smart_audio_smoke",
name="Smoke Alarm Detected",
device_class=DEVICE_CLASS_DETECTION,
icon="mdi:fire",
ufp_value="is_smart_detected",
ufp_required_field="can_detect_smoke",
ufp_enabled="is_smoke_detection_on",
ufp_event_obj="last_smart_audio_detect_event",
ufp_smart_type=SmartDetectAudioType.SMOKE,
),
ProtectBinaryEventEntityDescription(
key="smart_audio_cmonx",
name="CO Alarm Detected",
device_class=DEVICE_CLASS_DETECTION,
icon="mdi:fire",
ufp_value="is_smart_detected",
ufp_required_field="can_detect_smoke",
ufp_enabled="is_smoke_detection_on",
ufp_event_obj="last_smart_audio_detect_event",
ufp_smart_type=SmartDetectAudioType.CMONX,
),
)
DOORLOCK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (

View file

@ -406,10 +406,13 @@ class ProtectMediaSource(MediaSource):
event_text = "Motion Event"
elif event_type == EventType.SMART_DETECT.value:
if isinstance(event, Event):
smart_type = event.smart_detect_types[0]
smart_types = event.smart_detect_types
else:
smart_type = SmartDetectObjectType(event["smartDetectTypes"][0])
event_text = f"Smart Detection - {smart_type.name.title()}"
smart_types = [
SmartDetectObjectType(e) for e in event["smartDetectTypes"]
]
smart_type_names = [s.name.title().replace("_", " ") for s in smart_types]
event_text = f"Smart Detection - {','.join(smart_type_names)}"
title += f" {event_text}"
nvr = data.api.bootstrap.nvr

View file

@ -15,6 +15,7 @@ from pyunifiprotect.data import (
ProtectDeviceModel,
ProtectModelWithId,
Sensor,
SmartDetectObjectType,
)
from homeassistant.components.sensor import (
@ -527,6 +528,15 @@ MOTION_SENSORS: tuple[ProtectSensorEventEntityDescription, ...] = (
ufp_value="is_smart_detected",
ufp_event_obj="last_smart_detect_event",
),
ProtectSensorEventEntityDescription(
key="smart_obj_licenseplate",
name="License Plate Detected",
icon="mdi:car",
device_class=DEVICE_CLASS_DETECTION,
ufp_value="is_smart_detected",
ufp_event_obj="last_smart_detect_event",
ufp_smart_type=SmartDetectObjectType.LICENSE_PLATE,
),
)
@ -756,7 +766,20 @@ class ProtectEventSensor(EventEntityMixin, SensorEntity):
def _async_update_device_from_protect(self, device: ProtectModelWithId) -> None:
# do not call ProtectDeviceSensor method since we want event to get value here
EventEntityMixin._async_update_device_from_protect(self, device)
if self._event is None:
self._attr_native_value = OBJECT_TYPE_NONE
if (
self.entity_description.ufp_smart_type
== SmartDetectObjectType.LICENSE_PLATE
):
if (
self._event is None
or self._event.metadata is None
or self._event.metadata.license_plate is None
):
self._attr_native_value = OBJECT_TYPE_NONE
else:
self._attr_native_value = self._event.metadata.license_plate.name
else:
self._attr_native_value = self._event.smart_detect_types[0].value
if self._event is None:
self._attr_native_value = OBJECT_TYPE_NONE
else:
self._attr_native_value = self._event.smart_detect_types[0].value

View file

@ -126,7 +126,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
),
ProtectSwitchEntityDescription(
key="osd_bitrate",
name="Overlay: Show Bitrate",
name="Overlay: Show Nerd Mode",
icon="mdi:fullscreen",
entity_category=EntityCategory.CONFIG,
ufp_value="osd_settings.is_debug_enabled",
@ -182,6 +182,26 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ufp_set_method="set_package_detection",
ufp_perm=PermRequired.WRITE,
),
ProtectSwitchEntityDescription(
key="smart_licenseplate",
name="Detections: License Plate",
icon="mdi:car",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_license_plate",
ufp_value="is_license_plate_detection_on",
ufp_set_method="set_license_plate_detection",
ufp_perm=PermRequired.WRITE,
),
ProtectSwitchEntityDescription(
key="smart_smoke",
name="Detections: Smoke/CO",
icon="mdi:fire",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_smoke",
ufp_value="is_smoke_detection_on",
ufp_set_method="set_smoke_detection",
ufp_perm=PermRequired.WRITE,
),
)
PRIVACY_MODE_SWITCH = ProtectSwitchEntityDescription[Camera](

View file

@ -50,11 +50,11 @@ async def test_binary_sensor_camera_remove(
ufp.api.bootstrap.nvr.system_info.ustorage = None
await init_entry(hass, ufp, [doorbell, unadopted_camera])
assert_entity_counts(hass, Platform.BINARY_SENSOR, 6, 6)
assert_entity_counts(hass, Platform.BINARY_SENSOR, 7, 7)
await remove_entities(hass, ufp, [doorbell, unadopted_camera])
assert_entity_counts(hass, Platform.BINARY_SENSOR, 0, 0)
await adopt_devices(hass, ufp, [doorbell, unadopted_camera])
assert_entity_counts(hass, Platform.BINARY_SENSOR, 6, 6)
assert_entity_counts(hass, Platform.BINARY_SENSOR, 7, 7)
async def test_binary_sensor_light_remove(
@ -120,7 +120,7 @@ async def test_binary_sensor_setup_camera_all(
ufp.api.bootstrap.nvr.system_info.ustorage = None
await init_entry(hass, ufp, [doorbell, unadopted_camera])
assert_entity_counts(hass, Platform.BINARY_SENSOR, 6, 6)
assert_entity_counts(hass, Platform.BINARY_SENSOR, 7, 7)
entity_registry = er.async_get(hass)
@ -262,7 +262,7 @@ async def test_binary_sensor_update_motion(
"""Test binary_sensor motion entity."""
await init_entry(hass, ufp, [doorbell, unadopted_camera])
assert_entity_counts(hass, Platform.BINARY_SENSOR, 12, 12)
assert_entity_counts(hass, Platform.BINARY_SENSOR, 13, 13)
_, entity_id = ids_from_device_description(
Platform.BINARY_SENSOR, doorbell, MOTION_SENSORS[0]

View file

@ -13,7 +13,7 @@ from pyunifiprotect.data import (
Sensor,
SmartDetectObjectType,
)
from pyunifiprotect.data.nvr import EventMetadata
from pyunifiprotect.data.nvr import EventMetadata, LicensePlateMetadata
from homeassistant.components.unifiprotect.const import (
ATTR_EVENT_SCORE,
@ -62,11 +62,11 @@ async def test_sensor_camera_remove(
ufp.api.bootstrap.nvr.system_info.ustorage = None
await init_entry(hass, ufp, [doorbell, unadopted_camera])
assert_entity_counts(hass, Platform.SENSOR, 25, 12)
assert_entity_counts(hass, Platform.SENSOR, 26, 13)
await remove_entities(hass, ufp, [doorbell, unadopted_camera])
assert_entity_counts(hass, Platform.SENSOR, 12, 9)
await adopt_devices(hass, ufp, [doorbell, unadopted_camera])
assert_entity_counts(hass, Platform.SENSOR, 25, 12)
assert_entity_counts(hass, Platform.SENSOR, 26, 13)
async def test_sensor_sensor_remove(
@ -318,7 +318,7 @@ async def test_sensor_setup_camera(
"""Test sensor entity setup for camera devices."""
await init_entry(hass, ufp, [doorbell])
assert_entity_counts(hass, Platform.SENSOR, 25, 12)
assert_entity_counts(hass, Platform.SENSOR, 26, 13)
entity_registry = er.async_get(hass)
@ -424,7 +424,7 @@ async def test_sensor_setup_camera_with_last_trip_time(
"""Test sensor entity setup for camera devices with last trip time."""
await init_entry(hass, ufp, [doorbell])
assert_entity_counts(hass, Platform.SENSOR, 25, 25)
assert_entity_counts(hass, Platform.SENSOR, 26, 26)
entity_registry = er.async_get(hass)
@ -452,7 +452,7 @@ async def test_sensor_update_motion(
"""Test sensor motion entity."""
await init_entry(hass, ufp, [doorbell])
assert_entity_counts(hass, Platform.SENSOR, 25, 12)
assert_entity_counts(hass, Platform.SENSOR, 26, 13)
_, entity_id = ids_from_device_description(
Platform.SENSOR, doorbell, MOTION_SENSORS[0]
@ -565,3 +565,54 @@ async def test_sensor_update_alarm_with_last_trip_time(
== (fixed_now - timedelta(hours=1)).replace(microsecond=0).isoformat()
)
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
async def test_camera_update_licenseplate(
hass: HomeAssistant, ufp: MockUFPFixture, camera: Camera, fixed_now: datetime
):
"""Test sensor motion entity."""
camera.feature_flags.smart_detect_types.append(SmartDetectObjectType.LICENSE_PLATE)
camera.feature_flags.has_smart_detect = True
camera.smart_detect_settings.object_types.append(
SmartDetectObjectType.LICENSE_PLATE
)
await init_entry(hass, ufp, [camera])
assert_entity_counts(hass, Platform.SENSOR, 24, 13)
_, entity_id = ids_from_device_description(
Platform.SENSOR, camera, MOTION_SENSORS[1]
)
event_metadata = EventMetadata(
license_plate=LicensePlateMetadata(name="ABCD1234", confidence_level=95)
)
event = Event(
id="test_event_id",
type=EventType.SMART_DETECT,
start=fixed_now - timedelta(seconds=1),
end=None,
score=100,
smart_detect_types=[SmartDetectObjectType.LICENSE_PLATE],
smart_detect_event_ids=[],
metadata=event_metadata,
api=ufp.api,
)
new_camera = camera.copy()
new_camera.is_smart_detected = True
new_camera.last_smart_detect_event_id = event.id
mock_msg = Mock()
mock_msg.changed_data = {}
mock_msg.new_obj = new_camera
ufp.api.bootstrap.cameras = {new_camera.id: new_camera}
ufp.api.bootstrap.events = {event.id: event}
ufp.ws_msg(mock_msg)
await hass.async_block_till_done()
state = hass.states.get(entity_id)
assert state
assert state.state == "ABCD1234"

View file

@ -35,6 +35,8 @@ CAMERA_SWITCHES_BASIC = [
for d in CAMERA_SWITCHES
if d.name != "Detections: Face"
and d.name != "Detections: Package"
and d.name != "Detections: License Plate"
and d.name != "Detections: Smoke/CO"
and d.name != "SSH Enabled"
]
CAMERA_SWITCHES_NO_EXTRA = [