config_flow.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import logging
  2. import voluptuous as vol
  3. from homeassistant import config_entries, data_entry_flow
  4. from homeassistant.const import CONF_HOST, CONF_NAME
  5. from homeassistant.core import HomeAssistant, callback
  6. from . import DOMAIN
  7. from .device import TuyaLocalDevice
  8. from .const import (
  9. API_PROTOCOL_VERSIONS,
  10. CONF_DEVICE_ID,
  11. CONF_LOCAL_KEY,
  12. CONF_POLL_ONLY,
  13. CONF_PROTOCOL_VERSION,
  14. CONF_TYPE,
  15. )
  16. from .helpers.device_config import get_config
  17. _LOGGER = logging.getLogger(__name__)
  18. class ConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
  19. VERSION = 11
  20. CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
  21. device = None
  22. data = {}
  23. async def async_step_user(self, user_input=None):
  24. errors = {}
  25. devid_opts = {}
  26. host_opts = {"default": "Auto"}
  27. key_opts = {}
  28. proto_opts = {"default": "auto"}
  29. polling_opts = {"default": False}
  30. if user_input is not None:
  31. await self.async_set_unique_id(user_input[CONF_DEVICE_ID])
  32. self._abort_if_unique_id_configured()
  33. self.device = await async_test_connection(user_input, self.hass)
  34. if self.device:
  35. self.data = user_input
  36. return await self.async_step_select_type()
  37. else:
  38. errors["base"] = "connection"
  39. devid_opts["default"] = user_input[CONF_DEVICE_ID]
  40. host_opts["default"] = user_input[CONF_HOST]
  41. key_opts["default"] = user_input[CONF_LOCAL_KEY]
  42. proto_opts["default"] = user_input[CONF_PROTOCOL_VERSION]
  43. polling_opts["default"] = user_input[CONF_POLL_ONLY]
  44. return self.async_show_form(
  45. step_id="user",
  46. data_schema=vol.Schema(
  47. {
  48. vol.Required(CONF_DEVICE_ID, **devid_opts): str,
  49. vol.Required(CONF_HOST, **host_opts): str,
  50. vol.Required(CONF_LOCAL_KEY, **key_opts): str,
  51. vol.Required(
  52. CONF_PROTOCOL_VERSION,
  53. **proto_opts,
  54. ): vol.In(["auto"] + API_PROTOCOL_VERSIONS),
  55. vol.Required(CONF_POLL_ONLY, **polling_opts): bool,
  56. }
  57. ),
  58. errors=errors,
  59. )
  60. async def async_step_select_type(self, user_input=None):
  61. if user_input is not None:
  62. self.data[CONF_TYPE] = user_input[CONF_TYPE]
  63. return await self.async_step_choose_entities()
  64. types = []
  65. best_match = 0
  66. best_matching_type = None
  67. async for type in self.device.async_possible_types():
  68. types.append(type.config_type)
  69. q = type.match_quality(self.device._get_cached_state())
  70. if q > best_match:
  71. best_match = q
  72. best_matching_type = type.config_type
  73. if best_match < 100:
  74. best_match = int(best_match)
  75. dps = self.device._get_cached_state()
  76. _LOGGER.warning(
  77. f"Device matches {best_matching_type} with quality of {best_match}%. DPS: {dps}"
  78. )
  79. _LOGGER.warning(
  80. f"Report this to https://github.com/make-all/tuya-local/issues/"
  81. )
  82. if types:
  83. return self.async_show_form(
  84. step_id="select_type",
  85. data_schema=vol.Schema(
  86. {vol.Required(CONF_TYPE, default=best_matching_type): vol.In(types)}
  87. ),
  88. )
  89. else:
  90. return self.async_abort(reason="not_supported")
  91. async def async_step_choose_entities(self, user_input=None):
  92. if user_input is not None:
  93. title = user_input[CONF_NAME]
  94. del user_input[CONF_NAME]
  95. return self.async_create_entry(
  96. title=title, data={**self.data, **user_input}
  97. )
  98. config = get_config(self.data[CONF_TYPE])
  99. schema = {vol.Required(CONF_NAME, default=config.name): str}
  100. return self.async_show_form(
  101. step_id="choose_entities",
  102. data_schema=vol.Schema(schema),
  103. )
  104. @staticmethod
  105. @callback
  106. def async_get_options_flow(config_entry):
  107. return OptionsFlowHandler(config_entry)
  108. class OptionsFlowHandler(config_entries.OptionsFlow):
  109. def __init__(self, config_entry):
  110. """Initialize options flow."""
  111. self.config_entry = config_entry
  112. async def async_step_init(self, user_input=None):
  113. return await self.async_step_user(user_input)
  114. async def async_step_user(self, user_input=None):
  115. """Manage the options."""
  116. errors = {}
  117. config = {**self.config_entry.data, **self.config_entry.options}
  118. if user_input is not None:
  119. config = {**config, **user_input}
  120. device = await async_test_connection(config, self.hass)
  121. if device:
  122. return self.async_create_entry(title="", data=user_input)
  123. else:
  124. errors["base"] = "connection"
  125. schema = {
  126. vol.Required(CONF_LOCAL_KEY, default=config.get(CONF_LOCAL_KEY, "")): str,
  127. vol.Required(CONF_HOST, default=config.get(CONF_HOST, "")): str,
  128. vol.Required(
  129. CONF_PROTOCOL_VERSION, default=config.get(CONF_PROTOCOL_VERSION, "auto")
  130. ): vol.In(["auto"] + API_PROTOCOL_VERSIONS),
  131. vol.Required(
  132. CONF_POLL_ONLY, default=config.get(CONF_POLL_ONLY, False)
  133. ): bool,
  134. }
  135. cfg = get_config(config[CONF_TYPE])
  136. if cfg is None:
  137. return self.async_abort(reason="not_supported")
  138. return self.async_show_form(
  139. step_id="user",
  140. data_schema=vol.Schema(schema),
  141. errors=errors,
  142. )
  143. async def async_test_connection(config: dict, hass: HomeAssistant):
  144. domain_data = hass.data.get(DOMAIN)
  145. existing = domain_data.get(config[CONF_DEVICE_ID]) if domain_data else None
  146. if existing:
  147. existing["device"].pause()
  148. device = TuyaLocalDevice(
  149. "Test",
  150. config[CONF_DEVICE_ID],
  151. config[CONF_HOST],
  152. config[CONF_LOCAL_KEY],
  153. config[CONF_PROTOCOL_VERSION],
  154. hass,
  155. True,
  156. )
  157. await device.async_refresh()
  158. if existing:
  159. existing["device"].resume()
  160. return device if device.has_returned_state else None