0708更新

main
zcwBit 4 months ago
parent 3b075df941
commit 1eb5e3f2d3

@ -4,7 +4,9 @@ import pandas as pd
from PyQt5 import QtWidgets
from PyQt5.QtCore import Qt
from PyQt5.Qt import *
from PyQt5.QtWidgets import QApplication, QMainWindow, QStackedWidget, QMessageBox, QStackedWidget, QWidget, QTabWidget, QFileDialog
from PyQt5.QtWidgets import QApplication, QMainWindow, QStackedWidget, QMessageBox, QStackedWidget, QWidget, QTabWidget, QFileDialog, QPushButton
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QSize
from ctypes import POINTER, cast
from ctypes.wintypes import MSG
from win32 import win32api, win32gui
@ -15,6 +17,7 @@ from UI.Main.MainLeft import MainLeft
from UI.Main.MainTop import MainTop
from ..ProjectManages.ProjectWidget import ProjectWidgets
from ..VarManages.VarWidget import VarWidgets, HartWidgets, TcRtdWidgets, AnalogWidgets, HartSimulateWidgets
from UI.VarManages.VarTable import RpcVarTableView
from ..UserManage.UserWidget import UserWidgets
from ..TrendManage.TrendWidget import TrendWidgets
from ..Setting.Setting import SettingWidget
@ -97,6 +100,7 @@ class MainWindow(QMainWindow):
self.tcrtdWidget = TcRtdWidgets()
self.analogWidget = AnalogWidgets()
self.hartsimulateWidget = HartSimulateWidgets()
self.rpcVarTableWidget = RpcVarTableView()
self.userWidget.setObjectName('userWidget')
self.projectWidget.setObjectName('projectWidget')
@ -122,6 +126,7 @@ class MainWindow(QMainWindow):
self.varManageTabWidget.addTab(self.hartWidget,'HART读取')
self.varManageTabWidget.addTab(self.hartsimulateWidget,'HART模拟')
self.varManageTabWidget.addTab(self.profibusWidget,'PROFIBUS')
self.varManageTabWidget.addTab(self.rpcVarTableWidget, '远程通讯')
#添加导入变量按钮
self.importVarButton = QPushButton(QIcon(':/static/import.png'), '导入变量')

@ -58,17 +58,29 @@ class RemoteConnectSettingWidget(QWidget):
self.mainLayout.addStretch() # 底部弹簧(将内容向上推)
def switchMode(self, mode):
"""切换主站/从站模式"""
"""切换主站/从站模式,自动关闭另一端"""
# 关闭当前Widget对应的服务/客户端
if self.currentWidget == self.deviceMasterWidget:
# 主站切换到从站,关闭服务端
from utils import Globals
protocolManage = Globals.getValue('protocolManage')
if protocolManage and hasattr(protocolManage, 'closeServer'):
protocolManage.closeServer()
else:
# 从站切换到主站,关闭客户端
from utils import Globals
protocolManage = Globals.getValue('protocolManage')
if protocolManage and hasattr(protocolManage, 'closeClent'):
protocolManage.closeClent()
# 移除当前Widget
self.contentLayout.removeWidget(self.currentWidget)
self.currentWidget.hide()
# 根据选择显示对应的Widget
if mode == "主站模式":
self.currentWidget = self.deviceMasterWidget
else:
self.currentWidget = self.deviceSlaveWidget
# 重新添加Widget保持居中
self.contentLayout.insertWidget(1, self.currentWidget) # 插入到两个弹簧中间
self.currentWidget.show()

