6.8.2.py
Showing
5 changed files
with
2270 additions
and
1 deletions
s6.8.2.py
0 → 100644
| 1 | import asyncio | ||
| 2 | import websockets | ||
| 3 | from bleak import BleakScanner, BleakClient | ||
| 4 | import json | ||
| 5 | import base64 | ||
| 6 | import threading | ||
| 7 | from collections import defaultdict | ||
| 8 | |||
| 9 | # 添加线程锁以确保日志写入的原子性 | ||
| 10 | write_lock = threading.Lock() | ||
| 11 | |||
| 12 | def log_message_sync(direction, message): | ||
| 13 | """同步日志记录函数""" | ||
| 14 | log_entry = f"{direction}: {message}\n" | ||
| 15 | print(log_entry, end='') # 控制台仍然输出 | ||
| 16 | with write_lock: | ||
| 17 | with open('b.log', 'a', encoding='utf-8') as f: | ||
| 18 | f.write(log_entry) | ||
| 19 | |||
| 20 | async def log_message(direction, message): | ||
| 21 | """异步封装日志记录""" | ||
| 22 | loop = asyncio.get_event_loop() | ||
| 23 | await loop.run_in_executor(None, log_message_sync, direction, message) | ||
| 24 | |||
| 25 | class BLEClient: | ||
| 26 | def __init__(self): | ||
| 27 | self.target_device = None | ||
| 28 | self.client = None | ||
| 29 | self.services = [] | ||
| 30 | self.optional_services = [] | ||
| 31 | self.websocket = None | ||
| 32 | self.notification_records = defaultdict(lambda: (None, 0)) # 特征ID: (最后消息, 时间戳) | ||
| 33 | |||
| 34 | def on_disconnect(self, client): | ||
| 35 | print("BLE连接断开,关闭WebSocket") | ||
| 36 | if self.websocket and not self.websocket.closed: | ||
| 37 | asyncio.create_task(self.close_websocket()) | ||
| 38 | |||
| 39 | async def close_websocket(self): | ||
| 40 | await self.websocket.close() | ||
| 41 | self.websocket = None | ||
| 42 | |||
| 43 | def detection_callback(self, device, advertisement_data): | ||
| 44 | if any(service_uuid in advertisement_data.service_uuids for service_uuid in self.services): | ||
| 45 | self.target_device = (device, advertisement_data) | ||
| 46 | if not self.target_device: | ||
| 47 | print("未找到匹配设备") | ||
| 48 | return | ||
| 49 | else: | ||
| 50 | device, adv_data = self.target_device | ||
| 51 | print("\n找到目标设备:") | ||
| 52 | print(f"设备名称: {device.name}") | ||
| 53 | print(f"设备地址: {device.address}") | ||
| 54 | print(f"信号强度: {device.rssi} dBm") | ||
| 55 | print("\n广播信息:") | ||
| 56 | print(f"服务UUID列表: {adv_data.service_uuids}") | ||
| 57 | print(f"制造商数据: {adv_data.manufacturer_data}") | ||
| 58 | print(f"服务数据: {adv_data.service_data}") | ||
| 59 | print(f"本地名称: {adv_data.local_name}") | ||
| 60 | return self.target_device | ||
| 61 | |||
| 62 | async def handle_client(self, websocket, path): | ||
| 63 | self.websocket = websocket | ||
| 64 | if path != "/scratch/ble": | ||
| 65 | await websocket.close(code=1003, reason="Path not allowed") | ||
| 66 | return | ||
| 67 | |||
| 68 | try: | ||
| 69 | async for message in websocket: | ||
| 70 | try: | ||
| 71 | await log_message("接收", message) | ||
| 72 | request = json.loads(message) | ||
| 73 | |||
| 74 | if request["jsonrpc"] != "2.0": | ||
| 75 | continue | ||
| 76 | |||
| 77 | method = request.get("method") | ||
| 78 | params = request.get("params", {}) | ||
| 79 | request_id = request.get("id") | ||
| 80 | |||
| 81 | if method == "discover": | ||
| 82 | self.services = [] | ||
| 83 | for filt in params.get("filters", [{}]): | ||
| 84 | self.services.extend(filt.get("services", [])) | ||
| 85 | self.optional_services = params.get("optionalServices", []) | ||
| 86 | |||
| 87 | scanner = BleakScanner() | ||
| 88 | scanner.register_detection_callback(self.detection_callback) | ||
| 89 | |||
| 90 | max_retries = 3 | ||
| 91 | found = False | ||
| 92 | for attempt in range(max_retries): | ||
| 93 | self.target_device = None | ||
| 94 | await scanner.start() | ||
| 95 | await asyncio.sleep(5) | ||
| 96 | await scanner.stop() | ||
| 97 | |||
| 98 | if self.target_device: | ||
| 99 | found = True | ||
| 100 | break | ||
| 101 | |||
| 102 | if attempt < max_retries - 1: | ||
| 103 | print(f"未找到设备,第{attempt+1}次重试...") | ||
| 104 | await asyncio.sleep(3) | ||
| 105 | |||
| 106 | if found: | ||
| 107 | device, adv_data = self.target_device | ||
| 108 | discover_response = json.dumps({ | ||
| 109 | "jsonrpc": "2.0", | ||
| 110 | "method": "didDiscoverPeripheral", | ||
| 111 | "params": { | ||
| 112 | "name": device.name, | ||
| 113 | "peripheralId": device.address, | ||
| 114 | "rssi": device.rssi | ||
| 115 | } | ||
| 116 | }) | ||
| 117 | await log_message("下发", discover_response) | ||
| 118 | await websocket.send(discover_response) | ||
| 119 | |||
| 120 | result_response = json.dumps({ | ||
| 121 | "jsonrpc": "2.0", | ||
| 122 | "result": None, | ||
| 123 | "id": request_id | ||
| 124 | }) | ||
| 125 | await log_message("下发", result_response) | ||
| 126 | await websocket.send(result_response) | ||
| 127 | |||
| 128 | elif method == "connect": | ||
| 129 | peripheral_id = params.get("peripheralId") | ||
| 130 | if peripheral_id: | ||
| 131 | self.client = BleakClient(peripheral_id) | ||
| 132 | self.client.set_disconnected_callback(self.on_disconnect) | ||
| 133 | await self.client.connect() | ||
| 134 | |||
| 135 | if self.client.is_connected: | ||
| 136 | response = json.dumps({ | ||
| 137 | "jsonrpc": "2.0", | ||
| 138 | "result": None, | ||
| 139 | "id": request_id | ||
| 140 | }) | ||
| 141 | await log_message("下发", response) | ||
| 142 | await websocket.send(response) | ||
| 143 | |||
| 144 | elif method == "write": | ||
| 145 | service_id = params.get("serviceId") | ||
| 146 | characteristic_id = params.get("characteristicId") | ||
| 147 | message = params.get("message") | ||
| 148 | encoding = params.get("encoding", "utf-8") | ||
| 149 | |||
| 150 | if all([service_id, characteristic_id, message]): | ||
| 151 | if encoding == "base64": | ||
| 152 | message_bytes = base64.b64decode(message) | ||
| 153 | else: | ||
| 154 | message_bytes = message.encode(encoding) | ||
| 155 | if "withResponse" in params: | ||
| 156 | response=params.get("withResponse") | ||
| 157 | await self.client.write_gatt_char(characteristic_id, message_bytes,response=False) | ||
| 158 | else: | ||
| 159 | await self.client.write_gatt_char(characteristic_id, message_bytes) | ||
| 160 | |||
| 161 | # await self.client.write_gatt_char(characteristic_id, message_bytes) | ||
| 162 | response = json.dumps({ | ||
| 163 | "jsonrpc": "2.0", | ||
| 164 | "result": None, | ||
| 165 | "id": request_id | ||
| 166 | }) | ||
| 167 | await log_message("下发", response) | ||
| 168 | await websocket.send(response) | ||
| 169 | |||
| 170 | elif method == "read": | ||
| 171 | service_id = params.get("serviceId") | ||
| 172 | characteristic_id = params.get("characteristicId") | ||
| 173 | |||
| 174 | if all([service_id, characteristic_id]): | ||
| 175 | data = await self.client.read_gatt_char(characteristic_id) | ||
| 176 | response = json.dumps({ | ||
| 177 | "jsonrpc": "2.0", | ||
| 178 | "result": { | ||
| 179 | "serviceId": service_id, | ||
| 180 | "characteristicId": characteristic_id, | ||
| 181 | "message": base64.b64encode(data).decode("utf-8") | ||
| 182 | }, | ||
| 183 | "id": request_id | ||
| 184 | }) | ||
| 185 | await log_message("下发", response) | ||
| 186 | await websocket.send(response) | ||
| 187 | |||
| 188 | elif method == "startNotifications": | ||
| 189 | service_id = params.get("serviceId") | ||
| 190 | characteristic_id = params.get("characteristicId") | ||
| 191 | |||
| 192 | if all([service_id, characteristic_id]): | ||
| 193 | await self.client.start_notify( | ||
| 194 | characteristic_id, | ||
| 195 | self.notification_handler(websocket, service_id, characteristic_id) | ||
| 196 | ) | ||
| 197 | response = json.dumps({ | ||
| 198 | "jsonrpc": "2.0", | ||
| 199 | "result": None, | ||
| 200 | "id": request_id | ||
| 201 | }) | ||
| 202 | await log_message("下发", response) | ||
| 203 | await websocket.send(response) | ||
| 204 | |||
| 205 | except json.JSONDecodeError: | ||
| 206 | error_msg = json.dumps({ | ||
| 207 | "jsonrpc": "2.0", | ||
| 208 | "result": {"message": "Parse error"}, | ||
| 209 | "id": None | ||
| 210 | }) | ||
| 211 | await log_message("下发", error_msg) | ||
| 212 | except Exception as e: | ||
| 213 | error_msg = json.dumps({ | ||
| 214 | "jsonrpc": "2.0", | ||
| 215 | "result": {"message": str(e)}, | ||
| 216 | "id": request.get("id") if request else None | ||
| 217 | }) | ||
| 218 | await log_message("下发", error_msg) | ||
| 219 | |||
| 220 | except websockets.exceptions.ConnectionClosed: | ||
| 221 | print("WebSocket连接关闭") | ||
| 222 | finally: | ||
| 223 | if self.client and self.client.is_connected: | ||
| 224 | await self.client.disconnect() | ||
| 225 | self.client = None | ||
| 226 | self.target_device = None | ||
| 227 | |||
| 228 | def notification_handler(self, websocket, service_id, characteristic_id): | ||
| 229 | async def callback(sender, data): | ||
| 230 | current_time = asyncio.get_event_loop().time() | ||
| 231 | last_message, last_time = self.notification_records[characteristic_id] | ||
| 232 | |||
| 233 | # 解码当前数据用于比较 | ||
| 234 | current_message = base64.b64encode(data).decode('utf-8') | ||
| 235 | |||
| 236 | # 过滤逻辑 | ||
| 237 | if current_message == last_message and (current_time - last_time) < 0.5: | ||
| 238 | return | ||
| 239 | |||
| 240 | # 更新记录 | ||
| 241 | self.notification_records[characteristic_id] = (current_message, current_time) | ||
| 242 | |||
| 243 | response = json.dumps({ | ||
| 244 | "jsonrpc": "2.0", | ||
| 245 | "method": "characteristicDidChange", | ||
| 246 | "params": { | ||
| 247 | "serviceId": service_id, | ||
| 248 | "characteristicId": characteristic_id, | ||
| 249 | "message": current_message | ||
| 250 | } | ||
| 251 | }) | ||
| 252 | await log_message("下发", response) | ||
| 253 | await websocket.send(response) | ||
| 254 | return callback | ||
| 255 | |||
| 256 | async def main(): | ||
| 257 | async with websockets.serve( | ||
| 258 | lambda websocket, path: BLEClient().handle_client(websocket, path), | ||
| 259 | "localhost", 20111 | ||
| 260 | ): | ||
| 261 | print("WebSocket服务已启动: ws://localhost:20111/scratch/ble") | ||
| 262 | print("日志文件路径: ./b.log") | ||
| 263 | await asyncio.Future() | ||
| 264 | |||
| 265 | if __name__ == "__main__": | ||
| 266 | asyncio.run(main()) | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
s6.9.2.1.py
0 → 100644
| 1 | # -*- coding: utf-8 -*- | ||
| 2 | import sys | ||
| 3 | import asyncio | ||
| 4 | import websockets | ||
| 5 | import json | ||
| 6 | import base64 | ||
| 7 | import threading | ||
| 8 | from collections import defaultdict | ||
| 9 | import socket | ||
| 10 | import time | ||
| 11 | import platform | ||
| 12 | from typing import Optional, Dict, List, Callable, Any | ||
| 13 | |||
| 14 | # Windows 蓝牙 API 导入 | ||
| 15 | if sys.platform.startswith('win32'): | ||
| 16 | import winrt.windows.foundation.collections as wfc | ||
| 17 | import winrt.windows.devices.bluetooth as bt | ||
| 18 | import winrt.windows.devices.bluetooth.advertisement as bt_adv | ||
| 19 | import winrt.windows.devices.bluetooth.genericattributeprofile as gatt | ||
| 20 | import winrt.windows.devices.enumeration as de | ||
| 21 | import winrt.windows.storage.streams as streams | ||
| 22 | import winrt.windows.foundation as wf | ||
| 23 | print("当前运行在Windows系统,使用 pywinrt 蓝牙 API") | ||
| 24 | USE_PYWINRT = True | ||
| 25 | else: | ||
| 26 | # 非 Windows 系统回退到 bleak | ||
| 27 | from bleak import BleakScanner, BleakClient | ||
| 28 | print("当前运行在非Windows系统,使用 bleak 蓝牙库") | ||
| 29 | USE_PYWINRT = False | ||
| 30 | |||
| 31 | # 添加线程锁以确保日志写入的原子性 | ||
| 32 | write_lock = threading.Lock() | ||
| 33 | |||
| 34 | def log_message_sync(direction, message): | ||
| 35 | """同步日志记录函数""" | ||
| 36 | log_entry = f"{direction}: {message}\n" | ||
| 37 | print(log_entry, end='') # 控制台仍然输出 | ||
| 38 | with write_lock: | ||
| 39 | with open('b.log', 'a', encoding='utf-8') as f: | ||
| 40 | f.write(log_entry) | ||
| 41 | |||
| 42 | async def log_message(direction, message): | ||
| 43 | """异步封装日志记录""" | ||
| 44 | loop = asyncio.get_event_loop() | ||
| 45 | await loop.run_in_executor(None, log_message_sync, direction, message) | ||
| 46 | |||
| 47 | def is_port_in_use(port, host='localhost'): | ||
| 48 | """检查端口是否被占用""" | ||
| 49 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: | ||
| 50 | try: | ||
| 51 | s.bind((host, port)) | ||
| 52 | return False | ||
| 53 | except socket.error: | ||
| 54 | return True | ||
| 55 | |||
| 56 | async def self_test(websocket): | ||
| 57 | """发送自检测试消息""" | ||
| 58 | test_message = json.dumps({ | ||
| 59 | "jsonrpc": "2.0", | ||
| 60 | "method": "ping", | ||
| 61 | "params": {"timestamp": int(time.time())}, | ||
| 62 | "id": "test" | ||
| 63 | }) | ||
| 64 | await log_message("自测", test_message) | ||
| 65 | await websocket.send(test_message) | ||
| 66 | |||
| 67 | try: | ||
| 68 | # 等待响应,设置超时 | ||
| 69 | response = await asyncio.wait_for(websocket.recv(), timeout=5.0) | ||
| 70 | await log_message("自测响应", response) | ||
| 71 | return True | ||
| 72 | except asyncio.TimeoutError: | ||
| 73 | await log_message("自测", "自测超时,未收到响应") | ||
| 74 | return False | ||
| 75 | except Exception as e: | ||
| 76 | await log_message("自测", f"自测异常: {str(e)}") | ||
| 77 | return False | ||
| 78 | |||
| 79 | class BLEClient: | ||
| 80 | def __init__(self): | ||
| 81 | self.target_device = None | ||
| 82 | self.client = None | ||
| 83 | self.services = [] | ||
| 84 | self.optional_services = [] | ||
| 85 | self.websocket = None | ||
| 86 | self.notification_records = defaultdict(lambda: (None, 0)) # 特征ID: (最后消息, 时间戳) | ||
| 87 | |||
| 88 | # pywinrt 相关属性 | ||
| 89 | self.device_watcher = None | ||
| 90 | self.bluetooth_device = None | ||
| 91 | self.gatt_device_service = None | ||
| 92 | self.characteristics = {} | ||
| 93 | self.notification_handlers = {} | ||
| 94 | |||
| 95 | # 设备发现回调 | ||
| 96 | self.device_found_callback = None | ||
| 97 | self.scan_timeout = 10.0 | ||
| 98 | self.connection_timeout = 10.0 | ||
| 99 | self.operation_timeout = 5.0 | ||
| 100 | |||
| 101 | # 连接状态管理 | ||
| 102 | self.connection_state = { | ||
| 103 | 'status': 'disconnected', # disconnected, connecting, connected, disconnecting, error | ||
| 104 | 'device_id': None, | ||
| 105 | 'device_name': None, | ||
| 106 | 'connection_time': None, | ||
| 107 | 'last_activity': None, | ||
| 108 | 'error_count': 0, | ||
| 109 | 'retry_count': 0, | ||
| 110 | 'max_retries': 3, | ||
| 111 | 'connection_quality': 'unknown', # excellent, good, fair, poor, unknown | ||
| 112 | 'signal_strength': None, | ||
| 113 | 'services_discovered': False, | ||
| 114 | 'characteristics_discovered': False, | ||
| 115 | 'notifications_active': set(), | ||
| 116 | 'connection_history': [], | ||
| 117 | 'error_history': [] | ||
| 118 | } | ||
| 119 | |||
| 120 | # 状态变化回调 | ||
| 121 | self.state_change_callbacks = [] | ||
| 122 | self.connection_monitor_task = None | ||
| 123 | self.auto_reconnect = False | ||
| 124 | self.auto_reconnect_interval = 5.0 | ||
| 125 | |||
| 126 | def on_disconnect(self, client): | ||
| 127 | print("BLE连接断开,关闭WebSocket") | ||
| 128 | if self.websocket and not self.websocket.closed: | ||
| 129 | asyncio.create_task(self.close_websocket()) | ||
| 130 | |||
| 131 | async def close_websocket(self): | ||
| 132 | if self.websocket: | ||
| 133 | await self.websocket.close() | ||
| 134 | self.websocket = None | ||
| 135 | |||
| 136 | async def start_pywinrt_scan(self, target_services: List[str] = None) -> Optional[Dict]: | ||
| 137 | """使用 pywinrt 进行蓝牙设备扫描""" | ||
| 138 | if not USE_PYWINRT: | ||
| 139 | raise RuntimeError("pywinrt 仅在 Windows 系统上可用") | ||
| 140 | |||
| 141 | try: | ||
| 142 | # 创建设备观察器 | ||
| 143 | aqs_filter = "System.Devices.Aep.ProtocolId:=\"{bb7bb05e-5972-42b5-94fc-76eaa7084d49}\"" | ||
| 144 | self.device_watcher = de.DeviceWatcher.create_from_aqs_filter(aqs_filter) | ||
| 145 | |||
| 146 | discovered_devices = [] | ||
| 147 | |||
| 148 | def on_device_added(sender, device_info): | ||
| 149 | try: | ||
| 150 | device_name = device_info.name or "未知设备" | ||
| 151 | device_id = device_info.id | ||
| 152 | device_kind = device_info.kind | ||
| 153 | |||
| 154 | print(f"发现设备: {device_name}, ID: {device_id}") | ||
| 155 | |||
| 156 | # 检查是否包含目标服务 | ||
| 157 | if target_services: | ||
| 158 | # 这里可以添加更复杂的服务过滤逻辑 | ||
| 159 | pass | ||
| 160 | |||
| 161 | discovered_devices.append({ | ||
| 162 | 'name': device_name, | ||
| 163 | 'id': device_id, | ||
| 164 | 'kind': device_kind, | ||
| 165 | 'info': device_info | ||
| 166 | }) | ||
| 167 | |||
| 168 | except Exception as e: | ||
| 169 | print(f"处理设备信息时出错: {e}") | ||
| 170 | |||
| 171 | def on_device_updated(sender, device_info): | ||
| 172 | print(f"设备更新: {device_info.name}") | ||
| 173 | |||
| 174 | def on_device_removed(sender, device_info): | ||
| 175 | print(f"设备移除: {device_info.name}") | ||
| 176 | |||
| 177 | def on_enumeration_completed(sender, args): | ||
| 178 | print("设备枚举完成") | ||
| 179 | |||
| 180 | def on_stopped(sender, args): | ||
| 181 | print("设备扫描停止") | ||
| 182 | |||
| 183 | # 注册事件处理器 | ||
| 184 | self.device_watcher.added += on_device_added | ||
| 185 | self.device_watcher.updated += on_device_updated | ||
| 186 | self.device_watcher.removed += on_device_removed | ||
| 187 | self.device_watcher.enumeration_completed += on_enumeration_completed | ||
| 188 | self.device_watcher.stopped += on_stopped | ||
| 189 | |||
| 190 | # 开始扫描 | ||
| 191 | print("开始扫描蓝牙设备...") | ||
| 192 | self.device_watcher.start() | ||
| 193 | |||
| 194 | # 等待扫描完成或超时 | ||
| 195 | start_time = time.time() | ||
| 196 | while (self.device_watcher.status != de.DeviceWatcherStatus.STOPPED and | ||
| 197 | time.time() - start_time < self.scan_timeout): | ||
| 198 | await asyncio.sleep(0.1) | ||
| 199 | |||
| 200 | # 停止扫描 | ||
| 201 | if self.device_watcher.status == de.DeviceWatcherStatus.STARTED: | ||
| 202 | self.device_watcher.stop() | ||
| 203 | # 等待停止完成 | ||
| 204 | while self.device_watcher.status != de.DeviceWatcherStatus.STOPPED: | ||
| 205 | await asyncio.sleep(0.1) | ||
| 206 | |||
| 207 | print(f"扫描完成,发现 {len(discovered_devices)} 个设备") | ||
| 208 | return discovered_devices | ||
| 209 | |||
| 210 | except Exception as e: | ||
| 211 | print(f"pywinrt 扫描出错: {e}") | ||
| 212 | return None | ||
| 213 | |||
| 214 | async def connect_pywinrt_device(self, device_id: str) -> bool: | ||
| 215 | """使用 pywinrt 连接蓝牙设备""" | ||
| 216 | if not USE_PYWINRT: | ||
| 217 | raise RuntimeError("pywinrt 仅在 Windows 系统上可用") | ||
| 218 | |||
| 219 | try: | ||
| 220 | print(f"正在连接设备: {device_id}") | ||
| 221 | self.update_connection_state('connecting', device_id=device_id) | ||
| 222 | |||
| 223 | # 从设备ID获取蓝牙设备 | ||
| 224 | self.bluetooth_device = await bt.BluetoothLEDevice.from_id_async(device_id) | ||
| 225 | |||
| 226 | if not self.bluetooth_device: | ||
| 227 | print("无法获取蓝牙设备对象") | ||
| 228 | self.record_error('device_not_found', f'无法获取设备对象: {device_id}') | ||
| 229 | self.update_connection_state('error') | ||
| 230 | return False | ||
| 231 | |||
| 232 | # 检查连接状态 | ||
| 233 | if self.bluetooth_device.connection_status == bt.BluetoothConnectionStatus.CONNECTED: | ||
| 234 | print("设备已连接") | ||
| 235 | self.update_connection_state('connected', | ||
| 236 | device_id=device_id, | ||
| 237 | connection_time=time.time(), | ||
| 238 | services_discovered=False, | ||
| 239 | characteristics_discovered=False) | ||
| 240 | return True | ||
| 241 | |||
| 242 | # 等待连接建立 | ||
| 243 | start_time = time.time() | ||
| 244 | |||
| 245 | while (self.bluetooth_device.connection_status != bt.BluetoothConnectionStatus.CONNECTED and | ||
| 246 | time.time() - start_time < self.connection_timeout): | ||
| 247 | await asyncio.sleep(0.1) | ||
| 248 | |||
| 249 | if self.bluetooth_device.connection_status == bt.BluetoothConnectionStatus.CONNECTED: | ||
| 250 | print("设备连接成功") | ||
| 251 | self.update_connection_state('connected', | ||
| 252 | device_id=device_id, | ||
| 253 | connection_time=time.time(), | ||
| 254 | services_discovered=False, | ||
| 255 | characteristics_discovered=False) | ||
| 256 | |||
| 257 | # 连接成功后自动发现服务 | ||
| 258 | try: | ||
| 259 | await self.discover_services_pywinrt() | ||
| 260 | self.update_connection_state('connected', services_discovered=True) | ||
| 261 | except Exception as e: | ||
| 262 | print(f"服务发现失败: {e}") | ||
| 263 | self.record_error('service_discovery_failed', str(e)) | ||
| 264 | |||
| 265 | return True | ||
| 266 | else: | ||
| 267 | print("设备连接失败") | ||
| 268 | self.record_error('connection_timeout', f'连接超时: {device_id}') | ||
| 269 | self.update_connection_state('error') | ||
| 270 | return False | ||
| 271 | |||
| 272 | except Exception as e: | ||
| 273 | print(f"连接设备时出错: {e}") | ||
| 274 | self.record_error('connection_exception', str(e)) | ||
| 275 | self.update_connection_state('error') | ||
| 276 | return False | ||
| 277 | |||
| 278 | async def discover_services_pywinrt(self) -> List[Dict]: | ||
| 279 | """使用 pywinrt 发现设备服务""" | ||
| 280 | if not USE_PYWINRT or not self.bluetooth_device: | ||
| 281 | return [] | ||
| 282 | |||
| 283 | try: | ||
| 284 | services = [] | ||
| 285 | gatt_result = await self.bluetooth_device.get_gatt_services_async() | ||
| 286 | |||
| 287 | if gatt_result.status == gatt.GattCommunicationStatus.SUCCESS: | ||
| 288 | for service in gatt_result.services: | ||
| 289 | service_info = { | ||
| 290 | 'uuid': str(service.uuid), | ||
| 291 | 'service': service | ||
| 292 | } | ||
| 293 | services.append(service_info) | ||
| 294 | print(f"发现服务: {service.uuid}") | ||
| 295 | |||
| 296 | return services | ||
| 297 | |||
| 298 | except Exception as e: | ||
| 299 | print(f"发现服务时出错: {e}") | ||
| 300 | return [] | ||
| 301 | |||
| 302 | async def discover_characteristics_pywinrt(self, service_uuid: str) -> List[Dict]: | ||
| 303 | """使用 pywinrt 发现特征""" | ||
| 304 | if not USE_PYWINRT or not self.bluetooth_device: | ||
| 305 | return [] | ||
| 306 | |||
| 307 | try: | ||
| 308 | # 获取服务 | ||
| 309 | gatt_result = await self.bluetooth_device.get_gatt_services_async() | ||
| 310 | if gatt_result.status != gatt.GattCommunicationStatus.SUCCESS: | ||
| 311 | return [] | ||
| 312 | |||
| 313 | target_service = None | ||
| 314 | for service in gatt_result.services: | ||
| 315 | if str(service.uuid) == service_uuid: | ||
| 316 | target_service = service | ||
| 317 | break | ||
| 318 | |||
| 319 | if not target_service: | ||
| 320 | print(f"未找到服务: {service_uuid}") | ||
| 321 | return [] | ||
| 322 | |||
| 323 | # 获取特征 | ||
| 324 | characteristics = [] | ||
| 325 | char_result = await target_service.get_characteristics_async() | ||
| 326 | |||
| 327 | if char_result.status == gatt.GattCommunicationStatus.SUCCESS: | ||
| 328 | for char in char_result.characteristics: | ||
| 329 | char_info = { | ||
| 330 | 'uuid': str(char.uuid), | ||
| 331 | 'characteristic': char, | ||
| 332 | 'properties': { | ||
| 333 | 'read': char.characteristic_properties & gatt.GattCharacteristicProperties.READ != 0, | ||
| 334 | 'write': char.characteristic_properties & gatt.GattCharacteristicProperties.WRITE != 0, | ||
| 335 | 'notify': char.characteristic_properties & gatt.GattCharacteristicProperties.NOTIFY != 0, | ||
| 336 | 'indicate': char.characteristic_properties & gatt.GattCharacteristicProperties.INDICATE != 0 | ||
| 337 | } | ||
| 338 | } | ||
| 339 | characteristics.append(char_info) | ||
| 340 | self.characteristics[str(char.uuid)] = char | ||
| 341 | print(f"发现特征: {char.uuid}, 属性: {char_info['properties']}") | ||
| 342 | |||
| 343 | return characteristics | ||
| 344 | |||
| 345 | except Exception as e: | ||
| 346 | print(f"发现特征时出错: {e}") | ||
| 347 | return [] | ||
| 348 | |||
| 349 | async def write_characteristic_pywinrt(self, characteristic_uuid: str, data: bytes) -> bool: | ||
| 350 | """使用 pywinrt 写入特征值""" | ||
| 351 | if not USE_PYWINRT or not self.bluetooth_device: | ||
| 352 | return False | ||
| 353 | |||
| 354 | try: | ||
| 355 | if characteristic_uuid not in self.characteristics: | ||
| 356 | print(f"特征未找到: {characteristic_uuid}") | ||
| 357 | return False | ||
| 358 | |||
| 359 | characteristic = self.characteristics[characteristic_uuid] | ||
| 360 | |||
| 361 | # 创建数据写入器 | ||
| 362 | writer = streams.DataWriter() | ||
| 363 | writer.write_bytes(data) | ||
| 364 | buffer = writer.detach_buffer() | ||
| 365 | |||
| 366 | # 写入数据 | ||
| 367 | result = await characteristic.write_value_async(buffer) | ||
| 368 | |||
| 369 | if result == gatt.GattCommunicationStatus.SUCCESS: | ||
| 370 | print(f"成功写入特征 {characteristic_uuid}: {data}") | ||
| 371 | return True | ||
| 372 | else: | ||
| 373 | print(f"写入特征失败: {result}") | ||
| 374 | return False | ||
| 375 | |||
| 376 | except Exception as e: | ||
| 377 | print(f"写入特征时出错: {e}") | ||
| 378 | return False | ||
| 379 | |||
| 380 | async def read_characteristic_pywinrt(self, characteristic_uuid: str) -> Optional[bytes]: | ||
| 381 | """使用 pywinrt 读取特征值""" | ||
| 382 | if not USE_PYWINRT or not self.bluetooth_device: | ||
| 383 | return None | ||
| 384 | |||
| 385 | try: | ||
| 386 | if characteristic_uuid not in self.characteristics: | ||
| 387 | print(f"特征未找到: {characteristic_uuid}") | ||
| 388 | return None | ||
| 389 | |||
| 390 | characteristic = self.characteristics[characteristic_uuid] | ||
| 391 | |||
| 392 | # 读取数据 | ||
| 393 | result = await characteristic.read_value_async() | ||
| 394 | |||
| 395 | if result.status == gatt.GattCommunicationStatus.SUCCESS: | ||
| 396 | # 读取数据 | ||
| 397 | reader = streams.DataReader.from_buffer(result.value) | ||
| 398 | data = bytearray(reader.unconsumed_buffer_length) | ||
| 399 | reader.read_bytes(data) | ||
| 400 | print(f"成功读取特征 {characteristic_uuid}: {bytes(data)}") | ||
| 401 | return bytes(data) | ||
| 402 | else: | ||
| 403 | print(f"读取特征失败: {result.status}") | ||
| 404 | return None | ||
| 405 | |||
| 406 | except Exception as e: | ||
| 407 | print(f"读取特征时出错: {e}") | ||
| 408 | return None | ||
| 409 | |||
| 410 | async def start_notifications_pywinrt(self, characteristic_uuid: str, callback: Callable) -> bool: | ||
| 411 | """使用 pywinrt 启动通知""" | ||
| 412 | if not USE_PYWINRT or not self.bluetooth_device: | ||
| 413 | return False | ||
| 414 | |||
| 415 | try: | ||
| 416 | if characteristic_uuid not in self.characteristics: | ||
| 417 | print(f"特征未找到: {characteristic_uuid}") | ||
| 418 | return False | ||
| 419 | |||
| 420 | characteristic = self.characteristics[characteristic_uuid] | ||
| 421 | |||
| 422 | # 检查特征是否支持通知 | ||
| 423 | if not (characteristic.characteristic_properties & gatt.GattCharacteristicProperties.NOTIFY): | ||
| 424 | print(f"特征 {characteristic_uuid} 不支持通知") | ||
| 425 | return False | ||
| 426 | |||
| 427 | # 设置通知回调 | ||
| 428 | def notification_handler(sender, args): | ||
| 429 | try: | ||
| 430 | # 读取通知数据 | ||
| 431 | reader = streams.DataReader.from_buffer(args.characteristic_value) | ||
| 432 | data = bytearray(reader.unconsumed_buffer_length) | ||
| 433 | reader.read_bytes(data) | ||
| 434 | |||
| 435 | # 调用用户回调 | ||
| 436 | asyncio.create_task(callback(bytes(data))) | ||
| 437 | |||
| 438 | except Exception as e: | ||
| 439 | print(f"处理通知时出错: {e}") | ||
| 440 | |||
| 441 | # 订阅通知 | ||
| 442 | characteristic.value_changed += notification_handler | ||
| 443 | result = await characteristic.write_client_characteristic_configuration_descriptor_async( | ||
| 444 | gatt.GattClientCharacteristicConfigurationDescriptorValue.NOTIFY | ||
| 445 | ) | ||
| 446 | |||
| 447 | if result == gatt.GattCommunicationStatus.SUCCESS: | ||
| 448 | self.notification_handlers[characteristic_uuid] = notification_handler | ||
| 449 | print(f"成功启动特征 {characteristic_uuid} 的通知") | ||
| 450 | return True | ||
| 451 | else: | ||
| 452 | print(f"启动通知失败: {result}") | ||
| 453 | return False | ||
| 454 | |||
| 455 | except Exception as e: | ||
| 456 | print(f"启动通知时出错: {e}") | ||
| 457 | return False | ||
| 458 | |||
| 459 | async def stop_notifications_pywinrt(self, characteristic_uuid: str) -> bool: | ||
| 460 | """使用 pywinrt 停止通知""" | ||
| 461 | if not USE_PYWINRT or not self.bluetooth_device: | ||
| 462 | return False | ||
| 463 | |||
| 464 | try: | ||
| 465 | if characteristic_uuid not in self.characteristics: | ||
| 466 | print(f"特征未找到: {characteristic_uuid}") | ||
| 467 | return False | ||
| 468 | |||
| 469 | characteristic = self.characteristics[characteristic_uuid] | ||
| 470 | |||
| 471 | # 取消订阅通知 | ||
| 472 | result = await characteristic.write_client_characteristic_configuration_descriptor_async( | ||
| 473 | gatt.GattClientCharacteristicConfigurationDescriptorValue.NONE | ||
| 474 | ) | ||
| 475 | |||
| 476 | if result == gatt.GattCommunicationStatus.SUCCESS: | ||
| 477 | # 移除事件处理器 | ||
| 478 | if characteristic_uuid in self.notification_handlers: | ||
| 479 | characteristic.value_changed -= self.notification_handlers[characteristic_uuid] | ||
| 480 | del self.notification_handlers[characteristic_uuid] | ||
| 481 | |||
| 482 | print(f"成功停止特征 {characteristic_uuid} 的通知") | ||
| 483 | return True | ||
| 484 | else: | ||
| 485 | print(f"停止通知失败: {result}") | ||
| 486 | return False | ||
| 487 | |||
| 488 | except Exception as e: | ||
| 489 | print(f"停止通知时出错: {e}") | ||
| 490 | return False | ||
| 491 | |||
| 492 | async def disconnect_pywinrt(self): | ||
| 493 | """使用 pywinrt 断开连接""" | ||
| 494 | if not USE_PYWINRT or not self.bluetooth_device: | ||
| 495 | return | ||
| 496 | |||
| 497 | try: | ||
| 498 | print("正在断开 pywinrt 蓝牙连接...") | ||
| 499 | self.update_connection_state('disconnecting') | ||
| 500 | |||
| 501 | # 停止所有通知 | ||
| 502 | for char_uuid in list(self.notification_handlers.keys()): | ||
| 503 | try: | ||
| 504 | await self.stop_notifications_pywinrt(char_uuid) | ||
| 505 | except Exception as e: | ||
| 506 | print(f"停止通知 {char_uuid} 时出错: {e}") | ||
| 507 | |||
| 508 | # 清理资源 | ||
| 509 | self.characteristics.clear() | ||
| 510 | self.notification_handlers.clear() | ||
| 511 | self.bluetooth_device = None | ||
| 512 | |||
| 513 | # 更新状态 | ||
| 514 | self.update_connection_state('disconnected', | ||
| 515 | device_id=None, | ||
| 516 | device_name=None, | ||
| 517 | connection_time=None, | ||
| 518 | services_discovered=False, | ||
| 519 | characteristics_discovered=False, | ||
| 520 | notifications_active=set()) | ||
| 521 | |||
| 522 | print("已断开 pywinrt 蓝牙连接") | ||
| 523 | |||
| 524 | except Exception as e: | ||
| 525 | print(f"断开连接时出错: {e}") | ||
| 526 | self.record_error('disconnect_exception', str(e)) | ||
| 527 | self.update_connection_state('error') | ||
| 528 | |||
| 529 | def is_connected_pywinrt(self) -> bool: | ||
| 530 | """检查 pywinrt 连接状态""" | ||
| 531 | if not USE_PYWINRT or not self.bluetooth_device: | ||
| 532 | return False | ||
| 533 | return self.bluetooth_device.connection_status == bt.BluetoothConnectionStatus.CONNECTED | ||
| 534 | |||
| 535 | def set_timeouts(self, scan_timeout: float = None, connection_timeout: float = None, operation_timeout: float = None): | ||
| 536 | """设置超时时间""" | ||
| 537 | if scan_timeout is not None: | ||
| 538 | self.scan_timeout = scan_timeout | ||
| 539 | if connection_timeout is not None: | ||
| 540 | self.connection_timeout = connection_timeout | ||
| 541 | if operation_timeout is not None: | ||
| 542 | self.operation_timeout = operation_timeout | ||
| 543 | |||
| 544 | def update_connection_state(self, status: str, **kwargs): | ||
| 545 | """更新连接状态""" | ||
| 546 | old_status = self.connection_state['status'] | ||
| 547 | self.connection_state['status'] = status | ||
| 548 | self.connection_state['last_activity'] = time.time() | ||
| 549 | |||
| 550 | # 更新其他状态信息 | ||
| 551 | for key, value in kwargs.items(): | ||
| 552 | if key in self.connection_state: | ||
| 553 | self.connection_state[key] = value | ||
| 554 | |||
| 555 | # 记录状态变化历史 | ||
| 556 | state_change = { | ||
| 557 | 'timestamp': time.time(), | ||
| 558 | 'old_status': old_status, | ||
| 559 | 'new_status': status, | ||
| 560 | 'details': kwargs | ||
| 561 | } | ||
| 562 | self.connection_state['connection_history'].append(state_change) | ||
| 563 | |||
| 564 | # 保持历史记录在合理范围内 | ||
| 565 | if len(self.connection_state['connection_history']) > 100: | ||
| 566 | self.connection_state['connection_history'] = self.connection_state['connection_history'][-50:] | ||
| 567 | |||
| 568 | # 触发状态变化回调 | ||
| 569 | for callback in self.state_change_callbacks: | ||
| 570 | try: | ||
| 571 | callback(old_status, status, kwargs) | ||
| 572 | except Exception as e: | ||
| 573 | print(f"状态变化回调出错: {e}") | ||
| 574 | |||
| 575 | print(f"连接状态变化: {old_status} -> {status}") | ||
| 576 | |||
| 577 | def add_state_change_callback(self, callback: Callable): | ||
| 578 | """添加状态变化回调""" | ||
| 579 | self.state_change_callbacks.append(callback) | ||
| 580 | |||
| 581 | def remove_state_change_callback(self, callback: Callable): | ||
| 582 | """移除状态变化回调""" | ||
| 583 | if callback in self.state_change_callbacks: | ||
| 584 | self.state_change_callbacks.remove(callback) | ||
| 585 | |||
| 586 | def get_connection_state(self) -> Dict: | ||
| 587 | """获取当前连接状态""" | ||
| 588 | return self.connection_state.copy() | ||
| 589 | |||
| 590 | def get_connection_quality(self) -> str: | ||
| 591 | """评估连接质量""" | ||
| 592 | if not self.is_connected(): | ||
| 593 | return 'disconnected' | ||
| 594 | |||
| 595 | # 基于错误计数和重试次数评估连接质量 | ||
| 596 | error_count = self.connection_state['error_count'] | ||
| 597 | retry_count = self.connection_state['retry_count'] | ||
| 598 | |||
| 599 | if error_count == 0 and retry_count == 0: | ||
| 600 | return 'excellent' | ||
| 601 | elif error_count <= 2 and retry_count <= 1: | ||
| 602 | return 'good' | ||
| 603 | elif error_count <= 5 and retry_count <= 2: | ||
| 604 | return 'fair' | ||
| 605 | else: | ||
| 606 | return 'poor' | ||
| 607 | |||
| 608 | def is_connected(self) -> bool: | ||
| 609 | """检查是否已连接""" | ||
| 610 | if USE_PYWINRT: | ||
| 611 | return (self.connection_state['status'] == 'connected' and | ||
| 612 | self.bluetooth_device and | ||
| 613 | self.bluetooth_device.connection_status == bt.BluetoothConnectionStatus.CONNECTED) | ||
| 614 | else: | ||
| 615 | return (self.connection_state['status'] == 'connected' and | ||
| 616 | self.client and | ||
| 617 | self.client.is_connected) | ||
| 618 | |||
| 619 | def is_connecting(self) -> bool: | ||
| 620 | """检查是否正在连接""" | ||
| 621 | return self.connection_state['status'] == 'connecting' | ||
| 622 | |||
| 623 | def is_disconnecting(self) -> bool: | ||
| 624 | """检查是否正在断开连接""" | ||
| 625 | return self.connection_state['status'] == 'disconnecting' | ||
| 626 | |||
| 627 | def has_error(self) -> bool: | ||
| 628 | """检查是否有错误""" | ||
| 629 | return self.connection_state['status'] == 'error' | ||
| 630 | |||
| 631 | def get_connection_duration(self) -> float: | ||
| 632 | """获取连接持续时间(秒)""" | ||
| 633 | if self.connection_state['connection_time']: | ||
| 634 | return time.time() - self.connection_state['connection_time'] | ||
| 635 | return 0.0 | ||
| 636 | |||
| 637 | def get_last_activity_duration(self) -> float: | ||
| 638 | """获取最后活动时间(秒)""" | ||
| 639 | if self.connection_state['last_activity']: | ||
| 640 | return time.time() - self.connection_state['last_activity'] | ||
| 641 | return 0.0 | ||
| 642 | |||
| 643 | def record_error(self, error_type: str, error_message: str): | ||
| 644 | """记录错误""" | ||
| 645 | self.connection_state['error_count'] += 1 | ||
| 646 | error_record = { | ||
| 647 | 'timestamp': time.time(), | ||
| 648 | 'type': error_type, | ||
| 649 | 'message': error_message, | ||
| 650 | 'status': self.connection_state['status'] | ||
| 651 | } | ||
| 652 | self.connection_state['error_history'].append(error_record) | ||
| 653 | |||
| 654 | # 保持错误历史在合理范围内 | ||
| 655 | if len(self.connection_state['error_history']) > 50: | ||
| 656 | self.connection_state['error_history'] = self.connection_state['error_history'][-25:] | ||
| 657 | |||
| 658 | # 更新连接质量 | ||
| 659 | self.connection_state['connection_quality'] = self.get_connection_quality() | ||
| 660 | |||
| 661 | print(f"记录错误: {error_type} - {error_message}") | ||
| 662 | |||
| 663 | def reset_error_count(self): | ||
| 664 | """重置错误计数""" | ||
| 665 | self.connection_state['error_count'] = 0 | ||
| 666 | self.connection_state['retry_count'] = 0 | ||
| 667 | self.connection_state['connection_quality'] = self.get_connection_quality() | ||
| 668 | |||
| 669 | async def start_connection_monitor(self): | ||
| 670 | """启动连接监控""" | ||
| 671 | if self.connection_monitor_task: | ||
| 672 | return | ||
| 673 | |||
| 674 | self.connection_monitor_task = asyncio.create_task(self._connection_monitor_loop()) | ||
| 675 | |||
| 676 | async def stop_connection_monitor(self): | ||
| 677 | """停止连接监控""" | ||
| 678 | if self.connection_monitor_task: | ||
| 679 | self.connection_monitor_task.cancel() | ||
| 680 | try: | ||
| 681 | await self.connection_monitor_task | ||
| 682 | except asyncio.CancelledError: | ||
| 683 | pass | ||
| 684 | self.connection_monitor_task = None | ||
| 685 | |||
| 686 | async def _connection_monitor_loop(self): | ||
| 687 | """连接监控循环""" | ||
| 688 | while True: | ||
| 689 | try: | ||
| 690 | await asyncio.sleep(1.0) # 每秒检查一次 | ||
| 691 | |||
| 692 | if self.connection_state['status'] == 'connected': | ||
| 693 | # 检查连接是否仍然有效 | ||
| 694 | if not self.is_connected(): | ||
| 695 | print("检测到连接丢失") | ||
| 696 | self.update_connection_state('error', error_type='connection_lost') | ||
| 697 | |||
| 698 | # 自动重连 | ||
| 699 | if self.auto_reconnect: | ||
| 700 | await self._attempt_auto_reconnect() | ||
| 701 | |||
| 702 | elif self.connection_state['status'] == 'error' and self.auto_reconnect: | ||
| 703 | # 尝试自动重连 | ||
| 704 | await self._attempt_auto_reconnect() | ||
| 705 | |||
| 706 | except asyncio.CancelledError: | ||
| 707 | break | ||
| 708 | except Exception as e: | ||
| 709 | print(f"连接监控出错: {e}") | ||
| 710 | await asyncio.sleep(5.0) # 出错时等待更长时间 | ||
| 711 | |||
| 712 | async def _attempt_auto_reconnect(self): | ||
| 713 | """尝试自动重连""" | ||
| 714 | if self.connection_state['retry_count'] >= self.connection_state['max_retries']: | ||
| 715 | print("达到最大重试次数,停止自动重连") | ||
| 716 | return | ||
| 717 | |||
| 718 | device_id = self.connection_state['device_id'] | ||
| 719 | if not device_id: | ||
| 720 | print("没有设备ID,无法自动重连") | ||
| 721 | return | ||
| 722 | |||
| 723 | print(f"尝试自动重连 (第{self.connection_state['retry_count'] + 1}次)") | ||
| 724 | self.connection_state['retry_count'] += 1 | ||
| 725 | |||
| 726 | await asyncio.sleep(self.auto_reconnect_interval) | ||
| 727 | |||
| 728 | if USE_PYWINRT: | ||
| 729 | success = await self.connect_pywinrt_device(device_id) | ||
| 730 | else: | ||
| 731 | # 非 Windows 系统的重连逻辑 | ||
| 732 | if self.client: | ||
| 733 | await self.client.connect() | ||
| 734 | success = self.client.is_connected | ||
| 735 | |||
| 736 | if success: | ||
| 737 | print("自动重连成功") | ||
| 738 | self.reset_error_count() | ||
| 739 | else: | ||
| 740 | print("自动重连失败") | ||
| 741 | self.record_error('auto_reconnect_failed', f'重连尝试 {self.connection_state["retry_count"]} 失败') | ||
| 742 | |||
| 743 | def detection_callback(self, device, advertisement_data): | ||
| 744 | if any(service_uuid in advertisement_data.service_uuids for service_uuid in self.services): | ||
| 745 | self.target_device = (device, advertisement_data) | ||
| 746 | if not self.target_device: | ||
| 747 | print("未找到匹配设备") | ||
| 748 | return | ||
| 749 | else: | ||
| 750 | device, adv_data = self.target_device | ||
| 751 | print("\n找到目标设备:") | ||
| 752 | print(f"设备名称: {device.name}") | ||
| 753 | print(f"设备地址: {device.address}") | ||
| 754 | print(f"信号强度: {device.rssi} dBm") | ||
| 755 | print("\n广播信息:") | ||
| 756 | print(f"服务UUID列表: {adv_data.service_uuids}") | ||
| 757 | print(f"制造商数据: {adv_data.manufacturer_data}") | ||
| 758 | print(f"服务数据: {adv_data.service_data}") | ||
| 759 | print(f"本地名称: {adv_data.local_name}") | ||
| 760 | return self.target_device | ||
| 761 | |||
| 762 | async def handle_client(self, websocket, path): | ||
| 763 | self.websocket = websocket | ||
| 764 | if path != "/scratch/ble": | ||
| 765 | await websocket.close(code=1003, reason="Path not allowed") | ||
| 766 | return | ||
| 767 | |||
| 768 | try: | ||
| 769 | async for message in websocket: | ||
| 770 | try: | ||
| 771 | await log_message("接收", message) | ||
| 772 | request = json.loads(message) | ||
| 773 | |||
| 774 | if request["jsonrpc"] != "2.0": | ||
| 775 | continue | ||
| 776 | |||
| 777 | method = request.get("method") | ||
| 778 | params = request.get("params", {}) | ||
| 779 | request_id = request.get("id") | ||
| 780 | |||
| 781 | if method == "discover": | ||
| 782 | self.services = [] | ||
| 783 | for filt in params.get("filters", [{}]): | ||
| 784 | self.services.extend(filt.get("services", [])) | ||
| 785 | self.optional_services = params.get("optionalServices", []) | ||
| 786 | |||
| 787 | if USE_PYWINRT: | ||
| 788 | # 使用 pywinrt 进行设备发现 | ||
| 789 | discovered_devices = await self.start_pywinrt_scan(self.services) | ||
| 790 | |||
| 791 | if discovered_devices: | ||
| 792 | # 选择第一个设备(可以根据需要修改选择逻辑) | ||
| 793 | device_info = discovered_devices[0] | ||
| 794 | |||
| 795 | discover_response = json.dumps({ | ||
| 796 | "jsonrpc": "2.0", | ||
| 797 | "method": "didDiscoverPeripheral", | ||
| 798 | "params": { | ||
| 799 | "name": device_info['name'], | ||
| 800 | "peripheralId": device_info['id'], | ||
| 801 | "rssi": -50 # pywinrt 不直接提供 RSSI,使用默认值 | ||
| 802 | } | ||
| 803 | }) | ||
| 804 | await log_message("下发", discover_response) | ||
| 805 | await websocket.send(discover_response) | ||
| 806 | |||
| 807 | result_response = json.dumps({ | ||
| 808 | "jsonrpc": "2.0", | ||
| 809 | "result": None, | ||
| 810 | "id": request_id | ||
| 811 | }) | ||
| 812 | await log_message("下发", result_response) | ||
| 813 | await websocket.send(result_response) | ||
| 814 | else: | ||
| 815 | # 未找到设备 | ||
| 816 | error_response = json.dumps({ | ||
| 817 | "jsonrpc": "2.0", | ||
| 818 | "error": {"code": -1, "message": "未找到匹配的蓝牙设备"}, | ||
| 819 | "id": request_id | ||
| 820 | }) | ||
| 821 | await log_message("下发", error_response) | ||
| 822 | await websocket.send(error_response) | ||
| 823 | else: | ||
| 824 | # 使用 bleak 进行设备发现(非 Windows 系统) | ||
| 825 | scanner = BleakScanner(scanning_mode="active") | ||
| 826 | scanner.register_detection_callback(self.detection_callback) | ||
| 827 | |||
| 828 | max_retries = 3 | ||
| 829 | found = False | ||
| 830 | for attempt in range(max_retries): | ||
| 831 | self.target_device = None | ||
| 832 | await scanner.start() | ||
| 833 | await asyncio.sleep(5) | ||
| 834 | await scanner.stop() | ||
| 835 | |||
| 836 | if self.target_device: | ||
| 837 | found = True | ||
| 838 | break | ||
| 839 | |||
| 840 | if attempt < max_retries - 1: | ||
| 841 | print(f"未找到设备,第{attempt+1}次重试...") | ||
| 842 | await asyncio.sleep(3) | ||
| 843 | |||
| 844 | if found: | ||
| 845 | device, adv_data = self.target_device | ||
| 846 | discover_response = json.dumps({ | ||
| 847 | "jsonrpc": "2.0", | ||
| 848 | "method": "didDiscoverPeripheral", | ||
| 849 | "params": { | ||
| 850 | "name": device.name, | ||
| 851 | "peripheralId": device.address, | ||
| 852 | "rssi": device.rssi | ||
| 853 | } | ||
| 854 | }) | ||
| 855 | await log_message("下发", discover_response) | ||
| 856 | await websocket.send(discover_response) | ||
| 857 | |||
| 858 | result_response = json.dumps({ | ||
| 859 | "jsonrpc": "2.0", | ||
| 860 | "result": None, | ||
| 861 | "id": request_id | ||
| 862 | }) | ||
| 863 | await log_message("下发", result_response) | ||
| 864 | await websocket.send(result_response) | ||
| 865 | else: | ||
| 866 | error_response = json.dumps({ | ||
| 867 | "jsonrpc": "2.0", | ||
| 868 | "error": {"code": -1, "message": "未找到匹配的蓝牙设备"}, | ||
| 869 | "id": request_id | ||
| 870 | }) | ||
| 871 | await log_message("下发", error_response) | ||
| 872 | await websocket.send(error_response) | ||
| 873 | |||
| 874 | elif method == "connect": | ||
| 875 | peripheral_id = params.get("peripheralId") | ||
| 876 | if peripheral_id: | ||
| 877 | if USE_PYWINRT: | ||
| 878 | # 使用 pywinrt 连接 | ||
| 879 | success = await self.connect_pywinrt_device(peripheral_id) | ||
| 880 | if success: | ||
| 881 | response = json.dumps({ | ||
| 882 | "jsonrpc": "2.0", | ||
| 883 | "result": None, | ||
| 884 | "id": request_id | ||
| 885 | }) | ||
| 886 | await log_message("下发", response) | ||
| 887 | await websocket.send(response) | ||
| 888 | else: | ||
| 889 | error_response = json.dumps({ | ||
| 890 | "jsonrpc": "2.0", | ||
| 891 | "error": {"code": -1, "message": "连接设备失败"}, | ||
| 892 | "id": request_id | ||
| 893 | }) | ||
| 894 | await log_message("下发", error_response) | ||
| 895 | await websocket.send(error_response) | ||
| 896 | else: | ||
| 897 | # 使用 bleak 连接 | ||
| 898 | self.client = BleakClient(peripheral_id) | ||
| 899 | self.client.set_disconnected_callback(self.on_disconnect) | ||
| 900 | await self.client.connect() | ||
| 901 | |||
| 902 | if self.client.is_connected: | ||
| 903 | response = json.dumps({ | ||
| 904 | "jsonrpc": "2.0", | ||
| 905 | "result": None, | ||
| 906 | "id": request_id | ||
| 907 | }) | ||
| 908 | await log_message("下发", response) | ||
| 909 | await websocket.send(response) | ||
| 910 | else: | ||
| 911 | error_response = json.dumps({ | ||
| 912 | "jsonrpc": "2.0", | ||
| 913 | "error": {"code": -1, "message": "连接设备失败"}, | ||
| 914 | "id": request_id | ||
| 915 | }) | ||
| 916 | await log_message("下发", error_response) | ||
| 917 | await websocket.send(error_response) | ||
| 918 | |||
| 919 | elif method == "write": | ||
| 920 | service_id = params.get("serviceId") | ||
| 921 | characteristic_id = params.get("characteristicId") | ||
| 922 | message = params.get("message") | ||
| 923 | encoding = params.get("encoding", "utf-8") | ||
| 924 | |||
| 925 | if all([service_id, characteristic_id, message]): | ||
| 926 | if encoding == "base64": | ||
| 927 | message_bytes = base64.b64decode(message) | ||
| 928 | print("write message_bytes", message_bytes) | ||
| 929 | else: | ||
| 930 | print("write message", message) | ||
| 931 | message_bytes = message.encode(encoding) | ||
| 932 | |||
| 933 | if USE_PYWINRT: | ||
| 934 | # 使用 pywinrt 写入 | ||
| 935 | success = await self.write_characteristic_pywinrt(characteristic_id, message_bytes) | ||
| 936 | if success: | ||
| 937 | response = json.dumps({ | ||
| 938 | "jsonrpc": "2.0", | ||
| 939 | "result": None, | ||
| 940 | "id": request_id | ||
| 941 | }) | ||
| 942 | await log_message("下发", response) | ||
| 943 | await websocket.send(response) | ||
| 944 | else: | ||
| 945 | error_response = json.dumps({ | ||
| 946 | "jsonrpc": "2.0", | ||
| 947 | "error": {"code": -1, "message": "写入特征失败"}, | ||
| 948 | "id": request_id | ||
| 949 | }) | ||
| 950 | await log_message("下发", error_response) | ||
| 951 | await websocket.send(error_response) | ||
| 952 | else: | ||
| 953 | # 使用 bleak 写入 | ||
| 954 | await self.client.write_gatt_char(characteristic_id, message_bytes) | ||
| 955 | response = json.dumps({ | ||
| 956 | "jsonrpc": "2.0", | ||
| 957 | "result": None, | ||
| 958 | "id": request_id | ||
| 959 | }) | ||
| 960 | await log_message("下发", response) | ||
| 961 | await websocket.send(response) | ||
| 962 | |||
| 963 | elif method == "read": | ||
| 964 | service_id = params.get("serviceId") | ||
| 965 | characteristic_id = params.get("characteristicId") | ||
| 966 | |||
| 967 | if all([service_id, characteristic_id]): | ||
| 968 | if USE_PYWINRT: | ||
| 969 | # 使用 pywinrt 读取 | ||
| 970 | data = await self.read_characteristic_pywinrt(characteristic_id) | ||
| 971 | if data is not None: | ||
| 972 | print('read-data', data) | ||
| 973 | response = json.dumps({ | ||
| 974 | "jsonrpc": "2.0", | ||
| 975 | "result": { | ||
| 976 | "serviceId": service_id, | ||
| 977 | "characteristicId": characteristic_id, | ||
| 978 | "message": base64.b64encode(data).decode("utf-8") | ||
| 979 | }, | ||
| 980 | "id": request_id | ||
| 981 | }) | ||
| 982 | await log_message("下发", response) | ||
| 983 | await websocket.send(response) | ||
| 984 | else: | ||
| 985 | error_response = json.dumps({ | ||
| 986 | "jsonrpc": "2.0", | ||
| 987 | "error": {"code": -1, "message": "读取特征失败"}, | ||
| 988 | "id": request_id | ||
| 989 | }) | ||
| 990 | await log_message("下发", error_response) | ||
| 991 | await websocket.send(error_response) | ||
| 992 | else: | ||
| 993 | # 使用 bleak 读取 | ||
| 994 | data = await self.client.read_gatt_char(characteristic_id) | ||
| 995 | print('read-data', data) | ||
| 996 | response = json.dumps({ | ||
| 997 | "jsonrpc": "2.0", | ||
| 998 | "result": { | ||
| 999 | "serviceId": service_id, | ||
| 1000 | "characteristicId": characteristic_id, | ||
| 1001 | "message": base64.b64encode(data).decode("utf-8") | ||
| 1002 | }, | ||
| 1003 | "id": request_id | ||
| 1004 | }) | ||
| 1005 | await log_message("下发", response) | ||
| 1006 | await websocket.send(response) | ||
| 1007 | |||
| 1008 | elif method == "startNotifications": | ||
| 1009 | service_id = params.get("serviceId") | ||
| 1010 | characteristic_id = params.get("characteristicId") | ||
| 1011 | |||
| 1012 | if all([service_id, characteristic_id]): | ||
| 1013 | if USE_PYWINRT: | ||
| 1014 | # 使用 pywinrt 启动通知 | ||
| 1015 | async def pywinrt_notification_callback(data: bytes): | ||
| 1016 | await self.pywinrt_notification_handler(websocket, service_id, characteristic_id, data) | ||
| 1017 | |||
| 1018 | success = await self.start_notifications_pywinrt(characteristic_id, pywinrt_notification_callback) | ||
| 1019 | if success: | ||
| 1020 | response = json.dumps({ | ||
| 1021 | "jsonrpc": "2.0", | ||
| 1022 | "result": None, | ||
| 1023 | "id": request_id | ||
| 1024 | }) | ||
| 1025 | await log_message("下发", response) | ||
| 1026 | await websocket.send(response) | ||
| 1027 | else: | ||
| 1028 | error_response = json.dumps({ | ||
| 1029 | "jsonrpc": "2.0", | ||
| 1030 | "error": {"code": -1, "message": "启动通知失败"}, | ||
| 1031 | "id": request_id | ||
| 1032 | }) | ||
| 1033 | await log_message("下发", error_response) | ||
| 1034 | await websocket.send(error_response) | ||
| 1035 | else: | ||
| 1036 | # 使用 bleak 启动通知 | ||
| 1037 | await self.client.start_notify( | ||
| 1038 | characteristic_id, | ||
| 1039 | self.notification_handler(websocket, service_id, characteristic_id) | ||
| 1040 | ) | ||
| 1041 | response = json.dumps({ | ||
| 1042 | "jsonrpc": "2.0", | ||
| 1043 | "result": None, | ||
| 1044 | "id": request_id | ||
| 1045 | }) | ||
| 1046 | await log_message("下发", response) | ||
| 1047 | await websocket.send(response) | ||
| 1048 | |||
| 1049 | elif method == "discoverServices": | ||
| 1050 | # 发现设备服务 | ||
| 1051 | if USE_PYWINRT and self.bluetooth_device: | ||
| 1052 | services = await self.discover_services_pywinrt() | ||
| 1053 | response = json.dumps({ | ||
| 1054 | "jsonrpc": "2.0", | ||
| 1055 | "result": {"services": [s['uuid'] for s in services]}, | ||
| 1056 | "id": request_id | ||
| 1057 | }) | ||
| 1058 | await log_message("下发", response) | ||
| 1059 | await websocket.send(response) | ||
| 1060 | else: | ||
| 1061 | error_response = json.dumps({ | ||
| 1062 | "jsonrpc": "2.0", | ||
| 1063 | "error": {"code": -1, "message": "服务发现功能仅在 Windows 系统上可用"}, | ||
| 1064 | "id": request_id | ||
| 1065 | }) | ||
| 1066 | await log_message("下发", error_response) | ||
| 1067 | await websocket.send(error_response) | ||
| 1068 | |||
| 1069 | elif method == "discoverCharacteristics": | ||
| 1070 | # 发现服务特征 | ||
| 1071 | service_id = params.get("serviceId") | ||
| 1072 | if service_id and USE_PYWINRT and self.bluetooth_device: | ||
| 1073 | characteristics = await self.discover_characteristics_pywinrt(service_id) | ||
| 1074 | response = json.dumps({ | ||
| 1075 | "jsonrpc": "2.0", | ||
| 1076 | "result": { | ||
| 1077 | "characteristics": [ | ||
| 1078 | { | ||
| 1079 | "uuid": char['uuid'], | ||
| 1080 | "properties": char['properties'] | ||
| 1081 | } for char in characteristics | ||
| 1082 | ] | ||
| 1083 | }, | ||
| 1084 | "id": request_id | ||
| 1085 | }) | ||
| 1086 | await log_message("下发", response) | ||
| 1087 | await websocket.send(response) | ||
| 1088 | else: | ||
| 1089 | error_response = json.dumps({ | ||
| 1090 | "jsonrpc": "2.0", | ||
| 1091 | "error": {"code": -1, "message": "特征发现功能仅在 Windows 系统上可用"}, | ||
| 1092 | "id": request_id | ||
| 1093 | }) | ||
| 1094 | await log_message("下发", error_response) | ||
| 1095 | await websocket.send(error_response) | ||
| 1096 | |||
| 1097 | elif method == "disconnect": | ||
| 1098 | # 断开连接 | ||
| 1099 | if USE_PYWINRT: | ||
| 1100 | await self.disconnect_pywinrt() | ||
| 1101 | else: | ||
| 1102 | if self.client and self.client.is_connected: | ||
| 1103 | await self.client.disconnect() | ||
| 1104 | self.client = None | ||
| 1105 | |||
| 1106 | response = json.dumps({ | ||
| 1107 | "jsonrpc": "2.0", | ||
| 1108 | "result": None, | ||
| 1109 | "id": request_id | ||
| 1110 | }) | ||
| 1111 | await log_message("下发", response) | ||
| 1112 | await websocket.send(response) | ||
| 1113 | |||
| 1114 | elif method == "getConnectionStatus": | ||
| 1115 | # 获取连接状态 | ||
| 1116 | if USE_PYWINRT: | ||
| 1117 | is_connected = self.is_connected_pywinrt() | ||
| 1118 | else: | ||
| 1119 | is_connected = self.client and self.client.is_connected | ||
| 1120 | |||
| 1121 | response = json.dumps({ | ||
| 1122 | "jsonrpc": "2.0", | ||
| 1123 | "result": {"connected": is_connected}, | ||
| 1124 | "id": request_id | ||
| 1125 | }) | ||
| 1126 | await log_message("下发", response) | ||
| 1127 | await websocket.send(response) | ||
| 1128 | |||
| 1129 | elif method == "setTimeouts": | ||
| 1130 | # 设置超时时间 | ||
| 1131 | scan_timeout = params.get("scanTimeout") | ||
| 1132 | connection_timeout = params.get("connectionTimeout") | ||
| 1133 | operation_timeout = params.get("operationTimeout") | ||
| 1134 | |||
| 1135 | self.set_timeouts(scan_timeout, connection_timeout, operation_timeout) | ||
| 1136 | |||
| 1137 | response = json.dumps({ | ||
| 1138 | "jsonrpc": "2.0", | ||
| 1139 | "result": { | ||
| 1140 | "scanTimeout": self.scan_timeout, | ||
| 1141 | "connectionTimeout": self.connection_timeout, | ||
| 1142 | "operationTimeout": self.operation_timeout | ||
| 1143 | }, | ||
| 1144 | "id": request_id | ||
| 1145 | }) | ||
| 1146 | await log_message("下发", response) | ||
| 1147 | await websocket.send(response) | ||
| 1148 | |||
| 1149 | elif method == "ping": | ||
| 1150 | # 处理ping请求,返回pong响应 | ||
| 1151 | response = json.dumps({ | ||
| 1152 | "jsonrpc": "2.0", | ||
| 1153 | "result": {"pong": True, "timestamp": int(time.time())}, | ||
| 1154 | "id": request_id | ||
| 1155 | }) | ||
| 1156 | await log_message("下发", response) | ||
| 1157 | await websocket.send(response) | ||
| 1158 | |||
| 1159 | except json.JSONDecodeError: | ||
| 1160 | error_msg = json.dumps({ | ||
| 1161 | "jsonrpc": "2.0", | ||
| 1162 | "result": {"message": "Parse error"}, | ||
| 1163 | "id": None | ||
| 1164 | }) | ||
| 1165 | await log_message("下发", error_msg) | ||
| 1166 | except Exception as e: | ||
| 1167 | error_msg = json.dumps({ | ||
| 1168 | "jsonrpc": "2.0", | ||
| 1169 | "result": {"message": str(e)}, | ||
| 1170 | "id": request.get("id") if request else None | ||
| 1171 | }) | ||
| 1172 | await log_message("下发", error_msg) | ||
| 1173 | |||
| 1174 | except websockets.exceptions.ConnectionClosed: | ||
| 1175 | print("WebSocket连接关闭") | ||
| 1176 | finally: | ||
| 1177 | # 清理连接 | ||
| 1178 | if USE_PYWINRT: | ||
| 1179 | await self.disconnect_pywinrt() | ||
| 1180 | else: | ||
| 1181 | if self.client and self.client.is_connected: | ||
| 1182 | await self.client.disconnect() | ||
| 1183 | self.client = None | ||
| 1184 | self.target_device = None | ||
| 1185 | |||
| 1186 | async def pywinrt_notification_handler(self, websocket, service_id, characteristic_id, data: bytes): | ||
| 1187 | """pywinrt 通知处理器""" | ||
| 1188 | try: | ||
| 1189 | current_time = asyncio.get_event_loop().time() | ||
| 1190 | last_message, last_time = self.notification_records[characteristic_id] | ||
| 1191 | |||
| 1192 | # 解码当前数据用于比较 | ||
| 1193 | current_message = base64.b64encode(data).decode('utf-8') | ||
| 1194 | print('pywinrt_notification_handler current_message', data) | ||
| 1195 | |||
| 1196 | # 过滤逻辑 | ||
| 1197 | if current_message == last_message and (current_time - last_time) < 0.5: | ||
| 1198 | return | ||
| 1199 | |||
| 1200 | # 更新记录 | ||
| 1201 | self.notification_records[characteristic_id] = (current_message, current_time) | ||
| 1202 | |||
| 1203 | response = json.dumps({ | ||
| 1204 | "jsonrpc": "2.0", | ||
| 1205 | "method": "characteristicDidChange", | ||
| 1206 | "params": { | ||
| 1207 | "serviceId": service_id, | ||
| 1208 | "characteristicId": characteristic_id, | ||
| 1209 | "message": current_message | ||
| 1210 | } | ||
| 1211 | }) | ||
| 1212 | await log_message("下发", response) | ||
| 1213 | await websocket.send(response) | ||
| 1214 | |||
| 1215 | except Exception as e: | ||
| 1216 | print(f"pywinrt 通知处理出错: {e}") | ||
| 1217 | |||
| 1218 | def notification_handler(self, websocket, service_id, characteristic_id): | ||
| 1219 | async def callback(sender, data): | ||
| 1220 | current_time = asyncio.get_event_loop().time() | ||
| 1221 | last_message, last_time = self.notification_records[characteristic_id] | ||
| 1222 | |||
| 1223 | # 解码当前数据用于比较 | ||
| 1224 | current_message = base64.b64encode(data).decode('utf-8') | ||
| 1225 | print('notification_handler current_message',base64.b64decode(current_message)) | ||
| 1226 | |||
| 1227 | # 过滤逻辑 | ||
| 1228 | if current_message == last_message and (current_time - last_time) < 0.5: | ||
| 1229 | return | ||
| 1230 | |||
| 1231 | # 更新记录 | ||
| 1232 | self.notification_records[characteristic_id] = (current_message, current_time) | ||
| 1233 | |||
| 1234 | response = json.dumps({ | ||
| 1235 | "jsonrpc": "2.0", | ||
| 1236 | "method": "characteristicDidChange", | ||
| 1237 | "params": { | ||
| 1238 | "serviceId": service_id, | ||
| 1239 | "characteristicId": characteristic_id, | ||
| 1240 | "message": current_message | ||
| 1241 | } | ||
| 1242 | }) | ||
| 1243 | await log_message("下发", response) | ||
| 1244 | await websocket.send(response) | ||
| 1245 | return callback | ||
| 1246 | |||
| 1247 | async def check_port_and_start_server(port=20111, host='localhost'): | ||
| 1248 | """检查端口并启动服务器""" | ||
| 1249 | if is_port_in_use(port, host): | ||
| 1250 | print(f"错误: 端口 {port} 已被占用,无法启动服务") | ||
| 1251 | return False | ||
| 1252 | |||
| 1253 | print(f"端口 {port} 可用,正在启动服务...") | ||
| 1254 | server = await websockets.serve( | ||
| 1255 | lambda websocket, path: BLEClient().handle_client(websocket, path), | ||
| 1256 | host, port | ||
| 1257 | ) | ||
| 1258 | |||
| 1259 | print(f"WebSocket服务已启动: ws://{host}:{port}/scratch/ble") | ||
| 1260 | print("日志文件路径: ./b.log") | ||
| 1261 | |||
| 1262 | # 执行自检测试 | ||
| 1263 | try: | ||
| 1264 | async with websockets.connect(f"ws://{host}:{port}/scratch/ble") as websocket: | ||
| 1265 | print("正在执行自检测试...") | ||
| 1266 | test_result = await self_test(websocket) | ||
| 1267 | if test_result: | ||
| 1268 | print("自检测试成功: 服务正常运行") | ||
| 1269 | else: | ||
| 1270 | print("自检测试失败: 服务可能存在问题") | ||
| 1271 | except Exception as e: | ||
| 1272 | print(f"自检测试异常: {str(e)}") | ||
| 1273 | |||
| 1274 | return server | ||
| 1275 | |||
| 1276 | async def main(): | ||
| 1277 | server = await check_port_and_start_server() | ||
| 1278 | if server: | ||
| 1279 | await asyncio.Future() # 保持服务器运行 | ||
| 1280 | |||
| 1281 | if __name__ == "__main__": | ||
| 1282 | asyncio.run(main()) | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| ... | @@ -186,7 +186,7 @@ class BLEClient: | ... | @@ -186,7 +186,7 @@ class BLEClient: |
| 186 | "result": None, | 186 | "result": None, |
| 187 | "id": request_id | 187 | "id": request_id |
| 188 | }) | 188 | }) |
| 189 | await log_message("下发", response) | 189 | # await log_message("下发", response) |
| 190 | await websocket.send(response) | 190 | await websocket.send(response) |
| 191 | 191 | ||
| 192 | elif method == "write": | 192 | elif method == "write": |
| ... | @@ -198,8 +198,11 @@ class BLEClient: | ... | @@ -198,8 +198,11 @@ class BLEClient: |
| 198 | if all([service_id, characteristic_id, message]): | 198 | if all([service_id, characteristic_id, message]): |
| 199 | if encoding == "base64": | 199 | if encoding == "base64": |
| 200 | message_bytes = base64.b64decode(message) | 200 | message_bytes = base64.b64decode(message) |
| 201 | print("write message_bytes",message_bytes) | ||
| 201 | else: | 202 | else: |
| 203 | print("write message",message) | ||
| 202 | message_bytes = message.encode(encoding) | 204 | message_bytes = message.encode(encoding) |
| 205 | |||
| 203 | 206 | ||
| 204 | await self.client.write_gatt_char(characteristic_id, message_bytes) | 207 | await self.client.write_gatt_char(characteristic_id, message_bytes) |
| 205 | response = json.dumps({ | 208 | response = json.dumps({ |
| ... | @@ -216,6 +219,7 @@ class BLEClient: | ... | @@ -216,6 +219,7 @@ class BLEClient: |
| 216 | 219 | ||
| 217 | if all([service_id, characteristic_id]): | 220 | if all([service_id, characteristic_id]): |
| 218 | data = await self.client.read_gatt_char(characteristic_id) | 221 | data = await self.client.read_gatt_char(characteristic_id) |
| 222 | print('read-data',base64.decode(data)) | ||
| 219 | response = json.dumps({ | 223 | response = json.dumps({ |
| 220 | "jsonrpc": "2.0", | 224 | "jsonrpc": "2.0", |
| 221 | "result": { | 225 | "result": { |
| ... | @@ -285,6 +289,7 @@ class BLEClient: | ... | @@ -285,6 +289,7 @@ class BLEClient: |
| 285 | 289 | ||
| 286 | # 解码当前数据用于比较 | 290 | # 解码当前数据用于比较 |
| 287 | current_message = base64.b64encode(data).decode('utf-8') | 291 | current_message = base64.b64encode(data).decode('utf-8') |
| 292 | print('notification_handler current_message',base64.b64decode(current_message)) | ||
| 288 | 293 | ||
| 289 | # 过滤逻辑 | 294 | # 过滤逻辑 |
| 290 | if current_message == last_message and (current_time - last_time) < 0.5: | 295 | if current_message == last_message and (current_time - last_time) < 0.5: | ... | ... |
s6.9.2.py_0706
0 → 100644
| 1 | # -*- coding: utf-8 -*- | ||
| 2 | import sys | ||
| 3 | import asyncio | ||
| 4 | import websockets | ||
| 5 | from bleak import BleakScanner, BleakClient | ||
| 6 | import json | ||
| 7 | import base64 | ||
| 8 | import threading | ||
| 9 | from collections import defaultdict | ||
| 10 | import socket | ||
| 11 | import time | ||
| 12 | |||
| 13 | import platform | ||
| 14 | |||
| 15 | # 方法1:通过sys模块快速判断(推荐) | ||
| 16 | if sys.platform.startswith('win32'): | ||
| 17 | import winrt.windows.foundation.collections # noqa | ||
| 18 | import winrt.windows.devices.bluetooth # noqa | ||
| 19 | import winrt.windows.devices.bluetooth.advertisement # noq | ||
| 20 | print("当前运行在Windows系统") | ||
| 21 | elif sys.platform.startswith('linux'): | ||
| 22 | print("当前运行在Linux系统") | ||
| 23 | elif sys.platform.startswith('darwin'): | ||
| 24 | print("当前运行在macOS系统") | ||
| 25 | # 添加线程锁以确保日志写入的原子性 | ||
| 26 | write_lock = threading.Lock() | ||
| 27 | |||
| 28 | def log_message_sync(direction, message): | ||
| 29 | """同步日志记录函数""" | ||
| 30 | log_entry = f"{direction}: {message}\n" | ||
| 31 | print(log_entry, end='') # 控制台仍然输出 | ||
| 32 | with write_lock: | ||
| 33 | with open('b.log', 'a', encoding='utf-8') as f: | ||
| 34 | f.write(log_entry) | ||
| 35 | |||
| 36 | async def log_message(direction, message): | ||
| 37 | """异步封装日志记录""" | ||
| 38 | loop = asyncio.get_event_loop() | ||
| 39 | await loop.run_in_executor(None, log_message_sync, direction, message) | ||
| 40 | |||
| 41 | def is_port_in_use(port, host='localhost'): | ||
| 42 | """检查端口是否被占用""" | ||
| 43 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: | ||
| 44 | try: | ||
| 45 | s.bind((host, port)) | ||
| 46 | return False | ||
| 47 | except socket.error: | ||
| 48 | return True | ||
| 49 | |||
| 50 | async def self_test(websocket): | ||
| 51 | """发送自检测试消息""" | ||
| 52 | test_message = json.dumps({ | ||
| 53 | "jsonrpc": "2.0", | ||
| 54 | "method": "ping", | ||
| 55 | "params": {"timestamp": int(time.time())}, | ||
| 56 | "id": "test" | ||
| 57 | }) | ||
| 58 | await log_message("自测", test_message) | ||
| 59 | await websocket.send(test_message) | ||
| 60 | |||
| 61 | try: | ||
| 62 | # 等待响应,设置超时 | ||
| 63 | response = await asyncio.wait_for(websocket.recv(), timeout=5.0) | ||
| 64 | await log_message("自测响应", response) | ||
| 65 | return True | ||
| 66 | except asyncio.TimeoutError: | ||
| 67 | await log_message("自测", "自测超时,未收到响应") | ||
| 68 | return False | ||
| 69 | except Exception as e: | ||
| 70 | await log_message("自测", f"自测异常: {str(e)}") | ||
| 71 | return False | ||
| 72 | |||
| 73 | class BLEClient: | ||
| 74 | def __init__(self): | ||
| 75 | self.target_device = None | ||
| 76 | self.client = None | ||
| 77 | self.services = [] | ||
| 78 | self.optional_services = [] | ||
| 79 | self.websocket = None | ||
| 80 | self.notification_records = defaultdict(lambda: (None, 0)) # 特征ID: (最后消息, 时间戳) | ||
| 81 | |||
| 82 | def on_disconnect(self, client): | ||
| 83 | print("BLE连接断开,关闭WebSocket") | ||
| 84 | if self.websocket and not self.websocket.closed: | ||
| 85 | asyncio.create_task(self.close_websocket()) | ||
| 86 | |||
| 87 | async def close_websocket(self): | ||
| 88 | await self.websocket.close() | ||
| 89 | self.websocket = None | ||
| 90 | |||
| 91 | def detection_callback(self, device, advertisement_data): | ||
| 92 | if any(service_uuid in advertisement_data.service_uuids for service_uuid in self.services): | ||
| 93 | self.target_device = (device, advertisement_data) | ||
| 94 | if not self.target_device: | ||
| 95 | print("未找到匹配设备") | ||
| 96 | return | ||
| 97 | else: | ||
| 98 | device, adv_data = self.target_device | ||
| 99 | print("\n找到目标设备:") | ||
| 100 | print(f"设备名称: {device.name}") | ||
| 101 | print(f"设备地址: {device.address}") | ||
| 102 | print(f"信号强度: {device.rssi} dBm") | ||
| 103 | print("\n广播信息:") | ||
| 104 | print(f"服务UUID列表: {adv_data.service_uuids}") | ||
| 105 | print(f"制造商数据: {adv_data.manufacturer_data}") | ||
| 106 | print(f"服务数据: {adv_data.service_data}") | ||
| 107 | print(f"本地名称: {adv_data.local_name}") | ||
| 108 | return self.target_device | ||
| 109 | |||
| 110 | async def handle_client(self, websocket, path): | ||
| 111 | self.websocket = websocket | ||
| 112 | if path != "/scratch/ble": | ||
| 113 | await websocket.close(code=1003, reason="Path not allowed") | ||
| 114 | return | ||
| 115 | |||
| 116 | try: | ||
| 117 | async for message in websocket: | ||
| 118 | try: | ||
| 119 | await log_message("接收", message) | ||
| 120 | request = json.loads(message) | ||
| 121 | |||
| 122 | if request["jsonrpc"] != "2.0": | ||
| 123 | continue | ||
| 124 | |||
| 125 | method = request.get("method") | ||
| 126 | params = request.get("params", {}) | ||
| 127 | request_id = request.get("id") | ||
| 128 | |||
| 129 | if method == "discover": | ||
| 130 | self.services = [] | ||
| 131 | for filt in params.get("filters", [{}]): | ||
| 132 | self.services.extend(filt.get("services", [])) | ||
| 133 | self.optional_services = params.get("optionalServices", []) | ||
| 134 | |||
| 135 | scanner = BleakScanner(scanning_mode="active") | ||
| 136 | scanner.register_detection_callback(self.detection_callback) | ||
| 137 | |||
| 138 | max_retries = 3 | ||
| 139 | found = False | ||
| 140 | for attempt in range(max_retries): | ||
| 141 | self.target_device = None | ||
| 142 | await scanner.start() | ||
| 143 | await asyncio.sleep(5) | ||
| 144 | await scanner.stop() | ||
| 145 | |||
| 146 | if self.target_device: | ||
| 147 | found = True | ||
| 148 | break | ||
| 149 | |||
| 150 | if attempt < max_retries - 1: | ||
| 151 | print(f"未找到设备,第{attempt+1}次重试...") | ||
| 152 | await asyncio.sleep(3) | ||
| 153 | |||
| 154 | if found: | ||
| 155 | device, adv_data = self.target_device | ||
| 156 | discover_response = json.dumps({ | ||
| 157 | "jsonrpc": "2.0", | ||
| 158 | "method": "didDiscoverPeripheral", | ||
| 159 | "params": { | ||
| 160 | "name": device.name, | ||
| 161 | "peripheralId": device.address, | ||
| 162 | "rssi": device.rssi | ||
| 163 | } | ||
| 164 | }) | ||
| 165 | await log_message("下发", discover_response) | ||
| 166 | await websocket.send(discover_response) | ||
| 167 | |||
| 168 | result_response = json.dumps({ | ||
| 169 | "jsonrpc": "2.0", | ||
| 170 | "result": None, | ||
| 171 | "id": request_id | ||
| 172 | }) | ||
| 173 | await log_message("下发", result_response) | ||
| 174 | await websocket.send(result_response) | ||
| 175 | |||
| 176 | elif method == "connect": | ||
| 177 | peripheral_id = params.get("peripheralId") | ||
| 178 | if peripheral_id: | ||
| 179 | self.client = BleakClient(peripheral_id) | ||
| 180 | self.client.set_disconnected_callback(self.on_disconnect) | ||
| 181 | await self.client.connect() | ||
| 182 | |||
| 183 | if self.client.is_connected: | ||
| 184 | response = json.dumps({ | ||
| 185 | "jsonrpc": "2.0", | ||
| 186 | "result": None, | ||
| 187 | "id": request_id | ||
| 188 | }) | ||
| 189 | # await log_message("下发", response) | ||
| 190 | await websocket.send(response) | ||
| 191 | |||
| 192 | elif method == "write": | ||
| 193 | service_id = params.get("serviceId") | ||
| 194 | characteristic_id = params.get("characteristicId") | ||
| 195 | message = params.get("message") | ||
| 196 | encoding = params.get("encoding", "utf-8") | ||
| 197 | |||
| 198 | if all([service_id, characteristic_id, message]): | ||
| 199 | if encoding == "base64": | ||
| 200 | message_bytes = base64.b64decode(message) | ||
| 201 | print("write message_bytes",message_bytes) | ||
| 202 | else: | ||
| 203 | print("write message",message) | ||
| 204 | message_bytes = message.encode(encoding) | ||
| 205 | |||
| 206 | |||
| 207 | await self.client.write_gatt_char(characteristic_id, message_bytes) | ||
| 208 | response = json.dumps({ | ||
| 209 | "jsonrpc": "2.0", | ||
| 210 | "result": None, | ||
| 211 | "id": request_id | ||
| 212 | }) | ||
| 213 | await log_message("下发", response) | ||
| 214 | await websocket.send(response) | ||
| 215 | |||
| 216 | elif method == "read": | ||
| 217 | service_id = params.get("serviceId") | ||
| 218 | characteristic_id = params.get("characteristicId") | ||
| 219 | |||
| 220 | if all([service_id, characteristic_id]): | ||
| 221 | data = await self.client.read_gatt_char(characteristic_id) | ||
| 222 | print('read-data',base64.decode(data)) | ||
| 223 | response = json.dumps({ | ||
| 224 | "jsonrpc": "2.0", | ||
| 225 | "result": { | ||
| 226 | "serviceId": service_id, | ||
| 227 | "characteristicId": characteristic_id, | ||
| 228 | "message": base64.b64encode(data).decode("utf-8") | ||
| 229 | }, | ||
| 230 | "id": request_id | ||
| 231 | }) | ||
| 232 | await log_message("下发", response) | ||
| 233 | await websocket.send(response) | ||
| 234 | |||
| 235 | elif method == "startNotifications": | ||
| 236 | service_id = params.get("serviceId") | ||
| 237 | characteristic_id = params.get("characteristicId") | ||
| 238 | |||
| 239 | if all([service_id, characteristic_id]): | ||
| 240 | await self.client.start_notify( | ||
| 241 | characteristic_id, | ||
| 242 | self.notification_handler(websocket, service_id, characteristic_id) | ||
| 243 | ) | ||
| 244 | response = json.dumps({ | ||
| 245 | "jsonrpc": "2.0", | ||
| 246 | "result": None, | ||
| 247 | "id": request_id | ||
| 248 | }) | ||
| 249 | await log_message("下发", response) | ||
| 250 | await websocket.send(response) | ||
| 251 | |||
| 252 | elif method == "ping": | ||
| 253 | # 处理ping请求,返回pong响应 | ||
| 254 | response = json.dumps({ | ||
| 255 | "jsonrpc": "2.0", | ||
| 256 | "result": {"pong": True, "timestamp": int(time.time())}, | ||
| 257 | "id": request_id | ||
| 258 | }) | ||
| 259 | await log_message("下发", response) | ||
| 260 | await websocket.send(response) | ||
| 261 | |||
| 262 | except json.JSONDecodeError: | ||
| 263 | error_msg = json.dumps({ | ||
| 264 | "jsonrpc": "2.0", | ||
| 265 | "result": {"message": "Parse error"}, | ||
| 266 | "id": None | ||
| 267 | }) | ||
| 268 | await log_message("下发", error_msg) | ||
| 269 | except Exception as e: | ||
| 270 | error_msg = json.dumps({ | ||
| 271 | "jsonrpc": "2.0", | ||
| 272 | "result": {"message": str(e)}, | ||
| 273 | "id": request.get("id") if request else None | ||
| 274 | }) | ||
| 275 | await log_message("下发", error_msg) | ||
| 276 | |||
| 277 | except websockets.exceptions.ConnectionClosed: | ||
| 278 | print("WebSocket连接关闭") | ||
| 279 | finally: | ||
| 280 | if self.client and self.client.is_connected: | ||
| 281 | await self.client.disconnect() | ||
| 282 | self.client = None | ||
| 283 | self.target_device = None | ||
| 284 | |||
| 285 | def notification_handler(self, websocket, service_id, characteristic_id): | ||
| 286 | async def callback(sender, data): | ||
| 287 | current_time = asyncio.get_event_loop().time() | ||
| 288 | last_message, last_time = self.notification_records[characteristic_id] | ||
| 289 | |||
| 290 | # 解码当前数据用于比较 | ||
| 291 | current_message = base64.b64encode(data).decode('utf-8') | ||
| 292 | print('notification_handler current_message',base64.b64decode(current_message)) | ||
| 293 | |||
| 294 | # 过滤逻辑 | ||
| 295 | if current_message == last_message and (current_time - last_time) < 0.5: | ||
| 296 | return | ||
| 297 | |||
| 298 | # 更新记录 | ||
| 299 | self.notification_records[characteristic_id] = (current_message, current_time) | ||
| 300 | |||
| 301 | response = json.dumps({ | ||
| 302 | "jsonrpc": "2.0", | ||
| 303 | "method": "characteristicDidChange", | ||
| 304 | "params": { | ||
| 305 | "serviceId": service_id, | ||
| 306 | "characteristicId": characteristic_id, | ||
| 307 | "message": current_message | ||
| 308 | } | ||
| 309 | }) | ||
| 310 | await log_message("下发", response) | ||
| 311 | await websocket.send(response) | ||
| 312 | return callback | ||
| 313 | |||
| 314 | async def check_port_and_start_server(port=20111, host='localhost'): | ||
| 315 | """检查端口并启动服务器""" | ||
| 316 | if is_port_in_use(port, host): | ||
| 317 | print(f"错误: 端口 {port} 已被占用,无法启动服务") | ||
| 318 | return False | ||
| 319 | |||
| 320 | print(f"端口 {port} 可用,正在启动服务...") | ||
| 321 | server = await websockets.serve( | ||
| 322 | lambda websocket, path: BLEClient().handle_client(websocket, path), | ||
| 323 | host, port | ||
| 324 | ) | ||
| 325 | |||
| 326 | print(f"WebSocket服务已启动: ws://{host}:{port}/scratch/ble") | ||
| 327 | print("日志文件路径: ./b.log") | ||
| 328 | |||
| 329 | # 执行自检测试 | ||
| 330 | try: | ||
| 331 | async with websockets.connect(f"ws://{host}:{port}/scratch/ble") as websocket: | ||
| 332 | print("正在执行自检测试...") | ||
| 333 | test_result = await self_test(websocket) | ||
| 334 | if test_result: | ||
| 335 | print("自检测试成功: 服务正常运行") | ||
| 336 | else: | ||
| 337 | print("自检测试失败: 服务可能存在问题") | ||
| 338 | except Exception as e: | ||
| 339 | print(f"自检测试异常: {str(e)}") | ||
| 340 | |||
| 341 | return server | ||
| 342 | |||
| 343 | async def main(): | ||
| 344 | server = await check_port_and_start_server() | ||
| 345 | if server: | ||
| 346 | await asyncio.Future() # 保持服务器运行 | ||
| 347 | |||
| 348 | if __name__ == "__main__": | ||
| 349 | asyncio.run(main()) | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
s6.9.3.py
0 → 100644
| 1 | # -*- coding: utf-8 -*- | ||
| 2 | import sys | ||
| 3 | import asyncio | ||
| 4 | import websockets | ||
| 5 | import json | ||
| 6 | import base64 | ||
| 7 | import threading | ||
| 8 | from collections import defaultdict | ||
| 9 | import socket | ||
| 10 | import time | ||
| 11 | import platform | ||
| 12 | |||
| 13 | # 在导入其他模块前处理Windows特有的COM初始化问题 | ||
| 14 | if sys.platform.startswith('win32'): | ||
| 15 | print("Windows系统: 处理COM初始化") | ||
| 16 | try: | ||
| 17 | from bleak.backends.winrt.util import uninitialize_sta, allow_sta, assert_mta | ||
| 18 | # 取消其他库可能设置的STA初始化 | ||
| 19 | uninitialize_sta() | ||
| 20 | # 允许STA模式运行(需要确保有Windows事件循环) | ||
| 21 | allow_sta() | ||
| 22 | except ImportError: | ||
| 23 | print("警告: 无法导入Windows特有的bleak工具") | ||
| 24 | except Exception as e: | ||
| 25 | print(f"Windows初始化错误: {str(e)}") | ||
| 26 | |||
| 27 | # 导入其他模块 | ||
| 28 | from bleak import BleakScanner, BleakClient | ||
| 29 | |||
| 30 | # 添加线程锁以确保日志写入的原子性 | ||
| 31 | write_lock = threading.Lock() | ||
| 32 | |||
| 33 | def log_message_sync(direction, message): | ||
| 34 | """同步日志记录函数""" | ||
| 35 | log_entry = f"{direction}: {message}\n" | ||
| 36 | print(log_entry, end='') # 控制台仍然输出 | ||
| 37 | with write_lock: | ||
| 38 | with open('b.log', 'a', encoding='utf-8') as f: | ||
| 39 | f.write(log_entry) | ||
| 40 | |||
| 41 | async def log_message(direction, message): | ||
| 42 | """异步封装日志记录""" | ||
| 43 | loop = asyncio.get_event_loop() | ||
| 44 | await loop.run_in_executor(None, log_message_sync, direction, message) | ||
| 45 | |||
| 46 | def is_port_in_use(port, host='localhost'): | ||
| 47 | """检查端口是否被占用""" | ||
| 48 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: | ||
| 49 | try: | ||
| 50 | s.bind((host, port)) | ||
| 51 | return False | ||
| 52 | except socket.error: | ||
| 53 | return True | ||
| 54 | |||
| 55 | async def self_test(websocket): | ||
| 56 | """发送自检测试消息""" | ||
| 57 | test_message = json.dumps({ | ||
| 58 | "jsonrpc": "2.0", | ||
| 59 | "method": "ping", | ||
| 60 | "params": {"timestamp": int(time.time())}, | ||
| 61 | "id": "test" | ||
| 62 | }) | ||
| 63 | await log_message("自测", test_message) | ||
| 64 | await websocket.send(test_message) | ||
| 65 | |||
| 66 | try: | ||
| 67 | # 等待响应,设置超时 | ||
| 68 | response = await asyncio.wait_for(websocket.recv(), timeout=5.0) | ||
| 69 | await log_message("自测响应", response) | ||
| 70 | return True | ||
| 71 | except asyncio.TimeoutError: | ||
| 72 | await log_message("自测", "自测超时,未收到响应") | ||
| 73 | return False | ||
| 74 | except Exception as e: | ||
| 75 | await log_message("自测", f"自测异常: {str(e)}") | ||
| 76 | return False | ||
| 77 | |||
| 78 | class BLEClient: | ||
| 79 | def __init__(self): | ||
| 80 | self.target_device = None | ||
| 81 | self.client = None | ||
| 82 | self.services = [] | ||
| 83 | self.optional_services = [] | ||
| 84 | self.websocket = None | ||
| 85 | self.notification_records = defaultdict(lambda: (None, 0)) # 特征ID: (最后消息, 时间戳) | ||
| 86 | self.is_windows = sys.platform.startswith('win32') | ||
| 87 | |||
| 88 | def on_disconnect(self, client): | ||
| 89 | print("BLE连接断开,关闭WebSocket") | ||
| 90 | if self.websocket and not self.websocket.closed: | ||
| 91 | asyncio.create_task(self.close_websocket()) | ||
| 92 | |||
| 93 | async def close_websocket(self): | ||
| 94 | await self.websocket.close() | ||
| 95 | self.websocket = None | ||
| 96 | |||
| 97 | def detection_callback(self, device, advertisement_data): | ||
| 98 | if any(service_uuid in advertisement_data.service_uuids for service_uuid in self.services): | ||
| 99 | self.target_device = (device, advertisement_data) | ||
| 100 | if not self.target_device: | ||
| 101 | print("未找到匹配设备") | ||
| 102 | return | ||
| 103 | else: | ||
| 104 | device, adv_data = self.target_device | ||
| 105 | print("\n找到目标设备:") | ||
| 106 | print(f"设备名称: {device.name}") | ||
| 107 | print(f"设备地址: {device.address}") | ||
| 108 | print(f"信号强度: {device.rssi} dBm") | ||
| 109 | print("\n广播信息:") | ||
| 110 | print(f"服务UUID列表: {adv_data.service_uuids}") | ||
| 111 | print(f"制造商数据: {adv_data.manufacturer_data}") | ||
| 112 | print(f"服务数据: {adv_data.service_data}") | ||
| 113 | print(f"本地名称: {adv_data.local_name}") | ||
| 114 | return self.target_device | ||
| 115 | |||
| 116 | async def create_bleak_client(self, address): | ||
| 117 | """创建BleakClient,考虑Windows平台的特性""" | ||
| 118 | if self.is_windows: | ||
| 119 | print("Windows系统: 使用带地址类型的客户端") | ||
| 120 | winrt_args = {"address_type": "public"} # 或 "random" | ||
| 121 | return BleakClient(address, winrt=winrt_args) | ||
| 122 | else: | ||
| 123 | return BleakClient(address) | ||
| 124 | |||
| 125 | async def handle_client(self, websocket, path): | ||
| 126 | self.websocket = websocket | ||
| 127 | if path != "/scratch/ble": | ||
| 128 | await websocket.close(code=1003, reason="Path not allowed") | ||
| 129 | return | ||
| 130 | |||
| 131 | try: | ||
| 132 | async for message in websocket: | ||
| 133 | try: | ||
| 134 | await log_message("接收", message) | ||
| 135 | request = json.loads(message) | ||
| 136 | |||
| 137 | if request["jsonrpc"] != "2.0": | ||
| 138 | continue | ||
| 139 | |||
| 140 | method = request.get("method") | ||
| 141 | params = request.get("params", {}) | ||
| 142 | request_id = request.get("id") | ||
| 143 | |||
| 144 | if method == "discover": | ||
| 145 | self.services = [] | ||
| 146 | for filt in params.get("filters", [{}]): | ||
| 147 | self.services.extend(filt.get("services", [])) | ||
| 148 | self.optional_services = params.get("optionalServices", []) | ||
| 149 | |||
| 150 | # Windows使用特定扫描模式配置 | ||
| 151 | scanning_args = {"scanning_mode": "active"} | ||
| 152 | scanner = BleakScanner(scanning_mode="active") | ||
| 153 | scanner.register_detection_callback(self.detection_callback) | ||
| 154 | |||
| 155 | max_retries = 3 | ||
| 156 | found = False | ||
| 157 | for attempt in range(max_retries): | ||
| 158 | self.target_device = None | ||
| 159 | await scanner.start() | ||
| 160 | await asyncio.sleep(5) | ||
| 161 | await scanner.stop() | ||
| 162 | |||
| 163 | if self.target_device: | ||
| 164 | found = True | ||
| 165 | break | ||
| 166 | |||
| 167 | if attempt < max_retries - 1: | ||
| 168 | print(f"未找到设备,第{attempt+1}次重试...") | ||
| 169 | await asyncio.sleep(3) | ||
| 170 | |||
| 171 | if found: | ||
| 172 | device, adv_data = self.target_device | ||
| 173 | discover_response = json.dumps({ | ||
| 174 | "jsonrpc": "2.0", | ||
| 175 | "method": "didDiscoverPeripheral", | ||
| 176 | "params": { | ||
| 177 | "name": device.name, | ||
| 178 | "peripheralId": device.address, | ||
| 179 | "rssi": device.rssi | ||
| 180 | } | ||
| 181 | }) | ||
| 182 | await log_message("下发", discover_response) | ||
| 183 | await websocket.send(discover_response) | ||
| 184 | |||
| 185 | result_response = json.dumps({ | ||
| 186 | "jsonrpc": "2.0", | ||
| 187 | "result": None, | ||
| 188 | "id": request_id | ||
| 189 | }) | ||
| 190 | await log_message("下发", result_response) | ||
| 191 | await websocket.send(result_response) | ||
| 192 | |||
| 193 | elif method == "connect": | ||
| 194 | peripheral_id = params.get("peripheralId") | ||
| 195 | if peripheral_id: | ||
| 196 | self.client = await self.create_bleak_client(peripheral_id) | ||
| 197 | self.client.set_disconnected_callback(self.on_disconnect) | ||
| 198 | await self.client.connect() | ||
| 199 | |||
| 200 | if self.client.is_connected: | ||
| 201 | response = json.dumps({ | ||
| 202 | "jsonrpc": "2.0", | ||
| 203 | "result": None, | ||
| 204 | "id": request_id | ||
| 205 | }) | ||
| 206 | await websocket.send(response) | ||
| 207 | |||
| 208 | elif method == "write": | ||
| 209 | service_id = params.get("serviceId") | ||
| 210 | characteristic_id = params.get("characteristicId") | ||
| 211 | message = params.get("message") | ||
| 212 | encoding = params.get("encoding", "utf-8") | ||
| 213 | |||
| 214 | if all([service_id, characteristic_id, message]): | ||
| 215 | if encoding == "base64": | ||
| 216 | message_bytes = base64.b64decode(message) | ||
| 217 | else: | ||
| 218 | message_bytes = message.encode(encoding) | ||
| 219 | |||
| 220 | await self.client.write_gatt_char(characteristic_id, message_bytes) | ||
| 221 | response = json.dumps({ | ||
| 222 | "jsonrpc": "2.0", | ||
| 223 | "result": None, | ||
| 224 | "id": request_id | ||
| 225 | }) | ||
| 226 | await log_message("下发", response) | ||
| 227 | await websocket.send(response) | ||
| 228 | |||
| 229 | elif method == "read": | ||
| 230 | service_id = params.get("serviceId") | ||
| 231 | characteristic_id = params.get("characteristicId") | ||
| 232 | |||
| 233 | if all([service_id, characteristic_id]): | ||
| 234 | data = await self.client.read_gatt_char(characteristic_id) | ||
| 235 | response = json.dumps({ | ||
| 236 | "jsonrpc": "2.0", | ||
| 237 | "result": { | ||
| 238 | "serviceId": service_id, | ||
| 239 | "characteristicId": characteristic_id, | ||
| 240 | "message": base64.b64encode(data).decode("utf-8") | ||
| 241 | }, | ||
| 242 | "id": request_id | ||
| 243 | }) | ||
| 244 | await log_message("下发", response) | ||
| 245 | await websocket.send(response) | ||
| 246 | |||
| 247 | elif method == "startNotifications": | ||
| 248 | service_id = params.get("serviceId") | ||
| 249 | characteristic_id = params.get("characteristicId") | ||
| 250 | |||
| 251 | if all([service_id, characteristic_id]): | ||
| 252 | await self.client.start_notify( | ||
| 253 | characteristic_id, | ||
| 254 | self.notification_handler(websocket, service_id, characteristic_id) | ||
| 255 | ) | ||
| 256 | response = json.dumps({ | ||
| 257 | "jsonrpc": "2.0", | ||
| 258 | "result": None, | ||
| 259 | "id": request_id | ||
| 260 | }) | ||
| 261 | await log_message("下发", response) | ||
| 262 | await websocket.send(response) | ||
| 263 | |||
| 264 | elif method == "ping": | ||
| 265 | # 处理ping请求,返回pong响应 | ||
| 266 | response = json.dumps({ | ||
| 267 | "jsonrpc": "2.0", | ||
| 268 | "result": {"pong": True, "timestamp": int(time.time())}, | ||
| 269 | "id": request_id | ||
| 270 | }) | ||
| 271 | await log_message("下发", response) | ||
| 272 | await websocket.send(response) | ||
| 273 | |||
| 274 | except json.JSONDecodeError: | ||
| 275 | error_msg = json.dumps({ | ||
| 276 | "jsonrpc": "2.0", | ||
| 277 | "error": {"message": "Parse error", "code": -32700}, | ||
| 278 | "id": None | ||
| 279 | }) | ||
| 280 | await log_message("下发", error_msg) | ||
| 281 | await websocket.send(error_msg) | ||
| 282 | except Exception as e: | ||
| 283 | error_msg = json.dumps({ | ||
| 284 | "jsonrpc": "2.0", | ||
| 285 | "error": {"message": str(e), "code": -32000}, | ||
| 286 | "id": request.get("id") if request else None | ||
| 287 | }) | ||
| 288 | await log_message("下发", error_msg) | ||
| 289 | await websocket.send(error_msg) | ||
| 290 | |||
| 291 | except websockets.exceptions.ConnectionClosed: | ||
| 292 | print("WebSocket连接关闭") | ||
| 293 | finally: | ||
| 294 | if self.client and self.client.is_connected: | ||
| 295 | await self.client.disconnect() | ||
| 296 | self.client = None | ||
| 297 | self.target_device = None | ||
| 298 | |||
| 299 | def notification_handler(self, websocket, service_id, characteristic_id): | ||
| 300 | async def callback(sender, data): | ||
| 301 | current_time = asyncio.get_event_loop().time() | ||
| 302 | last_message, last_time = self.notification_records[characteristic_id] | ||
| 303 | |||
| 304 | # 解码当前数据用于比较 | ||
| 305 | current_message = base64.b64encode(data).decode('utf-8') | ||
| 306 | |||
| 307 | # 过滤逻辑 | ||
| 308 | if current_message == last_message and (current_time - last_time) < 0.5: | ||
| 309 | return | ||
| 310 | |||
| 311 | # 更新记录 | ||
| 312 | self.notification_records[characteristic_id] = (current_message, current_time) | ||
| 313 | |||
| 314 | response = json.dumps({ | ||
| 315 | "jsonrpc": "2.0", | ||
| 316 | "method": "characteristicDidChange", | ||
| 317 | "params": { | ||
| 318 | "serviceId": service_id, | ||
| 319 | "characteristicId": characteristic_id, | ||
| 320 | "message": current_message | ||
| 321 | } | ||
| 322 | }) | ||
| 323 | await log_message("下发", response) | ||
| 324 | await websocket.send(response) | ||
| 325 | return callback | ||
| 326 | |||
| 327 | async def check_port_and_start_server(port=20111, host='localhost'): | ||
| 328 | """检查端口并启动服务器""" | ||
| 329 | if is_port_in_use(port, host): | ||
| 330 | print(f"错误: 端口 {port} 已被占用,无法启动服务") | ||
| 331 | return False | ||
| 332 | |||
| 333 | print(f"端口 {port} 可用,正在启动服务...") | ||
| 334 | server = await websockets.serve( | ||
| 335 | lambda websocket, path: BLEClient().handle_client(websocket, path), | ||
| 336 | host, port | ||
| 337 | ) | ||
| 338 | |||
| 339 | print(f"WebSocket服务已启动: ws://{host}:{port}/scratch/ble") | ||
| 340 | print("日志文件路径: ./b.log") | ||
| 341 | |||
| 342 | # 执行自检测试 | ||
| 343 | try: | ||
| 344 | async with websockets.connect(f"ws://{host}:{port}/scratch/ble") as websocket: | ||
| 345 | print("正在执行自检测试...") | ||
| 346 | test_result = await self_test(websocket) | ||
| 347 | if test_result: | ||
| 348 | print("自检测试成功: 服务正常运行") | ||
| 349 | else: | ||
| 350 | print("自检测试失败: 服务可能存在问题") | ||
| 351 | except Exception as e: | ||
| 352 | print(f"自检测试异常: {str(e)}") | ||
| 353 | |||
| 354 | return server | ||
| 355 | |||
| 356 | async def main(): | ||
| 357 | # Windows上保证事件循环设置正确 | ||
| 358 | if sys.platform.startswith('win32'): | ||
| 359 | import asyncio | ||
| 360 | asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) | ||
| 361 | |||
| 362 | server = await check_port_and_start_server() | ||
| 363 | if server: | ||
| 364 | await asyncio.Future() # 保持服务器运行 | ||
| 365 | |||
| 366 | if __name__ == "__main__": | ||
| 367 | asyncio.run(main()) | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or sign in to post a comment