42
42
DISCONNECT_DELAY = 49
43
43
44
44
45
+ class CharacteristicMissingError (Exception ):
46
+ """Raised when a characteristic is missing."""
47
+
48
+
45
49
def _sb_uuid (comms_type : str = "service" ) -> UUID | str :
46
50
"""Return Switchbot UUID."""
47
51
@@ -53,6 +57,10 @@ def _sb_uuid(comms_type: str = "service") -> UUID | str:
53
57
return "Incorrect type, choose between: tx, rx or service"
54
58
55
59
60
+ READ_CHAR_UUID = _sb_uuid (comms_type = "rx" )
61
+ WRITE_CHAR_UUID = _sb_uuid (comms_type = "tx" )
62
+
63
+
56
64
class SwitchbotDevice :
57
65
"""Base Representation of a Switchbot Device."""
58
66
@@ -105,6 +113,12 @@ async def _sendcommand(self, key: str, retry: int) -> bytes:
105
113
)
106
114
107
115
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
+ )
108
122
async with self ._operation_lock :
109
123
for attempt in range (max_attempts ):
110
124
try :
@@ -116,6 +130,24 @@ async def _sendcommand(self, key: str, retry: int) -> bytes:
116
130
exc_info = True ,
117
131
)
118
132
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
+ )
119
151
except BLEAK_EXCEPTIONS :
120
152
if attempt == retry :
121
153
_LOGGER .error (
@@ -167,14 +199,21 @@ async def _ensure_connected(self):
167
199
cached_services = self ._cached_services ,
168
200
ble_device_callback = lambda : self ._device ,
169
201
)
170
- self ._cached_services = client .services
171
202
_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
175
208
self ._client = client
176
209
self ._reset_disconnect_timer ()
177
210
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
+
178
217
def _reset_disconnect_timer (self ):
179
218
"""Reset disconnect timer."""
180
219
if self ._disconnect_timer :
@@ -214,13 +253,13 @@ async def _execute_timed_disconnect(self):
214
253
async def _execute_disconnect (self ):
215
254
"""Execute disconnection."""
216
255
async with self ._connect_lock :
217
- if not self ._client or not self ._client .is_connected :
218
- return
256
+ client = self ._client
219
257
self ._expected_disconnect = True
220
- await self ._client .disconnect ()
221
258
self ._client = None
222
259
self ._read_char = None
223
260
self ._write_char = None
261
+ if client and client .is_connected :
262
+ await client .disconnect ()
224
263
225
264
async def _send_command_locked (self , key : str , command : bytes ) -> bytes :
226
265
"""Send command to device and read response."""
@@ -250,8 +289,10 @@ async def _send_command_locked(self, key: str, command: bytes) -> bytes:
250
289
async def _execute_command_locked (self , key : str , command : bytes ) -> bytes :
251
290
"""Execute command and read response."""
252
291
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 )
255
296
future : asyncio .Future [bytearray ] = asyncio .Future ()
256
297
client = self ._client
257
298
0 commit comments