From ef50285c33bcfbb1f8a8e0942c8f5caf751e85a7 Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Mon, 23 May 2022 12:31:15 +0200 Subject: [PATCH] Obfuscated diagnostics; fixed IP auto-update; cleaned logs --- custom_components/localtuya/__init__.py | 73 ++++++++-------------- custom_components/localtuya/common.py | 14 ++--- custom_components/localtuya/config_flow.py | 17 ++--- custom_components/localtuya/diagnostics.py | 46 +++++++++++--- 4 files changed, 75 insertions(+), 75 deletions(-) diff --git a/custom_components/localtuya/__init__.py b/custom_components/localtuya/__init__.py index e547a37..4a6f1bf 100644 --- a/custom_components/localtuya/__init__.py +++ b/custom_components/localtuya/__init__.py @@ -66,7 +66,6 @@ SERVICE_SET_DP_SCHEMA = vol.Schema( async def async_setup(hass: HomeAssistant, config: dict): """Set up the LocalTuya integration component.""" hass.data.setdefault(DOMAIN, {}) - print("SETUP") device_cache = {} @@ -134,6 +133,9 @@ async def async_setup(hass: HomeAssistant, config: dict): # settings triggers a reload of the config entry, which tears down the device # so no need to connect in that case. if updated: + _LOGGER.debug("Updating keys for device %s: %s %s", + device_id, device_ip, product_key + ) new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000)) hass.config_entries.async_update_entry( entry, data=new_data @@ -148,26 +150,17 @@ async def async_setup(hass: HomeAssistant, config: dict): if not device.connected: device.async_connect() - discovery = TuyaDiscovery(_device_discovered) - def _shutdown(event): """Clean up resources when shutting down.""" discovery.close() - try: - await discovery.start() - hass.data[DOMAIN][DATA_DISCOVERY] = discovery - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown) - except Exception: # pylint: disable=broad-except - _LOGGER.exception("failed to set up discovery") - async def _async_reconnect(now): """Try connecting to devices not already connected to.""" for device_id, device in hass.data[DOMAIN][TUYA_DEVICES].items(): 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, @@ -179,6 +172,14 @@ async def async_setup(hass: HomeAssistant, config: dict): DOMAIN, SERVICE_SET_DP, _handle_set_dp, schema=SERVICE_SET_DP_SCHEMA ) + discovery = TuyaDiscovery(_device_discovered) + try: + await discovery.start() + hass.data[DOMAIN][DATA_DISCOVERY] = discovery + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown) + except Exception: # pylint: disable=broad-except + _LOGGER.exception("failed to set up discovery") + return True @@ -228,7 +229,6 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): - print("SETUP ENTRY STARTED!") """Set up LocalTuya integration from a config entry.""" unsub_listener = entry.add_update_listener(update_listener) hass.data[DOMAIN][UNSUB_LISTENER] = unsub_listener @@ -244,19 +244,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): _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( - "Cloud device: %s \t dev_id %s \t key %s", dev["name"], dev["id"], dev["local_key"] - ) hass.data[DOMAIN][DATA_CLOUD] = tuya_api - # device = TuyaDevice(hass, entry.data) - # - # hass.data[DOMAIN][entry.entry_id] = { - # UNSUB_LISTENER: unsub_listener, - # TUYA_DEVICE: device, - # } - # async def setup_entities(dev_id): dev_entry = entry.data[CONF_DEVICES][dev_id] device = TuyaDevice(hass, entry, dev_id) @@ -272,19 +261,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): device.async_connect() await async_remove_orphan_entities(hass, entry) - print("SETUP_ENTITIES for {} ENDED".format(dev_id)) for dev_id in entry.data[CONF_DEVICES]: hass.async_create_task(setup_entities(dev_id)) - print("SETUP ENTRY ENDED!") - return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Unload a config entry.""" - # print("ASYNC_UNLOAD_ENTRY INVOKED...") platforms = {} for dev_id, dev_entry in entry.data[CONF_DEVICES].items(): for entity in dev_entry[CONF_ENTITIES]: @@ -301,20 +286,18 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): hass.data[DOMAIN][UNSUB_LISTENER]() for dev_id, device in hass.data[DOMAIN][TUYA_DEVICES].items(): - await device.close() + if device.connected: + await device.close() if unload_ok: hass.data[DOMAIN][TUYA_DEVICES] = {} - # print("ASYNC_UNLOAD_ENTRY ENDED") return True async def update_listener(hass, config_entry): """Update listener.""" - print("UPDATE_LISTENER INVOKED") await hass.config_entries.async_reload(config_entry.entry_id) - print("UPDATE_LISTENER RELOADED {}".format(config_entry.entry_id)) async def async_remove_config_entry_device( @@ -323,9 +306,18 @@ async def async_remove_config_entry_device( """Remove a config entry from a device.""" dev_id = list(device_entry.identifiers)[0][1].split("_")[-1] + ent_reg = await er.async_get_registry(hass) + entities = { + ent.unique_id: ent.entity_id + for ent in er.async_entries_for_config_entry(ent_reg, config_entry.entry_id) + if dev_id in ent.unique_id + } + for entity_id in entities.values(): + ent_reg.async_remove(entity_id) + if dev_id not in config_entry.data[CONF_DEVICES]: - _LOGGER.debug( - "Device ID %s not found in config entry: finalizing device removal", dev_id + _LOGGER.info( + "Device %s not found in config entry: finalizing device removal", dev_id ) return True @@ -340,15 +332,7 @@ async def async_remove_config_entry_device( data=new_data, ) - ent_reg = await er.async_get_registry(hass) - entities = { - ent.unique_id: ent.entity_id - for ent in er.async_entries_for_config_entry(ent_reg, config_entry.entry_id) - if dev_id in ent.unique_id - } - for entity_id in entities.values(): - ent_reg.async_remove(entity_id) - print("REMOVED {}".format(entity_id)) + _LOGGER.info("Device %s removed.", dev_id) return True @@ -361,11 +345,8 @@ async def async_remove_orphan_entities(hass, entry): ent.unique_id: ent.entity_id for ent in er.async_entries_for_config_entry(ent_reg, entry.entry_id) } - print("ENTITIES ORPHAN {}".format(entities)) + _LOGGER.info("ENTITIES ORPHAN %s", entities) return - res = ent_reg.async_remove("switch.aa") - print("RESULT ORPHAN {}".format(res)) - # del entities[101] for entity in entry.data[CONF_ENTITIES]: if entity[CONF_ID] in entities: diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index c7b8c0d..7921383 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -74,6 +74,10 @@ async def async_setup_entry( if len(entities_to_setup) > 0: + if dev_id not in hass.data[DOMAIN][TUYA_DEVICES]: + print("STRANO: {}".format(hass.data[DOMAIN][TUYA_DEVICES])) + return + tuyainterface = hass.data[DOMAIN][TUYA_DEVICES][dev_id] dps_config_fields = list(get_dps_for_platform(flow_schema)) @@ -127,7 +131,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): super().__init__() self._hass = hass self._config_entry = config_entry - self._dev_config_entry = config_entry.data[CONF_DEVICES][dev_id] + self._dev_config_entry = config_entry.data[CONF_DEVICES][dev_id].copy() self._interface = None self._status = {} self.dps_to_request = {} @@ -205,9 +209,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): except Exception as e: # pylint: disable=broad-except 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: @@ -220,7 +222,6 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): 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 @@ -229,10 +230,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self._config_entry, data=new_data, ) - self.debug( - "New local key for %s: from %s to %s", - dev_id, old_key, self._local_key - ) + self.info("local_key updated for device %s.", dev_id) async def _async_refresh(self, _now): if self._interface is not None: diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index e901289..28c0b65 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -27,7 +27,7 @@ from homeassistant.const import ( from homeassistant.core import callback from .cloud_api import TuyaCloudApi -from .common import async_config_entry_by_device_id, pytuya +from .common import pytuya from .const import ( CONF_ACTION, CONF_ADD_DEVICE, @@ -304,11 +304,6 @@ class LocaltuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): placeholders = {"msg": res["msg"]} defaults = {} - defaults[CONF_REGION] = "eu" - defaults[CONF_CLIENT_ID] = "xxx" - defaults[CONF_CLIENT_SECRET] = "xxx" - defaults[CONF_USER_ID] = "xxx" - defaults[CONF_USERNAME] = "xxx" defaults.update(user_input or {}) return self.async_show_form( @@ -383,16 +378,17 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): 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 - ) + model = cloud_devs[dev_id].get(CONF_PRODUCT_NAME) + new_data[CONF_DEVICES][dev_id][CONF_MODEL] = model 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=new_data.get(CONF_USERNAME), data={}) + return self.async_create_entry( + title=new_data.get(CONF_USERNAME), data={} + ) errors["base"] = res["reason"] placeholders = {"msg": res["msg"]} @@ -559,7 +555,6 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): CONF_ENTITIES: self.entities, } - # 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]: self.hass.config_entries.async_update_entry( diff --git a/custom_components/localtuya/diagnostics.py b/custom_components/localtuya/diagnostics.py index 72c3979..3d3cb95 100644 --- a/custom_components/localtuya/diagnostics.py +++ b/custom_components/localtuya/diagnostics.py @@ -3,11 +3,26 @@ 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 -from .const import DOMAIN, DATA_CLOUD + +from homeassistant.const import ( + CONF_CLIENT_ID, + CONF_CLIENT_SECRET, + CONF_DEVICES, +) + +from .const import ( + DOMAIN, + DATA_CLOUD, + CONF_USER_ID, + CONF_LOCAL_KEY, +) + +CLOUD_DEVICES = "cloud_devices" +DEVICE_CONFIG = "device_config" +DEVICE_CLOUD_INFO = "device_cloud_info" async def async_get_config_entry_diagnostics( @@ -15,14 +30,20 @@ async def async_get_config_entry_diagnostics( ) -> dict[str, Any]: """Return diagnostics for a config entry.""" data = {} - data = {**entry.data} - # print("DATA is {}".format(data)) + data = entry.data.copy() tuya_api = hass.data[DOMAIN][DATA_CLOUD] - data["cloud_devices"] = tuya_api._device_list - # censoring private information - # data["token"] = re.sub(r"[^\-]", "*", data["token"]) - # data["userId"] = re.sub(r"[^\-]", "*", data["userId"]) + for field in [CONF_CLIENT_ID, CONF_CLIENT_SECRET, CONF_USER_ID]: + data[field] = f"{data[field][0:3]}...{data[field][-3:]}" + for dev_id, dev in data[CONF_DEVICES].items(): + local_key = data[CONF_DEVICES][dev_id][CONF_LOCAL_KEY] + local_key_obfuscated = f"{local_key[0:3]}...{local_key[-3:]}" + data[CONF_DEVICES][dev_id][CONF_LOCAL_KEY] = local_key_obfuscated + data[CLOUD_DEVICES] = tuya_api._device_list + for dev_id, dev in data[CLOUD_DEVICES].items(): + local_key = data[CLOUD_DEVICES][dev_id][CONF_LOCAL_KEY] + local_key_obfuscated = f"{local_key[0:3]}...{local_key[-3:]}" + data[CLOUD_DEVICES][dev_id][CONF_LOCAL_KEY] = local_key_obfuscated return data @@ -32,9 +53,14 @@ async def async_get_device_diagnostics( """Return diagnostics for a device entry.""" data = {} dev_id = list(device.identifiers)[0][1].split("_")[-1] - data["device_config"] = entry.data[CONF_DEVICES][dev_id] + data[DEVICE_CONFIG] = entry.data[CONF_DEVICES][dev_id] + local_key = data[DEVICE_CONFIG][CONF_LOCAL_KEY] + data[DEVICE_CONFIG][CONF_LOCAL_KEY] = f"{local_key[0:3]}...{local_key[-3:]}" + 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[DEVICE_CLOUD_INFO] = tuya_api._device_list[dev_id] + local_key = data[DEVICE_CLOUD_INFO][CONF_LOCAL_KEY] + data[DEVICE_CLOUD_INFO][CONF_LOCAL_KEY] = f"{local_key[0:3]}...{local_key[-3:]}" # data["log"] = hass.data[DOMAIN][CONF_DEVICES][dev_id].logger.retrieve_log() return data