Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for motionEye integration."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 from typing import Any
7 
8 from motioneye_client.client import (
9  MotionEyeClientConnectionError,
10  MotionEyeClientInvalidAuthError,
11  MotionEyeClientRequestError,
12 )
13 import voluptuous as vol
14 
15 from homeassistant.config_entries import (
16  SOURCE_REAUTH,
17  ConfigEntry,
18  ConfigFlow,
19  ConfigFlowResult,
20  OptionsFlow,
21 )
22 from homeassistant.const import CONF_URL, CONF_WEBHOOK_ID
23 from homeassistant.core import callback
24 from homeassistant.helpers import config_validation as cv
25 from homeassistant.helpers.aiohttp_client import async_get_clientsession
26 from homeassistant.helpers.service_info.hassio import HassioServiceInfo
27 from homeassistant.helpers.typing import VolDictType
28 
29 from . import create_motioneye_client
30 from .const import (
31  CONF_ADMIN_PASSWORD,
32  CONF_ADMIN_USERNAME,
33  CONF_STREAM_URL_TEMPLATE,
34  CONF_SURVEILLANCE_PASSWORD,
35  CONF_SURVEILLANCE_USERNAME,
36  CONF_WEBHOOK_SET,
37  CONF_WEBHOOK_SET_OVERWRITE,
38  DEFAULT_WEBHOOK_SET,
39  DEFAULT_WEBHOOK_SET_OVERWRITE,
40  DOMAIN,
41 )
42 
43 
44 class MotionEyeConfigFlow(ConfigFlow, domain=DOMAIN):
45  """Handle a config flow for motionEye."""
46 
47  VERSION = 1
48  _hassio_discovery: dict[str, Any] | None = None
49 
50  async def async_step_user(
51  self, user_input: dict[str, Any] | None = None
52  ) -> ConfigFlowResult:
53  """Handle the initial step."""
54 
55  def _get_form(
56  user_input: Mapping[str, Any], errors: dict[str, str] | None = None
57  ) -> ConfigFlowResult:
58  """Show the form to the user."""
59  url_schema: VolDictType = {}
60  if not self._hassio_discovery_hassio_discovery:
61  # Only ask for URL when not discovered
62  url_schema[
63  vol.Required(CONF_URL, default=user_input.get(CONF_URL, ""))
64  ] = str
65 
66  return self.async_show_formasync_show_formasync_show_form(
67  step_id="user",
68  data_schema=vol.Schema(
69  {
70  **url_schema,
71  vol.Optional(
72  CONF_ADMIN_USERNAME,
73  default=user_input.get(CONF_ADMIN_USERNAME),
74  ): str,
75  vol.Optional(
76  CONF_ADMIN_PASSWORD,
77  default=user_input.get(CONF_ADMIN_PASSWORD, ""),
78  ): str,
79  vol.Optional(
80  CONF_SURVEILLANCE_USERNAME,
81  default=user_input.get(CONF_SURVEILLANCE_USERNAME),
82  ): str,
83  vol.Optional(
84  CONF_SURVEILLANCE_PASSWORD,
85  default=user_input.get(CONF_SURVEILLANCE_PASSWORD, ""),
86  ): str,
87  }
88  ),
89  errors=errors,
90  )
91 
92  if user_input is None:
93  if self.sourcesourcesourcesource == SOURCE_REAUTH:
94  return _get_form(self._get_reauth_entry_get_reauth_entry().data)
95  return _get_form({})
96 
97  if self._hassio_discovery_hassio_discovery:
98  # In case of Supervisor discovery, use pushed URL
99  user_input[CONF_URL] = self._hassio_discovery_hassio_discovery[CONF_URL]
100 
101  try:
102  # Cannot use cv.url validation in the schema itself, so
103  # apply extra validation here.
104  cv.url(user_input[CONF_URL])
105  except vol.Invalid:
106  return _get_form(user_input, {"base": "invalid_url"})
107 
108  client = create_motioneye_client(
109  user_input[CONF_URL],
110  admin_username=user_input.get(CONF_ADMIN_USERNAME),
111  admin_password=user_input.get(CONF_ADMIN_PASSWORD),
112  surveillance_username=user_input.get(CONF_SURVEILLANCE_USERNAME),
113  surveillance_password=user_input.get(CONF_SURVEILLANCE_PASSWORD),
114  session=async_get_clientsession(self.hass),
115  )
116 
117  errors = {}
118  try:
119  await client.async_client_login()
120  except MotionEyeClientConnectionError:
121  errors["base"] = "cannot_connect"
122  except MotionEyeClientInvalidAuthError:
123  errors["base"] = "invalid_auth"
124  except MotionEyeClientRequestError:
125  errors["base"] = "unknown"
126  finally:
127  await client.async_client_close()
128 
129  if errors:
130  return _get_form(user_input, errors)
131 
132  if self.sourcesourcesourcesource == SOURCE_REAUTH:
133  reauth_entry = self._get_reauth_entry_get_reauth_entry()
134  # Persist the same webhook id across reauths.
135  if CONF_WEBHOOK_ID in reauth_entry.data:
136  user_input[CONF_WEBHOOK_ID] = reauth_entry.data[CONF_WEBHOOK_ID]
137 
138  return self.async_update_reload_and_abortasync_update_reload_and_abort(reauth_entry, data=user_input)
139 
140  # Search for duplicates: there isn't a useful unique_id, but
141  # at least prevent entries with the same motionEye URL.
142  self._async_abort_entries_match_async_abort_entries_match({CONF_URL: user_input[CONF_URL]})
143 
144  title = user_input[CONF_URL]
145  if self._hassio_discovery_hassio_discovery:
146  title = "Add-on"
147 
148  return self.async_create_entryasync_create_entryasync_create_entry(
149  title=title,
150  data=user_input,
151  )
152 
153  async def async_step_reauth(
154  self, entry_data: Mapping[str, Any]
155  ) -> ConfigFlowResult:
156  """Handle a reauthentication flow."""
157  return await self.async_step_userasync_step_userasync_step_user()
158 
159  async def async_step_hassio(
160  self, discovery_info: HassioServiceInfo
161  ) -> ConfigFlowResult:
162  """Handle Supervisor discovery."""
163  self._hassio_discovery_hassio_discovery = discovery_info.config
164  await self._async_handle_discovery_without_unique_id_async_handle_discovery_without_unique_id()
165 
166  return await self.async_step_hassio_confirm()
167 
168  async def async_step_hassio_confirm(
169  self, user_input: dict[str, Any] | None = None
170  ) -> ConfigFlowResult:
171  """Confirm Supervisor discovery."""
172  if user_input is None and self._hassio_discovery_hassio_discovery is not None:
173  return self.async_show_formasync_show_formasync_show_form(
174  step_id="hassio_confirm",
175  description_placeholders={"addon": self._hassio_discovery_hassio_discovery["addon"]},
176  )
177 
178  return await self.async_step_userasync_step_userasync_step_user()
179 
180  @staticmethod
181  @callback
183  config_entry: ConfigEntry,
184  ) -> MotionEyeOptionsFlow:
185  """Get the Hyperion Options flow."""
186  return MotionEyeOptionsFlow()
187 
188 
190  """motionEye options flow."""
191 
192  async def async_step_init(
193  self, user_input: dict[str, Any] | None = None
194  ) -> ConfigFlowResult:
195  """Manage the options."""
196  if user_input is not None:
197  return self.async_create_entryasync_create_entry(title="", data=user_input)
198 
199  schema: dict[vol.Marker, type] = {
200  vol.Required(
201  CONF_WEBHOOK_SET,
202  default=self.config_entryconfig_entryconfig_entry.options.get(
203  CONF_WEBHOOK_SET,
204  DEFAULT_WEBHOOK_SET,
205  ),
206  ): bool,
207  vol.Required(
208  CONF_WEBHOOK_SET_OVERWRITE,
209  default=self.config_entryconfig_entryconfig_entry.options.get(
210  CONF_WEBHOOK_SET_OVERWRITE,
211  DEFAULT_WEBHOOK_SET_OVERWRITE,
212  ),
213  ): bool,
214  }
215 
216  if self.show_advanced_optionsshow_advanced_options:
217  # The input URL is not validated as being a URL, to allow for the possibility
218  # the template input won't be a valid URL until after it's rendered
219  description: dict[str, str] | None = None
220  if CONF_STREAM_URL_TEMPLATE in self.config_entryconfig_entryconfig_entry.options:
221  description = {
222  "suggested_value": self.config_entryconfig_entryconfig_entry.options[
223  CONF_STREAM_URL_TEMPLATE
224  ]
225  }
226 
227  schema[vol.Optional(CONF_STREAM_URL_TEMPLATE, description=description)] = (
228  str
229  )
230 
231  return self.async_show_formasync_show_form(step_id="init", data_schema=vol.Schema(schema))
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:52
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:194
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_update_reload_and_abort(self, ConfigEntry entry, *str|None|UndefinedType unique_id=UNDEFINED, str|UndefinedType title=UNDEFINED, Mapping[str, Any]|UndefinedType data=UNDEFINED, Mapping[str, Any]|UndefinedType data_updates=UNDEFINED, Mapping[str, Any]|UndefinedType options=UNDEFINED, str|UndefinedType reason=UNDEFINED, bool reload_even_if_entry_is_unchanged=True)
ConfigFlowResult async_step_hassio(self, HassioServiceInfo discovery_info)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
None _async_abort_entries_match(self, dict[str, Any]|None match_dict=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
OptionsFlow async_get_options_flow(ConfigEntry config_entry)
None config_entry(self, ConfigEntry value)
bool show_advanced_options(self)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
str|None source(self)
MotionEyeClient create_motioneye_client(*Any args, **Any kwargs)
Definition: __init__.py:95
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)