@ -3,7 +3,7 @@ import socket
import threading
from PyQt5.QtWidgets import QMainWindow, QVBoxLayout, QPushButton, \
QListWidget, QLabel, QWidget, QMessageBox, QTextEdit
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QObject, QTimer
from PyQt5.QtNetwork import QNetworkInterface,QAbstractSocket
from datetime import datetime
@ -24,19 +24,26 @@ class TcpClient(object):
udpSock.sendto(b"DISCOVERY_REQUEST", (self.broadcastAddr, self.udpPort))
servers = []
seen = set()
local_ips = self.getLocalIps()
try:
while True:
data, addr = udpSock.recvfrom(1024)
if data.startswith(b"DISCOVERY_RESPONSE"):
# 修正点正确解析服务端IP和TCP端口
_, hostname, tcpPort = data.decode('utf-8').split(':')
serverIp = addr # 提取IP字符串
serverIp = addr[0]
# 跳过本机IP
if serverIp in local_ips:
continue
key = (serverIp, int(tcpPort))
if key in seen:
continue
seen.add(key)
servers.append({
'ip': serverIp, # 存储为字符串
'port': int(tcpPort), # 存储为整数
'ip': (serverIp,),
'port': int(tcpPort),
'hostname': hostname
})
# self.connectToServer(serverIp, tcpPort)
print(data.decode('utf-8'))
except socket.timeout:
pass
@ -49,14 +56,15 @@ class TcpClient(object):
try:
# 显式转换类型确保安全
client.connect((str(ip), int(tcpPort)))
print(f"[+] 已连接到 {ip}:{tcpPort}")
# print(f"[+] 已连接到 {ip}:{tcpPort}")
Globals.getValue('protocolManage').setServerMode()
clientName = Globals.getValue('protocolManage').RpcServer.getNextClientName()
client.send(clientName.encode('utf-8'))
response = client.recv(1024)
print(f"服务端响应: {response.decode('utf-8')}")
# print(f"服务端响应: {response.decode('utf-8')}")
resClientName = response.decode('utf-8')
Globals.getValue('protocolManage').addClient(clientName = resClientName)
QMessageBox.information(None, '连接成功', f'已成功连接到从站 {ip}:{tcpPort}')
return True
except Exception as e:
print(f"连接失败: {e}")
@ -64,6 +72,17 @@ class TcpClient(object):
finally:
client.close()
def getLocalIps(self):
"""获取本机所有IPv4地址"""
ips = set()
for interface in QNetworkInterface.allInterfaces():
if interface.flags() & QNetworkInterface.IsUp:
for entry in interface.addressEntries():
ip = entry.ip()
if ip.protocol() == QAbstractSocket.IPv4Protocol and not ip.isLoopback():
ips.add(ip.toString())
return ips
class DiscoveryThread(QThread):
"""用于在后台执行设备发现的线程"""
@ -83,7 +102,15 @@ class DeviceMasterWidget(QMainWindow):
super().__init__()
self.tcpClient = TcpClient()
self.servers = []
self.discoveryThread = None
self.statusLabel = None
self.infoLabel = None
self.disconnectList = None
self.initUI()
# 定时器启动放到控件初始化后
self.statusTimer = QTimer(self)
self.statusTimer.timeout.connect(self.updateStatusInfo)
self.statusTimer.start(1000)
def initUI(self):
self.setWindowTitle('局域网设备发现')
@ -94,6 +121,17 @@ class DeviceMasterWidget(QMainWindow):
self.setCentralWidget(centralWidget)
layout = QVBoxLayout()
self.statusLabel = QLabel('服务器模式: 无')
layout.addWidget(self.statusLabel)
self.infoLabel = QLabel('连接信息: 无')
layout.addWidget(self.infoLabel)
# 新增:断开客户端区
self.disconnectList = QListWidget()
self.disconnectList.setFixedHeight(60)
layout.addWidget(QLabel('已连接客户端(点击可断开):'))
layout.addWidget(self.disconnectList)
self.disconnectList.itemClicked.connect(self.disconnectClient)
self.ipAddresslabel = QLabel(self)
self.ipAddresslabel.setObjectName("setlabel")
self.ipAddresslabel.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter)
@ -159,10 +197,22 @@ class DeviceMasterWidget(QMainWindow):
if selectedIndex == -1:
QMessageBox.warning(self, '警告', '请先选择一个设备')
return
selectedServer = self.servers[selectedIndex]
# print(selectedServer['ip'][0], selectedServer['port'])
self.tcpClient.connectToServer(selectedServer['ip'][0], selectedServer['port'])
from utils import Globals
protocolManage = Globals.getValue('protocolManage')
already_connected = False
ip = selectedServer['ip'][0] if isinstance(selectedServer['ip'], (list, tuple)) else selectedServer['ip']
if protocolManage and hasattr(protocolManage, 'RpcServer') and protocolManage.RpcServer:
clients = protocolManage.RpcServer.getClientNames()
ipMap = protocolManage.RpcServer.getClientIpMap() if hasattr(protocolManage.RpcServer, 'getClientIpMap') else {}
for c in clients:
if ipMap.get(c, '') == ip:
already_connected = True
break
if already_connected:
QMessageBox.information(self, '提示', f'客户端 {ip} 已连接,无需重复连接')
return
self.tcpClient.connectToServer(ip, selectedServer['port'])
def getLocalIp(self):
for interface in QNetworkInterface.allInterfaces():
@ -174,6 +224,62 @@ class DeviceMasterWidget(QMainWindow):
return ip.toString()
return "192.168.1.1" # 默认回退值
def cleanup(self):
# 释放后台线程
if self.discoveryThread and self.discoveryThread.isRunning():
self.discoveryThread.terminate()
self.discoveryThread.wait()
def closeEvent(self, event):
self.cleanup()
event.accept()
def __del__(self):
self.cleanup()
def updateStatusInfo(self):
protocolManage = Globals.getValue('protocolManage')
mode = ''
info = ''
if protocolManage:
if hasattr(protocolManage, 'RpcServer') and protocolManage.RpcServer:
mode = '服务端模式'
try:
clients = protocolManage.RpcServer.getClientNames()
clientIpMap = protocolManage.RpcServer.getClientIpMap() if hasattr(protocolManage.RpcServer, 'getClientIpMap') else {}
info = '已连接客户端: ' + (', '.join([f"{c} ({clientIpMap.get(c, '')})" for c in clients]) if clients else '')
self.disconnectList.clear()
for c in clients:
ip = clientIpMap.get(c, '')
display = f"{c} ({ip})" if ip else c
self.disconnectList.addItem(display)
except:
info = '已连接客户端: 无'
self.disconnectList.clear()
elif hasattr(protocolManage, 'RpcClient') and protocolManage.RpcClient:
mode = '客户端模式'
try:
clientName = protocolManage.RpcClient.clientName
serverInfo = protocolManage.RpcClient.rabbitHost
info = f'客户端名: {clientName} 服务器: {serverInfo}'
self.disconnectList.clear()
except:
info = '客户端信息获取失败'
self.disconnectList.clear()
self.statusLabel.setText(f'服务器模式: {mode}')
self.infoLabel.setText(f'连接信息: {info}')
def disconnectClient(self, item):
# 支持“客户端名 (IP)”格式
text = item.text()
clientName = text.split(' ')[0]
from utils import Globals
protocolManage = Globals.getValue('protocolManage')
if protocolManage and hasattr(protocolManage, 'disconnectClient'):
protocolManage.disconnectClient(clientName)
QMessageBox.information(self, '断开连接', f'已断开客户端 {clientName}')
# 立即刷新列表
self.updateStatusInfo()
class TcpServer(QObject):
@ -229,11 +335,12 @@ class TcpServer(QObject):
data = clientSock.recv(1024)
if not data:
break
# print(addr, data)
Globals.getValue('protocolManage').closeServer()
Globals.getValue('protocolManage').setClentMode(data.decode('utf-8'), rabbitmqHost = addr[0])
# print(data.decode('utf-8'))
self.log(f"收到消息: {data.decode('utf-8')}")
clientSock.send(data) # 返回确认
# 新增:弹窗提示本机已被连接
# QMessageBox.information(None, '连接提示', f'本机已被 {addr[0]} 连接')
except Exception as e:
self.log(f"客户端异常断开: {e}")
finally:
@ -273,13 +380,18 @@ class TcpServer(QObject):
def stopTcpServer(self):
if self.tcpRunning:
self.tcpRunning = False
# 创建一个临时连接来解除accept阻塞
try:
tempSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tempSocket.connect(('127.0.0.1', self.tcpPort))
tempSocket.close()
except:
pass
# 唤醒accept阻塞安全关闭线程
if self.serverSocket:
try:
self.serverSocket.shutdown(socket.SHUT_RDWR)
except:
pass
try:
self.serverSocket.close()
except:
pass
if self.tcpThread and self.tcpThread.is_alive():
self.tcpThread.join(timeout=1)
def startUdpServer(self):
if not self.udpRunning:
@ -290,13 +402,18 @@ class TcpServer(QObject):
def stopUdpServer(self):
if self.udpRunning:
self.udpRunning = False
# 发送一个空数据包来解除recvfrom阻塞
try:
tempSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
tempSocket.sendto(b'', ('127.0.0.1', self.udpPort))
tempSocket.close()
except:
pass
# 唤醒recvfrom阻塞安全关闭线程
if self.udpSocket:
try:
self.udpSocket.shutdown(socket.SHUT_RDWR)
except:
pass
try:
self.udpSocket.close()
except:
pass
if self.udpThread and self.udpThread.is_alive():
self.udpThread.join(timeout=1)
class DeviceSlaveWidget(QMainWindow):
def __init__(self):
@ -306,12 +423,22 @@ class DeviceSlaveWidget(QMainWindow):
self.server = TcpServer()
self.server.updateSignal.connect(self.updateLog)
self.statusLabel = None
self.infoLabel = None
self.initUI()
# 定时器启动放到控件初始化后
self.statusTimer = QTimer(self)
self.statusTimer.timeout.connect(self.updateStatusInfo)
self.statusTimer.start(1000)
def initUI(self):
# 主布局
mainLayout = QVBoxLayout()
self.statusLabel = QLabel('服务器模式: 无')
mainLayout.addWidget(self.statusLabel)
self.infoLabel = QLabel('连接信息: 无')
mainLayout.addWidget(self.infoLabel)
# 控制按钮
# self.tcpButton = QPushButton("启动 TCP 服务器")
@ -338,10 +465,10 @@ class DeviceSlaveWidget(QMainWindow):
def toggleTcpServer(self):
if self.server.tcpRunning:
self.server.stopTcpServer()
print("关闭 TCP 服务器")
# print("关闭 TCP 服务器")
else:
self.server.startTcpServer()
print("启动 TCP 服务器")
# print("启动 TCP 服务器")
def toggleUdpServer(self):
if self.server.udpRunning:
@ -355,10 +482,39 @@ class DeviceSlaveWidget(QMainWindow):
def updateLog(self, message):
self.logDisplay.append(message)
def closeEvent(self, event):
# 关闭窗口时停止所有服务
def cleanup(self):
if self.server.tcpRunning:
self.server.stopTcpServer()
if self.server.udpRunning:
self.server.stopUdpServer()
def closeEvent(self, event):
self.cleanup()
event.accept()
def __del__(self):
# print(111111111111111111)
self.cleanup()
def updateStatusInfo(self):
protocolManage = Globals.getValue('protocolManage')
mode = ''
info = ''
if protocolManage:
if hasattr(protocolManage, 'RpcClient') and protocolManage.RpcClient:
mode = '客户端模式'
try:
clientName = protocolManage.RpcClient.clientName
serverInfo = protocolManage.RpcClient.rabbitHost
info = f'客户端名: {clientName} 服务器: {serverInfo}'
except:
info = '客户端信息获取失败'
else:
# 没有RpcClient显示无
mode = ''
info = ''
else:
mode = ''
info = ''
self.statusLabel.setText(f'服务器模式: {mode}')
self.infoLabel.setText(f'连接信息: {info}')

