1 """Component providing select entities for UniFi Protect."""
3 from __future__
import annotations
5 from collections.abc
import Callable, Sequence
6 from dataclasses
import dataclass
9 from typing
import Any, Final
11 from uiprotect.api
import ProtectApiClient
12 from uiprotect.data
import (
23 ProtectAdoptableDeviceModel,
34 from .const
import TYPE_EMPTY_VALUE
35 from .data
import ProtectData, ProtectDeviceType, UFPConfigEntry
39 ProtectEntityDescription,
40 ProtectSetableKeysMixin,
42 async_all_device_entities,
44 from .utils
import async_get_light_motion_current
46 _LOGGER = logging.getLogger(__name__)
47 _KEY_LIGHT_MOTION =
"light_motion"
50 {
"id":
"always",
"name":
"Always On"},
51 {
"id":
"off",
"name":
"Always Off"},
52 {
"id":
"auto",
"name":
"Auto"},
56 {
"id": IRLEDMode.AUTO.value,
"name":
"Auto"},
57 {
"id": IRLEDMode.ON.value,
"name":
"Always Enable"},
58 {
"id": IRLEDMode.AUTO_NO_LED.value,
"name":
"Auto (Filter Only, no LED's)"},
59 {
"id": IRLEDMode.CUSTOM.value,
"name":
"Auto (Custom Lux)"},
60 {
"id": IRLEDMode.OFF.value,
"name":
"Always Disable"},
64 {
"id": ChimeType.NONE.value,
"name":
"None"},
65 {
"id": ChimeType.MECHANICAL.value,
"name":
"Mechanical"},
66 {
"id": ChimeType.DIGITAL.value,
"name":
"Digital"},
70 {
"id": MountType.NONE.value,
"name":
"None"},
71 {
"id": MountType.DOOR.value,
"name":
"Door"},
72 {
"id": MountType.WINDOW.value,
"name":
"Window"},
73 {
"id": MountType.GARAGE.value,
"name":
"Garage"},
74 {
"id": MountType.LEAK.value,
"name":
"Leak"},
77 LIGHT_MODE_MOTION =
"On Motion - Always"
78 LIGHT_MODE_MOTION_DARK =
"On Motion - When Dark"
79 LIGHT_MODE_DARK =
"When Dark"
80 LIGHT_MODE_OFF =
"Manual"
81 LIGHT_MODES = [LIGHT_MODE_MOTION, LIGHT_MODE_DARK, LIGHT_MODE_OFF]
83 LIGHT_MODE_TO_SETTINGS = {
84 LIGHT_MODE_MOTION: (LightModeType.MOTION.value, LightModeEnableType.ALWAYS.value),
85 LIGHT_MODE_MOTION_DARK: (
86 LightModeType.MOTION.value,
87 LightModeEnableType.DARK.value,
89 LIGHT_MODE_DARK: (LightModeType.WHEN_DARK.value, LightModeEnableType.DARK.value),
90 LIGHT_MODE_OFF: (LightModeType.MANUAL.value,
None),
93 MOTION_MODE_TO_LIGHT_MODE = [
94 {
"id": LightModeType.MOTION.value,
"name": LIGHT_MODE_MOTION},
95 {
"id": f
"{LightModeType.MOTION.value}Dark",
"name": LIGHT_MODE_MOTION_DARK},
96 {
"id": LightModeType.WHEN_DARK.value,
"name": LIGHT_MODE_DARK},
97 {
"id": LightModeType.MANUAL.value,
"name": LIGHT_MODE_OFF},
100 DEVICE_RECORDING_MODES = [
101 {
"id": mode.value,
"name": mode.value.title()}
for mode
in list(RecordingMode)
104 DEVICE_CLASS_LCD_MESSAGE: Final =
"unifiprotect__lcd_message"
107 @dataclass(frozen=True, kw_only=True)
109 ProtectSetableKeysMixin[T], SelectEntityDescription
111 """Describes UniFi Protect Select entity."""
113 ufp_options: list[dict[str, Any]] |
None =
None
114 ufp_options_fn: Callable[[ProtectApiClient], list[dict[str, Any]]] |
None =
None
115 ufp_enum_type: type[Enum] |
None =
None
120 {
"id": item.id,
"name": item.name}
for item
in api.bootstrap.liveviews.values()
125 default_message = api.bootstrap.nvr.doorbell_settings.default_message_text
126 messages = api.bootstrap.nvr.doorbell_settings.all_messages
127 built_messages: list[dict[str, str]] = []
129 for item
in messages:
130 msg_type = item.type.value
131 if item.type
is DoorbellMessageType.CUSTOM_MESSAGE:
132 msg_type = f
"{DoorbellMessageType.CUSTOM_MESSAGE.value}:{item.text}"
134 built_messages.append({
"id": msg_type,
"name": item.text})
137 {
"id":
"",
"name": f
"Default Message ({default_message})"},
143 options = [{
"id": TYPE_EMPTY_VALUE,
"name":
"Not Paired"}]
145 {
"id": camera.id,
"name": camera.display_name
or camera.type}
146 for camera
in api.bootstrap.cameras.values()
153 return obj.liveview_id
157 if obj.lcd_message
is None:
159 return obj.lcd_message.text
163 lightmode, timing = LIGHT_MODE_TO_SETTINGS[mode]
164 await obj.set_light_settings(
165 LightModeType(lightmode),
166 enable_at=
None if timing
is None else LightModeEnableType(timing),
171 if camera_id == TYPE_EMPTY_VALUE:
172 camera: Camera |
None =
None
174 camera = obj.api.bootstrap.cameras.get(camera_id)
175 await obj.set_paired_camera(camera)
179 if message.startswith(DoorbellMessageType.CUSTOM_MESSAGE.value):
180 message = message.split(
":")[-1]
181 await obj.set_lcd_text(DoorbellMessageType.CUSTOM_MESSAGE, text=message)
182 elif message == TYPE_EMPTY_VALUE:
183 await obj.set_lcd_text(
None)
185 await obj.set_lcd_text(DoorbellMessageType(message))
189 liveview = obj.api.bootstrap.liveviews[liveview_id]
190 await obj.set_liveview(liveview)
193 CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
195 key=
"recording_mode",
196 name=
"Recording mode",
197 icon=
"mdi:video-outline",
198 entity_category=EntityCategory.CONFIG,
199 ufp_options=DEVICE_RECORDING_MODES,
200 ufp_enum_type=RecordingMode,
201 ufp_value=
"recording_settings.mode",
202 ufp_set_method=
"set_recording_mode",
203 ufp_perm=PermRequired.WRITE,
207 name=
"Infrared mode",
208 icon=
"mdi:circle-opacity",
209 entity_category=EntityCategory.CONFIG,
210 ufp_required_field=
"feature_flags.has_led_ir",
211 ufp_options=INFRARED_MODES,
212 ufp_enum_type=IRLEDMode,
213 ufp_value=
"isp_settings.ir_led_mode",
214 ufp_set_method=
"set_ir_led_model",
215 ufp_perm=PermRequired.WRITE,
217 ProtectSelectEntityDescription[Camera](
219 name=
"Doorbell text",
220 icon=
"mdi:card-text",
221 entity_category=EntityCategory.CONFIG,
222 device_class=DEVICE_CLASS_LCD_MESSAGE,
223 ufp_required_field=
"feature_flags.has_lcd_screen",
224 ufp_value_fn=_get_doorbell_current,
225 ufp_options_fn=_get_doorbell_options,
226 ufp_set_method_fn=_set_doorbell_message,
227 ufp_perm=PermRequired.WRITE,
233 entity_category=EntityCategory.CONFIG,
234 ufp_required_field=
"feature_flags.has_chime",
235 ufp_options=CHIME_TYPES,
236 ufp_enum_type=ChimeType,
237 ufp_value=
"chime_type",
238 ufp_set_method=
"set_chime_type",
239 ufp_perm=PermRequired.WRITE,
244 icon=
"mdi:brightness-7",
245 entity_category=EntityCategory.CONFIG,
246 ufp_required_field=
"feature_flags.has_hdr",
247 ufp_options=HDR_MODES,
248 ufp_value=
"hdr_mode_display",
249 ufp_set_method=
"set_hdr_mode",
250 ufp_perm=PermRequired.WRITE,
254 LIGHT_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
255 ProtectSelectEntityDescription[Light](
256 key=_KEY_LIGHT_MOTION,
258 icon=
"mdi:spotlight",
259 entity_category=EntityCategory.CONFIG,
260 ufp_options=MOTION_MODE_TO_LIGHT_MODE,
261 ufp_value_fn=async_get_light_motion_current,
262 ufp_set_method_fn=_set_light_mode,
263 ufp_perm=PermRequired.WRITE,
265 ProtectSelectEntityDescription[Light](
267 name=
"Paired camera",
269 entity_category=EntityCategory.CONFIG,
270 ufp_value=
"camera_id",
271 ufp_options_fn=_get_paired_camera_options,
272 ufp_set_method_fn=_set_paired_camera,
273 ufp_perm=PermRequired.WRITE,
277 SENSE_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
281 icon=
"mdi:screwdriver",
282 entity_category=EntityCategory.CONFIG,
283 ufp_options=MOUNT_TYPES,
284 ufp_enum_type=MountType,
285 ufp_value=
"mount_type",
286 ufp_set_method=
"set_mount_type",
287 ufp_perm=PermRequired.WRITE,
289 ProtectSelectEntityDescription[Sensor](
291 name=
"Paired camera",
293 entity_category=EntityCategory.CONFIG,
294 ufp_value=
"camera_id",
295 ufp_options_fn=_get_paired_camera_options,
296 ufp_set_method_fn=_set_paired_camera,
297 ufp_perm=PermRequired.WRITE,
301 DOORLOCK_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
302 ProtectSelectEntityDescription[Doorlock](
304 name=
"Paired camera",
306 entity_category=EntityCategory.CONFIG,
307 ufp_value=
"camera_id",
308 ufp_options_fn=_get_paired_camera_options,
309 ufp_set_method_fn=_set_paired_camera,
310 ufp_perm=PermRequired.WRITE,
314 VIEWER_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
315 ProtectSelectEntityDescription[Viewer](
318 icon=
"mdi:view-dashboard",
319 entity_category=
None,
320 ufp_options_fn=_get_viewer_options,
321 ufp_value_fn=_get_viewer_current,
322 ufp_set_method_fn=_set_liveview,
323 ufp_perm=PermRequired.WRITE,
327 _MODEL_DESCRIPTIONS: dict[ModelType, Sequence[ProtectEntityDescription]] = {
328 ModelType.CAMERA: CAMERA_SELECTS,
329 ModelType.LIGHT: LIGHT_SELECTS,
330 ModelType.SENSOR: SENSE_SELECTS,
331 ModelType.VIEWPORT: VIEWER_SELECTS,
332 ModelType.DOORLOCK: DOORLOCK_SELECTS,
337 hass: HomeAssistant, entry: UFPConfigEntry, async_add_entities: AddEntitiesCallback
339 """Set up number entities for UniFi Protect integration."""
340 data = entry.runtime_data
343 def _add_new_device(device: ProtectAdoptableDeviceModel) ->
None:
348 model_descriptions=_MODEL_DESCRIPTIONS,
353 data.async_subscribe_adopt(_add_new_device)
356 data, ProtectSelects, model_descriptions=_MODEL_DESCRIPTIONS
362 """A UniFi Protect Select Entity."""
364 device: Camera | Light | Viewer
365 entity_description: ProtectSelectEntityDescription
366 _state_attrs = (
"_attr_available",
"_attr_options",
"_attr_current_option")
371 device: Camera | Light | Viewer,
372 description: ProtectSelectEntityDescription,
374 """Initialize the unifi protect select entity."""
376 super().
__init__(data, device, description)
385 entity_description.entity_category
is not None
386 and entity_description.ufp_options_fn
is not None
389 "Updating dynamic select options for %s", entity_description.name
392 if (unifi_value := entity_description.get_ufp_value(device))
is None:
393 unifi_value = TYPE_EMPTY_VALUE
395 unifi_value, unifi_value
400 self, data: ProtectData, description: ProtectSelectEntityDescription
402 """Set options attributes from UniFi Protect device."""
403 if (ufp_options := description.ufp_options)
is not None:
404 options = ufp_options
406 assert description.ufp_options_fn
is not None
407 options = description.ufp_options_fn(data.api)
414 """Change the Select Entity Option."""
None __init__(self, ProtectData data, Camera|Light|Viewer device, ProtectSelectEntityDescription description)
None _async_update_device_from_protect(self, ProtectDeviceType device)
None _async_set_options(self, ProtectData data, ProtectSelectEntityDescription description)
None async_select_option(self, str option)
web.Response get(self, web.Request request, str config_key)
list[BaseProtectEntity] async_all_device_entities(ProtectData data, type[BaseProtectEntity] klass, dict[ModelType, Sequence[ProtectEntityDescription]]|None model_descriptions=None, Sequence[ProtectEntityDescription]|None all_descs=None, list[ProtectEntityDescription]|None unadopted_descs=None, ProtectAdoptableDeviceModel|None ufp_device=None)
str _get_viewer_current(Viewer obj)
None _set_liveview(Viewer obj, str liveview_id)
list[dict[str, Any]] _get_paired_camera_options(ProtectApiClient api)
None async_setup_entry(HomeAssistant hass, UFPConfigEntry entry, AddEntitiesCallback async_add_entities)
list[dict[str, Any]] _get_viewer_options(ProtectApiClient api)
None _set_doorbell_message(Camera obj, str message)
str|None _get_doorbell_current(Camera obj)
list[dict[str, Any]] _get_doorbell_options(ProtectApiClient api)
None _set_light_mode(Light obj, str mode)
None _set_paired_camera(Light|Sensor|Doorlock obj, str camera_id)