diff --git a/Static/Main.qss b/Static/Main.qss index f02b8f0..2017598 100644 --- a/Static/Main.qss +++ b/Static/Main.qss @@ -713,13 +713,13 @@ QCheckBox#userBox::indicator{ QCheckBox#userBox::indicator:checked{ - image: url(:/static/checkoff.png); + image: url(./Static/checkoff.png); } QCheckBox#userBox::indicator:enabled:unchecked{ - image: url(:/static/checkon.png); + image: url(./Static/checkon.png); } @@ -816,7 +816,7 @@ QComboBox#timeBox::drop-down{ QComboBox#setBox::drop-down, QComboBox#ProTypeBox::drop-down,QComboBox#TcRtdTypeBox::drop-down, QComboBox#ModbusTypeBox::drop-down, QComboBox#timeBox::drop-down{ - image: url(:/static/down.png); + image: url(./Static/down.png); } diff --git a/UI/LoginWidgets/LoginWidget.py b/UI/LoginWidgets/LoginWidget.py index 77e3516..e14a468 100644 --- a/UI/LoginWidgets/LoginWidget.py +++ b/UI/LoginWidgets/LoginWidget.py @@ -28,7 +28,7 @@ class LoginWidget(QWidget): self.resize(306, 451) self.picLabel = QLabel() - self.picLabel.setPixmap(QPixmap(':/static/userPic.png')) + self.picLabel.setPixmap(QPixmap('./Static/userPic.png')) self.picLabel.setScaledContents(True) self.userEdit = QLineEdit() diff --git a/UI/Main/Main.py b/UI/Main/Main.py index dbfed97..445f32d 100644 --- a/UI/Main/Main.py +++ b/UI/Main/Main.py @@ -45,7 +45,7 @@ class MainWindow(QMainWindow): super(MainWindow, self).__init__() InitParameterDB() self.setupUi() - # self.setStyleSheet(CommonHelper.readQss('static/main.qss')) + # self.setStyleSheet(CommonHelper.readQss('Static/main.qss')) self.setWindowFlags(Qt.FramelessWindowHint|Qt.WindowSystemMenuHint) self.SHADOW_WIDTH = 0 #边框距离 @@ -130,7 +130,7 @@ class MainWindow(QMainWindow): self.varManageTabWidget.addTab(self.rpcVarTableWidget, '远程通讯') #添加导入变量按钮 - self.importVarButton = QPushButton(QIcon(':/static/import.png'), '导入变量') + self.importVarButton = QPushButton(QIcon('./Static/import.png'), '导入变量') self.importVarButton.setObjectName('importBtn') self.importVarButton.setIconSize(QSize(22, 22)) self.importVarButton.setFlat(True) @@ -421,7 +421,7 @@ class MainWindow(QMainWindow): if __name__ == '__main__': app = QApplication(sys.argv) app.setStyle(QtWidgets.QStyleFactory.create('Fusion')) - app.setStyleSheet(CommonHelper.readQss('static/main.qss')) + app.setStyleSheet(CommonHelper.readQss('Static/main.qss')) # print(QtWidgets.QStyleFactory.keys()) ex = MainWindow() ex.show() diff --git a/UI/Main/MainLeft.py b/UI/Main/MainLeft.py index fb821eb..44d3379 100644 --- a/UI/Main/MainLeft.py +++ b/UI/Main/MainLeft.py @@ -77,23 +77,23 @@ class MainLeft(QWidget): self.protocolMag.setText("通讯配置") self.procedureMag.setText("规程管理") - self.createProject.setIcon(QIcon(':/static/newH.png')) - # self.openProject.setIcon(QIcon(':/static/open.png')) - self.varMag.setIcon(QIcon(':/static/varMag.png')) - self.trendMag.setIcon(QIcon(':/static/trend.png')) - self.userMag.setIcon(QIcon(':/static/userMag.png')) - self.protocolMag.setIcon(QIcon(':/static/setting.png')) - self.procedureMag.setIcon(QIcon(':/static/procedure.png')) + self.createProject.setIcon(QIcon('./Static/newH.png')) + # self.openProject.setIcon(QIcon('./Static/open.png')) + self.varMag.setIcon(QIcon('./Static/varMag.png')) + self.trendMag.setIcon(QIcon('./Static/trend.png')) + self.userMag.setIcon(QIcon('./Static/userMag.png')) + self.protocolMag.setIcon(QIcon('./Static/setting.png')) + self.procedureMag.setIcon(QIcon('./Static/procedure.png')) for btn in [self.createProject, self.varMag, self.trendMag, self.userMag, self.protocolMag, self.procedureMag]: self.setBtn(btn) - # self.openProject.clicked.connect(lambda:self.openProject.setIcon(QIcon(':/static/openH.png'))) - self.createProject.clicked.connect(lambda:self.createProject.setIcon(QIcon(':/static/newH.png'))) - self.varMag.clicked.connect(lambda:self.varMag.setIcon(QIcon(':/static/varMagH.png'))) - self.trendMag.clicked.connect(lambda:self.trendMag.setIcon(QIcon(':/static/trendH.png'))) - self.userMag.clicked.connect(lambda:self.userMag.setIcon(QIcon(':/static/userMagH.png'))) - self.protocolMag.clicked.connect(lambda:self.protocolMag.setIcon(QIcon(':/static/settingH.png'))) + # self.openProject.clicked.connect(lambda:self.openProject.setIcon(QIcon('./Static/openH.png'))) + self.createProject.clicked.connect(lambda:self.createProject.setIcon(QIcon('./Static/newH.png'))) + self.varMag.clicked.connect(lambda:self.varMag.setIcon(QIcon('./Static/varMagH.png'))) + self.trendMag.clicked.connect(lambda:self.trendMag.setIcon(QIcon('./Static/trendH.png'))) + self.userMag.clicked.connect(lambda:self.userMag.setIcon(QIcon('./Static/userMagH.png'))) + self.protocolMag.clicked.connect(lambda:self.protocolMag.setIcon(QIcon('./Static/settingH.png'))) self.createProject.setChecked(True) self.createProject.setDown(True) @@ -114,45 +114,45 @@ class MainLeft(QWidget): def eventFilter(self, object, event): if object == self.createProject: if event.type() == QtCore.QEvent.HoverEnter: - self.createProject.setIcon(QIcon(':/static/newH.png')) + self.createProject.setIcon(QIcon('./Static/newH.png')) return True if event.type() == QtCore.QEvent.HoverLeave and not self.createProject.isChecked(): - self.createProject.setIcon(QIcon(':/static/new.png')) + self.createProject.setIcon(QIcon('./Static/new.png')) return True # if object == self.openProject: # if event.type() == QtCore.QEvent.HoverEnter: - # self.openProject.setIcon(QIcon(':/static/openH.png')) + # self.openProject.setIcon(QIcon('./Static/openH.png')) # return True # if event.type() == QtCore.QEvent.HoverLeave and not self.openProject.isChecked(): - # self.openProject.setIcon(QIcon(':/static/open.png')) + # self.openProject.setIcon(QIcon('./Static/open.png')) # return True if object == self.varMag: if event.type() == QtCore.QEvent.HoverEnter: - self.varMag.setIcon(QIcon(':/static/varMagH.png')) + self.varMag.setIcon(QIcon('./Static/varMagH.png')) return True if event.type() == QtCore.QEvent.HoverLeave and not self.varMag.isChecked(): - self.varMag.setIcon(QIcon(':/static/varMag.png')) + self.varMag.setIcon(QIcon('./Static/varMag.png')) return True if object == self.trendMag: if event.type() == QtCore.QEvent.HoverEnter: - self.trendMag.setIcon(QIcon(':/static/trendH.png')) + self.trendMag.setIcon(QIcon('./Static/trendH.png')) return True if event.type() == QtCore.QEvent.HoverLeave and not self.trendMag.isChecked(): - self.trendMag.setIcon(QIcon(':/static/trend.png')) + self.trendMag.setIcon(QIcon('./Static/trend.png')) return True if object == self.userMag: if event.type() == QtCore.QEvent.HoverEnter: - self.userMag.setIcon(QIcon(':/static/userMagH.png')) + self.userMag.setIcon(QIcon('./Static/userMagH.png')) return True if event.type() == QtCore.QEvent.HoverLeave and not self.userMag.isChecked(): - self.userMag.setIcon(QIcon(':/static/userMag.png')) + self.userMag.setIcon(QIcon('./Static/userMag.png')) return True if object == self.protocolMag: if event.type() == QtCore.QEvent.HoverEnter: - self.protocolMag.setIcon(QIcon(':/static/settingH.png')) + self.protocolMag.setIcon(QIcon('./Static/settingH.png')) return True if event.type() == QtCore.QEvent.HoverLeave and not self.protocolMag.isChecked(): - self.protocolMag.setIcon(QIcon(':/static/setting.png')) + self.protocolMag.setIcon(QIcon('./Static/setting.png')) return True return False @@ -164,9 +164,9 @@ class MainLeft(QWidget): self.userMag.setDown(False) self.protocolMag.setDown(False) - self.createProject.setIcon(QIcon(':/static/new.png')) - # self.openProject.setIcon(QIcon(':/static/open.png')) - self.varMag.setIcon(QIcon(':/static/varMag.png')) - self.trendMag.setIcon(QIcon(':/static/trend.png')) - self.userMag.setIcon(QIcon(':/static/userMag.png')) - self.protocolMag.setIcon(QIcon(':/static/setting.png')) + self.createProject.setIcon(QIcon('./Static/new.png')) + # self.openProject.setIcon(QIcon('./Static/open.png')) + self.varMag.setIcon(QIcon('./Static/varMag.png')) + self.trendMag.setIcon(QIcon('./Static/trend.png')) + self.userMag.setIcon(QIcon('./Static/userMag.png')) + self.protocolMag.setIcon(QIcon('./Static/setting.png')) diff --git a/UI/Main/MainTop.py b/UI/Main/MainTop.py index 59774d4..1fb1f91 100644 --- a/UI/Main/MainTop.py +++ b/UI/Main/MainTop.py @@ -24,7 +24,7 @@ class MainTop(QtWidgets.QWidget): self.searchEdit = QtWidgets.QLineEdit() action = QtWidgets.QAction(self) - action.setIcon(QIcon(':/static/search.png')) + action.setIcon(QIcon('./Static/search.png')) self.searchEdit.addAction(action, QtWidgets.QLineEdit.LeadingPosition) self.searchEdit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) self.searchEdit.setObjectName('searchEdit') @@ -35,16 +35,16 @@ class MainTop(QtWidgets.QWidget): self.searchEdit.textChanged.connect(self.searchEditTextChanged) # print(Globals.getValue('SearchWidget')) - self.minBtn = QtWidgets.QPushButton(QIcon(':/static/min.png'), '' ,self) + self.minBtn = QtWidgets.QPushButton(QIcon('./Static/min.png'), '' ,self) self.minBtn.setObjectName("minBtn") - self.maxBtn = QtWidgets.QPushButton(QIcon(':/static/normal.png'), '' ,self) + self.maxBtn = QtWidgets.QPushButton(QIcon('./Static/normal.png'), '' ,self) self.maxBtn.setObjectName("maxBtn") - self.closeBtn = QtWidgets.QPushButton(QIcon(':/static/close.png'), '' ,self) + self.closeBtn = QtWidgets.QPushButton(QIcon('./Static/close.png'), '' ,self) self.closeBtn.setObjectName("closeBtn") - self.iconLabol.setPixmap(QPixmap(':/static/zhjt.png').scaled(50,50)) + self.iconLabol.setPixmap(QPixmap('./Static/zhjt.png').scaled(50,50)) self.titleLabel.setText("信号发生装置") self.closeBtn.clicked.connect(self.MainWindow.close) @@ -95,14 +95,14 @@ class MainTop(QtWidgets.QWidget): def showMax(self): if self.MainWindow.isMaximized(): self.MainWindow.showNormal() - self.maxBtn.setIcon(QIcon(':/static/max.png')) + self.maxBtn.setIcon(QIcon('./Static/max.png')) self.MainWindow.verticalLayout.setStretch(0, 1) self.MainWindow.verticalLayout.setStretch(1, 15) else: self.MainWindow.showMaximized() self.MainWindow.verticalLayout.setStretch(0, 1) self.MainWindow.verticalLayout.setStretch(1, 18) - self.maxBtn.setIcon(QIcon(':/static/normal.png')) + self.maxBtn.setIcon(QIcon('./Static/normal.png')) def mouseDoubleClickEvent(self, e): # 双击 self.showMax() diff --git a/UI/ProfibusWidgets/AreaTabWidget.py b/UI/ProfibusWidgets/AreaTabWidget.py index 4bb0a81..5594ca1 100644 --- a/UI/ProfibusWidgets/AreaTabWidget.py +++ b/UI/ProfibusWidgets/AreaTabWidget.py @@ -36,7 +36,7 @@ class AreaTabWidget(QTabWidget): self.tabBar().setObjectName('areaTabBar') self.addAreaButton = QPushButton("添加通道 ") self.addAreaButton.setObjectName('addareabutton') - self.addAreaButton.setIcon(QIcon(':/static/add.png')) + self.addAreaButton.setIcon(QIcon('./Static/add.png')) self.addAreaButton.setFlat(True) self.addAreaButton.clicked.connect(self.addAreaTab) self.setCornerWidget(self.addAreaButton) @@ -75,7 +75,7 @@ class AreaTabWidget(QTabWidget): layout = QHBoxLayout() addButton = QPushButton('添加通道') addButton.setObjectName('initAreaAddButton') - icon = QIcon(':/static/add.png') + icon = QIcon('./Static/add.png') iconSize = QSize(50,50) addButton.setIcon(icon) addButton.setIconSize(iconSize) @@ -196,7 +196,7 @@ class AreaWidget(QWidget): self.okBtnValue = True self.delAreaBtn = QPushButton('删除') - self.delAreaBtn.setIcon(QIcon(':/static/delete.png')) + self.delAreaBtn.setIcon(QIcon('./Static/delete.png')) self.delAreaBtn.setObjectName('delAreaBtn') self.delAreaBtn.clicked.connect(self.removeAreaTab) diff --git a/UI/ProfibusWidgets/ProfibusWindow.py b/UI/ProfibusWidgets/ProfibusWindow.py index 9ac09a7..1213b5c 100644 --- a/UI/ProfibusWidgets/ProfibusWindow.py +++ b/UI/ProfibusWidgets/ProfibusWindow.py @@ -102,7 +102,7 @@ class ProfibusWidgets(QWidget): self.startProtocolBtn = QPushButton() self.startProtocolBtn.setObjectName("profibusStartProtocolBtn") - # self.startProtocolBtn.setIcon(QIcon(':/static/startProtocol.png')) + # self.startProtocolBtn.setIcon(QIcon('./Static/startProtocol.png')) self.startProtocolBtn.setText('开始通讯') self.startProtocolBtn.setIcon(QIcon('Static/start_green.png')) self.startProtocolBtn.setIconSize(QSize(23, 23)) @@ -149,9 +149,9 @@ class ProfibusWidgets(QWidget): # 创建按钮 - # self.minimizeButton = QPushButton(QIcon(':/static/min.png'), "") + # self.minimizeButton = QPushButton(QIcon('./Static/min.png'), "") # self.minimizeButton.setObjectName('minButton') - # self.closeButton = QPushButton(QIcon(':/static/close.png'), "") + # self.closeButton = QPushButton(QIcon('./Static/close.png'), "") # self.closeButton.setObjectName('closeButton') # # 按钮点击事件连接 @@ -162,7 +162,7 @@ class ProfibusWidgets(QWidget): # hLayout.addWidget(self.closeButton) iconLabel = QLabel() - pix = QPixmap('static/Hicent.png') + pix = QPixmap('Static/Hicent.png') scaledPixmap = pix.scaled(168, 28, Qt.KeepAspectRatio) iconLabel.setPixmap(scaledPixmap) iconLabel.setScaledContents(True) @@ -222,7 +222,7 @@ class ProfibusWidgets(QWidget): self.setLayout(self.mainLayout) # self.setCentralWidget(self.stackWidget) - self.setWindowIcon(QIcon(':/static/Hicent.jpg')) + self.setWindowIcon(QIcon('./Static/Hicent.jpg')) self.setWindowTitle("PROFIBUS总线测试工具") @@ -239,13 +239,13 @@ class ProfibusWidgets(QWidget): if self.startProtocolBtn.isChecked(): self.startProtocolBtn.setText('停止通讯') - self.startProtocolBtn.setIcon(QIcon(':/static/pause.png')) + self.startProtocolBtn.setIcon(QIcon('./Static/pause.png')) self.startProtocolBtn.setIconSize(QSize(22, 22)) self.protocolTimer.start(500) else: self.startProtocolBtn.setText('开始通讯') - self.startProtocolBtn.setIcon(QIcon(':/static/start.png')) + self.startProtocolBtn.setIcon(QIcon('./Static/start.png')) self.protocolTimer.stop() def readValues(self): @@ -315,7 +315,7 @@ class ProfibusWidgets(QWidget): self.process = subprocess.Popen("D:\\EnTalk PROFIBUS Manager\\DP.exe",startupinfo=startupInfo) QTimer.singleShot(500, lambda:self.showLowerWidget(self.process)) # self.showFullScreen() - # self.switchBtn.setIcon(QIcon(':/static/varMagH.png')) + # self.switchBtn.setIcon(QIcon('./Static/varMagH.png')) def switchDeviceValueManageWidget(self): self.stackWidget.setCurrentIndex(0) diff --git a/UI/ProjectManages/ProjectWidget.py b/UI/ProjectManages/ProjectWidget.py index 8153f18..cfb6fbc 100644 --- a/UI/ProjectManages/ProjectWidget.py +++ b/UI/ProjectManages/ProjectWidget.py @@ -13,17 +13,17 @@ class ProjectWidgets(QtWidgets.QWidget): self.setupUi() def setupUi(self): - self.createBtn = QPushButton(QIcon(':/static/add.png'), '新建工程') + self.createBtn = QPushButton(QIcon('./Static/add.png'), '新建工程') self.createBtn.setObjectName('createBtn') self.createBtn.setIconSize(QSize(22, 22)) self.createBtn.clicked.connect(self.createProject) - self.importBtn = QPushButton(QIcon(':/static/import.png'), '导入工程') + self.importBtn = QPushButton(QIcon('./Static/import.png'), '导入工程') self.importBtn.setObjectName('importBtn') self.importBtn.setIconSize(QSize(22, 22)) self.importBtn.clicked.connect(self.importProject) - self.exportBtn = QPushButton(QIcon(':/static/export.png'), '导出工程') + self.exportBtn = QPushButton(QIcon('./Static/export.png'), '导出工程') self.exportBtn.setObjectName('exportBtn') self.exportBtn.setIconSize(QSize(22, 22)) self.exportBtn.clicked.connect(self.exportPorject) diff --git a/UI/RegisterWidgets/RegisterWidget.py b/UI/RegisterWidgets/RegisterWidget.py index 94fbe95..71ccd46 100644 --- a/UI/RegisterWidgets/RegisterWidget.py +++ b/UI/RegisterWidgets/RegisterWidget.py @@ -97,7 +97,7 @@ class RegisterWidget(QWidget): self.setWindowTitle("Form") - pix = QtGui.QPixmap(':/:/static/logo.png') + pix = QtGui.QPixmap(':/Static/logo.png') self.label.setPixmap(pix) self.label.setScaledContents(True) self.label.setMaximumSize(QSize(110,40)) @@ -145,7 +145,7 @@ class RegisterWidget(QWidget): '提示', "注册成功", QMessageBox.Yes) - with open('static/license.lic', 'w') as f: + with open('Static/license.lic', 'w') as f: f.write(passwd) self.showMainWindow() self.close() diff --git a/UI/UserManage/UserWidget.py b/UI/UserManage/UserWidget.py index 49fc6ee..fdf957c 100644 --- a/UI/UserManage/UserWidget.py +++ b/UI/UserManage/UserWidget.py @@ -16,16 +16,16 @@ class UserWidgets(QtWidgets.QWidget): super(UserWidgets, self).__init__(parent) self.setAttribute(Qt.WA_StyledBackground, True) - self.createBtn = QPushButton(QIcon(':/static/add.png'), '添加用户') + self.createBtn = QPushButton(QIcon('./Static/add.png'), '添加用户') self.createBtn.setObjectName('forceBtn') self.createBtn.setIconSize(QSize(22, 22)) self.createBtn.clicked.connect(self.createUser) - # self.importBtn = QPushButton(QIcon(':/static/import.png'), '导入变量') + # self.importBtn = QPushButton(QIcon('./Static/import.png'), '导入变量') # self.importBtn.setObjectName('importBtn') # self.importBtn.setIconSize(QSize(22, 22)) - self.delBtn = QPushButton(QIcon(':/static/delete.png'), '批量删除') + self.delBtn = QPushButton(QIcon('./Static/delete.png'), '批量删除') self.delBtn.setObjectName('delBtn') self.delBtn.setIconSize(QSize(22, 22)) self.delBtn.clicked.connect(self.deleteUser) diff --git a/UI/VarManages/MessageWidget.py b/UI/VarManages/MessageWidget.py index 5eca398..1de5d06 100644 --- a/UI/VarManages/MessageWidget.py +++ b/UI/VarManages/MessageWidget.py @@ -1,116 +1,113 @@ -from PyQt5.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, - QMetaObject, QObject, QPoint, QRect, - QSize, QTime, QUrl, Qt, QTimer) -from PyQt5.QtGui import (QBrush, QColor, QConicalGradient, QCursor, - QFont, QFontDatabase, QGradient, QIcon, - QImage, QKeySequence, QLinearGradient, QPainter, - QPalette, QPixmap, QRadialGradient, QTransform) -from PyQt5.QtWidgets import (QApplication, QGridLayout, QHBoxLayout, QLabel, - QPushButton, QSizePolicy, QSpacerItem, QTextEdit, - QWidget) +from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QTextEdit, QHBoxLayout, QVBoxLayout, QGridLayout, QSpacerItem, QSizePolicy +from PyQt5.QtCore import Qt, QTimer, QCoreApplication +from PyQt5.QtGui import QIcon +from utils import Globals + -from protocol.Celery.MBTCPMaster import app -from Static import static class MessageWidget(QWidget): def __init__(self, parent=None): super(MessageWidget, self).__init__(parent) - - self.setObjectName(u"self") + self.setupUI() + self.setupConnections() + + def setupUI(self): + """设置UI界面""" + self.setObjectName("MessageWidget") self.resize(1037, 648) - - self.decima = 16 - - self.recvLabel = QLabel(self) + self.setWindowTitle('报文查看') + # self.setWindowIcon(QIcon('./Static/file.png')) + + # 创建控件 + self.recvLabel = QLabel("接收到报文") self.recvLabel.setObjectName("mesLabel") self.recvLabel.setAlignment(Qt.AlignCenter) - - self.sendLabel = QLabel(self) + + self.sendLabel = QLabel("发送的报文") self.sendLabel.setObjectName("mesLabel") self.sendLabel.setAlignment(Qt.AlignCenter) - - self.clearButton = QPushButton(self) + + self.clearButton = QPushButton("清空报文") self.clearButton.setObjectName("mesButton") - - self.reButton = QPushButton(self) - self.reButton.setObjectName("mesButton") - - self.decimaButton = QPushButton(self) - self.decimaButton.setObjectName("mesButton") - - self.recvEdit = QTextEdit(self) + + self.refreshButton = QPushButton("停止刷新") + self.refreshButton.setObjectName("mesButton") + + self.recvEdit = QTextEdit() self.recvEdit.setObjectName("mesEdit") - - self.sendEdit = QTextEdit(self) + + self.sendEdit = QTextEdit() self.sendEdit.setObjectName("mesEdit") - - self.horizontalLayout = QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.horizontalLayout.addWidget(self.clearButton) - self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) - self.horizontalLayout.addItem(self.horizontalSpacer) - self.horizontalLayout.addWidget(self.reButton) - self.horizontalLayout.addWidget(self.decimaButton) - - self.gridLayout = QGridLayout(self) - self.gridLayout.setObjectName("gridLayout") - self.gridLayout.addWidget(self.recvLabel, 1, 0, 1, 1) - self.gridLayout.addWidget(self.sendLabel, 1, 1, 1, 1) - self.gridLayout.addWidget(self.recvEdit, 2, 0, 1, 1) - self.gridLayout.addWidget(self.sendEdit, 2, 1, 1, 1) - self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 2) - - self.setWindowTitle('报文查看') - self.setWindowIcon(QIcon('./:/static/file.png')) - self.recvLabel.setText(QCoreApplication.translate("Form", u"\u63a5\u6536\u5230\u62a5\u6587", None)) - self.sendLabel.setText(QCoreApplication.translate("Form", u"\u53d1\u9001\u7684\u62a5\u6587", None)) - self.clearButton.setText("清空报文") - self.reButton.setText('停止刷新') - self.decimaButton.setText('切换进制') - + + # 布局设置 + self.setupLayout() + + # 定时器设置 self.timer = QTimer(self) - # 将定时器超时信号与槽函数showTime()连接 - self.timer.timeout.connect(self.addText) - self.timer.start(1000) # 启动timer - - self.reButton.clicked.connect(self.stopRe) - self.decimaButton.clicked.connect(self.changeDecima) - self.clearButton.clicked.connect(self.clearText) - - def clearText(self): + self.timer.timeout.connect(self.updateMessages) + self.timer.start(1000) # 每秒更新一次 + + def setupLayout(self): + """设置布局""" + # 按钮布局 + buttonLayout = QHBoxLayout() + buttonLayout.addWidget(self.clearButton) + buttonSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + buttonLayout.addItem(buttonSpacer) + buttonLayout.addWidget(self.refreshButton) + + # 主布局 + mainLayout = QGridLayout(self) + mainLayout.addLayout(buttonLayout, 0, 0, 1, 2) + mainLayout.addWidget(self.recvLabel, 1, 0, 1, 1) + mainLayout.addWidget(self.sendLabel, 1, 1, 1, 1) + mainLayout.addWidget(self.recvEdit, 2, 0, 1, 1) + mainLayout.addWidget(self.sendEdit, 2, 1, 1, 1) + + def setupConnections(self): + """设置信号连接""" + self.clearButton.clicked.connect(self.clearMessages) + self.refreshButton.clicked.connect(self.toggleRefresh) + + def updateMessages(self): + """更新报文显示""" + try: + protocolManage = Globals.getValue('protocolManage') + if not protocolManage: + return + + # 获取 Modbus 报文 + messages = protocolManage.getModbusMessages() + + # 清空并更新接收报文 + self.recvEdit.clear() + for msg in messages['receive']: + self.recvEdit.append(msg) + + # 清空并更新发送报文 + self.sendEdit.clear() + for msg in messages['send']: + self.sendEdit.append(msg) + + except Exception as e: + pass + + def clearMessages(self): + """清空报文""" self.recvEdit.clear() self.sendEdit.clear() - - def changeDecima(self): - if self.decima == 16: - self.decima = 10 - elif self.decima == 10: - self.decima = 16 - - def stopRe(self): + + try: + protocolManage = Globals.getValue('protocolManage') + if protocolManage: + protocolManage.clearModbusMessages() + except Exception as e: + pass + + def toggleRefresh(self): + """切换刷新状态""" if self.timer.isActive(): self.timer.stop() - self.reButton.setText('开始刷新') + self.refreshButton.setText('开始刷新') else: - self.timer.start() - self.reButton.setText('停止刷新') - - def addText(self): - try: - self.recvEdit.clear() - self.sendEdit.clear() - if self.decima == 16: - for x in app.backend.client.lrange("16R" , 0 , -1): - text = str(x)[2:-1] - self.recvEdit.append(text) - for x in app.backend.client.lrange("16S" , 0 , -1): - text = str(x)[2:-1] - self.sendEdit.append(text) - elif self.decima == 10: - for x in app.backend.client.lrange("10R" , 0 , -1): - text = str(x)[2:-1] - self.recvEdit.append(text) - for x in app.backend.client.lrange("10S" , 0 , -1): - text = str(x)[2:-1] - self.sendEdit.append(text) - except: - pass \ No newline at end of file + self.timer.start(1000) + self.refreshButton.setText('停止刷新') \ No newline at end of file diff --git a/UI/VarManages/ModbusModel.py b/UI/VarManages/ModbusModel.py index cd48cc3..86db4a1 100644 --- a/UI/VarManages/ModbusModel.py +++ b/UI/VarManages/ModbusModel.py @@ -12,14 +12,7 @@ from PyQt5.QtWidgets import QItemDelegate, QHBoxLayout, QWidget, QMessageBox, QC from sympy import N -from protocol.Celery.MBTCPMaster import app as MBTCPMApp -from protocol.Celery.MBRTUMaster import app as MBRTUMApp -from protocol.Celery.MBRTUSlave import app as MBRTUSApp -from protocol.Celery.MBTCPSlave import app as MBTCPSApp -# from protocol.Celery.MBTCPMaster.MBTCPMTask import setValue as setMTcpValue -# from protocol.Celery.MBRTUMaster.MBRTUMTask import setValue as setMRTUValue - -from celery.result import AsyncResult +# 移除 Celery 相关导入,现在使用 ProtocolManage 和 ModbusManager from ..TrendManage.ActualTrendWidget import ActualTrend from model.ProjectModel.VarManage import * @@ -129,16 +122,13 @@ class VarTableModel(QAbstractTableModel): if QModelIndex.column() == 2: varName = self.datas[QModelIndex.row()][3] if varName != '': - # print(uid) try: - uid = MBTCPMApp.backend.get('ModBus').decode('utf-8') - res = AsyncResult(uid) # 参数为task id - if res.result: - # print(res.result, res.date_done) - result = res.result[varName] - if result or result in [0, '0']: + protocolManage = Globals.getValue('protocolManage') + if protocolManage: + result = protocolManage.readVariableValue(varName) + if result is not None: self.datas[QModelIndex.row()][QModelIndex.column()] = result - except: + except Exception as e: pass return QVariant(self.datas[QModelIndex.row()][QModelIndex.column()]) @@ -350,24 +340,24 @@ class VarButtonDelegate(QItemDelegate): "超出量程范围", QMessageBox.Yes) return - # 0 : MODBUSTCP 主站模式 - # 1 : MODBUSTCP 从站模式 - # 2 : MODBUSRTU 主站模式 - # 3 : MODBUSRTU 从站模式 - proType = Globals.getValue('currentProType') - forceVars = Globals.getValue('forceVars') - forceVars.add(name) - Globals.setValue('forceVars', forceVars) - # print(Globals.getValue('forceVars')) - - if proType == '0': - MBTCPMApp.send_task('protocol.Celery.MBTCPMaster.MBTCPMTask.setValue',args=[name, varType, slaveID, address, value, order]) - if proType == '1': - MBTCPSApp.send_task('protocol.Celery.MBTCPSlave.MBTCPSTask.setValue',args=[name, varType, slaveID, address, value]) - elif proType == '2': - MBRTUMApp.send_task('protocol.Celery.MBRTUMaster.MBRTUMTask.setValue',args=[name, varType, slaveID, address, value, order]) - elif proType == '3': - MBRTUSApp.send_task('protocol.Celery.MBRTUSlave.MBRTUSTask.setValue',args=[name, varType, slaveID, address, value]) + + # 使用新的 ProtocolManage 进行变量写入 + protocolManage = Globals.getValue('protocolManage') + if protocolManage: + try: + result = protocolManage.writeVariableValue(name, value) + if result: + forceVars = Globals.getValue('forceVars') + if forceVars is None: + forceVars = set() + forceVars.add(name) + Globals.setValue('forceVars', forceVars) + else: + QMessageBox.warning(self.parent(), '错误', f'变量 {name} 写入失败', QMessageBox.Yes) + except Exception as e: + QMessageBox.warning(self.parent(), '错误', f'写入变量时发生错误: {str(e)}', QMessageBox.Yes) + else: + QMessageBox.warning(self.parent(), '错误', '协议管理器未初始化', QMessageBox.Yes) def edit_action(self): sender = self.sender() diff --git a/UI/VarManages/VarWidget.py b/UI/VarManages/VarWidget.py index 3ee0592..6fcd435 100644 --- a/UI/VarManages/VarWidget.py +++ b/UI/VarManages/VarWidget.py @@ -10,10 +10,7 @@ from UI.VarManages.MessageWidget import MessageWidget from model.ProjectModel.ProjectManage import ProjectManage from model.ProjectModel.VarManage import ModbusVarManage from utils import Globals -from protocol.Celery.MBTCPMaster import app as MBTCPMApp -from protocol.Celery.MBRTUMaster import app as MBRTUMApp -from protocol.Celery.MBRTUSlave import app as MBRTUSApp -from protocol.Celery.MBTCPSlave import app as MBTCPSApp +# 移除 Celery 相关导入,现在使用 ProtocolManage 和 ModbusManager from protocol.TCP.TemToMv import temToMv from protocol.TCP.Analog import getRealAO import re @@ -28,37 +25,37 @@ class VarWidgets(QtWidgets.QWidget): def setupUI(self): self.setAttribute(Qt.WA_StyledBackground, True) - self.createBtn = QPushButton(QIcon(':/static/add.png'), '新建变量') + self.createBtn = QPushButton(QIcon(':/Static/add.png'), '新建变量') self.createBtn.setObjectName('createBtn') self.createBtn.setIconSize(QSize(22, 22)) self.createBtn.clicked.connect(self.createVar) - self.forceBtn = QPushButton(QIcon(':/static/start.png'), '批量强制') + self.forceBtn = QPushButton(QIcon(':/Static/start.png'), '批量强制') self.forceBtn.setObjectName('forceBtn') self.forceBtn.setIconSize(QSize(22, 22)) self.forceBtn.clicked.connect(self.forceVar) - self.clearBtn = QPushButton(QIcon(':/static/clear.png'), '清除颜色') + self.clearBtn = QPushButton(QIcon(':/Static/clear.png'), '清除颜色') self.clearBtn.setObjectName('clearBtn') self.clearBtn.setIconSize(QSize(22, 22)) self.clearBtn.clicked.connect(self.clearColour) - # self.importBtn = QPushButton(QIcon(':/static/import.png'), '导入变量') + # self.importBtn = QPushButton(QIcon(':/Static/import.png'), '导入变量') # self.importBtn.setObjectName('importBtn') # self.importBtn.setIconSize(QSize(22, 22)) # self.importBtn.clicked.connect(self.importVar) - # self.exportBtn = QPushButton(QIcon(':/static/export.png'), '导出变量') + # self.exportBtn = QPushButton(QIcon(':/Static/export.png'), '导出变量') # self.exportBtn.setObjectName('exportBtn') # self.exportBtn.setIconSize(QSize(22, 22)) # self.exportBtn.clicked.connect(self.exportVar) - self.messageBtn = QPushButton(QIcon(':/static/message.png'), '查看报文') + self.messageBtn = QPushButton(QIcon(':/Static/message.png'), '查看报文') self.messageBtn.setObjectName('messageBtn') self.messageBtn.setIconSize(QSize(22, 22)) self.messageBtn.clicked.connect(self.showMessage) - self.startProtocolBtn = QPushButton(QIcon(':/static/startProtocol.png'), '开始通讯') + self.startProtocolBtn = QPushButton(QIcon(':/Static/startProtocol.png'), '开始通讯') self.startProtocolBtn.setObjectName('startProtocolBtn') self.startProtocolBtn.setIconSize(QSize(22, 22)) self.startProtocolBtn.clicked.connect(self.startProtocol) @@ -210,51 +207,63 @@ class VarWidgets(QtWidgets.QWidget): self.varView.model.append_data(['', '', '', '', '', '', '', '', '', '', '','本地值','int']) def forceVar(self): - check = [i for i,x in enumerate(self.varView.model.checkList) if x == 'Checked'] - # check.sort(reverse=True) + check = [i for i, x in enumerate(self.varView.model.checkList) if x == 'Checked'] + if not check: + QMessageBox.information(self, '提示', '请先勾选要强制的变量', QMessageBox.Yes) + return + + protocolManage = Globals.getValue('protocolManage') + if not protocolManage: + QMessageBox.warning(self, '错误', '协议管理器未初始化', QMessageBox.Yes) + return + + forceVars = Globals.getValue('forceVars') + if forceVars is None: + forceVars = set() + for i in check: varMes = self.varView.model.datas[i] - value, name, des, varType, slaveID, address, min, max, order = str(varMes[1]), str(varMes[3]), str(varMes[4]), str(varMes[5]), str(varMes[6]), str(varMes[7]), str(varMes[8]), str(varMes[9]), str(varMes[10]), str(varMes[11]) + value, name, des, varType, slaveID, address, min, max, order = str(varMes[1]), str(varMes[3]), str(varMes[4]), str(varMes[5]), str(varMes[6]), str(varMes[7]), str(varMes[8]), str(varMes[9]), str(varMes[10]) pattern = re.compile(r'[^0-9\.-]+') if not value or re.findall(pattern, str(value)): - reply = QMessageBox.question(self.parent(), - '警告', - "请输入强制值或数字", - QMessageBox.Yes) + QMessageBox.warning(self, '警告', "请输入强制值或数字", QMessageBox.Yes) return if min and max: - if float(value) < float(min) or float(value) > float(max): - reply = QMessageBox.question(self.parent(), - '警告', - "超出量程范围", - QMessageBox.Yes) - return + try: + if float(value) < float(min) or float(value) > float(max): + QMessageBox.warning(self, '警告', "超出量程范围", QMessageBox.Yes) + return + except Exception: + QMessageBox.warning(self, '警告', "量程范围格式错误", QMessageBox.Yes) + return elif min and not max: - if float(value) < float(min): - reply = QMessageBox.question(self.parent(), - '警告', - "超出量程范围", - QMessageBox.Yes) - return + try: + if float(value) < float(min): + QMessageBox.warning(self, '警告', "超出量程范围", QMessageBox.Yes) + return + except Exception: + QMessageBox.warning(self, '警告', "量程范围格式错误", QMessageBox.Yes) + return elif max and not min: - if float(value) > float(max): - reply = QMessageBox.question(self.parent(), - '警告', - "超出量程范围", - QMessageBox.Yes) - return - proType = Globals.getValue('currentProType') - forceVars = Globals.getValue('forceVars') - forceVars.add(name) - Globals.setValue('forceVars', forceVars) - if proType == '0': - MBTCPMApp.send_task('protocol.Celery.MBTCPMaster.MBTCPMTask.setValue',args=[name, varType, slaveID, address, value, order]) - if proType == '1': - MBTCPSApp.send_task('protocol.Celery.MBTCPSlave.MBTCPSTask.setValue',args=[name, varType, slaveID, address, value]) - elif proType == '2': - MBRTUMApp.send_task('protocol.Celery.MBRTUMaster.MBRTUMTask.setValue',args=[name, varType, slaveID, address, value, order]) - elif proType == '3': - MBRTUSApp.send_task('protocol.Celery.MBRTUSlave.MBRTUSTask.setValue',args=[name, varType, slaveID, address, value]) + try: + if float(value) > float(max): + QMessageBox.warning(self, '警告', "超出量程范围", QMessageBox.Yes) + return + except Exception: + QMessageBox.warning(self, '警告', "量程范围格式错误", QMessageBox.Yes) + return + + # 使用新的 ProtocolManage 进行变量写入 + try: + result = protocolManage.writeVariableValue(name, value) + if result: + forceVars.add(name) + else: + QMessageBox.information(self, '提示', f'变量 {name} 写入失败', QMessageBox.Yes) + except Exception as e: + QMessageBox.warning(self, '错误', f'写入变量 {name} 时发生错误: {str(e)}', QMessageBox.Yes) + + Globals.setValue('forceVars', forceVars) def importVar(self): @@ -277,21 +286,59 @@ class VarWidgets(QtWidgets.QWidget): self.messageWidget.show() def startProtocol(self): + protocolManage = Globals.getValue('protocolManage') + if not protocolManage: + QMessageBox.warning(self, '错误', '协议管理器未初始化', QMessageBox.Yes) + return + if not self._isPopenOpen: - ProjectManage.startProtocol() - self._isPopenOpen = True - self.startProtocolBtn.setText('停止通讯') - self.startProtocolBtn.setIcon(QIcon(':/static/pause.png')) + # 根据 modbusType 启动对应的 Modbus 通讯 + print(self.modbusType) + try: + success = False + if self.modbusType == 'ModbusTcpMaster': # TCP Master + success = protocolManage.startModbusTcpMaster() + elif self.modbusType == 'ModbusTcpSlave': # TCP Slave + success = protocolManage.startModbusTcpSlave() + elif self.modbusType == 'ModbusRtuMaster': # RTU Master + success = protocolManage.startModbusRtuMaster() + elif self.modbusType == 'ModbusRtuSlave': # RTU Slave + success = protocolManage.startModbusRtuSlave() + + if success: + self._isPopenOpen = True + self.startProtocolBtn.setText('停止通讯') + self.startProtocolBtn.setIcon(QIcon(':/Static/pause.png')) + else: + QMessageBox.warning(self, '错误', 'Modbus 通讯启动失败', QMessageBox.Yes) + except Exception as e: + QMessageBox.warning(self, '错误', f'启动通讯时发生错误: {str(e)}', QMessageBox.Yes) else: - ProjectManage.closePopen() - self._isPopenOpen = False - self.startProtocolBtn.setText('开始通讯') - self.startProtocolBtn.setIcon(QIcon(':/static/startProtocol.png')) + # 停止对应的 Modbus 通讯 + try: + success = False + if self.modbusType == 'ModbusTcpMaster': # TCP Master + success = protocolManage.stopModbusTcpMaster() + elif self.modbusType == 'ModbusTcpSlave': # TCP Slave + success = protocolManage.stopModbusTcpSlave() + elif self.modbusType == 'ModbusRtuMaster': # RTU Master + success = protocolManage.stopModbusRtuMaster() + elif self.modbusType == 'ModbusRtuSlave': # RTU Slave + success = protocolManage.stopModbusRtuSlave() + + if success: + self._isPopenOpen = False + self.startProtocolBtn.setText('开始通讯') + self.startProtocolBtn.setIcon(QIcon(':/Static/startProtocol.png')) + else: + QMessageBox.warning(self, '错误', 'Modbus 通讯停止失败', QMessageBox.Yes) + except Exception as e: + QMessageBox.warning(self, '错误', f'停止通讯时发生错误: {str(e)}', QMessageBox.Yes) def initIcon(self): self._isPopenOpen = False self.startProtocolBtn.setText('开始通讯') - self.startProtocolBtn.setIcon(QIcon(':/static/startProtocol.png')) + self.startProtocolBtn.setIcon(QIcon(':/Static/startProtocol.png')) class HartWidgets(VarWidgets): @@ -415,7 +462,7 @@ class TcRtdWidgets(VarWidgets): except Exception: QMessageBox.warning(self, '警告', "量程范围格式错误", QMessageBox.Yes) return - res = Globals.getValue('protocolManage').writeVariableValue(varName, float(value)) + res = Globals.getValue('protocolManage').writeVariableValue(varName, value) if res: forceVars.add(varName) else: diff --git a/bin.py b/bin.py index 5d92ef9..1344d30 100644 --- a/bin.py +++ b/bin.py @@ -4,16 +4,21 @@ from utils import Globals from model.ClientModel.Client import Client from UI.RegisterWidgets.RegisterWidget import RegisterWidget from model.ClientModel.RegisterManage import Register +# import Static.staticQrc_rc # 导入资源文件 import sys +import logging + +# 设置 modbus_tk 日志级别为 ERROR,避免过多的调试信息和格式化错误 +logging.getLogger('modbus_tk').setLevel(logging.ERROR) if __name__ == '__main__': app = QApplication(sys.argv) app.setStyle(QStyleFactory.create('Fusion')) - app.setStyleSheet(CommonHelper.readQss('static/Main.qss') - + CommonHelper.readQss('static/profibus.qss') - + CommonHelper.readQss('static/Area.qss')) + app.setStyleSheet(CommonHelper.readQss('Static/Main.qss') + + CommonHelper.readQss('Static/profibus.qss') + + CommonHelper.readQss('Static/Area.qss')) reg = Register() if not reg.checkLicense(): regWidget = RegisterWidget() diff --git a/model/ClientModel/Client.py b/model/ClientModel/Client.py index 8bea13c..f8330b5 100644 --- a/model/ClientModel/Client.py +++ b/model/ClientModel/Client.py @@ -49,6 +49,9 @@ class Client(object): popenBeat = Globals.getValue('beatPopen') histroyMan = Globals.getValue('historyDBManage') histroyMan.close() + protocolManage = Globals.getValue('protocolManage') + if protocolManage: + protocolManage.shutdown() if popen: popen.kill() if popenBeat: diff --git a/model/ProjectModel/DeviceManage.py b/model/ProjectModel/DeviceManage.py index 8670bdf..99d3ebb 100644 --- a/model/ProjectModel/DeviceManage.py +++ b/model/ProjectModel/DeviceManage.py @@ -3,7 +3,7 @@ import json from utils.DBModels.DeviceModels import DeviceDB import numpy as np from protocol.ModBus.ByteOrder import * -from protocol.ModBus.TCPMaster import * +from protocol.ModBus.tcpmaster_example import TcpMaster import struct import time #从站 "AI" "DI"可强制 diff --git a/protocol/ModBus/DPV1Master.py b/protocol/ModBus/DPV1Master.py index 7765bfa..d20ac18 100644 --- a/protocol/ModBus/DPV1Master.py +++ b/protocol/ModBus/DPV1Master.py @@ -1,7 +1,7 @@ import modbus_tk import modbus_tk.defines as cst -from modbus_tk import modbus_tcp, hooks +from modbus_tk import modbus_tcp import logging import struct import threading @@ -35,8 +35,7 @@ class DPV1Master(): self.master.set_timeout(5.0) except Exception as e: self.master = None - # hooks.install_hook("modbus_tcp.TcpMaster.after_recv", afterRecv) - # hooks.install_hook("modbus_tcp.TcpMaster.after_send", afterSend) + def writeMultipleRegister(self, slaveId, address, outputValue): diff --git a/protocol/ModBus/ModbusManager.py b/protocol/ModBus/ModbusManager.py new file mode 100644 index 0000000..1dc8e15 --- /dev/null +++ b/protocol/ModBus/ModbusManager.py @@ -0,0 +1,703 @@ +from utils.DBModels.ProtocolModel import TCPSetting, RTUSetting +from .tcpmaster_example import TcpMaster +from .rtumaster_example import RTUMaster +from .tcpslave_example import TCPSlave +from .rtuslave_example import RTUSlave +from modbus_tk import hooks +import threading +import time + + +class ModbusManager: + """Modbus 通讯管理器,负责管理所有 Modbus TCP/RTU 主站和从站""" + + def __init__(self): + # Modbus 通讯实例 + self.modbusTcpMaster = None + self.modbusRtuMaster = None + self.modbusTcpSlave = None + self.modbusRtuSlave = None + + # 线程管理 + self.modbusReadThreads = {} # 存储各个Modbus读取线程 + self.modbusStopEvents = {} # 存储各个Modbus停止事件 + self.modbusLocks = {} # 存储各个Modbus的锁 + + # 变量缓存回调 + self.variableValueCache = None + self.cacheLock = None + self.varInfoCache = None + + # 报文记录 + self.messageHistory = { + 'send': [], # 发送的报文 + 'receive': [] # 接收的报文 + } + self.maxMessageCount = 100 # 最大保存报文数量 + self.messageLock = threading.Lock() + + # 设置报文捕获 hooks + self._setupHooks() + + def setVariableCache(self, variableValueCache, cacheLock, varInfoCache): + """设置变量缓存引用""" + self.variableValueCache = variableValueCache + self.cacheLock = cacheLock + self.varInfoCache = varInfoCache + + # ==================== 启动/停止方法 ==================== + + def startModbusTcpMaster(self): + """启动 Modbus TCP 主站""" + try: + if self.modbusTcpMaster is not None: + return True + + # 从数据库获取TCP设置 + tcpSettings = self._getTcpSettings('master') + if not tcpSettings: + return False + + # 创建TCP主站实例 + # print(tcpSettings['host'], tcpSettings['port']) + self.modbusTcpMaster = TcpMaster( + host=tcpSettings['host'], + port=tcpSettings['port'] + ) + + # 启动读取线程 + self._startModbusReadThread('TCP_MASTER', tcpSettings['frequency']) + return True + + except Exception as e: + print(f"启动 Modbus TCP 主站失败: {str(e)}") + return False + + def stopModbusTcpMaster(self): + """停止 Modbus TCP 主站""" + try: + if self.modbusTcpMaster is None: + return True + + # 停止读取线程 + self._stopModbusReadThread('TCP_MASTER') + + # 清理主站实例 + self.modbusTcpMaster = None + return True + + except Exception as e: + return False + + def startModbusRtuMaster(self): + """启动 Modbus RTU 主站""" + try: + if self.modbusRtuMaster is not None: + return True + + # 从数据库获取RTU设置 + rtuSettings = self._getRtuSettings('master') + if not rtuSettings: + return False + + # 创建RTU主站实例 + self.modbusRtuMaster = RTUMaster( + port=rtuSettings['port'], + baudrate=rtuSettings['baudrate'], + bytesize=rtuSettings['byteSize'], + parity=rtuSettings['parity'][0], # 取第一个字符 + stopbits=rtuSettings['stopbits'] + ) + + # 启动读取线程 + self._startModbusReadThread('RTU_MASTER', rtuSettings['frequency']) + return True + + except Exception as e: + return False + + def stopModbusRtuMaster(self): + """停止 Modbus RTU 主站""" + try: + if self.modbusRtuMaster is None: + return True + + # 停止读取线程 + self._stopModbusReadThread('RTU_MASTER') + + # 清理主站实例 + self.modbusRtuMaster = None + return True + + except Exception as e: + return False + + def startModbusTcpSlave(self): + """启动 Modbus TCP 从站""" + try: + if self.modbusTcpSlave is not None: + return True + + # 从数据库获取TCP设置 + tcpSettings = self._getTcpSettings('slave') + if not tcpSettings: + return False + + # 创建TCP从站实例 + self.modbusTcpSlave = TCPSlave( + address=tcpSettings['host'], + port=tcpSettings['port'] + ) + + # 添加默认从站ID + self.modbusTcpSlave.addSlave(1) + + return True + + except Exception as e: + return False + + def stopModbusTcpSlave(self): + """停止 Modbus TCP 从站""" + try: + if self.modbusTcpSlave is None: + return True + + # 停止从站服务器 + if hasattr(self.modbusTcpSlave, 'server'): + self.modbusTcpSlave.server.stop() + + self.modbusTcpSlave = None + return True + + except Exception as e: + return False + + def startModbusRtuSlave(self): + """启动 Modbus RTU 从站""" + try: + if self.modbusRtuSlave is not None: + return True + + # 从数据库获取RTU设置 + rtuSettings = self._getRtuSettings('slave') + if not rtuSettings: + return False + + # 创建RTU从站实例 + self.modbusRtuSlave = RTUSlave( + port=rtuSettings['port'], + baudrate=rtuSettings['baudrate'], + bytesize=rtuSettings['byteSize'], + parity=rtuSettings['parity'][0], # 取第一个字符 + stopbits=rtuSettings['stopbits'] + ) + + # 启动从站服务器 + self.modbusRtuSlave.start() + + # 添加默认从站ID + self.modbusRtuSlave.addSlave(1) + + return True + + except Exception as e: + return False + + def stopModbusRtuSlave(self): + """停止 Modbus RTU 从站""" + try: + if self.modbusRtuSlave is None: + return True + + # 停止从站服务器 + if hasattr(self.modbusRtuSlave, 'server'): + self.modbusRtuSlave.server.stop() + + self.modbusRtuSlave = None + return True + + except Exception as e: + return False + + def stopAllModbus(self): + """停止所有 Modbus 通讯""" + results = [] + results.append(self.stopModbusTcpMaster()) + results.append(self.stopModbusRtuMaster()) + results.append(self.stopModbusTcpSlave()) + results.append(self.stopModbusRtuSlave()) + return all(results) + + def getModbusStatus(self): + """获取所有 Modbus 通讯状态""" + return { + 'tcpMaster': self.modbusTcpMaster is not None, + 'rtuMaster': self.modbusRtuMaster is not None, + 'tcpSlave': self.modbusTcpSlave is not None, + 'rtuSlave': self.modbusRtuSlave is not None + } + + # ==================== 读写方法 ==================== + + def writeModbusTcpMasterValue(self, info, value): + """写入TCP主站变量值""" + try: + if self.modbusTcpMaster is None: + return False + + slaveId = int(info['slaveID']) + address = int(info['address']) + varType = int(info['varType']) + order = info['order'] + + return self._writeModbusValue(self.modbusTcpMaster, slaveId, address, varType, value, order) + + except Exception as e: + print(f"写入TCP主站变量失败: {str(e)}") + return False + + def writeModbusRtuMasterValue(self, info, value): + """写入RTU主站变量值""" + try: + if self.modbusRtuMaster is None: + return False + + slaveId = int(info['slaveID']) + address = int(info['address']) + varType = int(info['varType']) + order = info['order'] + + return self._writeModbusValue(self.modbusRtuMaster, slaveId, address, varType, value, order) + + except Exception as e: + print(f"写入RTU主站变量失败: {str(e)}") + return False + + def writeModbusTcpSlaveValue(self, info, value): + """写入TCP从站变量值""" + try: + if self.modbusTcpSlave is None: + print("Modbus TCP 从站未启动") + return False + + slaveId = int(info['slaveID']) + address = int(info['address']) + varType = int(info['varType']) + order = info['order'] + + return self._writeModbusSlaveValue(self.modbusTcpSlave, slaveId, address, varType, value, order) + + except Exception as e: + print(f"写入TCP从站变量失败: {str(e)}") + return False + + def writeModbusRtuSlaveValue(self, info, value): + """写入RTU从站变量值""" + try: + if self.modbusRtuSlave is None: + print("Modbus RTU 从站未启动") + return False + + slaveId = int(info['slaveID']) + address = int(info['address']) + varType = int(info['varType']) + order = info['order'] + + return self._writeModbusSlaveValue(self.modbusRtuSlave, slaveId, address, varType, value, order) + + except Exception as e: + print(f"写入RTU从站变量失败: {str(e)}") + return False + + def readModbusTcpMasterValue(self, info): + """读取TCP主站变量值""" + try: + if self.modbusTcpMaster is None: + return None + + slaveId = int(info['slaveID']) + address = int(info['address']) + varType = int(info['varType']) + order = info['order'] + + return self._readModbusValue(self.modbusTcpMaster, slaveId, address, varType, order) + + except Exception as e: + print(f"读取TCP主站变量失败: {str(e)}") + return None + + def readModbusRtuMasterValue(self, info): + """读取RTU主站变量值""" + try: + if self.modbusRtuMaster is None: + return None + + slaveId = int(info['slaveID']) + address = int(info['address']) + varType = int(info['varType']) + order = info['order'] + + return self._readModbusValue(self.modbusRtuMaster, slaveId, address, varType, order) + + except Exception as e: + print(f"读取RTU主站变量失败: {str(e)}") + return None + + def readModbusTcpSlaveValue(self, info): + """读取TCP从站变量值""" + try: + if self.modbusTcpSlave is None: + return None + + slaveId = int(info['slaveID']) + address = int(info['address']) + varType = int(info['varType']) + order = info['order'] + + return self._readModbusSlaveValue(self.modbusTcpSlave, slaveId, address, varType, order) + + except Exception as e: + print(f"读取TCP从站变量失败: {str(e)}") + return None + + def readModbusRtuSlaveValue(self, info): + """读取RTU从站变量值""" + try: + if self.modbusRtuSlave is None: + return None + + slaveId = int(info['slaveID']) + address = int(info['address']) + varType = int(info['varType']) + order = info['order'] + + return self._readModbusSlaveValue(self.modbusRtuSlave, slaveId, address, varType, order) + + except Exception as e: + print(f"读取RTU从站变量失败: {str(e)}") + return None + + # ==================== 私有方法 ==================== + + def _getTcpSettings(self, tcpType): + """从数据库获取TCP设置""" + try: + setting = TCPSetting.select().where(TCPSetting.tcpType == tcpType).first() + if setting: + return { + 'host': setting.host, + 'port': setting.port, + 'frequency': float(setting.frequency), + 'offset': setting.offset + } + else: + # 如果没有设置,创建默认设置 + newSetting = TCPSetting() + newSetting.initSetting(tcpType) + return { + 'host': newSetting.host, + 'port': newSetting.port, + 'frequency': float(newSetting.frequency), + 'offset': newSetting.offset + } + except Exception as e: + print(f"获取TCP设置失败: {str(e)}") + return None + + def _getRtuSettings(self, tcpType): + """从数据库获取RTU设置""" + try: + setting = RTUSetting.select().where(RTUSetting.tcpType == tcpType).first() + if setting: + return { + 'port': setting.port, + 'byteSize': setting.byteSize, + 'baudrate': setting.baudrate, + 'stopbits': setting.stopbits, + 'parity': setting.parity, + 'frequency': float(setting.frequency), + 'offset': setting.offset + } + else: + # 如果没有设置,创建默认设置 + newSetting = RTUSetting() + newSetting.initSetting(tcpType) + return { + 'port': newSetting.port, + 'byteSize': newSetting.byteSize, + 'baudrate': newSetting.baudrate, + 'stopbits': newSetting.stopbits, + 'parity': newSetting.parity, + 'frequency': float(newSetting.frequency), + 'offset': newSetting.offset + } + except Exception as e: + print(f"获取RTU设置失败: {str(e)}") + return None + + def _startModbusReadThread(self, protocolType, frequency): + """启动Modbus读取线程""" + if protocolType in self.modbusReadThreads: + return + + stopEvent = threading.Event() + lock = threading.Lock() + + self.modbusStopEvents[protocolType] = stopEvent + self.modbusLocks[protocolType] = lock + + readThread = threading.Thread( + target=self._modbusReadWorker, + args=(protocolType, frequency, stopEvent, lock), + daemon=True + ) + + self.modbusReadThreads[protocolType] = readThread + readThread.start() + + def _stopModbusReadThread(self, protocolType): + """停止Modbus读取线程""" + if protocolType in self.modbusStopEvents: + self.modbusStopEvents[protocolType].set() + + if protocolType in self.modbusReadThreads: + self.modbusReadThreads[protocolType].join(timeout=2) + del self.modbusReadThreads[protocolType] + + if protocolType in self.modbusStopEvents: + del self.modbusStopEvents[protocolType] + + if protocolType in self.modbusLocks: + del self.modbusLocks[protocolType] + + def _modbusReadWorker(self, protocolType, frequency, stopEvent, lock): + """Modbus读取工作线程""" + interval = 1.0 / frequency if frequency > 0 else 1.0 + + while not stopEvent.is_set(): + try: + with lock: + self._readModbusVariables(protocolType) + except Exception as e: + print(f"Modbus读取线程错误 ({protocolType}): {str(e)}") + + time.sleep(interval) + + def _readModbusVariables(self, protocolType): + """读取指定协议类型的所有Modbus变量""" + if protocolType == 'TCP_MASTER': + self._readTcpMasterVariables() + elif protocolType == 'RTU_MASTER': + self._readRtuMasterVariables() + + def _readTcpMasterVariables(self): + """读取TCP主站变量""" + if self.modbusTcpMaster is None or self.varInfoCache is None: + return + + for varName, varInfo in self.varInfoCache.items(): + if varInfo['modelType'] == 'ModbusTcpMasterVar': + try: + info = varInfo['variableData'] + slaveId = int(info['slaveID']) + address = int(info['address']) + varType = int(info['varType']) + order = info['order'] + + value = self._readModbusValue(self.modbusTcpMaster, slaveId, address, varType, order) + if value != 'error' and self.variableValueCache is not None and self.cacheLock is not None: + with self.cacheLock: + self.variableValueCache[varName] = value + + except Exception as e: + print(f"读取TCP主站变量失败 {varName}: {str(e)}") + + def _readRtuMasterVariables(self): + """读取RTU主站变量""" + if self.modbusRtuMaster is None or self.varInfoCache is None: + return + + for varName, varInfo in self.varInfoCache.items(): + if varInfo['modelType'] == 'ModbusRtuMasterVar': + try: + info = varInfo['variableData'] + slaveId = int(info['slaveID']) + address = int(info['address']) + varType = int(info['varType']) + order = info['order'] + + value = self._readModbusValue(self.modbusRtuMaster, slaveId, address, varType, order) + if value != 'error' and self.variableValueCache is not None and self.cacheLock is not None: + with self.cacheLock: + self.variableValueCache[varName] = value + + except Exception as e: + print(f"读取RTU主站变量失败 {varName}: {str(e)}") + + def _readModbusValue(self, master, slaveId, address, varType, order): + """通用Modbus值读取方法""" + try: + # varType: 1=线圈, 2=离散输入, 3=输入寄存器, 4=保持寄存器 + if varType == 0: # 线圈 + return master.readCoils(slaveId, address, 1) + elif varType == 1: # 离散输入 + return master.readInputCoils(slaveId, address, 1) + elif varType == 3: # 输入寄存器 + return master.readInputRegisters(slaveId, address, 1, order) + elif varType == 4: # 保持寄存器 + return master.readHoldingRegisters(slaveId, address, 1, order) + else: + return 'error' + except Exception as e: + print(f"读取Modbus值失败: {str(e)}") + return 'error' + + def _writeModbusValue(self, master, slaveId, address, varType, value, order): + """通用Modbus主站值写入方法""" + try: + # varType: 0=线圈, 1=离散输入, 3=输入寄存器, 4=保持寄存器 + if varType == 0: # 线圈 + result = master.writeSingleCoil(slaveId, address, value) + return result != 'error' + elif varType == 4: # 保持寄存器 + result = master.writeSingleRegister(slaveId, address, value, order) + return result != 'error' + else: + print(f"不支持写入的变量类型: {varType}") + return False + except Exception as e: + print(f"写入Modbus主站值失败: {str(e)}") + return False + + def _writeModbusSlaveValue(self, slave, slaveId, address, varType, value, order): + """通用Modbus从站值写入方法""" + try: + # 根据变量类型确定存储区名称 + blockName = self._getModbusBlockName(varType, address) + if not blockName: + return False + + # 转换地址为存储区内部地 + + slave.setValue(slaveId, blockName, address, value, order) + return True + + except Exception as e: + print(f"写入Modbus从站值失败: {str(e)}") + return False + + def _readModbusSlaveValue(self, slave, slaveId, address, varType, order): + """通用Modbus从站值读取方法""" + try: + # 根据变量类型确定存储区名称 + blockName = self._getModbusBlockName(varType, address) + if not blockName: + return None + + return slave.readValue(slaveId, blockName, address, order) + + except Exception as e: + print(f"读取Modbus从站值失败: {str(e)}") + return None + + def _getModbusBlockName(self, varType, address): + """根据变量类型和地址获取Modbus存储区名称""" + # varType: 0=线圈, 1=离散输入, 3=输入寄存器, 4=保持寄存器 + if varType == 0: # 线圈 (0-9999) + return '0' + elif varType == 1: # 离散输入 (10000-19999) + return '1' + elif varType == 3: # 输入寄存器 (30000-39999) + return '3' + elif varType == 4: # 保持寄存器 (40000-49999) + return '4' + else: + print(f"未知的变量类型: {varType}") + return None + + + + # ==================== 报文记录方法 ==================== + + def _setupHooks(self): + """设置 modbus_tk hooks 来捕获报文""" + try: + def captureRequest(args): + """捕获请求报文数据""" + try: + if args and len(args) > 0: + # args[-1] 通常包含报文数据 + data = args[-1] + if isinstance(data, (list, tuple)): + hexData = ' '.join([f'{b:02X}' for b in data]) + elif isinstance(data, bytes): + hexData = ' '.join([f'{b:02X}' for b in data]) + else: + hexData = str(data) + self._addMessage('receive', f"接收请求: {hexData}") + except Exception as e: + pass + + def captureResponse(args): + """捕获响应报文数据""" + try: + if args and len(args) > 0: + # args[-1] 通常包含报文数据 + data = args[-1] + if isinstance(data, (list, tuple)): + hexData = ' '.join([f'{b:02X}' for b in data]) + elif isinstance(data, bytes): + hexData = ' '.join([f'{b:02X}' for b in data]) + else: + hexData = str(data) + self._addMessage('send', f"发送响应: {hexData}") + except Exception as e: + pass + + # 使用正确的 hook 名称 + hooks.install_hook('modbus.Server.before_handle_request', captureRequest) + hooks.install_hook('modbus.Server.after_handle_request', captureResponse) + + except Exception as e: + print(f"设置 hooks 失败: {e}") + # 如果 hooks 设置失败,不影响正常通讯功能 + pass + + def _addMessage(self, messageType, content): + """添加报文记录""" + try: + with self.messageLock: + timestamp = time.strftime('%H:%M:%S') + message = f"[{timestamp}] {content}" + + if messageType == 'send': + self.messageHistory['send'].append(message) + if len(self.messageHistory['send']) > self.maxMessageCount: + self.messageHistory['send'].pop(0) + elif messageType == 'receive': + self.messageHistory['receive'].append(message) + if len(self.messageHistory['receive']) > self.maxMessageCount: + self.messageHistory['receive'].pop(0) + except Exception as e: + pass + + def getMessages(self): + """获取报文信息""" + try: + with self.messageLock: + return { + 'send': self.messageHistory['send'].copy(), + 'receive': self.messageHistory['receive'].copy() + } + except Exception as e: + return {'send': [], 'receive': []} + + def clearMessages(self): + """清空报文记录""" + try: + with self.messageLock: + self.messageHistory['send'].clear() + self.messageHistory['receive'].clear() + except Exception as e: + pass diff --git a/protocol/ModBus/rtumaster_example.py b/protocol/ModBus/rtumaster_example.py index 49fca8a..21eac4b 100644 --- a/protocol/ModBus/rtumaster_example.py +++ b/protocol/ModBus/rtumaster_example.py @@ -3,7 +3,7 @@ import serial import modbus_tk import modbus_tk.defines as cst -from modbus_tk import modbus_rtu, hooks +from modbus_tk import modbus_rtu import sys from .ByteOrder import * @@ -31,33 +31,93 @@ class RTUMaster(): except Exception as e: pass - hooks.install_hook("modbus_rtu.RtuMaster.after_recv", afterRecv) - hooks.install_hook("modbus_rtu.RtuMaster.before_send", afterSend) - def writeSingleRegister(self, slaveId, address, outputValue, order = 'ABCD'): + + def writeSingleRegister(self, slaveId, address, outputValue, order='ABCD'): try: if '.' not in str(outputValue): + # 整数值,使用单寄存器写入 outputValue = int(outputValue) - # print(outputValue) - self.master.execute(slaveId, cst.WRITE_SINGLE_REGISTER, starting_address = address, output_value = outputValue) + self.master.execute(slaveId, cst.WRITE_SINGLE_REGISTER, starting_address=address, output_value=outputValue) else: + # 浮点数值,需要使用多寄存器写入(因为浮点数占用2个寄存器) outputValue = float(outputValue) - if order == 'ABCD': # 大端模式 + valueByte = None # 初始化变量 + + if order == 'ABCD': # 大端模式 valueByte = floatToABCD(outputValue) - elif order == 'DCBA': # 小端模式 + elif order == 'DCBA': # 小端模式 valueByte = floatToDCBA(outputValue) elif order == 'BADC': valueByte = floatToBADC(outputValue) elif order == 'CDAB': valueByte = floatToCDAB(outputValue) - self.master.execute(slaveId, cst.WRITE_MULTIPLE_REGISTERS, starting_address = address, output_value=valueByte) + else: + # 默认使用 ABCD 字节序 + # print(f"Unknown byte order '{order}', using default ABCD") + valueByte = floatToABCD(outputValue) + + if valueByte is not None: + # 浮点数必须使用 WRITE_MULTIPLE_REGISTERS,因为需要写入2个寄存器 + self.master.execute(slaveId, cst.WRITE_MULTIPLE_REGISTERS, starting_address=address, output_value=valueByte) + else: + # print(f"Failed to convert float value {outputValue} with order {order}") + return 'error' except Exception as e: - print('error') + print(f'writeSingleRegister error: {e}') + return 'error' def writeSingleCoil(self, slaveId, address, outputValue): - - if outputValue in [0, 1]: - self.master.execute(slaveId, cst.WRITE_SINGLE_COIL, address, output_value = outputValue) + try: + outputValue = int(outputValue) + if outputValue in [0, 1]: + self.master.execute(slaveId, cst.WRITE_SINGLE_COIL, address, output_value=outputValue) + except Exception as e: + return 'error' + + def writeMultipleRegisters(self, slaveId, startAddress, outputValues, order='ABCD'): + """写多个寄存器,支持不同数据类型和字节序""" + try: + processedValues = [] + for value in outputValues: + if '.' not in str(value): + # 整数值 + processedValues.append(int(value)) + else: + # 浮点值 - 根据字节序转换为寄存器对 + floatValue = float(value) + if order == 'ABCD': # 大端模式 + valueByte = floatToABCD(floatValue) + elif order == 'DCBA': # 小端模式 + valueByte = floatToDCBA(floatValue) + elif order == 'BADC': + valueByte = floatToBADC(floatValue) + elif order == 'CDAB': + valueByte = floatToCDAB(floatValue) + processedValues.extend(valueByte) + + self.master.execute(slaveId, cst.WRITE_MULTIPLE_REGISTERS, starting_address=startAddress, output_value=processedValues) + except Exception as e: + return 'error' + + def writeMultipleCoils(self, slaveId, startAddress, outputValues): + """写多个线圈,支持布尔值列表或0/1整数列表""" + try: + processedValues = [] + for value in outputValues: + if isinstance(value, bool): + processedValues.append(1 if value else 0) + else: + intValue = int(value) + if intValue in [0, 1]: + processedValues.append(intValue) + else: + # 如果值不是0或1,转换为布尔值 + processedValues.append(1 if intValue else 0) + + self.master.execute(slaveId, cst.WRITE_MULTIPLE_COILS, starting_address=startAddress, output_value=processedValues) + except Exception as e: + return 'error' def readHoldingRegisters(self, slaveId, startAddress, varNums, order = 'ABCD'): @@ -96,9 +156,10 @@ class RTUMaster(): except Exception as e: return 'error' - def readCoils(self, slaveId, startAddress, varNums, order = 'ABCD'): + def readCoils(self, slaveId, startAddress, varNums, order='ABCD'): try: value = self.master.execute(slaveId, cst.READ_COILS, startAddress, varNums) + # 如果只读取一个线圈,返回单个值;否则返回列表 return value[0] except Exception as e: return 'error' @@ -106,6 +167,7 @@ class RTUMaster(): def readInputCoils(self, slaveId, startAddress, varNums): try: value = self.master.execute(slaveId, cst.READ_DISCRETE_INPUTS, startAddress, varNums) + # 如果只读取一个线圈,返回单个值;否则返回列表 return value[0] except Exception as e: return 'error' @@ -133,7 +195,7 @@ class RTUMaster(): # red = master.execute(1, cst.READ_HOLDING_REGISTERS, 0, 9) # 这里可以修改需要读取的功能码 # print(red) # alarm = "正常" -# return list(red), alarm +# return list(red), alarm# # except Exception as exc: # print(str(exc)) diff --git a/protocol/ModBus/rtuslave_example.py b/protocol/ModBus/rtuslave_example.py index 31f5e24..9dced66 100644 --- a/protocol/ModBus/rtuslave_example.py +++ b/protocol/ModBus/rtuslave_example.py @@ -18,8 +18,8 @@ class RTUSlave(): self.server = modbus_rtu.RtuServer(serial.Serial(port = port, baudrate = baudrate, bytesize = bytesize, parity = parity, stopbits = stopbits, xonxoff = xonxoff)) except Exception as e: return - hooks.install_hook('modbus.Server.before_handle_request', afterRecv) - hooks.install_hook("modbus.Server.after_handle_request", afterSend) + # hooks.install_hook('modbus.Server.before_handle_request', afterRecv) + # hooks.install_hook("modbus.Server.after_handle_request", afterSend) # self.server.set_timeout(5.0) # self.server.set_verbose(True) @@ -59,6 +59,8 @@ class RTUSlave(): valueByte = floatToBADC(value) elif order == 'CDAB': valueByte = floatToCDAB(value) + else: + valueByte = floatToABCD(value) slave.set_values(name, address, valueByte) else: @@ -80,6 +82,8 @@ class RTUSlave(): value = BADCToFloat(value) elif order == 'CDAB': value = CDABToFloat(value) + else: + value = ABCDToFloat(value) return value except Exception as e: print(e) diff --git a/protocol/ModBus/tcpmaster_example.py b/protocol/ModBus/tcpmaster_example.py index a0df64b..e7b52cc 100644 --- a/protocol/ModBus/tcpmaster_example.py +++ b/protocol/ModBus/tcpmaster_example.py @@ -1,7 +1,7 @@ import modbus_tk import modbus_tk.defines as cst -from modbus_tk import modbus_tcp, hooks +from modbus_tk import modbus_tcp import logging import struct from .SetMessage import * @@ -9,53 +9,96 @@ from .ByteOrder import * class TcpMaster(): - def __init__(self, host = None, port = None): + def __init__(self, host=None, port=None): try: - self.master = modbus_tcp.TcpMaster(host = host, port = port) + self.master = modbus_tcp.TcpMaster(host=host, port=port) self.master.set_timeout(5.0) except Exception as e: pass - - hooks.install_hook("modbus_tcp.TcpMaster.after_recv", afterRecv) - hooks.install_hook("modbus_tcp.TcpMaster.before_send", afterSend) - def writeSingleRegister(self, slaveId, address, outputValue, order = 'ABCD'): + def writeSingleRegister(self, slaveId, address, outputValue, order='ABCD'): try: if '.' not in str(outputValue): + # 整数值,使用单寄存器写入 outputValue = int(outputValue) - print(outputValue) - self.master.execute(slaveId, cst.WRITE_SINGLE_REGISTER, starting_address = address, output_value = outputValue) + self.master.execute(slaveId, cst.WRITE_SINGLE_REGISTER, starting_address=address, output_value=outputValue) else: + # 浮点数值,需要使用多寄存器写入(因为浮点数占用2个寄存器) outputValue = float(outputValue) - if order == 'ABCD': # 大端模式 + if order == 'ABCD': # 大端模式 valueByte = floatToABCD(outputValue) - elif order == 'DCBA': # 小端模式 + elif order == 'DCBA': # 小端模式 valueByte = floatToDCBA(outputValue) elif order == 'BADC': valueByte = floatToBADC(outputValue) elif order == 'CDAB': valueByte = floatToCDAB(outputValue) - self.master.execute(slaveId, cst.WRITE_MULTIPLE_REGISTERS, starting_address = address, output_value=valueByte) + else: + valueByte = floatToABCD(outputValue) + # 浮点数必须使用 WRITE_MULTIPLE_REGISTERS,因为需要写入2个寄存器 + self.master.execute(slaveId, cst.WRITE_MULTIPLE_REGISTERS, starting_address=address, output_value=valueByte) + + except Exception as e: + return 'error' + + def writeMultipleRegisters(self, slaveId, startAddress, outputValues, order='ABCD'): + """写多个寄存器,支持不同数据类型和字节序""" + try: + processedValues = [] + for value in outputValues: + if '.' not in str(value): + processedValues.append(int(value)) + else: + floatValue = float(value) + if order == 'ABCD': + valueByte = floatToABCD(floatValue) + elif order == 'DCBA': + valueByte = floatToDCBA(floatValue) + elif order == 'BADC': + valueByte = floatToBADC(floatValue) + elif order == 'CDAB': + valueByte = floatToCDAB(floatValue) + processedValues.extend(valueByte) + + self.master.execute(slaveId, cst.WRITE_MULTIPLE_REGISTERS, starting_address=startAddress, output_value=processedValues) except Exception as e: return 'error' + def writeSingleCoil(self, slaveId, address, outputValue): try: outputValue = int(outputValue) if outputValue in [0, 1]: - self.master.execute(slaveId, cst.WRITE_SINGLE_COIL, address, output_value = outputValue) + self.master.execute(slaveId, cst.WRITE_SINGLE_COIL, address, output_value=outputValue) except Exception as e: return 'error' - def readHoldingRegisters(self, slaveId, startAddress, varNums, order = 'ABCD'): - print(order) + def writeMultipleCoils(self, slaveId, startAddress, outputValues): + """写多个线圈,支持布尔值列表或0/1整数列表""" + try: + processedValues = [] + for value in outputValues: + if isinstance(value, bool): + processedValues.append(1 if value else 0) + else: + intValue = int(value) + if intValue in [0, 1]: + processedValues.append(intValue) + else: + processedValues.append(1 if intValue else 0) + + self.master.execute(slaveId, cst.WRITE_MULTIPLE_COILS, starting_address=startAddress, output_value=processedValues) + except Exception as e: + return 'error' + + def readHoldingRegisters(self, slaveId, startAddress, varNums, order='ABCD'): try: 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) - if order == 'ABCD': # 大端模式 + if order == 'ABCD': valueByte = ABCDToFloat(value) - elif order == 'DCBA': # 小端模式 + elif order == 'DCBA': valueByte = DCBAToFloat(value) elif order == 'BADC': valueByte = BADCToFloat(value) @@ -65,15 +108,15 @@ class TcpMaster(): except: return 'error' - def readInputRegisters(self, slaveId, startAddress, varNums, order = 'ABCD'): + def readInputRegisters(self, slaveId, startAddress, varNums, order='ABCD'): try: if order == 'int': valueByte = self.master.execute(slaveId, cst.READ_INPUT_REGISTERS, startAddress, varNums)[0] else: value = self.master.execute(slaveId, cst.READ_INPUT_REGISTERS, startAddress, 2) - if order == 'ABCD': # 大端模式 + if order == 'ABCD': valueByte = ABCDToFloat(value) - elif order == 'DCBA': # 小端模式 + elif order == 'DCBA': valueByte = DCBAToFloat(value) elif order == 'BADC': valueByte = BADCToFloat(value) @@ -83,18 +126,24 @@ class TcpMaster(): except Exception as e: return 'error' - def readCoils(self, slaveId, startAddress, varNums, order = 'ABCD'): + def readCoils(self, slaveId, startAddress, varNums, order='ABCD'): try: value = self.master.execute(slaveId, cst.READ_COILS, startAddress, varNums) - return value[0] + if varNums == 1: + return value[0] if value else 0 + else: + return value except Exception as e: return 'error' + def readInputCoils(self, slaveId, startAddress, varNums): - print(slaveId, startAddress, varNums) try: value = self.master.execute(slaveId, cst.READ_DISCRETE_INPUTS, startAddress, varNums) - return value[0] - except: + if varNums == 1: + return value[0] if value else 0 + else: + return value + except Exception as e: return 'error' diff --git a/protocol/ModBus/tcpslave_example.py b/protocol/ModBus/tcpslave_example.py index 7e6cb2e..9fff73f 100644 --- a/protocol/ModBus/tcpslave_example.py +++ b/protocol/ModBus/tcpslave_example.py @@ -12,13 +12,15 @@ from .SetMessage import * class TCPSlave(): def __init__(self, address = '127.0.0.1', port = 502): try: - self.server = modbus_tcp.TcpServer(address = address, port = port) + self.server = modbus_tcp.TcpServer(address=address, port=port) self.server.start() + print(f"TCP Slave started on {address}:{port}") except Exception as e: - pass + print(f"Failed to start TCP Slave on {address}:{port}: {e}") + raise e - hooks.install_hook('modbus.Server.before_handle_request', afterRecv) - hooks.install_hook("modbus.Server.after_handle_request", afterSend) + # hooks.install_hook('modbus.Server.before_handle_request', afterRecv) + # hooks.install_hook("modbus.Server.after_handle_request", afterSend) # 创建从站 # 添加存储区 @@ -29,36 +31,52 @@ class TCPSlave(): # ANALOG_INPUTS = 4 def addSlave(self, slaveId): try: - self.server.add_slave(slaveId) slave = self.server.get_slave(slaveId) - slave.add_block('0', cst.COILS, 0, 9999) # 从0开始创建10000个地址 - slave.add_block('1', cst.DISCRETE_INPUTS, 10000,19999) - slave.add_block('3', cst.ANALOG_INPUTS, 30000, 39999) - slave.add_block('4', cst.HOLDING_REGISTERS, 40000, 49999) + + # 添加存储区 - 使用正确的地址范围 + slave.add_block('0', cst.COILS, 0, 9999) # 线圈 0-9999 + slave.add_block('1', cst.DISCRETE_INPUTS, 10000, 19999) # 离散输入 10000-19999 + slave.add_block('3', cst.ANALOG_INPUTS, 30000, 39999) # 输入寄存器 30000-39999 + slave.add_block('4', cst.HOLDING_REGISTERS, 40000, 49999) # 保持寄存器 40000-49999 + + print(f"Added slave {slaveId} with storage blocks") + return True + except Exception as e: - pass + print(f"Failed to add slave {slaveId}: {e}") + return False - def setValue(self, slaveId, name, address, value, order = 'ABCD'): + def setValue(self, slaveId, name, address, value, order='ABCD'): try: slave = self.server.get_slave(slaveId) + # print(value) if '.' in str(value): - if order == 'ABCD': # 大端模式 - valueByte = floatToABCD(value) - elif order == 'DCBA': # 小端模式 - valueByte = floatToDCBA(value) + # 浮点数处理 + floatValue = float(value) + if order == 'ABCD': # 大端模式 + valueByte = floatToABCD(floatValue) + elif order == 'DCBA': # 小端模式 + valueByte = floatToDCBA(floatValue) elif order == 'BADC': - valueByte = floatToBADC(value) + valueByte = floatToBADC(floatValue) elif order == 'CDAB': - valueByte = floatToCDAB(value) + valueByte = floatToCDAB(floatValue) + else: + valueByte = floatToABCD(floatValue) slave.set_values(name, address, valueByte) - else: - slave.set_values(name, address, value) + # 整数处理 + intValue = int(value) + slave.set_values(name, address, intValue) + return True + except Exception as e: - pass + print(f"TCP Slave setValue error: {e}") + print(f"Error details - slaveId: {slaveId}, name: {name}, address: {address}, value: {value}") + return False def readValue(self, slaveId, name, address, order = 'int'): try: diff --git a/protocol/ProtocolManage.py b/protocol/ProtocolManage.py index da94164..d16470d 100644 --- a/protocol/ProtocolManage.py +++ b/protocol/ProtocolManage.py @@ -7,6 +7,7 @@ from protocol.TCP.TCPVarManage import * from protocol.TCP.TemToMv import temToMv from protocol.RPC.RpcClient import RpcClient from protocol.RPC.RpcServer import RpcServer +from protocol.ModBus.ModbusManager import ModbusManager from utils import Globals import threading import time @@ -31,13 +32,20 @@ class ProtocolManage(object): self.varInfoCache = {} # 保持驼峰命名 self.historyDBManage = Globals.getValue('historyDBManage') self.variableValueCache = {} # {varName: value} + + # Modbus 管理器 + self.modbusManager = ModbusManager() + self.modbusManager.setVariableCache(self.variableValueCache, None, self.varInfoCache) + self.refreshVarCache() self.cacheLock = threading.Lock() + # 设置 Modbus 管理器的缓存锁 + self.modbusManager.setVariableCache(self.variableValueCache, self.cacheLock, self.varInfoCache) + self.readThreadStop = threading.Event() self.readThread = threading.Thread(target=self._backgroundReadAllVariables, daemon=True) self.readThread.start() - def clearVarCache(self): """清空变量信息缓存""" self.varInfoCache.clear() @@ -68,7 +76,6 @@ class ProtocolManage(object): :return: 包含变量信息和模型类型的字典,如果未找到返回None """ if variableName in self.varInfoCache: - # print(111) return self.varInfoCache[variableName] for modelClass in self.MODEL_CLASSES: varInstance = modelClass.getByName(variableName) @@ -101,25 +108,19 @@ class ProtocolManage(object): if self.RpcServer: return self.RpcServer = RpcServer(rabbitmqHost) - def addClient(self, clientName): if self.RpcServer: - self.RpcServer.addClient(clientName = clientName) + self.RpcServer.addClient(clientName=clientName) else: return - # print(11111111111) - # time.sleep(3) - # print(self.RpcServer.broadcastRead()) - # self.RpcServer.broadcastRead() def closeServer(self): if self.RpcServer: self.RpcServer.close() self.RpcServer = None - # @classmethod - def writeVariableValue(self, variableName, value, trigger = None, timeoutMS = 2000): + def writeVariableValue(self, variableName, value, trigger=None, timeoutMS=2000): """ 根据变量名写入变量值,根据变量类型进行不同处理(不保存到数据库) @@ -130,40 +131,42 @@ class ProtocolManage(object): varInfo = self.lookupVariable(variableName) if not varInfo: if self.RpcServer: - existsVar, clientNames = self.RpcServer.existsVar(variableName) - if existsVar: - value = self.RpcServer.writeVar(variableName, value) - return True - else: - return False + existsVar, clientNames = self.RpcServer.existsVar(variableName) + if existsVar: + value = self.RpcServer.writeVar(variableName, value) + return True + else: + return False return False modelType = varInfo['modelType'] info = varInfo['variableData'] - - # print(info) try: - # 拆分为四个独立的Modbus协议条件判断 + # 拆分为四个独立的Modbus协议条件判断 if modelType == 'ModbusTcpMasterVar': - # 仅设置值,不保存到数据库 - pass + res = self.modbusManager.writeModbusTcpMasterValue(info, value) + # print(res) + if res == 'error': + return elif modelType == 'ModbusTcpSlaveVar': - # 仅设置值,不保存到数据库 - pass + res = self.modbusManager.writeModbusTcpSlaveValue(info, value) + if res == 'error': + return elif modelType == 'ModbusRtuMasterVar': - # 仅设置值,不保存到数据库 - pass + res = self.modbusManager.writeModbusRtuMasterValue(info, value) + if res == 'error': + return elif modelType == 'ModbusRtuSlaveVar': - # 仅设置值,不保存到数据库 - pass + res = self.modbusManager.writeModbusRtuSlaveValue(info, value) + if res == 'error': + return # HART协议变量处理 elif modelType == 'HartVar': - # 仅设置值,不保存到数据库 pass # 温度/RTD变量处理 @@ -173,21 +176,18 @@ class ProtocolManage(object): compensationVar = float(info['compensationVar']) varModel = info['varModel'] model = self.getModelType(varModel) if self.getModelType(varModel) else localModel - # print(value + compensationVar) if model == localModel: if varType == 'PT100': self.writeRTD[channel] = value else: self.writeTC[channel] = value if varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A', 'PT100'] and model != SimModel: - value = temToMv(varType, value + compensationVar) # 直接补偿温度 补偿mv调整到括号外 + value = temToMv(varType, value + compensationVar) if trigger and varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']: trigger = TCTrigger if trigger and varType in ['PT100']: trigger = RTDTrigger self.tcpVarManager.writeValue(varType, channel, value, trigger=trigger, model=model, timeoutMS=timeoutMS) - - # 模拟量变量处理 elif modelType == 'AnalogVar': @@ -195,17 +195,14 @@ class ProtocolManage(object): varType = info['varType'] varModel = info['varModel'] model = self.getModelType(varModel) if self.getModelType(varModel) else localModel - if info['varType'] in ['AI','AO']: + if info['varType'] in ['AI', 'AO']: value = self.getRealAO(value, info['max'], info['min']) trigger = FPGATrigger if trigger else trigger self.tcpVarManager.writeValue(varType, channel, value, trigger=trigger, model=model, timeoutMS=timeoutMS) - - # print(1) + # HART模拟变量处理 elif modelType == 'HartSimulateVar': - # 仅设置值,不保存到数据库 pass - if self.RpcClient: self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType']) @@ -224,12 +221,9 @@ class ProtocolManage(object): time.sleep(interval) def getAllVariableNames(self): - # 直接从缓存获取所有变量名,降低与数据库模型的耦合 return list(self.varInfoCache.keys()) def _readVariableValueOriginal(self, variableName): - # 完全保留原有读取逻辑 - # print(12) varInfo = self.lookupVariable(variableName) value = None if not varInfo: @@ -237,7 +231,6 @@ class ProtocolManage(object): existsVar, clientNames = self.RpcServer.existsVar(variableName) if existsVar: value = float(self.RpcServer.getVarValue(clientNames[0], variableName)['value']) - # return value else: return None return None @@ -246,13 +239,13 @@ class ProtocolManage(object): try: # 拆分为独立的协议条件判断 if modelType == 'ModbusTcpMasterVar': - pass + value = self.modbusManager.readModbusTcpMasterValue(info) elif modelType == 'ModbusTcpSlaveVar': - pass + value = self.modbusManager.readModbusTcpSlaveValue(info) elif modelType == 'ModbusRtuMasterVar': - pass + value = self.modbusManager.readModbusRtuMasterValue(info) elif modelType == 'ModbusRtuSlaveVar': - pass + value = self.modbusManager.readModbusRtuSlaveValue(info) elif modelType == 'HartVar': pass elif modelType == 'TcRtdVar': @@ -265,34 +258,25 @@ class ProtocolManage(object): value = self.tcpVarManager.simRTDData[channel] elif varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']: value = self.tcpVarManager.simTCData[channel] - # if self.RpcClient: - # self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType']) - # return value else: if varType == 'PT100': value = self.writeRTD[channel] elif varType in ['R', 'S', 'B', 'J', 'T', 'E', 'K', 'N', 'C', 'A']: value = self.writeTC[channel] - # if self.RpcClient: - # self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType']) - # return value elif modelType == 'AnalogVar': channel = int(info['channelNumber']) - 1 varType = info['varType'] varModel = info['varModel'] model = self.getModelType(varModel) value = self.tcpVarManager.readValue(varType, channel, model=model) - if varType in ['AI','AO']: + if varType in ['AI', 'AO']: value = self.getRealAI(value, info['max'], info['min']) - - # return value elif modelType == 'HartSimulateVar': pass - if value is not None: + if value is not None and value != 'error': if self.RpcClient: self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType']) self.historyDBManage.writeVarValue(variableName, value) - # print('sucess') return value else: return None @@ -301,57 +285,44 @@ class ProtocolManage(object): return None def readVariableValue(self, variableName): - # 优先从缓存读取,未命中则走原有逻辑 with self.cacheLock: if variableName in self.variableValueCache: return self.variableValueCache[variableName] return self._readVariableValueOriginal(variableName) - - # def sendTrigger(self, variableName, value, timeoutMS): - # self.writeVariableValue(variableName, value, trigger=True, timeoutMS = timeoutMS) - def recvDeltaT(self): return self.tcpVarManager.recvDeltaT() def shutdown(self): self.tcpVarManager.shutdown() + # 关闭所有Modbus通讯 + self.modbusManager.stopAllModbus() # 关闭后台读取线程 if hasattr(self, 'readThreadStop') and hasattr(self, 'readThread'): self.readThreadStop.set() self.readThread.join(timeout=1) - - - def getRealAO(self, value,highValue, lowValue): + def getRealAO(self, value, highValue, lowValue): if highValue: lowValue = float(lowValue) highValue = float(highValue) - return (16 * (value - lowValue) + 4 * (highValue-lowValue))/(1000 * (highValue - lowValue)) + return (16 * (value - lowValue) + 4 * (highValue - lowValue)) / (1000 * (highValue - lowValue)) else: - return value/1000 + return value / 1000 def getRealAI(self, mA, highValue, lowValue): - """ - 将毫安值转换为实际工程值(getRealAO的反向计算) - - :param mA: 毫安值 - :param highValue: 工程值上限 - :param lowValue: 工程值下限 - :return: 实际工程值 - """ + """将毫安值转换为实际工程值(getRealAO的反向计算)""" try: if highValue: lowValue = float(lowValue) highValue = float(highValue) - # 反向计算: value = (1000 * mA * (highValue - lowValue) - 4*(highValue - lowValue)) / 16 + lowValue return (1000 * mA * (highValue - lowValue) - 4 * (highValue - lowValue)) / 16.0 + lowValue else: return mA * 1000 except Exception as e: print(f"工程值转换失败: {str(e)}") - return 0.0 # 默认返回0避免中断流程 - + return 0.0 + def getModelType(self, varModel): if varModel == '本地值': return localModel @@ -364,4 +335,52 @@ class ProtocolManage(object): if self.RpcServer: self.RpcServer.removeClient(clientName) - + # ==================== Modbus 通讯管理方法(委托给 ModbusManager) ==================== + + def startModbusTcpMaster(self): + """启动 Modbus TCP 主站""" + return self.modbusManager.startModbusTcpMaster() + + def stopModbusTcpMaster(self): + """停止 Modbus TCP 主站""" + return self.modbusManager.stopModbusTcpMaster() + + def startModbusRtuMaster(self): + """启动 Modbus RTU 主站""" + return self.modbusManager.startModbusRtuMaster() + + def stopModbusRtuMaster(self): + """停止 Modbus RTU 主站""" + return self.modbusManager.stopModbusRtuMaster() + + def startModbusTcpSlave(self): + """启动 Modbus TCP 从站""" + return self.modbusManager.startModbusTcpSlave() + + def stopModbusTcpSlave(self): + """停止 Modbus TCP 从站""" + return self.modbusManager.stopModbusTcpSlave() + + def startModbusRtuSlave(self): + """启动 Modbus RTU 从站""" + return self.modbusManager.startModbusRtuSlave() + + def stopModbusRtuSlave(self): + """停止 Modbus RTU 从站""" + return self.modbusManager.stopModbusRtuSlave() + + def stopAllModbus(self): + """停止所有 Modbus 通讯""" + return self.modbusManager.stopAllModbus() + + def getModbusStatus(self): + """获取所有 Modbus 通讯状态""" + return self.modbusManager.getModbusStatus() + + def getModbusMessages(self): + """获取 Modbus 报文信息""" + return self.modbusManager.getMessages() + + def clearModbusMessages(self): + """清空 Modbus 报文记录""" + self.modbusManager.clearMessages() \ No newline at end of file diff --git a/utils/DBModels/InitParameterDB.py b/utils/DBModels/InitParameterDB.py index 1bdc777..5fa2475 100644 --- a/utils/DBModels/InitParameterDB.py +++ b/utils/DBModels/InitParameterDB.py @@ -7,9 +7,9 @@ class InitParameterDB(): self.writeUnitParameter() def writeParameter(self) -> None: - blockNames = pd.ExcelFile('static/PA块信息表.xlsx').sheet_names + blockNames = pd.ExcelFile('Static/PA块信息表.xlsx').sheet_names for blockName in blockNames: - parameters = pd.read_excel('static/PA块信息表.xlsx', sheet_name = str(blockName)) + parameters = pd.read_excel('Static/PA块信息表.xlsx', sheet_name = str(blockName)) for index, row in parameters.iterrows(): parameter = row.values @@ -19,7 +19,7 @@ class InitParameterDB(): saveType = parameter[4], dataSize = parameter[5], accessType = parameter[6], transferType = parameter[7], description = parameter[8]) def writeUnitParameter(self): - parameters = pd.read_excel('static/UnitTable.xlsx', sheet_name = 'UnitTable') + parameters = pd.read_excel('Static/UnitTable.xlsx', sheet_name = 'UnitTable') for index, row in parameters.iterrows(): parameter = row.values clsblockName = UnitTable()