@ -0,0 +1,135 @@
from PyQt5.QtCore import Qt, QAbstractTableModel, QVariant, QTimer, QModelIndex, QEvent, QSize
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QMessageBox, QPushButton, QItemDelegate, QStyleOptionButton, QStyle, QWidget, QInputDialog, QHBoxLayout
from utils import Globals
import qtawesome
class RpcVarModel(QAbstractTableModel):
def __init__(self, header, parent=None):
# 新顺序:当前值、强制值、变量名、类型、下限、上限、客户端、强制按钮
self.header = ['当前值', '强制值', '变量名', '类型', '下限', '上限', '客户端', '操作']
super().__init__(parent)
self.datas = []
self.valueCache = {}
self.inputValues = {} # 行号->输入值
self.refreshTimer = QTimer()
self.refreshTimer.timeout.connect(self.refreshData)
self.refreshTimer.start(500) # 每秒刷新一次
def refreshData(self):
"""刷新所有远程变量数据,并按类型排序"""
protocolManage = Globals.getValue('protocolManage')
if protocolManage and protocolManage.RpcServer:
allVars = protocolManage.RpcServer.broadcastRead()
temp = []
for client in allVars:
clientName = client.get('client', '')
variables = client.get('variables', {})
for varName, varInfo in variables.items():
row = [
varInfo.get('value', ''), # 当前值
self.inputValues.get(len(temp), ''),# 强制值(输入缓存)
varName, # 变量名
varInfo.get('type', ''), # 类型
varInfo.get('min', ''), # 下限
varInfo.get('max', ''), # 上限
clientName, # 客户端(倒数第二列)
'', # 强制按钮(最后一列)
]
temp.append(row)
# 按类型排序
temp.sort(key=lambda x: x[3])
self.datas = temp
self.layoutChanged.emit()
def rowCount(self, parent=None):
return len(self.datas)
def columnCount(self, parent=None):
return len(self.header)
def data(self, index, role=Qt.DisplayRole):
if not index.isValid():
return QVariant()
row = index.row()
col = index.column()
if role == Qt.DisplayRole:
# 强制值输入列显示缓存值
if col == 1:
return self.inputValues.get(row, '')
return self.datas[row][col]
if role == Qt.TextAlignmentRole:
return Qt.AlignCenter
if role == Qt.BackgroundRole:
if row % 2 == 0:
return QColor('#EFEFEF')
return QVariant()
def setData(self, index, value, role=Qt.EditRole):
if index.isValid() and index.column() == 1:
self.inputValues[index.row()] = value
self.dataChanged.emit(index, index)
return True
return False
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.header[section]
return QVariant()
def flags(self, index):
if not index.isValid():
return Qt.ItemIsEnabled
if index.column() == 1:
return Qt.ItemIsEnabled | Qt.ItemIsEditable
if index.column() == 7:
return Qt.ItemIsEnabled # 按钮列不可编辑
return Qt.ItemIsEnabled
from PyQt5.QtWidgets import QStyledItemDelegate, QPushButton, QStyleOptionButton, QStyle, QApplication
class RpcVarButtonDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
super(RpcVarButtonDelegate, self).__init__(parent)
def paint(self, painter, option, index):
if not self.parent().indexWidget(index) and index.column() == 7:
button = QPushButton(
qtawesome.icon('fa.play', color='#1fbb6f'),
'',
self.parent()
)
button.setIconSize(QSize(15, 15))
button.setStyleSheet('border:none;')
button.index = [index.row(), index.column()]
button.clicked.connect(lambda: self.force_action(button))
h_box_layout = QHBoxLayout()
h_box_layout.addWidget(button)
h_box_layout.setContentsMargins(2, 0, 0, 2)
h_box_layout.setAlignment(Qt.AlignCenter)
widget = QWidget()
widget.setLayout(h_box_layout)
self.parent().setIndexWidget(index, widget)
# 不再用QStyleOptionButton绘制
def force_action(self, sender):
model = self.parent().model
row = sender.index[0]
varName = model.datas[row][2]
minValue = model.datas[row][4]
maxValue = model.datas[row][5]
forceValue = model.inputValues.get(row, '')
if forceValue == '':
QMessageBox.warning(self.parent(), '提示', '请先输入强制值')
return
try:
protocolManage = Globals.getValue('protocolManage')
if protocolManage and protocolManage.RpcServer:
resp = protocolManage.RpcServer.writeVar(varName, float(forceValue))
if resp.get('result') == 'success':
QMessageBox.information(self.parent(), '提示', f'{varName} 强制成功!')
else:
reason = resp.get('reason', '')
QMessageBox.warning(self.parent(), '失败', f'{varName} 强制失败: {reason}')
except Exception as e:
QMessageBox.warning(self.parent(), '错误', f'强制失败: {e}')

