diff --git a/custom_components/localtuya/__init__.py b/custom_components/localtuya/__init__.py index 9b38548..e431dd3 100644 --- a/custom_components/localtuya/__init__.py +++ b/custom_components/localtuya/__init__.py @@ -29,9 +29,10 @@ from homeassistant.helpers.event import async_track_time_interval from .cloud_api import TuyaCloudApi from .common import TuyaDevice, async_config_entry_by_device_id -from .config_flow import config_schema +from .config_flow import config_schema, ENTRIES_VERSION from .const import ( ATTR_UPDATED_AT, + CONF_NO_CLOUD, CONF_PRODUCT_KEY, CONF_USER_ID, DATA_CLOUD, @@ -183,10 +184,10 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_migrate_entry(hass, config_entry: ConfigEntry): """Migrate old entries merging all of them in one.""" - new_version = 2 + new_version = ENTRIES_VERSION + stored_entries = hass.config_entries.async_entries(DOMAIN) if config_entry.version == 1: _LOGGER.debug("Migrating config entry from version %s", config_entry.version) - stored_entries = hass.config_entries.async_entries(DOMAIN) if config_entry.entry_id == stored_entries[0].entry_id: _LOGGER.debug( @@ -198,6 +199,7 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry): new_data[CONF_CLIENT_SECRET] = "" new_data[CONF_USER_ID] = "" new_data[CONF_USERNAME] = DOMAIN + new_data[CONF_NO_CLOUD] = True new_data[CONF_DEVICES] = { config_entry.data[CONF_DEVICE_ID]: config_entry.data.copy() } @@ -223,24 +225,41 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry): config_entry.entry_id, new_version, ) + return True async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up LocalTuya integration from a config entry.""" - unsub_listener = entry.add_update_listener(update_listener) - hass.data[DOMAIN][UNSUB_LISTENER] = unsub_listener + if entry.version < ENTRIES_VERSION: + _LOGGER.debug( + "Skipping setup for entry %s since its version (%s) is old", + entry.entry_id, + entry.version, + ) + return + + _LOGGER.debug("ASE: %s %s", entry.entry_id, entry.data) region = entry.data[CONF_REGION] client_id = entry.data[CONF_CLIENT_ID] secret = entry.data[CONF_CLIENT_SECRET] user_id = entry.data[CONF_USER_ID] tuya_api = TuyaCloudApi(hass, region, client_id, secret, user_id) - res = await tuya_api.async_get_access_token() - if res != "ok": - _LOGGER.error("Cloud API connection failed: %s", res) - _LOGGER.info("Cloud API connection succeeded.") - res = await tuya_api.async_get_devices_list() + no_cloud = True + if CONF_NO_CLOUD in entry.data: + no_cloud = entry.data.get(CONF_NO_CLOUD) + _LOGGER.debug("ASE DC: %s", no_cloud) + if no_cloud: + _LOGGER.info("Cloud API account not configured.") + # wait 1 second to make sure possible migration has finished + await asyncio.sleep(1) + else: + res = await tuya_api.async_get_access_token() + if res != "ok": + _LOGGER.error("Cloud API connection failed: %s", res) + _LOGGER.info("Cloud API connection succeeded.") + res = await tuya_api.async_get_devices_list() hass.data[DOMAIN][DATA_CLOUD] = tuya_api async def setup_entities(dev_id): @@ -262,6 +281,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): for dev_id in entry.data[CONF_DEVICES]: hass.async_create_task(setup_entities(dev_id)) + unsub_listener = entry.add_update_listener(update_listener) + hass.data[DOMAIN][entry.entry_id] = {UNSUB_LISTENER: unsub_listener} + return True @@ -269,7 +291,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" platforms = {} - hass.data[DOMAIN][UNSUB_LISTENER]() + hass.data[DOMAIN][entry.entry_id][UNSUB_LISTENER]() for dev_id, device in hass.data[DOMAIN][TUYA_DEVICES].items(): if device.connected: await device.close() diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index a995fe1..cb74ab1 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -38,6 +38,7 @@ from .const import ( CONF_PRODUCT_NAME, CONF_PROTOCOL_VERSION, CONF_SETUP_CLOUD, + CONF_NO_CLOUD, CONF_USER_ID, DATA_CLOUD, DATA_DISCOVERY, @@ -48,6 +49,8 @@ from .discovery import discover _LOGGER = logging.getLogger(__name__) +ENTRIES_VERSION = 2 + PLATFORM_TO_ADD = "platform_to_add" NO_ADDITIONAL_ENTITIES = "no_additional_entities" SELECTED_DEVICE = "selected_device" @@ -69,10 +72,11 @@ CONFIGURE_SCHEMA = vol.Schema( CLOUD_SETUP_SCHEMA = vol.Schema( { vol.Required(CONF_REGION, default="eu"): vol.In(["eu", "us", "cn", "in"]), - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_CLIENT_SECRET): cv.string, - vol.Required(CONF_USER_ID): cv.string, + vol.Optional(CONF_CLIENT_ID): cv.string, + vol.Optional(CONF_CLIENT_SECRET): cv.string, + vol.Optional(CONF_USER_ID): cv.string, vol.Optional(CONF_USERNAME, default=DOMAIN): cv.string, + vol.Required(CONF_NO_CLOUD, default=False): bool, } ) @@ -279,7 +283,7 @@ async def attempt_cloud_connection(hass, user_input): class LocaltuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for LocalTuya integration.""" - VERSION = 2 + VERSION = ENTRIES_VERSION CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL @staticmethod @@ -296,9 +300,14 @@ class LocaltuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors = {} placeholders = {} if user_input is not None: + if user_input.get(CONF_NO_CLOUD): + for i in [CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_USER_ID]: + user_input[i] = "" + return await self._create_entry(user_input) + cloud_api, res = await attempt_cloud_connection(self.hass, user_input) - if len(res) == 0: + if not res: return await self._create_entry(user_input) errors["base"] = res["reason"] placeholders = {"msg": res["msg"]} @@ -315,8 +324,8 @@ class LocaltuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _create_entry(self, user_input): """Register new entry.""" - if self._async_current_entries(): - return self.async_abort(reason="already_configured") + # if self._async_current_entries(): + # return self.async_abort(reason="already_configured") await self.async_set_unique_id(user_input.get(CONF_USER_ID)) user_input[CONF_DEVICES] = {} @@ -370,9 +379,22 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): errors = {} placeholders = {} if user_input is not None: + if user_input.get(CONF_NO_CLOUD): + new_data = self.config_entry.data.copy() + new_data.update(user_input) + for i in [CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_USER_ID]: + new_data[i] = "" + self.hass.config_entries.async_update_entry( + self.config_entry, + data=new_data, + ) + return self.async_create_entry( + title=new_data.get(CONF_USERNAME), data={} + ) + cloud_api, res = await attempt_cloud_connection(self.hass, user_input) - if len(res) == 0: + if not res: new_data = self.config_entry.data.copy() new_data.update(user_input) cloud_devs = cloud_api.device_list @@ -394,6 +416,7 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): defaults = self.config_entry.data.copy() defaults.update(user_input or {}) + defaults[CONF_NO_CLOUD] = False return self.async_show_form( step_id="cloud_setup", @@ -490,12 +513,11 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): self.device_data.update( { CONF_DEVICE_ID: dev_id, - CONF_MODEL: self.device_data[CONF_MODEL], CONF_DPS_STRINGS: self.dps_strings, CONF_ENTITIES: [], } ) - if len(user_input[CONF_ENTITIES]) > 0: + if user_input[CONF_ENTITIES]: entity_ids = [ int(entity.split(":")[0]) for entity in user_input[CONF_ENTITIES]