From 8c597bb18ff6a3bc231273f6cb5dd1f19fd504d4 Mon Sep 17 00:00:00 2001 From: sibowler Date: Mon, 17 Oct 2022 07:18:06 +1100 Subject: [PATCH 01/33] Fix for AttributeError: 'TuyaMessage' object has no attribute 'release' error --- custom_components/localtuya/pytuya/__init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index d36cd5e..9c3bd45 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -303,8 +303,11 @@ class MessageDispatcher(ContextualLogger): if msg.seqno in self.listeners: self.debug("Dispatching sequence number %d", msg.seqno) sem = self.listeners[msg.seqno] - self.listeners[msg.seqno] = msg - sem.release() + if isinstance(sem, asyncio.Semaphore): + self.listeners[msg.seqno] = msg + sem.release() + else: + self.debug("Got additional message without request - skipping: %s", sem) elif msg.cmd == 0x09: self.debug("Got heartbeat response") if self.HEARTBEAT_SEQNO in self.listeners: From 4d3a6cce5cd55beb1a268f837dd99137e6e2b235 Mon Sep 17 00:00:00 2001 From: Daniel O'Connor Date: Mon, 23 Jan 2023 21:28:15 +1030 Subject: [PATCH 02/33] Force to int --- custom_components/localtuya/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 85ed21c..e373d50 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -270,7 +270,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): if ( CONF_SCAN_INTERVAL in self._dev_config_entry - and self._dev_config_entry[CONF_SCAN_INTERVAL] > 0 + and int(self._dev_config_entry[CONF_SCAN_INTERVAL]) > 0 ): self._unsub_interval = async_track_time_interval( self._hass, From dccb32820bbcf40b4ec2244f164168db74f755aa Mon Sep 17 00:00:00 2001 From: Daniel O'Connor Date: Mon, 23 Jan 2023 21:33:52 +1030 Subject: [PATCH 03/33] Adjust input to avoid putting a string into an int --- custom_components/localtuya/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index 0099901..6bda251 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -140,7 +140,7 @@ def options_schema(entities): ["3.1", "3.2", "3.3", "3.4"] ), vol.Required(CONF_ENABLE_DEBUG, default=False): bool, - vol.Optional(CONF_SCAN_INTERVAL): cv.string, + vol.Optional(CONF_SCAN_INTERVAL): cv.int, vol.Optional(CONF_MANUAL_DPS): cv.string, vol.Optional(CONF_RESET_DPIDS): cv.string, vol.Required( From 8ff194ede4e5ea8d87227ed4a56a0e1236b35722 Mon Sep 17 00:00:00 2001 From: Daniel O'Connor Date: Mon, 23 Jan 2023 21:45:42 +1030 Subject: [PATCH 04/33] Adjust input to avoid putting a string into an int --- custom_components/localtuya/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index e373d50..fd7fa46 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -275,7 +275,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self._unsub_interval = async_track_time_interval( self._hass, self._async_refresh, - timedelta(seconds=self._dev_config_entry[CONF_SCAN_INTERVAL]), + timedelta(seconds=int(self._dev_config_entry[CONF_SCAN_INTERVAL])), ) self._connect_task = None From 77ac5a86b6732d893ade599005c387fe32d43c83 Mon Sep 17 00:00:00 2001 From: Daniel O'Connor Date: Mon, 23 Jan 2023 21:50:31 +1030 Subject: [PATCH 05/33] Swap to int --- custom_components/localtuya/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index 6bda251..d7175e9 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -140,7 +140,7 @@ def options_schema(entities): ["3.1", "3.2", "3.3", "3.4"] ), vol.Required(CONF_ENABLE_DEBUG, default=False): bool, - vol.Optional(CONF_SCAN_INTERVAL): cv.int, + vol.Optional(CONF_SCAN_INTERVAL): int, vol.Optional(CONF_MANUAL_DPS): cv.string, vol.Optional(CONF_RESET_DPIDS): cv.string, vol.Required( From 613bde4d6ab0bd127029edaae804ae1119b3cf3f Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Tue, 24 Jan 2023 17:02:10 +0100 Subject: [PATCH 06/33] Improved logging --- custom_components/localtuya/pytuya/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index ad546c4..d05a27f 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -444,11 +444,12 @@ class MessageDispatcher(ContextualLogger): if seqno in self.listeners: raise Exception(f"listener exists for {seqno}") - self.debug("Command %d waiting for sequence number %d", cmd, seqno) + self.debug("Command %d waiting for seq. number %d", cmd, seqno) self.listeners[seqno] = asyncio.Semaphore(0) try: await asyncio.wait_for(self.listeners[seqno].acquire(), timeout=timeout) except asyncio.TimeoutError: + self.warning("Command %d timed out waiting for sequence number %d", cmd, seqno) del self.listeners[seqno] raise @@ -511,7 +512,7 @@ class MessageDispatcher(ContextualLogger): if msg.cmd == CONTROL_NEW: self.debug("Got ACK message for command %d: will ignore it", msg.cmd) else: - self.error( + self.debug( "Got message type %d for unknown listener %d: %s", msg.cmd, msg.seqno, From 7a97fdfe9752a759a7add5a02454385ea7a9cf60 Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Tue, 24 Jan 2023 17:03:19 +0100 Subject: [PATCH 07/33] Improved stability and Fix local_key update to not be thwarted by retries --- custom_components/localtuya/common.py | 66 ++++++++++++++------------- 1 file changed, 34 insertions(+), 32 deletions(-) diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 85ed21c..547bad3 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -1,5 +1,6 @@ """Code shared between all platforms.""" import asyncio +import json.decoder import logging import time from datetime import timedelta @@ -176,12 +177,13 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): def async_connect(self): """Connect to device if not already connected.""" + # self.debug("async_connect: %d %r %r", self._is_closing, self._connect_task, self._interface) if not self._is_closing and self._connect_task is None and not self._interface: self._connect_task = asyncio.create_task(self._make_connection()) async def _make_connection(self): """Subscribe localtuya entity events.""" - self.debug("Connecting to %s", self._dev_config_entry[CONF_HOST]) + self.info("Trying to connect to %s...", self._dev_config_entry[CONF_HOST]) try: self._interface = await pytuya.connect( @@ -194,23 +196,23 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): ) self._interface.add_dps_to_request(self.dps_to_request) except Exception: # pylint: disable=broad-except - self.exception(f"Connect to {self._dev_config_entry[CONF_HOST]} failed") + self.warning(f"Connect to {self._dev_config_entry[CONF_HOST]} failed attempting to connect") if self._interface is not None: await self._interface.close() self._interface = None if self._interface is not None: try: - self.debug("Retrieving initial state") - status = await self._interface.status() - if status is None: - raise Exception("Failed to retrieve status") - - self._interface.start_heartbeat() - self.status_updated(status) - - except Exception as ex: # pylint: disable=broad-except try: + self.debug("Retrieving initial state") + status = await self._interface.status() + if status is None: + raise Exception("Failed to retrieve status") + + self._interface.start_heartbeat() + self.status_updated(status) + + except Exception as ex: # pylint: disable=broad-except if (self._default_reset_dpids is not None) and ( len(self._default_reset_dpids) > 0 ): @@ -228,26 +230,20 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self._interface.start_heartbeat() self.status_updated(status) + else: + self.error( + f"Initial state update failed, giving up: %r", ex + ) + # return + except (UnicodeDecodeError, json.decoder.JSONDecodeError) as ex: + self.exception( + f"Initial state update failed, trying key update" + ) + await self.update_local_key() - except UnicodeDecodeError as e: # pylint: disable=broad-except - self.exception( - 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._dev_config_entry[CONF_HOST]} failed" - ) - if "json.decode" in str(type(e)): - await self.update_local_key() - - if self._interface is not None: - await self._interface.close() - self._interface = None + if self._interface is not None: + await self._interface.close() + self._interface = None if self._interface is not None: # Attempt to restore status for all entities that need to first set @@ -278,6 +274,8 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): timedelta(seconds=self._dev_config_entry[CONF_SCAN_INTERVAL]), ) + self.info(f"Successfully connected to {self._dev_config_entry[CONF_HOST]}") + self._connect_task = None async def update_local_key(self): @@ -310,7 +308,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): await self._interface.close() if self._disconnect_task is not None: self._disconnect_task() - self.debug( + self.info( "Closed connection with device %s.", self._dev_config_entry[CONF_FRIENDLY_NAME], ) @@ -358,7 +356,11 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self._unsub_interval() self._unsub_interval = None self._interface = None - self.debug("Disconnected - waiting for discovery broadcast") + + if self._connect_task is not None: + self._connect_task.cancel() + self._connect_task = None + self.warning("Disconnected - waiting for discovery broadcast") class LocalTuyaEntity(RestoreEntity, pytuya.ContextualLogger): From cf3221d4c8bd0df2e37dba07952d7353f7c0438d Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Mon, 6 Feb 2023 10:17:57 +0100 Subject: [PATCH 08/33] Introduced update of local_key when editing a device --- custom_components/localtuya/config_flow.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index 0099901..09374e5 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -594,8 +594,18 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): if self.editing_device: # If selected device exists as a config entry, load config from it defaults = self.config_entry.data[CONF_DEVICES][dev_id].copy() - schema = schema_defaults(options_schema(self.entities), **defaults) + cloud_devs = self.hass.data[DOMAIN][DATA_CLOUD].device_list placeholders = {"for_device": f" for device `{dev_id}`"} + if dev_id in cloud_devs: + cloud_local_key = cloud_devs[dev_id].get(CONF_LOCAL_KEY) + if defaults[CONF_LOCAL_KEY] != cloud_local_key: + _LOGGER.info("New local_key detected: new %s vs old %s", + cloud_local_key, + defaults[CONF_LOCAL_KEY] + ) + defaults[CONF_LOCAL_KEY] = cloud_devs[dev_id].get(CONF_LOCAL_KEY) + placeholders = {"for_device": f" for device `{dev_id}`.\nNOTE: a new local_key has been retrieved using cloud API"} + schema = schema_defaults(options_schema(self.entities), **defaults) else: defaults[CONF_PROTOCOL_VERSION] = "3.3" defaults[CONF_HOST] = "" From c909349cdeed40c9a777569ccbdfcf7c74e66511 Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Mon, 6 Feb 2023 10:28:31 +0100 Subject: [PATCH 09/33] Improved logging --- custom_components/localtuya/common.py | 6 +++--- custom_components/localtuya/config_flow.py | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 1018934..8511e1b 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -177,7 +177,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): def async_connect(self): """Connect to device if not already connected.""" - # self.debug("async_connect: %d %r %r", self._is_closing, self._connect_task, self._interface) + # self.info("async_connect: %d %r %r", self._is_closing, self._connect_task, self._interface) if not self._is_closing and self._connect_task is None and not self._interface: self._connect_task = asyncio.create_task(self._make_connection()) @@ -195,9 +195,9 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self, ) self._interface.add_dps_to_request(self.dps_to_request) - except Exception: # pylint: disable=broad-except + except Exception as ex: # pylint: disable=broad-except self.warning( - f"Connect to {self._dev_config_entry[CONF_HOST]} failed attempting to connect" + f"Failed to connect to {self._dev_config_entry[CONF_HOST]}: %s", ex ) if self._interface is not None: await self._interface.close() diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index 0e72e09..eef00a9 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -256,14 +256,14 @@ async def validate_input(hass: core.HomeAssistant, data): ) try: detected_dps = await interface.detect_available_dps() - except Exception: # pylint: disable=broad-except + except Exception as ex: try: - _LOGGER.debug("Initial state update failed, trying reset command") + _LOGGER.debug("Initial state update failed (%s), trying reset command", ex) if len(reset_ids) > 0: await interface.reset(reset_ids) detected_dps = await interface.detect_available_dps() - except Exception: # pylint: disable=broad-except - _LOGGER.debug("No DPS able to be detected") + except Exception as ex: + _LOGGER.debug("No DPS able to be detected: %s", ex) detected_dps = {} # if manual DPs are set, merge these. @@ -493,7 +493,7 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): errors["base"] = "address_in_use" else: errors["base"] = "discovery_failed" - except Exception: # pylint: disable= broad-except + except Exception as ex: _LOGGER.exception("discovery failed") errors["base"] = "discovery_failed" @@ -586,8 +586,8 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): errors["base"] = "invalid_auth" except EmptyDpsList: errors["base"] = "empty_dps" - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") + except Exception as ex: + _LOGGER.exception("Unexpected exception: %s", ex) errors["base"] = "unknown" defaults = {} From 6d0e407e7e48252ea5e133072f723d1314a6e8a9 Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Tue, 7 Feb 2023 09:57:27 +0100 Subject: [PATCH 10/33] Tox fixes --- .github/workflows/tox.yaml | 4 ++-- custom_components/localtuya/common.py | 7 ++++--- custom_components/localtuya/config_flow.py | 11 ++++++----- custom_components/localtuya/pytuya/__init__.py | 9 ++++++--- tox.ini | 4 ++-- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 40ca8d9..6725fb4 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -14,8 +14,8 @@ jobs: platform: - ubuntu-latest python-version: - - 3.7 - - 3.8 + - 3.9 + - 3.10 steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 9e04579..36d9104 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -81,6 +81,7 @@ async def async_setup_entry( ] if entities_to_setup: + tuyainterface = hass.data[DOMAIN][TUYA_DEVICES][dev_id] dps_config_fields = list(get_dps_for_platform(flow_schema)) @@ -213,7 +214,7 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self._interface.start_heartbeat() self.status_updated(status) - except Exception as ex: # pylint: disable=broad-except + except Exception as ex: if (self._default_reset_dpids is not None) and ( len(self._default_reset_dpids) > 0 ): @@ -232,10 +233,10 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self._interface.start_heartbeat() self.status_updated(status) else: - self.error(f"Initial state update failed, giving up: %r", ex) + self.error("Initial state update failed, giving up: %r", ex) # return except (UnicodeDecodeError, json.decoder.JSONDecodeError) as ex: - self.exception(f"Initial state update failed, trying key update") + self.warning("Initial state update failed (%s), trying key update", ex) await self.update_local_key() if self._interface is not None: diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index ebde246..d4272d4 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -258,7 +258,9 @@ async def validate_input(hass: core.HomeAssistant, data): detected_dps = await interface.detect_available_dps() except Exception as ex: try: - _LOGGER.debug("Initial state update failed (%s), trying reset command", ex) + _LOGGER.debug( + "Initial state update failed (%s), trying reset command", ex + ) if len(reset_ids) > 0: await interface.reset(reset_ids) detected_dps = await interface.detect_available_dps() @@ -493,7 +495,7 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): else: errors["base"] = "discovery_failed" except Exception as ex: - _LOGGER.exception("discovery failed") + _LOGGER.exception("discovery failed: %s", ex) errors["base"] = "discovery_failed" devices = { @@ -604,9 +606,8 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): defaults[CONF_LOCAL_KEY], ) defaults[CONF_LOCAL_KEY] = cloud_devs[dev_id].get(CONF_LOCAL_KEY) - placeholders = { - "for_device": f" for device `{dev_id}`.\nNOTE: a new local_key has been retrieved using cloud API" - } + note = "\nNOTE: a new local_key has been retrieved using cloud API" + placeholders = {"for_device": f" for device `{dev_id}`.{note}"} schema = schema_defaults(options_schema(self.entities), **defaults) else: defaults[CONF_PROTOCOL_VERSION] = "3.3" diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index cfa0618..472274a 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -923,9 +923,11 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): if not isinstance(payload, str): try: payload = payload.decode() - except Exception: + except Exception as ex: self.debug("payload was not string type and decoding failed") - return self.error_json(ERR_JSON, payload) + raise DecodeError("payload was not a string: %s", ex) + # return self.error_json(ERR_JSON, payload) + if "data unvalid" in payload: self.dev_type = "type_0d" self.debug( @@ -943,7 +945,8 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): try: json_payload = json.loads(payload) except Exception: - json_payload = self.error_json(ERR_JSON, payload) + raise DecodeError("could not decrypt data: wrong local_key?") + # json_payload = self.error_json(ERR_JSON, payload) # v3.4 stuffs it into {"data":{"dps":{"1":true}}, ...} if ( diff --git a/tox.ini b/tox.ini index 2602e5e..c753d25 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,13 @@ [tox] skipsdist = true -envlist = py{38,39}, lint, typing +envlist = py{39,310}, lint, typing skip_missing_interpreters = True cs_exclude_words = hass,unvalid [gh-actions] python = - 3.8: clean, py38, lint, typing 3.9: clean, py39, lint, typing + 3.10: clean, py310, lint, typing [testenv] passenv = TOXENV,CI From febbf421d44ea82261e149fee0726447c0b5d039 Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Tue, 7 Feb 2023 10:00:18 +0100 Subject: [PATCH 11/33] Tox fixes --- .github/workflows/tox.yaml | 1 - tox.ini | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 6725fb4..05465c0 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -15,7 +15,6 @@ jobs: - ubuntu-latest python-version: - 3.9 - - 3.10 steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/tox.ini b/tox.ini index c753d25..69dd508 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,12 @@ [tox] skipsdist = true -envlist = py{39,310}, lint, typing +envlist = py{39}, lint, typing skip_missing_interpreters = True cs_exclude_words = hass,unvalid [gh-actions] python = 3.9: clean, py39, lint, typing - 3.10: clean, py310, lint, typing [testenv] passenv = TOXENV,CI From f51d833ae1e435d435659eb1dcfc89c30e2acbf2 Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Tue, 7 Feb 2023 10:02:29 +0100 Subject: [PATCH 12/33] Tox fixes --- .github/workflows/tox.yaml | 1 + tox.ini | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 05465c0..3d1c78a 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -14,6 +14,7 @@ jobs: platform: - ubuntu-latest python-version: + - 3.8 - 3.9 steps: - uses: actions/checkout@v2 diff --git a/tox.ini b/tox.ini index 69dd508..2602e5e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,12 @@ [tox] skipsdist = true -envlist = py{39}, lint, typing +envlist = py{38,39}, lint, typing skip_missing_interpreters = True cs_exclude_words = hass,unvalid [gh-actions] python = + 3.8: clean, py38, lint, typing 3.9: clean, py39, lint, typing [testenv] From fa301d1ad61f1a6d860fa6c2895e96027fcf95b4 Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Tue, 7 Feb 2023 17:19:27 +0100 Subject: [PATCH 13/33] Fix for not reconnecting after 'giving up' --- custom_components/localtuya/common.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/custom_components/localtuya/common.py b/custom_components/localtuya/common.py index 36d9104..cd503c2 100644 --- a/custom_components/localtuya/common.py +++ b/custom_components/localtuya/common.py @@ -234,7 +234,10 @@ class TuyaDevice(pytuya.TuyaListener, pytuya.ContextualLogger): self.status_updated(status) else: self.error("Initial state update failed, giving up: %r", ex) - # return + if self._interface is not None: + await self._interface.close() + self._interface = None + except (UnicodeDecodeError, json.decoder.JSONDecodeError) as ex: self.warning("Initial state update failed (%s), trying key update", ex) await self.update_local_key() From d4af0f63f6956f541fb66695b2edc5dbce46161c Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Tue, 7 Feb 2023 17:44:51 +0100 Subject: [PATCH 14/33] Tox fixes --- .github/workflows/tox.yaml | 1 - tox.ini | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 3d1c78a..05465c0 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -14,7 +14,6 @@ jobs: platform: - ubuntu-latest python-version: - - 3.8 - 3.9 steps: - uses: actions/checkout@v2 diff --git a/tox.ini b/tox.ini index 2602e5e..031fef7 100644 --- a/tox.ini +++ b/tox.ini @@ -6,7 +6,6 @@ cs_exclude_words = hass,unvalid [gh-actions] python = - 3.8: clean, py38, lint, typing 3.9: clean, py39, lint, typing [testenv] From 6b9f3df555fc8dc064e74c48dc3546c348a0d730 Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Tue, 7 Feb 2023 17:54:02 +0100 Subject: [PATCH 15/33] Tox fixes, reduced to 3.9 only --- custom_components/localtuya/pytuya/__init__.py | 2 +- pylint.rc | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index 472274a..746cc54 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -925,7 +925,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): payload = payload.decode() except Exception as ex: self.debug("payload was not string type and decoding failed") - raise DecodeError("payload was not a string: %s", ex) + raise DecodeError("payload was not a string: %s" % ex) # return self.error_json(ERR_JSON, payload) if "data unvalid" in payload: diff --git a/pylint.rc b/pylint.rc index 223e881..ec80820 100644 --- a/pylint.rc +++ b/pylint.rc @@ -176,7 +176,8 @@ disable=line-too-long, dangerous-default-value, unreachable, unnecessary-pass, - broad-except + broad-except, + raise-missing-from # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option From 904f4feb6c0248b317750bc587cdc082362185f9 Mon Sep 17 00:00:00 2001 From: GaoJing <270615179@qq.com> Date: Thu, 16 Mar 2023 15:50:31 +0800 Subject: [PATCH 16/33] Add entities during 'Edit a device' --- custom_components/localtuya/config_flow.py | 75 ++++++++++--------- custom_components/localtuya/const.py | 1 + .../localtuya/translations/en.json | 1 + 3 files changed, 41 insertions(+), 36 deletions(-) diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index d4272d4..4bdf5c6 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -43,6 +43,7 @@ from .const import ( CONF_RESET_DPIDS, CONF_SETUP_CLOUD, CONF_USER_ID, + CONF_ENABLE_ADD_ENTITIES, DATA_CLOUD, DATA_DISCOVERY, DOMAIN, @@ -74,7 +75,7 @@ CONFIGURE_SCHEMA = vol.Schema( CLOUD_SETUP_SCHEMA = vol.Schema( { - vol.Required(CONF_REGION, default="eu"): vol.In(["eu", "us", "cn", "in"]), + vol.Required(CONF_REGION, default="cn"): vol.In(["eu", "us", "cn", "in"]), vol.Optional(CONF_CLIENT_ID): cv.string, vol.Optional(CONF_CLIENT_SECRET): cv.string, vol.Optional(CONF_USER_ID): cv.string, @@ -146,6 +147,7 @@ def options_schema(entities): vol.Required( CONF_ENTITIES, description={"suggested_value": entity_names} ): cv.multi_select(entity_names), + vol.Required(CONF_ENABLE_ADD_ENTITIES, default=False): bool, } ) @@ -554,30 +556,41 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): CONF_PRODUCT_NAME ) if self.editing_device: - self.device_data.update( - { - CONF_DEVICE_ID: dev_id, - CONF_DPS_STRINGS: self.dps_strings, - CONF_ENTITIES: [], - } - ) - if len(user_input[CONF_ENTITIES]) == 0: - return self.async_abort( - reason="no_entities", - description_placeholders={}, + if user_input[CONF_ENABLE_ADD_ENTITIES]: + self.editing_device = False + user_input[CONF_DEVICE_ID] = dev_id + self.device_data.update( + { + CONF_DEVICE_ID: dev_id, + CONF_DPS_STRINGS: self.dps_strings, + } ) - if user_input[CONF_ENTITIES]: - entity_ids = [ - int(entity.split(":")[0]) - for entity in user_input[CONF_ENTITIES] - ] - device_config = self.config_entry.data[CONF_DEVICES][dev_id] - self.entities = [ - entity - for entity in device_config[CONF_ENTITIES] - if entity[CONF_ID] in entity_ids - ] - return await self.async_step_configure_entity() + return await self.async_step_pick_entity_type() + else: + self.device_data.update( + { + CONF_DEVICE_ID: dev_id, + CONF_DPS_STRINGS: self.dps_strings, + CONF_ENTITIES: [], + } + ) + if len(user_input[CONF_ENTITIES]) == 0: + return self.async_abort( + reason="no_entities", + description_placeholders={}, + ) + if user_input[CONF_ENTITIES]: + entity_ids = [ + int(entity.split(":")[0]) + for entity in user_input[CONF_ENTITIES] + ] + device_config = self.config_entry.data[CONF_DEVICES][dev_id] + self.entities = [ + entity + for entity in device_config[CONF_ENTITIES] + if entity[CONF_ID] in entity_ids + ] + return await self.async_step_configure_entity() self.dps_strings = await validate_input(self.hass, user_input) return await self.async_step_pick_entity_type() @@ -608,6 +621,7 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): defaults[CONF_LOCAL_KEY] = cloud_devs[dev_id].get(CONF_LOCAL_KEY) note = "\nNOTE: a new local_key has been retrieved using cloud API" placeholders = {"for_device": f" for device `{dev_id}`.{note}"} + defaults[CONF_ENABLE_ADD_ENTITIES] = False schema = schema_defaults(options_schema(self.entities), **defaults) else: defaults[CONF_PROTOCOL_VERSION] = "3.3" @@ -647,17 +661,6 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): } 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( - self.config_entry, data=config - ) - return self.async_abort( - reason="device_success", - description_placeholders={ - "dev_name": config.get(CONF_FRIENDLY_NAME), - "action": "updated", - }, - ) new_data = self.config_entry.data.copy() new_data[ATTR_UPDATED_AT] = str(int(time.time() * 1000)) @@ -740,7 +743,7 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): 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) + ent_reg = er.async_get(self.hass) reg_entities = { ent.unique_id: ent.entity_id for ent in er.async_entries_for_config_entry(ent_reg, entry_id) diff --git a/custom_components/localtuya/const.py b/custom_components/localtuya/const.py index 3a6c252..630d630 100644 --- a/custom_components/localtuya/const.py +++ b/custom_components/localtuya/const.py @@ -35,6 +35,7 @@ CONF_MODEL = "model" CONF_PRODUCT_KEY = "product_key" CONF_PRODUCT_NAME = "product_name" CONF_USER_ID = "user_id" +CONF_ENABLE_ADD_ENTITIES = "add_entities" CONF_ACTION = "action" diff --git a/custom_components/localtuya/translations/en.json b/custom_components/localtuya/translations/en.json index 947141c..b9beee4 100644 --- a/custom_components/localtuya/translations/en.json +++ b/custom_components/localtuya/translations/en.json @@ -99,6 +99,7 @@ "enable_debug": "Enable debugging for this device (debug must be enabled also in configuration.yaml)", "scan_interval": "Scan interval (seconds, only when not updating automatically)", "entities": "Entities (uncheck an entity to remove it)", + "add_entities": "Add more entities in 'edit device' mode", "manual_dps_strings": "Manual DPS to add (separated by commas ',') - used when detection is not working (optional)", "reset_dpids": "DPIDs to send in RESET command (separated by commas ',')- Used when device does not respond to status requests after turning on (optional)" } From 7cb065bf51c5bf6905b1d25c21b2d309d995eaa5 Mon Sep 17 00:00:00 2001 From: GaoJing <270615179@qq.com> Date: Thu, 16 Mar 2023 16:06:58 +0800 Subject: [PATCH 17/33] revert the default region --- custom_components/localtuya/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index 4bdf5c6..b2c99fa 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -75,7 +75,7 @@ CONFIGURE_SCHEMA = vol.Schema( CLOUD_SETUP_SCHEMA = vol.Schema( { - vol.Required(CONF_REGION, default="cn"): vol.In(["eu", "us", "cn", "in"]), + vol.Required(CONF_REGION, default="eu"): vol.In(["eu", "us", "cn", "in"]), vol.Optional(CONF_CLIENT_ID): cv.string, vol.Optional(CONF_CLIENT_SECRET): cv.string, vol.Optional(CONF_USER_ID): cv.string, From 1691c5f2968a499b1bfad179151104de7bacc96d Mon Sep 17 00:00:00 2001 From: oven-lab Date: Sat, 20 May 2023 10:05:49 +0200 Subject: [PATCH 18/33] Fix depreciated async_get_registry --- custom_components/localtuya/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index d4272d4..58159cf 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -740,7 +740,7 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): 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) + ent_reg = er.async_get(self.hass) reg_entities = { ent.unique_id: ent.entity_id for ent in er.async_entries_for_config_entry(ent_reg, entry_id) From d241f0323bae8d03cc610b07b4028a8ca8be12c8 Mon Sep 17 00:00:00 2001 From: oven-lab Date: Sat, 20 May 2023 13:47:25 +0200 Subject: [PATCH 19/33] Add HACS validation --- .github/workflows/validate.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/validate.yml diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..e7e8e0a --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,18 @@ +name: HACS Validate + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + validate-hacs: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v3" + - name: HACS validation + uses: "hacs/action@main" + with: + category: "integration" From e79520ebc09121abd314f5f2af0661e00547f6ef Mon Sep 17 00:00:00 2001 From: oven-lab Date: Sat, 20 May 2023 13:54:46 +0200 Subject: [PATCH 20/33] Remove extra key --- hacs.json | 1 - 1 file changed, 1 deletion(-) diff --git a/hacs.json b/hacs.json index 3c5a41a..116f017 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,5 @@ { "name": "Local Tuya", - "domains": ["climate", "cover", "fan", "light", "number", "select", "sensor", "switch"], "homeassistant": "0.116.0", "iot_class": ["Local Push"] } From 861dcc5ffdd7067d71f0e8fe22c1f43ee58af04f Mon Sep 17 00:00:00 2001 From: oven-lab Date: Sat, 20 May 2023 13:58:52 +0200 Subject: [PATCH 21/33] Update manifest.json --- custom_components/localtuya/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/manifest.json b/custom_components/localtuya/manifest.json index 3f1e00b..9403b7b 100644 --- a/custom_components/localtuya/manifest.json +++ b/custom_components/localtuya/manifest.json @@ -1,7 +1,7 @@ { "domain": "localtuya", "name": "LocalTuya integration", - "version": "5.0.0", + "version": "5.0.1", "documentation": "https://github.com/rospogrigio/localtuya/", "dependencies": [], "codeowners": [ From de88ee5681af6cffb93b32a671db19953a08d9c8 Mon Sep 17 00:00:00 2001 From: oven-lab Date: Sat, 20 May 2023 13:59:29 +0200 Subject: [PATCH 22/33] Remove extra key --- hacs.json | 1 - 1 file changed, 1 deletion(-) diff --git a/hacs.json b/hacs.json index 116f017..65bb6af 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,4 @@ { "name": "Local Tuya", "homeassistant": "0.116.0", - "iot_class": ["Local Push"] } From 651dc8bd94756269da66a251ede3547ea23b44a2 Mon Sep 17 00:00:00 2001 From: oven-lab Date: Sat, 20 May 2023 14:00:48 +0200 Subject: [PATCH 23/33] Update hacs.json --- hacs.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hacs.json b/hacs.json index 65bb6af..0496f6f 100644 --- a/hacs.json +++ b/hacs.json @@ -1,4 +1,4 @@ { "name": "Local Tuya", - "homeassistant": "0.116.0", + "homeassistant": "0.116.0" } From a47cdc4096e3f1b6a6530db4afcc94d3ee36d2e3 Mon Sep 17 00:00:00 2001 From: oven-lab Date: Sat, 20 May 2023 14:06:54 +0200 Subject: [PATCH 24/33] Add Hassfest validation --- .github/workflows/validate.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index e7e8e0a..f0e209c 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -16,3 +16,5 @@ jobs: uses: "hacs/action@main" with: category: "integration" + - name: Hassfest validation + uses: home-assistant/actions/hassfest@master From 2dbff4750570b510fa9b0f979240a60e6b1b93c9 Mon Sep 17 00:00:00 2001 From: oven-lab Date: Sat, 20 May 2023 14:15:08 +0200 Subject: [PATCH 25/33] Alphabetize manifest.json Alphabetized manifest.json according to home assistant requirements. --- custom_components/localtuya/manifest.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/custom_components/localtuya/manifest.json b/custom_components/localtuya/manifest.json index 9403b7b..e492348 100644 --- a/custom_components/localtuya/manifest.json +++ b/custom_components/localtuya/manifest.json @@ -1,14 +1,14 @@ { "domain": "localtuya", "name": "LocalTuya integration", - "version": "5.0.1", - "documentation": "https://github.com/rospogrigio/localtuya/", - "dependencies": [], "codeowners": [ "@rospogrigio", "@postlund" ], + "config_flow": true, + "dependencies": [], + "documentation": "https://github.com/rospogrigio/localtuya/", + "iot_class": "local_push", "issue_tracker": "https://github.com/rospogrigio/localtuya/issues", "requirements": [], - "config_flow": true, - "iot_class": "local_push" + "version": "5.0.1" } From 7329aefd08ae0aa064dd43f1a713f2466904ccb4 Mon Sep 17 00:00:00 2001 From: oven-lab Date: Sat, 20 May 2023 14:24:06 +0200 Subject: [PATCH 26/33] Delete combined.yaml As described in KTibow/ha-blueprint, ha-blueprint is being retired and we should thereby switch over to hassfest and hacs validation. --- .github/workflows/combined.yaml | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .github/workflows/combined.yaml diff --git a/.github/workflows/combined.yaml b/.github/workflows/combined.yaml deleted file mode 100644 index 8cad813..0000000 --- a/.github/workflows/combined.yaml +++ /dev/null @@ -1,26 +0,0 @@ -name: "Validation And Formatting" -on: - push: - pull_request: - schedule: - - cron: '0 0 * * *' -jobs: - ci: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - name: Download repo - with: - fetch-depth: 0 - - uses: actions/setup-python@v2 - name: Setup Python - - uses: actions/cache@v2 - name: Cache - with: - path: | - ~/.cache/pip - key: custom-component-ci - - uses: KTibow/ha-blueprint@stable - name: CI - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From f2134b877f01ff96b184cc4ae26629c15d47e2dd Mon Sep 17 00:00:00 2001 From: oven-lab Date: Sat, 20 May 2023 14:27:49 +0200 Subject: [PATCH 27/33] Update tox.yaml Added workflow_dispatch --- .github/workflows/tox.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index 05465c0..a7910f4 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -1,6 +1,8 @@ name: Tox PR CI -on: [pull_request] +on: + pull_request: + workflow_dispatch: jobs: build: From 8af18365427e600e3b67faef60ae6e5ec72a1040 Mon Sep 17 00:00:00 2001 From: oven-lab Date: Sat, 20 May 2023 14:49:49 +0200 Subject: [PATCH 28/33] Update tox.ini --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 031fef7..a0b7eac 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ python = [testenv] passenv = TOXENV,CI -whitelist_externals = +allowlist_externals = true setenv = LANG=en_US.UTF-8 From 9d92cadef30cec9244abae184d53f215336c5ea1 Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Wed, 24 May 2023 11:24:09 +0200 Subject: [PATCH 29/33] Reduced logging level for 'Command %d timed out waiting for sequence number'; set v.5.1.0 --- custom_components/localtuya/manifest.json | 2 +- custom_components/localtuya/pytuya/__init__.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/custom_components/localtuya/manifest.json b/custom_components/localtuya/manifest.json index e492348..b11a090 100644 --- a/custom_components/localtuya/manifest.json +++ b/custom_components/localtuya/manifest.json @@ -10,5 +10,5 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/rospogrigio/localtuya/issues", "requirements": [], - "version": "5.0.1" + "version": "5.1.0" } diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index 746cc54..9d076e3 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -449,7 +449,7 @@ class MessageDispatcher(ContextualLogger): try: await asyncio.wait_for(self.listeners[seqno].acquire(), timeout=timeout) except asyncio.TimeoutError: - self.warning( + self.debug( "Command %d timed out waiting for sequence number %d", cmd, seqno ) del self.listeners[seqno] @@ -766,8 +766,9 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): enc_payload = self._encode_message(payload) self.transport.write(enc_payload) - msg = await self.dispatcher.wait_for(seqno, payload.cmd) - if msg is None: + try: + msg = await self.dispatcher.wait_for(seqno, payload.cmd) + except Exception as ex: self.debug("Wait was aborted for seqno %d", seqno) return None @@ -887,7 +888,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): try: # self.debug("decrypting=%r", payload) payload = cipher.decrypt(payload, False, decode_text=False) - except Exception: + except Exception as ex: self.debug("incomplete payload=%r (len:%d)", payload, len(payload)) return self.error_json(ERR_PAYLOAD) @@ -913,7 +914,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): try: # self.debug("decrypting=%r", payload) payload = cipher.decrypt(payload, False) - except Exception: + except Exception as ex: self.debug("incomplete payload=%r (len:%d)", payload, len(payload)) return self.error_json(ERR_PAYLOAD) @@ -944,7 +945,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): self.debug("Deciphered data = %r", payload) try: json_payload = json.loads(payload) - except Exception: + except Exception as ex: raise DecodeError("could not decrypt data: wrong local_key?") # json_payload = self.error_json(ERR_JSON, payload) @@ -980,7 +981,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): # self.debug("decrypting %r using %r", payload, self.real_local_key) cipher = AESCipher(self.real_local_key) payload = cipher.decrypt(payload, False, decode_text=False) - except Exception: + except Exception as ex: self.debug( "session key step 2 decrypt failed, payload=%r (len:%d)", payload, From 5f9141790df9f19406b3bfb11572022199f7e734 Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Wed, 24 May 2023 11:50:27 +0200 Subject: [PATCH 30/33] Fixed tox errors --- custom_components/localtuya/config_flow.py | 48 +++++++++---------- .../localtuya/pytuya/__init__.py | 20 ++++++-- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/custom_components/localtuya/config_flow.py b/custom_components/localtuya/config_flow.py index b2c99fa..5c87e25 100644 --- a/custom_components/localtuya/config_flow.py +++ b/custom_components/localtuya/config_flow.py @@ -566,31 +566,31 @@ class LocalTuyaOptionsFlowHandler(config_entries.OptionsFlow): } ) return await self.async_step_pick_entity_type() - else: - self.device_data.update( - { - CONF_DEVICE_ID: dev_id, - CONF_DPS_STRINGS: self.dps_strings, - CONF_ENTITIES: [], - } + + self.device_data.update( + { + CONF_DEVICE_ID: dev_id, + CONF_DPS_STRINGS: self.dps_strings, + CONF_ENTITIES: [], + } + ) + if len(user_input[CONF_ENTITIES]) == 0: + return self.async_abort( + reason="no_entities", + description_placeholders={}, ) - if len(user_input[CONF_ENTITIES]) == 0: - return self.async_abort( - reason="no_entities", - description_placeholders={}, - ) - if user_input[CONF_ENTITIES]: - entity_ids = [ - int(entity.split(":")[0]) - for entity in user_input[CONF_ENTITIES] - ] - device_config = self.config_entry.data[CONF_DEVICES][dev_id] - self.entities = [ - entity - for entity in device_config[CONF_ENTITIES] - if entity[CONF_ID] in entity_ids - ] - return await self.async_step_configure_entity() + if user_input[CONF_ENTITIES]: + entity_ids = [ + int(entity.split(":")[0]) + for entity in user_input[CONF_ENTITIES] + ] + device_config = self.config_entry.data[CONF_DEVICES][dev_id] + self.entities = [ + entity + for entity in device_config[CONF_ENTITIES] + if entity[CONF_ID] in entity_ids + ] + return await self.async_step_configure_entity() self.dps_strings = await validate_input(self.hass, user_input) return await self.async_step_pick_entity_type() diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index 9d076e3..e2abc58 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -769,7 +769,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): try: msg = await self.dispatcher.wait_for(seqno, payload.cmd) except Exception as ex: - self.debug("Wait was aborted for seqno %d", seqno) + self.debug("Wait was aborted for seqno %d (%s)", seqno, ex) return None # TODO: Verify stuff, e.g. CRC sequence number? @@ -889,7 +889,9 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): # self.debug("decrypting=%r", payload) payload = cipher.decrypt(payload, False, decode_text=False) except Exception as ex: - self.debug("incomplete payload=%r (len:%d)", payload, len(payload)) + self.debug( + "incomplete payload=%r with len:%d (%s)", payload, len(payload), ex + ) return self.error_json(ERR_PAYLOAD) # self.debug("decrypted 3.x payload=%r", payload) @@ -915,7 +917,12 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): # self.debug("decrypting=%r", payload) payload = cipher.decrypt(payload, False) except Exception as ex: - self.debug("incomplete payload=%r (len:%d)", payload, len(payload)) + self.debug( + "incomplete payload=%r with len:%d (%s)", + payload, + len(payload), + ex, + ) return self.error_json(ERR_PAYLOAD) # self.debug("decrypted 3.x payload=%r", payload) @@ -946,7 +953,9 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): try: json_payload = json.loads(payload) except Exception as ex: - raise DecodeError("could not decrypt data: wrong local_key?") + raise DecodeError( + "could not decrypt data: wrong local_key? (exception %s)", ex + ) # json_payload = self.error_json(ERR_JSON, payload) # v3.4 stuffs it into {"data":{"dps":{"1":true}}, ...} @@ -983,9 +992,10 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): payload = cipher.decrypt(payload, False, decode_text=False) except Exception as ex: self.debug( - "session key step 2 decrypt failed, payload=%r (len:%d)", + "session key step 2 decrypt failed, payload=%r with len:%d (%s)", payload, len(payload), + ex, ) return False From 53e1ad8488d5d4f3d50157ac5b188e0eea150cc5 Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Wed, 24 May 2023 14:20:18 +0200 Subject: [PATCH 31/33] Updated manifest.json --- custom_components/localtuya/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/manifest.json b/custom_components/localtuya/manifest.json index b11a090..dad10d4 100644 --- a/custom_components/localtuya/manifest.json +++ b/custom_components/localtuya/manifest.json @@ -10,5 +10,5 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/rospogrigio/localtuya/issues", "requirements": [], - "version": "5.1.0" + "version": "5.2.0" } From 9d5984e4cf07e7fff3b93cfd5b0bed546e6e9e74 Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Wed, 7 Jun 2023 11:59:31 +0200 Subject: [PATCH 32/33] Fix for socket.send() exceptions in the logs --- custom_components/localtuya/manifest.json | 2 +- custom_components/localtuya/pytuya/__init__.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/custom_components/localtuya/manifest.json b/custom_components/localtuya/manifest.json index dad10d4..28e36fa 100644 --- a/custom_components/localtuya/manifest.json +++ b/custom_components/localtuya/manifest.json @@ -10,5 +10,5 @@ "iot_class": "local_push", "issue_tracker": "https://github.com/rospogrigio/localtuya/issues", "requirements": [], - "version": "5.2.0" + "version": "5.2.1" } diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index e2abc58..fd69e7a 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -766,10 +766,9 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): enc_payload = self._encode_message(payload) self.transport.write(enc_payload) - try: - msg = await self.dispatcher.wait_for(seqno, payload.cmd) - except Exception as ex: - self.debug("Wait was aborted for seqno %d (%s)", seqno, ex) + msg = await self.dispatcher.wait_for(seqno, payload.cmd) + if msg is None: + self.debug("Wait was aborted for seqno %d", seqno) return None # TODO: Verify stuff, e.g. CRC sequence number? From ba5670fc1d5a804f37fcac0a902b1281a5566daf Mon Sep 17 00:00:00 2001 From: rospogrigio Date: Wed, 7 Jun 2023 12:23:41 +0200 Subject: [PATCH 33/33] Fixing pylint error --- custom_components/localtuya/pytuya/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/localtuya/pytuya/__init__.py b/custom_components/localtuya/pytuya/__init__.py index fd69e7a..bcc8bbe 100644 --- a/custom_components/localtuya/pytuya/__init__.py +++ b/custom_components/localtuya/pytuya/__init__.py @@ -953,7 +953,7 @@ class TuyaProtocol(asyncio.Protocol, ContextualLogger): json_payload = json.loads(payload) except Exception as ex: raise DecodeError( - "could not decrypt data: wrong local_key? (exception %s)", ex + "could not decrypt data: wrong local_key? (exception: %s)" % ex ) # json_payload = self.error_json(ERR_JSON, payload)