1 """Config flow for Overkiz integration."""
3 from __future__
import annotations
5 from collections.abc
import Mapping
6 from typing
import Any, cast
8 from aiohttp
import ClientConnectorCertificateError, ClientError
9 from pyoverkiz.client
import OverkizClient
10 from pyoverkiz.const
import SERVERS_WITH_LOCAL_API, SUPPORTED_SERVERS
11 from pyoverkiz.enums
import APIType, Server
12 from pyoverkiz.exceptions
import (
13 BadCredentialsException,
14 CozyTouchBadCredentialsException,
16 NotSuchTokenException,
17 TooManyAttemptsBannedException,
18 TooManyRequestsException,
21 from pyoverkiz.models
import OverkizServer
22 from pyoverkiz.obfuscate
import obfuscate_id
23 from pyoverkiz.utils
import generate_local_server, is_overkiz_gateway
24 import voluptuous
as vol
38 from .const
import CONF_API_TYPE, CONF_HUB, DEFAULT_SERVER, DOMAIN, LOGGER
42 """Error to indicate Somfy Developer Mode is disabled."""
46 """Handle a config flow for Overkiz (by Somfy)."""
50 _api_type: APIType = APIType.CLOUD
51 _user: str |
None =
None
52 _server: str = DEFAULT_SERVER
53 _host: str =
"gateway-xxxx-xxxx-xxxx.local:8443"
56 """Validate user credentials."""
57 user_input[CONF_API_TYPE] = self.
_api_type_api_type
59 client = self._create_cloud_client(
60 username=user_input[CONF_USERNAME],
61 password=user_input[CONF_PASSWORD],
62 server=SUPPORTED_SERVERS[user_input[CONF_HUB]],
64 await client.login(register_event_listener=
False)
68 user_input[CONF_TOKEN] = await self._create_local_api_token(
70 host=user_input[CONF_HOST],
71 verify_ssl=user_input[CONF_VERIFY_SSL],
75 if gateways := await client.get_gateways():
76 for gateway
in gateways:
77 if is_overkiz_gateway(gateway.id):
78 gateway_id = gateway.id
84 self, user_input: dict[str, Any] |
None =
None
85 ) -> ConfigFlowResult:
86 """Handle the initial step via config flow."""
88 self.
_server_server = user_input[CONF_HUB]
92 if self.
_server_server
in SERVERS_WITH_LOCAL_API:
93 return await self.async_step_local_or_cloud()
95 return await self.async_step_cloud()
99 data_schema=vol.Schema(
101 vol.Required(CONF_HUB, default=self.
_server_server): vol.In(
102 {key: hub.name
for key, hub
in SUPPORTED_SERVERS.items()}
108 async
def async_step_local_or_cloud(
109 self, user_input: dict[str, Any] |
None =
None
110 ) -> ConfigFlowResult:
111 """Users can choose between local API or cloud API via config flow."""
113 self.
_api_type_api_type = user_input[CONF_API_TYPE]
115 if self.
_api_type_api_type == APIType.LOCAL:
116 return await self.async_step_local()
118 return await self.async_step_cloud()
121 step_id=
"local_or_cloud",
122 data_schema=vol.Schema(
124 vol.Required(CONF_API_TYPE): vol.In(
126 APIType.LOCAL:
"Local API",
127 APIType.CLOUD:
"Cloud API",
134 async
def async_step_cloud(
135 self, user_input: dict[str, Any] |
None =
None
136 ) -> ConfigFlowResult:
137 """Handle the cloud authentication step via config flow."""
138 errors: dict[str, str] = {}
139 description_placeholders = {}
142 self.
_user_user = user_input[CONF_USERNAME]
145 user_input[CONF_HUB] = self.
_server_server
149 except TooManyRequestsException:
150 errors[
"base"] =
"too_many_requests"
151 except BadCredentialsException
as exception:
154 if user_input[CONF_HUB] == Server.ATLANTIC_COZYTOUCH
and not isinstance(
155 exception, CozyTouchBadCredentialsException
157 description_placeholders[
"unsupported_device"] =
"CozyTouch"
158 errors[
"base"] =
"unsupported_hardware"
160 errors[
"base"] =
"invalid_auth"
161 except (TimeoutError, ClientError):
162 errors[
"base"] =
"cannot_connect"
163 except MaintenanceException:
164 errors[
"base"] =
"server_in_maintenance"
165 except TooManyAttemptsBannedException:
166 errors[
"base"] =
"too_many_attempts"
167 except UnknownUserException:
170 description_placeholders[
"unsupported_device"] =
"Somfy Protect"
171 errors[
"base"] =
"unsupported_hardware"
173 errors[
"base"] =
"unknown"
174 LOGGER.exception(
"Unknown error")
187 title=user_input[CONF_USERNAME], data=user_input
192 data_schema=vol.Schema(
194 vol.Required(CONF_USERNAME, default=self.
_user_user): str,
195 vol.Required(CONF_PASSWORD): str,
198 description_placeholders=description_placeholders,
202 async
def async_step_local(
203 self, user_input: dict[str, Any] |
None =
None
204 ) -> ConfigFlowResult:
205 """Handle the local authentication step via config flow."""
207 description_placeholders = {}
210 self.
_host_host = user_input[CONF_HOST]
211 self.
_user_user = user_input[CONF_USERNAME]
214 user_input[CONF_HUB] = self.
_server_server
218 except TooManyRequestsException:
219 errors[
"base"] =
"too_many_requests"
220 except BadCredentialsException:
221 errors[
"base"] =
"invalid_auth"
222 except ClientConnectorCertificateError
as exception:
223 errors[
"base"] =
"certificate_verify_failed"
224 LOGGER.debug(exception)
225 except (TimeoutError, ClientError)
as exception:
226 errors[
"base"] =
"cannot_connect"
227 LOGGER.debug(exception)
228 except MaintenanceException:
229 errors[
"base"] =
"server_in_maintenance"
230 except TooManyAttemptsBannedException:
231 errors[
"base"] =
"too_many_attempts"
232 except NotSuchTokenException:
233 errors[
"base"] =
"no_such_token"
234 except DeveloperModeDisabled:
235 errors[
"base"] =
"developer_mode_disabled"
236 except UnknownUserException:
239 description_placeholders[
"unsupported_device"] =
"Somfy Protect"
240 errors[
"base"] =
"unsupported_hardware"
242 errors[
"base"] =
"unknown"
243 LOGGER.exception(
"Unknown error")
256 title=user_input[CONF_HOST], data=user_input
261 data_schema=vol.Schema(
263 vol.Required(CONF_HOST, default=self.
_host_host): str,
264 vol.Required(CONF_USERNAME, default=self.
_user_user): str,
265 vol.Required(CONF_PASSWORD): str,
266 vol.Required(CONF_VERIFY_SSL, default=
True): bool,
269 description_placeholders=description_placeholders,
274 self, discovery_info: dhcp.DhcpServiceInfo
275 ) -> ConfigFlowResult:
276 """Handle DHCP discovery."""
277 hostname = discovery_info.hostname
278 gateway_id = hostname[8:22]
279 self.
_host_host = f
"gateway-{gateway_id}.local:8443"
281 LOGGER.debug(
"DHCP discovery detected gateway %s", obfuscate_id(gateway_id))
282 return await self._process_discovery(gateway_id)
285 self, discovery_info: zeroconf.ZeroconfServiceInfo
286 ) -> ConfigFlowResult:
287 """Handle ZeroConf discovery."""
288 properties = discovery_info.properties
289 gateway_id = properties[
"gateway_pin"]
290 hostname = discovery_info.hostname
293 "ZeroConf discovery detected gateway %s on %s (%s)",
294 obfuscate_id(gateway_id),
299 if discovery_info.type ==
"_kizbox._tcp.local.":
300 self.
_host_host = f
"gateway-{gateway_id}.local:8443"
302 if discovery_info.type ==
"_kizboxdev._tcp.local.":
303 self.
_host_host = f
"{discovery_info.hostname[:-1]}:{discovery_info.port}"
306 return await self._process_discovery(gateway_id)
308 async
def _process_discovery(self, gateway_id: str) -> ConfigFlowResult:
309 """Handle discovery of a gateway."""
312 self.context[
"title_placeholders"] = {
"gateway_id": gateway_id}
316 async
def async_step_reauth(
317 self, entry_data: Mapping[str, Any]
318 ) -> ConfigFlowResult:
321 self.context[
"title_placeholders"] = {
"gateway_id": cast(str, self.
unique_idunique_id)}
323 self.
_user_user = entry_data[CONF_USERNAME]
324 self.
_server_server = entry_data[CONF_HUB]
325 self.
_api_type_api_type = entry_data.get(CONF_API_TYPE, APIType.CLOUD)
327 if self.
_api_type_api_type == APIType.LOCAL:
328 self.
_host_host = entry_data[CONF_HOST]
332 def _create_cloud_client(
333 self, username: str, password: str, server: OverkizServer
336 return OverkizClient(
337 username=username, password=password, server=server, session=session
340 async
def _create_local_api_token(
341 self, cloud_client: OverkizClient, host: str, verify_ssl: bool
343 """Create local API token."""
345 gateways = await cloud_client.get_gateways()
348 for gateway
in gateways:
351 if is_overkiz_gateway(gateway.id):
352 gateway_id = gateway.id
354 developer_mode = await cloud_client.get_setup_option(
355 f
"developerMode-{gateway_id}"
358 if developer_mode
is None:
359 raise DeveloperModeDisabled
361 token = await cloud_client.generate_local_token(gateway_id)
362 await cloud_client.activate_local_token(
363 gateway_id=gateway_id, token=token, label=
"Home Assistant/local"
369 local_client = OverkizClient(
374 server=generate_local_server(host=host),
375 verify_ssl=verify_ssl,
378 await local_client.login()
dict[str, Any] async_validate_input(self, dict[str, Any] user_input)
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)
ConfigFlowResult async_step_dhcp(self, DhcpServiceInfo discovery_info)
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)
ConfigFlowResult async_step_zeroconf(self, ZeroconfServiceInfo discovery_info)
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)
_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)
aiohttp.ClientSession async_create_clientsession()