core/homeassistant/components/unifiprotect/utils.py

162 lines
4.4 KiB
Python

"""UniFi Protect Integration utils."""
from __future__ import annotations
from collections.abc import Generator, Iterable
import contextlib
from enum import Enum
from pathlib import Path
import socket
from typing import Any
from aiohttp import CookieJar
from pyunifiprotect import ProtectApiClient
from pyunifiprotect.data import (
Bootstrap,
CameraChannel,
Light,
LightModeEnableType,
LightModeType,
ProtectAdoptableDeviceModel,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_USERNAME,
CONF_VERIFY_SSL,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.storage import STORAGE_DIR
from .const import (
CONF_ALL_UPDATES,
CONF_OVERRIDE_CHOST,
DEVICES_FOR_SUBSCRIBE,
DOMAIN,
ModelType,
)
_SENTINEL = object()
def get_nested_attr(obj: Any, attrs: tuple[str, ...]) -> Any:
"""Fetch a nested attribute."""
if len(attrs) == 1:
value = getattr(obj, attrs[0], None)
else:
value = obj
for key in attrs:
if (value := getattr(value, key, _SENTINEL)) is _SENTINEL:
return None
return value.value if isinstance(value, Enum) else value
@callback
def _async_unifi_mac_from_hass(mac: str) -> str:
# MAC addresses in UFP are always caps
return mac.replace(":", "").upper()
@callback
def _async_short_mac(mac: str) -> str:
"""Get the short mac address from the full mac."""
return _async_unifi_mac_from_hass(mac)[-6:]
async def _async_resolve(hass: HomeAssistant, host: str) -> str | None:
"""Resolve a hostname to an ip."""
with contextlib.suppress(OSError):
return next(
iter(
raw[0]
for family, _, _, _, raw in await hass.loop.getaddrinfo(
host, None, type=socket.SOCK_STREAM, proto=socket.IPPROTO_TCP
)
if family == socket.AF_INET
),
None,
)
return None
@callback
def async_get_devices_by_type(
bootstrap: Bootstrap, device_type: ModelType
) -> dict[str, ProtectAdoptableDeviceModel]:
"""Get devices by type."""
devices: dict[str, ProtectAdoptableDeviceModel] = getattr(
bootstrap, f"{device_type.value}s"
)
return devices
@callback
def async_get_devices(
bootstrap: Bootstrap, model_type: Iterable[ModelType]
) -> Generator[ProtectAdoptableDeviceModel, None, None]:
"""Return all device by type."""
return (
device
for device_type in model_type
for device in async_get_devices_by_type(bootstrap, device_type).values()
)
@callback
def async_get_light_motion_current(obj: Light) -> str:
"""Get light motion mode for Flood Light."""
if (
obj.light_mode_settings.mode is LightModeType.MOTION
and obj.light_mode_settings.enable_at is LightModeEnableType.DARK
):
return f"{LightModeType.MOTION.value}Dark"
return obj.light_mode_settings.mode.value
@callback
def async_dispatch_id(entry: ConfigEntry, dispatch: str) -> str:
"""Generate entry specific dispatch ID."""
return f"{DOMAIN}.{entry.entry_id}.{dispatch}"
@callback
def async_create_api_client(
hass: HomeAssistant, entry: ConfigEntry
) -> ProtectApiClient:
"""Create ProtectApiClient from config entry."""
session = async_create_clientsession(hass, cookie_jar=CookieJar(unsafe=True))
return ProtectApiClient(
host=entry.data[CONF_HOST],
port=entry.data[CONF_PORT],
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
verify_ssl=entry.data[CONF_VERIFY_SSL],
session=session,
subscribed_models=DEVICES_FOR_SUBSCRIBE,
override_connection_host=entry.options.get(CONF_OVERRIDE_CHOST, False),
ignore_stats=not entry.options.get(CONF_ALL_UPDATES, False),
ignore_unadopted=False,
cache_dir=Path(hass.config.path(STORAGE_DIR, "unifiprotect")),
config_dir=Path(hass.config.path(STORAGE_DIR, "unifiprotect")),
)
@callback
def get_camera_base_name(channel: CameraChannel) -> str:
"""Get base name for cameras channel."""
camera_name = channel.name
if channel.name != "Package Camera":
camera_name = f"{channel.name} Resolution Channel"
return camera_name