Quellcode durchsuchen

Synchronize accesses to sub-devices (#3929)

When multiple sub-devices are under the same parent device, we need to make sure to access only one sub-device at a time,
because the underlying tinytuya device for the parent cannot handle multiple acceses at the same time. This bug often
manifests as "No local key for device!" in the tinytuya logs.

This commit adds a lock that is associated with the parent device, and acquires the lock whenever we access a sub-device or
the parent device itself.

Issue #2289
Jim Chen vor 3 Monaten
Ursprung
Commit
cdd6050495
1 geänderte Dateien mit 21 neuen und 4 gelöschten Zeilen
  1. 21 4
      custom_components/tuya_local/device.py

+ 21 - 4
custom_components/tuya_local/device.py

@@ -78,22 +78,33 @@ class TuyaLocalDevice(object):
             if dev_cid:
                 if hass.data[DOMAIN].get(dev_id) and name != "Test":
                     parent = hass.data[DOMAIN][dev_id]["tuyadevice"]
+                    parent_lock = hass.data[DOMAIN][dev_id]["tuyadevicelock"]
                 else:
                     parent = tinytuya.Device(dev_id, address, local_key)
+                    parent_lock = asyncio.Lock()
                     if name != "Test":
-                        hass.data[DOMAIN][dev_id] = {"tuyadevice": parent}
+                        hass.data[DOMAIN][dev_id] = {
+                            "tuyadevice": parent,
+                            "tuyadevicelock": parent_lock,
+                        }
                 self._api = tinytuya.Device(
                     dev_cid,
                     cid=dev_cid,
                     parent=parent,
                 )
+                self._api_lock = parent_lock
             else:
                 if hass.data[DOMAIN].get(dev_id) and name != "Test":
                     self._api = hass.data[DOMAIN][dev_id]["tuyadevice"]
+                    self._api_lock = hass.data[DOMAIN][dev_id]["tuyadevicelock"]
                 else:
                     self._api = tinytuya.Device(dev_id, address, local_key)
+                    self._api_lock = asyncio.Lock()
                     if name != "Test":
-                        hass.data[DOMAIN][dev_id] = {"tuyadevice": self._api}
+                        hass.data[DOMAIN][dev_id] = {
+                            "tuyadevice": self._api,
+                            "tuyadevicelock": self._api_lock,
+                        }
         except Exception as e:
             _LOGGER.error(
                 "%s: %s while initialising device %s",
@@ -288,6 +299,7 @@ class TuyaLocalDevice(object):
 
         while self._running:
             error_count = self._api_working_protocol_failures
+            await self._api_lock.acquire()
             try:
                 last_cache = self._cached_state.get("updated_at", 0)
                 now = time()
@@ -332,6 +344,7 @@ class TuyaLocalDevice(object):
                         self._api.receive,
                     )
                 else:
+                    self._api_lock.release()
                     await asyncio.sleep(5)
                     poll = None
 
@@ -360,8 +373,6 @@ class TuyaLocalDevice(object):
                         poll["full_poll"] = full_poll
                         yield poll
 
-                await asyncio.sleep(0.1 if self.has_returned_state else 5)
-
             except CancelledError:
                 self._running = False
                 # Close the persistent connection when exiting the loop
@@ -379,7 +390,13 @@ class TuyaLocalDevice(object):
                 self._api.set_socketPersistent(False)
                 if self._api.parent:
                     self._api.parent.set_socketPersistent(False)
+                self._api_lock.release()
                 await asyncio.sleep(5)
+            finally:
+                if self._api_lock.locked():
+                    self._api_lock.release()
+
+            await asyncio.sleep(0.1 if self.has_returned_state else 5)
 
         # Close the persistent connection when exiting the loop
         self._api.set_socketPersistent(False)