Add get_user_keyring_info service to UniFi Protect integration (#133138)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Raphael Hehl 2024-12-22 19:54:14 +01:00 committed by GitHub
parent 0f1835139f
commit 6c70586f7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 171 additions and 5 deletions

View File

@ -83,3 +83,10 @@ EVENT_TYPE_FINGERPRINT_IDENTIFIED: Final = "identified"
EVENT_TYPE_FINGERPRINT_NOT_IDENTIFIED: Final = "not_identified"
EVENT_TYPE_NFC_SCANNED: Final = "scanned"
EVENT_TYPE_DOORBELL_RING: Final = "ring"
KEYRINGS_ULP_ID: Final = "ulp_id"
KEYRINGS_USER_STATUS: Final = "user_status"
KEYRINGS_USER_FULL_NAME: Final = "full_name"
KEYRINGS_KEY_TYPE: Final = "key_type"
KEYRINGS_KEY_TYPE_ID_FINGERPRINT: Final = "fingerprint_id"
KEYRINGS_KEY_TYPE_ID_NFC: Final = "nfc_id"

View File

@ -11,6 +11,9 @@
},
"remove_privacy_zone": {
"service": "mdi:eye-minus"
},
"get_user_keyring_info": {
"service": "mdi:key-chain"
}
}
}

View File

@ -13,7 +13,13 @@ import voluptuous as vol
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.const import ATTR_DEVICE_ID, ATTR_NAME, Platform
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.core import (
HomeAssistant,
ServiceCall,
ServiceResponse,
SupportsResponse,
callback,
)
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import (
config_validation as cv,
@ -21,9 +27,19 @@ from homeassistant.helpers import (
entity_registry as er,
)
from homeassistant.helpers.service import async_extract_referenced_entity_ids
from homeassistant.util.json import JsonValueType
from homeassistant.util.read_only_dict import ReadOnlyDict
from .const import ATTR_MESSAGE, DOMAIN
from .const import (
ATTR_MESSAGE,
DOMAIN,
KEYRINGS_KEY_TYPE,
KEYRINGS_KEY_TYPE_ID_FINGERPRINT,
KEYRINGS_KEY_TYPE_ID_NFC,
KEYRINGS_ULP_ID,
KEYRINGS_USER_FULL_NAME,
KEYRINGS_USER_STATUS,
)
from .data import async_ufp_instance_for_config_entry_ids
SERVICE_ADD_DOORBELL_TEXT = "add_doorbell_text"
@ -31,12 +47,14 @@ SERVICE_REMOVE_DOORBELL_TEXT = "remove_doorbell_text"
SERVICE_SET_PRIVACY_ZONE = "set_privacy_zone"
SERVICE_REMOVE_PRIVACY_ZONE = "remove_privacy_zone"
SERVICE_SET_CHIME_PAIRED = "set_chime_paired_doorbells"
SERVICE_GET_USER_KEYRING_INFO = "get_user_keyring_info"
ALL_GLOBAL_SERIVCES = [
SERVICE_ADD_DOORBELL_TEXT,
SERVICE_REMOVE_DOORBELL_TEXT,
SERVICE_SET_CHIME_PAIRED,
SERVICE_REMOVE_PRIVACY_ZONE,
SERVICE_GET_USER_KEYRING_INFO,
]
DOORBELL_TEXT_SCHEMA = vol.All(
@ -69,6 +87,15 @@ REMOVE_PRIVACY_ZONE_SCHEMA = vol.All(
cv.has_at_least_one_key(ATTR_DEVICE_ID),
)
GET_USER_KEYRING_INFO_SCHEMA = vol.All(
vol.Schema(
{
**cv.ENTITY_SERVICE_FIELDS,
},
),
cv.has_at_least_one_key(ATTR_DEVICE_ID),
)
@callback
def _async_get_ufp_instance(hass: HomeAssistant, device_id: str) -> ProtectApiClient:
@ -205,26 +232,70 @@ async def set_chime_paired_doorbells(call: ServiceCall) -> None:
await chime.save_device(data_before_changed)
async def get_user_keyring_info(call: ServiceCall) -> ServiceResponse:
"""Get the user keyring info."""
camera = _async_get_ufp_camera(call)
ulp_users = camera.api.bootstrap.ulp_users.as_list()
user_keyrings: list[JsonValueType] = [
{
KEYRINGS_USER_FULL_NAME: user.full_name,
KEYRINGS_USER_STATUS: user.status,
KEYRINGS_ULP_ID: user.ulp_id,
"keys": [
{
KEYRINGS_KEY_TYPE: key.registry_type,
**(
{KEYRINGS_KEY_TYPE_ID_FINGERPRINT: key.registry_id}
if key.registry_type == "fingerprint"
else {}
),
**(
{KEYRINGS_KEY_TYPE_ID_NFC: key.registry_id}
if key.registry_type == "nfc"
else {}
),
}
for key in camera.api.bootstrap.keyrings.as_list()
if key.ulp_user == user.ulp_id
],
}
for user in ulp_users
]
response: ServiceResponse = {"users": user_keyrings}
return response
SERVICES = [
(
SERVICE_ADD_DOORBELL_TEXT,
add_doorbell_text,
DOORBELL_TEXT_SCHEMA,
SupportsResponse.NONE,
),
(
SERVICE_REMOVE_DOORBELL_TEXT,
remove_doorbell_text,
DOORBELL_TEXT_SCHEMA,
SupportsResponse.NONE,
),
(
SERVICE_SET_CHIME_PAIRED,
set_chime_paired_doorbells,
CHIME_PAIRED_SCHEMA,
SupportsResponse.NONE,
),
(
SERVICE_REMOVE_PRIVACY_ZONE,
remove_privacy_zone,
REMOVE_PRIVACY_ZONE_SCHEMA,
SupportsResponse.NONE,
),
(
SERVICE_GET_USER_KEYRING_INFO,
get_user_keyring_info,
GET_USER_KEYRING_INFO_SCHEMA,
SupportsResponse.ONLY,
),
]
@ -232,5 +303,7 @@ SERVICES = [
def async_setup_services(hass: HomeAssistant) -> None:
"""Set up the global UniFi Protect services."""
for name, method, schema in SERVICES:
hass.services.async_register(DOMAIN, name, method, schema=schema)
for name, method, schema, supports_response in SERVICES:
hass.services.async_register(
DOMAIN, name, method, schema=schema, supports_response=supports_response
)

View File

@ -53,3 +53,10 @@ remove_privacy_zone:
required: true
selector:
text:
get_user_keyring_info:
fields:
device_id:
required: true
selector:
device:
integration: unifiprotect

View File

@ -225,6 +225,16 @@
"description": "The name of the zone to remove."
}
}
},
"get_user_keyring_info": {
"name": "Retrieve Keyring Details for Users",
"description": "Fetch a detailed list of users with NFC and fingerprint associations for automations.",
"fields": {
"device_id": {
"name": "UniFi Protect NVR",
"description": "Any device from the UniFi Protect instance you want to retrieve keyring details. This is useful for systems with multiple Protect instances."
}
}
}
}
}

