homeassistant/custom_components/frigate/config_flow.py
2025-01-10 21:08:35 -08:00

192 lines
6.4 KiB
Python

"""Adds config flow for Frigate."""
from __future__ import annotations
import logging
from typing import Any, Dict, cast
import voluptuous as vol
from voluptuous.validators import All, Range
from yarl import URL
from homeassistant import config_entries
from homeassistant.const import CONF_URL
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from .api import FrigateApiClient, FrigateApiClientError
from .const import (
CONF_ENABLE_WEBRTC,
CONF_MEDIA_BROWSER_ENABLE,
CONF_NOTIFICATION_PROXY_ENABLE,
CONF_NOTIFICATION_PROXY_EXPIRE_AFTER_SECONDS,
CONF_RTMP_URL_TEMPLATE,
CONF_RTSP_URL_TEMPLATE,
DEFAULT_HOST,
DOMAIN,
)
_LOGGER: logging.Logger = logging.getLogger(__name__)
def get_config_entry_title(url_str: str) -> str:
"""Get the title of a config entry from the URL."""
# Strip the scheme from the URL as it's not that interesting in the title
# and space is limited on the integrations page.
url = URL(url_str)
return str(url)[len(url.scheme + "://") :]
class FrigateFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): # type: ignore[call-arg,misc]
"""Config flow for Frigate."""
VERSION = 2
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
"""Handle a flow initialized by the user."""
if user_input is None:
return self._show_config_form()
try:
# Cannot use cv.url validation in the schema itself, so
# apply extra validation here.
cv.url(user_input[CONF_URL])
except vol.Invalid:
return self._show_config_form(user_input, errors={"base": "invalid_url"})
try:
session = async_create_clientsession(self.hass)
client = FrigateApiClient(user_input[CONF_URL], session)
await client.async_get_stats()
except FrigateApiClientError:
return self._show_config_form(user_input, errors={"base": "cannot_connect"})
# Search for duplicates with the same Frigate CONF_HOST value.
for existing_entry in self._async_current_entries(include_ignore=False):
if existing_entry.data.get(CONF_URL) == user_input[CONF_URL]:
return cast(
Dict[str, Any], self.async_abort(reason="already_configured")
)
return cast(
Dict[str, Any],
self.async_create_entry(
title=get_config_entry_title(user_input[CONF_URL]), data=user_input
),
)
def _show_config_form(
self,
user_input: dict[str, Any] | None = None,
errors: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Show the configuration form."""
if user_input is None:
user_input = {}
return cast(
Dict[str, Any],
self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(
CONF_URL, default=user_input.get(CONF_URL, DEFAULT_HOST)
): str
}
),
errors=errors,
),
)
@staticmethod
@callback # type: ignore[misc]
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> FrigateOptionsFlowHandler:
"""Get the Frigate Options flow."""
return FrigateOptionsFlowHandler(config_entry)
class FrigateOptionsFlowHandler(config_entries.OptionsFlow): # type: ignore[misc]
"""Frigate options flow."""
def __init__(self, config_entry: config_entries.ConfigEntry):
"""Initialize a Frigate options flow."""
self._config_entry = config_entry
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
"""Manage the options."""
if user_input is not None:
return cast(
Dict[str, Any], self.async_create_entry(title="", data=user_input)
)
if not self.show_advanced_options:
return cast(
Dict[str, Any], self.async_abort(reason="only_advanced_options")
)
schema: dict[Any, Any] = {
# Whether to enable webrtc as the medium for camera streaming
vol.Optional(
CONF_ENABLE_WEBRTC,
default=self._config_entry.options.get(
CONF_ENABLE_WEBRTC,
False,
),
): bool,
# The input URL is not validated as being a URL to allow for the
# possibility the template input won't be a valid URL until after
# it's rendered.
vol.Optional(
CONF_RTMP_URL_TEMPLATE,
default=self._config_entry.options.get(
CONF_RTMP_URL_TEMPLATE,
"",
),
): str,
# The input URL is not validated as being a URL to allow for the
# possibility the template input won't be a valid URL until after
# it's rendered.
vol.Optional(
CONF_RTSP_URL_TEMPLATE,
default=self._config_entry.options.get(
CONF_RTSP_URL_TEMPLATE,
"",
),
): str,
vol.Optional(
CONF_NOTIFICATION_PROXY_ENABLE,
default=self._config_entry.options.get(
CONF_NOTIFICATION_PROXY_ENABLE,
True,
),
): bool,
vol.Optional(
CONF_MEDIA_BROWSER_ENABLE,
default=self._config_entry.options.get(
CONF_MEDIA_BROWSER_ENABLE,
True,
),
): bool,
vol.Optional(
CONF_NOTIFICATION_PROXY_EXPIRE_AFTER_SECONDS,
default=self._config_entry.options.get(
CONF_NOTIFICATION_PROXY_EXPIRE_AFTER_SECONDS,
0,
),
): All(int, Range(min=0)),
}
return cast(
Dict[str, Any],
self.async_show_form(step_id="init", data_schema=vol.Schema(schema)),
)