Simplify UniFi Protect service setup/cleanup (#63908)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Christopher Bailey 2022-01-11 17:37:47 -05:00 committed by GitHub
parent 240c9979c7
commit 05ee5e0251
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 97 deletions

View file

@ -3,14 +3,13 @@ from __future__ import annotations
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
import functools
import logging import logging
from aiohttp import CookieJar from aiohttp import CookieJar
from aiohttp.client_exceptions import ServerDisconnectedError from aiohttp.client_exceptions import ServerDisconnectedError
from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient from pyunifiprotect import NotAuthorized, NvrError, ProtectApiClient
from homeassistant.config_entries import ConfigEntry, ConfigEntryState from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
CONF_HOST, CONF_HOST,
CONF_PASSWORD, CONF_PASSWORD,
@ -24,22 +23,17 @@ from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_create_clientsession from homeassistant.helpers.aiohttp_client import async_create_clientsession
from .const import ( from .const import (
ALL_GLOBAL_SERIVCES,
CONF_ALL_UPDATES, CONF_ALL_UPDATES,
CONF_OVERRIDE_CHOST, CONF_OVERRIDE_CHOST,
DEFAULT_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL,
DEVICES_FOR_SUBSCRIBE, DEVICES_FOR_SUBSCRIBE,
DOMAIN, DOMAIN,
DOORBELL_TEXT_SCHEMA,
MIN_REQUIRED_PROTECT_V, MIN_REQUIRED_PROTECT_V,
OUTDATED_LOG_MESSAGE, OUTDATED_LOG_MESSAGE,
PLATFORMS, PLATFORMS,
SERVICE_ADD_DOORBELL_TEXT,
SERVICE_REMOVE_DOORBELL_TEXT,
SERVICE_SET_DEFAULT_DOORBELL_TEXT,
) )
from .data import ProtectData from .data import ProtectData
from .services import add_doorbell_text, remove_doorbell_text, set_default_doorbell_text from .services import async_cleanup_services, async_setup_services
from .views import ThumbnailProxyView from .views import ThumbnailProxyView
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -89,29 +83,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = data_service hass.data.setdefault(DOMAIN, {})[entry.entry_id] = data_service
hass.config_entries.async_setup_platforms(entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
async_setup_services(hass)
services = [
(
SERVICE_ADD_DOORBELL_TEXT,
functools.partial(add_doorbell_text, hass),
DOORBELL_TEXT_SCHEMA,
),
(
SERVICE_REMOVE_DOORBELL_TEXT,
functools.partial(remove_doorbell_text, hass),
DOORBELL_TEXT_SCHEMA,
),
(
SERVICE_SET_DEFAULT_DOORBELL_TEXT,
functools.partial(set_default_doorbell_text, hass),
DOORBELL_TEXT_SCHEMA,
),
]
for name, method, schema in services:
if hass.services.has_service(DOMAIN, name):
continue
hass.services.async_register(DOMAIN, name, method, schema=schema)
hass.http.register_view(ThumbnailProxyView(hass)) hass.http.register_view(ThumbnailProxyView(hass))
entry.async_on_unload(entry.add_update_listener(_async_options_updated)) entry.async_on_unload(entry.add_update_listener(_async_options_updated))
@ -133,14 +105,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
data: ProtectData = hass.data[DOMAIN][entry.entry_id] data: ProtectData = hass.data[DOMAIN][entry.entry_id]
await data.async_stop() await data.async_stop()
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)
async_cleanup_services(hass)
loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state == ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
for name in ALL_GLOBAL_SERIVCES:
hass.services.async_remove(DOMAIN, name)
return bool(unload_ok) return bool(unload_ok)

View file

@ -1,10 +1,8 @@
"""Constant definitions for UniFi Protect Integration.""" """Constant definitions for UniFi Protect Integration."""
from pyunifiprotect.data.types import ModelType, Version from pyunifiprotect.data.types import ModelType, Version
import voluptuous as vol
from homeassistant.const import ATTR_DEVICE_ID, ATTR_ENTITY_ID, Platform from homeassistant.const import Platform
from homeassistant.helpers import config_validation as cv
DOMAIN = "unifiprotect" DOMAIN = "unifiprotect"
@ -49,16 +47,6 @@ OUTDATED_LOG_MESSAGE = "You are running v%s of UniFi Protect. Minimum required v
TYPE_EMPTY_VALUE = "" TYPE_EMPTY_VALUE = ""
SERVICE_ADD_DOORBELL_TEXT = "add_doorbell_text"
SERVICE_REMOVE_DOORBELL_TEXT = "remove_doorbell_text"
SERVICE_SET_DEFAULT_DOORBELL_TEXT = "set_default_doorbell_text"
ALL_GLOBAL_SERIVCES = [
SERVICE_ADD_DOORBELL_TEXT,
SERVICE_REMOVE_DOORBELL_TEXT,
SERVICE_SET_DEFAULT_DOORBELL_TEXT,
]
PLATFORMS = [ PLATFORMS = [
Platform.BINARY_SENSOR, Platform.BINARY_SENSOR,
Platform.BUTTON, Platform.BUTTON,
@ -70,43 +58,3 @@ PLATFORMS = [
Platform.SENSOR, Platform.SENSOR,
Platform.SWITCH, Platform.SWITCH,
] ]
DOORBELL_TEXT_SCHEMA = vol.All(
vol.Schema(
{
**cv.ENTITY_SERVICE_FIELDS,
vol.Required(ATTR_MESSAGE): cv.string,
},
),
cv.has_at_least_one_key(ATTR_DEVICE_ID),
)
GENERATE_DATA_SCHEMA = vol.All(
vol.Schema(
{
**cv.ENTITY_SERVICE_FIELDS,
vol.Required(ATTR_DURATION): vol.Coerce(int),
vol.Required(ATTR_ANONYMIZE): vol.Coerce(bool),
},
),
cv.has_at_least_one_key(ATTR_DEVICE_ID),
)
PROFILE_WS_SCHEMA = vol.All(
vol.Schema(
{
**cv.ENTITY_SERVICE_FIELDS,
vol.Required(ATTR_DURATION): vol.Coerce(int),
},
),
cv.has_at_least_one_key(ATTR_DEVICE_ID),
)
SET_DOORBELL_LCD_MESSAGE_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_MESSAGE): cv.string,
vol.Optional(ATTR_DURATION, default="None"): cv.string,
}
)

