import serial 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()