92bcaa4a by huangyf2

1

1 parent 61a6b1a6
1 dist
2 build
3 __pycache__
...\ No newline at end of file ...\ No newline at end of file
No preview for this file type
1 r'r'rv
...\ No newline at end of file ...\ No newline at end of file
This file is too large to display.
1 ##
2 重要文件就是这个了
3
4 ##
5 tray_icon.py 是来进行显示系统托盘图标的
6
7 ##
8 主函数 we.py
9 使用了 注释和引用图标逻辑
10 # -*- coding: utf-8 -*--
11 import asyncio
12 import websockets
13 from bleak import BleakScanner, BleakClient
14 import json
15 import base64
16 import threading
17 from tray_icon import start_tray_icon
18 start_tray_icon("app.ico")
19 ## 打包需要python3 然后
20 使用 pip cache dir 观察python3 的路径
21
22 安装环境
23 ##
24 pip install pyinstaller
25 pip install bleak
26 pip install pystray pillow
27
28 ##
29 打包指令 产生一个带窗口的程序 打包完后可以随便改exe 名字
30 C:\Users\Admin\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0\LocalCache\local-packages\Python313\Scripts\pyinstaller.exe --onefile --icon=data\app.ico we.py --add-data "data;data"
31
32 ##
33 端口占用
34 netstat -ano | findstr :201111
35 taskkill /PID /F 1234
36
37 ##
38 app.ico 需要有特定格式 不能随便图片
39
40
41
42 ##
43 运行时的路径 和打包后的路径 是不一致的 如何解决 见具体函数
44
45 icon_path = resource_path(resource_path("data/app.ico"))
1 import pystray
2 from PIL import Image, ImageDraw
3 import threading
4
5 def create_image(width, height, color1, color2):
6 image = Image.new("RGB", (width, height), color1)
7 dc = ImageDraw.Draw(image)
8 dc.rectangle((width // 2, 0, width, height // 2), fill=color2)
9 return image
10
11 def run_icon(icon_path):
12 image = Image.open(icon_path) # 加载自定义图标
13 icon = pystray.Icon("test_icon", image, "My System Tray Icon")
14 icon.run()
15
16 def start_tray_icon(icon_path):
17 threading.Thread(target=run_icon, args=(icon_path,), daemon=True).start()
...\ No newline at end of file ...\ No newline at end of file
1 # -*- coding: utf-8 -*--
2 import asyncio
3 import websockets
4 from bleak import BleakScanner, BleakClient
5 import json
6 import base64
7 import threading
8 from tray_icon import start_tray_icon
9 import os
10 import sys
11
12 def resource_path(relative_path):
13 try:
14 base_path = sys._MEIPASS # 打包后运行时的临时路径
15 except AttributeError:
16 base_path = os.path.dirname(os.path.abspath(__file__)) # 开发环境下的路径
17 # 使用 os.path.join 拼接路径,并通过 os.path.abspath 转换为绝对路径
18 return os.path.abspath(os.path.join(base_path, relative_path))
19
20 def resource_path(relative_path):
21 """获取资源文件的绝对路径,适用于打包后的程序"""
22 try:
23 base_path = sys._MEIPASS # 打包后运行时的临时路径
24 except AttributeError:
25 base_path = os.path.abspath(".") # 开发环境下的路径
26 return os.path.join(base_path, relative_path)
27
28 # 获取图标路径
29 icon_path = resource_path(resource_path("data/app.ico"))
30
31 # 调用系统托盘图标函数
32 start_tray_icon(icon_path)
33
34 # 添加线程锁以确保日志写入的原子性
35 write_lock = threading.Lock()
36
37 def log_message_sync(direction, message):
38 """同步日志记录函数"""
39 log_entry = f"{direction}: {message}\n"
40 print(log_entry, end='') # 控制台仍然输出
41 with write_lock:
42 with open('a.log', 'a', encoding='utf-8') as f:
43 f.write(log_entry)
44
45 async def log_message(direction, message):
46 """异步封装日志记录"""
47 loop = asyncio.get_event_loop()
48 await loop.run_in_executor(None, log_message_sync, direction, message)
49
50 class BLEClient:
51 def __init__(self):
52 self.target_device = None
53 self.client = None
54 self.services = []
55 self.optional_services = []
56 self.websocket = None
57
58 def on_disconnect(self, client):
59 print("BLE连接断开,关闭WebSocket")
60 if self.websocket and not self.websocket.closed:
61 asyncio.create_task(self.close_websocket())
62
63 async def close_websocket(self):
64 await self.websocket.close()
65 self.websocket = None
66
67 def detection_callback(self, device, advertisement_data):
68 if any(service_uuid in advertisement_data.service_uuids for service_uuid in self.services):
69 self.target_device = (device, advertisement_data)
70 if not self.target_device:
71 print("未找到匹配设备")
72 return
73 else:
74 device, adv_data = self.target_device
75 print("\n找到目标设备:")
76 print(f"设备名称: {device.name}")
77 print(f"设备地址: {device.address}")
78 print(f"信号强度: {device.rssi} dBm")
79 print("\n广播信息:")
80 print(f"服务UUID列表: {adv_data.service_uuids}")
81 print(f"制造商数据: {adv_data.manufacturer_data}")
82 print(f"服务数据: {adv_data.service_data}")
83 print(f"本地名称: {adv_data.local_name}")
84 return self.target_device
85
86 async def handle_client(self, websocket, path):
87 self.websocket = websocket
88 if path != "/scratch/ble":
89 await websocket.close(code=1003, reason="Path not allowed")
90 return
91
92 try:
93 async for message in websocket:
94 try:
95 # 记录接收到的消息
96 await log_message("接收", message)
97
98 request = json.loads(message)
99 if request["jsonrpc"] != "2.0":
100 response = json.dumps({
101 "jsonrpc": "2.0",
102 "result": {"message": "Invalid Request"},
103 "id": request.get("id", None)
104 })
105 await log_message("下发", response)
106 await websocket.send(response)
107 continue
108
109 method = request.get("method")
110 params = request.get("params", {})
111 request_id = request.get("id")
112
113 if method == "discover":
114 self.services = params.get("filters", [{}])[0].get("services", [])
115 self.optional_services = params.get("optionalServices", [])
116
117 scanner = BleakScanner()
118 scanner.register_detection_callback(self.detection_callback)
119
120 # 添加重试机制
121 max_retries = 3
122 found = False
123 for attempt in range(max_retries):
124 self.target_device = None # 重置目标设备
125 await scanner.start()
126 await asyncio.sleep(5)
127 await scanner.stop()
128
129 if self.target_device:
130 found = True
131 break
132
133 if attempt < max_retries - 1: # 最后一次不等待
134 print(f"未找到设备,第{attempt+1}次重试...")
135 await asyncio.sleep(3)
136
137 if not found:
138 # response = json.dumps({
139 # "jsonrpc": "2.0",
140 # "result": {"message": "Device not found"},
141 # "id": request_id
142 # })
143 # await log_message("下发", response)
144 # await websocket.send(response)
145 continue
146
147 device, adv_data = self.target_device
148 discover_response = json.dumps({
149 "jsonrpc": "2.0",
150 "method": "didDiscoverPeripheral",
151 "params": {
152 "name": device.name,
153 "peripheralId": device.address,
154 "rssi": device.rssi
155 }
156 })
157 await log_message("下发", discover_response)
158 await websocket.send(discover_response)
159
160 result_response = json.dumps({
161 "jsonrpc": "2.0",
162 "result": None,
163 "id": request_id
164 })
165 await log_message("下发", result_response)
166 await websocket.send(result_response)
167
168 elif method == "connect":
169 peripheral_id = params.get("peripheralId")
170 if not peripheral_id:
171 response = json.dumps({
172 "jsonrpc": "2.0",
173 "result": {"message": "Invalid params"},
174 "id": request_id
175 })
176 await log_message("下发", response)
177 await websocket.send(response)
178 continue
179
180 self.client = BleakClient(peripheral_id)
181 self.client.set_disconnected_callback(self.on_disconnect)
182 await self.client.connect()
183 if self.client.is_connected:
184 print(f"已连接至设备: {peripheral_id}")
185 response = json.dumps({
186 "jsonrpc": "2.0",
187 "result": None,
188 "id": request_id
189 })
190 await log_message("下发", response)
191 await websocket.send(response)
192 else:
193 response = json.dumps({
194 "jsonrpc": "2.0",
195 "result": {"message": "Failed to connect"},
196 "id": request_id
197 })
198 await log_message("下发", response)
199 await websocket.send(response)
200
201 elif method == "write":
202 service_id = params.get("serviceId")
203 characteristic_id = params.get("characteristicId")
204 message = params.get("message")
205 encoding = params.get("encoding", "utf-8")
206
207 if not all([service_id, characteristic_id, message]):
208 response = json.dumps({
209 "jsonrpc": "2.0",
210 "result": {"message": "Invalid params"},
211 "id": request_id
212 })
213 await log_message("下发", response)
214 await websocket.send(response)
215 continue
216
217 if encoding == "base64":
218 message_bytes = base64.b64decode(message)
219 else:
220 message_bytes = message.encode(encoding)
221
222 await self.client.write_gatt_char(characteristic_id, message_bytes)
223 response = json.dumps({
224 "jsonrpc": "2.0",
225 "result": None,
226 "id": request_id
227 })
228 await log_message("下发", response)
229 await websocket.send(response)
230
231 elif method == "read":
232 service_id = params.get("serviceId")
233 characteristic_id = params.get("characteristicId")
234
235 if not all([service_id, characteristic_id]):
236 response = json.dumps({
237 "jsonrpc": "2.0",
238 "result": {"message": "Invalid params"},
239 "id": request_id
240 })
241 await log_message("下发", response)
242 await websocket.send(response)
243 continue
244
245 data = await self.client.read_gatt_char(characteristic_id)
246 response = json.dumps({
247 "jsonrpc": "2.0",
248 "result": {
249 "serviceId": service_id,
250 "characteristicId": characteristic_id,
251 "message": base64.b64encode(data).decode("utf-8")
252 },
253 "id": request_id
254 })
255 await log_message("下发", response)
256 await websocket.send(response)
257
258 elif method == "startNotifications":
259 service_id = params.get("serviceId")
260 characteristic_id = params.get("characteristicId")
261
262 if not all([service_id, characteristic_id]):
263 response = json.dumps({
264 "jsonrpc": "2.0",
265 "result": {"message": "Invalid params"},
266 "id": request_id
267 })
268 await log_message("下发", response)
269 await websocket.send(response)
270 continue
271
272 await self.client.start_notify(
273 characteristic_id,
274 self.notification_handler(websocket, service_id, characteristic_id)
275 )
276 response = json.dumps({
277 "jsonrpc": "2.0",
278 "result": None,
279 "id": request_id
280 })
281 await log_message("下发", response)
282 await websocket.send(response)
283
284 else:
285 response = json.dumps({
286 "jsonrpc": "2.0",
287 "result": {"message": "Method not found"},
288 "id": request_id
289 })
290 await log_message("下发", response)
291 await websocket.send(response)
292
293 except json.JSONDecodeError:
294 response = json.dumps({
295 "jsonrpc": "2.0",
296 "result": {"message": "Parse error"},
297 "id": None
298 })
299 await log_message("下发", response)
300 await websocket.send(response)
301 except Exception as e:
302 response = json.dumps({
303 "jsonrpc": "2.0",
304 "result": {"message": str(e)},
305 "id": request_id
306 })
307 await log_message("下发", response)
308 await websocket.send(response)
309
310 except websockets.exceptions.ConnectionClosedOK:
311 print("WebSocket客户端正常断开")
312 except websockets.exceptions.ConnectionClosedError as e:
313 print(f"WebSocket客户端异常断开: {e.code} - {e.reason}")
314 finally:
315 if self.client and self.client.is_connected:
316 await self.client.disconnect()
317 print("BLE设备已主动断开")
318 self.client = None
319 self.target_device = None
320
321 def notification_handler(self, websocket, service_id, characteristic_id):
322 async def callback(sender, data):
323 response = json.dumps({
324 "jsonrpc": "2.0",
325 "method": "characteristicDidChange",
326 "params": {
327 "serviceId": service_id,
328 "characteristicId": characteristic_id,
329 "message": base64.b64encode(data).decode("utf-8")
330 }
331 })
332 await log_message("下发", response)
333 await websocket.send(response)
334 return callback
335
336 async def main():
337 async with websockets.serve(
338 lambda websocket, path: BLEClient().handle_client(websocket, path),
339 "localhost", 20111
340 ):
341 print("WebSocket服务已启动: ws://localhost:20111/scratch/ble")
342 print("日志文件路径: ./b.log")
343 await asyncio.Future() # 永久运行
344
345 if __name__ == "__main__":
346 asyncio.run(main())
...\ No newline at end of file ...\ No newline at end of file
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!