core/homeassistant/components/motioneye/config_flow.py

237 lines
8.5 KiB
Python

"""Config flow for motionEye integration."""
from __future__ import annotations
from typing import Any, cast
from motioneye_client.client import (
MotionEyeClientConnectionError,
MotionEyeClientInvalidAuthError,
MotionEyeClientRequestError,
)
import voluptuous as vol
from homeassistant.components.hassio import HassioServiceInfo
from homeassistant.config_entries import (
SOURCE_REAUTH,
ConfigEntry,
ConfigFlow,
OptionsFlow,
)
from homeassistant.const import CONF_SOURCE, CONF_URL, CONF_WEBHOOK_ID
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import create_motioneye_client
from .const import (
CONF_ADMIN_PASSWORD,
CONF_ADMIN_USERNAME,
CONF_STREAM_URL_TEMPLATE,
CONF_SURVEILLANCE_PASSWORD,
CONF_SURVEILLANCE_USERNAME,
CONF_WEBHOOK_SET,
CONF_WEBHOOK_SET_OVERWRITE,
DEFAULT_WEBHOOK_SET,
DEFAULT_WEBHOOK_SET_OVERWRITE,
DOMAIN,
)
class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for motionEye."""
VERSION = 1
_hassio_discovery: dict[str, Any] | None = None
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial step."""
def _get_form(
user_input: dict[str, Any], errors: dict[str, str] | None = None
) -> FlowResult:
"""Show the form to the user."""
url_schema: dict[vol.Required, type[str]] = {}
if not self._hassio_discovery:
# Only ask for URL when not discovered
url_schema[
vol.Required(CONF_URL, default=user_input.get(CONF_URL, ""))
] = str
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
**url_schema,
vol.Optional(
CONF_ADMIN_USERNAME,
default=user_input.get(CONF_ADMIN_USERNAME),
): str,
vol.Optional(
CONF_ADMIN_PASSWORD,
default=user_input.get(CONF_ADMIN_PASSWORD),
): str,
vol.Optional(
CONF_SURVEILLANCE_USERNAME,
default=user_input.get(CONF_SURVEILLANCE_USERNAME),
): str,
vol.Optional(
CONF_SURVEILLANCE_PASSWORD,
default=user_input.get(CONF_SURVEILLANCE_PASSWORD),
): str,
}
),
errors=errors,
)
reauth_entry = None
if self.context.get("entry_id"):
reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
if user_input is None:
return _get_form(
cast(dict[str, Any], reauth_entry.data) if reauth_entry else {}
)
if self._hassio_discovery:
# In case of Supervisor discovery, use pushed URL
user_input[CONF_URL] = self._hassio_discovery[CONF_URL]
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 _get_form(user_input, {"base": "invalid_url"})
client = create_motioneye_client(
user_input[CONF_URL],
admin_username=user_input.get(CONF_ADMIN_USERNAME),
admin_password=user_input.get(CONF_ADMIN_PASSWORD),
surveillance_username=user_input.get(CONF_SURVEILLANCE_USERNAME),
surveillance_password=user_input.get(CONF_SURVEILLANCE_PASSWORD),
session=async_get_clientsession(self.hass),
)
errors = {}
try:
await client.async_client_login()
except MotionEyeClientConnectionError:
errors["base"] = "cannot_connect"
except MotionEyeClientInvalidAuthError:
errors["base"] = "invalid_auth"
except MotionEyeClientRequestError:
errors["base"] = "unknown"
finally:
await client.async_client_close()
if errors:
return _get_form(user_input, errors)
if self.context.get(CONF_SOURCE) == SOURCE_REAUTH and reauth_entry is not None:
# Persist the same webhook id across reauths.
if CONF_WEBHOOK_ID in reauth_entry.data:
user_input[CONF_WEBHOOK_ID] = reauth_entry.data[CONF_WEBHOOK_ID]
self.hass.config_entries.async_update_entry(reauth_entry, data=user_input)
# Need to manually reload, as the listener won't have been
# installed because the initial load did not succeed (the reauth
# flow will not be initiated if the load succeeds).
await self.hass.config_entries.async_reload(reauth_entry.entry_id)
return self.async_abort(reason="reauth_successful")
# Search for duplicates: there isn't a useful unique_id, but
# at least prevent entries with the same motionEye URL.
self._async_abort_entries_match({CONF_URL: user_input[CONF_URL]})
title = user_input[CONF_URL]
if self._hassio_discovery:
title = "Add-on"
return self.async_create_entry(
title=title,
data=user_input,
)
async def async_step_reauth(
self,
config_data: dict[str, Any] | None = None,
) -> FlowResult:
"""Handle a reauthentication flow."""
return await self.async_step_user(config_data)
async def async_step_hassio(self, discovery_info: HassioServiceInfo) -> FlowResult:
"""Handle Supervisor discovery."""
self._hassio_discovery = discovery_info.config
await self._async_handle_discovery_without_unique_id()
return await self.async_step_hassio_confirm()
async def async_step_hassio_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Confirm Supervisor discovery."""
if user_input is None and self._hassio_discovery is not None:
return self.async_show_form(
step_id="hassio_confirm",
description_placeholders={"addon": self._hassio_discovery["addon"]},
)
return await self.async_step_user()
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry) -> MotionEyeOptionsFlow:
"""Get the Hyperion Options flow."""
return MotionEyeOptionsFlow(config_entry)
class MotionEyeOptionsFlow(OptionsFlow):
"""motionEye options flow."""
def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize a motionEye options flow."""
self._config_entry = config_entry
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
schema: dict[vol.Marker, type] = {
vol.Required(
CONF_WEBHOOK_SET,
default=self._config_entry.options.get(
CONF_WEBHOOK_SET,
DEFAULT_WEBHOOK_SET,
),
): bool,
vol.Required(
CONF_WEBHOOK_SET_OVERWRITE,
default=self._config_entry.options.get(
CONF_WEBHOOK_SET_OVERWRITE,
DEFAULT_WEBHOOK_SET_OVERWRITE,
),
): bool,
}
if self.show_advanced_options:
# 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
stream_kwargs = {}
if CONF_STREAM_URL_TEMPLATE in self._config_entry.options:
stream_kwargs["description"] = {
"suggested_value": self._config_entry.options[
CONF_STREAM_URL_TEMPLATE
]
}
schema[vol.Optional(CONF_STREAM_URL_TEMPLATE, **stream_kwargs)] = str
return self.async_show_form(step_id="init", data_schema=vol.Schema(schema))