Improve UniFi Protect Smart Sensor support (#64019)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
cafbcb634a
commit
20768172b1
13 changed files with 501 additions and 22 deletions
|
@ -5,7 +5,7 @@ from copy import copy
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pyunifiprotect.data import NVR, Camera, Event, Light, Sensor
|
from pyunifiprotect.data import NVR, Camera, Event, Light, MountType, Sensor
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import (
|
from homeassistant.components.binary_sensor import (
|
||||||
BinarySensorDeviceClass,
|
BinarySensorDeviceClass,
|
||||||
|
@ -30,6 +30,7 @@ from .models import ProtectRequiredKeysMixin
|
||||||
from .utils import get_nested_attr
|
from .utils import get_nested_attr
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
_KEY_DOOR = "door"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -41,6 +42,13 @@ class ProtectBinaryEntityDescription(
|
||||||
ufp_last_trip_value: str | None = None
|
ufp_last_trip_value: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
MOUNT_DEVICE_CLASS_MAP = {
|
||||||
|
MountType.GARAGE: BinarySensorDeviceClass.GARAGE_DOOR,
|
||||||
|
MountType.WINDOW: BinarySensorDeviceClass.WINDOW,
|
||||||
|
MountType.DOOR: BinarySensorDeviceClass.DOOR,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="doorbell",
|
key="doorbell",
|
||||||
|
@ -77,11 +85,12 @@ LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
||||||
|
|
||||||
SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="door",
|
key=_KEY_DOOR,
|
||||||
name="Door",
|
name="Contact",
|
||||||
device_class=BinarySensorDeviceClass.DOOR,
|
device_class=BinarySensorDeviceClass.DOOR,
|
||||||
ufp_value="is_opened",
|
ufp_value="is_opened",
|
||||||
ufp_last_trip_value="open_status_changed_at",
|
ufp_last_trip_value="open_status_changed_at",
|
||||||
|
ufp_enabled="is_contact_sensor_enabled",
|
||||||
),
|
),
|
||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="battery_low",
|
key="battery_low",
|
||||||
|
@ -96,6 +105,14 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
||||||
device_class=BinarySensorDeviceClass.MOTION,
|
device_class=BinarySensorDeviceClass.MOTION,
|
||||||
ufp_value="is_motion_detected",
|
ufp_value="is_motion_detected",
|
||||||
ufp_last_trip_value="motion_detected_at",
|
ufp_last_trip_value="motion_detected_at",
|
||||||
|
ufp_enabled="is_motion_sensor_enabled",
|
||||||
|
),
|
||||||
|
ProtectBinaryEntityDescription(
|
||||||
|
key="tampering",
|
||||||
|
name="Tampering Detected",
|
||||||
|
device_class=BinarySensorDeviceClass.TAMPER,
|
||||||
|
ufp_value="is_tampering_detected",
|
||||||
|
ufp_last_trip_value="tampering_detected_at",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -197,6 +214,12 @@ class ProtectDeviceBinarySensor(ProtectDeviceEntity, BinarySensorEntity):
|
||||||
ATTR_LAST_TRIP_TIME: last_trip,
|
ATTR_LAST_TRIP_TIME: last_trip,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# UP Sense can be any of the 3 contact sensor device classes
|
||||||
|
if self.entity_description.key == _KEY_DOOR and isinstance(self.device, Sensor):
|
||||||
|
self.entity_description.device_class = MOUNT_DEVICE_CLASS_MAP.get(
|
||||||
|
self.device.mount_type, BinarySensorDeviceClass.DOOR
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ProtectDiskBinarySensor(ProtectNVREntity, BinarySensorEntity):
|
class ProtectDiskBinarySensor(ProtectNVREntity, BinarySensorEntity):
|
||||||
"""A UniFi Protect NVR Disk Binary Sensor."""
|
"""A UniFi Protect NVR Disk Binary Sensor."""
|
||||||
|
|
|
@ -7,9 +7,14 @@ import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient
|
from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient
|
||||||
from pyunifiprotect.data import Bootstrap, ModelType, WSSubscriptionMessage
|
from pyunifiprotect.data import (
|
||||||
|
Bootstrap,
|
||||||
|
Event,
|
||||||
|
Liveview,
|
||||||
|
ModelType,
|
||||||
|
WSSubscriptionMessage,
|
||||||
|
)
|
||||||
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel, ProtectDeviceModel
|
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel, ProtectDeviceModel
|
||||||
from pyunifiprotect.data.nvr import Liveview
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||||
|
@ -115,6 +120,14 @@ class ProtectData:
|
||||||
for camera in self.api.bootstrap.cameras.values():
|
for camera in self.api.bootstrap.cameras.values():
|
||||||
if camera.feature_flags.has_lcd_screen:
|
if camera.feature_flags.has_lcd_screen:
|
||||||
self.async_signal_device_id_update(camera.id)
|
self.async_signal_device_id_update(camera.id)
|
||||||
|
# trigger updates for camera that the event references
|
||||||
|
elif isinstance(message.new_obj, Event):
|
||||||
|
if message.new_obj.camera is not None:
|
||||||
|
self.async_signal_device_id_update(message.new_obj.camera.id)
|
||||||
|
elif message.new_obj.light is not None:
|
||||||
|
self.async_signal_device_id_update(message.new_obj.light.id)
|
||||||
|
elif message.new_obj.sensor is not None:
|
||||||
|
self.async_signal_device_id_update(message.new_obj.sensor.id)
|
||||||
# alert user viewport needs restart so voice clients can get new options
|
# alert user viewport needs restart so voice clients can get new options
|
||||||
elif len(self.api.bootstrap.viewers) > 0 and isinstance(
|
elif len(self.api.bootstrap.viewers) > 0 and isinstance(
|
||||||
message.new_obj, Liveview
|
message.new_obj, Liveview
|
||||||
|
|
|
@ -149,9 +149,19 @@ class ProtectDeviceEntity(Entity):
|
||||||
devices = getattr(self.data.api.bootstrap, f"{self.device.model.value}s")
|
devices = getattr(self.data.api.bootstrap, f"{self.device.model.value}s")
|
||||||
self.device = devices[self.device.id]
|
self.device = devices[self.device.id]
|
||||||
|
|
||||||
self._attr_available = (
|
is_connected = (
|
||||||
self.data.last_update_success and self.device.state == StateType.CONNECTED
|
self.data.last_update_success and self.device.state == StateType.CONNECTED
|
||||||
)
|
)
|
||||||
|
if (
|
||||||
|
hasattr(self, "entity_description")
|
||||||
|
and self.entity_description is not None
|
||||||
|
and hasattr(self.entity_description, "get_ufp_enabled")
|
||||||
|
):
|
||||||
|
assert isinstance(self.entity_description, ProtectRequiredKeysMixin)
|
||||||
|
is_connected = is_connected and self.entity_description.get_ufp_enabled(
|
||||||
|
self.device
|
||||||
|
)
|
||||||
|
self._attr_available = is_connected
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_updated_event(self) -> None:
|
def _async_updated_event(self) -> None:
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/unifiprotect",
|
"documentation": "https://www.home-assistant.io/integrations/unifiprotect",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"pyunifiprotect==1.6.2"
|
"pyunifiprotect==1.6.3"
|
||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"http"
|
"http"
|
||||||
|
|
|
@ -22,6 +22,7 @@ class ProtectRequiredKeysMixin:
|
||||||
ufp_required_field: str | None = None
|
ufp_required_field: str | None = None
|
||||||
ufp_value: str | None = None
|
ufp_value: str | None = None
|
||||||
ufp_value_fn: Callable[[ProtectAdoptableDeviceModel | NVR], Any] | None = None
|
ufp_value_fn: Callable[[ProtectAdoptableDeviceModel | NVR], Any] | None = None
|
||||||
|
ufp_enabled: str | None = None
|
||||||
|
|
||||||
def get_ufp_value(self, obj: ProtectAdoptableDeviceModel | NVR) -> Any:
|
def get_ufp_value(self, obj: ProtectAdoptableDeviceModel | NVR) -> Any:
|
||||||
"""Return value from UniFi Protect device."""
|
"""Return value from UniFi Protect device."""
|
||||||
|
@ -35,6 +36,12 @@ class ProtectRequiredKeysMixin:
|
||||||
"`ufp_value` or `ufp_value_fn` is required"
|
"`ufp_value` or `ufp_value_fn` is required"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_ufp_enabled(self, obj: ProtectAdoptableDeviceModel | NVR) -> bool:
|
||||||
|
"""Return value from UniFi Protect device."""
|
||||||
|
if self.ufp_enabled is not None:
|
||||||
|
return bool(get_nested_attr(obj, self.ufp_enabled))
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ProtectSetableKeysMixin(ProtectRequiredKeysMixin):
|
class ProtectSetableKeysMixin(ProtectRequiredKeysMixin):
|
||||||
|
|
|
@ -111,6 +111,21 @@ LIGHT_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SENSE_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
||||||
|
ProtectNumberEntityDescription(
|
||||||
|
key="sensitivity",
|
||||||
|
name="Motion Sensitivity",
|
||||||
|
icon="mdi:walk",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
ufp_min=0,
|
||||||
|
ufp_max=100,
|
||||||
|
ufp_step=1,
|
||||||
|
ufp_required_field=None,
|
||||||
|
ufp_value="motion_settings.sensitivity",
|
||||||
|
ufp_set_method="set_motion_sensitivity",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -124,6 +139,7 @@ async def async_setup_entry(
|
||||||
ProtectNumbers,
|
ProtectNumbers,
|
||||||
camera_descs=CAMERA_NUMBERS,
|
camera_descs=CAMERA_NUMBERS,
|
||||||
light_descs=LIGHT_NUMBERS,
|
light_descs=LIGHT_NUMBERS,
|
||||||
|
sense_descs=SENSE_NUMBERS,
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
|
@ -19,7 +19,10 @@ from pyunifiprotect.data import (
|
||||||
RecordingMode,
|
RecordingMode,
|
||||||
Viewer,
|
Viewer,
|
||||||
)
|
)
|
||||||
from pyunifiprotect.data.types import ChimeType
|
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel
|
||||||
|
from pyunifiprotect.data.devices import Sensor
|
||||||
|
from pyunifiprotect.data.nvr import NVR
|
||||||
|
from pyunifiprotect.data.types import ChimeType, MountType
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||||
|
@ -52,6 +55,14 @@ CHIME_TYPES = [
|
||||||
{"id": ChimeType.DIGITAL.value, "name": "Digital"},
|
{"id": ChimeType.DIGITAL.value, "name": "Digital"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MOUNT_TYPES = [
|
||||||
|
{"id": MountType.NONE.value, "name": "None"},
|
||||||
|
{"id": MountType.DOOR.value, "name": "Door"},
|
||||||
|
{"id": MountType.WINDOW.value, "name": "Window"},
|
||||||
|
{"id": MountType.GARAGE.value, "name": "Garage"},
|
||||||
|
{"id": MountType.LEAK.value, "name": "Leak"},
|
||||||
|
]
|
||||||
|
|
||||||
LIGHT_MODE_MOTION = "On Motion - Always"
|
LIGHT_MODE_MOTION = "On Motion - Always"
|
||||||
LIGHT_MODE_MOTION_DARK = "On Motion - When Dark"
|
LIGHT_MODE_MOTION_DARK = "On Motion - When Dark"
|
||||||
LIGHT_MODE_DARK = "When Dark"
|
LIGHT_MODE_DARK = "When Dark"
|
||||||
|
@ -161,8 +172,10 @@ async def _set_light_mode(obj: Any, mode: str) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _set_paired_camera(obj: Any, camera_id: str) -> None:
|
async def _set_paired_camera(
|
||||||
assert isinstance(obj, Light)
|
obj: ProtectAdoptableDeviceModel | NVR, camera_id: str
|
||||||
|
) -> None:
|
||||||
|
assert isinstance(obj, (Sensor, Light))
|
||||||
if camera_id == TYPE_EMPTY_VALUE:
|
if camera_id == TYPE_EMPTY_VALUE:
|
||||||
camera: Camera | None = None
|
camera: Camera | None = None
|
||||||
else:
|
else:
|
||||||
|
@ -253,6 +266,28 @@ LIGHT_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SENSE_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
||||||
|
ProtectSelectEntityDescription(
|
||||||
|
key="mount_type",
|
||||||
|
name="Mount Type",
|
||||||
|
icon="mdi:screwdriver",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
ufp_options=MOUNT_TYPES,
|
||||||
|
ufp_enum_type=MountType,
|
||||||
|
ufp_value="mount_type",
|
||||||
|
ufp_set_method="set_mount_type",
|
||||||
|
),
|
||||||
|
ProtectSelectEntityDescription(
|
||||||
|
key="paired_camera",
|
||||||
|
name="Paired Camera",
|
||||||
|
icon="mdi:cctv",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
ufp_value="camera_id",
|
||||||
|
ufp_options_callable=_get_paired_camera_options,
|
||||||
|
ufp_set_method_fn=_set_paired_camera,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
VIEWER_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
VIEWER_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
||||||
ProtectSelectEntityDescription(
|
ProtectSelectEntityDescription(
|
||||||
key="viewer",
|
key="viewer",
|
||||||
|
@ -278,6 +313,7 @@ async def async_setup_entry(
|
||||||
ProtectSelects,
|
ProtectSelects,
|
||||||
camera_descs=CAMERA_SELECTS,
|
camera_descs=CAMERA_SELECTS,
|
||||||
light_descs=LIGHT_SELECTS,
|
light_descs=LIGHT_SELECTS,
|
||||||
|
sense_descs=SENSE_SELECTS,
|
||||||
viewer_descs=VIEWER_SELECTS,
|
viewer_descs=VIEWER_SELECTS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ from typing import Any
|
||||||
|
|
||||||
from pyunifiprotect.data import NVR, Camera, Event
|
from pyunifiprotect.data import NVR, Camera, Event
|
||||||
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel
|
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel
|
||||||
|
from pyunifiprotect.data.devices import Sensor
|
||||||
|
|
||||||
from homeassistant.components.sensor import (
|
from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
|
@ -42,7 +43,7 @@ from .entity import (
|
||||||
from .models import ProtectRequiredKeysMixin
|
from .models import ProtectRequiredKeysMixin
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
DETECTED_OBJECT_NONE = "none"
|
OBJECT_TYPE_NONE = "none"
|
||||||
DEVICE_CLASS_DETECTION = "unifiprotect__detection"
|
DEVICE_CLASS_DETECTION = "unifiprotect__detection"
|
||||||
|
|
||||||
|
|
||||||
|
@ -88,6 +89,19 @@ def _get_nvr_memory(obj: Any) -> float | None:
|
||||||
return (1 - memory.available / memory.total) * 100
|
return (1 - memory.available / memory.total) * 100
|
||||||
|
|
||||||
|
|
||||||
|
def _get_alarm_sound(obj: ProtectAdoptableDeviceModel | NVR) -> str:
|
||||||
|
assert isinstance(obj, Sensor)
|
||||||
|
|
||||||
|
alarm_type = OBJECT_TYPE_NONE
|
||||||
|
if (
|
||||||
|
obj.is_alarm_detected
|
||||||
|
and obj.last_alarm_event is not None
|
||||||
|
and obj.last_alarm_event.metadata is not None
|
||||||
|
):
|
||||||
|
alarm_type = obj.last_alarm_event.metadata.alarm_type or OBJECT_TYPE_NONE
|
||||||
|
return alarm_type.lower()
|
||||||
|
|
||||||
|
|
||||||
ALL_DEVICES_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
ALL_DEVICES_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="uptime",
|
key="uptime",
|
||||||
|
@ -210,6 +224,7 @@ SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
||||||
device_class=SensorDeviceClass.ILLUMINANCE,
|
device_class=SensorDeviceClass.ILLUMINANCE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
ufp_value="stats.light.value",
|
ufp_value="stats.light.value",
|
||||||
|
ufp_enabled="is_light_sensor_enabled",
|
||||||
),
|
),
|
||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="humidity_level",
|
key="humidity_level",
|
||||||
|
@ -218,6 +233,7 @@ SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
ufp_value="stats.humidity.value",
|
ufp_value="stats.humidity.value",
|
||||||
|
ufp_enabled="is_humidity_sensor_enabled",
|
||||||
),
|
),
|
||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="temperature_level",
|
key="temperature_level",
|
||||||
|
@ -226,6 +242,13 @@ SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
ufp_value="stats.temperature.value",
|
ufp_value="stats.temperature.value",
|
||||||
|
ufp_enabled="is_temperature_sensor_enabled",
|
||||||
|
),
|
||||||
|
ProtectSensorEntityDescription(
|
||||||
|
key="alarm_sound",
|
||||||
|
name="Alarm Sound Detected",
|
||||||
|
ufp_value_fn=_get_alarm_sound,
|
||||||
|
ufp_enabled="is_alarm_sensor_enabled",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -479,6 +502,6 @@ class ProtectEventSensor(ProtectDeviceSensor, EventThumbnailMixin):
|
||||||
# do not call ProtectDeviceSensor method since we want event to get value here
|
# do not call ProtectDeviceSensor method since we want event to get value here
|
||||||
EventThumbnailMixin._async_update_device_from_protect(self)
|
EventThumbnailMixin._async_update_device_from_protect(self)
|
||||||
if self._event is None:
|
if self._event is None:
|
||||||
self._attr_native_value = DETECTED_OBJECT_NONE
|
self._attr_native_value = OBJECT_TYPE_NONE
|
||||||
else:
|
else:
|
||||||
self._attr_native_value = self._event.smart_detect_types[0].value
|
self._attr_native_value = self._event.smart_detect_types[0].value
|
||||||
|
|
|
@ -152,6 +152,56 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
||||||
|
ProtectSwitchEntityDescription(
|
||||||
|
key="status_light",
|
||||||
|
name="Status Light On",
|
||||||
|
icon="mdi:led-on",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
ufp_value="led_settings.is_enabled",
|
||||||
|
ufp_set_method="set_status_light",
|
||||||
|
),
|
||||||
|
ProtectSwitchEntityDescription(
|
||||||
|
key="motion",
|
||||||
|
name="Motion Detection",
|
||||||
|
icon="mdi:walk",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
ufp_value="motion_settings.is_enabled",
|
||||||
|
ufp_set_method="set_motion_status",
|
||||||
|
),
|
||||||
|
ProtectSwitchEntityDescription(
|
||||||
|
key="temperature",
|
||||||
|
name="Temperature Sensor",
|
||||||
|
icon="mdi:thermometer",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
ufp_value="temperature_settings.is_enabled",
|
||||||
|
ufp_set_method="set_temperature_status",
|
||||||
|
),
|
||||||
|
ProtectSwitchEntityDescription(
|
||||||
|
key="humidity",
|
||||||
|
name="Humidity Sensor",
|
||||||
|
icon="mdi:water-percent",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
ufp_value="humidity_settings.is_enabled",
|
||||||
|
ufp_set_method="set_humidity_status",
|
||||||
|
),
|
||||||
|
ProtectSwitchEntityDescription(
|
||||||
|
key="light",
|
||||||
|
name="Light Sensor",
|
||||||
|
icon="mdi:brightness-5",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
ufp_value="light_settings.is_enabled",
|
||||||
|
ufp_set_method="set_light_status",
|
||||||
|
),
|
||||||
|
ProtectSwitchEntityDescription(
|
||||||
|
key="alarm",
|
||||||
|
name="Alarm Sound Detection",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
ufp_value="alarm_settings.is_enabled",
|
||||||
|
ufp_set_method="set_alarm_status",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
LIGHT_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
LIGHT_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
|
@ -178,6 +228,7 @@ async def async_setup_entry(
|
||||||
all_descs=ALL_DEVICES_SWITCHES,
|
all_descs=ALL_DEVICES_SWITCHES,
|
||||||
camera_descs=CAMERA_SWITCHES,
|
camera_descs=CAMERA_SWITCHES,
|
||||||
light_descs=LIGHT_SWITCHES,
|
light_descs=LIGHT_SWITCHES,
|
||||||
|
sense_descs=SENSE_SWITCHES,
|
||||||
)
|
)
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
|
@ -2015,7 +2015,7 @@ pytrafikverket==0.1.6.2
|
||||||
pyudev==0.22.0
|
pyudev==0.22.0
|
||||||
|
|
||||||
# homeassistant.components.unifiprotect
|
# homeassistant.components.unifiprotect
|
||||||
pyunifiprotect==1.6.2
|
pyunifiprotect==1.6.3
|
||||||
|
|
||||||
# homeassistant.components.uptimerobot
|
# homeassistant.components.uptimerobot
|
||||||
pyuptimerobot==21.11.0
|
pyuptimerobot==21.11.0
|
||||||
|
|
|
@ -1240,7 +1240,7 @@ pytrafikverket==0.1.6.2
|
||||||
pyudev==0.22.0
|
pyudev==0.22.0
|
||||||
|
|
||||||
# homeassistant.components.unifiprotect
|
# homeassistant.components.unifiprotect
|
||||||
pyunifiprotect==1.6.2
|
pyunifiprotect==1.6.3
|
||||||
|
|
||||||
# homeassistant.components.uptimerobot
|
# homeassistant.components.uptimerobot
|
||||||
pyuptimerobot==21.11.0
|
pyuptimerobot==21.11.0
|
||||||
|
|
|
@ -7,8 +7,10 @@ from datetime import datetime, timedelta
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pyunifiprotect.data import Camera, Event, EventType, Light, Sensor
|
from pyunifiprotect.data import Camera, Event, EventType, Light, MountType, Sensor
|
||||||
|
from pyunifiprotect.data.nvr import EventMetadata
|
||||||
|
|
||||||
|
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||||
from homeassistant.components.unifiprotect.binary_sensor import (
|
from homeassistant.components.unifiprotect.binary_sensor import (
|
||||||
CAMERA_SENSORS,
|
CAMERA_SENSORS,
|
||||||
LIGHT_SENSORS,
|
LIGHT_SENSORS,
|
||||||
|
@ -21,9 +23,11 @@ from homeassistant.components.unifiprotect.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION,
|
ATTR_ATTRIBUTION,
|
||||||
|
ATTR_DEVICE_CLASS,
|
||||||
ATTR_LAST_TRIP_TIME,
|
ATTR_LAST_TRIP_TIME,
|
||||||
STATE_OFF,
|
STATE_OFF,
|
||||||
STATE_ON,
|
STATE_ON,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -157,11 +161,15 @@ async def sensor_fixture(
|
||||||
sensor_obj = mock_sensor.copy(deep=True)
|
sensor_obj = mock_sensor.copy(deep=True)
|
||||||
sensor_obj._api = mock_entry.api
|
sensor_obj._api = mock_entry.api
|
||||||
sensor_obj.name = "Test Sensor"
|
sensor_obj.name = "Test Sensor"
|
||||||
|
sensor_obj.mount_type = MountType.DOOR
|
||||||
sensor_obj.is_opened = False
|
sensor_obj.is_opened = False
|
||||||
sensor_obj.battery_status.is_low = False
|
sensor_obj.battery_status.is_low = False
|
||||||
sensor_obj.is_motion_detected = False
|
sensor_obj.is_motion_detected = False
|
||||||
|
sensor_obj.alarm_settings.is_enabled = True
|
||||||
sensor_obj.motion_detected_at = now - timedelta(hours=1)
|
sensor_obj.motion_detected_at = now - timedelta(hours=1)
|
||||||
sensor_obj.open_status_changed_at = now - timedelta(hours=1)
|
sensor_obj.open_status_changed_at = now - timedelta(hours=1)
|
||||||
|
sensor_obj.alarm_triggered_at = now - timedelta(hours=1)
|
||||||
|
sensor_obj.tampering_detected_at = now - timedelta(hours=1)
|
||||||
|
|
||||||
mock_entry.api.bootstrap.reset_objects()
|
mock_entry.api.bootstrap.reset_objects()
|
||||||
mock_entry.api.bootstrap.nvr.system_info.storage.devices = []
|
mock_entry.api.bootstrap.nvr.system_info.storage.devices = []
|
||||||
|
@ -172,7 +180,43 @@ async def sensor_fixture(
|
||||||
await hass.config_entries.async_setup(mock_entry.entry.entry_id)
|
await hass.config_entries.async_setup(mock_entry.entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert_entity_counts(hass, Platform.BINARY_SENSOR, 3, 3)
|
assert_entity_counts(hass, Platform.BINARY_SENSOR, 4, 4)
|
||||||
|
|
||||||
|
yield sensor_obj
|
||||||
|
|
||||||
|
Sensor.__config__.validate_assignment = True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="sensor_none")
|
||||||
|
async def sensor_none_fixture(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_entry: MockEntityFixture,
|
||||||
|
mock_sensor: Sensor,
|
||||||
|
now: datetime,
|
||||||
|
):
|
||||||
|
"""Fixture for a single sensor for testing the binary_sensor platform."""
|
||||||
|
|
||||||
|
# disable pydantic validation so mocking can happen
|
||||||
|
Sensor.__config__.validate_assignment = False
|
||||||
|
|
||||||
|
sensor_obj = mock_sensor.copy(deep=True)
|
||||||
|
sensor_obj._api = mock_entry.api
|
||||||
|
sensor_obj.name = "Test Sensor"
|
||||||
|
sensor_obj.mount_type = MountType.LEAK
|
||||||
|
sensor_obj.battery_status.is_low = False
|
||||||
|
sensor_obj.alarm_settings.is_enabled = False
|
||||||
|
sensor_obj.tampering_detected_at = now - timedelta(hours=1)
|
||||||
|
|
||||||
|
mock_entry.api.bootstrap.reset_objects()
|
||||||
|
mock_entry.api.bootstrap.nvr.system_info.storage.devices = []
|
||||||
|
mock_entry.api.bootstrap.sensors = {
|
||||||
|
sensor_obj.id: sensor_obj,
|
||||||
|
}
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(mock_entry.entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert_entity_counts(hass, Platform.BINARY_SENSOR, 4, 4)
|
||||||
|
|
||||||
yield sensor_obj
|
yield sensor_obj
|
||||||
|
|
||||||
|
@ -308,6 +352,35 @@ async def test_binary_sensor_setup_sensor(
|
||||||
assert state.attributes[ATTR_LAST_TRIP_TIME] == expected_trip_time
|
assert state.attributes[ATTR_LAST_TRIP_TIME] == expected_trip_time
|
||||||
|
|
||||||
|
|
||||||
|
async def test_binary_sensor_setup_sensor_none(
|
||||||
|
hass: HomeAssistant, sensor_none: Sensor
|
||||||
|
):
|
||||||
|
"""Test binary_sensor entity setup for sensor with most sensors disabled."""
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_OFF,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_OFF,
|
||||||
|
]
|
||||||
|
for index, description in enumerate(SENSE_SENSORS):
|
||||||
|
unique_id, entity_id = ids_from_device_description(
|
||||||
|
Platform.BINARY_SENSOR, sensor_none, description
|
||||||
|
)
|
||||||
|
|
||||||
|
entity = entity_registry.async_get(entity_id)
|
||||||
|
assert entity
|
||||||
|
assert entity.unique_id == unique_id
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
print(entity_id)
|
||||||
|
assert state.state == expected[index]
|
||||||
|
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
||||||
|
|
||||||
|
|
||||||
async def test_binary_sensor_update_motion(
|
async def test_binary_sensor_update_motion(
|
||||||
hass: HomeAssistant, mock_entry: MockEntityFixture, camera: Camera, now: datetime
|
hass: HomeAssistant, mock_entry: MockEntityFixture, camera: Camera, now: datetime
|
||||||
):
|
):
|
||||||
|
@ -348,3 +421,109 @@ async def test_binary_sensor_update_motion(
|
||||||
assert state.state == STATE_ON
|
assert state.state == STATE_ON
|
||||||
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
||||||
assert state.attributes[ATTR_EVENT_SCORE] == 100
|
assert state.attributes[ATTR_EVENT_SCORE] == 100
|
||||||
|
|
||||||
|
|
||||||
|
async def test_binary_sensor_update_light_motion(
|
||||||
|
hass: HomeAssistant, mock_entry: MockEntityFixture, light: Light, now: datetime
|
||||||
|
):
|
||||||
|
"""Test binary_sensor motion entity."""
|
||||||
|
|
||||||
|
_, entity_id = ids_from_device_description(
|
||||||
|
Platform.BINARY_SENSOR, light, LIGHT_SENSORS[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
event_metadata = EventMetadata(light_id=light.id)
|
||||||
|
event = Event(
|
||||||
|
id="test_event_id",
|
||||||
|
type=EventType.MOTION_LIGHT,
|
||||||
|
start=now - timedelta(seconds=1),
|
||||||
|
end=None,
|
||||||
|
score=100,
|
||||||
|
smart_detect_types=[],
|
||||||
|
smart_detect_event_ids=[],
|
||||||
|
metadata=event_metadata,
|
||||||
|
api=mock_entry.api,
|
||||||
|
)
|
||||||
|
|
||||||
|
new_bootstrap = copy(mock_entry.api.bootstrap)
|
||||||
|
new_light = light.copy()
|
||||||
|
new_light.is_pir_motion_detected = True
|
||||||
|
new_light.last_motion_event_id = event.id
|
||||||
|
|
||||||
|
mock_msg = Mock()
|
||||||
|
mock_msg.changed_data = {}
|
||||||
|
mock_msg.new_obj = event
|
||||||
|
|
||||||
|
new_bootstrap.lights = {new_light.id: new_light}
|
||||||
|
new_bootstrap.events = {event.id: event}
|
||||||
|
mock_entry.api.bootstrap = new_bootstrap
|
||||||
|
mock_entry.api.ws_subscription(mock_msg)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
|
||||||
|
|
||||||
|
async def test_binary_sensor_update_mount_type_window(
|
||||||
|
hass: HomeAssistant, mock_entry: MockEntityFixture, sensor: Sensor
|
||||||
|
):
|
||||||
|
"""Test binary_sensor motion entity."""
|
||||||
|
|
||||||
|
_, entity_id = ids_from_device_description(
|
||||||
|
Platform.BINARY_SENSOR, sensor, SENSE_SENSORS[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert state.attributes[ATTR_DEVICE_CLASS] == BinarySensorDeviceClass.DOOR.value
|
||||||
|
|
||||||
|
new_bootstrap = copy(mock_entry.api.bootstrap)
|
||||||
|
new_sensor = sensor.copy()
|
||||||
|
new_sensor.mount_type = MountType.WINDOW
|
||||||
|
|
||||||
|
mock_msg = Mock()
|
||||||
|
mock_msg.changed_data = {}
|
||||||
|
mock_msg.new_obj = new_sensor
|
||||||
|
|
||||||
|
new_bootstrap.sensors = {new_sensor.id: new_sensor}
|
||||||
|
mock_entry.api.bootstrap = new_bootstrap
|
||||||
|
mock_entry.api.ws_subscription(mock_msg)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert state.attributes[ATTR_DEVICE_CLASS] == BinarySensorDeviceClass.WINDOW.value
|
||||||
|
|
||||||
|
|
||||||
|
async def test_binary_sensor_update_mount_type_garage(
|
||||||
|
hass: HomeAssistant, mock_entry: MockEntityFixture, sensor: Sensor
|
||||||
|
):
|
||||||
|
"""Test binary_sensor motion entity."""
|
||||||
|
|
||||||
|
_, entity_id = ids_from_device_description(
|
||||||
|
Platform.BINARY_SENSOR, sensor, SENSE_SENSORS[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert state.attributes[ATTR_DEVICE_CLASS] == BinarySensorDeviceClass.DOOR.value
|
||||||
|
|
||||||
|
new_bootstrap = copy(mock_entry.api.bootstrap)
|
||||||
|
new_sensor = sensor.copy()
|
||||||
|
new_sensor.mount_type = MountType.GARAGE
|
||||||
|
|
||||||
|
mock_msg = Mock()
|
||||||
|
mock_msg.changed_data = {}
|
||||||
|
mock_msg.new_obj = new_sensor
|
||||||
|
|
||||||
|
new_bootstrap.sensors = {new_sensor.id: new_sensor}
|
||||||
|
mock_entry.api.bootstrap = new_bootstrap
|
||||||
|
mock_entry.api.ws_subscription(mock_msg)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert (
|
||||||
|
state.attributes[ATTR_DEVICE_CLASS] == BinarySensorDeviceClass.GARAGE_DOOR.value
|
||||||
|
)
|
||||||
|
|
|
@ -9,6 +9,7 @@ from unittest.mock import Mock
|
||||||
import pytest
|
import pytest
|
||||||
from pyunifiprotect.data import NVR, Camera, Event, Sensor
|
from pyunifiprotect.data import NVR, Camera, Event, Sensor
|
||||||
from pyunifiprotect.data.base import WifiConnectionState, WiredConnectionState
|
from pyunifiprotect.data.base import WifiConnectionState, WiredConnectionState
|
||||||
|
from pyunifiprotect.data.nvr import EventMetadata
|
||||||
from pyunifiprotect.data.types import EventType, SmartDetectObjectType
|
from pyunifiprotect.data.types import EventType, SmartDetectObjectType
|
||||||
|
|
||||||
from homeassistant.components.unifiprotect.const import (
|
from homeassistant.components.unifiprotect.const import (
|
||||||
|
@ -19,13 +20,18 @@ from homeassistant.components.unifiprotect.sensor import (
|
||||||
ALL_DEVICES_SENSORS,
|
ALL_DEVICES_SENSORS,
|
||||||
CAMERA_DISABLED_SENSORS,
|
CAMERA_DISABLED_SENSORS,
|
||||||
CAMERA_SENSORS,
|
CAMERA_SENSORS,
|
||||||
DETECTED_OBJECT_NONE,
|
|
||||||
MOTION_SENSORS,
|
MOTION_SENSORS,
|
||||||
NVR_DISABLED_SENSORS,
|
NVR_DISABLED_SENSORS,
|
||||||
NVR_SENSORS,
|
NVR_SENSORS,
|
||||||
|
OBJECT_TYPE_NONE,
|
||||||
SENSE_SENSORS,
|
SENSE_SENSORS,
|
||||||
)
|
)
|
||||||
from homeassistant.const import ATTR_ATTRIBUTION, STATE_UNKNOWN, Platform
|
from homeassistant.const import (
|
||||||
|
ATTR_ATTRIBUTION,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
Platform,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
@ -53,6 +59,10 @@ async def sensor_fixture(
|
||||||
sensor_obj._api = mock_entry.api
|
sensor_obj._api = mock_entry.api
|
||||||
sensor_obj.name = "Test Sensor"
|
sensor_obj.name = "Test Sensor"
|
||||||
sensor_obj.battery_status.percentage = 10.0
|
sensor_obj.battery_status.percentage = 10.0
|
||||||
|
sensor_obj.light_settings.is_enabled = True
|
||||||
|
sensor_obj.humidity_settings.is_enabled = True
|
||||||
|
sensor_obj.temperature_settings.is_enabled = True
|
||||||
|
sensor_obj.alarm_settings.is_enabled = True
|
||||||
sensor_obj.stats.light.value = 10.0
|
sensor_obj.stats.light.value = 10.0
|
||||||
sensor_obj.stats.humidity.value = 10.0
|
sensor_obj.stats.humidity.value = 10.0
|
||||||
sensor_obj.stats.temperature.value = 10.0
|
sensor_obj.stats.temperature.value = 10.0
|
||||||
|
@ -68,7 +78,46 @@ async def sensor_fixture(
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
# 2 from all, 4 from sense, 12 NVR
|
# 2 from all, 4 from sense, 12 NVR
|
||||||
assert_entity_counts(hass, Platform.SENSOR, 18, 13)
|
assert_entity_counts(hass, Platform.SENSOR, 19, 14)
|
||||||
|
|
||||||
|
yield sensor_obj
|
||||||
|
|
||||||
|
Sensor.__config__.validate_assignment = True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="sensor_none")
|
||||||
|
async def sensor_none_fixture(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_entry: MockEntityFixture,
|
||||||
|
mock_sensor: Sensor,
|
||||||
|
now: datetime,
|
||||||
|
):
|
||||||
|
"""Fixture for a single sensor for testing the sensor platform."""
|
||||||
|
|
||||||
|
# disable pydantic validation so mocking can happen
|
||||||
|
Sensor.__config__.validate_assignment = False
|
||||||
|
|
||||||
|
sensor_obj = mock_sensor.copy(deep=True)
|
||||||
|
sensor_obj._api = mock_entry.api
|
||||||
|
sensor_obj.name = "Test Sensor"
|
||||||
|
sensor_obj.battery_status.percentage = 10.0
|
||||||
|
sensor_obj.light_settings.is_enabled = False
|
||||||
|
sensor_obj.humidity_settings.is_enabled = False
|
||||||
|
sensor_obj.temperature_settings.is_enabled = False
|
||||||
|
sensor_obj.alarm_settings.is_enabled = False
|
||||||
|
sensor_obj.up_since = now
|
||||||
|
sensor_obj.bluetooth_connection_state.signal_strength = -50.0
|
||||||
|
|
||||||
|
mock_entry.api.bootstrap.reset_objects()
|
||||||
|
mock_entry.api.bootstrap.sensors = {
|
||||||
|
sensor_obj.id: sensor_obj,
|
||||||
|
}
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(mock_entry.entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# 2 from all, 4 from sense, 12 NVR
|
||||||
|
assert_entity_counts(hass, Platform.SENSOR, 19, 14)
|
||||||
|
|
||||||
yield sensor_obj
|
yield sensor_obj
|
||||||
|
|
||||||
|
@ -131,7 +180,7 @@ async def test_sensor_setup_sensor(
|
||||||
|
|
||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
expected_values = ("10", "10.0", "10.0", "10.0")
|
expected_values = ("10", "10.0", "10.0", "10.0", "none")
|
||||||
for index, description in enumerate(SENSE_SENSORS):
|
for index, description in enumerate(SENSE_SENSORS):
|
||||||
unique_id, entity_id = ids_from_device_description(
|
unique_id, entity_id = ids_from_device_description(
|
||||||
Platform.SENSOR, sensor, description
|
Platform.SENSOR, sensor, description
|
||||||
|
@ -164,6 +213,35 @@ async def test_sensor_setup_sensor(
|
||||||
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_setup_sensor_none(
|
||||||
|
hass: HomeAssistant, mock_entry: MockEntityFixture, sensor_none: Sensor
|
||||||
|
):
|
||||||
|
"""Test sensor entity setup for sensor devices with no sensors enabled."""
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
expected_values = (
|
||||||
|
"10",
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
)
|
||||||
|
for index, description in enumerate(SENSE_SENSORS):
|
||||||
|
unique_id, entity_id = ids_from_device_description(
|
||||||
|
Platform.SENSOR, sensor_none, description
|
||||||
|
)
|
||||||
|
|
||||||
|
entity = entity_registry.async_get(entity_id)
|
||||||
|
assert entity
|
||||||
|
assert entity.unique_id == unique_id
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert state.state == expected_values[index]
|
||||||
|
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
||||||
|
|
||||||
|
|
||||||
async def test_sensor_setup_nvr(
|
async def test_sensor_setup_nvr(
|
||||||
hass: HomeAssistant, mock_entry: MockEntityFixture, now: datetime
|
hass: HomeAssistant, mock_entry: MockEntityFixture, now: datetime
|
||||||
):
|
):
|
||||||
|
@ -403,7 +481,7 @@ async def test_sensor_setup_camera(
|
||||||
|
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
assert state
|
assert state
|
||||||
assert state.state == DETECTED_OBJECT_NONE
|
assert state.state == OBJECT_TYPE_NONE
|
||||||
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
||||||
assert state.attributes[ATTR_EVENT_SCORE] == 0
|
assert state.attributes[ATTR_EVENT_SCORE] == 0
|
||||||
|
|
||||||
|
@ -426,6 +504,7 @@ async def test_sensor_update_motion(
|
||||||
smart_detect_types=[SmartDetectObjectType.PERSON],
|
smart_detect_types=[SmartDetectObjectType.PERSON],
|
||||||
smart_detect_event_ids=[],
|
smart_detect_event_ids=[],
|
||||||
camera_id=camera.id,
|
camera_id=camera.id,
|
||||||
|
api=mock_entry.api,
|
||||||
)
|
)
|
||||||
|
|
||||||
new_bootstrap = copy(mock_entry.api.bootstrap)
|
new_bootstrap = copy(mock_entry.api.bootstrap)
|
||||||
|
@ -435,7 +514,7 @@ async def test_sensor_update_motion(
|
||||||
|
|
||||||
mock_msg = Mock()
|
mock_msg = Mock()
|
||||||
mock_msg.changed_data = {}
|
mock_msg.changed_data = {}
|
||||||
mock_msg.new_obj = new_camera
|
mock_msg.new_obj = event
|
||||||
|
|
||||||
new_bootstrap.cameras = {new_camera.id: new_camera}
|
new_bootstrap.cameras = {new_camera.id: new_camera}
|
||||||
new_bootstrap.events = {event.id: event}
|
new_bootstrap.events = {event.id: event}
|
||||||
|
@ -448,3 +527,45 @@ async def test_sensor_update_motion(
|
||||||
assert state.state == SmartDetectObjectType.PERSON.value
|
assert state.state == SmartDetectObjectType.PERSON.value
|
||||||
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
assert state.attributes[ATTR_ATTRIBUTION] == DEFAULT_ATTRIBUTION
|
||||||
assert state.attributes[ATTR_EVENT_SCORE] == 100
|
assert state.attributes[ATTR_EVENT_SCORE] == 100
|
||||||
|
|
||||||
|
|
||||||
|
async def test_sensor_update_alarm(
|
||||||
|
hass: HomeAssistant, mock_entry: MockEntityFixture, sensor: Sensor, now: datetime
|
||||||
|
):
|
||||||
|
"""Test sensor motion entity."""
|
||||||
|
|
||||||
|
_, entity_id = ids_from_device_description(
|
||||||
|
Platform.SENSOR, sensor, SENSE_SENSORS[4]
|
||||||
|
)
|
||||||
|
|
||||||
|
event_metadata = EventMetadata(sensor_id=sensor.id, alarm_type="smoke")
|
||||||
|
event = Event(
|
||||||
|
id="test_event_id",
|
||||||
|
type=EventType.SENSOR_ALARM,
|
||||||
|
start=now - timedelta(seconds=1),
|
||||||
|
end=None,
|
||||||
|
score=100,
|
||||||
|
smart_detect_types=[],
|
||||||
|
smart_detect_event_ids=[],
|
||||||
|
metadata=event_metadata,
|
||||||
|
api=mock_entry.api,
|
||||||
|
)
|
||||||
|
|
||||||
|
new_bootstrap = copy(mock_entry.api.bootstrap)
|
||||||
|
new_sensor = sensor.copy()
|
||||||
|
new_sensor.set_alarm_timeout()
|
||||||
|
new_sensor.last_alarm_event_id = event.id
|
||||||
|
|
||||||
|
mock_msg = Mock()
|
||||||
|
mock_msg.changed_data = {}
|
||||||
|
mock_msg.new_obj = event
|
||||||
|
|
||||||
|
new_bootstrap.sensors = {new_sensor.id: new_sensor}
|
||||||
|
new_bootstrap.events = {event.id: event}
|
||||||
|
mock_entry.api.bootstrap = new_bootstrap
|
||||||
|
mock_entry.api.ws_subscription(mock_msg)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(entity_id)
|
||||||
|
assert state
|
||||||
|
assert state.state == "smoke"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue