Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Tuya."""
2 
3 from __future__ import annotations
4 
5 from collections.abc import Mapping
6 from typing import Any
7 
8 from tuya_sharing import LoginControl
9 import voluptuous as vol
10 
11 from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
12 from homeassistant.helpers import selector
13 
14 from .const import (
15  CONF_ENDPOINT,
16  CONF_TERMINAL_ID,
17  CONF_TOKEN_INFO,
18  CONF_USER_CODE,
19  DOMAIN,
20  TUYA_CLIENT_ID,
21  TUYA_RESPONSE_CODE,
22  TUYA_RESPONSE_MSG,
23  TUYA_RESPONSE_QR_CODE,
24  TUYA_RESPONSE_RESULT,
25  TUYA_RESPONSE_SUCCESS,
26  TUYA_SCHEMA,
27 )
28 
29 
30 class TuyaConfigFlow(ConfigFlow, domain=DOMAIN):
31  """Tuya config flow."""
32 
33  __user_code: str
34  __qr_code: str
35 
36  def __init__(self) -> None:
37  """Initialize the config flow."""
38  self.__login_control__login_control = LoginControl()
39 
40  async def async_step_user(
41  self, user_input: dict[str, Any] | None = None
42  ) -> ConfigFlowResult:
43  """Step user."""
44  errors = {}
45  placeholders = {}
46 
47  if user_input is not None:
48  success, response = await self.__async_get_qr_code__async_get_qr_code(
49  user_input[CONF_USER_CODE]
50  )
51  if success:
52  return await self.async_step_scanasync_step_scan()
53 
54  errors["base"] = "login_error"
55  placeholders = {
56  TUYA_RESPONSE_MSG: response.get(TUYA_RESPONSE_MSG, "Unknown error"),
57  TUYA_RESPONSE_CODE: response.get(TUYA_RESPONSE_CODE, "0"),
58  }
59  else:
60  user_input = {}
61 
62  return self.async_show_formasync_show_formasync_show_form(
63  step_id="user",
64  data_schema=vol.Schema(
65  {
66  vol.Required(
67  CONF_USER_CODE, default=user_input.get(CONF_USER_CODE, "")
68  ): str,
69  }
70  ),
71  errors=errors,
72  description_placeholders=placeholders,
73  )
74 
75  async def async_step_scan(
76  self, user_input: dict[str, Any] | None = None
77  ) -> ConfigFlowResult:
78  """Step scan."""
79  if user_input is None:
80  return self.async_show_formasync_show_formasync_show_form(
81  step_id="scan",
82  data_schema=vol.Schema(
83  {
84  vol.Optional("QR"): selector.QrCodeSelector(
85  config=selector.QrCodeSelectorConfig(
86  data=f"tuyaSmart--qrLogin?token={self.__qr_code}",
87  scale=5,
88  error_correction_level=selector.QrErrorCorrectionLevel.QUARTILE,
89  )
90  )
91  }
92  ),
93  )
94 
95  ret, info = await self.hass.async_add_executor_job(
96  self.__login_control__login_control.login_result,
97  self.__qr_code__qr_code,
98  TUYA_CLIENT_ID,
99  self.__user_code__user_code,
100  )
101  if not ret:
102  # Try to get a new QR code on failure
103  await self.__async_get_qr_code__async_get_qr_code(self.__user_code__user_code)
104  return self.async_show_formasync_show_formasync_show_form(
105  step_id="scan",
106  errors={"base": "login_error"},
107  data_schema=vol.Schema(
108  {
109  vol.Optional("QR"): selector.QrCodeSelector(
110  config=selector.QrCodeSelectorConfig(
111  data=f"tuyaSmart--qrLogin?token={self.__qr_code}",
112  scale=5,
113  error_correction_level=selector.QrErrorCorrectionLevel.QUARTILE,
114  )
115  )
116  }
117  ),
118  description_placeholders={
119  TUYA_RESPONSE_MSG: info.get(TUYA_RESPONSE_MSG, "Unknown error"),
120  TUYA_RESPONSE_CODE: info.get(TUYA_RESPONSE_CODE, 0),
121  },
122  )
123 
124  entry_data = {
125  CONF_USER_CODE: self.__user_code__user_code,
126  CONF_TOKEN_INFO: {
127  "t": info["t"],
128  "uid": info["uid"],
129  "expire_time": info["expire_time"],
130  "access_token": info["access_token"],
131  "refresh_token": info["refresh_token"],
132  },
133  CONF_TERMINAL_ID: info[CONF_TERMINAL_ID],
134  CONF_ENDPOINT: info[CONF_ENDPOINT],
135  }
136 
137  if self.sourcesourcesourcesource == SOURCE_REAUTH:
138  return self.async_update_reload_and_abortasync_update_reload_and_abort(
139  self._get_reauth_entry_get_reauth_entry(),
140  data=entry_data,
141  )
142 
143  return self.async_create_entryasync_create_entryasync_create_entry(
144  title=info.get("username"),
145  data=entry_data,
146  )
147 
148  async def async_step_reauth(
149  self, entry_data: Mapping[str, Any]
150  ) -> ConfigFlowResult:
151  """Handle initiation of re-authentication with Tuya."""
152  if CONF_USER_CODE in entry_data:
153  success, _ = await self.__async_get_qr_code__async_get_qr_code(entry_data[CONF_USER_CODE])
154  if success:
155  return await self.async_step_scanasync_step_scan()
156 
157  return await self.async_step_reauth_user_codeasync_step_reauth_user_code()
158 
160  self, user_input: dict[str, Any] | None = None
161  ) -> ConfigFlowResult:
162  """Handle re-authentication with a Tuya."""
163  errors = {}
164  placeholders = {}
165 
166  if user_input is not None:
167  success, response = await self.__async_get_qr_code__async_get_qr_code(
168  user_input[CONF_USER_CODE]
169  )
170  if success:
171  return await self.async_step_scanasync_step_scan()
172 
173  errors["base"] = "login_error"
174  placeholders = {
175  TUYA_RESPONSE_MSG: response.get(TUYA_RESPONSE_MSG, "Unknown error"),
176  TUYA_RESPONSE_CODE: response.get(TUYA_RESPONSE_CODE, "0"),
177  }
178  else:
179  user_input = {}
180 
181  return self.async_show_formasync_show_formasync_show_form(
182  step_id="reauth_user_code",
183  data_schema=vol.Schema(
184  {
185  vol.Required(
186  CONF_USER_CODE, default=user_input.get(CONF_USER_CODE, "")
187  ): str,
188  }
189  ),
190  errors=errors,
191  description_placeholders=placeholders,
192  )
193 
194  async def __async_get_qr_code(self, user_code: str) -> tuple[bool, dict[str, Any]]:
195  """Get the QR code."""
196  response = await self.hass.async_add_executor_job(
197  self.__login_control__login_control.qr_code,
198  TUYA_CLIENT_ID,
199  TUYA_SCHEMA,
200  user_code,
201  )
202  if success := response.get(TUYA_RESPONSE_SUCCESS, False):
203  self.__user_code__user_code = user_code
204  self.__qr_code__qr_code = response[TUYA_RESPONSE_RESULT][TUYA_RESPONSE_QR_CODE]
205  return success, response
ConfigFlowResult async_step_reauth_user_code(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:161
ConfigFlowResult async_step_reauth(self, Mapping[str, Any] entry_data)
Definition: config_flow.py:150
ConfigFlowResult async_step_scan(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:77
tuple[bool, dict[str, Any]] __async_get_qr_code(self, str user_code)
Definition: config_flow.py:194
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:42
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_update_reload_and_abort(self, ConfigEntry entry, *str|None|UndefinedType unique_id=UNDEFINED, str|UndefinedType title=UNDEFINED, Mapping[str, Any]|UndefinedType data=UNDEFINED, Mapping[str, Any]|UndefinedType data_updates=UNDEFINED, Mapping[str, Any]|UndefinedType options=UNDEFINED, str|UndefinedType reason=UNDEFINED, bool reload_even_if_entry_is_unchanged=True)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
str|None source(self)