Skip to content

Commit aaed98b

Browse files
authored
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
1 parent 3c8cff4 commit aaed98b

File tree

2 files changed

+48
-46
lines changed

2 files changed

+48
-46
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
setup(
44
name = 'PySwitchbot',
55
packages = ['switchbot'],
6-
install_requires=['bleak'],
6+
install_requires=['bleak', 'bleak-retry-connector'],
77
version = '0.15.0',
88
description = 'A library to communicate with Switchbot',
99
author='Daniel Hjelseth Hoyer',

switchbot/__init__.py

Lines changed: 47 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import bleak
1212
from bleak.backends.device import BLEDevice
1313
from bleak.backends.scanner import AdvertisementData
14+
from bleak_retry_connector import BleakClient, establish_connection
1415

1516
DEFAULT_RETRY_COUNT = 3
1617
DEFAULT_RETRY_TIMEOUT = 1
@@ -285,61 +286,62 @@ def _commandkey(self, key: str) -> str:
285286
async def _sendcommand(self, key: str, retry: int) -> bytes:
286287
"""Send command to device and read response."""
287288
command = bytearray.fromhex(self._commandkey(key))
288-
notify_msg = b""
289289
_LOGGER.debug("Sending command to switchbot %s", command)
290-
290+
max_attempts = retry + 1
291291
async with CONNECT_LOCK:
292-
try:
293-
async with bleak.BleakClient(
294-
address_or_ble_device=self._device,
295-
timeout=float(self._scan_timeout),
296-
) as client:
297-
_LOGGER.debug("Connnected to switchbot: %s", client.is_connected)
298-
299-
_LOGGER.debug("Subscribe to notifications")
300-
await client.start_notify(
301-
_sb_uuid(comms_type="rx"), self._notification_handler
302-
)
303-
304-
_LOGGER.debug("Sending command, %s", key)
305-
await client.write_gatt_char(
306-
_sb_uuid(comms_type="tx"), command, False
307-
)
308-
309-
await asyncio.sleep(
310-
1.0
311-
) # Bot needs pause. Otherwise notification could be missed.
312-
313-
notify_msg = self._last_notification
314-
_LOGGER.info("Notification received: %s", notify_msg)
292+
for attempt in range(max_attempts):
293+
try:
294+
return await self._send_command_locked(key, command)
295+
except (bleak.BleakError, asyncio.exceptions.TimeoutError):
296+
if attempt == retry:
297+
_LOGGER.error(
298+
"Switchbot communication failed. Stopping trying",
299+
exc_info=True,
300+
)
301+
return b"\x00"
302+
303+
_LOGGER.debug("Switchbot communication failed with:", exc_info=True)
304+
305+
raise RuntimeError("Unreachable")
306+
307+
async def _send_command_locked(self, key: str, command: bytes) -> bytes:
308+
"""Send command to device and read response."""
309+
client: BleakClient | None = None
310+
try:
311+
_LOGGER.debug("Connnecting to switchbot: %s", self._device.address)
315312

316-
_LOGGER.debug("UnSubscribe to notifications")
317-
await client.stop_notify(_sb_uuid(comms_type="rx"))
313+
client = await establish_connection(
314+
BleakClient, self._device.address, self._device, max_attempts=1
315+
)
316+
_LOGGER.debug("Connnected to switchbot: %s", client.is_connected)
318317

319-
except (bleak.BleakError, asyncio.exceptions.TimeoutError):
318+
_LOGGER.debug("Subscribe to notifications")
319+
await client.start_notify(
320+
_sb_uuid(comms_type="rx"), self._notification_handler
321+
)
320322

321-
if retry < 1:
322-
_LOGGER.error(
323-
"Switchbot communication failed. Stopping trying", exc_info=True
324-
)
325-
return b"\x00"
323+
_LOGGER.debug("Sending command, %s", key)
324+
await client.write_gatt_char(_sb_uuid(comms_type="tx"), command, False)
326325

327-
_LOGGER.debug("Switchbot communication failed with:", exc_info=True)
326+
await asyncio.sleep(
327+
1.0
328+
) # Bot needs pause. Otherwise notification could be missed.
328329

329-
if notify_msg:
330-
if notify_msg == b"\x07":
331-
_LOGGER.error("Password required")
332-
elif notify_msg == b"\t":
333-
_LOGGER.error("Password incorrect")
334-
return notify_msg
330+
notify_msg = self._last_notification
331+
_LOGGER.info("Notification received: %s", notify_msg)
335332

336-
_LOGGER.warning("Cannot connect to Switchbot. Retrying (remaining: %d)", retry)
333+
_LOGGER.debug("UnSubscribe to notifications")
334+
await client.stop_notify(_sb_uuid(comms_type="rx"))
337335

338-
if retry < 1: # failsafe
339-
return b"\x00"
336+
finally:
337+
if client:
338+
await client.disconnect()
340339

341-
await asyncio.sleep(DEFAULT_RETRY_TIMEOUT)
342-
return await self._sendcommand(key, retry - 1)
340+
if notify_msg == b"\x07":
341+
_LOGGER.error("Password required")
342+
elif notify_msg == b"\t":
343+
_LOGGER.error("Password incorrect")
344+
return notify_msg
343345

344346
def get_address(self) -> str:
345347
"""Return address of device."""

0 commit comments

Comments
 (0)