View File

@ -9,9 +9,19 @@ from uiprotect.data import Camera, Chime, Color, Light, ModelType
from uiprotect.data.devices import CameraZone
from uiprotect.exceptions import BadRequest
from homeassistant.components.unifiprotect.const import ATTR_MESSAGE, DOMAIN
from homeassistant.components.unifiprotect.const import (
ATTR_MESSAGE,
DOMAIN,
KEYRINGS_KEY_TYPE,
KEYRINGS_KEY_TYPE_ID_FINGERPRINT,
KEYRINGS_KEY_TYPE_ID_NFC,
KEYRINGS_ULP_ID,
KEYRINGS_USER_FULL_NAME,
KEYRINGS_USER_STATUS,
)
from homeassistant.components.unifiprotect.services import (
SERVICE_ADD_DOORBELL_TEXT,
SERVICE_GET_USER_KEYRING_INFO,
SERVICE_REMOVE_DOORBELL_TEXT,
SERVICE_REMOVE_PRIVACY_ZONE,
SERVICE_SET_CHIME_PAIRED,
@ -249,3 +259,59 @@ async def test_remove_privacy_zone(
)
ufp.api.update_device.assert_called()
assert not doorbell.privacy_zones
@pytest.mark.asyncio
async def test_get_doorbell_user(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
ufp: MockUFPFixture,
doorbell: Camera,
) -> None:
"""Test get_doorbell_user service."""
ulp_user = Mock(full_name="Test User", status="active", ulp_id="user_ulp_id")
keyring = Mock(
registry_type="nfc",
registry_id="123456",
ulp_user="user_ulp_id",
)
keyring_2 = Mock(
registry_type="fingerprint",
registry_id="2",
ulp_user="user_ulp_id",
)
ufp.api.bootstrap.ulp_users.as_list = Mock(return_value=[ulp_user])
ufp.api.bootstrap.keyrings.as_list = Mock(return_value=[keyring, keyring_2])
await init_entry(hass, ufp, [doorbell])
camera_entry = entity_registry.async_get("binary_sensor.test_camera_doorbell")
response = await hass.services.async_call(
DOMAIN,
SERVICE_GET_USER_KEYRING_INFO,
{ATTR_DEVICE_ID: camera_entry.device_id},
blocking=True,
return_response=True,
)
assert response == {
"users": [
{
KEYRINGS_USER_FULL_NAME: "Test User",
"keys": [
{
KEYRINGS_KEY_TYPE: "nfc",
KEYRINGS_KEY_TYPE_ID_NFC: "123456",
},
{
KEYRINGS_KEY_TYPE_ID_FINGERPRINT: "2",
KEYRINGS_KEY_TYPE: "fingerprint",
},
],
KEYRINGS_USER_STATUS: "active",
KEYRINGS_ULP_ID: "user_ulp_id",
},
],
}