View file

@ -2,20 +2,44 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
import functools
from typing import Any from typing import Any
from pydantic import ValidationError from pydantic import ValidationError
from pyunifiprotect.api import ProtectApiClient from pyunifiprotect.api import ProtectApiClient
from pyunifiprotect.exceptions import BadRequest from pyunifiprotect.exceptions import BadRequest
import voluptuous as vol
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_DEVICE_ID
from homeassistant.core import HomeAssistant, ServiceCall, callback from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.service import async_extract_referenced_entity_ids from homeassistant.helpers.service import async_extract_referenced_entity_ids
from .const import ATTR_MESSAGE, DOMAIN from .const import ATTR_MESSAGE, DOMAIN
from .data import ProtectData from .data import ProtectData
SERVICE_ADD_DOORBELL_TEXT = "add_doorbell_text"
SERVICE_REMOVE_DOORBELL_TEXT = "remove_doorbell_text"
SERVICE_SET_DEFAULT_DOORBELL_TEXT = "set_default_doorbell_text"
ALL_GLOBAL_SERIVCES = [
SERVICE_ADD_DOORBELL_TEXT,
SERVICE_REMOVE_DOORBELL_TEXT,
SERVICE_SET_DEFAULT_DOORBELL_TEXT,
]
DOORBELL_TEXT_SCHEMA = vol.All(
vol.Schema(
{
**cv.ENTITY_SERVICE_FIELDS,
vol.Required(ATTR_MESSAGE): cv.string,
},
),
cv.has_at_least_one_key(ATTR_DEVICE_ID),
)
def _async_all_ufp_instances(hass: HomeAssistant) -> list[ProtectApiClient]: def _async_all_ufp_instances(hass: HomeAssistant) -> list[ProtectApiClient]:
"""All active UFP instances.""" """All active UFP instances."""
@ -110,3 +134,40 @@ async def set_default_doorbell_text(hass: HomeAssistant, call: ServiceCall) -> N
message: str = call.data[ATTR_MESSAGE] message: str = call.data[ATTR_MESSAGE]
instances = _async_get_protect_from_call(hass, call) instances = _async_get_protect_from_call(hass, call)
await _async_call_nvr(instances, "set_default_doorbell_message", message) await _async_call_nvr(instances, "set_default_doorbell_message", message)
def async_setup_services(hass: HomeAssistant) -> None:
"""Set up the global UniFi Protect services."""
services = [
(
SERVICE_ADD_DOORBELL_TEXT,
functools.partial(add_doorbell_text, hass),
DOORBELL_TEXT_SCHEMA,
),
(
SERVICE_REMOVE_DOORBELL_TEXT,
functools.partial(remove_doorbell_text, hass),
DOORBELL_TEXT_SCHEMA,
),
(
SERVICE_SET_DEFAULT_DOORBELL_TEXT,
functools.partial(set_default_doorbell_text, hass),
DOORBELL_TEXT_SCHEMA,
),
]
for name, method, schema in services:
if hass.services.has_service(DOMAIN, name):
continue
hass.services.async_register(DOMAIN, name, method, schema=schema)
def async_cleanup_services(hass: HomeAssistant) -> None:
"""Cleanup global UniFi Protect services (if all config entries unloaded)."""
loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state == ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
for name in ALL_GLOBAL_SERIVCES:
hass.services.async_remove(DOMAIN, name)

View file

@ -8,9 +8,8 @@ import pytest
from pyunifiprotect.data import Light from pyunifiprotect.data import Light
from pyunifiprotect.exceptions import BadRequest from pyunifiprotect.exceptions import BadRequest
from homeassistant.components.unifiprotect.const import ( from homeassistant.components.unifiprotect.const import ATTR_MESSAGE, DOMAIN
ATTR_MESSAGE, from homeassistant.components.unifiprotect.services import (
DOMAIN,
SERVICE_ADD_DOORBELL_TEXT, SERVICE_ADD_DOORBELL_TEXT,
SERVICE_REMOVE_DOORBELL_TEXT, SERVICE_REMOVE_DOORBELL_TEXT,
SERVICE_SET_DEFAULT_DOORBELL_TEXT, SERVICE_SET_DEFAULT_DOORBELL_TEXT,