mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Add basic Doorlock support for UniFi protect (#64877)
This commit is contained in:
parent
2aaca346bd
commit
6cb9f0df2a
@ -126,6 +126,16 @@ MOTION_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DOORLOCK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
||||||
|
ProtectBinaryEntityDescription(
|
||||||
|
key="battery_low",
|
||||||
|
name="Battery low",
|
||||||
|
device_class=BinarySensorDeviceClass.BATTERY,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
ufp_value="battery_status.is_low",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
DISK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
DISK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
@ -150,6 +160,7 @@ async def async_setup_entry(
|
|||||||
camera_descs=CAMERA_SENSORS,
|
camera_descs=CAMERA_SENSORS,
|
||||||
light_descs=LIGHT_SENSORS,
|
light_descs=LIGHT_SENSORS,
|
||||||
sense_descs=SENSE_SENSORS,
|
sense_descs=SENSE_SENSORS,
|
||||||
|
lock_descs=DOORLOCK_SENSORS,
|
||||||
)
|
)
|
||||||
entities += _async_motion_entities(data)
|
entities += _async_motion_entities(data)
|
||||||
entities += _async_nvr_entities(data)
|
entities += _async_nvr_entities(data)
|
||||||
|
@ -37,6 +37,7 @@ DEVICES_THAT_ADOPT = {
|
|||||||
ModelType.LIGHT,
|
ModelType.LIGHT,
|
||||||
ModelType.VIEWPORT,
|
ModelType.VIEWPORT,
|
||||||
ModelType.SENSOR,
|
ModelType.SENSOR,
|
||||||
|
ModelType.DOORLOCK,
|
||||||
}
|
}
|
||||||
DEVICES_WITH_ENTITIES = DEVICES_THAT_ADOPT | {ModelType.NVR}
|
DEVICES_WITH_ENTITIES = DEVICES_THAT_ADOPT | {ModelType.NVR}
|
||||||
DEVICES_FOR_SUBSCRIBE = DEVICES_WITH_ENTITIES | {ModelType.EVENT}
|
DEVICES_FOR_SUBSCRIBE = DEVICES_WITH_ENTITIES | {ModelType.EVENT}
|
||||||
|
@ -7,6 +7,7 @@ from typing import Any
|
|||||||
|
|
||||||
from pyunifiprotect.data import (
|
from pyunifiprotect.data import (
|
||||||
Camera,
|
Camera,
|
||||||
|
Doorlock,
|
||||||
Event,
|
Event,
|
||||||
Light,
|
Light,
|
||||||
ModelType,
|
ModelType,
|
||||||
@ -41,7 +42,7 @@ def _async_device_entities(
|
|||||||
|
|
||||||
entities: list[ProtectDeviceEntity] = []
|
entities: list[ProtectDeviceEntity] = []
|
||||||
for device in data.get_by_types({model_type}):
|
for device in data.get_by_types({model_type}):
|
||||||
assert isinstance(device, (Camera, Light, Sensor, Viewer))
|
assert isinstance(device, (Camera, Light, Sensor, Viewer, Doorlock))
|
||||||
for description in descs:
|
for description in descs:
|
||||||
assert isinstance(description, EntityDescription)
|
assert isinstance(description, EntityDescription)
|
||||||
if description.ufp_required_field:
|
if description.ufp_required_field:
|
||||||
@ -74,6 +75,7 @@ def async_all_device_entities(
|
|||||||
light_descs: Sequence[ProtectRequiredKeysMixin] | None = None,
|
light_descs: Sequence[ProtectRequiredKeysMixin] | None = None,
|
||||||
sense_descs: Sequence[ProtectRequiredKeysMixin] | None = None,
|
sense_descs: Sequence[ProtectRequiredKeysMixin] | None = None,
|
||||||
viewer_descs: Sequence[ProtectRequiredKeysMixin] | None = None,
|
viewer_descs: Sequence[ProtectRequiredKeysMixin] | None = None,
|
||||||
|
lock_descs: Sequence[ProtectRequiredKeysMixin] | None = None,
|
||||||
all_descs: Sequence[ProtectRequiredKeysMixin] | None = None,
|
all_descs: Sequence[ProtectRequiredKeysMixin] | None = None,
|
||||||
) -> list[ProtectDeviceEntity]:
|
) -> list[ProtectDeviceEntity]:
|
||||||
"""Generate a list of all the device entities."""
|
"""Generate a list of all the device entities."""
|
||||||
@ -82,12 +84,14 @@ def async_all_device_entities(
|
|||||||
light_descs = list(light_descs or []) + all_descs
|
light_descs = list(light_descs or []) + all_descs
|
||||||
sense_descs = list(sense_descs or []) + all_descs
|
sense_descs = list(sense_descs or []) + all_descs
|
||||||
viewer_descs = list(viewer_descs or []) + all_descs
|
viewer_descs = list(viewer_descs or []) + all_descs
|
||||||
|
lock_descs = list(lock_descs or []) + all_descs
|
||||||
|
|
||||||
return (
|
return (
|
||||||
_async_device_entities(data, klass, ModelType.CAMERA, camera_descs)
|
_async_device_entities(data, klass, ModelType.CAMERA, camera_descs)
|
||||||
+ _async_device_entities(data, klass, ModelType.LIGHT, light_descs)
|
+ _async_device_entities(data, klass, ModelType.LIGHT, light_descs)
|
||||||
+ _async_device_entities(data, klass, ModelType.SENSOR, sense_descs)
|
+ _async_device_entities(data, klass, ModelType.SENSOR, sense_descs)
|
||||||
+ _async_device_entities(data, klass, ModelType.VIEWPORT, viewer_descs)
|
+ _async_device_entities(data, klass, ModelType.VIEWPORT, viewer_descs)
|
||||||
|
+ _async_device_entities(data, klass, ModelType.DOORLOCK, lock_descs)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,10 +5,11 @@ from dataclasses import dataclass
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Generic
|
from typing import Generic
|
||||||
|
|
||||||
from pyunifiprotect.data.devices import Camera, Light
|
from pyunifiprotect.data.devices import Camera, Doorlock, Light
|
||||||
|
|
||||||
from homeassistant.components.number import NumberEntity, NumberEntityDescription
|
from homeassistant.components.number import NumberEntity, NumberEntityDescription
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.const import TIME_SECONDS
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers.entity import EntityCategory
|
from homeassistant.helpers.entity import EntityCategory
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
@ -43,6 +44,14 @@ async def _set_pir_duration(obj: Light, value: float) -> None:
|
|||||||
await obj.set_duration(timedelta(seconds=value))
|
await obj.set_duration(timedelta(seconds=value))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_auto_close(obj: Doorlock) -> int:
|
||||||
|
return int(obj.auto_close_time.total_seconds())
|
||||||
|
|
||||||
|
|
||||||
|
async def _set_auto_close(obj: Doorlock, value: float) -> None:
|
||||||
|
await obj.set_auto_close_time(timedelta(seconds=value))
|
||||||
|
|
||||||
|
|
||||||
CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
||||||
ProtectNumberEntityDescription(
|
ProtectNumberEntityDescription(
|
||||||
key="wdr_value",
|
key="wdr_value",
|
||||||
@ -100,6 +109,7 @@ LIGHT_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
name="Auto-shutoff Duration",
|
name="Auto-shutoff Duration",
|
||||||
icon="mdi:camera-timer",
|
icon="mdi:camera-timer",
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
unit_of_measurement=TIME_SECONDS,
|
||||||
ufp_min=15,
|
ufp_min=15,
|
||||||
ufp_max=900,
|
ufp_max=900,
|
||||||
ufp_step=15,
|
ufp_step=15,
|
||||||
@ -124,6 +134,22 @@ SENSE_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DOORLOCK_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
||||||
|
ProtectNumberEntityDescription[Doorlock](
|
||||||
|
key="auto_lock_time",
|
||||||
|
name="Auto-lock Timeout",
|
||||||
|
icon="mdi:walk",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
unit_of_measurement=TIME_SECONDS,
|
||||||
|
ufp_min=0,
|
||||||
|
ufp_max=3600,
|
||||||
|
ufp_step=15,
|
||||||
|
ufp_required_field=None,
|
||||||
|
ufp_value_fn=_get_auto_close,
|
||||||
|
ufp_set_method_fn=_set_auto_close,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -138,6 +164,7 @@ async def async_setup_entry(
|
|||||||
camera_descs=CAMERA_NUMBERS,
|
camera_descs=CAMERA_NUMBERS,
|
||||||
light_descs=LIGHT_NUMBERS,
|
light_descs=LIGHT_NUMBERS,
|
||||||
sense_descs=SENSE_NUMBERS,
|
sense_descs=SENSE_NUMBERS,
|
||||||
|
lock_descs=DOORLOCK_NUMBERS,
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
@ -12,14 +12,15 @@ from pyunifiprotect.api import ProtectApiClient
|
|||||||
from pyunifiprotect.data import (
|
from pyunifiprotect.data import (
|
||||||
Camera,
|
Camera,
|
||||||
DoorbellMessageType,
|
DoorbellMessageType,
|
||||||
|
Doorlock,
|
||||||
IRLEDMode,
|
IRLEDMode,
|
||||||
Light,
|
Light,
|
||||||
LightModeEnableType,
|
LightModeEnableType,
|
||||||
LightModeType,
|
LightModeType,
|
||||||
RecordingMode,
|
RecordingMode,
|
||||||
|
Sensor,
|
||||||
Viewer,
|
Viewer,
|
||||||
)
|
)
|
||||||
from pyunifiprotect.data.devices import Sensor
|
|
||||||
from pyunifiprotect.data.types import ChimeType, MountType
|
from pyunifiprotect.data.types import ChimeType, MountType
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -165,7 +166,7 @@ async def _set_light_mode(obj: Light, mode: str) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def _set_paired_camera(obj: Light | Sensor, camera_id: str) -> None:
|
async def _set_paired_camera(obj: Light | Sensor | Doorlock, camera_id: str) -> None:
|
||||||
if camera_id == TYPE_EMPTY_VALUE:
|
if camera_id == TYPE_EMPTY_VALUE:
|
||||||
camera: Camera | None = None
|
camera: Camera | None = None
|
||||||
else:
|
else:
|
||||||
@ -276,6 +277,18 @@ SENSE_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DOORLOCK_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
||||||
|
ProtectSelectEntityDescription[Doorlock](
|
||||||
|
key="paired_camera",
|
||||||
|
name="Paired Camera",
|
||||||
|
icon="mdi:cctv",
|
||||||
|
entity_category=EntityCategory.CONFIG,
|
||||||
|
ufp_value="camera_id",
|
||||||
|
ufp_options_fn=_get_paired_camera_options,
|
||||||
|
ufp_set_method_fn=_set_paired_camera,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
VIEWER_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
VIEWER_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
||||||
ProtectSelectEntityDescription[Viewer](
|
ProtectSelectEntityDescription[Viewer](
|
||||||
key="viewer",
|
key="viewer",
|
||||||
@ -303,6 +316,7 @@ async def async_setup_entry(
|
|||||||
light_descs=LIGHT_SELECTS,
|
light_descs=LIGHT_SELECTS,
|
||||||
sense_descs=SENSE_SELECTS,
|
sense_descs=SENSE_SELECTS,
|
||||||
viewer_descs=VIEWER_SELECTS,
|
viewer_descs=VIEWER_SELECTS,
|
||||||
|
lock_descs=DOORLOCK_SELECTS,
|
||||||
)
|
)
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
@ -254,6 +254,18 @@ SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DOORLOCK_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
||||||
|
ProtectSensorEntityDescription(
|
||||||
|
key="battery_level",
|
||||||
|
name="Battery Level",
|
||||||
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
|
device_class=SensorDeviceClass.BATTERY,
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
ufp_value="battery_status.percentage",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
||||||
ProtectSensorEntityDescription[ProtectDeviceModel](
|
ProtectSensorEntityDescription[ProtectDeviceModel](
|
||||||
key="uptime",
|
key="uptime",
|
||||||
@ -400,6 +412,7 @@ async def async_setup_entry(
|
|||||||
all_descs=ALL_DEVICES_SENSORS,
|
all_descs=ALL_DEVICES_SENSORS,
|
||||||
camera_descs=CAMERA_SENSORS + CAMERA_DISABLED_SENSORS,
|
camera_descs=CAMERA_SENSORS + CAMERA_DISABLED_SENSORS,
|
||||||
sense_descs=SENSE_SENSORS,
|
sense_descs=SENSE_SENSORS,
|
||||||
|
lock_descs=DOORLOCK_SENSORS,
|
||||||
)
|
)
|
||||||
entities += _async_motion_entities(data)
|
entities += _async_motion_entities(data)
|
||||||
entities += _async_nvr_entities(data)
|
entities += _async_nvr_entities(data)
|
||||||
|
@ -214,6 +214,17 @@ LIGHT_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DOORLOCK_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",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -229,6 +240,7 @@ async def async_setup_entry(
|
|||||||
camera_descs=CAMERA_SWITCHES,
|
camera_descs=CAMERA_SWITCHES,
|
||||||
light_descs=LIGHT_SWITCHES,
|
light_descs=LIGHT_SWITCHES,
|
||||||
sense_descs=SENSE_SWITCHES,
|
sense_descs=SENSE_SWITCHES,
|
||||||
|
lock_descs=DOORLOCK_SWITCHES,
|
||||||
)
|
)
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
@ -11,10 +11,17 @@ from typing import Any
|
|||||||
from unittest.mock import AsyncMock, Mock, patch
|
from unittest.mock import AsyncMock, Mock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pyunifiprotect.data import Camera, Light, WSSubscriptionMessage
|
from pyunifiprotect.data import (
|
||||||
|
NVR,
|
||||||
|
Camera,
|
||||||
|
Doorlock,
|
||||||
|
Light,
|
||||||
|
Liveview,
|
||||||
|
Sensor,
|
||||||
|
Viewer,
|
||||||
|
WSSubscriptionMessage,
|
||||||
|
)
|
||||||
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel
|
from pyunifiprotect.data.base import ProtectAdoptableDeviceModel
|
||||||
from pyunifiprotect.data.devices import Sensor, Viewer
|
|
||||||
from pyunifiprotect.data.nvr import NVR, Liveview
|
|
||||||
|
|
||||||
from homeassistant.components.unifiprotect.const import DOMAIN
|
from homeassistant.components.unifiprotect.const import DOMAIN
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
@ -41,6 +48,7 @@ class MockBootstrap:
|
|||||||
viewers: dict[str, Any]
|
viewers: dict[str, Any]
|
||||||
liveviews: dict[str, Any]
|
liveviews: dict[str, Any]
|
||||||
events: dict[str, Any]
|
events: dict[str, Any]
|
||||||
|
doorlocks: dict[str, Any]
|
||||||
|
|
||||||
def reset_objects(self) -> None:
|
def reset_objects(self) -> None:
|
||||||
"""Reset all devices on bootstrap for tests."""
|
"""Reset all devices on bootstrap for tests."""
|
||||||
@ -50,6 +58,7 @@ class MockBootstrap:
|
|||||||
self.viewers = {}
|
self.viewers = {}
|
||||||
self.liveviews = {}
|
self.liveviews = {}
|
||||||
self.events = {}
|
self.events = {}
|
||||||
|
self.doorlocks = {}
|
||||||
|
|
||||||
def process_ws_packet(self, msg: WSSubscriptionMessage) -> None:
|
def process_ws_packet(self, msg: WSSubscriptionMessage) -> None:
|
||||||
"""Fake process method for tests."""
|
"""Fake process method for tests."""
|
||||||
@ -117,6 +126,7 @@ def mock_bootstrap_fixture(mock_nvr: NVR):
|
|||||||
viewers={},
|
viewers={},
|
||||||
liveviews={},
|
liveviews={},
|
||||||
events={},
|
events={},
|
||||||
|
doorlocks={},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -164,7 +174,7 @@ def mock_entry(
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_liveview():
|
def mock_liveview():
|
||||||
"""Mock UniFi Protect Camera device."""
|
"""Mock UniFi Protect Liveview."""
|
||||||
|
|
||||||
data = json.loads(load_fixture("sample_liveview.json", integration=DOMAIN))
|
data = json.loads(load_fixture("sample_liveview.json", integration=DOMAIN))
|
||||||
return Liveview.from_unifi_dict(**data)
|
return Liveview.from_unifi_dict(**data)
|
||||||
@ -180,7 +190,7 @@ def mock_camera():
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_light():
|
def mock_light():
|
||||||
"""Mock UniFi Protect Camera device."""
|
"""Mock UniFi Protect Light device."""
|
||||||
|
|
||||||
data = json.loads(load_fixture("sample_light.json", integration=DOMAIN))
|
data = json.loads(load_fixture("sample_light.json", integration=DOMAIN))
|
||||||
return Light.from_unifi_dict(**data)
|
return Light.from_unifi_dict(**data)
|
||||||
@ -202,6 +212,14 @@ def mock_sensor():
|
|||||||
return Sensor.from_unifi_dict(**data)
|
return Sensor.from_unifi_dict(**data)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_doorlock():
|
||||||
|
"""Mock UniFi Protect Doorlock device."""
|
||||||
|
|
||||||
|
data = json.loads(load_fixture("sample_doorlock.json", integration=DOMAIN))
|
||||||
|
return Doorlock.from_unifi_dict(**data)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def now():
|
def now():
|
||||||
"""Return datetime object that will be consistent throughout test."""
|
"""Return datetime object that will be consistent throughout test."""
|
||||||
|
52
tests/components/unifiprotect/fixtures/sample_doorlock.json
Normal file
52
tests/components/unifiprotect/fixtures/sample_doorlock.json
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"mac": "F10599AB6955",
|
||||||
|
"host": null,
|
||||||
|
"connectionHost": "192.168.102.63",
|
||||||
|
"type": "UFP-LOCK-R",
|
||||||
|
"name": "Wkltg Qcjxv",
|
||||||
|
"upSince": 1643050461849,
|
||||||
|
"uptime": null,
|
||||||
|
"lastSeen": 1643052750858,
|
||||||
|
"connectedSince": 1643052765849,
|
||||||
|
"state": "CONNECTED",
|
||||||
|
"hardwareRevision": 7,
|
||||||
|
"firmwareVersion": "1.2.0",
|
||||||
|
"latestFirmwareVersion": "1.2.0",
|
||||||
|
"firmwareBuild": null,
|
||||||
|
"isUpdating": false,
|
||||||
|
"isAdopting": false,
|
||||||
|
"isAdopted": true,
|
||||||
|
"isAdoptedByOther": false,
|
||||||
|
"isProvisioned": false,
|
||||||
|
"isRebooting": false,
|
||||||
|
"isSshEnabled": false,
|
||||||
|
"canAdopt": false,
|
||||||
|
"isAttemptingToConnect": false,
|
||||||
|
"credentials": "955756200c7f43936df9d5f7865f058e1528945aac0f0cb27cef960eb58f17db",
|
||||||
|
"lockStatus": "CLOSING",
|
||||||
|
"enableHomekit": false,
|
||||||
|
"autoCloseTimeMs": 15000,
|
||||||
|
"wiredConnectionState": {
|
||||||
|
"phyRate": null
|
||||||
|
},
|
||||||
|
"ledSettings": {
|
||||||
|
"isEnabled": true
|
||||||
|
},
|
||||||
|
"bluetoothConnectionState": {
|
||||||
|
"signalQuality": 62,
|
||||||
|
"signalStrength": -65
|
||||||
|
},
|
||||||
|
"batteryStatus": {
|
||||||
|
"percentage": 100,
|
||||||
|
"isLow": false
|
||||||
|
},
|
||||||
|
"bridge": "61b3f5c90050a703e700042a",
|
||||||
|
"camera": "e2ff0ade6be0f2a2beb61869",
|
||||||
|
"bridgeCandidates": [],
|
||||||
|
"id": "1c812e80fd693ab51535be38",
|
||||||
|
"isConnected": true,
|
||||||
|
"hasHomekit": false,
|
||||||
|
"marketName": "UP DoorLock",
|
||||||
|
"modelKey": "doorlock",
|
||||||
|
"privateToken": "MsjIV0UUpMWuAQZvJnCOfC1K9UAfgqDKCIcWtANWIuW66OXLwSgMbNEG2MEkL2TViSkMbJvFxAQEyHU0EJeVCWzY6dGHGuKXFXZMqJWZivBGDC8JoXiRxNIBqHZtXQKXZIoXWKLmhBL7SDxLoFNYEYNNLUGKGFBBGX2oNLi8KRW3SDSUTTWJZNwAUs8GKeJJ"
|
||||||
|
}
|
@ -6,11 +6,12 @@ from datetime import timedelta
|
|||||||
from unittest.mock import AsyncMock, Mock
|
from unittest.mock import AsyncMock, Mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pyunifiprotect.data import Camera, Light
|
from pyunifiprotect.data import Camera, Doorlock, Light
|
||||||
|
|
||||||
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
|
from homeassistant.components.unifiprotect.const import DEFAULT_ATTRIBUTION
|
||||||
from homeassistant.components.unifiprotect.number import (
|
from homeassistant.components.unifiprotect.number import (
|
||||||
CAMERA_NUMBERS,
|
CAMERA_NUMBERS,
|
||||||
|
DOORLOCK_NUMBERS,
|
||||||
LIGHT_NUMBERS,
|
LIGHT_NUMBERS,
|
||||||
ProtectNumberEntityDescription,
|
ProtectNumberEntityDescription,
|
||||||
)
|
)
|
||||||
@ -93,6 +94,35 @@ async def camera_fixture(
|
|||||||
Camera.__config__.validate_assignment = True
|
Camera.__config__.validate_assignment = True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="doorlock")
|
||||||
|
async def doorlock_fixture(
|
||||||
|
hass: HomeAssistant, mock_entry: MockEntityFixture, mock_doorlock: Doorlock
|
||||||
|
):
|
||||||
|
"""Fixture for a single doorlock for testing the number platform."""
|
||||||
|
|
||||||
|
# disable pydantic validation so mocking can happen
|
||||||
|
Doorlock.__config__.validate_assignment = False
|
||||||
|
|
||||||
|
lock_obj = mock_doorlock.copy(deep=True)
|
||||||
|
lock_obj._api = mock_entry.api
|
||||||
|
lock_obj.name = "Test Lock"
|
||||||
|
lock_obj.auto_close_time = timedelta(seconds=45)
|
||||||
|
|
||||||
|
mock_entry.api.bootstrap.reset_objects()
|
||||||
|
mock_entry.api.bootstrap.doorlocks = {
|
||||||
|
lock_obj.id: lock_obj,
|
||||||
|
}
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(mock_entry.entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert_entity_counts(hass, Platform.NUMBER, 1, 1)
|
||||||
|
|
||||||
|
yield lock_obj
|
||||||
|
|
||||||
|
Doorlock.__config__.validate_assignment = True
|
||||||
|
|
||||||
|
|
||||||
async def test_number_setup_light(
|
async def test_number_setup_light(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
light: Light,
|
light: Light,
|
||||||
@ -249,3 +279,20 @@ async def test_number_camera_simple(
|
|||||||
)
|
)
|
||||||
|
|
||||||
set_method.assert_called_once_with(1.0)
|
set_method.assert_called_once_with(1.0)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_number_lock_auto_close(hass: HomeAssistant, doorlock: Doorlock):
|
||||||
|
"""Test auto-lock timeout for locks."""
|
||||||
|
|
||||||
|
description = DOORLOCK_NUMBERS[0]
|
||||||
|
|
||||||
|
doorlock.__fields__["set_auto_close_time"] = Mock()
|
||||||
|
doorlock.set_auto_close_time = AsyncMock()
|
||||||
|
|
||||||
|
_, entity_id = ids_from_device_description(Platform.NUMBER, doorlock, description)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
"number", "set_value", {ATTR_ENTITY_ID: entity_id, "value": 15.0}, blocking=True
|
||||||
|
)
|
||||||
|
|
||||||
|
doorlock.set_auto_close_time.assert_called_once_with(timedelta(seconds=15.0))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user