|
|
@@ -1,30 +1,124 @@
|
|
|
import serial
|
|
|
-import time
|
|
|
-
|
|
|
-try:
|
|
|
- # 配置串口参数 - 请根据实际设备修改端口号
|
|
|
- ser = serial.Serial(
|
|
|
- port='COM27', # 修改为实际的端口号
|
|
|
- baudrate=115200,
|
|
|
- parity=serial.PARITY_NONE,
|
|
|
- stopbits=serial.STOPBITS_ONE,
|
|
|
- bytesize=serial.EIGHTBITS,
|
|
|
- timeout=1 # 添加超时设置,避免程序卡死
|
|
|
- )
|
|
|
-
|
|
|
- if ser.is_open:
|
|
|
- print(f"成功打开串口: {ser.port}")
|
|
|
-
|
|
|
- # 发送数据
|
|
|
- data = "Hello, Serial!"
|
|
|
- ser.write(data.encode('utf-8'))
|
|
|
- print(f"发送数据: {data}")
|
|
|
-
|
|
|
- time.sleep(1)
|
|
|
- ser.close()
|
|
|
- print("串口已关闭")
|
|
|
-
|
|
|
-except serial.SerialException as e:
|
|
|
- print(f"串口错误: {e}")
|
|
|
-except Exception as e:
|
|
|
- print(f"发生错误: {e}")
|
|
|
+import serial.tools.list_ports
|
|
|
+import threading
|
|
|
+import tkinter as tk
|
|
|
+from tkinter import ttk, scrolledtext, messagebox
|
|
|
+
|
|
|
+class SerialAssistant:
|
|
|
+ def __init__(self, master):
|
|
|
+ self.master = master
|
|
|
+ self.master.title("Python 串口助手 (青春版)")
|
|
|
+ self.master.geometry("700x450")
|
|
|
+ self.master.resizable(False, False)
|
|
|
+
|
|
|
+ # 串口对象和状态
|
|
|
+ self.ser = None
|
|
|
+ self.is_running = False
|
|
|
+ self.hex_display = tk.BooleanVar(value=False) # 是否以16进制显示
|
|
|
+
|
|
|
+ # 串口设置区
|
|
|
+ frame_top = ttk.LabelFrame(master, text="串口设置", padding=10)
|
|
|
+ frame_top.pack(fill="x")
|
|
|
+
|
|
|
+ ttk.Label(frame_top, text="端口:").grid(row=0, column=0)
|
|
|
+ self.combo_port = ttk.Combobox(frame_top, width=10, values=self.list_serial_ports())
|
|
|
+ self.combo_port.grid(row=0, column=1, padx=5)
|
|
|
+ if self.combo_port["values"]:
|
|
|
+ self.combo_port.current(0)
|
|
|
+
|
|
|
+ ttk.Label(frame_top, text="波特率:").grid(row=0, column=2)
|
|
|
+ self.combo_baud = ttk.Combobox(frame_top, width=10, values=[9600, 19200, 38400, 57600, 115200])
|
|
|
+ self.combo_baud.current(4)
|
|
|
+ self.combo_baud.grid(row=0, column=3, padx=5)
|
|
|
+
|
|
|
+ self.btn_open = ttk.Button(frame_top, text="打开串口", command=self.open_serial)
|
|
|
+ self.btn_open.grid(row=0, column=4, padx=5)
|
|
|
+ self.btn_close = ttk.Button(frame_top, text="关闭串口", command=self.close_serial, state=tk.DISABLED)
|
|
|
+ self.btn_close.grid(row=0, column=5, padx=5)
|
|
|
+
|
|
|
+ ttk.Checkbutton(frame_top, text="16进制显示", variable=self.hex_display).grid(row=0, column=6, padx=10)
|
|
|
+
|
|
|
+ # 接收区
|
|
|
+ frame_rx = ttk.LabelFrame(master, text="接收数据", padding=10)
|
|
|
+ frame_rx.pack(fill="both", expand=True)
|
|
|
+ self.text_rx = scrolledtext.ScrolledText(frame_rx, wrap=tk.WORD, height=15)
|
|
|
+ self.text_rx.pack(fill="both", expand=True)
|
|
|
+
|
|
|
+ # 发送区
|
|
|
+ frame_tx = ttk.LabelFrame(master, text="发送数据", padding=10)
|
|
|
+ frame_tx.pack(fill="x")
|
|
|
+ self.entry_tx = ttk.Entry(frame_tx, width=70)
|
|
|
+ self.entry_tx.pack(side="left", padx=5)
|
|
|
+ ttk.Button(frame_tx, text="发送", command=self.send_data).pack(side="left", padx=5)
|
|
|
+
|
|
|
+ # 状态栏
|
|
|
+ self.status = ttk.Label(master, text="状态: 未连接", relief="sunken", anchor="w")
|
|
|
+ self.status.pack(fill="x", side="bottom")
|
|
|
+
|
|
|
+ def list_serial_ports(self):
|
|
|
+ ports = [port.device for port in serial.tools.list_ports.comports()]
|
|
|
+ return ports if ports else ["无可用端口"]
|
|
|
+
|
|
|
+ def open_serial(self):
|
|
|
+ port = self.combo_port.get()
|
|
|
+ baud = self.combo_baud.get()
|
|
|
+ try:
|
|
|
+ self.ser = serial.Serial(port, baudrate=int(baud), timeout=0)
|
|
|
+ self.is_running = True
|
|
|
+ self.btn_open["state"] = tk.DISABLED
|
|
|
+ self.btn_close["state"] = tk.NORMAL
|
|
|
+ self.status["text"] = f"状态: 已连接 {port} ({baud}bps)"
|
|
|
+ self.text_rx.insert(tk.END, f"[系统] 成功打开串口 {port}\n")
|
|
|
+ threading.Thread(target=self.read_serial, daemon=True).start()
|
|
|
+ except Exception as e:
|
|
|
+ messagebox.showerror("错误", f"无法打开串口: {e}")
|
|
|
+
|
|
|
+ def close_serial(self):
|
|
|
+ self.is_running = False
|
|
|
+ if self.ser and self.ser.is_open:
|
|
|
+ self.ser.close()
|
|
|
+ self.btn_open["state"] = tk.NORMAL
|
|
|
+ self.btn_close["state"] = tk.DISABLED
|
|
|
+ self.status["text"] = "状态: 未连接"
|
|
|
+ self.text_rx.insert(tk.END, "[系统] 串口已关闭\n")
|
|
|
+
|
|
|
+ def read_serial(self):
|
|
|
+ while self.is_running and self.ser and self.ser.is_open:
|
|
|
+ try:
|
|
|
+ count = self.ser.in_waiting
|
|
|
+ if count:
|
|
|
+ raw_data = self.ser.read(count)
|
|
|
+ # 判断显示格式
|
|
|
+ if self.hex_display.get():
|
|
|
+ display_data = " ".join(f"{b:02X}" for b in raw_data)
|
|
|
+ else:
|
|
|
+ display_data = raw_data.decode("utf-8", errors="ignore")
|
|
|
+
|
|
|
+ self.text_rx.insert(tk.END, display_data + "\n") # 自动换行
|
|
|
+ self.text_rx.see(tk.END)
|
|
|
+ except Exception:
|
|
|
+ break
|
|
|
+
|
|
|
+ def send_data(self):
|
|
|
+ if self.ser and self.ser.is_open:
|
|
|
+ data = self.entry_tx.get()
|
|
|
+ if data:
|
|
|
+ try:
|
|
|
+ # 若以 "0x" 开头或包含空格数字,则自动解析为 hex 发送
|
|
|
+ if all(c in "0123456789ABCDEFabcdefxX " for c in data) and (" " in data or data.startswith("0x")):
|
|
|
+ data_bytes = bytes.fromhex(data.replace("0x", "").replace("0X", ""))
|
|
|
+ else:
|
|
|
+ data_bytes = data.encode("utf-8")
|
|
|
+
|
|
|
+ self.ser.write(data_bytes)
|
|
|
+ self.text_rx.insert(tk.END, f"[发送] {data}\n")
|
|
|
+ self.entry_tx.delete(0, tk.END)
|
|
|
+ except Exception as e:
|
|
|
+ messagebox.showerror("发送错误", str(e))
|
|
|
+ else:
|
|
|
+ messagebox.showwarning("警告", "请先打开串口!")
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ root = tk.Tk()
|
|
|
+ app = SerialAssistant(root)
|
|
|
+ root.mainloop()
|