1 """Config flow for the Reolink camera component."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
9 from reolink_aio.api
import ALLOWED_SPECIAL_CHARS
10 from reolink_aio.exceptions
import (
12 CredentialsInvalidError,
16 import voluptuous
as vol
38 from .const
import CONF_USE_HTTPS, DOMAIN
39 from .exceptions
import (
42 ReolinkWebhookException,
45 from .host
import ReolinkHost
46 from .util
import ReolinkConfigEntry, is_connected
48 _LOGGER = logging.getLogger(__name__)
50 DEFAULT_PROTOCOL =
"rtsp"
51 DEFAULT_OPTIONS = {CONF_PROTOCOL: DEFAULT_PROTOCOL}
55 """Handle Reolink options."""
58 self, user_input: dict[str, Any] |
None =
None
59 ) -> ConfigFlowResult:
60 """Manage the Reolink options."""
61 if user_input
is not None:
66 data_schema=vol.Schema(
71 ): selector.SelectSelector(
72 selector.SelectSelectorConfig(
74 selector.SelectOptionDict(
78 selector.SelectOptionDict(
82 selector.SelectOptionDict(
95 """Handle a config flow for Reolink device."""
101 self.
_host_host: str |
None =
None
103 self.
_password_password: str |
None =
None
108 config_entry: ReolinkConfigEntry,
109 ) -> ReolinkOptionsFlowHandler:
110 """Options callback for Reolink."""
114 self, entry_data: Mapping[str, Any]
115 ) -> ConfigFlowResult:
116 """Perform reauth upon an authentication error or no admin privileges."""
117 self.
_host_host = entry_data[CONF_HOST]
121 **self.context[
"title_placeholders"],
122 "ip_address": entry_data[CONF_HOST],
123 "hostname": self.context[
"title_placeholders"][
"name"],
125 self.context[
"title_placeholders"] = placeholders
129 self, user_input: dict[str, Any] |
None =
None
130 ) -> ConfigFlowResult:
131 """Dialog that informs the user that reauth is required."""
132 if user_input
is not None:
134 placeholders = {
"name": self.context[
"title_placeholders"][
"name"]}
136 step_id=
"reauth_confirm", description_placeholders=placeholders
140 self, user_input: dict[str, Any] |
None =
None
141 ) -> ConfigFlowResult:
142 """Perform a reconfiguration."""
144 self.
_host_host = entry_data[CONF_HOST]
145 self.
_username_username = entry_data[CONF_USERNAME]
146 self.
_password_password = entry_data[CONF_PASSWORD]
150 self, discovery_info: dhcp.DhcpServiceInfo
151 ) -> ConfigFlowResult:
152 """Handle discovery via dhcp."""
153 mac_address =
format_mac(discovery_info.macaddress)
157 and CONF_PASSWORD
in existing_entry.data
158 and existing_entry.data[CONF_HOST] != discovery_info.ip
162 "Reolink DHCP reported new IP '%s', "
163 "but connection to camera seems to be okay, so sticking to IP '%s'",
165 existing_entry.data[CONF_HOST],
170 new_config =
dict(existing_entry.data)
171 new_config[CONF_HOST] = discovery_info.ip
172 host =
ReolinkHost(self.hass, new_config, existing_entry.options)
174 await host.api.get_state(
"GetLocalLink")
175 await host.api.logout()
176 except ReolinkError
as err:
178 "Reolink DHCP reported new IP '%s', "
179 "but got error '%s' trying to connect, so sticking to IP '%s'",
182 existing_entry.data[CONF_HOST],
184 raise AbortFlow(
"already_configured")
from err
185 if format_mac(host.api.mac_address) != mac_address:
187 "Reolink mac address '%s' at new IP '%s' from DHCP, "
188 "does not match mac '%s' of config entry, so sticking to IP '%s'",
192 existing_entry.data[CONF_HOST],
198 self.context[
"title_placeholders"] = {
199 "ip_address": discovery_info.ip,
200 "hostname": discovery_info.hostname,
203 self.
_host_host = discovery_info.ip
207 self, user_input: dict[str, Any] |
None =
None
208 ) -> ConfigFlowResult:
209 """Handle the initial step."""
213 "troubleshooting_link":
"https://www.home-assistant.io/integrations/reolink/#troubleshooting",
216 if user_input
is not None:
217 if CONF_HOST
not in user_input:
218 user_input[CONF_HOST] = self.
_host_host
221 self.
_username_username = user_input[CONF_USERNAME]
222 self.
_password_password = user_input[CONF_PASSWORD]
223 self.
_host_host = user_input[CONF_HOST]
225 host =
ReolinkHost(self.hass, user_input, DEFAULT_OPTIONS)
227 await host.async_init()
229 errors[CONF_USERNAME] =
"not_admin"
230 placeholders[
"username"] = host.api.username
231 placeholders[
"userlevel"] = host.api.user_level
232 except PasswordIncompatible:
233 errors[CONF_PASSWORD] =
"password_incompatible"
234 placeholders[
"special_chars"] = ALLOWED_SPECIAL_CHARS
235 except CredentialsInvalidError:
236 errors[CONF_PASSWORD] =
"invalid_auth"
237 except LoginFirmwareError:
238 errors[
"base"] =
"update_needed"
239 placeholders[
"current_firmware"] = host.api.sw_version
240 placeholders[
"needed_firmware"] = (
241 host.api.sw_version_required.version_string
243 placeholders[
"download_center_url"] = (
244 "https://reolink.com/download-center"
246 except ApiError
as err:
247 placeholders[
"error"] =
str(err)
248 errors[CONF_HOST] =
"api_error"
249 except ReolinkWebhookException
as err:
250 placeholders[
"error"] =
str(err)
251 placeholders[
"more_info"] = (
252 "https://www.home-assistant.io/more-info/no-url-available/#configuring-the-instance-url"
254 errors[
"base"] =
"webhook_exception"
255 except (ReolinkError, ReolinkException)
as err:
256 placeholders[
"error"] =
str(err)
257 errors[CONF_HOST] =
"cannot_connect"
258 except Exception
as err:
259 _LOGGER.exception(
"Unexpected exception")
260 placeholders[
"error"] =
str(err)
261 errors[CONF_HOST] =
"unknown"
266 user_input[CONF_PORT] = host.api.port
267 user_input[CONF_USE_HTTPS] = host.api.use_https
269 mac_address =
format_mac(host.api.mac_address)
284 title=
str(host.api.nvr_name),
286 options=DEFAULT_OPTIONS,
289 data_schema = vol.Schema(
291 vol.Required(CONF_USERNAME, default=self.
_username_username): str,
292 vol.Required(CONF_PASSWORD, default=self.
_password_password): str,
296 data_schema = data_schema.extend(
298 vol.Required(CONF_HOST, default=self.
_host_host): str,
302 data_schema = data_schema.extend(
304 vol.Optional(CONF_PORT): cv.positive_int,
305 vol.Required(CONF_USE_HTTPS, default=
False): bool,
311 data_schema=data_schema,
313 description_placeholders=placeholders,
ReolinkOptionsFlowHandler async_get_options_flow(ReolinkConfigEntry config_entry)
ConfigFlowResult async_step_reauth_confirm(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_reconfigure(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
ConfigFlowResult async_step_dhcp(self, dhcp.DhcpServiceInfo discovery_info)
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry _get_reauth_entry(self)
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
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_user(self, dict[str, Any]|None user_input=None)
ConfigEntry _get_reconfigure_entry(self)
None _abort_if_unique_id_mismatch(self, *str reason="unique_id_mismatch", Mapping[str, str]|None description_placeholders=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)
ConfigEntry config_entry(self)
None config_entry(self, ConfigEntry value)
_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)