1 """UniFi Protect Integration services."""
3 from __future__
import annotations
7 from typing
import Any, cast
9 from pydantic
import ValidationError
10 from uiprotect.api
import ProtectApiClient
11 from uiprotect.data
import Camera, Chime
12 from uiprotect.exceptions
import ClientError
13 import voluptuous
as vol
20 config_validation
as cv,
21 device_registry
as dr,
22 entity_registry
as er,
27 from .const
import ATTR_MESSAGE, DOMAIN
28 from .data
import async_ufp_instance_for_config_entry_ids
30 SERVICE_ADD_DOORBELL_TEXT =
"add_doorbell_text"
31 SERVICE_REMOVE_DOORBELL_TEXT =
"remove_doorbell_text"
32 SERVICE_SET_PRIVACY_ZONE =
"set_privacy_zone"
33 SERVICE_REMOVE_PRIVACY_ZONE =
"remove_privacy_zone"
34 SERVICE_SET_CHIME_PAIRED =
"set_chime_paired_doorbells"
36 ALL_GLOBAL_SERIVCES = [
37 SERVICE_ADD_DOORBELL_TEXT,
38 SERVICE_REMOVE_DOORBELL_TEXT,
39 SERVICE_SET_CHIME_PAIRED,
40 SERVICE_REMOVE_PRIVACY_ZONE,
43 DOORBELL_TEXT_SCHEMA = vol.All(
46 **cv.ENTITY_SERVICE_FIELDS,
47 vol.Required(ATTR_MESSAGE): cv.string,
50 cv.has_at_least_one_key(ATTR_DEVICE_ID),
53 CHIME_PAIRED_SCHEMA = vol.All(
56 **cv.ENTITY_SERVICE_FIELDS,
57 "doorbells": cv.TARGET_SERVICE_FIELDS,
60 cv.has_at_least_one_key(ATTR_DEVICE_ID),
63 REMOVE_PRIVACY_ZONE_SCHEMA = vol.All(
66 **cv.ENTITY_SERVICE_FIELDS,
67 vol.Required(ATTR_NAME): cv.string,
70 cv.has_at_least_one_key(ATTR_DEVICE_ID),
76 device_registry = dr.async_get(hass)
77 if not (device_entry := device_registry.async_get(device_id)):
80 if device_entry.via_device_id
is not None:
83 config_entry_ids = device_entry.config_entries
93 entity_registry = er.async_get(hass)
95 entity_id = ref.indirectly_referenced.pop()
96 camera_entity = entity_registry.async_get(entity_id)
97 assert camera_entity
is not None
98 assert camera_entity.device_id
is not None
102 return cast(Camera, instance.bootstrap.get_device_from_mac(camera_mac))
107 hass: HomeAssistant, call: ServiceCall
108 ) -> set[ProtectApiClient]:
126 await asyncio.gather(
127 *(getattr(i.bootstrap.nvr, method)(*args, **kwargs)
for i
in instances)
129 except (ClientError, ValidationError)
as err:
134 """Add a custom doorbell text message."""
135 message: str = call.data[ATTR_MESSAGE]
140 """Remove a custom doorbell text message."""
141 message: str = call.data[ATTR_MESSAGE]
146 """Remove privacy zone from camera."""
148 name: str = call.data[ATTR_NAME]
151 remove_index: int |
None =
None
152 for index, zone
in enumerate(camera.privacy_zones):
153 if zone.name == name:
157 if remove_index
is None:
159 f
"Could not find privacy zone with name {name} on camera {camera.display_name}."
162 def remove_zone() -> None:
163 camera.privacy_zones.pop(remove_index)
165 await camera.queue_update(remove_zone)
170 """Extract the MAC address from the registry entry unique id."""
171 return unique_id.split(
"_")[0]
175 """Set paired doorbells on chime."""
177 entity_registry = er.async_get(hass)
179 entity_id = ref.indirectly_referenced.pop()
180 chime_button = entity_registry.async_get(entity_id)
181 assert chime_button
is not None
182 assert chime_button.device_id
is not None
186 chime = instance.bootstrap.get_device_from_mac(chime_mac)
187 chime = cast(Chime, chime)
188 assert chime
is not None
190 call.data =
ReadOnlyDict(call.data.get(
"doorbells")
or {})
192 doorbell_ids: set[str] = set()
193 for camera_id
in doorbell_refs.referenced | doorbell_refs.indirectly_referenced:
194 doorbell_sensor = entity_registry.async_get(camera_id)
195 assert doorbell_sensor
is not None
197 doorbell_sensor.platform != DOMAIN
198 or doorbell_sensor.domain != Platform.BINARY_SENSOR
199 or doorbell_sensor.original_device_class
200 != BinarySensorDeviceClass.OCCUPANCY
204 camera = instance.bootstrap.get_device_from_mac(doorbell_mac)
205 assert camera
is not None
206 doorbell_ids.add(camera.id)
207 data_before_changed = chime.dict_with_excludes()
208 chime.camera_ids = sorted(doorbell_ids)
209 await chime.save_device(data_before_changed)
213 """Set up the global UniFi Protect services."""
216 SERVICE_ADD_DOORBELL_TEXT,
217 functools.partial(add_doorbell_text, hass),
218 DOORBELL_TEXT_SCHEMA,
221 SERVICE_REMOVE_DOORBELL_TEXT,
222 functools.partial(remove_doorbell_text, hass),
223 DOORBELL_TEXT_SCHEMA,
226 SERVICE_SET_CHIME_PAIRED,
227 functools.partial(set_chime_paired_doorbells, hass),
231 SERVICE_REMOVE_PRIVACY_ZONE,
232 functools.partial(remove_privacy_zone, hass),
233 REMOVE_PRIVACY_ZONE_SCHEMA,
236 for name, method, schema
in services:
237 if hass.services.has_service(DOMAIN, name):
239 hass.services.async_register(DOMAIN, name, method, schema=schema)
ProtectApiClient|None async_ufp_instance_for_config_entry_ids(HomeAssistant hass, set[str] config_entry_ids)
None remove_privacy_zone(HomeAssistant hass, ServiceCall call)
None add_doorbell_text(HomeAssistant hass, ServiceCall call)
None async_setup_services(HomeAssistant hass)
ProtectApiClient _async_get_ufp_instance(HomeAssistant hass, str device_id)
str _async_unique_id_to_mac(str unique_id)
None set_chime_paired_doorbells(HomeAssistant hass, ServiceCall call)
Camera _async_get_ufp_camera(HomeAssistant hass, ServiceCall call)
None remove_doorbell_text(HomeAssistant hass, ServiceCall call)
None _async_service_call_nvr(HomeAssistant hass, ServiceCall call, str method, *Any args, **Any kwargs)
set[ProtectApiClient] _async_get_protect_from_call(HomeAssistant hass, ServiceCall call)
SelectedEntities async_extract_referenced_entity_ids(HomeAssistant hass, ServiceCall service_call, bool expand_group=True)