@ -11,6 +11,7 @@ from .AnalogModel import *
from .HartModel import *
from .HartSimulateModel import *
from .TCRTDModel import *
from .RpcVarModel import RpcVarModel, RpcVarButtonDelegate
from model.ProjectModel.VarManage import *
@ -180,3 +181,21 @@ class HartSimulateTableView(VarTableView):
self.setItemDelegateForColumn(10, HartSimulateButtonDelegate(self))
self.setItemDelegateForColumn(9, HartSimulateVarModelBox(self))
self.model = HartSimulateModel([' ID', '仪器名', '描述', '主变量', '过程变量1', '过程变量2', '过程变量3', '工程量下限', '工程量上限', '值类型','操作'], [], table=self)
class RpcVarTableView(QTableView):
def __init__(self, parent=None):
super(RpcVarTableView, self).__init__(parent)
self.setShowGrid(True)
self.setAlternatingRowColors(True)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setItemDelegateForColumn(7, RpcVarButtonDelegate(self))
self.model = RpcVarModel(['客户端', '变量名', '类型', '下限', '上限', '当前值', '操作'], self)
self.setModel(self.model)
self.header = QHeaderView(Qt.Horizontal, self)
self.header.setStretchLastSection(True)
self.setHorizontalHeader(self.header)
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setupColumnWidths()
def setupColumnWidths(self):
self.header.setSectionResizeMode(QHeaderView.Stretch)

