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

Tkinter与Modbus Server结合:写入寄存器数据不持久问题求助

问题分析与解决方案:Modbus Server寄存器值无法持久保存

问题根源

  1. 重复创建Server实例:每次点击"Write Data to Server"按钮,都会在print_entry()中新建ModbusServer对象,旧的Server实例被销毁,寄存器数据自然清零。
  2. 数据类型不匹配:输入框获取的是字符串,直接写入寄存器会导致类型错误;Port参数需要整数类型,字符串传入会引发启动异常。
  3. 冗余控件创建:每次点击都新建Label覆盖原有控件,属于无效重复操作,还会增加内存占用。

修复方案

  1. 将ModbusServer实例设为全局变量,确保整个程序生命周期内只初始化一次。
  2. 首次点击按钮时初始化并启动Server,后续点击仅更新寄存器数据,不再重启Server。
  3. 将输入框的字符串值转换为整数后写入寄存器,Port参数也转换为整数类型。
  4. 提前创建用于显示的Label控件,点击按钮时仅更新其text属性,避免重复创建。
  5. 绑定窗口关闭事件,确保关闭程序时停止Server,避免残留进程。

修改后的完整代码

from tkinter import *
import tkinter as tk
from pyModbusTCP.server import ModbusServer

# 创建主窗口
root = tk.Tk()
root.geometry("300x290")
root.title("Resolve Batterie Profil")

# 全局变量:Modbus Server实例
server = None

# 描述标签区域
#------------------------------------------------------------------------------
IPFensterLabel = Label(root, text="Server IP", fg="black", bg="white")
IPFensterLabel.place(y=0, x=0, width=100, height=30)

PortFenster = Label(root, text="Port", fg="black", bg="white")
PortFenster.place(y=30, x=0, width=100, height=30)

SOCFenster = Label(root, text="SOC", fg="black", bg="white")
SOCFenster.place(y=80, x=0, width=100, height=30)

SOHFenster = Label(root, text="SOH", fg="black", bg="white")
SOHFenster.place(y=110, x=0, width=100, height=30)

EntladeFenster = Label(root, text="Entladeleistung", fg="black", bg="white")
EntladeFenster.place(y=140, x=0, width=100, height=30)

LadeFenster = Label(root, text="Ladeleistung", fg="black", bg="white")
LadeFenster.place(y=170, x=0, width=100, height=30)

# 输入框区域
#------------------------------------------------------------------------------
IPEntry = Entry(root, fg="black", bg="white")
IPEntry.place(y=0, x=100, width=100, height=30)
IPEntry.insert(0, "127.0.0.1")  # 设置默认IP

PortEntry = Entry(root, fg="black", bg="white")
PortEntry.place(y=30, x=100, width=100, height=30)
PortEntry.insert(0, "502")  # 设置默认Modbus端口

SOCEntry = Entry(root, fg="black", bg="white")
SOCEntry.place(y=80, x=100, width=100, height=30)

SOHEntry = Entry(root, fg="black", bg="white")
SOHEntry.place(y=110, x=100, width=100, height=30)

EntladeEntry = Entry(root, fg="black", bg="white")
EntladeEntry.place(y=140, x=100, width=100, height=30)

LadeEntry = Entry(root, fg="black", bg="white")
LadeEntry.place(y=170, x=100, width=100, height=30)

# 提前创建数据显示标签,后续仅更新内容
#------------------------------------------------------------------------------
IPDisplayLabel = Label(root, fg="black", bg="white")
IPDisplayLabel.place(y=0, x=200, width=100, height=30)

PortDisplayLabel = Label(root, fg="black", bg="white")
PortDisplayLabel.place(y=30, x=200, width=100, height=30)

SOCDisplayLabel = Label(root, fg="black", bg="white")
SOCDisplayLabel.place(y=80, x=200, width=100, height=30)

SOHDisplayLabel = Label(root, fg="black", bg="white")
SOHDisplayLabel.place(y=110, x=200, width=100, height=30)

EntladeDisplayLabel = Label(root, fg="black", bg="white")
EntladeDisplayLabel.place(y=140, x=200, width=100, height=30)

LadeDisplayLabel = Label(root, fg="black", bg="white")
LadeDisplayLabel.place(y=170, x=200, width=100, height=30)

ConnectionFenster = Label(root, fg="black", bg="white")
ConnectionFenster.place(y=200, x=90, width=120, height=30)

# 核心功能函数
#------------------------------------------------------------------------------
def print_entry():
    global server
    try:
        # 转换输入数据为正确类型
        ip = IPEntry.get()
        port = int(PortEntry.get())
        soc_val = int(SOCEntry.get())
        soh_val = int(SOHEntry.get())
        entlade_val = int(EntladeEntry.get())
        lade_val = int(LadeEntry.get())
    except ValueError:
        print("输入数据格式错误,请确保所有数值为整数")
        ConnectionFenster.config(text="输入错误")
        return

    # 首次启动或Server未运行时初始化Server
    if server is None or not server.is_run:
        server = ModbusServer(ip, port, no_block=True)
        try:
            server.start()
            print("Server启动成功")
            ConnectionFenster.config(text="已连接")
        except Exception as e:
            print(f"Server启动失败:{e}")
            ConnectionFenster.config(text="连接失败")
            return
    else:
        print("Server已在运行,直接更新寄存器")

    # 更新显示标签内容
    IPDisplayLabel.config(text=ip)
    PortDisplayLabel.config(text=str(port))
    SOCDisplayLabel.config(text=str(soc_val))
    SOHDisplayLabel.config(text=str(soh_val))
    EntladeDisplayLabel.config(text=str(entlade_val))
    LadeDisplayLabel.config(text=str(lade_val))

    # 写入Modbus寄存器
    server.data_bank.set_input_registers(7, [soc_val])
    server.data_bank.set_input_registers(8, [soh_val])
    server.data_bank.set_input_registers(9, [entlade_val])
    server.data_bank.set_input_registers(10, [lade_val])

    # 调试输出
    print("\n")
    print(f"Server运行状态:{server.is_run}")
    print(f"寄存器7当前值:{server.data_bank.get_input_registers(7)}")

# 按钮区域
#------------------------------------------------------------------------------
Button2 = tk.Button(text="Write Data to Server", command=print_entry)
Button2.place(y=230, x=90, width=120, height=30)

def on_close():
    # 关闭程序前停止Server
    if server and server.is_run:
        server.stop()
        print("Server已停止")
    root.destroy()

QuitButton = tk.Button(text="Close Programm", command=on_close)
QuitButton.place(y=260, x=90, width=120, height=30)

# 绑定窗口关闭事件
root.protocol("WM_DELETE_WINDOW", on_close)

root.mainloop()

关键修改说明

  • 全局Server实例:确保整个程序只创建一个Server,避免重复初始化导致数据丢失。
  • 数据类型校验与转换:解决输入类型不匹配问题,避免启动Server或写入寄存器时出错。
  • 复用显示控件:提前创建Label,点击时仅更新内容,优化程序性能。
  • 优雅关闭Server:绑定窗口关闭事件,确保程序退出时停止Server,避免残留进程。

内容的提问来源于stack exchange,提问作者Hotte

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 09:20:25