f3b1a85d by huangyf2

11 优化蓝牙扫描

1 parent 0e90a8e9
Showing 1 changed file with 231 additions and 52 deletions
...@@ -128,6 +128,8 @@ class BLEClient: ...@@ -128,6 +128,8 @@ class BLEClient:
128 self._notification_callbacks = {} # 存储通知回调,用于清理 128 self._notification_callbacks = {} # 存储通知回调,用于清理
129 self._shutdown = False # 添加关闭标志 129 self._shutdown = False # 添加关闭标志
130 self._scanner = None # 当前扫描器实例 130 self._scanner = None # 当前扫描器实例
131 self._connecting = False # 连接状态标志
132 self._connect_task = None # 连接任务
131 133
132 def on_disconnect(self, client): 134 def on_disconnect(self, client):
133 print("BLE连接断开,关闭WebSocket") 135 print("BLE连接断开,关闭WebSocket")
...@@ -160,14 +162,9 @@ class BLEClient: ...@@ -160,14 +162,9 @@ class BLEClient:
160 162
161 def detection_callback(self, device, advertisement_data): 163 def detection_callback(self, device, advertisement_data):
162 if any(service_uuid in advertisement_data.service_uuids for service_uuid in self.services): 164 if any(service_uuid in advertisement_data.service_uuids for service_uuid in self.services):
163 # 优先使用广播包中的local_name 165 # 设备名称默认获取方式:优先使用广播包local_name,如果为空则使用device.name
164 device_name = advertisement_data.local_name if advertisement_data.local_name else (device.name if device.name else "") 166 device_name = advertisement_data.local_name if advertisement_data.local_name else (device.name if device.name else "")
165 167
166 # 丢弃名字为空的设备
167 if not device_name or device_name.strip() == "":
168 print(f"跳过名字为空的设备: {device.address}")
169 return None
170
171 self.target_device = (device, advertisement_data) 168 self.target_device = (device, advertisement_data)
172 if not self.target_device: 169 if not self.target_device:
173 print("未找到匹配设备") 170 print("未找到匹配设备")
...@@ -175,7 +172,7 @@ class BLEClient: ...@@ -175,7 +172,7 @@ class BLEClient:
175 else: 172 else:
176 device, adv_data = self.target_device 173 device, adv_data = self.target_device
177 print("\n找到目标设备:") 174 print("\n找到目标设备:")
178 print(f"设备名称(从广播包): {device_name}") 175 print(f"设备名称: {device_name} (优先广播包local_name,否则使用device.name)")
179 print(f"设备地址: {device.address}") 176 print(f"设备地址: {device.address}")
180 print(f"信号强度: {device.rssi} dBm") 177 print(f"信号强度: {device.rssi} dBm")
181 print("\n广播信息:") 178 print("\n广播信息:")
...@@ -200,6 +197,137 @@ class BLEClient: ...@@ -200,6 +197,137 @@ class BLEClient:
200 except Exception: 197 except Exception:
201 pass 198 pass
202 199
200 async def _background_connect(self, peripheral_id, request_id):
201 """
202 后台执行BLE连接任务
203 优化策略:
204 1. 快速断开旧连接(不阻塞)
205 2. 等待资源释放
206 3. 执行连接(带超时和重试)
207 4. 验证连接状态
208 5. 发送状态通知
209 """
210 try:
211 self._connecting = True
212
213 # 步骤1: 快速断开旧连接(带超时)
214 old_client = self.client
215 if old_client:
216 try:
217 if old_client.is_connected:
218 await asyncio.wait_for(old_client.disconnect(), timeout=1.0)
219 print(f"旧连接已断开: {peripheral_id}")
220 except asyncio.TimeoutError:
221 print(f"断开旧连接超时,强制清理: {peripheral_id}")
222 except Exception as e:
223 print(f"断开旧连接异常: {e}")
224 finally:
225 if old_client == self.client:
226 self.client = None
227 # 等待资源释放,避免冲突
228 await asyncio.sleep(0.3)
229
230 # 步骤2: 执行连接(带超时和重试机制)
231 client = None
232 max_retries = 2
233 connect_timeout = 8.0
234
235 for attempt in range(1, max_retries + 1):
236 try:
237 if attempt > 1:
238 print(f"连接重试 {attempt}/{max_retries}: {peripheral_id}")
239 await asyncio.sleep(0.5 * attempt) # 退避延迟
240
241 client = BleakClient(peripheral_id, timeout=10.0)
242 client.set_disconnected_callback(self.on_disconnect)
243
244 # 连接操作,带超时控制
245 await asyncio.wait_for(client.connect(), timeout=connect_timeout)
246
247 # 验证连接状态
248 if not client.is_connected:
249 raise Exception("连接后状态检查失败")
250
251 # 再次验证(等待一小段时间)
252 await asyncio.sleep(0.1)
253 if not client.is_connected:
254 raise Exception("连接状态验证失败")
255
256 # 连接成功
257 self.client = client
258 self._connecting = False
259 client = None
260
261 print(f"成功连接到设备: {peripheral_id}")
262
263 # 发送连接成功通知
264 success_response = json.dumps({
265 "jsonrpc": "2.0",
266 "method": "connectionStatus",
267 "params": {
268 "connected": True,
269 "peripheralId": peripheral_id
270 },
271 "id": request_id
272 })
273 await log_message("下发", success_response)
274 if self.websocket and not self.websocket.closed:
275 await self.websocket.send(success_response)
276 return
277
278 except asyncio.TimeoutError:
279 print(f"连接超时 ({connect_timeout}秒) - 尝试 {attempt}/{max_retries}: {peripheral_id}")
280 if client:
281 try:
282 asyncio.create_task(client.disconnect())
283 except Exception:
284 pass
285 client = None
286 if attempt == max_retries:
287 error_msg = f"连接BLE设备超时({connect_timeout}秒)"
288 break
289 continue
290
291 except Exception as e:
292 print(f"连接异常 - 尝试 {attempt}/{max_retries}: {e}")
293 if client:
294 try:
295 asyncio.create_task(client.disconnect())
296 except Exception:
297 pass
298 client = None
299 if attempt == max_retries:
300 error_msg = str(e)
301 break
302 continue
303
304 # 连接失败
305 self._connecting = False
306 error_response = json.dumps({
307 "jsonrpc": "2.0",
308 "method": "connectionStatus",
309 "params": {
310 "connected": False,
311 "error": error_msg,
312 "peripheralId": peripheral_id
313 },
314 "id": request_id
315 })
316 await log_message("下发", error_response)
317 if self.websocket and not self.websocket.closed:
318 await self.websocket.send(error_response)
319
320 except Exception as e:
321 self._connecting = False
322 print(f"后台连接未知错误: {e}")
323 finally:
324 # 清理未使用的client
325 if client and client != self.client:
326 try:
327 asyncio.create_task(client.disconnect())
328 except Exception:
329 pass
330
203 async def handle_client(self, websocket, path): 331 async def handle_client(self, websocket, path):
204 self.websocket = websocket 332 self.websocket = websocket
205 self._shutdown = False # 重置关闭标志 333 self._shutdown = False # 重置关闭标志
...@@ -234,24 +362,50 @@ class BLEClient: ...@@ -234,24 +362,50 @@ class BLEClient:
234 # 双重扫描:快速扫描后若未发现,再进行扩展扫描;发现即停 362 # 双重扫描:快速扫描后若未发现,再进行扩展扫描;发现即停
235 phases = [("active", 3.0), ("passive", 6.0)] 363 phases = [("active", 3.0), ("passive", 6.0)]
236 found = False 364 found = False
365 scan_error = None
366
237 for phase_index, (scan_mode, duration) in enumerate(phases, start=1): 367 for phase_index, (scan_mode, duration) in enumerate(phases, start=1):
238 self.target_device = None 368 self.target_device = None
239 self._scanner = BleakScanner(scanning_mode=scan_mode) 369 self._scanner = None
240 self._scanner.register_detection_callback(self.detection_callback) 370
241 print(f"开始第{phase_index}阶段扫描(模式: {scan_mode}, 时长: {duration}s)...")
242 try: 371 try:
243 await self._scanner.start() 372 # 创建扫描器
373 self._scanner = BleakScanner(scanning_mode=scan_mode)
374 self._scanner.register_detection_callback(self.detection_callback)
375 print(f"开始第{phase_index}阶段扫描(模式: {scan_mode}, 时长: {duration}s)...")
376
377 # 启动扫描
378 try:
379 await self._scanner.start()
380 except Exception as e:
381 print(f"扫描启动失败-阶段{phase_index}: {e}")
382 scan_error = str(e)
383 continue
384
244 # 轮询检查是否已找到,找到则提前停止 385 # 轮询检查是否已找到,找到则提前停止
245 start_ts = time.time() 386 start_ts = time.time()
387 check_interval = 0.05 # 更频繁的检查,更快响应
246 while time.time() - start_ts < duration and not self.target_device: 388 while time.time() - start_ts < duration and not self.target_device:
247 await asyncio.sleep(0.1) 389 await asyncio.sleep(check_interval)
390 # 检查是否应该关闭
391 if self._shutdown:
392 break
393
394 except Exception as e:
395 print(f"扫描过程异常-阶段{phase_index}: {e}")
396 scan_error = str(e)
248 finally: 397 finally:
249 try: 398 # 确保扫描器被停止和清理
250 await self._scanner.stop() 399 if self._scanner is not None:
251 except Exception: 400 try:
252 pass 401 await self._scanner.stop()
253 self._scanner = None 402 except Exception as e:
403 print(f"扫描停止失败-阶段{phase_index}: {e}")
404 self._scanner = None
405 # 等待扫描完全停止
406 await asyncio.sleep(0.1)
254 407
408 # 检查是否找到设备
255 if self.target_device: 409 if self.target_device:
256 found = True 410 found = True
257 break 411 break
...@@ -260,13 +414,14 @@ class BLEClient: ...@@ -260,13 +414,14 @@ class BLEClient:
260 414
261 if found: 415 if found:
262 device, adv_data = self.target_device 416 device, adv_data = self.target_device
263 # 优先使用广播包中的local_name,如果没有则使用device.name 417 # 设备名称默认获取方式:优先使用广播包local_name,如果为空则使用device.name
264 device_name = adv_data.local_name if adv_data.local_name else (device.name if device.name else "") 418 device_name = adv_data.local_name if adv_data.local_name else (device.name if device.name else "")
419
265 discover_response = json.dumps({ 420 discover_response = json.dumps({
266 "jsonrpc": "2.0", 421 "jsonrpc": "2.0",
267 "method": "didDiscoverPeripheral", 422 "method": "didDiscoverPeripheral",
268 "params": { 423 "params": {
269 "name": device_name, 424 "name": device_name, # 设备名称(优先广播包local_name,否则device.name)
270 "peripheralId": device.address, 425 "peripheralId": device.address,
271 "rssi": device.rssi 426 "rssi": device.rssi
272 } 427 }
...@@ -285,39 +440,16 @@ class BLEClient: ...@@ -285,39 +440,16 @@ class BLEClient:
285 await websocket.send(result_response) 440 await websocket.send(result_response)
286 441
287 elif method == "connect": 442 elif method == "connect":
443 """
444 优化连接策略:
445 1. 立即返回"连接中"状态,不阻塞WebSocket
446 2. 在后台异步执行连接(带重试机制)
447 3. 连接完成后通过connectionStatus通知客户端
448 """
288 peripheral_id = params.get("peripheralId") 449 peripheral_id = params.get("peripheralId")
289 if peripheral_id: 450 if peripheral_id:
290 # 如果已有连接,先断开 451 # 验证设备地址格式
291 if self.client and self.client.is_connected: 452 if len(peripheral_id) < 12:
292 try:
293 await self.client.disconnect()
294 except Exception:
295 pass
296
297 try:
298 self.client = BleakClient(peripheral_id, timeout=10.0) # 添加超时
299 self.client.set_disconnected_callback(self.on_disconnect)
300 await self.client.connect()
301
302 if self.client.is_connected:
303 response = json.dumps({
304 "jsonrpc": "2.0",
305 "result": None,
306 "id": request_id
307 })
308 await log_message("下发", response)
309 if not websocket.closed:
310 await websocket.send(response)
311 else:
312 error_response = json.dumps({
313 "jsonrpc": "2.0",
314 "result": None,
315 "id": request_id if request_id else 0
316 })
317 await log_message("下发", error_response)
318 if not websocket.closed:
319 await websocket.send(error_response)
320 except Exception as e:
321 error_response = json.dumps({ 453 error_response = json.dumps({
322 "jsonrpc": "2.0", 454 "jsonrpc": "2.0",
323 "result": None, 455 "result": None,
...@@ -326,8 +458,46 @@ class BLEClient: ...@@ -326,8 +458,46 @@ class BLEClient:
326 await log_message("下发", error_response) 458 await log_message("下发", error_response)
327 if not websocket.closed: 459 if not websocket.closed:
328 await websocket.send(error_response) 460 await websocket.send(error_response)
329 if self.client: 461 # 检查是否已有连接任务在进行
330 self.client = None 462 elif self._connecting:
463 # 已有连接任务,返回提示
464 response = json.dumps({
465 "jsonrpc": "2.0",
466 "result": None,
467 "id": request_id
468 })
469 await log_message("下发", response)
470 if not websocket.closed:
471 await websocket.send(response)
472 else:
473 # 取消之前的连接任务(如果存在)
474 if self._connect_task and not self._connect_task.done():
475 self._connect_task.cancel()
476
477 # 立即返回"连接中"状态(不等待实际连接完成)
478 response = json.dumps({
479 "jsonrpc": "2.0",
480 "result": {"connecting": True},
481 "id": request_id
482 })
483 await log_message("下发", response)
484 if not websocket.closed:
485 await websocket.send(response)
486
487 # 在后台启动连接任务(不阻塞)
488 self._connect_task = asyncio.create_task(
489 self._background_connect(peripheral_id, request_id)
490 )
491 else:
492 # 参数错误
493 error_response = json.dumps({
494 "jsonrpc": "2.0",
495 "result": None,
496 "id": request_id if request_id else 0
497 })
498 await log_message("下发", error_response)
499 if not websocket.closed:
500 await websocket.send(error_response)
331 501
332 elif method == "write": 502 elif method == "write":
333 service_id = params.get("serviceId") 503 service_id = params.get("serviceId")
...@@ -516,6 +686,15 @@ class BLEClient: ...@@ -516,6 +686,15 @@ class BLEClient:
516 finally: 686 finally:
517 # 清理BLE连接和通知 687 # 清理BLE连接和通知
518 self._shutdown = True 688 self._shutdown = True
689 self._connecting = False
690
691 # 取消后台连接任务
692 if self._connect_task and not self._connect_task.done():
693 try:
694 self._connect_task.cancel()
695 except Exception:
696 pass
697
519 if self.client and self.client.is_connected: 698 if self.client and self.client.is_connected:
520 try: 699 try:
521 # 停止所有通知 700 # 停止所有通知
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!