You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Tkinter中复用同一线程执行其他命令?

ESP32 CLI指令自动化:实现Tkinter多按钮共享串口连接

核心问题分析

当前代码中,subprocess.Popen创建的ESP32连接实例是test()函数的局部变量,导致点击“Lets Go!!”按钮时无法复用已建立的连接发送指令。同时串口路径硬编码,后续需要对接下拉框动态获取参数。

解决方案步骤

  1. 全局共享连接实例:将subprocess进程实例设为全局变量,让两个按钮能访问同一连接
  2. 启用进程输入流:启动rfq命令时打开stdin,支持后续发送指令
  3. 新增指令发送函数:为“Lets Go!!”按钮绑定独立函数,通过已连接的进程发送CLI指令
  4. 预留下拉框参数接口:修改连接逻辑,支持从界面控件动态获取串口路径

修改后的完整代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 01:55:15