@ -1,10 +1,10 @@
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QSize, Qt, QTimer
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QSplitter, QPushButton, QFileDialog, QMessageBox
from PyQt5.QtWidgets import QSplitter, QPushButton, QFileDialog, QMessageBox, QVBoxLayout
from UI.VarManages.VarTable import VarTableView, HartTableView, TcRtdTableView, AnalogTableView, \
HartSimulateTableView
HartSimulateTableView, RpcVarTableView
from UI.VarManages.MessageWidget import MessageWidget
from model.ProjectModel.ProjectManage import ProjectManage
@ -391,51 +391,38 @@ class TcRtdWidgets(VarWidgets):
self.varView.model.append_data(['', '', '', '', '', '', '', '', '',''])
def forceVar(self):
# print(self.varView.model.datas)
check = [i for i,x in enumerate(self.varView.model.checkList) if x == 'Checked']
check = [i for i, x in enumerate(self.varView.model.checkList) if x == 'Checked']
if not check:
QMessageBox.information(self, '提示', '请先勾选要强制的变量', QMessageBox.Yes)
return
forceVars = Globals.getValue('forceVars')
if forceVars is None:
forceVars = set()
for i in check:
value = self.varView.model.datas[i][1]
varType = self.varView.model.datas[i][5]
min = self.varView.model.datas[i][6]
max = self.varView.model.datas[i][7]
varName = self.varView.model.datas[i][3]
min_ = self.varView.model.datas[i][7]
max_ = self.varView.model.datas[i][8]
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
elif min and not max:
if float(value) < float(min):
reply = QMessageBox.question(self.parent(),
'警告',
"超出量程范围",
QMessageBox.Yes)
return
elif max and not min:
if float(value) > float(max):
reply = QMessageBox.question(self.parent(),
'警告',
"超出量程范围",
QMessageBox.Yes)
return
mv = temToMv(varType, float(value))
if not mv and mv != 0:
continue
self.varView.mvList[i] = mv
self.varView.valueList[i] = float(value)
forceVars = Globals.getValue('forceVars')
# forceVars.add(model.datas[sender.index[0]][3])
forceVars.add(self.varView.model.datas[i][3])
Globals.setValue('forceVars', forceVars)
# self.varView.valueList = [float(x[1]) for x in self.varView.model.datas]
if min_ and max_:
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
res = Globals.getValue('protocolManage').writeVariableValue(varName, float(value))
if res:
forceVars.add(varName)
else:
QMessageBox.information(self, '提示', f'变量 {varName} 写入失败', QMessageBox.Yes)
Globals.setValue('forceVars', forceVars)
# self.varView.model.refreshValueCache()
# self.varView.model.layoutChanged.emit()
@ -494,58 +481,38 @@ class AnalogWidgets(VarWidgets):
self.varView.model.append_data(['', '', '', '', '', '', '', '', ''])
def forceVar(self):
check = [i for i,x in enumerate(self.varView.model.checkList) if x == 'Checked']
check = [i for i, x in enumerate(self.varView.model.checkList) if x == 'Checked']
if not check:
QMessageBox.information(self, '提示', '请先勾选要强制的变量', QMessageBox.Yes)
return
forceVars = Globals.getValue('forceVars')
if forceVars is None:
forceVars = set()
for i in check:
value = self.varView.model.datas[i][1]
min = self.varView.model.datas[i][6]
max = self.varView.model.datas[i][7]
varName = self.varView.model.datas[i][3]
min_ = self.varView.model.datas[i][7]
max_ = self.varView.model.datas[i][8]
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 i > 16:
if not value.isdigit:
reply = QMessageBox.question(self.parent(),
'警告',
"请输入0或1",
QMessageBox.Yes)
if min_ and max_:
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
if min and max and i < 16:
if float(value) < float(min) or float(value) > float(max):
reply = QMessageBox.question(self.parent(),
'警告',
"超出量程范围",
QMessageBox.Yes)
return
else:
min = None
max = None
if (float(value) > 20 or float(value)) < 4 and i < 8:
reply = QMessageBox.question(self.parent(),
'警告',
"超出量程范围",
QMessageBox.Yes)
return
if (float(value) > 10000 or float(value) < 0.1) and 7 < i < 16:
reply = QMessageBox.question(self.parent(),
'警告',
"超出量程范围",
QMessageBox.Yes)
return
if i < 8:
self.varView.model.table.realList[i] = getRealAO(float(value), float(max), float(min))
self.varView.model.table.valueList[i] = float(value)
res = Globals.getValue('protocolManage').writeVariableValue(varName, float(value))
if res:
forceVars.add(varName)
else:
self.varView.model.table.realList[i] = float(value)
self.varView.model.table.valueList[i] = float(value)
self.varView.valueList[i] = float(value)
forceVars.add(self.varView.model.datas[i][3])
Globals.setValue('forceVars', forceVars)
QMessageBox.information(self, '提示', f'变量 {varName} 写入失败', QMessageBox.Yes)
Globals.setValue('forceVars', forceVars)
# self.varView.model.refreshValueCache()
# self.varView.model.layoutChanged.emit()

