diff --git a/custom_components/localtuya/__init__.py b/custom_components/localtuya/__init__.py index a32ab83..e547a37 100644 --- a/custom_components/localtuya/__init__.py +++ b/custom_components/localtuya/__init__.py @@ -27,7 +27,6 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.event import async_track_time_interval -from homeassistant.helpers.reload import async_integration_yaml_config from .cloud_api import TuyaCloudApi from .common import TuyaDevice, async_config_entry_by_device_id @@ -73,13 +72,9 @@ async def async_setup(hass: HomeAssistant, config: dict): async def _handle_reload(service): """Handle reload service call.""" - config = await async_integration_yaml_config(hass, DOMAIN) - - if not config or DOMAIN not in config: - return + _LOGGER.debug("Service %s.reload called: reloading integration", DOMAIN) current_entries = hass.config_entries.async_entries(DOMAIN) - # entries_by_id = {entry.data[CONF_DEVICE_ID]: entry for entry in current_entries} reload_tasks = [ hass.config_entries.async_reload(entry.entry_id) @@ -107,42 +102,51 @@ async def async_setup(hass: HomeAssistant, config: dict): product_key = device["productKey"] # If device is not in cache, check if a config entry exists - if device_id not in device_cache: - entry = async_config_entry_by_device_id(hass, device_id) - if entry: - # Save address from config entry in cache to trigger - # potential update below - device_cache[device_id] = entry.data[CONF_HOST] - - if device_id not in device_cache: - return - entry = async_config_entry_by_device_id(hass, device_id) if entry is None: return - updates = {} + if device_id not in device_cache: + if entry and device_id in entry.data[CONF_DEVICES]: + # Save address from config entry in cache to trigger + # potential update below + host_ip = entry.data[CONF_DEVICES][device_id][CONF_HOST] + device_cache[device_id] = host_ip + + if device_id not in device_cache: + return + + dev_entry = entry.data[CONF_DEVICES][device_id] + + new_data = entry.data.copy() + updated = False if device_cache[device_id] != device_ip: - updates[CONF_HOST] = device_ip + updated = True + new_data[CONF_DEVICES][device_id][CONF_HOST] = device_ip device_cache[device_id] = device_ip - if entry.data.get(CONF_PRODUCT_KEY) != product_key: - updates[CONF_PRODUCT_KEY] = product_key + if dev_entry.get(CONF_PRODUCT_KEY) != product_key: + updated = True + new_data[CONF_DEVICES][device_id][CONF_PRODUCT_KEY] = product_key # Update settings if something changed, otherwise try to connect. Updating # settings triggers a reload of the config entry, which tears down the device # so no need to connect in that case. - if updates: - _LOGGER.debug("Update keys for device %s: %s", device_id, updates) + if updated: + new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000)) hass.config_entries.async_update_entry( - entry, data={**entry.data, **updates} + entry, data=new_data ) - elif entry.entry_id in hass.data[DOMAIN]: - _LOGGER.debug("Device %s found with IP %s", device_id, device_ip) + device = hass.data[DOMAIN][TUYA_DEVICES][device_id] + if not device.connected: + device.async_connect() + elif device_id in hass.data[DOMAIN][TUYA_DEVICES]: + # _LOGGER.debug("Device %s found with IP %s", device_id, device_ip) device = hass.data[DOMAIN][TUYA_DEVICES][device_id] - device.async_connect() + if not device.connected: + device.async_connect() discovery = TuyaDiscovery(_device_discovered) @@ -159,18 +163,11 @@ async def async_setup(hass: HomeAssistant, config: dict): async def _async_reconnect(now): """Try connecting to devices not already connected to.""" - for device in hass.data[DOMAIN][TUYA_DEVICES]: + for device_id, device in hass.data[DOMAIN][TUYA_DEVICES].items(): if not device.connected: device.async_connect() - # for entry_id, value in hass.data[DOMAIN].items(): - # if entry_id == DATA_DISCOVERY: - # continue - # - # device = value[TUYA_DEVICE] - # if not device.connected: - # device.async_connect() - async_track_time_interval(hass, _async_reconnect, RECONNECT_INTERVAL) + # async_track_time_interval(hass, _async_reconnect, RECONNECT_INTERVAL) hass.helpers.service.async_register_admin_service( DOMAIN, @@ -205,6 +202,7 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry): new_data[CONF_DEVICES] = { config_entry.data[CONF_DEVICE_ID]: config_entry.data.copy() } + new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000)) config_entry.version = new_version hass.config_entries.async_update_entry( config_entry, title=DOMAIN, data=new_data @@ -217,6 +215,7 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry): new_data[CONF_DEVICES].update( {config_entry.data[CONF_DEVICE_ID]: config_entry.data.copy()} ) + new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000)) hass.config_entries.async_update_entry(stored_entries[0], data=new_data) await hass.config_entries.async_remove(config_entry.entry_id) @@ -241,11 +240,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): 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() - _LOGGER.debug("ACCESS TOKEN RES: %s . CLOUD DEVICES:", res) + 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() for dev_id, dev in tuya_api._device_list.items(): _LOGGER.debug( - "Name: %s \t dev_id %s \t key %s", dev["name"], dev["id"], dev["local_key"] + "Cloud device: %s \t dev_id %s \t key %s", dev["name"], dev["id"], dev["local_key"] ) hass.data[DOMAIN][DATA_CLOUD] = tuya_api @@ -258,7 +259,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): # async def setup_entities(dev_id): dev_entry = entry.data[CONF_DEVICES][dev_id] - device = TuyaDevice(hass, dev_entry) + device = TuyaDevice(hass, entry, dev_id) hass.data[DOMAIN][TUYA_DEVICES][dev_id] = device platforms = set(entity[CONF_PLATFORM] for entity in dev_entry[CONF_ENTITIES]) diff --git a/custom_components/localtuya/cloud_api.py b/custom_components/localtuya/cloud_api.py index b6343ea..ac82c4e 100755 --- a/custom_components/localtuya/cloud_api.py +++ b/custom_components/localtuya/cloud_api.py @@ -67,7 +67,7 @@ class TuyaCloudApi: "sign_method": "HMAC-SHA256", } full_url = self._base_url + url - print("\n" + method + ": [{}]".format(full_url)) + # print("\n" + method + ": [{}]".format(full_url)) if method == "GET": func = functools.partial( @@ -80,7 +80,7 @@ class TuyaCloudApi: headers=dict(default_par, **headers), data=json.dumps(body), ) - print("BODY: [{}]".format(body)) + # print("BODY: [{}]".format(body)) elif method == "PUT": func = functools.partial( requests.put, @@ -104,9 +104,7 @@ class TuyaCloudApi: if not r_json["success"]: return f"Error {r_json['code']}: {r_json['msg']}" - print(r.json()) self._access_token = r.json()["result"]["access_token"] - print("GET_ACCESS_TOKEN: {}".format(self._access_token)) return "ok" async def async_get_devices_list(self): @@ -120,11 +118,11 @@ class TuyaCloudApi: r_json = r.json() if not r_json["success"]: - print( - "Request failed, reply is {}".format( - json.dumps(r_json, indent=2, ensure_ascii=False) - ) - ) + # print( + # "Request failed, reply is {}".format( + # json.dumps(r_json, indent=2, ensure_ascii=False) + # ) + # ) return f"Error {r_json['code']}: {r_json['msg']}" self._device_list = {dev["id"]: dev for dev in r_json["result"]} diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 0ebb5e0..c7b8c0d 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -2,6 +2,7 @@ import asyncio import logging from datetime import timedelta +import time from homeassistant.const import ( CONF_DEVICE_ID, @@ -24,6 +25,7 @@ from homeassistant.helpers.restore_state import RestoreEntity from . import pytuya from .const import ( + ATTR_UPDATED_AT, CONF_LOCAL_KEY, CONF_PROTOCOL_VERSION, DOMAIN, @@ -112,7 +114,7 @@ def async_config_entry_by_device_id(hass, device_id): """Look up config entry by device id.""" current_entries = hass.config_entries.async_entries(DOMAIN) for entry in current_entries: - if entry.data[CONF_DEVICE_ID] == device_id: + if device_id in entry.data[CONF_DEVICES]: return entry return None @@ -120,11 +122,12 @@ def async_config_entry_by_device_id(hass, device_id): class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): """Cache wrapper for pytuya.TuyaInterface.""" - def __init__(self, hass, config_entry): + def __init__(self, hass, config_entry, dev_id): """Initialize the cache.""" super().__init__() self._hass = hass self._config_entry = config_entry + self._dev_config_entry = config_entry.data[CONF_DEVICES][dev_id] self._interface = None self._status = {} self.dps_to_request = {} @@ -132,11 +135,11 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self._connect_task = None self._disconnect_task = None self._unsub_interval = None - self._local_key = self._config_entry[CONF_LOCAL_KEY] - self.set_logger(_LOGGER, config_entry[CONF_DEVICE_ID]) + self._local_key = self._dev_config_entry[CONF_LOCAL_KEY] + self.set_logger(_LOGGER, self._dev_config_entry[CONF_DEVICE_ID]) # This has to be done in case the device type is type_0d - for entity in config_entry[CONF_ENTITIES]: + for entity in self._dev_config_entry[CONF_ENTITIES]: self.dps_to_request[entity[CONF_ID]] = None @property @@ -151,14 +154,14 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): async def _make_connection(self): """Subscribe localtuya entity events.""" - self.debug("Connecting to %s", self._config_entry[CONF_HOST]) + self.debug("Connecting to %s", self._dev_config_entry[CONF_HOST]) try: self._interface = await pytuya.connect( - self._config_entry[CONF_HOST], - self._config_entry[CONF_DEVICE_ID], + self._dev_config_entry[CONF_HOST], + self._dev_config_entry[CONF_DEVICE_ID], self._local_key, - float(self._config_entry[CONF_PROTOCOL_VERSION]), + float(self._dev_config_entry[CONF_PROTOCOL_VERSION]), self, ) self._interface.add_dps_to_request(self.dps_to_request) @@ -174,52 +177,63 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self.debug( "New entity %s was added to %s", entity_id, - self._config_entry[CONF_HOST], + self._dev_config_entry[CONF_HOST], ) self._dispatch_status() - signal = f"localtuya_entity_{self._config_entry[CONF_DEVICE_ID]}" + signal = f"localtuya_entity_{self._dev_config_entry[CONF_DEVICE_ID]}" self._disconnect_task = async_dispatcher_connect( self._hass, signal, _new_entity_handler ) if ( - CONF_SCAN_INTERVAL in self._config_entry - and self._config_entry[CONF_SCAN_INTERVAL] > 0 + CONF_SCAN_INTERVAL in self._dev_config_entry + and self._dev_config_entry[CONF_SCAN_INTERVAL] > 0 ): self._unsub_interval = async_track_time_interval( self._hass, self._async_refresh, - timedelta(seconds=self._config_entry[CONF_SCAN_INTERVAL]), + timedelta(seconds=self._dev_config_entry[CONF_SCAN_INTERVAL]), ) except UnicodeDecodeError as e: # pylint: disable=broad-except - dev_id = self._config_entry[CONF_DEVICE_ID] - cloud_devs = self._hass.data[DOMAIN][DATA_CLOUD]._device_list - if dev_id in cloud_devs: - old_key = self._local_key - self._local_key = cloud_devs[dev_id].get(CONF_LOCAL_KEY) - self.error( - "New local key for %s: from %s to %s", - dev_id, - old_key, - self._local_key, - ) self.exception( - f"Connect to {self._config_entry[CONF_HOST]} failed: %s", type(e) + f"Connect to {self._dev_config_entry[CONF_HOST]} failed: %s", type(e) ) if self._interface is not None: await self._interface.close() self._interface = None except Exception as e: # pylint: disable=broad-except - self.exception(f"Connect to {self._config_entry[CONF_HOST]} failed") - self.error("BBBB: %s", type(e)) + self.exception(f"Connect to {self._dev_config_entry[CONF_HOST]} failed") + self.error("BBBB: %s", type(type(e))) + if 'json.decode' in str(type(e)): + self.error("BBBB2") + await self.update_local_key() if self._interface is not None: await self._interface.close() self._interface = None self._connect_task = None + async def update_local_key(self): + dev_id = self._dev_config_entry[CONF_DEVICE_ID] + await self._hass.data[DOMAIN][DATA_CLOUD].async_get_devices_list() + cloud_devs = self._hass.data[DOMAIN][DATA_CLOUD]._device_list + if dev_id in cloud_devs: + old_key = self._local_key + self._local_key = cloud_devs[dev_id].get(CONF_LOCAL_KEY) + new_data = self._config_entry.data.copy() + new_data[CONF_DEVICES][dev_id][CONF_LOCAL_KEY] = self._local_key + new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000)) + self._hass.config_entries.async_update_entry( + self._config_entry, + data=new_data, + ) + self.debug( + "New local key for %s: from %s to %s", + dev_id, old_key, self._local_key + ) + async def _async_refresh(self, _now): if self._interface is not None: await self._interface.update_dps() @@ -235,7 +249,8 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): if self._disconnect_task is not None: self._disconnect_task() self.debug( - "Closed connection with device %s.", self._config_entry[CONF_FRIENDLY_NAME] + "Closed connection with device %s.", + self._dev_config_entry[CONF_FRIENDLY_NAME] ) async def set_dp(self, state, dp_index): @@ -247,7 +262,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self.exception("Failed to set DP %d to %d", dp_index, state) else: self.error( - "Not connected to device %s", self._config_entry[CONF_FRIENDLY_NAME] + "Not connected to device %s", self._dev_config_entry[CONF_FRIENDLY_NAME] ) async def set_dps(self, states): @@ -259,7 +274,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self.exception("Failed to set DPs %r", states) else: self.error( - "Not connected to device %s", self._config_entry[CONF_FRIENDLY_NAME] + "Not connected to device %s", self._dev_config_entry[CONF_FRIENDLY_NAME] ) @callback @@ -269,13 +284,13 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self._dispatch_status() def _dispatch_status(self): - signal = f"localtuya_{self._config_entry[CONF_DEVICE_ID]}" + signal = f"localtuya_{self._dev_config_entry[CONF_DEVICE_ID]}" async_dispatcher_send(self._hass, signal, self._status) @callback def disconnected(self): """Device disconnected.""" - signal = f"localtuya_{self._config_entry[CONF_DEVICE_ID]}" + signal = f"localtuya_{self._dev_config_entry[CONF_DEVICE_ID]}" async_dispatcher_send(self._hass, signal, None) if self._unsub_interval is not None: self._unsub_interval() @@ -291,11 +306,11 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger): """Initialize the Tuya entity.""" super().__init__() self._device = device - self._config_entry = config_entry + self._dev_config_entry = config_entry self._config = get_entity_config(config_entry, dp_id) self._dp_id = dp_id self._status = {} - self.set_logger(logger, self._config_entry[CONF_DEVICE_ID]) + self.set_logger(logger, self._dev_config_entry[CONF_DEVICE_ID]) async def async_added_to_hass(self): """Subscribe localtuya events.""" @@ -317,28 +332,28 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger): self.status_updated() self.schedule_update_ha_state() - signal = f"localtuya_{self._config_entry[CONF_DEVICE_ID]}" + signal = f"localtuya_{self._dev_config_entry[CONF_DEVICE_ID]}" self.async_on_remove( async_dispatcher_connect(self.hass, signal, _update_handler) ) - signal = f"localtuya_entity_{self._config_entry[CONF_DEVICE_ID]}" + signal = f"localtuya_entity_{self._dev_config_entry[CONF_DEVICE_ID]}" async_dispatcher_send(self.hass, signal, self.entity_id) @property def device_info(self): """Return device information for the device registry.""" - model = self._config_entry.get(CONF_MODEL, "Tuya generic") + model = self._dev_config_entry.get(CONF_MODEL, "Tuya generic") return { "identifiers": { # Serial numbers are unique identifiers within a specific domain - (DOMAIN, f"local_{self._config_entry[CONF_DEVICE_ID]}") + (DOMAIN, f"local_{self._dev_config_entry[CONF_DEVICE_ID]}") }, - "name": self._config_entry[CONF_FRIENDLY_NAME], + "name": self._dev_config_entry[CONF_FRIENDLY_NAME], "manufacturer": "Tuya", - "model": f"{model} ({self._config_entry[CONF_DEVICE_ID]})", - "sw_version": self._config_entry[CONF_PROTOCOL_VERSION], + "model": f"{model} ({self._dev_config_entry[CONF_DEVICE_ID]})", + "sw_version": self._dev_config_entry[CONF_PROTOCOL_VERSION], } @property @@ -354,7 +369,7 @@ class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger): @property def unique_id(self): """Return unique device identifier.""" - return f"local_{self._config_entry[CONF_DEVICE_ID]}_{self._dp_id}" + return f"local_{self._dev_config_entry[CONF_DEVICE_ID]}_{self._dp_id}" def has_config(self, attr): """Return if a config parameter has a valid value.""" diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index 7c69cbb..e901289 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -5,6 +5,7 @@ import time from importlib import import_module import homeassistant.helpers.config_validation as cv +import homeassistant.helpers.entity_registry as er import voluptuous as vol from homeassistant import config_entries, core, exceptions from homeassistant.const import ( @@ -253,7 +254,7 @@ async def validate_input(hass: core.HomeAssistant, data): async def attempt_cloud_connection(hass, user_input): """Create device.""" - tuya_api = TuyaCloudApi( + cloud_api = TuyaCloudApi( hass, user_input.get(CONF_REGION), user_input.get(CONF_CLIENT_ID), @@ -261,20 +262,18 @@ async def attempt_cloud_connection(hass, user_input): user_input.get(CONF_USER_ID), ) - res = await tuya_api.async_get_access_token() - _LOGGER.debug("ACCESS TOKEN RES: %s", res) + res = await cloud_api.async_get_access_token() if res != "ok": - return {"reason": "authentication_failed", "msg": res} + _LOGGER.error("Cloud API connection failed: %s", res) + return cloud_api, {"reason": "authentication_failed", "msg": res} - res = await tuya_api.async_get_devices_list() - _LOGGER.debug("DEV LIST RES: %s", res) + res = await cloud_api.async_get_devices_list() if res != "ok": - return {"reason": "device_list_failed", "msg": res} + _LOGGER.error("Cloud API get_devices_list failed: %s", res) + return cloud_api, {"reason": "device_list_failed", "msg": res} + _LOGGER.info("Cloud API connection succeeded.") - for dev_id, dev in tuya_api._device_list.items(): - print(f"Name: {dev['name']} \t dev_id {dev['id']} \t key {dev['local_key']} ") - - return {} + return cloud_api, {} class LocaltuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -297,8 +296,7 @@ class LocaltuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors = {} placeholders = {} if user_input is not None: - print("ECCOCI") - res = await attempt_cloud_connection(self.hass, user_input) + cloud_api, res = await attempt_cloud_connection(self.hass, user_input) if len(res) == 0: return await self._create_entry(user_input) @@ -322,9 +320,6 @@ class LocaltuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _create_entry(self, user_input): """Register new entry.""" - # if not self.unique_id: - # await self.async_set_unique_id(password) - # self._abort_if_unique_id_configured() if self._async_current_entries(): return self.async_abort(reason="already_configured") @@ -341,11 +336,6 @@ class LocaltuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): _LOGGER.error( "Configuration via YAML file is no longer supported by this integration." ) - # await self.async_set_unique_id(user_input[CONF_DEVICE_ID]) - # self._abort_if_unique_id_configured(updates=user_input) - # return self.async_create_entry( - # title=f"{user_input[CONF_FRIENDLY_NAME]} (YAML)", data=user_input - # ) class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): @@ -385,19 +375,24 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): errors = {} placeholders = {} if user_input is not None: - res = await attempt_cloud_connection(self.hass, user_input) + cloud_api, res = await attempt_cloud_connection(self.hass, user_input) if len(res) == 0: new_data = self.config_entry.data.copy() new_data.update(user_input) - print("CURR_ENTRY {}".format(self.config_entry)) - print("NEW DATA {}".format(new_data)) + cloud_devs = cloud_api._device_list + for dev_id, dev in new_data[CONF_DEVICES].items(): + if CONF_MODEL not in dev and dev_id in cloud_devs: + new_data[CONF_DEVICES][dev_id][CONF_MODEL] = cloud_devs[dev_id].get( + CONF_PRODUCT_NAME + ) + new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000)) self.hass.config_entries.async_update_entry( self.config_entry, data=new_data, ) - return self.async_create_entry(title="", data={}) + return self.async_create_entry(title=new_data.get(CONF_USERNAME), data={}) errors["base"] = res["reason"] placeholders = {"msg": res["msg"]} @@ -417,7 +412,6 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): self.editing_device = False errors = {} if user_input is not None: - print("Selected {}".format(user_input)) if user_input[SELECTED_DEVICE] != CUSTOM_DEVICE: self.selected_device = user_input[SELECTED_DEVICE] return await self.async_step_configure_device() @@ -444,7 +438,6 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): for dev_id, dev in self.discovered_devices.items() if dev["gwId"] not in self.config_entry.data[CONF_DEVICES] } - print("SCHEMA DEVS {}".format(devices)) return self.async_show_form( step_id="add_device", @@ -458,23 +451,18 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): """Handle editing a device.""" self.editing_device = True # Use cache if available or fallback to manual discovery - print("AAA") errors = {} if user_input is not None: - print("Selected {}".format(user_input)) self.selected_device = user_input[SELECTED_DEVICE] dev_conf = self.config_entry.data[CONF_DEVICES][self.selected_device] self.dps_strings = dev_conf.get(CONF_DPS_STRINGS, gen_dps_strings()) self.entities = dev_conf[CONF_ENTITIES] - print("Selected DPS {} ENT {}".format(self.dps_strings, self.entities)) return await self.async_step_configure_device() - print("BBB: {}".format(self.config_entry.data[CONF_DEVICES])) devices = {} for dev_id, configured_dev in self.config_entry.data[CONF_DEVICES].items(): devices[dev_id] = configured_dev[CONF_HOST] - print("SCHEMA DEVS {}".format(devices)) return self.async_show_form( step_id="edit_device", @@ -489,8 +477,6 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): errors = {} dev_id = self.selected_device if user_input is not None: - print("INPUT1!! {} {}".format(user_input, dev_id)) - try: self.device_data = user_input.copy() if dev_id is not None: @@ -525,7 +511,6 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): return await self.async_step_configure_entity() self.dps_strings = await validate_input(self.hass, user_input) - print("ZIO KEN!! {} ".format(self.dps_strings)) return await self.async_step_pick_entity_type() except CannotConnect: errors["base"] = "cannot_connect" @@ -540,15 +525,11 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): defaults = {} if self.editing_device: # If selected device exists as a config entry, load config from it - print("ALREADY EXISTING!! {}".format(dev_id)) defaults = self.config_entry.data[CONF_DEVICES][dev_id].copy() - print("ALREADY EXISTING!! {} {}".format(self.entities, defaults)) schema = schema_defaults(options_schema(self.entities), **defaults) placeholders = {"for_device": f" for device `{dev_id}`"} - print("SCHEMA!! {}".format(schema)) elif dev_id is not None: # Insert default values from discovery and cloud if present - print("NEW DEVICE!! {}".format(dev_id)) device = self.discovered_devices[dev_id] defaults[CONF_HOST] = device.get("ip") defaults[CONF_DEVICE_ID] = device.get("gwId") @@ -571,19 +552,16 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): async def async_step_pick_entity_type(self, user_input=None): """Handle asking if user wants to add another entity.""" if user_input is not None: - print("INPUT4!! {} {}".format(user_input, self.device_data)) if user_input.get(NO_ADDITIONAL_ENTITIES): config = { **self.device_data, CONF_DPS_STRINGS: self.dps_strings, CONF_ENTITIES: self.entities, } - print("NEW CONFIG!! {}".format(config)) # entry = async_config_entry_by_device_id(self.hass, self.unique_id) dev_id = self.device_data.get(CONF_DEVICE_ID) if dev_id in self.config_entry.data[CONF_DEVICES]: - print("AGGIORNO !! {}".format(dev_id)) self.hass.config_entries.async_update_entry( self.config_entry, data=config ) @@ -595,13 +573,9 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): }, ) - print("CREO NUOVO DEVICE!! {}".format(dev_id)) new_data = self.config_entry.data.copy() - print("PRE: {}".format(new_data)) new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000)) - # new_data[CONF_DEVICES]["AZZ"] = "OK" new_data[CONF_DEVICES].update({dev_id: config}) - print("POST: {}".format(new_data)) self.hass.config_entries.async_update_entry( self.config_entry, @@ -625,12 +599,9 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): def available_dps_strings(self): available_dps = [] - # print("FILTERING!! {} {}".format(self.dps_strings, self.entities)) used_dps = [str(entity[CONF_ID]) for entity in self.entities] - # print("FILTERING-- {} {}".format(self.dps_strings, used_dps)) for dp_string in self.dps_strings: dp = dp_string.split(" ")[0] - # print("FILTERING2!! {} {}".format(dp_string, dp)) if dp not in used_dps: available_dps.append(dp_string) return available_dps @@ -639,8 +610,6 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): """Manage entity settings.""" errors = {} if user_input is not None: - print("INPUT2!! {} {}".format(user_input, CONF_DEVICE_ID)) - print("ZIO KEN!! {} ".format(self.dps_strings)) entity = strip_dps_values(user_input, self.dps_strings) entity[CONF_ID] = self.current_entity[CONF_ID] entity[CONF_PLATFORM] = self.current_entity[CONF_PLATFORM] @@ -673,16 +642,38 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): """Manage entity settings.""" errors = {} if user_input is not None: - print("INPUT3!! {} {}".format(user_input, CONF_DEVICE_ID)) - already_configured = any( - entity[CONF_ID] == int(user_input[CONF_ID].split(" ")[0]) - for entity in self.entities - ) - if not already_configured: + if self.editing_device: + entity = strip_dps_values(user_input, self.dps_strings) + entity[CONF_ID] = self.current_entity[CONF_ID] + entity[CONF_PLATFORM] = self.current_entity[CONF_PLATFORM] + self.device_data[CONF_ENTITIES].append(entity) + + if len(self.entities) == len(self.device_data[CONF_ENTITIES]): + # finished editing device. Let's store the new config entry.... + dev_id = self.device_data[CONF_DEVICE_ID] + new_data = self.config_entry.data.copy() + entry_id = self.config_entry.entry_id + # removing entities from registry (they will be recreated) + ent_reg = await er.async_get_registry(self.hass) + reg_entities = { + ent.unique_id: ent.entity_id + for ent in er.async_entries_for_config_entry(ent_reg, entry_id) + if dev_id in ent.unique_id + } + for entity_id in reg_entities.values(): + ent_reg.async_remove(entity_id) + + new_data[CONF_DEVICES][dev_id] = self.device_data + new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000)) + self.hass.config_entries.async_update_entry( + self.config_entry, + data=new_data, + ) + return self.async_create_entry(title="", data={}) + else: user_input[CONF_PLATFORM] = self.selected_platform self.entities.append(strip_dps_values(user_input, self.dps_strings)) # new entity added. Let's check if there are more left... - print("ADDED. Remaining... {}".format(self.available_dps_strings())) user_input = None if len(self.available_dps_strings()) == 0: user_input = {NO_ADDITIONAL_ENTITIES: True} diff --git a/custom_components/localtuya/diagnostics.py b/custom_components/localtuya/diagnostics.py index d4b5cda..72c3979 100644 --- a/custom_components/localtuya/diagnostics.py +++ b/custom_components/localtuya/diagnostics.py @@ -3,6 +3,7 @@ from __future__ import annotations from typing import Any from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_DEVICES from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceEntry @@ -29,9 +30,11 @@ async def async_get_device_diagnostics( hass: HomeAssistant, entry: ConfigEntry, device: DeviceEntry ) -> dict[str, Any]: """Return diagnostics for a device entry.""" - # dev_id = next(iter(device.identifiers))[1] - data = {} - # data["device"] = device - # data["log"] = hass.data[DOMAIN][AIRBNK_DEVICES][dev_id].logger.retrieve_log() + dev_id = list(device.identifiers)[0][1].split("_")[-1] + data["device_config"] = entry.data[CONF_DEVICES][dev_id] + tuya_api = hass.data[DOMAIN][DATA_CLOUD] + if dev_id in tuya_api._device_list: + data["device_cloud_info"] = tuya_api._device_list[dev_id] + # data["log"] = hass.data[DOMAIN][CONF_DEVICES][dev_id].logger.retrieve_log() return data