config_flow.py 7.3 KB

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