11 优化蓝牙扫描
Showing
1 changed file
with
212 additions
and
33 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 |
| 369 | self._scanner = None | ||
| 370 | |||
| 371 | try: | ||
| 372 | # 创建扫描器 | ||
| 239 | self._scanner = BleakScanner(scanning_mode=scan_mode) | 373 | self._scanner = BleakScanner(scanning_mode=scan_mode) |
| 240 | self._scanner.register_detection_callback(self.detection_callback) | 374 | self._scanner.register_detection_callback(self.detection_callback) |
| 241 | print(f"开始第{phase_index}阶段扫描(模式: {scan_mode}, 时长: {duration}s)...") | 375 | print(f"开始第{phase_index}阶段扫描(模式: {scan_mode}, 时长: {duration}s)...") |
| 376 | |||
| 377 | # 启动扫描 | ||
| 242 | try: | 378 | try: |
| 243 | await self._scanner.start() | 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: |
| 398 | # 确保扫描器被停止和清理 | ||
| 399 | if self._scanner is not None: | ||
| 249 | try: | 400 | try: |
| 250 | await self._scanner.stop() | 401 | await self._scanner.stop() |
| 251 | except Exception: | 402 | except Exception as e: |
| 252 | pass | 403 | print(f"扫描停止失败-阶段{phase_index}: {e}") |
| 253 | self._scanner = None | 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,21 +440,27 @@ class BLEClient: | ... | @@ -285,21 +440,27 @@ 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: | 453 | error_response = json.dumps({ |
| 293 | await self.client.disconnect() | 454 | "jsonrpc": "2.0", |
| 294 | except Exception: | 455 | "result": None, |
| 295 | pass | 456 | "id": request_id if request_id else 0 |
| 296 | 457 | }) | |
| 297 | try: | 458 | await log_message("下发", error_response) |
| 298 | self.client = BleakClient(peripheral_id, timeout=10.0) # 添加超时 | 459 | if not websocket.closed: |
| 299 | self.client.set_disconnected_callback(self.on_disconnect) | 460 | await websocket.send(error_response) |
| 300 | await self.client.connect() | 461 | # 检查是否已有连接任务在进行 |
| 301 | 462 | elif self._connecting: | |
| 302 | if self.client.is_connected: | 463 | # 已有连接任务,返回提示 |
| 303 | response = json.dumps({ | 464 | response = json.dumps({ |
| 304 | "jsonrpc": "2.0", | 465 | "jsonrpc": "2.0", |
| 305 | "result": None, | 466 | "result": None, |
| ... | @@ -309,15 +470,26 @@ class BLEClient: | ... | @@ -309,15 +470,26 @@ class BLEClient: |
| 309 | if not websocket.closed: | 470 | if not websocket.closed: |
| 310 | await websocket.send(response) | 471 | await websocket.send(response) |
| 311 | else: | 472 | else: |
| 312 | error_response = json.dumps({ | 473 | # 取消之前的连接任务(如果存在) |
| 474 | if self._connect_task and not self._connect_task.done(): | ||
| 475 | self._connect_task.cancel() | ||
| 476 | |||
| 477 | # 立即返回"连接中"状态(不等待实际连接完成) | ||
| 478 | response = json.dumps({ | ||
| 313 | "jsonrpc": "2.0", | 479 | "jsonrpc": "2.0", |
| 314 | "result": None, | 480 | "result": {"connecting": True}, |
| 315 | "id": request_id if request_id else 0 | 481 | "id": request_id |
| 316 | }) | 482 | }) |
| 317 | await log_message("下发", error_response) | 483 | await log_message("下发", response) |
| 318 | if not websocket.closed: | 484 | if not websocket.closed: |
| 319 | await websocket.send(error_response) | 485 | await websocket.send(response) |
| 320 | except Exception as e: | 486 | |
| 487 | # 在后台启动连接任务(不阻塞) | ||
| 488 | self._connect_task = asyncio.create_task( | ||
| 489 | self._background_connect(peripheral_id, request_id) | ||
| 490 | ) | ||
| 491 | else: | ||
| 492 | # 参数错误 | ||
| 321 | error_response = json.dumps({ | 493 | error_response = json.dumps({ |
| 322 | "jsonrpc": "2.0", | 494 | "jsonrpc": "2.0", |
| 323 | "result": None, | 495 | "result": None, |
| ... | @@ -326,8 +498,6 @@ class BLEClient: | ... | @@ -326,8 +498,6 @@ class BLEClient: |
| 326 | await log_message("下发", error_response) | 498 | await log_message("下发", error_response) |
| 327 | if not websocket.closed: | 499 | if not websocket.closed: |
| 328 | await websocket.send(error_response) | 500 | await websocket.send(error_response) |
| 329 | if self.client: | ||
| 330 | self.client = None | ||
| 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 | # 停止所有通知 | ... | ... |
-
Please register or sign in to post a comment