| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- 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("800x550")
- self.master.resizable(False, False)
- self.ser = None
- self.is_running = False
- self.hex_display = tk.BooleanVar(value=True)
- # 串口设置
- 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")
- # 命令模板下拉
- ttk.Label(frame_tx, text="命令模板:").grid(row=0, column=0)
- self.combo_frame = ttk.Combobox(frame_tx, width=50, state="readonly", values=[
- "0A 00 00 00", # 保留字段部分固定
- "08 00 00 00",
- "03 0A 00 D0",
- "07 03 00 00",
- "05 00 00 14",
- "06 00 00 14",
- "05 00 00 14"
- ])
- self.combo_frame.grid(row=0, column=1, padx=5)
- self.combo_frame.current(0)
- # 源地址下拉
- ttk.Label(frame_tx, text="源地址(2B):").grid(row=1, column=0)
- self.combo_src = ttk.Combobox(frame_tx, width=10, values=[
- "29 8A", "FF FF", "2A 75"
- ])
- self.combo_src.grid(row=1, column=1, padx=5)
- self.combo_src.current(0)
- # 目标地址下拉
- ttk.Label(frame_tx, text="目标地址(2B):").grid(row=1, column=2)
- self.combo_dst = ttk.Combobox(frame_tx, width=10, values=[
- "B4 60", "50 05", "60 06" , "FF FF"
- ])
- self.combo_dst.grid(row=1, column=3, padx=5)
- self.combo_dst.current(0)
- ttk.Button(frame_tx, text="发送帧", command=self.send_frame).grid(row=1, column=4, padx=10)
- # 状态栏
- 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)
- display_data = " ".join(f"{b:02X}" for b in raw_data) if self.hex_display.get() else 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_frame(self):
- if not (self.ser and self.ser.is_open):
- messagebox.showwarning("警告", "请先打开串口!")
- return
- try:
- cmd_template = self.combo_frame.get().replace(" ", "")
- src_addr = self.combo_src.get().replace(" ", "")
- dst_addr = self.combo_dst.get().replace(" ", "")
- if len(cmd_template) % 2 != 0:
- raise ValueError("命令模板长度非法")
- if len(src_addr) != 4 or len(dst_addr) != 4:
- raise ValueError("地址长度非法")
- # 组帧: 55 BB [命令模板] [源地址] [目标地址] FF EE
- frame = bytearray()
- frame.extend(b'\x55\xBB')
- frame.extend(bytes.fromhex(cmd_template))
- frame.extend(bytes.fromhex(src_addr))
- # 保留字段 3B, 可在模板里也可固定
- if len(cmd_template) < 4:
- frame.extend(b'\x00\x00\x00')
- frame.extend(bytes.fromhex(dst_addr))
- frame.extend(b'\xFF\xEE')
- self.ser.write(frame)
- self.text_rx.insert(tk.END, f"[发送] {' '.join(f'{b:02X}' for b in frame)}\n")
- self.text_rx.see(tk.END)
- except ValueError as e:
- messagebox.showerror("输入错误", str(e))
- if __name__ == "__main__":
- root = tk.Tk()
- app = SerialAssistant(root)
- root.mainloop()
|