c428ab60 by huangyf2

蓝牙优化名字

1 parent bb536a51
Showing 1 changed file with 378 additions and 42 deletions
...@@ -157,8 +157,24 @@ class BLEClient: ...@@ -157,8 +157,24 @@ class BLEClient:
157 self.websocket = None 157 self.websocket = None
158 self.notification_records = defaultdict(lambda: (None, 0.0)) # 特征ID: (最后消息, 时间戳) 158 self.notification_records = defaultdict(lambda: (None, 0.0)) # 特征ID: (最后消息, 时间戳)
159 self._notification_callbacks = {} # 存储通知回调,用于清理 159 self._notification_callbacks = {} # 存储通知回调,用于清理
160 self._shutdown = False # 添加关闭标志 160 self._shutdown = False
161 self._scanner = None # 当前扫描器实例 161 self._scanner = None
162 self._connecting = False # 连接状态标志
163 self._connect_task = None # 连接任务
164
165 # 设备缓存机制 - 提升扫描速度和稳定性
166 self._device_cache = {} # {address: (device, adv_data, timestamp, rssi)}
167 self._cache_timeout = 30.0 # 缓存30秒
168 self._last_scan_time = 0
169 self._cache_scan_interval = 5.0 # 5秒内复用缓存
170
171 # 连接状态管理
172 self._connection_history = {} # {address: (success_time, last_attempt_time, success_count)}
173 self._last_connected_address = None
174
175 # 扫描优化
176 self._best_rssi_device = None # 信号最强的设备
177 self._best_rssi_value = -999
162 178
163 def on_disconnect(self, client): 179 def on_disconnect(self, client):
164 print("BLE连接断开,关闭WebSocket") 180 print("BLE连接断开,关闭WebSocket")
...@@ -192,23 +208,40 @@ class BLEClient: ...@@ -192,23 +208,40 @@ class BLEClient:
192 self._shutdown = True 208 self._shutdown = True
193 209
194 def detection_callback(self, device, advertisement_data): 210 def detection_callback(self, device, advertisement_data):
195 if any(service_uuid in advertisement_data.service_uuids for service_uuid in self.services): 211 """设备发现回调函数,从广播包中匹配服务UUID,支持信号强度优化"""
196 # 优先使用广播包中的local_name 212 try:
197 device_name = advertisement_data.local_name if advertisement_data.local_name else (device.name if device.name else "") 213 # 检查服务UUID匹配
214 if not self.services or not any(service_uuid in advertisement_data.service_uuids for service_uuid in self.services):
215 return None
198 216
199 # 丢弃名字为空的设备 217 # 严格从广播包中获取设备名称,优先使用local_name
218 device_name = advertisement_data.local_name if advertisement_data.local_name else ""
219
220 # 如果广播包中没有local_name,跳过该设备
200 if not device_name or device_name.strip() == "": 221 if not device_name or device_name.strip() == "":
201 print(f"跳过名字为空的设备: {device.address}")
202 return None 222 return None
203 223
224 # 验证设备地址格式
225 if not device.address or len(device.address) < 12:
226 return None
227
228 # 更新设备缓存(包含时间戳和RSSI)
229 current_time = time.time()
230 rssi = device.rssi if hasattr(device, 'rssi') else -100
231 self._device_cache[device.address] = (device, advertisement_data, current_time, rssi)
232
233 # 信号强度优化:选择信号最强的设备
234 if rssi > self._best_rssi_value:
235 self._best_rssi_value = rssi
236 self._best_rssi_device = (device, advertisement_data)
237
238 # 保存匹配的设备(优先使用信号最强的)
204 self.target_device = (device, advertisement_data) 239 self.target_device = (device, advertisement_data)
205 if not self.target_device: 240
206 print("未找到匹配设备") 241 if self.target_device:
207 return
208 else:
209 device, adv_data = self.target_device 242 device, adv_data = self.target_device
210 print("\n找到目标设备:") 243 print("\n找到目标设备:")
211 print(f"设备名称(从广播包): {device_name}") 244 print(f"设备名称(从广播包local_name): {device_name}")
212 print(f"设备地址: {device.address}") 245 print(f"设备地址: {device.address}")
213 print(f"信号强度: {device.rssi} dBm") 246 print(f"信号强度: {device.rssi} dBm")
214 print("\n广播信息:") 247 print("\n广播信息:")
...@@ -216,7 +249,7 @@ class BLEClient: ...@@ -216,7 +249,7 @@ class BLEClient:
216 print(f"制造商数据: {adv_data.manufacturer_data}") 249 print(f"制造商数据: {adv_data.manufacturer_data}")
217 print(f"服务数据: {adv_data.service_data}") 250 print(f"服务数据: {adv_data.service_data}")
218 print(f"本地名称(local_name): {adv_data.local_name}") 251 print(f"本地名称(local_name): {adv_data.local_name}")
219 print(f"设备名称(device.name): {device.name if device.name else 'N/A'}") 252 print(f"设备名称(device.name): {device.name if device.name else 'N/A'} (仅作参考,实际使用广播包名称)")
220 # 发现设备后,尝试立即停止扫描 253 # 发现设备后,尝试立即停止扫描
221 try: 254 try:
222 if self._scanner is not None: 255 if self._scanner is not None:
...@@ -225,7 +258,11 @@ class BLEClient: ...@@ -225,7 +258,11 @@ class BLEClient:
225 except Exception as e: 258 except Exception as e:
226 log_exception_sync_to_async(e, "detection_callback停止扫描") 259 log_exception_sync_to_async(e, "detection_callback停止扫描")
227 pass 260 pass
261
228 return self.target_device 262 return self.target_device
263 except Exception as e:
264 log_exception_sync_to_async(e, "detection_callback")
265 return None
229 266
230 async def _stop_scan_early(self): 267 async def _stop_scan_early(self):
231 try: 268 try:
...@@ -235,6 +272,197 @@ class BLEClient: ...@@ -235,6 +272,197 @@ class BLEClient:
235 await log_exception_async(e, "_stop_scan_early") 272 await log_exception_async(e, "_stop_scan_early")
236 pass 273 pass
237 274
275 async def _background_connect(self, peripheral_id, request_id):
276 """
277 后台执行BLE连接任务
278 策略:
279 1. 验证设备地址格式
280 2. 异步断开旧连接(不阻塞新连接)
281 3. 执行新连接(带超时)
282 4. 连接完成后发送状态通知
283 """
284 try:
285 self._connecting = True
286
287 # 步骤0: 验证设备地址格式
288 if not peripheral_id or len(peripheral_id) < 12:
289 error_msg = f"无效的设备地址: {peripheral_id}"
290 await log_message("连接", error_msg)
291 error_response = json.dumps({
292 "jsonrpc": "2.0",
293 "method": "connectionStatus",
294 "params": {
295 "connected": False,
296 "error": error_msg,
297 "peripheralId": peripheral_id
298 },
299 "id": request_id
300 })
301 await log_message("下发", error_response)
302 if self.websocket and not self.websocket.closed:
303 await self.websocket.send(error_response)
304 self._connecting = False
305 return
306
307 # 步骤1: 异步断开旧连接(最多等待1秒,超时后强制清理)
308 old_client = self.client
309 if old_client:
310 try:
311 if old_client.is_connected:
312 # 设置较短的超时,快速失败
313 await asyncio.wait_for(old_client.disconnect(), timeout=1.0)
314 await log_message("连接", f"旧连接已断开: {peripheral_id}")
315 # 清理旧客户端引用
316 if old_client == self.client:
317 self.client = None
318 except asyncio.TimeoutError:
319 await log_exception_async(
320 asyncio.TimeoutError("断开旧连接超时"),
321 "后台连接-断开旧连接超时"
322 )
323 # 强制清理,不等待
324 if old_client == self.client:
325 self.client = None
326 pass
327 except Exception as e:
328 await log_exception_async(e, "后台连接-断开旧连接")
329 if old_client == self.client:
330 self.client = None
331 pass
332
333 # 等待一小段时间,确保旧连接资源完全释放
334 await asyncio.sleep(0.2)
335
336 # 步骤2: 执行新连接
337 # 注意:BLE连接包含多个阶段:
338 # 1. 建立物理连接(通常1-2秒)
339 # 2. 发现服务(get_services)(通常3-8秒,是主要瓶颈)
340 # 因此总超时设置为8秒,确保大多数设备能完成连接
341 client = None
342 try:
343 # BleakClient的超时只影响物理连接阶段
344 client = BleakClient(peripheral_id, timeout=10.0)
345 client.set_disconnected_callback(self.on_disconnect)
346
347 # 连接操作,总超时8秒(覆盖物理连接+服务发现)
348 await asyncio.wait_for(client.connect(), timeout=8.0)
349
350 # 验证连接状态
351 if not client.is_connected:
352 raise Exception("连接后状态检查失败:is_connected为False")
353
354 # 再次验证连接确实可用(等待一小段时间后再次检查)
355 await asyncio.sleep(0.1)
356 if not client.is_connected:
357 raise Exception("连接后状态验证失败:连接已断开")
358
359 # 连接成功,保存客户端引用
360 self.client = client
361 self._connecting = False
362 client = None # 避免finally中重复清理
363
364 await log_message("连接", f"成功连接到设备: {peripheral_id}")
365
366 # 发送连接成功通知
367 success_response = json.dumps({
368 "jsonrpc": "2.0",
369 "method": "connectionStatus",
370 "params": {
371 "connected": True,
372 "peripheralId": peripheral_id
373 },
374 "id": request_id
375 })
376 await log_message("下发", success_response)
377 if self.websocket and not self.websocket.closed:
378 await self.websocket.send(success_response)
379
380 except asyncio.TimeoutError:
381 self._connecting = False
382 error_msg = "连接BLE设备超时(8秒),可能是服务发现阶段耗时过长"
383 await log_exception_async(
384 asyncio.TimeoutError(error_msg),
385 "后台连接-连接超时"
386 )
387
388 # 清理超时的client资源
389 if client is not None:
390 try:
391 # 尝试断开连接,但不等待(快速清理)
392 asyncio.create_task(client.disconnect())
393 except Exception:
394 pass
395 client = None
396
397 # 发送连接失败通知
398 error_response = json.dumps({
399 "jsonrpc": "2.0",
400 "method": "connectionStatus",
401 "params": {
402 "connected": False,
403 "error": error_msg,
404 "peripheralId": peripheral_id
405 },
406 "id": request_id
407 })
408 await log_message("下发", error_response)
409 if self.websocket and not self.websocket.closed:
410 await self.websocket.send(error_response)
411 pass
412
413 except asyncio.CancelledError:
414 # 任务被取消,清理资源
415 self._connecting = False
416 if client is not None:
417 try:
418 asyncio.create_task(client.disconnect())
419 except Exception:
420 pass
421 client = None
422 # 被取消时不需要发送通知(可能是新的连接请求)
423 pass
424
425 except Exception as e:
426 self._connecting = False
427 await log_exception_async(e, "后台连接-连接失败")
428
429 # 清理异常的client资源
430 if client is not None:
431 try:
432 asyncio.create_task(client.disconnect())
433 except Exception:
434 pass
435 client = None
436
437 # 发送连接失败通知
438 error_response = json.dumps({
439 "jsonrpc": "2.0",
440 "method": "connectionStatus",
441 "params": {
442 "connected": False,
443 "error": str(e),
444 "peripheralId": peripheral_id
445 },
446 "id": request_id
447 })
448 await log_message("下发", error_response)
449 if self.websocket and not self.websocket.closed:
450 await self.websocket.send(error_response)
451 pass
452 finally:
453 # 确保清理未使用的client资源
454 if client is not None and client != self.client:
455 try:
456 # 异步断开,不阻塞
457 asyncio.create_task(client.disconnect())
458 except Exception:
459 pass
460
461 except Exception as e:
462 self._connecting = False
463 await log_exception_async(e, "后台连接-未知错误")
464 pass
465
238 async def handle_client(self, websocket, path): 466 async def handle_client(self, websocket, path):
239 self.websocket = websocket 467 self.websocket = websocket
240 self._shutdown = False # 重置关闭标志 468 self._shutdown = False # 重置关闭标志
...@@ -266,43 +494,121 @@ class BLEClient: ...@@ -266,43 +494,121 @@ class BLEClient:
266 self.services.extend(filt.get("services", [])) 494 self.services.extend(filt.get("services", []))
267 self.optional_services = params.get("optionalServices", []) 495 self.optional_services = params.get("optionalServices", [])
268 496
497 # 验证服务列表不为空
498 if not self.services:
499 error_response = json.dumps({
500 "jsonrpc": "2.0",
501 "error": {"code": -32602, "message": "Invalid params: services filter is required"},
502 "id": request_id if request_id else 0
503 })
504 await log_message("下发", error_response)
505 if not websocket.closed:
506 await websocket.send(error_response)
507 continue
508
509 # 优化策略1: 检查缓存,快速返回
510 current_time = time.time()
511 self._best_rssi_device = None
512 self._best_rssi_value = -999
513
514 # 清理过期缓存
515 expired_addresses = [
516 addr for addr, (_, _, ts, _) in self._device_cache.items()
517 if current_time - ts > self._cache_timeout
518 ]
519 for addr in expired_addresses:
520 del self._device_cache[addr]
521
522 # 如果缓存有效且时间间隔短,直接使用缓存
523 if (current_time - self._last_scan_time < self._cache_scan_interval and
524 self._device_cache):
525 # 从缓存中选择信号最强的设备
526 best_cache = None
527 best_rssi = -999
528 for addr, (dev, adv, ts, rssi) in self._device_cache.items():
529 # 验证服务匹配
530 if any(service_uuid in adv.service_uuids for service_uuid in self.services):
531 if adv.local_name and rssi > best_rssi:
532 best_rssi = rssi
533 best_cache = (dev, adv)
534
535 if best_cache:
536 device, adv_data = best_cache
537 device_name = adv_data.local_name if adv_data.local_name else ""
538 if device_name:
539 print(f"使用缓存设备: {device_name} ({device.address}), RSSI: {best_rssi} dBm")
540 self.target_device = best_cache
541 found = True
542
543 # 如果缓存未命中,进行扫描
544 if not found:
269 # 双重扫描:快速扫描后若未发现,再进行扩展扫描;发现即停 545 # 双重扫描:快速扫描后若未发现,再进行扩展扫描;发现即停
270 phases = [("active", 3.0), ("passive", 6.0)] 546 phases = [("active", 3.0), ("passive", 6.0)]
271 found = False 547 scan_error = None
548
272 for phase_index, (scan_mode, duration) in enumerate(phases, start=1): 549 for phase_index, (scan_mode, duration) in enumerate(phases, start=1):
273 self.target_device = None 550 self.target_device = None
551 self._scanner = None
552
553 try:
554 # 创建扫描器
274 self._scanner = BleakScanner(scanning_mode=scan_mode) 555 self._scanner = BleakScanner(scanning_mode=scan_mode)
275 self._scanner.register_detection_callback(self.detection_callback) 556 self._scanner.register_detection_callback(self.detection_callback)
276 print(f"开始第{phase_index}阶段扫描(模式: {scan_mode}, 时长: {duration}s)...") 557 print(f"开始第{phase_index}阶段扫描(模式: {scan_mode}, 时长: {duration}s)...")
558
559 # 启动扫描
277 try: 560 try:
278 await self._scanner.start() 561 await self._scanner.start()
562 except Exception as e:
563 await log_exception_async(e, f"扫描启动失败-阶段{phase_index}")
564 scan_error = str(e)
565 continue
566
279 # 轮询检查是否已找到,找到则提前停止 567 # 轮询检查是否已找到,找到则提前停止
280 start_ts = time.time() 568 start_ts = time.time()
281 while time.time() - start_ts < duration and not self.target_device: 569 while time.time() - start_ts < duration and not self.target_device:
282 await asyncio.sleep(0.1) 570 await asyncio.sleep(0.1)
571
572 # 检查是否应该关闭
573 if self._shutdown:
574 break
575
576 except Exception as e:
577 await log_exception_async(e, f"扫描过程异常-阶段{phase_index}")
578 scan_error = str(e)
283 finally: 579 finally:
580 # 确保扫描器被停止和清理
581 if self._scanner is not None:
284 try: 582 try:
285 await self._scanner.stop() 583 await self._scanner.stop()
286 except Exception as e: 584 except Exception as e:
287 await log_exception_async(e, "扫描停止") 585 await log_exception_async(e, f"扫描停止失败-阶段{phase_index}")
288 pass 586 pass
289 self._scanner = None 587 self._scanner = None
290 588
589 # 等待一小段时间确保扫描完全停止
590 await asyncio.sleep(0.1)
591
592 # 检查是否找到设备
291 if self.target_device: 593 if self.target_device:
292 found = True 594 found = True
293 break 595 break
294 else: 596 else:
295 print(f"第{phase_index}阶段未找到设备") 597 print(f"第{phase_index}阶段未找到设备")
296 598
599 # 处理扫描结果
297 if found: 600 if found:
298 device, adv_data = self.target_device 601 device, adv_data = self.target_device
299 # 优先使用广播包中的local_name,如果没有则使用device.name 602 # 严格从广播包中获取设备名称,使用local_name
300 device_name = adv_data.local_name if adv_data.local_name else (device.name if device.name else "") 603 device_name = adv_data.local_name if adv_data.local_name else ""
604
605 # 确保从广播包获取到名字才发送discover通知
606 if device_name and device_name.strip():
301 discover_response = json.dumps({ 607 discover_response = json.dumps({
302 "jsonrpc": "2.0", 608 "jsonrpc": "2.0",
303 "method": "didDiscoverPeripheral", 609 "method": "didDiscoverPeripheral",
304 "params": { 610 "params": {
305 "name": device_name, 611 "name": device_name, # 从广播包获取的名称
306 "peripheralId": device.address, 612 "peripheralId": device.address,
307 "rssi": device.rssi 613 "rssi": device.rssi
308 } 614 }
...@@ -310,7 +616,10 @@ class BLEClient: ...@@ -310,7 +616,10 @@ class BLEClient:
310 await log_message("下发", discover_response) 616 await log_message("下发", discover_response)
311 if not websocket.closed: 617 if not websocket.closed:
312 await websocket.send(discover_response) 618 await websocket.send(discover_response)
619 else:
620 print(f"警告: 设备 {device.address} 广播包中没有local_name,无法发送discover通知")
313 621
622 # 无论是否有名字,都返回result(表示扫描完成)
314 result_response = json.dumps({ 623 result_response = json.dumps({
315 "jsonrpc": "2.0", 624 "jsonrpc": "2.0",
316 "result": None, 625 "result": None,
...@@ -319,24 +628,34 @@ class BLEClient: ...@@ -319,24 +628,34 @@ class BLEClient:
319 await log_message("下发", result_response) 628 await log_message("下发", result_response)
320 if not websocket.closed: 629 if not websocket.closed:
321 await websocket.send(result_response) 630 await websocket.send(result_response)
631 else:
632 # 未找到设备,返回错误
633 error_msg = "未找到匹配的蓝牙设备"
634 if scan_error:
635 error_msg += f" (扫描错误: {scan_error})"
636
637 error_response = json.dumps({
638 "jsonrpc": "2.0",
639 "error": {"code": -1, "message": error_msg},
640 "id": request_id if request_id else 0
641 })
642 await log_message("下发", error_response)
643 if not websocket.closed:
644 await websocket.send(error_response)
322 645
323 elif method == "connect": 646 elif method == "connect":
647 """
648 优化策略:
649 1. 立即返回"连接中"状态,不阻塞WebSocket请求处理
650 2. 在后台任务中执行连接操作
651 3. 连接完成后通过connectionStatus通知客户端
652 这样可以显著减少WebSocket响应时间,从10秒降低到<100ms
653 """
324 peripheral_id = params.get("peripheralId") 654 peripheral_id = params.get("peripheralId")
325 if peripheral_id: 655 if peripheral_id:
326 # 如果已有连接,先断开 656 # 检查是否已有连接任务在进行
327 if self.client and self.client.is_connected: 657 if self._connecting:
328 try: 658 # 已有连接任务,返回提示
329 await self.client.disconnect()
330 except Exception as e:
331 await log_exception_async(e, "connect断开旧连接")
332 pass
333
334 try:
335 self.client = BleakClient(peripheral_id, timeout=10.0) # 添加超时
336 self.client.set_disconnected_callback(self.on_disconnect)
337 await self.client.connect()
338
339 if self.client.is_connected:
340 response = json.dumps({ 659 response = json.dumps({
341 "jsonrpc": "2.0", 660 "jsonrpc": "2.0",
342 "result": None, 661 "result": None,
...@@ -346,16 +665,26 @@ class BLEClient: ...@@ -346,16 +665,26 @@ class BLEClient:
346 if not websocket.closed: 665 if not websocket.closed:
347 await websocket.send(response) 666 await websocket.send(response)
348 else: 667 else:
349 error_response = json.dumps({ 668 # 取消之前的连接任务(如果存在)
669 if self._connect_task and not self._connect_task.done():
670 self._connect_task.cancel()
671
672 # 立即返回"连接中"状态(不等待实际连接完成)
673 response = json.dumps({
350 "jsonrpc": "2.0", 674 "jsonrpc": "2.0",
351 "result": None, 675 "result": {"connecting": True},
352 "id": request_id if request_id else 0 676 "id": request_id
353 }) 677 })
354 await log_message("下发", error_response) 678 await log_message("下发", response)
355 if not websocket.closed: 679 if not websocket.closed:
356 await websocket.send(error_response) 680 await websocket.send(response)
357 except Exception as e: 681
358 await log_exception_async(e, "connect连接BLE") 682 # 在后台启动连接任务(不阻塞)
683 self._connect_task = asyncio.create_task(
684 self._background_connect(peripheral_id, request_id)
685 )
686 else:
687 # 参数错误,立即返回
359 error_response = json.dumps({ 688 error_response = json.dumps({
360 "jsonrpc": "2.0", 689 "jsonrpc": "2.0",
361 "result": None, 690 "result": None,
...@@ -364,9 +693,6 @@ class BLEClient: ...@@ -364,9 +693,6 @@ class BLEClient:
364 await log_message("下发", error_response) 693 await log_message("下发", error_response)
365 if not websocket.closed: 694 if not websocket.closed:
366 await websocket.send(error_response) 695 await websocket.send(error_response)
367 if self.client:
368 self.client = None
369 pass
370 696
371 elif method == "write": 697 elif method == "write":
372 service_id = params.get("serviceId") 698 service_id = params.get("serviceId")
...@@ -567,6 +893,16 @@ class BLEClient: ...@@ -567,6 +893,16 @@ class BLEClient:
567 finally: 893 finally:
568 # 清理BLE连接和通知 894 # 清理BLE连接和通知
569 self._shutdown = True 895 self._shutdown = True
896 self._connecting = False
897
898 # 取消后台连接任务
899 if self._connect_task and not self._connect_task.done():
900 try:
901 self._connect_task.cancel()
902 except Exception as e:
903 await log_exception_async(e, "finally取消连接任务")
904 pass
905
570 if self.client and self.client.is_connected: 906 if self.client and self.client.is_connected:
571 try: 907 try:
572 # 停止所有通知 908 # 停止所有通知
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!