config_flow.py 5.7 KB

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