config_flow.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. """Config flow for Integration 101 Template integration."""
  2. from __future__ import annotations
  3. import logging
  4. from typing import Any
  5. import voluptuous as vol
  6. from homeassistant.config_entries import (
  7. ConfigEntry,
  8. ConfigFlow,
  9. ConfigFlowResult,
  10. OptionsFlow,
  11. )
  12. from homeassistant.const import (
  13. CONF_HOST,
  14. CONF_PASSWORD,
  15. CONF_SCAN_INTERVAL,
  16. CONF_USERNAME,
  17. )
  18. from homeassistant.core import HomeAssistant, callback
  19. from homeassistant.exceptions import HomeAssistantError
  20. from homeassistant.helpers.aiohttp_client import async_get_clientsession
  21. from .api import (RouterAPI,
  22. RouterAPIAuthError,
  23. RouterAPIConnectionError,
  24. RouterAPIInvalidResponse)
  25. from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, MIN_SCAN_INTERVAL
  26. _LOGGER = logging.getLogger(__name__)
  27. # TODO adjust the data schema to the data that you need
  28. STEP_USER_DATA_SCHEMA = vol.Schema(
  29. {
  30. vol.Required(CONF_HOST, description={"suggested_value": "192.168.1.1"}): str,
  31. vol.Required(CONF_USERNAME, description={"suggested_value": "admin"}): str,
  32. vol.Required(CONF_PASSWORD, description={"suggested_value": "*****"}): str,
  33. }
  34. )
  35. async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
  36. """Validate the user input allows us to connect.
  37. Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
  38. """
  39. # TODO validate the data can be used to set up a connection.
  40. # If your PyPI package is not built with async, pass your methods
  41. # to the executor:
  42. # await hass.async_add_executor_job(
  43. # your_validate_func, data[CONF_USERNAME], data[CONF_PASSWORD]
  44. # )
  45. session = async_get_clientsession(
  46. hass=hass,
  47. verify_ssl=False
  48. )
  49. api = RouterAPI(host=data[CONF_HOST],
  50. user=data[CONF_USERNAME],
  51. pwd=data[CONF_PASSWORD],
  52. session=session)
  53. try:
  54. await api.async_login()
  55. except RouterAPIAuthError as err:
  56. raise InvalidAuth from err
  57. except RouterAPIConnectionError as err:
  58. raise CannotConnect from err
  59. return {"title": f"Odido Klik & Klaar router - {data[CONF_HOST]}"}
  60. class RouterConfigFlow(ConfigFlow, domain=DOMAIN):
  61. """Handle a config flow for Example Integration."""
  62. VERSION = 1
  63. _input_data: dict[str, Any]
  64. @staticmethod
  65. @callback
  66. def async_get_options_flow(config_entry):
  67. """Get the options flow for this handler."""
  68. # Remove this method and the ExampleOptionsFlowHandler class
  69. # if you do not want any options for your integration.
  70. return RouterOptionsFlowHandler(config_entry)
  71. async def async_step_user(
  72. self, user_input: dict[str, Any] | None = None
  73. ) -> ConfigFlowResult:
  74. """Handle the initial step."""
  75. # Called when you initiate adding an integration via the UI
  76. errors: dict[str, str] = {}
  77. if user_input is not None:
  78. # The form has been filled in and submitted, so process the data provided.
  79. try:
  80. # Validate that the setup data is valid and if not handle errors.
  81. # The errors["base"] values match the values in your strings.json and translation files.
  82. info = await validate_input(self.hass, user_input)
  83. except CannotConnect:
  84. errors["base"] = "cannot_connect"
  85. except InvalidAuth:
  86. errors["base"] = "invalid_auth"
  87. except Exception: # pylint: disable=broad-except
  88. _LOGGER.exception("Unexpected exception")
  89. errors["base"] = "unknown"
  90. if "base" not in errors:
  91. # Validation was successful, so create a unique id for this instance of your integration
  92. # and create the config entry.
  93. await self.async_set_unique_id(info.get("title"))
  94. self._abort_if_unique_id_configured()
  95. return self.async_create_entry(title=info["title"], data=user_input)
  96. # Show initial form.
  97. return self.async_show_form(
  98. step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
  99. )
  100. async def async_step_reconfigure(
  101. self, user_input: dict[str, Any] | None = None
  102. ) -> ConfigFlowResult:
  103. """Add reconfigure step to allow to reconfigure a config entry."""
  104. # This methid displays a reconfigure option in the integration and is
  105. # different to options.
  106. # It can be used to reconfigure any of the data submitted when first installed.
  107. # This is optional and can be removed if you do not want to allow reconfiguration.
  108. errors: dict[str, str] = {}
  109. config_entry = self.hass.config_entries.async_get_entry(
  110. self.context["entry_id"]
  111. )
  112. if user_input is not None:
  113. try:
  114. user_input[CONF_HOST] = config_entry.data[CONF_HOST]
  115. await validate_input(self.hass, user_input)
  116. except CannotConnect:
  117. errors["base"] = "cannot_connect"
  118. except InvalidAuth:
  119. errors["base"] = "invalid_auth"
  120. except Exception: # pylint: disable=broad-except
  121. _LOGGER.exception("Unexpected exception")
  122. errors["base"] = "unknown"
  123. else:
  124. return self.async_update_reload_and_abort(
  125. config_entry,
  126. unique_id=config_entry.unique_id,
  127. data={**config_entry.data, **user_input},
  128. reason="reconfigure_successful",
  129. )
  130. return self.async_show_form(
  131. step_id="reconfigure",
  132. data_schema=vol.Schema(
  133. {
  134. vol.Required(
  135. CONF_USERNAME, default=config_entry.data[CONF_USERNAME]
  136. ): str,
  137. vol.Required(CONF_PASSWORD): str,
  138. }
  139. ),
  140. errors=errors,
  141. )
  142. class RouterOptionsFlowHandler(OptionsFlow):
  143. """Handles the options flow."""
  144. def __init__(self, config_entry: ConfigEntry) -> None:
  145. """Initialize options flow."""
  146. self.config_entry = config_entry
  147. self.options = dict(config_entry.options)
  148. async def async_step_init(self, user_input=None):
  149. """Handle options flow."""
  150. if user_input is not None:
  151. options = self.config_entry.options | user_input
  152. return self.async_create_entry(title="", data=options)
  153. # It is recommended to prepopulate options fields with default values if available.
  154. # These will be the same default values you use on your coordinator for setting variable values
  155. # if the option has not been set.
  156. data_schema = vol.Schema(
  157. {
  158. vol.Required(
  159. CONF_SCAN_INTERVAL,
  160. default=self.options.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL),
  161. ): (vol.All(vol.Coerce(int), vol.Clamp(min=MIN_SCAN_INTERVAL))),
  162. }
  163. )
  164. return self.async_show_form(step_id="init", data_schema=data_schema)
  165. class CannotConnect(HomeAssistantError):
  166. """Error to indicate we cannot connect."""
  167. class InvalidAuth(HomeAssistantError):
  168. """Error to indicate there is invalid auth."""