main.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import serial
  2. import serial.tools.list_ports
  3. import threading
  4. import tkinter as tk
  5. from tkinter import ttk, scrolledtext, messagebox
  6. class SerialAssistant:
  7. def __init__(self, master):
  8. self.master = master
  9. self.master.title("Python 串口助手 (青春版)")
  10. self.master.geometry("700x450")
  11. self.master.resizable(False, False)
  12. # 串口对象和状态
  13. self.ser = None
  14. self.is_running = False
  15. self.hex_display = tk.BooleanVar(value=False) # 是否以16进制显示
  16. # 串口设置区
  17. frame_top = ttk.LabelFrame(master, text="串口设置", padding=10)
  18. frame_top.pack(fill="x")
  19. ttk.Label(frame_top, text="端口:").grid(row=0, column=0)
  20. self.combo_port = ttk.Combobox(frame_top, width=10, values=self.list_serial_ports())
  21. self.combo_port.grid(row=0, column=1, padx=5)
  22. if self.combo_port["values"]:
  23. self.combo_port.current(0)
  24. ttk.Label(frame_top, text="波特率:").grid(row=0, column=2)
  25. self.combo_baud = ttk.Combobox(frame_top, width=10, values=[9600, 19200, 38400, 57600, 115200])
  26. self.combo_baud.current(4)
  27. self.combo_baud.grid(row=0, column=3, padx=5)
  28. self.btn_open = ttk.Button(frame_top, text="打开串口", command=self.open_serial)
  29. self.btn_open.grid(row=0, column=4, padx=5)
  30. self.btn_close = ttk.Button(frame_top, text="关闭串口", command=self.close_serial, state=tk.DISABLED)
  31. self.btn_close.grid(row=0, column=5, padx=5)
  32. ttk.Checkbutton(frame_top, text="16进制显示", variable=self.hex_display).grid(row=0, column=6, padx=10)
  33. # 接收区
  34. frame_rx = ttk.LabelFrame(master, text="接收数据", padding=10)
  35. frame_rx.pack(fill="both", expand=True)
  36. self.text_rx = scrolledtext.ScrolledText(frame_rx, wrap=tk.WORD, height=15)
  37. self.text_rx.pack(fill="both", expand=True)
  38. # 发送区
  39. frame_tx = ttk.LabelFrame(master, text="发送数据", padding=10)
  40. frame_tx.pack(fill="x")
  41. self.entry_tx = ttk.Entry(frame_tx, width=70)
  42. self.entry_tx.pack(side="left", padx=5)
  43. ttk.Button(frame_tx, text="发送", command=self.send_data).pack(side="left", padx=5)
  44. # 状态栏
  45. self.status = ttk.Label(master, text="状态: 未连接", relief="sunken", anchor="w")
  46. self.status.pack(fill="x", side="bottom")
  47. def list_serial_ports(self):
  48. ports = [port.device for port in serial.tools.list_ports.comports()]
  49. return ports if ports else ["无可用端口"]
  50. def open_serial(self):
  51. port = self.combo_port.get()
  52. baud = self.combo_baud.get()
  53. try:
  54. self.ser = serial.Serial(port, baudrate=int(baud), timeout=0)
  55. self.is_running = True
  56. self.btn_open["state"] = tk.DISABLED
  57. self.btn_close["state"] = tk.NORMAL
  58. self.status["text"] = f"状态: 已连接 {port} ({baud}bps)"
  59. self.text_rx.insert(tk.END, f"[系统] 成功打开串口 {port}\n")
  60. threading.Thread(target=self.read_serial, daemon=True).start()
  61. except Exception as e:
  62. messagebox.showerror("错误", f"无法打开串口: {e}")
  63. def close_serial(self):
  64. self.is_running = False
  65. if self.ser and self.ser.is_open:
  66. self.ser.close()
  67. self.btn_open["state"] = tk.NORMAL
  68. self.btn_close["state"] = tk.DISABLED
  69. self.status["text"] = "状态: 未连接"
  70. self.text_rx.insert(tk.END, "[系统] 串口已关闭\n")
  71. def read_serial(self):
  72. while self.is_running and self.ser and self.ser.is_open:
  73. try:
  74. count = self.ser.in_waiting
  75. if count:
  76. raw_data = self.ser.read(count)
  77. # 判断显示格式
  78. if self.hex_display.get():
  79. display_data = " ".join(f"{b:02X}" for b in raw_data)
  80. else:
  81. display_data = raw_data.decode("utf-8", errors="ignore")
  82. self.text_rx.insert(tk.END, display_data + "\n") # 自动换行
  83. self.text_rx.see(tk.END)
  84. except Exception:
  85. break
  86. def send_data(self):
  87. if self.ser and self.ser.is_open:
  88. data = self.entry_tx.get()
  89. if data:
  90. try:
  91. # 若以 "0x" 开头或包含空格数字,则自动解析为 hex 发送
  92. if all(c in "0123456789ABCDEFabcdefxX " for c in data) and (" " in data or data.startswith("0x")):
  93. data_bytes = bytes.fromhex(data.replace("0x", "").replace("0X", ""))
  94. else:
  95. data_bytes = data.encode("utf-8")
  96. self.ser.write(data_bytes)
  97. self.text_rx.insert(tk.END, f"[发送] {data}\n")
  98. self.entry_tx.delete(0, tk.END)
  99. except Exception as e:
  100. messagebox.showerror("发送错误", str(e))
  101. else:
  102. messagebox.showwarning("警告", "请先打开串口!")
  103. if __name__ == "__main__":
  104. root = tk.Tk()
  105. app = SerialAssistant(root)
  106. root.mainloop()