|
@@ -4,19 +4,19 @@ import threading
|
|
|
import tkinter as tk
|
|
import tkinter as tk
|
|
|
from tkinter import ttk, scrolledtext, messagebox
|
|
from tkinter import ttk, scrolledtext, messagebox
|
|
|
|
|
|
|
|
|
|
+
|
|
|
class SerialAssistant:
|
|
class SerialAssistant:
|
|
|
def __init__(self, master):
|
|
def __init__(self, master):
|
|
|
self.master = master
|
|
self.master = master
|
|
|
- self.master.title("Python 串口助手 (青春版)")
|
|
|
|
|
- self.master.geometry("700x450")
|
|
|
|
|
|
|
+ self.master.title("Python 帧格式串口助手")
|
|
|
|
|
+ self.master.geometry("800x550")
|
|
|
self.master.resizable(False, False)
|
|
self.master.resizable(False, False)
|
|
|
|
|
|
|
|
- # 串口对象和状态
|
|
|
|
|
self.ser = None
|
|
self.ser = None
|
|
|
self.is_running = False
|
|
self.is_running = False
|
|
|
- self.hex_display = tk.BooleanVar(value=False) # 是否以16进制显示
|
|
|
|
|
|
|
+ self.hex_display = tk.BooleanVar(value=True)
|
|
|
|
|
|
|
|
- # 串口设置区
|
|
|
|
|
|
|
+ # 串口设置
|
|
|
frame_top = ttk.LabelFrame(master, text="串口设置", padding=10)
|
|
frame_top = ttk.LabelFrame(master, text="串口设置", padding=10)
|
|
|
frame_top.pack(fill="x")
|
|
frame_top.pack(fill="x")
|
|
|
|
|
|
|
@@ -44,12 +44,41 @@ class SerialAssistant:
|
|
|
self.text_rx = scrolledtext.ScrolledText(frame_rx, wrap=tk.WORD, height=15)
|
|
self.text_rx = scrolledtext.ScrolledText(frame_rx, wrap=tk.WORD, height=15)
|
|
|
self.text_rx.pack(fill="both", expand=True)
|
|
self.text_rx.pack(fill="both", expand=True)
|
|
|
|
|
|
|
|
- # 发送区
|
|
|
|
|
- frame_tx = ttk.LabelFrame(master, text="发送数据", padding=10)
|
|
|
|
|
|
|
+ # 发送帧区
|
|
|
|
|
+ frame_tx = ttk.LabelFrame(master, text="帧发送设置", padding=10)
|
|
|
frame_tx.pack(fill="x")
|
|
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)
|
|
|
|
|
|
|
+
|
|
|
|
|
+ # 命令模板下拉
|
|
|
|
|
+ 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 = ttk.Label(master, text="状态: 未连接", relief="sunken", anchor="w")
|
|
@@ -88,35 +117,45 @@ class SerialAssistant:
|
|
|
count = self.ser.in_waiting
|
|
count = self.ser.in_waiting
|
|
|
if count:
|
|
if count:
|
|
|
raw_data = self.ser.read(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") # 自动换行
|
|
|
|
|
|
|
+ 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)
|
|
self.text_rx.see(tk.END)
|
|
|
except Exception:
|
|
except Exception:
|
|
|
break
|
|
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:
|
|
|
|
|
|
|
+ def send_frame(self):
|
|
|
|
|
+ if not (self.ser and self.ser.is_open):
|
|
|
messagebox.showwarning("警告", "请先打开串口!")
|
|
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__":
|
|
if __name__ == "__main__":
|
|
|
root = tk.Tk()
|
|
root = tk.Tk()
|