@ -55,7 +55,7 @@ class ProtocolManage(object):
def setClentMode(self, clentName, rabbitmqHost='localhost'):
if self.RpcClient:
self.RpcClient.close()
self.RpcClient = RpcClient(clentName, rabbitmqHost)
self.RpcClient = RpcClient(clentName, rabbitmqHost, self)
# 使用非阻塞方式启动RPC客户端
self.RpcClient.startNonBlocking()
@ -66,7 +66,7 @@ class ProtocolManage(object):
def setServerMode(self, rabbitmqHost='localhost'):
if self.RpcServer:
self.RpcServer.close()
return
self.RpcServer = RpcServer(rabbitmqHost)
@ -109,15 +109,15 @@ class ProtocolManage(object):
# 仅设置值,不保存到数据库
pass
if modelType == 'ModbusTcpSlaveVar':
elif modelType == 'ModbusTcpSlaveVar':
# 仅设置值,不保存到数据库
pass
if modelType == 'ModbusRtuMasterVar':
elif modelType == 'ModbusRtuMasterVar':
# 仅设置值,不保存到数据库
pass
if modelType == 'ModbusRtuSlaveVar':
elif modelType == 'ModbusRtuSlaveVar':
# 仅设置值,不保存到数据库
pass
@ -165,6 +165,14 @@ class ProtocolManage(object):
elif modelType == 'HartSimulateVar':
# 仅设置值,不保存到数据库
pass
else:
if self.RpcServer:
existsVar, clientNames = self.RpcServer.existsVar(variableName)
if existsVar:
value = self.RpcServer.writeVar(variableName, value)
return True
else:
return True
if self.RpcClient:
self.RpcClient.setVarContent(variableName, value, info['min'], info['max'], info['varType'])
@ -249,15 +257,21 @@ class ProtocolManage(object):
# HART模拟变量处理
elif modelType == 'HartSimulateVar':
pass
# print(1111111111111111)
if self.RpcServer:
existsVar, clientNames = self.RpcServer.existsVar(variableName)
if existsVar:
# print(clientNames, 1111111111111111)
value = self.RpcServer.getVarValue(clientNames[0], variableName)
return value
else:
return None
return None # 暂时返回None
except Exception as e:
print(f"读取变量值失败: {str(e)}")
return str(e)
def readRpcVariable(self):
if self.RpcServer:
print(self.RpcServer.broadcastRead())
# def sendTrigger(self, variableName, value, timeoutMS):
# self.writeVariableValue(variableName, value, trigger=True, timeoutMS = timeoutMS)
@ -306,4 +320,8 @@ class ProtocolManage(object):
elif varModel == '模拟值':
return SimModel
def disconnectClient(self, clientName):
if self.RpcServer:
self.RpcServer.removeClient(clientName)

