b0685f53 by huangyf2

init

0 parents
1 dist
2 build
3 __pycache__
...\ No newline at end of file ...\ No newline at end of file
No preview for this file type
No preview for this file type
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!