Skip to content

Commit 017a08a

Browse files
authored
Avoid caching services when there are missing Characteristics (#84)
1 parent 74f2996 commit 017a08a

File tree

1 file changed

+50
-9
lines changed

1 file changed

+50
-9
lines changed

switchbot/devices/device.py

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@
4242
DISCONNECT_DELAY = 49
4343

4444

45+
class CharacteristicMissingError(Exception):
46+
"""Raised when a characteristic is missing."""
47+
48+
4549
def _sb_uuid(comms_type: str = "service") -> UUID | str:
4650
"""Return Switchbot UUID."""
4751

@@ -53,6 +57,10 @@ def _sb_uuid(comms_type: str = "service") -> UUID | str:
5357
return "Incorrect type, choose between: tx, rx or service"
5458

5559

60+
READ_CHAR_UUID = _sb_uuid(comms_type="rx")
61+
WRITE_CHAR_UUID = _sb_uuid(comms_type="tx")
62+
63+
5664
class SwitchbotDevice:
5765
"""Base Representation of a Switchbot Device."""
5866

@@ -105,6 +113,12 @@ async def _sendcommand(self, key: str, retry: int) -> bytes:
105113
)
106114

107115
max_attempts = retry + 1
116+
if self._operation_lock.locked():
117+
_LOGGER.debug(
118+
"%s: Operation already in progress, waiting for it to complete; RSSI: %s",
119+
self.name,
120+
self.rssi,
121+
)
108122
async with self._operation_lock:
109123
for attempt in range(max_attempts):
110124
try:
@@ -116,6 +130,24 @@ async def _sendcommand(self, key: str, retry: int) -> bytes:
116130
exc_info=True,
117131
)
118132
return b"\x00"
133+
except CharacteristicMissingError as ex:
134+
if attempt == retry:
135+
_LOGGER.error(
136+
"%s: characteristic missing: %s; Stopping trying; RSSI: %s",
137+
self.name,
138+
ex,
139+
self.rssi,
140+
exc_info=True,
141+
)
142+
return b"\x00"
143+
144+
_LOGGER.debug(
145+
"%s: characteristic missing: %s; RSSI: %s",
146+
self.name,
147+
ex,
148+
self.rssi,
149+
exc_info=True,
150+
)
119151
except BLEAK_EXCEPTIONS:
120152
if attempt == retry:
121153
_LOGGER.error(
@@ -167,14 +199,21 @@ async def _ensure_connected(self):
167199
cached_services=self._cached_services,
168200
ble_device_callback=lambda: self._device,
169201
)
170-
self._cached_services = client.services
171202
_LOGGER.debug("%s: Connected; RSSI: %s", self.name, self.rssi)
172-
services = client.services
173-
self._read_char = services.get_characteristic(_sb_uuid(comms_type="rx"))
174-
self._write_char = services.get_characteristic(_sb_uuid(comms_type="tx"))
203+
resolved = self._resolve_characteristics(client.services)
204+
if not resolved:
205+
# Try to handle services failing to load
206+
resolved = self._resolve_characteristics(await client.get_services())
207+
self._cached_services = client.services if resolved else None
175208
self._client = client
176209
self._reset_disconnect_timer()
177210

211+
def _resolve_characteristics(self, services: BleakGATTServiceCollection) -> bool:
212+
"""Resolve characteristics."""
213+
self._read_char = services.get_characteristic(READ_CHAR_UUID)
214+
self._write_char = services.get_characteristic(WRITE_CHAR_UUID)
215+
return bool(self._read_char and self._write_char)
216+
178217
def _reset_disconnect_timer(self):
179218
"""Reset disconnect timer."""
180219
if self._disconnect_timer:
@@ -214,13 +253,13 @@ async def _execute_timed_disconnect(self):
214253
async def _execute_disconnect(self):
215254
"""Execute disconnection."""
216255
async with self._connect_lock:
217-
if not self._client or not self._client.is_connected:
218-
return
256+
client = self._client
219257
self._expected_disconnect = True
220-
await self._client.disconnect()
221258
self._client = None
222259
self._read_char = None
223260
self._write_char = None
261+
if client and client.is_connected:
262+
await client.disconnect()
224263

225264
async def _send_command_locked(self, key: str, command: bytes) -> bytes:
226265
"""Send command to device and read response."""
@@ -250,8 +289,10 @@ async def _send_command_locked(self, key: str, command: bytes) -> bytes:
250289
async def _execute_command_locked(self, key: str, command: bytes) -> bytes:
251290
"""Execute command and read response."""
252291
assert self._client is not None
253-
assert self._read_char is not None
254-
assert self._write_char is not None
292+
if not self._read_char:
293+
raise CharacteristicMissingError(READ_CHAR_UUID)
294+
if not self._write_char:
295+
raise CharacteristicMissingError(WRITE_CHAR_UUID)
255296
future: asyncio.Future[bytearray] = asyncio.Future()
256297
client = self._client
257298

0 commit comments

Comments
 (0)