一、阶段总结:
目前阶段的系统构成:
- DataStore:变量存储区(变量池)
变量池位于上位机,是一个连续区域的存储区,最小存取单位是字节。

- AreaVar:区域变量
区域变量:NamedTuple
python
AreaVar(NamedTuple,
area:I、Q、M或DB区,
DBnum:DB编号,
offset:地址偏移,
byte_count:字节数,
content:数据内容
)
区域变量的作用是携带区域信息,用来成块 读写变量池中 和PLC 的连续数据。所有的通信读写均通过区域变量进行。
- UiVari:UI变量
python
class UiVari(QObject):
valueChanged = Signal(object)
def __init__(self, typ, byte_count, area, offset, DBnum=0, bit=0, value=None ):
super().__init__()
self.typ = typ,
self.byte_count = byte_count,
self.area = area,
self.DBnum = DBnum,
self.offset = offset,
self.bit = bit
self.value = value
UI变量通过信号槽机制与UI部件交互,UI变量中携带的地址和数据信息转换成区域变量(AreaVar),并通过该区域变量与变量池进行读写交互。

UI变量具备"变化才发射"的信号特性,只有它的值发生变化后,才执行与之连接的功能槽函数(比如刷新UI显示、发送下行数据等)。
- 变量表:
从excel文件直接生成变量表,变量表中的变量格式为UiVari(UI变量)。 - 变量管理器(VariManager):
使用变量管理器,以变量表为依据对数据池进行变量读写。 - 上行报文:
从PLC发送到上位机的报文,报文中包含了本帧数据的区域变量(AreaVar)的信息以及数据的具体内容,上位机收到报文后,按照区域变量所指的区域和内容对变量池写操作。上行报文由PLC主动周期发送。 - 下行报文:
从上位机发送到PLC的报文,报文中同样包含了本帧数据的区域变量(AreaVar)的信息以及数据的具体内容。上位机收到报文后,按照区域变量所指的区域和内容对PLC写操作。PLC没有需要写的数据时,发送固定的心跳代码。 - 报文总结:
上行:写上位机数据池,由PLC主动周期发送;
下行:写PLC数据,上位机周期发送心跳和实时发送PLC写数据;
上下行操作相互独立,不呼应、不答复。
在目前的基础上,下一步就可以很方便改进成上下行呼应、答复的模式。 - 阶段总结:
- 两个数据池:PLC的I、Q、M、DB存储区,上位机的DataStore;
- 两个数据变量:AreaVar,块读写数据池的时候使用,以字节为单位,忽略读写内容的变量类型;UiVari,按格式读写单个变量;
- 两个报文:上行报文写上位机的DataStore,下行报文写PLC的I、Q、M、DB存储区;
- 一个变量管理器,上位机中使用变量管理器读写变量;
- 上位机使用变量管理器的write_vari()函数写变量,对应的变量值和变量池中对应的存储区都会被写成新值,但不会改变PLC中的内容,需要通过一次立即发送send_now()将变量发送到PLC进行更新;使用变量管理器的read_vari()函数读变量,对应的变量值会被更新为变量池中对应的存储区内容。
- 使用上行报文写上位机的变量池,只会更新变量池的存储内容,需要执行read_vari()才能更新变量值。
二、一个最简的Qt demo
python
import struct
import sys
from PySide6.QtWidgets import QApplication, QPushButton, QWidget, QVBoxLayout, QLineEdit, QLabel
from VariManager import 变量表
from VariManager.manager import DataStore, VariManager, AreaVar
def invert_value(v):
return not v
if __name__ == '__main__':
app = QApplication(sys.argv)
# 初始化存储
vari_store = DataStore() # 创建变量存储区
vm = VariManager(vari_store) # 创建变量管理器
# 创建窗口
form = QWidget()
layout = QVBoxLayout()
form.setLayout(layout)
linedit = QLineEdit('0')
linedit.textChanged.connect(lambda t:vm.write_vari(变量表.油泵2压力设定, float(t)))
label = QLabel('压力设定显示')
layout.addWidget(label)
layout.addWidget(linedit)
变量表.油泵2压力设定.valueChanged.connect(lambda t:label.setText(str(t)))
btn1 = QPushButton('油泵启停(False)')
layout.addWidget(btn1)
form.show()
btn1.clicked.connect(lambda :vm.write_vari(变量表.油泵1按钮起, invert_value(变量表.油泵1按钮起.value)))
变量表.油泵1按钮起.valueChanged.connect(lambda :btn1.setText(f'油泵启停({str(变量表.油泵1按钮起.value)})'))
btn2 = QPushButton('模拟字节串通信')
layout.addWidget(btn2)
def move_bytes():
data_block = b''
value_bytes = struct.pack('>f', 1.23888)
data_block += value_bytes
value_bytes = struct.pack('>f', 2.34567)
data_block += value_bytes
area_vari = AreaVar(
area='DB', # I、Q、M、
DBnum=5, # DB号(I/Q/M=0)
offset=2, # 字节偏移
byte_count=8, # 字节数
content=data_block
)
vari_store.write_block(area_vari)
btn2.clicked.connect(move_bytes)
btn3 = QPushButton('读取变量')
layout.addWidget(btn3)
btn3.clicked.connect(lambda :vm.read_vari(变量表.油泵2压力设定))
sys.exit(app.exec())
