|
|
@@ -4,86 +4,100 @@ 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.master.geometry("900x650")
|
|
|
+ self.master.minsize(750, 500)
|
|
|
+ self.master.columnconfigure(0, weight=1)
|
|
|
+ self.master.rowconfigure(1, weight=1)
|
|
|
|
|
|
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")
|
|
|
+ frame_top.grid(row=0, column=0, sticky="ew", padx=5, pady=5)
|
|
|
+ for i in range(7):
|
|
|
+ frame_top.columnconfigure(i, weight=1)
|
|
|
|
|
|
- ttk.Label(frame_top, text="端口:").grid(row=0, column=0)
|
|
|
+ ttk.Label(frame_top, text="端口:").grid(row=0, column=0, sticky="w")
|
|
|
self.combo_port = ttk.Combobox(frame_top, width=10, values=self.list_serial_ports())
|
|
|
- self.combo_port.grid(row=0, column=1, padx=5)
|
|
|
+ self.combo_port.grid(row=0, column=1, padx=5, sticky="w")
|
|
|
if self.combo_port["values"]:
|
|
|
self.combo_port.current(0)
|
|
|
|
|
|
- ttk.Label(frame_top, text="波特率:").grid(row=0, column=2)
|
|
|
+ ttk.Label(frame_top, text="波特率:").grid(row=0, column=2, sticky="w")
|
|
|
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.combo_baud.grid(row=0, column=3, padx=5, sticky="w")
|
|
|
|
|
|
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)
|
|
|
+ ttk.Checkbutton(frame_top, text="16进制显示", variable=self.hex_display).grid(row=0, column=6, padx=10, sticky="w")
|
|
|
|
|
|
- # 接收区
|
|
|
+ # ------------------ 接收区 ------------------
|
|
|
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_rx.grid(row=1, column=0, sticky="nsew", padx=5, pady=5)
|
|
|
+ frame_rx.columnconfigure(0, weight=1)
|
|
|
+ frame_rx.rowconfigure(0, weight=1)
|
|
|
+
|
|
|
+ self.text_rx = scrolledtext.ScrolledText(frame_rx, wrap=tk.WORD)
|
|
|
+ self.text_rx.grid(row=0, column=0, sticky="nsew")
|
|
|
|
|
|
- # 发送帧区
|
|
|
+ # ------------------ 发送帧区 ------------------
|
|
|
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"
|
|
|
+ frame_tx.grid(row=2, column=0, sticky="ew", padx=5, pady=5)
|
|
|
+ for i in range(5):
|
|
|
+ frame_tx.columnconfigure(i, weight=1)
|
|
|
+
|
|
|
+ # 命令模板
|
|
|
+ ttk.Label(frame_tx, text="命令模板:").grid(row=0, column=0, sticky="w")
|
|
|
+ self.combo_frame = ttk.Combobox(frame_tx, width=50, state="normal", values=[
|
|
|
+ "0A 00 00 00 B4 60 FF EE",
|
|
|
+ "08 00 00 00 B4 60 FF EE",
|
|
|
+ "03 0A 00 D0 B4 60 FF EE",
|
|
|
+ "07 03 00 00 B4 60 FF EE",
|
|
|
+ "05 00 00 14 B4 60 FF EE",
|
|
|
+ "06 00 00 14 B4 60 FF EE",
|
|
|
+ "05 00 00 14 B4 60 FF EE"
|
|
|
])
|
|
|
- self.combo_frame.grid(row=0, column=1, padx=5)
|
|
|
+ self.combo_frame.grid(row=0, column=1, padx=5, sticky="w")
|
|
|
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)
|
|
|
+ frame_template_btn = ttk.Frame(frame_tx)
|
|
|
+ frame_template_btn.grid(row=0, column=2, padx=5, sticky="w")
|
|
|
+ ttk.Button(frame_template_btn, text="保存模板", command=self.save_template).pack(side="left", padx=2)
|
|
|
+ ttk.Button(frame_template_btn, text="删除模板", command=self.delete_template).pack(side="left", padx=2)
|
|
|
+
|
|
|
+ # ------------------ 源地址 ------------------
|
|
|
+ ttk.Label(frame_tx, text="源地址(2B):").grid(row=1, column=0, sticky="w")
|
|
|
+ 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, sticky="w")
|
|
|
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.Label(frame_tx, text="新增源地址:").grid(row=2, column=0, sticky="w")
|
|
|
+ self.entry_new_src = ttk.Entry(frame_tx, width=10)
|
|
|
+ self.entry_new_src.grid(row=2, column=1, padx=5, sticky="w")
|
|
|
+
|
|
|
+ frame_src_btn = ttk.Frame(frame_tx)
|
|
|
+ frame_src_btn.grid(row=2, column=2, columnspan=3, sticky="w")
|
|
|
+ ttk.Button(frame_src_btn, text="添加源地址", command=self.add_src).pack(side="left", padx=2)
|
|
|
+ ttk.Button(frame_src_btn, text="删除选中源地址", command=self.delete_src).pack(side="left", padx=2)
|
|
|
|
|
|
- ttk.Button(frame_tx, text="发送帧", command=self.send_frame).grid(row=1, column=4, padx=10)
|
|
|
+ # 发送帧按钮
|
|
|
+ ttk.Button(frame_tx, text="发送帧", command=self.send_frame).grid(row=1, column=2, padx=10)
|
|
|
|
|
|
# 状态栏
|
|
|
self.status = ttk.Label(master, text="状态: 未连接", relief="sunken", anchor="w")
|
|
|
- self.status.pack(fill="x", side="bottom")
|
|
|
+ self.status.grid(row=3, column=0, sticky="ew")
|
|
|
|
|
|
+ # ------------------ 串口相关 ------------------
|
|
|
def list_serial_ports(self):
|
|
|
ports = [port.device for port in serial.tools.list_ports.comports()]
|
|
|
return ports if ports else ["无可用端口"]
|
|
|
@@ -111,6 +125,7 @@ class SerialAssistant:
|
|
|
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:
|
|
|
@@ -123,6 +138,53 @@ class SerialAssistant:
|
|
|
except Exception:
|
|
|
break
|
|
|
|
|
|
+ # ------------------ 源地址管理 ------------------
|
|
|
+ def add_src(self):
|
|
|
+ addr = self.entry_new_src.get().strip()
|
|
|
+ if addr and addr not in self.combo_src["values"]:
|
|
|
+ values = list(self.combo_src["values"])
|
|
|
+ values.append(addr)
|
|
|
+ self.combo_src["values"] = values
|
|
|
+ self.entry_new_src.delete(0, tk.END)
|
|
|
+
|
|
|
+ def delete_src(self):
|
|
|
+ addr = self.combo_src.get().strip()
|
|
|
+ values = list(self.combo_src["values"])
|
|
|
+ if addr in values:
|
|
|
+ values.remove(addr)
|
|
|
+ self.combo_src["values"] = values
|
|
|
+ if values:
|
|
|
+ self.combo_src.current(0)
|
|
|
+
|
|
|
+ # ------------------ 命令模板管理 ------------------
|
|
|
+ def save_template(self):
|
|
|
+ new_template = self.combo_frame.get().strip()
|
|
|
+ if not new_template:
|
|
|
+ messagebox.showwarning("提示", "命令模板不能为空!")
|
|
|
+ return
|
|
|
+ values = list(self.combo_frame["values"])
|
|
|
+ if new_template not in values:
|
|
|
+ values.append(new_template)
|
|
|
+ self.combo_frame["values"] = values
|
|
|
+ messagebox.showinfo("提示", f"模板已保存: {new_template}")
|
|
|
+ else:
|
|
|
+ messagebox.showinfo("提示", "模板已存在,无需重复添加")
|
|
|
+
|
|
|
+ def delete_template(self):
|
|
|
+ template = self.combo_frame.get().strip()
|
|
|
+ values = list(self.combo_frame["values"])
|
|
|
+ if template in values:
|
|
|
+ values.remove(template)
|
|
|
+ self.combo_frame["values"] = values
|
|
|
+ if values:
|
|
|
+ self.combo_frame.current(0)
|
|
|
+ else:
|
|
|
+ self.combo_frame.set('')
|
|
|
+ messagebox.showinfo("提示", f"模板已删除: {template}")
|
|
|
+ else:
|
|
|
+ messagebox.showwarning("提示", "模板不存在或未选择")
|
|
|
+
|
|
|
+ # ------------------ 发送帧 ------------------
|
|
|
def send_frame(self):
|
|
|
if not (self.ser and self.ser.is_open):
|
|
|
messagebox.showwarning("警告", "请先打开串口!")
|
|
|
@@ -131,22 +193,17 @@ class SerialAssistant:
|
|
|
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("地址长度非法")
|
|
|
+ if len(src_addr) != 4:
|
|
|
+ raise ValueError("源地址长度非法")
|
|
|
|
|
|
- # 组帧: 55 BB [命令模板] [源地址] [目标地址] FF EE
|
|
|
+ # 组帧: 55 BB [命令模板] [源地址] [保留3B] 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)
|