@ -2,6 +2,7 @@ import pika
import uuid
import json
import threading
import socket
class RpcClient:
def __init__(self, clientName, rabbitHost='localhost', protocolManager=None):
@ -13,6 +14,7 @@ class RpcClient:
"""
self.clientName = clientName
self.protocolManager = protocolManager
self.rabbitHost = rabbitHost
self.variables = {}
# self.variables = {
# "temp": {"type": "AI", "min": 0, "max": 100, "value": 25.5},
@ -25,7 +27,7 @@ class RpcClient:
self.channel = self.connection.channel()
self.queueName = f"rpc_queue_{self.clientName}"
self.channel.queue_declare(queue=self.queueName)
print(f"[{self.clientName}] 等待服务端指令...")
# print(f"[{self.clientName}] 等待服务端指令...")
def onRequest(self, ch, method, props, body):
request = json.loads(body)
@ -33,8 +35,9 @@ class RpcClient:
response = {}
if cmd == "ping":
# 响应ping命令
response = {"client": self.clientName, "result": "pong"}
# 响应ping命令带本机IP
ip = self.getLocalIp()
response = {"client": self.clientName, "result": "pong", "ip": ip}
elif cmd == "read":
response = {"client": self.clientName, "variables": self.variables}
elif cmd == "write":
@ -91,8 +94,8 @@ class RpcClient:
if type in ['DI', 'DO']:
min = 0
max = 1
self.variables[variableName] = {}
self.variables[variableName]["value"] = value
variableName = variableName + '.' + self.clientName
self.variables[variableName]["value"] = value + '.' + self.clientName
self.variables[variableName]["min"] = min
self.variables[variableName]["max"] = max
self.variables[variableName]["type"] = type
@ -126,6 +129,17 @@ class RpcClient:
except Exception as e:
print(f"[{self.clientName}] 关闭连接时出错: {e}")
def getLocalIp(self):
# 获取本机第一个非回环IPv4地址
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
ip = s.getsockname()[0]
s.close()
return ip
except:
return '127.0.0.1'
if __name__ == "__main__":
import sys
clientName = sys.argv[1] if len(sys.argv) > 1 else "Client1"

@ -18,10 +18,13 @@ class RpcServer:
self.callbackQueue = None
self.responses = {}
self.lock = threading.Lock()
self.connected = False
self.connected = False
self.clientIpMap = {} # 客户端名到IP的映射
# 初始化连接
self.connectToRabbitMQ()
self.autoCheckThread = threading.Thread(target=self._autoCheckClients, daemon=True)
self.autoCheckThread.start()
def connectToRabbitMQ(self):
"""连接到RabbitMQ服务器"""
@ -58,18 +61,22 @@ class RpcServer:
if not self.connected:
print("服务端未连接到RabbitMQ")
return False
# 修复:防止重复添加同名客户端
if clientName in self.clientNames:
print(f"客户端 {clientName} 已存在")
return False
# 检查客户端是否在线
try:
# 发送ping消息测试客户端是否响应
response = self.call(clientName, {"cmd": "ping"})
if response and response.get("result") == "pong":
self.clientNames.append(clientName)
print(f"客户端 {clientName} 添加成功")
# 记录客户端IP
if 'ip' in response:
self.clientIpMap[clientName] = response['ip']
# 去重,防止多线程下重复
self.clientNames = list(dict.fromkeys(self.clientNames))
# print(f"客户端 {clientName} 添加成功")
return True
else:
print(f"客户端 {clientName} 无响应")
@ -86,7 +93,8 @@ class RpcServer:
"""
if clientName in self.clientNames:
self.clientNames.remove(clientName)
print(f"客户端 {clientName} 已移除")
if clientName in self.clientIpMap:
self.clientIpMap.pop(clientName)
return True
return False
@ -134,6 +142,10 @@ class RpcServer:
"""
return self.clientNames.copy()
def getClientIpMap(self):
"""获取客户端名到IP的映射"""
return self.clientIpMap.copy()
def pingClient(self, clientName):
"""
测试客户端是否在线
@ -193,7 +205,7 @@ class RpcServer:
:return: 所有客户端的变量数据
"""
if not self.clientNames:
print("没有可用的客户端")
# print("没有可用的客户端")
return []
results = []
@ -215,18 +227,20 @@ class RpcServer:
"""
if not self.clientNames:
return {"result": "fail", "reason": "no clients available"}
# 先查找变量在哪个客户端
for client in self.clientNames:
exists, clients = self.existsVar(varName)
if not exists or not clients:
return {"result": "fail", "reason": "var not found"}
# 支持多个客户端有同名变量,优先写第一个成功的
for client in clients:
try:
resp = self.call(client, {"cmd": "read"})
if resp and "variables" in resp and varName in resp["variables"]:
writeResp = self.call(client, {"cmd": "write", "varName": varName, "value": value})
print(f"写入客户端 {client} 变量 {varName}{value}")
writeResp = self.call(client, {"cmd": "write", "varName": varName, "value": value})
if writeResp and writeResp.get("result") == "success":
return writeResp
except Exception as e:
print(f"检查客户端 {client} 失败: {e}")
continue
return {"result": "fail", "reason": "var not found"}
return {"result": "fail", "reason": "write failed on all clients"}
def scanAndAddClients(self, clientNameList):
"""
@ -269,6 +283,51 @@ class RpcServer:
self.connected = False
print("服务端连接已关闭")
def _autoCheckClients(self):
"""后台线程每2秒自动检测客户端是否在线不在线则移除"""
while True:
try:
self.checkAllClients()
except Exception as e:
print(f"自动检测客户端异常: {e}")
time.sleep(1)
def existsVar(self, varName):
"""
判断变量名是否存在于所有已连接客户端的变量中
:param varName: 变量名
:return: (True, clientName) (False, None)
"""
foundClients = []
for client in self.clientNames:
try:
resp = self.call(client, {"cmd": "read"})
if resp and "variables" in resp and varName in resp["variables"]:
foundClients.append(client)
except Exception as e:
continue
if foundClients:
return True, foundClients
else:
return False, None
def getVarValue(self, clientName, varName):
"""
获取指定客户端上指定变量的值
:param clientName: 客户端名称
:param varName: 变量名
:return: 变量值或None
"""
if clientName not in self.clientNames:
return None
try:
resp = self.call(clientName, {"cmd": "read"})
if resp and "variables" in resp and varName in resp["variables"]:
return resp["variables"][varName]
except Exception as e:
pass
return None
if __name__ == "__main__":
# 创建服务端
server = RpcServer()

Loading…
Cancel
Save