Selaa lähdekoodia

Use bleak-retry-connector to handle transient connection errors (#50)

We built bleak-retry-connector for aiohomekit to handle the
transient errors that happen when using bleak with dbus since
we were missing disconnected events. As switchbots seem to
fail in the same way with bleak on dbus, using the library
made the problem go away
J. Nick Koston 2 vuotta sitten
vanhempi
commit
aaed98ba0e
2 muutettua tiedostoa jossa 48 lisäystä ja 46 poistoa
  1. 1 1
      setup.py
  2. 47 45
      switchbot/__init__.py

+ 1 - 1
setup.py

@@ -3,7 +3,7 @@ from setuptools import setup
 setup(
 setup(
     name = 'PySwitchbot',
     name = 'PySwitchbot',
     packages = ['switchbot'],
     packages = ['switchbot'],
-    install_requires=['bleak'],
+    install_requires=['bleak', 'bleak-retry-connector'],
     version = '0.15.0',
     version = '0.15.0',
     description = 'A library to communicate with Switchbot',
     description = 'A library to communicate with Switchbot',
     author='Daniel Hjelseth Hoyer',
     author='Daniel Hjelseth Hoyer',

+ 47 - 45
switchbot/__init__.py

@@ -11,6 +11,7 @@ from uuid import UUID
 import bleak
 import bleak
 from bleak.backends.device import BLEDevice
 from bleak.backends.device import BLEDevice
 from bleak.backends.scanner import AdvertisementData
 from bleak.backends.scanner import AdvertisementData
+from bleak_retry_connector import BleakClient, establish_connection
 
 
 DEFAULT_RETRY_COUNT = 3
 DEFAULT_RETRY_COUNT = 3
 DEFAULT_RETRY_TIMEOUT = 1
 DEFAULT_RETRY_TIMEOUT = 1
@@ -285,61 +286,62 @@ class SwitchbotDevice:
     async def _sendcommand(self, key: str, retry: int) -> bytes:
     async def _sendcommand(self, key: str, retry: int) -> bytes:
         """Send command to device and read response."""
         """Send command to device and read response."""
         command = bytearray.fromhex(self._commandkey(key))
         command = bytearray.fromhex(self._commandkey(key))
-        notify_msg = b""
         _LOGGER.debug("Sending command to switchbot %s", command)
         _LOGGER.debug("Sending command to switchbot %s", command)
-
+        max_attempts = retry + 1
         async with CONNECT_LOCK:
         async with CONNECT_LOCK:
-            try:
+            for attempt in range(max_attempts):
-                async with bleak.BleakClient(
+                try:
-                    address_or_ble_device=self._device,
+                    return await self._send_command_locked(key, command)
-                    timeout=float(self._scan_timeout),
+                except (bleak.BleakError, asyncio.exceptions.TimeoutError):
-                ) as client:
+                    if attempt == retry:
-                    _LOGGER.debug("Connnected to switchbot: %s", client.is_connected)
+                        _LOGGER.error(
-
+                            "Switchbot communication failed. Stopping trying",
-                    _LOGGER.debug("Subscribe to notifications")
+                            exc_info=True,
-                    await client.start_notify(
+                        )
-                        _sb_uuid(comms_type="rx"), self._notification_handler
+                        return b"\x00"
-                    )
+
-
+                    _LOGGER.debug("Switchbot communication failed with:", exc_info=True)
-                    _LOGGER.debug("Sending command, %s", key)
+
-                    await client.write_gatt_char(
+        raise RuntimeError("Unreachable")
-                        _sb_uuid(comms_type="tx"), command, False
+
-                    )
+    async def _send_command_locked(self, key: str, command: bytes) -> bytes:
-
+        """Send command to device and read response."""
-                    await asyncio.sleep(
+        client: BleakClient | None = None
-                        1.0
+        try:
-                    )  # Bot needs pause. Otherwise notification could be missed.
+            _LOGGER.debug("Connnecting to switchbot: %s", self._device.address)
-
-                    notify_msg = self._last_notification
-                    _LOGGER.info("Notification received: %s", notify_msg)
 
 
-                    _LOGGER.debug("UnSubscribe to notifications")
+            client = await establish_connection(
-                    await client.stop_notify(_sb_uuid(comms_type="rx"))
+                BleakClient, self._device.address, self._device, max_attempts=1
+            )
+            _LOGGER.debug("Connnected to switchbot: %s", client.is_connected)
 
 
-            except (bleak.BleakError, asyncio.exceptions.TimeoutError):
+            _LOGGER.debug("Subscribe to notifications")
+            await client.start_notify(
+                _sb_uuid(comms_type="rx"), self._notification_handler
+            )
 
 
-                if retry < 1:
+            _LOGGER.debug("Sending command, %s", key)
-                    _LOGGER.error(
+            await client.write_gatt_char(_sb_uuid(comms_type="tx"), command, False)
-                        "Switchbot communication failed. Stopping trying", exc_info=True
-                    )
-                    return b"\x00"
 
 
-                _LOGGER.debug("Switchbot communication failed with:", exc_info=True)
+            await asyncio.sleep(
+                1.0
+            )  # Bot needs pause. Otherwise notification could be missed.
 
 
-        if notify_msg:
+            notify_msg = self._last_notification
-            if notify_msg == b"\x07":
+            _LOGGER.info("Notification received: %s", notify_msg)
-                _LOGGER.error("Password required")
-            elif notify_msg == b"\t":
-                _LOGGER.error("Password incorrect")
-            return notify_msg
 
 
-        _LOGGER.warning("Cannot connect to Switchbot. Retrying (remaining: %d)", retry)
+            _LOGGER.debug("UnSubscribe to notifications")
+            await client.stop_notify(_sb_uuid(comms_type="rx"))
 
 
-        if retry < 1:  # failsafe
+        finally:
-            return b"\x00"
+            if client:
+                await client.disconnect()
 
 
-        await asyncio.sleep(DEFAULT_RETRY_TIMEOUT)
+        if notify_msg == b"\x07":
-        return await self._sendcommand(key, retry - 1)
+            _LOGGER.error("Password required")
+        elif notify_msg == b"\t":
+            _LOGGER.error("Password incorrect")
+        return notify_msg
 
 
     def get_address(self) -> str:
     def get_address(self) -> str:
         """Return address of device."""
         """Return address of device."""