diff --git a/UI/Main/Main.py b/UI/Main/Main.py index e16aaac..7aa7bd3 100644 --- a/UI/Main/Main.py +++ b/UI/Main/Main.py @@ -14,6 +14,7 @@ from ctypes.wintypes import MSG from win32 import win32api, win32gui from win32.lib import win32con from model.ProjectModel.VarManage import GlobalVarManager +from model.ProjectModel.GlobalConfigManager import GlobalConfigManager from UI.Main.MainLeft import MainLeft from UI.Main.MainTop import MainTop from ..ProjectManages.ProjectWidget import ProjectWidgets @@ -144,55 +145,117 @@ class MainWindow(QMainWindow): # 添加圆角(QSS) # self.centralwidget.setStyleSheet("") def createWidgets(self): - self.ModbusTcpMasterWidget = VarWidgets('ModbusTcpMaster') - self.ModbusTcpSlaveWidget = VarWidgets('ModbusTcpSlave') - self.ModbusRtuMasterWidget = VarWidgets('ModbusRtuMaster') - self.ModbusRtuSlaveWidget = VarWidgets('ModbusRtuSlave') + # 创建固定的widgets(非变量管理相关) self.userWidget = UserWidgets() self.procedureManagerWidget = ProcedureManager() self.controlWidget = getControlWidget() - - modbusWidgetList = [self.ModbusTcpMasterWidget, self.ModbusTcpSlaveWidget, self.ModbusRtuMasterWidget, self.ModbusRtuSlaveWidget] - modbusNameList = ['MODBUSTCP主站', 'MODBUSTCP从站', 'MODBUSRTU主站', 'MODBUSRTU从站',] - - - - - self.profibusWidget = ProfibusWidgets() self.trendWidget = TrendWidgets() self.SettingWidget = SettingWidget() - self.hartWidget = HartMainWindow() - self.tcrtdWidget = TcRtdWidgets() - self.analogWidget = AnalogWidgets() - self.hartsimulateWidget = HartSimulateWidgets() - self.rpcVarTableWidget = RpcVarTableView() + # 设置固定widgets的对象名 self.userWidget.setObjectName('userWidget') self.projectWidget.setObjectName('projectWidget') self.trendWidget.setObjectName('trendWidget') - self.hartWidget.setObjectName('hartWidget') - # self.ModBusWidget.setObjectName('varWidget') - self.analogWidget.setObjectName('analogWidget') - self.hartsimulateWidget.setObjectName('hartsimulateWidget') - + # 创建变量管理标签页容器 self.varManageTabWidget = QTabWidget() self.varManageTabWidget.setObjectName("varManageTabWidget") self.varManageTabWidget.tabBar().setObjectName('varManageTabBar') - for widget, name in zip(modbusWidgetList, modbusNameList): - widget.setObjectName('varWidget') - self.varManageTabWidget.addTab(widget, name) - - self.varManageTabWidget.addTab(self.analogWidget,'IO') - self.varManageTabWidget.addTab(self.tcrtdWidget,'TCRTD') - self.varManageTabWidget.addTab(self.hartWidget,'HART读取') - self.varManageTabWidget.addTab(self.hartsimulateWidget,'HART模拟') - self.varManageTabWidget.addTab(self.profibusWidget,'PROFIBUS') - self.varManageTabWidget.addTab(self.rpcVarTableWidget, '远程通讯') - self.varManageTabWidget.setCornerWidget(self.importVarButton) - - + + # 初始化变量管理widgets字典 + self.varWidgets = {} + + # 为QTabWidget添加updateTabs方法 + self.varManageTabWidget.updateTabs = self.updateVarTabs + + # 根据全局配置动态创建变量管理tabs + self.createDynamicVarTabs() + + def createDynamicVarTabs(self): + """根据全局配置动态创建变量管理标签页""" + try: + # 清空现有tabs + self.varManageTabWidget.clear() + self.varWidgets.clear() + + # 获取全局配置 + globalConfig = GlobalConfigManager.loadGlobalConfig() + variableModules = globalConfig.get('variableModules', {}) + + # 遍历所有可用模块,只创建启用的模块 + for moduleKey, enabled in variableModules.items(): + if enabled: + # 获取模块显示名称 + displayName = GlobalConfigManager.getModuleDisplayName(moduleKey) + + # 获取模块对应的Widget类 + widgetClass = GlobalConfigManager.getModuleWidgetClass(moduleKey) + + if widgetClass: + # 创建widget实例 + widget = widgetClass() + + # 设置对象名 + if hasattr(widget, 'setObjectName'): + if moduleKey.startswith('modbus'): + widget.setObjectName('varWidget') + elif moduleKey == 'ioModule': + widget.setObjectName('analogWidget') + elif moduleKey == 'hartSimulateModule': + widget.setObjectName('hartsimulateWidget') + elif moduleKey == 'hartReadModule': + widget.setObjectName('hartWidget') + else: + widget.setObjectName(f'{moduleKey}Widget') + + # 添加到标签页 + self.varManageTabWidget.addTab(widget, displayName) + + # 保存widget引用 + self.varWidgets[moduleKey] = widget + + except Exception as e: + print(f"创建动态变量管理标签页失败: {e}") + # 如果出错,创建默认的标签页 + self.createDefaultVarTabs() + + def createDefaultVarTabs(self): + """创建默认的变量管理标签页(兼容性备用方案)""" + try: + # 创建默认widgets + defaultWidgets = [ + (VarWidgets('ModbusTcpMaster'), 'MODBUSTCP主站'), + (VarWidgets('ModbusTcpSlave'), 'MODBUSTCP从站'), + (VarWidgets('ModbusRtuMaster'), 'MODBUSRTU主站'), + (VarWidgets('ModbusRtuSlave'), 'MODBUSRTU从站'), + (AnalogWidgets(), 'IO'), + (TcRtdWidgets(), 'TCRTD'), + (HartMainWindow(), 'HART读取'), + (HartSimulateWidgets(), 'HART模拟'), + (ProfibusWidgets(), 'PROFIBUS'), + (RpcVarTableView(), '远程通讯') + ] + + for widget, name in defaultWidgets: + if hasattr(widget, 'setObjectName'): + if 'Modbus' in name: + widget.setObjectName('varWidget') + elif name == 'IO': + widget.setObjectName('analogWidget') + elif name == 'HART模拟': + widget.setObjectName('hartsimulateWidget') + elif name == 'HART读取': + widget.setObjectName('hartWidget') + + self.varManageTabWidget.addTab(widget, name) + + except Exception as e: + print(f"创建默认变量管理标签页失败: {e}") + + def updateVarTabs(self, globalConfig=None): + """更新变量管理标签页(供外部调用)""" + self.createDynamicVarTabs() self.rightWidget.addWidget(self.varManageTabWidget) self.rightWidget.addWidget(self.trendWidget) diff --git a/UI/ProfibusWidgets/AreaTabWidget.py b/UI/ProfibusWidgets/AreaTabWidget.py index 5594ca1..2524e75 100644 --- a/UI/ProfibusWidgets/AreaTabWidget.py +++ b/UI/ProfibusWidgets/AreaTabWidget.py @@ -30,6 +30,7 @@ class AreaTabWidget(QTabWidget): def initUI(self): self.deviceName = self.deviceWidget.deviceName + print(1231421) # 创建一个 QTabWidget self.setObjectName('areaTabWidget') self.setTabPosition(QTabWidget.South) diff --git a/UI/ProfibusWidgets/EditAddressWidget.py b/UI/ProfibusWidgets/EditAddressWidget.py index e51910b..6611ca3 100644 --- a/UI/ProfibusWidgets/EditAddressWidget.py +++ b/UI/ProfibusWidgets/EditAddressWidget.py @@ -116,7 +116,7 @@ class EditAddressWidget(QDialog): self.setWindowOpacity(0.995) # 设置窗口透明度 self.setWindowFlag(Qt.FramelessWindowHint) - self.DPV1Master = DPV1Master('192.168.3.10', 502) + self.DPV1Master = DPV1Master('192.168.4.38', 502) self.exec_() # def changeDeviceType(self, index): diff --git a/UI/ProfibusWidgets/ProfibusWindow.py b/UI/ProfibusWidgets/ProfibusWindow.py index 1213b5c..fdcf26a 100644 --- a/UI/ProfibusWidgets/ProfibusWindow.py +++ b/UI/ProfibusWidgets/ProfibusWindow.py @@ -76,7 +76,7 @@ class ProfibusWidgets(QWidget): self.setObjectName("MainWindow") self.devicesManange = DevicesManange() # self.batteryManange = BatteryManange() - self.dpv1Master = DPV1Master('192.168.3.10', 502) + self.dpv1Master = DPV1Master('192.168.4.38', 502) self.blockParameterManageWidget = BlockParameterManageWidget() self.process = None # Globals.setValue('ProfibusWindow', self) @@ -92,7 +92,7 @@ class ProfibusWidgets(QWidget): self.searchAddressTimer = QTimer() self.searchAddressTimer.timeout.connect(self.searchSlave) - # self.searchAddressTimer.start(1000) + self.searchAddressTimer.start(1000) # self.searchAddressTimer.start(10000) # self.toolbarWidget = QWidget() @@ -297,7 +297,7 @@ class ProfibusWidgets(QWidget): def searchSlave(self): address = self.dpv1Master.searchSlave() if address == '读取错误': - self.dpv1Master = DPV1Master('192.168.3.10', 502) + self.dpv1Master = DPV1Master('192.168.4.38', 502) self.addressLabel.setText('在线仪表地址:') self.addressLabel.setText('在线仪表地址:{}'.format(address)) @@ -312,7 +312,7 @@ class ProfibusWidgets(QWidget): startupInfo = subprocess.STARTUPINFO() startupInfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupInfo.wShowWindow = 2 - self.process = subprocess.Popen("D:\\EnTalk PROFIBUS Manager\\DP.exe",startupinfo=startupInfo) + self.process = subprocess.Popen("C:\\EnTalk PROFIBUS Manager\\DP.exe",startupinfo=startupInfo) QTimer.singleShot(500, lambda:self.showLowerWidget(self.process)) # self.showFullScreen() # self.switchBtn.setIcon(QIcon('./Static/varMagH.png')) diff --git a/UI/ProfibusWidgets/SearchAddressWidget.py b/UI/ProfibusWidgets/SearchAddressWidget.py index cc22727..8a39380 100644 --- a/UI/ProfibusWidgets/SearchAddressWidget.py +++ b/UI/ProfibusWidgets/SearchAddressWidget.py @@ -57,7 +57,7 @@ class SearchAddressWidget(QWidget): self.setLayout(self.mainlayout) self.setWindowTitle('从站地址查找') - self.DPV1Master = DPV1Master('192.168.3.10', 502) + self.DPV1Master = DPV1Master('192.168.4.38', 502) self.DPV1Master.searchSlave(self.updateProgress) self.cancelBtn.clicked.connect(self.close) # self.timer = QTimer() diff --git a/UI/ProjectManages/ProjectModel.py b/UI/ProjectManages/ProjectModel.py index 9a27264..7f4d3ff 100644 --- a/UI/ProjectManages/ProjectModel.py +++ b/UI/ProjectManages/ProjectModel.py @@ -5,6 +5,7 @@ from PyQt5 import QtGui,QtCore,QtWidgets from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt, QVariant, QSize from PyQt5.QtWidgets import QItemDelegate, QHBoxLayout, QWidget,QMessageBox from model.ProjectModel.ProjectManage import ProjectManage +from model.ProjectModel.GlobalConfigManager import GlobalConfigManager from ..LoginWidgets.LoginWidget import LoginWidget from utils import Globals @@ -264,12 +265,17 @@ class ProjectButtonDelegate(QItemDelegate): Globals.getValue('MainWindows').delecteWidget() Globals.getValue('MainWindows').createWidgets() - modelLists = ['ModbusTcpMasterTable', 'ModbusTcpSlaveTable', 'ModbusRtuMasterTable', \ - 'ModbusRtuSlaveTable', 'TcRtdTable', 'AnalogTable', 'HartSimulateTable', 'userTable'] + # 根据全局配置获取启用的模型表列表 + modelLists = GlobalConfigManager.getEnabledModelTables() for l in modelLists: # print(l) - Globals.getValue(l).model.initTable() - QtWidgets.QApplication.processEvents() + try: + model_table = Globals.getValue(l) + if model_table and hasattr(model_table, 'model') and hasattr(model_table.model, 'initTable'): + model_table.model.initTable() + QtWidgets.QApplication.processEvents() + except Exception as e: + print(f"初始化模型表 {l} 失败: {e}") Globals.clearValue('forceVars')#清除颜色 diff --git a/UI/ProjectManages/ProjectWidget.py b/UI/ProjectManages/ProjectWidget.py index c92fa3a..c676a79 100644 --- a/UI/ProjectManages/ProjectWidget.py +++ b/UI/ProjectManages/ProjectWidget.py @@ -283,4 +283,6 @@ class ProjectWidgets(QtWidgets.QWidget): for name in repeatProject: proMes = ProjectManage.getByName(name) proMes.append('') - self.projectView.model.append_data(proMes) \ No newline at end of file + self.projectView.model.append_data(proMes) + + \ No newline at end of file diff --git a/UI/VarManages/HartSimulateModel.py b/UI/VarManages/HartSimulateModel.py index 2000a88..c315edb 100644 --- a/UI/VarManages/HartSimulateModel.py +++ b/UI/VarManages/HartSimulateModel.py @@ -216,6 +216,7 @@ class HartSimulateButtonDelegate(BaseButtonDelegate): minSpan = model.datas[source_row][7] maxSpan = model.datas[source_row][8] writeList = [str(x) for x in model.datas[source_row][3:9]] + # print(writeList) pattern = re.compile(r'[^0-9\.-]+') if minSpan and maxSpan and value1 and not re.findall(pattern, str(value1) + str(minSpan) + str(maxSpan)): if float(value1) < float(minSpan) or float(value1) > float(maxSpan): @@ -226,21 +227,44 @@ class HartSimulateButtonDelegate(BaseButtonDelegate): return for index, value in enumerate(writeList): if re.findall(pattern, value): + print(value) reply = QMessageBox.question(self.parent(), '警告', "请输入强制值或数字", QMessageBox.Yes) - return - + return try: - self.parent().workThread.HartSimulate.writeValue(index, value) - except: - reply = QMessageBox.question(self.parent(), - '警告', - "请先打开通讯", - QMessageBox.Yes) - return - + # 使用HART RTU从站功能写入变量值 + protocolManage = Globals.getValue('protocolManage') + print(protocolManage,protocolManage.hartRtuSlaveManager) + if not protocolManage or not protocolManage.hartRtuSlaveManager: + return + + # 检查value是否为空或无效 + if not value or str(value).strip() == '': + # print(f"HART变量{index}的值为空,跳过写入") + continue + + try: + floatValue = float(value) + except (ValueError, TypeError): + print(f"HART变量{index}的值'{value}'无法转换为浮点数") + return + + # 根据索引写入对应的变量 + if index == 0: # 主变量 + protocolManage.writeHartVariable('primaryVariable', floatValue) + # print(floatValue, 111) + elif index == 1: # 动态变量1 + protocolManage.writeHartVariable('dynamicVariable1', floatValue) + elif index == 2: # 动态变量2 + protocolManage.writeHartVariable('dynamicVariable2', floatValue) + elif index == 3: # 动态变量3 + protocolManage.writeHartVariable('dynamicVariable3', floatValue) + # print('1111', floatValue) + + except Exception as e: + print(f"写入HART变量失败: {e}") forceVars = Globals.getValue('forceVars') forceVars.add(model.datas[source_row][1]) Globals.setValue('forceVars', forceVars) \ No newline at end of file diff --git a/UI/VarManages/VarWidget.py b/UI/VarManages/VarWidget.py index 87829db..29a1d9f 100644 --- a/UI/VarManages/VarWidget.py +++ b/UI/VarManages/VarWidget.py @@ -750,10 +750,10 @@ class HartSimulateWidgets(VarWidgets): def setupUI(self): self.setAttribute(Qt.WA_StyledBackground, True) - self.startProtocolBtn = QPushButton(QIcon('./Static/startProtocol.png'), '开始通讯') - self.startProtocolBtn.setObjectName('startProtocolBtn') - self.startProtocolBtn.setIconSize(QSize(22, 22)) - self.startProtocolBtn.clicked.connect(self.startProtocol) + # self.startProtocolBtn = QPushButton(QIcon('./Static/startProtocol.png'), '开始通讯') + # self.startProtocolBtn.setObjectName('startProtocolBtn') + # self.startProtocolBtn.setIconSize(QSize(22, 22)) + # self.startProtocolBtn.clicked.connect(self.startProtocol) self.varView = HartSimulateTableView() self.varView.setObjectName('varView') @@ -771,8 +771,23 @@ class HartSimulateWidgets(VarWidgets): Globals.setValue('HartSimulateTable', self.varView) + # 添加HART RTU从站控制按钮 + self.startProtocolBtn = QPushButton('启动HART RTU从站') + self.startProtocolBtn.setObjectName('startProtocolBtn') + self.startProtocolBtn.clicked.connect(self.startProtocol) + + self.stopProtocolBtn = QPushButton('停止HART RTU从站') + self.stopProtocolBtn.setObjectName('stopProtocolBtn') + self.stopProtocolBtn.clicked.connect(self.stopProtocol) + + # 创建按钮布局 + self.buttonLayout = QtWidgets.QHBoxLayout() + self.buttonLayout.addWidget(self.startProtocolBtn) + self.buttonLayout.addWidget(self.stopProtocolBtn) + self.buttonLayout.addStretch() # 添加弹性空间 + self.gridLayout = QtWidgets.QGridLayout(self) - self.gridLayout.addWidget(self.startProtocolBtn, 0, 0, 1, 1) + self.gridLayout.addLayout(self.buttonLayout, 0, 0, 1, 26) self.gridLayout.addWidget(self.varView, 1, 0, 10, 26) self.gridLayout.setSpacing(20) self.gridLayout.setContentsMargins(20, 30, 20, 20) @@ -780,4 +795,23 @@ class HartSimulateWidgets(VarWidgets): self.horizontalHeader.sectionClicked.connect(self.on_view_horizontalHeader_sectionClicked) def startProtocol(self): - pass \ No newline at end of file + """启动HART RTU从站""" + try: + protocolManage = Globals.getValue('protocolManage') + if protocolManage and protocolManage.hartRtuSlaveManager: + if protocolManage.startHartRtuSlave(): + QMessageBox.information(self, '信息', 'HART RTU从站启动成功') + else: + QMessageBox.warning(self, '警告', 'HART RTU从站启动失败') + except Exception as e: + QMessageBox.critical(self, '错误', f'启动HART RTU从站时出错: {str(e)}') + + def stopProtocol(self): + """停止HART RTU从站""" + try: + protocolManage = Globals.getValue('protocolManage') + if protocolManage and protocolManage.hartRtuSlaveManager: + protocolManage.stopHartRtuSlave() + QMessageBox.information(self, '信息', 'HART RTU从站已停止') + except Exception as e: + QMessageBox.critical(self, '错误', f'停止HART RTU从站时出错: {str(e)}') \ No newline at end of file diff --git a/model/ProjectModel/BlockManage.py b/model/ProjectModel/BlockManage.py index f5d603c..cef2489 100644 --- a/model/ProjectModel/BlockManage.py +++ b/model/ProjectModel/BlockManage.py @@ -65,8 +65,8 @@ class BlockManage(): @property def DPV1Master(self): if not self._dpv1MasterPA or not self._dpv1MasterDP: - self._dpv1MasterPA = DPV1Master('192.168.3.10', 502) - self._dpv1MasterDP = DPV1Master('192.168.1.10', 502) + self._dpv1MasterPA = DPV1Master('192.168.4.38', 502) + self._dpv1MasterDP = DPV1Master('192.168.4.8', 502) if self._isPa: return self._dpv1MasterPA else: diff --git a/model/ProjectModel/DeviceManage.py b/model/ProjectModel/DeviceManage.py index 99d3ebb..86078ff 100644 --- a/model/ProjectModel/DeviceManage.py +++ b/model/ProjectModel/DeviceManage.py @@ -194,10 +194,10 @@ class DevicesManange(): self.paMasterDevices = collections.OrderedDict() self.paSlaveDevices = collections.OrderedDict() def connect(self): - self.dpSlaveModbus = TcpMaster(host = '192.168.2.10', port = 502) - self.paSlaveModbus = TcpMaster(host = '192.168.4.10', port = 502) - self.dpMasterModbus = TcpMaster(host = '192.168.1.10', port = 502) - self.paMasterModbus = TcpMaster(host = '192.168.3.10', port = 502) + self.dpSlaveModbus = TcpMaster(host = '192.168.4.18', port = 502) + self.paSlaveModbus = TcpMaster(host = '192.168.4.48', port = 502) + self.dpMasterModbus = TcpMaster(host = '192.168.4.8', port = 502) + self.paMasterModbus = TcpMaster(host = '192.168.4.38', port = 502) @@ -287,9 +287,10 @@ class DevicesManange(): area.qualityValueList = qualityValueList if area.type in ['DO', 'DI'] and device.type == 'PA': area.forceValue = values[8:] + values[:8] + elif area.type in ['DO', 'DI'] and device.type == 'DP' and device.masterOrSlave == "从站": + area.forceValue = values[8:] + values[:8] elif area.type in ['DO', 'DI'] and device.type == 'DP' and device.masterOrSlave == "主站": area.forceValue = values[8:] + values[:8] - for device in curProDict.values(): forceAreas = device.outputAreas if device.masterOrSlave == "主站" else device.inputAreas for area in forceAreas: @@ -308,7 +309,7 @@ class DevicesManange(): # print(bytes) # print(bytes) values = struct.unpack('!' + 'H' * int(len(bytes) / 2), bytes) - modbusM.writeMultipleRegister(slaveId = 1, address = 0, outputValue = values) + modbusM.writeMultipleRegisters(slaveId = 1, startAddress = 0, outputValues = values) # print(struct.unpack('>f', struct.pack('!HH', *values[:2]))) def readAreas(self): @@ -336,7 +337,8 @@ class DevicesManange(): intNums = int(bytesNums / 2) if bytesNums % 2 == 0 else int(bytesNums / 2) + 1 if bytesNums == 0: continue - intValues = modbusM.readHoldingRegisters(slaveId = 1, startAddress = 0, varNums = intNums) + intValues = modbusM.readHoldingRegisters(slaveId = 1, startAddress = 0, varNums = intNums, order = 'Profibus') + print(intValues, index) if intValues == 'error': self.connect() return @@ -366,10 +368,10 @@ class DevicesManange(): # print(area.qualityValueList) # print(round(struct.unpack('!f', reorderBytes(byte, area.order))[0], 4)) elif area.type in ['DI', 'DO']: - if device.masterOrSlave == '主站' and device.type == 'DP': - bytes = bytes - else: - bytes = bytes[::-1] + # if device.masterOrSlave == '主站' and device.type == 'DP': + # bytes = bytes + # else: + bytes = bytes[::-1] area.currentValue = bytesToCoils(bytes) # print(area.currentValue, device.deviceName, area.startAddress,area.endAddress) # print() diff --git a/model/ProjectModel/GlobalConfigManager.py b/model/ProjectModel/GlobalConfigManager.py new file mode 100644 index 0000000..7e1c0a4 --- /dev/null +++ b/model/ProjectModel/GlobalConfigManager.py @@ -0,0 +1,185 @@ +import os +import json +from utils import Globals + + +class GlobalConfigManager: + """全局配置管理器 - 所有工程使用统一配置""" + + CONFIG_FILE = 'Static/global_config.json' + + @classmethod + def loadGlobalConfig(cls): + """加载全局配置文件""" + try: + with open(cls.CONFIG_FILE, 'r', encoding='utf-8') as f: + data = json.load(f) + return data.get('globalConfig', cls.getDefaultConfig()) + except Exception as e: + print(f"加载全局配置文件失败: {e}") + return cls.getDefaultConfig() + + @classmethod + def getDefaultConfig(cls): + """获取默认配置(当配置文件不存在时使用)""" + return { + "name": "默认全局配置", + "description": "所有工程统一使用的默认配置", + "variableModules": { + "modbusTcpMaster": True, + "modbusTcpSlave": True, + "modbusRtuMaster": True, + "modbusRtuSlave": True, + "ioModule": True, + "tcRtdModule": True, + "hartReadModule": True, + "hartSimulateModule": True, + "profibusModule": True, + "rpcModule": True + } + } + + @classmethod + def saveGlobalConfig(cls, config): + """保存全局配置文件""" + try: + data = {"globalConfig": config} + with open(cls.CONFIG_FILE, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + return True + except Exception as e: + print(f"保存全局配置文件失败: {e}") + return False + + @classmethod + def getModuleDisplayName(cls, moduleKey): + """获取模块显示名称""" + displayNames = { + 'modbusTcpMaster': 'MODBUSTCP主站', + 'modbusTcpSlave': 'MODBUSTCP从站', + 'modbusRtuMaster': 'MODBUSRTU主站', + 'modbusRtuSlave': 'MODBUSRTU从站', + 'ioModule': 'IO模块', + 'tcRtdModule': 'TCRTD模块', + 'hartReadModule': 'HART读取模块', + 'hartSimulateModule': 'HART模拟模块', + 'profibusModule': 'PROFIBUS模块', + 'rpcModule': '远程通讯模块' + } + return displayNames.get(moduleKey, moduleKey) + + @classmethod + def getModuleWidgetClass(cls, moduleKey): + """获取模块对应的Widget类""" + from UI.VarManages.VarWidget import VarWidgets, TcRtdWidgets, AnalogWidgets, HartSimulateWidgets + from UI.HartWidgets.HartMainWindow import HartMainWindow + from UI.VarManages.VarTable import RpcVarTableView + from UI.ProfibusWidgets.ProfibusWindow import ProfibusWidgets + + widgetClasses = { + 'modbusTcpMaster': lambda: VarWidgets('ModbusTcpMaster'), + 'modbusTcpSlave': lambda: VarWidgets('ModbusTcpSlave'), + 'modbusRtuMaster': lambda: VarWidgets('ModbusRtuMaster'), + 'modbusRtuSlave': lambda: VarWidgets('ModbusRtuSlave'), + 'ioModule': AnalogWidgets, + 'tcRtdModule': TcRtdWidgets, + 'hartReadModule': HartMainWindow, + 'hartSimulateModule': HartSimulateWidgets, + 'profibusModule': ProfibusWidgets, + 'rpcModule': RpcVarTableView + } + return widgetClasses.get(moduleKey) + + @classmethod + def getEnabledModules(cls): + """获取启用的模块列表""" + config = cls.loadGlobalConfig() + variableModules = config.get('variableModules', {}) + return [moduleKey for moduleKey, enabled in variableModules.items() if enabled] + + @classmethod + def isModuleEnabled(cls, moduleKey): + """检查模块是否启用""" + config = cls.loadGlobalConfig() + variableModules = config.get('variableModules', {}) + return variableModules.get(moduleKey, False) + + @classmethod + def getEnabledModelTables(cls): + """获取启用模块对应的模型表列表""" + config = cls.loadGlobalConfig() + variableModules = config.get('variableModules', {}) + + # 模块与模型表的映射关系 + moduleToModelMapping = { + 'modbusTcpMaster': 'ModbusTcpMasterTable', + 'modbusTcpSlave': 'ModbusTcpSlaveTable', + 'modbusRtuMaster': 'ModbusRtuMasterTable', + 'modbusRtuSlave': 'ModbusRtuSlaveTable', + 'tcRtdModule': 'TcRtdTable', + 'ioModule': 'AnalogTable', + 'hartSimulateModule': 'HartSimulateTable' + # 注意:userTable 不在变量管理模块中,始终需要初始化 + # hartReadModule 和 profibusModule 可能没有对应的表模型 + # rpcModule 可能也没有对应的表模型 + } + + enabledTables = [] + + # 添加启用模块对应的表 + for moduleKey, enabled in variableModules.items(): + if enabled and moduleKey in moduleToModelMapping: + enabledTables.append(moduleToModelMapping[moduleKey]) + + # 始终添加用户表(非变量管理模块) + enabledTables.append('userTable') + + return enabledTables + + @classmethod + def getEnabledModelClasses(cls): + """获取启用模块对应的数据库模型类列表""" + from utils.DBModels.ProtocolModel import ( + ModbusTcpMasterVar, ModbusTcpSlaveVar, ModbusRtuMasterVar, ModbusRtuSlaveVar, + HartVar, TcRtdVar, AnalogVar, HartSimulateVar + ) + + config = cls.loadGlobalConfig() + variableModules = config.get('variableModules', {}) + + # 模块与数据库模型类的映射关系 + moduleToModelClassMapping = { + 'modbusTcpMaster': ModbusTcpMasterVar, + 'modbusTcpSlave': ModbusTcpSlaveVar, + 'modbusRtuMaster': ModbusRtuMasterVar, + 'modbusRtuSlave': ModbusRtuSlaveVar, + 'hartReadModule': HartVar, + 'tcRtdModule': TcRtdVar, + 'ioModule': AnalogVar, + 'hartSimulateModule': HartSimulateVar + } + + enabledModelClasses = [] + + # 添加启用模块对应的模型类 + for moduleKey, enabled in variableModules.items(): + if enabled and moduleKey in moduleToModelClassMapping: + enabledModelClasses.append(moduleToModelClassMapping[moduleKey]) + + return enabledModelClasses + + @classmethod + def needsTcpVarManager(cls): + """检查是否需要初始化TCPVarManager(当启用IO或TCRTD模块时)""" + config = cls.loadGlobalConfig() + variableModules = config.get('variableModules', {}) + + # TCPVarManager相关的模块 + tcpVarModules = ['ioModule', 'tcRtdModule'] + + # 检查是否有任何相关模块被启用 + for moduleKey in tcpVarModules: + if variableModules.get(moduleKey, False): + return True + + return False \ No newline at end of file diff --git a/model/ProjectModel/ProjectManage.py b/model/ProjectModel/ProjectManage.py index f7b8737..9ad7077 100644 --- a/model/ProjectModel/ProjectManage.py +++ b/model/ProjectModel/ProjectManage.py @@ -17,6 +17,7 @@ from model.ClientModel.Client import Client from model.HistoryDBModel.HistoryDBManage import HistoryDBManage from model.ProjectModel.VarManage import TcRtdManage, AnalogManage, HartVarManage, \ HartSimulateVarManage +from model.ProjectModel.GlobalConfigManager import GlobalConfigManager from model.UserModel.UserManage import UserManage from utils import Globals @@ -50,8 +51,8 @@ class ProjectManage(object): os.mkdir(os.path.join('project', name, 'config')) os.mkdir(os.path.join('project', name, 'log')) - with open(os.path.join('project', name, 'config', 'config.json'), 'w') as f: - json.dump({'description' : des}, f) + # with open(os.path.join('project', name, 'config', 'config.json'), 'w') as f: + # json.dump({'description': des}, f) project = Project() project.createProject(name, des) @@ -146,6 +147,10 @@ class ProjectManage(object): Globals.setValue('currentProDB', projectDB) Globals.setValue('currentProType', 1)#切换工程 + # 加载全局配置 + globalConfig = GlobalConfigManager.loadGlobalConfig() + Globals.setValue('currentProjectConfig', globalConfig) + histroyTableName = 'p' + name historyDBManage = HistoryDBManage(table=histroyTableName) Globals.setValue('historyDBManage', historyDBManage) @@ -162,6 +167,10 @@ class ProjectManage(object): Globals.getValue('HistoryWidget').exchangeProject() Globals.getValue('MainWindow').procedureManagerWidget.initDB() Globals.getValue('MainWindow').procedureManagerWidget.initUI() + + # 根据全局配置动态更新变量管理界面 + self.updateDynamicVarWidgets(globalConfig) + # 通知控制系统工程已切换 try: from control.ControlManager import getControlManager @@ -322,6 +331,24 @@ class ProjectManage(object): os.rename(os.path.join('project', name), os.path.join('project', Nname)) self.switchProject(Nname) + @classmethod + def updateDynamicVarWidgets(self, globalConfig): + """根据全局配置动态更新变量管理界面""" + try: + mainWindow = Globals.getValue('MainWindows') + if not mainWindow: + return + + # 获取变量管理标签页 + varManageTabWidget = mainWindow.varManageTabWidget + if not hasattr(varManageTabWidget, 'updateTabs'): + return + + # 更新标签页 + varManageTabWidget.updateTabs(globalConfig) + + except Exception as e: + print(f"更新动态变量管理界面失败: {e}") @classmethod def getAllProject(self): diff --git a/protocol/HartRtuSlaveManager.py b/protocol/HartRtuSlaveManager.py new file mode 100644 index 0000000..ab4a298 --- /dev/null +++ b/protocol/HartRtuSlaveManager.py @@ -0,0 +1,234 @@ +""" +HART模拟MODBUS-RTU从站管理器 +使用modbus_tk实现HART一个主变量和三个动态变量的通讯 +使用COM1端口,站地址1,寄存器地址0,3,5,7 +""" + +import threading +import time +import struct +from typing import Optional, List, Dict, Any +import modbus_tk +import modbus_tk.defines as cst +from modbus_tk import modbus_rtu +from protocol.ModBus.ByteOrder import * + + +class HartRtuSlaveManager: + """HART模拟MODBUS-RTU从站管理器""" + + def __init__(self, comPort='COM1', slaveId=1, baudRate=9600): + """ + 初始化HART RTU从站管理器 + + :param comPort: 串口端口,默认COM1 + :param slaveId: 从站地址,默认1 + :param baudRate: 波特率,默认9600 + """ + self.comPort = comPort + self.slaveId = slaveId + self.baudRate = baudRate + self.rtuServer = None + self.rtuSlave = None + self.isConnected = False + self.isRunning = False + self.lock = threading.Lock() + + # HART变量寄存器地址映射 + self.registerMap = { + 'primaryVariable': 0, # 主变量 - 寄存器地址0 + 'dynamicVariable1': 3, # 动态变量1 - 寄存器地址3 + 'dynamicVariable2': 5, # 动态变量2 - 寄存器地址5 + 'dynamicVariable3': 7 # 动态变量3 - 寄存器地址7 + } + + # 变量值缓存 + self.variableValues = { + 'primaryVariable': 0.0, + 'dynamicVariable1': 0.0, + 'dynamicVariable2': 0.0, + 'dynamicVariable3': 0.0 + } + + def _initializeRegisters(self): + """初始化寄存器数据""" + if not self.rtuSlave: + return + + try: + # 初始化主变量寄存器(地址0) + self.rtuSlave.set_values("primary_var", 0, [0, 0]) + # 初始化动态变量1寄存器(地址3) + self.rtuSlave.set_values("dynamic_var1", 3, [0, 0]) + # 初始化动态变量2寄存器(地址5) + self.rtuSlave.set_values("dynamic_var2", 5, [0, 0]) + # 初始化动态变量3寄存器(地址7) + self.rtuSlave.set_values("dynamic_var3", 7, [0, 0]) + except Exception as e: + print(f"初始化寄存器失败: {e}") + + def _floatToRegisters(self, value: float) -> tuple: + """将浮点数转换为两个16位寄存器值""" + return floatToBADC(value) + + def _registersToFloat(self, high: int, low: int) -> float: + """将两个16位寄存器值转换为浮点数""" + return BADCToFloat([high, low]) + + def startSlave(self) -> bool: + """启动RTU从站""" + try: + with self.lock: + if self.isConnected: + return True + + # 创建串口对象 + import serial + serialPort = serial.Serial( + port=self.comPort, + baudrate=self.baudRate, + bytesize=8, + parity='N', + stopbits=1, + timeout=1.0 + ) + + # 创建RTU服务器 + self.rtuServer = modbus_rtu.RtuServer(serialPort) + + # 启动服务器 + self.rtuServer.start() + + # 添加从站 + self.rtuSlave = self.rtuServer.add_slave(self.slaveId) + + # 为每个HART变量添加独立的保持寄存器块 + # 主变量:寄存器0-1(起始地址0,长度2) + self.rtuSlave.add_block("primary_var", cst.HOLDING_REGISTERS, 0, 2) + # 动态变量1:寄存器3-4(起始地址3,长度2) + self.rtuSlave.add_block("dynamic_var1", cst.HOLDING_REGISTERS, 3, 2) + # 动态变量2:寄存器5-6(起始地址5,长度2) + self.rtuSlave.add_block("dynamic_var2", cst.HOLDING_REGISTERS, 5, 2) + # 动态变量3:寄存器7-8(起始地址7,长度2) + self.rtuSlave.add_block("dynamic_var3", cst.HOLDING_REGISTERS, 7, 2) + + # 初始化寄存器 + self._initializeRegisters() + + self.isConnected = True + self.isRunning = True + print(f"HART RTU从站启动成功: {self.comPort}, 站地址: {self.slaveId}") + + return True + + except Exception as e: + print(f"启动HART RTU从站时出错: {e}") + self.isConnected = False + return False + + def stopSlave(self): + """停止RTU从站""" + try: + with self.lock: + self.isRunning = False + + if self.rtuServer: + self.rtuServer.stop() + self.rtuServer = None + + self.rtuSlave = None + self.isConnected = False + print("HART RTU从站已停止") + + except Exception as e: + print(f"停止HART RTU从站时出错: {e}") + + # 移除数据同步线程相关方法,modbus_tk会自动处理 + + def writeVariable(self, variableName: str, value: float) -> bool: + """ + 写入HART变量值 + + :param variableName: 变量名称 ('primaryVariable', 'dynamicVariable1', etc.) + :param value: 变量值 + :return: 写入是否成功 + """ + if not self.isConnected or variableName not in self.registerMap: + return False + + try: + with self.lock: + # 更新变量值缓存 + self.variableValues[variableName] = value + + # 获取对应的寄存器块名称 + blockName = self._getBlockName(variableName) + if not blockName: + return False + + # 将浮点数转换为寄存器值 + high, low = self._floatToRegisters(value) + + # 获取寄存器地址 + regAddr = self.registerMap[variableName] + + # 直接写入RTU从站寄存器(使用绝对地址) + if self.rtuSlave and self.isConnected: + self.rtuSlave.set_values(blockName, regAddr, [high, low]) + + return True + + except Exception as e: + print(f"写入HART变量 {variableName} 失败: {e}") + return False + + def readVariable(self, variableName: str) -> Optional[float]: + """ + 读取HART变量值 + + :param variableName: 变量名称 + :return: 变量值,失败返回None + """ + if not self.isConnected or variableName not in self.registerMap: + return None + + try: + with self.lock: + # 获取对应的寄存器块名称 + blockName = self._getBlockName(variableName) + if not blockName: + return None + + # 获取寄存器地址 + regAddr = self.registerMap[variableName] + + # 从RTU从站读取最新数据(使用绝对地址) + if self.rtuSlave: + values = self.rtuSlave.get_values(blockName, regAddr, 2) + + if len(values) == 2: + high, low = values + value = self._registersToFloat(high, low) + self.variableValues[variableName] = value + return value + + # 如果从RTU从站读取失败,返回缓存值 + return self.variableValues.get(variableName, 0.0) + + except Exception as e: + print(f"读取HART变量 {variableName} 失败: {e}") + return None + + def _getBlockName(self, variableName: str) -> Optional[str]: + """根据变量名获取对应的寄存器块名称""" + blockMapping = { + 'primaryVariable': 'primary_var', + 'dynamicVariable1': 'dynamic_var1', + 'dynamicVariable2': 'dynamic_var2', + 'dynamicVariable3': 'dynamic_var3' + } + return blockMapping.get(variableName) + + def isCommunicationOk(self) -> bool: + """检查通讯是否正常""" + return self.isConnected and self.rtuServer is not None and self.rtuSlave is not None \ No newline at end of file diff --git a/protocol/ModBus/DPV1Master.py b/protocol/ModBus/DPV1Master.py index d20ac18..c12a826 100644 --- a/protocol/ModBus/DPV1Master.py +++ b/protocol/ModBus/DPV1Master.py @@ -64,6 +64,8 @@ class DPV1Master(): # self.searchSlaveThread = SearchSlaveThread(callback = callback, master = self) # self.searchSlaveThread.start() data = self.readHoldingRegisters(1, 850, 64) + # print(1111) + # print(data) if data == '读取错误': return data dataHex = struct.pack('>64h', *data) diff --git a/protocol/ModBus/tcpmaster_example.py b/protocol/ModBus/tcpmaster_example.py index e7b52cc..45548cb 100644 --- a/protocol/ModBus/tcpmaster_example.py +++ b/protocol/ModBus/tcpmaster_example.py @@ -33,6 +33,8 @@ class TcpMaster(): valueByte = floatToBADC(outputValue) elif order == 'CDAB': valueByte = floatToCDAB(outputValue) + # elif order == 'Profibus': + else: valueByte = floatToABCD(outputValue) # 浮点数必须使用 WRITE_MULTIPLE_REGISTERS,因为需要写入2个寄存器 @@ -95,7 +97,7 @@ class TcpMaster(): if order == 'int': valueByte = self.master.execute(slaveId, cst.READ_HOLDING_REGISTERS, startAddress, varNums)[0] else: - value = self.master.execute(slaveId, cst.READ_HOLDING_REGISTERS, startAddress, 2) + value = self.master.execute(slaveId, cst.READ_HOLDING_REGISTERS, startAddress, varNums) if order == 'ABCD': valueByte = ABCDToFloat(value) elif order == 'DCBA': @@ -104,6 +106,8 @@ class TcpMaster(): valueByte = BADCToFloat(value) elif order == 'CDAB': valueByte = CDABToFloat(value) + elif order == 'Profibus': + return value return valueByte except: return 'error' diff --git a/protocol/ProtocolManage.py b/protocol/ProtocolManage.py index 98e3f52..4445cd9 100644 --- a/protocol/ProtocolManage.py +++ b/protocol/ProtocolManage.py @@ -2,6 +2,8 @@ from utils.DBModels.ProtocolModel import ( ModbusTcpMasterVar, ModbusTcpSlaveVar, ModbusRtuMasterVar, ModbusRtuSlaveVar, HartVar, TcRtdVar, AnalogVar, HartSimulateVar ) +from model.ProjectModel.GlobalConfigManager import GlobalConfigManager +from protocol.HartRtuSlaveManager import HartRtuSlaveManager from protocol.TCP.TCPVarManage import * from protocol.TCP.TemToMv import temToMv @@ -14,19 +16,22 @@ import time class ProtocolManage(object): """通讯变量查找类,用于根据变量名在数据库模型中检索变量信息""" - - # 支持的模型类列表 - MODEL_CLASSES = [ - ModbusTcpMasterVar, ModbusTcpSlaveVar, - ModbusRtuMasterVar, ModbusRtuSlaveVar, - HartVar, TcRtdVar, AnalogVar, HartSimulateVar - ] def __init__(self): - # self.tcpVarManager = TCPVarManager('192.168.1.50', 5055) - self.tcpVarManager = TCPVarManager('127.0.0.1', 8000) - self.writeTC = [0] * 8 - self.writeRTD = [0] * 8 + # 根据全局配置动态获取启用的模型类 + self.MODEL_CLASSES = GlobalConfigManager.getEnabledModelClasses() + + # 根据配置决定是否初始化TCPVarManager(仅当启用IO或TCRTD模块时) + if GlobalConfigManager.needsTcpVarManager(): + # self.tcpVarManager = TCPVarManager('192.168.1.50', 5055) + self.tcpVarManager = TCPVarManager('127.0.0.1', 8000) + self.writeTC = [0] * 8 + self.writeRTD = [0] * 8 + else: + self.tcpVarManager = None + self.writeTC = None + self.writeRTD = None + self.RpcClient = None self.RpcServer = None self.varInfoCache = {} # 保持驼峰命名 @@ -37,6 +42,16 @@ class ProtocolManage(object): self.modbusManager = ModbusManager() # self.modbusManager.setVariableCache(self.variableValueCache, None, self.varInfoCache) + # HART模拟RTU从站管理器 + self.hartRtuSlaveManager = None + if GlobalConfigManager.isModuleEnabled('hartSimulateModule'): + try: + self.hartRtuSlaveManager = HartRtuSlaveManager() + print("HART模拟RTU从站管理器已初始化") + except Exception as e: + print(f"初始化HART模拟RTU从站管理器失败: {e}") + self.hartRtuSlaveManager = None + self.refreshVarCache() self.cacheLock = threading.Lock() # 设置 Modbus 管理器的缓存锁 @@ -53,6 +68,8 @@ class ProtocolManage(object): def refreshVarCache(self): """重新加载所有变量信息到缓存(可选实现)""" self.varInfoCache.clear() + # 重新获取最新的启用模型类列表 + self.MODEL_CLASSES = GlobalConfigManager.getEnabledModelClasses() for modelClass in self.MODEL_CLASSES: try: for varInstance in modelClass.select(): @@ -175,6 +192,10 @@ class ProtocolManage(object): # 温度/RTD变量处理 elif modelType == 'TcRtdVar': + if not self.tcpVarManager: + print("TCPVarManager未初始化,无法处理TcRtdVar变量") + return False + channel = int(info['channelNumber']) - 1 varType = info['varType'] compensationVar = float(info['compensationVar']) @@ -195,6 +216,10 @@ class ProtocolManage(object): # 模拟量变量处理 elif modelType == 'AnalogVar': + if not self.tcpVarManager: + print("TCPVarManager未初始化,无法处理AnalogVar变量") + return False + channel = int(info['channelNumber']) - 1 varType = info['varType'] varModel = info['varModel'] @@ -206,7 +231,27 @@ class ProtocolManage(object): # HART模拟变量处理 elif modelType == 'HartSimulateVar': - pass + if not self.hartRtuSlaveManager: + print("HART RTU从站管理器未初始化,无法处理HartSimulateVar变量") + return False + + # 根据变量名确定是主变量还是动态变量 + varName = variableName.lower() + if 'primary' in varName or '主变量' in variableName: + success = self.hartRtuSlaveManager.writeVariable('primaryVariable', float(value)) + elif 'dynamic1' in varName or '动态变量1' in variableName: + success = self.hartRtuSlaveManager.writeVariable('dynamicVariable1', float(value)) + elif 'dynamic2' in varName or '动态变量2' in variableName: + success = self.hartRtuSlaveManager.writeVariable('dynamicVariable2', float(value)) + elif 'dynamic3' in varName or '动态变量3' in variableName: + success = self.hartRtuSlaveManager.writeVariable('dynamicVariable3', float(value)) + else: + # 默认写入主变量 + success = self.hartRtuSlaveManager.writeVariable('primaryVariable', float(value)) + + if not success: + print(f"HART模拟变量 {variableName} 写入失败") + return False if self.RpcClient: self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType']) @@ -286,6 +331,9 @@ class ProtocolManage(object): elif modelType == 'HartVar': pass elif modelType == 'TcRtdVar': + if not self.tcpVarManager: + return None + channel = int(info['channelNumber']) - 1 varType = info['varType'] varModel = info['varModel'] @@ -297,10 +345,13 @@ class ProtocolManage(object): value = self.tcpVarManager.simTCData[channel] else: if varType == 'PT100': - value = self.writeRTD[channel] + value = self.writeRTD[channel] if self.writeRTD else 0 elif varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']: - value = self.writeTC[channel] + value = self.writeTC[channel] if self.writeTC else 0 elif modelType == 'AnalogVar': + if not self.tcpVarManager: + return None + channel = int(info['channelNumber']) - 1 varType = info['varType'] varModel = info['varModel'] @@ -309,7 +360,22 @@ class ProtocolManage(object): if varType in ['AI', 'AO']: value = self.getRealAI(value, info['max'], info['min']) elif modelType == 'HartSimulateVar': - pass + if not self.hartRtuSlaveManager: + return None + + # 根据变量名确定是主变量还是动态变量 + varName = variableName.lower() + if 'primary' in varName or '主变量' in variableName: + value = self.hartRtuSlaveManager.readVariable('primaryVariable') + elif 'dynamic1' in varName or '动态变量1' in variableName: + value = self.hartRtuSlaveManager.readVariable('dynamicVariable1') + elif 'dynamic2' in varName or '动态变量2' in variableName: + value = self.hartRtuSlaveManager.readVariable('dynamicVariable2') + elif 'dynamic3' in varName or '动态变量3' in variableName: + value = self.hartRtuSlaveManager.readVariable('dynamicVariable3') + else: + # 默认读取主变量 + value = self.hartRtuSlaveManager.readVariable('primaryVariable') if value is not None and value != 'error': if self.RpcClient: self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType']) @@ -328,12 +394,18 @@ class ProtocolManage(object): return self._readVariableValueOriginal(variableName) def recvDeltaT(self): - return self.tcpVarManager.recvDeltaT() + if self.tcpVarManager: + return self.tcpVarManager.recvDeltaT() + return None def shutdown(self): - self.tcpVarManager.shutdown() + if self.tcpVarManager: + self.tcpVarManager.shutdown() # 关闭所有Modbus通讯 self.modbusManager.stopAllModbus() + # 关闭HART RTU从站 + if self.hartRtuSlaveManager: + self.hartRtuSlaveManager.stopSlave() self.closeClient() self.closeServer() # 关闭后台读取线程 @@ -435,4 +507,29 @@ class ProtocolManage(object): def clearModbusMessages(self): """清空 Modbus 报文记录""" - self.modbusManager.clearMessages() \ No newline at end of file + self.modbusManager.clearMessages() + + # ==================== HART模拟RTU从站管理方法 ==================== + + def startHartRtuSlave(self): + """启动HART RTU从站""" + if not self.hartRtuSlaveManager: + return False + return self.hartRtuSlaveManager.startSlave() + + def stopHartRtuSlave(self): + """停止HART RTU从站""" + if self.hartRtuSlaveManager: + self.hartRtuSlaveManager.stopSlave() + + def writeHartVariable(self, variableName: str, value: float) -> bool: + """写入HART变量值""" + if not self.hartRtuSlaveManager: + return False + return self.hartRtuSlaveManager.writeVariable(variableName, value) + + def readHartVariable(self, variableName: str): + """读取HART变量值""" + if not self.hartRtuSlaveManager: + return None + return self.hartRtuSlaveManager.readVariable(variableName) \ No newline at end of file