如何在Tkinter中复用同一线程执行其他命令?
ESP32 CLI指令自动化:实现Tkinter多按钮共享串口连接
核心问题分析
当前代码中,subprocess.Popen创建的ESP32连接实例是test()函数的局部变量,导致点击“Lets Go!!”按钮时无法复用已建立的连接发送指令。同时串口路径硬编码,后续需要对接下拉框动态获取参数。
解决方案步骤
- 全局共享连接实例:将subprocess进程实例设为全局变量,让两个按钮能访问同一连接
- 启用进程输入流:启动
rfq命令时打开stdin,支持后续发送指令 - 新增指令发送函数:为“Lets Go!!”按钮绑定独立函数,通过已连接的进程发送CLI指令
- 预留下拉框参数接口:修改连接逻辑,支持从界面控件动态获取串口路径
修改后的完整代码
import tkinter as tk from tkinter import messagebox, ttk import sys import subprocess import threading # --- 全局变量:保存ESP32连接进程实例 --- esp_process = None # --- classes --- class Redirect(): def __init__(self, widget, autoscroll=True): self.widget = widget self.autoscroll = autoscroll def write(self, text): self.widget.insert('end', text) if self.autoscroll: self.widget.see("end") # 自动滚动到末尾 # --- functions --- def run(): global esp_process # 避免重复创建连接 if esp_process is not None and esp_process.poll() is None: print("已存在活跃连接,请先断开后重试") return # 从文本框获取串口路径(后续可改为从下拉框读取) serial_port = serial.get("1.0", "end-1c").strip() or "/dev/ttyUSB0" threading.Thread(target=test, args=(serial_port,)).start() def test(port): global esp_process # 启动rfq命令,同时打开stdin用于发送指令 esp_process = subprocess.Popen( ["rfq", "tty", "-P", port], stdout=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=1, text=True ) print(f"已连接到串口 {port}") # 持续读取进程输出 while esp_process.poll() is None: msg = esp_process.stdout.readline().strip() if msg: print(msg) # 进程结束后重置实例 print("连接已断开") esp_process = None def send_command(): global esp_process # 检查连接状态 if esp_process is None or esp_process.poll() is not None: print("请先点击Connect建立连接") return # 根据下拉框参数拼接指令(示例格式,需匹配rfq实际CLI语法) txrx_mode = txrx.get() freq_val = freq.get() mod_val = mod.get() command = f"set mode {txrx_mode}\nset freq {freq_val}\nset mod {mod_val}\nstart\n" try: esp_process.stdin.write(command) esp_process.stdin.flush() # 确保指令发送到进程 print(f"已发送指令:{command.strip()}") except Exception as e: print(f"发送指令失败:{str(e)}") esp_process = None # --- main --- root = tk.Tk() root.title("ESP32 CLI控制器") # 窗口最大化 width= root.winfo_screenwidth() height= root.winfo_screenheight() root.geometry("%dx%d" % (width, height)) frame = tk.Frame(root) frame.pack(expand=True, fill='both') # 串口输入框 serial = tk.Text(frame, width=50, height=1) serial.insert("1.0", "/dev/ttyUSB0") # 默认串口路径 serial.place(x=48, y=7) # Tx/Rx下拉框 txrx = ttk.Combobox( frame, state="readonly", values=["Tx", "Rx"] ) txrx.current(0) # 默认选中Tx txrx.place(x=50, y=50) rxtxlabel= tk.Label(frame, text="Tx/Rx") rxtxlabel.place(in_=txrx, relx=0, x=0, rely=1.5) # 频率下拉框 freq = ttk.Combobox( frame, state="readonly", values=["315","433","868"] ) freq.current(1) # 默认选中433 freq.place(in_=txrx, relx=1.0, x=20, rely=0) freqlabel= tk.Label(frame, text="Freq") freqlabel.place(in_=freq, relx=0, x=0, rely=1) # 调制方式下拉框 mod = ttk.Combobox( frame, state="readonly", values=["OOK","2-FSK","4-FSK", "MSK"] ) mod.current(0) # 默认选中OOK mod.place(in_=freq, relx=1.0, x=20, rely=0) modlabel= tk.Label(frame, text="Modulation") modlabel.place(in_=mod, relx=0, x=0, rely=1) # 输出文本框 text = tk.Text(frame, width=width, height=20) text.place(x=0, y=300) # 重定向stdout到文本框 old_stdout = sys.stdout sys.stdout = Redirect(text) # 按钮绑定 button_go = tk.Button(root, text='Lets Go!!', command=send_command) button_go.place(in_=mod, relx=1, x=0, rely=0, y=210) button_connect = tk.Button(root, text='Connect', command=run) button_connect.place(in_=serial, relx=1, x=10, rely=0, y= -4) root.mainloop() # 恢复stdout sys.stdout = old_stdout
关键修改说明
- 全局变量
esp_process:保存连接进程实例,让send_command()能直接复用已建立的连接 test()函数优化:支持传入串口路径,启动进程时打开stdin,进程结束后自动重置实例避免无效引用send_command()函数:先检查连接状态,再根据下拉框参数拼接指令并发送,同时处理异常情况- 界面优化:给下拉框设置默认值,串口输入框预填默认路径,提升用户操作体验
后续扩展提示
- 可添加“Disconnect”按钮,调用
esp_process.terminate()主动断开连接 - 可根据
rfq命令的实际CLI格式,调整指令拼接逻辑,支持更多参数配置 - 可添加自定义指令输入框,让用户直接输入并发送任意CLI指令
内容的提问来源于stack exchange,提问作者Swordfish II
相关产品推荐
相关产品推荐

