config_flow.py 5.8 KB

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