"""Config flow for Tuya.""" from __future__ import annotations from collections.abc import Mapping from io import BytesIO from typing import Any import segno from tuya_sharing import LoginControl import voluptuous as vol from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.data_entry_flow import FlowResult from .const import ( CONF_ENDPOINT, CONF_TERMINAL_ID, CONF_TOKEN_INFO, CONF_USER_CODE, DOMAIN, TUYA_CLIENT_ID, TUYA_RESPONSE_CODE, TUYA_RESPONSE_MSG, TUYA_RESPONSE_QR_CODE, TUYA_RESPONSE_RESULT, TUYA_RESPONSE_SUCCESS, TUYA_SCHEMA, ) class TuyaConfigFlow(ConfigFlow, domain=DOMAIN): """Tuya config flow.""" __user_code: str __qr_code: str __qr_image: str __reauth_entry: ConfigEntry | None = None def __init__(self) -> None: """Initialize the config flow.""" self.__login_control = LoginControl() async def async_step_user( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Step user.""" errors = {} placeholders = {} if user_input is not None: success, response = await self.__async_get_qr_code( user_input[CONF_USER_CODE] ) if success: return await self.async_step_scan() errors["base"] = "login_error" placeholders = { TUYA_RESPONSE_MSG: response.get(TUYA_RESPONSE_MSG, "Unknown error"), TUYA_RESPONSE_CODE: response.get(TUYA_RESPONSE_CODE, "0"), } else: user_input = {} return self.async_show_form( step_id="user", data_schema=vol.Schema( { vol.Required( CONF_USER_CODE, default=user_input.get(CONF_USER_CODE, "") ): str, } ), errors=errors, description_placeholders=placeholders, ) async def async_step_scan( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Step scan.""" if user_input is None: return self.async_show_form( step_id="scan", description_placeholders={ TUYA_RESPONSE_QR_CODE: self.__qr_image, }, ) ret, info = await self.hass.async_add_executor_job( self.__login_control.login_result, self.__qr_code, TUYA_CLIENT_ID, self.__user_code, ) if not ret: return self.async_show_form( step_id="scan", errors={"base": "login_error"}, description_placeholders={ TUYA_RESPONSE_QR_CODE: self.__qr_image, TUYA_RESPONSE_MSG: info.get(TUYA_RESPONSE_MSG, "Unknown error"), TUYA_RESPONSE_CODE: info.get(TUYA_RESPONSE_CODE, 0), }, ) entry_data = { CONF_USER_CODE: self.__user_code, CONF_TOKEN_INFO: { "t": info["t"], "uid": info["uid"], "expire_time": info["expire_time"], "access_token": info["access_token"], "refresh_token": info["refresh_token"], }, CONF_TERMINAL_ID: info[CONF_TERMINAL_ID], CONF_ENDPOINT: info[CONF_ENDPOINT], } if self.__reauth_entry: return self.async_update_reload_and_abort( self.__reauth_entry, data=entry_data, ) return self.async_create_entry( title=info.get("username"), data=entry_data, ) async def async_step_reauth(self, _: Mapping[str, Any]) -> FlowResult: """Handle initiation of re-authentication with Tuya.""" self.__reauth_entry = self.hass.config_entries.async_get_entry( self.context["entry_id"] ) if self.__reauth_entry and CONF_USER_CODE in self.__reauth_entry.data: success, _ = await self.__async_get_qr_code( self.__reauth_entry.data[CONF_USER_CODE] ) if success: return await self.async_step_scan() return await self.async_step_reauth_user_code() async def async_step_reauth_user_code( self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle re-authentication with a Tuya.""" errors = {} placeholders = {} if user_input is not None: success, response = await self.__async_get_qr_code( user_input[CONF_USER_CODE] ) if success: return await self.async_step_scan() errors["base"] = "login_error" placeholders = { TUYA_RESPONSE_MSG: response.get(TUYA_RESPONSE_MSG, "Unknown error"), TUYA_RESPONSE_CODE: response.get(TUYA_RESPONSE_CODE, "0"), } else: user_input = {} return self.async_show_form( step_id="reauth_user_code", data_schema=vol.Schema( { vol.Required( CONF_USER_CODE, default=user_input.get(CONF_USER_CODE, "") ): str, } ), errors=errors, description_placeholders=placeholders, ) async def __async_get_qr_code(self, user_code: str) -> tuple[bool, dict[str, Any]]: """Get the QR code.""" response = await self.hass.async_add_executor_job( self.__login_control.qr_code, TUYA_CLIENT_ID, TUYA_SCHEMA, user_code, ) if success := response.get(TUYA_RESPONSE_SUCCESS, False): self.__user_code = user_code self.__qr_code = response[TUYA_RESPONSE_RESULT][TUYA_RESPONSE_QR_CODE] self.__qr_image = _generate_qr_code(self.__qr_code) return success, response def _generate_qr_code(data: str) -> str: """Create an SVG QR code that can be scanned with the Smart Life app.""" qr_code = segno.make(f"tuyaSmart--qrLogin?token={data}", error="h") with BytesIO() as buffer: qr_code.save( buffer, kind="svg", border=5, scale=5, xmldecl=False, svgns=False, svgclass=None, lineclass=None, svgversion=2, dark="#1abcf2", ) return str(buffer.getvalue().decode("ascii"))