|
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
HART变量信息显示界面模块
|
|
|
|
|
|
实现主变量、第二变量、回路电流等动态数据的读取与显示
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
|
|
|
|
QPushButton, QGroupBox, QFormLayout, QTextEdit,
|
|
|
|
|
|
QMessageBox, QProgressBar, QSplitter)
|
|
|
|
|
|
from PyQt5.QtCore import Qt, QThread, pyqtSignal
|
|
|
|
|
|
from PyQt5.QtGui import QFont, QTextCursor
|
|
|
|
|
|
from protocol.HART import common
|
|
|
|
|
|
|
|
|
|
|
|
class ReadVariableThread(QThread):
|
|
|
|
|
|
"""读取单个变量信息的线程类"""
|
|
|
|
|
|
infoReceived = pyqtSignal(dict, str) # 信号发送(响应字典, 命令类型)
|
|
|
|
|
|
errorOccurred = pyqtSignal(str, str)
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, hartComm, command_name):
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
self.hartComm = hartComm
|
|
|
|
|
|
self.command_name = command_name
|
|
|
|
|
|
self._is_running = True
|
|
|
|
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
|
|
self._is_running = False
|
|
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
|
|
try:
|
|
|
|
|
|
if not self.hartComm:
|
|
|
|
|
|
raise Exception("HART通信对象未初始化")
|
|
|
|
|
|
|
|
|
|
|
|
func = getattr(self.hartComm, self.command_name, None)
|
|
|
|
|
|
if not func:
|
|
|
|
|
|
raise Exception(f"未找到名为 {self.command_name} 的通信方法")
|
|
|
|
|
|
|
|
|
|
|
|
response = func()
|
|
|
|
|
|
|
|
|
|
|
|
if self._is_running:
|
|
|
|
|
|
msg = response[0] if isinstance(response, list) and response else response
|
|
|
|
|
|
# print(msg)
|
|
|
|
|
|
if msg and isinstance(msg, dict):
|
|
|
|
|
|
if msg.get("status") != "fail":
|
|
|
|
|
|
self.infoReceived.emit(msg, self.command_name)
|
|
|
|
|
|
else:
|
|
|
|
|
|
error_msg = msg.get("error", "未知错误")
|
|
|
|
|
|
self.errorOccurred.emit(f"命令 {self.command_name} 执行失败: {error_msg}", self.command_name)
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.errorOccurred.emit(f"收到无效响应: {response}", self.command_name)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
self.errorOccurred.emit(str(e), self.command_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class HartVariableInfoWidget(QWidget):
|
|
|
|
|
|
"""HART变量信息显示界面"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
self.hartComm = None
|
|
|
|
|
|
self.readThreads = {}
|
|
|
|
|
|
self.initUI()
|
|
|
|
|
|
|
|
|
|
|
|
def initUI(self):
|
|
|
|
|
|
mainLayout = QVBoxLayout(self)
|
|
|
|
|
|
topLayout = QHBoxLayout()
|
|
|
|
|
|
|
|
|
|
|
|
# --- Command 1 Group ---
|
|
|
|
|
|
cmd1Group = QGroupBox("主变量 (Command 1)")
|
|
|
|
|
|
cmd1Layout = QFormLayout()
|
|
|
|
|
|
self.pvLabel = QLabel("--")
|
|
|
|
|
|
self.pvUnitLabel = QLabel("--")
|
|
|
|
|
|
cmd1Layout.addRow("主变量值:", self.pvLabel)
|
|
|
|
|
|
cmd1Layout.addRow("单位:", self.pvUnitLabel)
|
|
|
|
|
|
self.readCmd1Button = QPushButton("读取")
|
|
|
|
|
|
self.readCmd1Button.clicked.connect(lambda: self.readVariableInfo('readPrimaryVariable'))
|
|
|
|
|
|
cmd1Layout.addRow(self.readCmd1Button)
|
|
|
|
|
|
cmd1Group.setLayout(cmd1Layout)
|
|
|
|
|
|
|
|
|
|
|
|
# --- Command 2 Group ---
|
|
|
|
|
|
cmd2Group = QGroupBox("回路电流和百分比 (Command 2)")
|
|
|
|
|
|
cmd2Layout = QFormLayout()
|
|
|
|
|
|
self.loopCurrentLabel_cmd2 = QLabel("--")
|
|
|
|
|
|
self.percentRangeLabel = QLabel("--")
|
|
|
|
|
|
cmd2Layout.addRow("回路电流:", self.loopCurrentLabel_cmd2)
|
|
|
|
|
|
cmd2Layout.addRow("量程百分比:", self.percentRangeLabel)
|
|
|
|
|
|
self.readCmd2Button = QPushButton("读取")
|
|
|
|
|
|
self.readCmd2Button.clicked.connect(lambda: self.readVariableInfo('readLoopCurrentAndPercent'))
|
|
|
|
|
|
cmd2Layout.addRow(self.readCmd2Button)
|
|
|
|
|
|
cmd2Group.setLayout(cmd2Layout)
|
|
|
|
|
|
|
|
|
|
|
|
# --- Command 3 Group ---
|
|
|
|
|
|
cmd3Group = QGroupBox("动态变量和回路电流 (Command 3)")
|
|
|
|
|
|
cmd3Layout = QFormLayout()
|
|
|
|
|
|
self.loopCurrentLabel_cmd3 = QLabel("--")
|
|
|
|
|
|
self.pv_cmd3_Label = QLabel("--")
|
|
|
|
|
|
self.pv_cmd3_UnitLabel = QLabel("--")
|
|
|
|
|
|
self.sv_cmd3_Label = QLabel("--")
|
|
|
|
|
|
self.sv_cmd3_UnitLabel = QLabel("--")
|
|
|
|
|
|
self.tv_cmd3_Label = QLabel("--")
|
|
|
|
|
|
self.tv_cmd3_UnitLabel = QLabel("--")
|
|
|
|
|
|
self.qv_cmd3_Label = QLabel("--")
|
|
|
|
|
|
self.qv_cmd3_UnitLabel = QLabel("--")
|
|
|
|
|
|
cmd3Layout.addRow("回路电流:", self.loopCurrentLabel_cmd3)
|
|
|
|
|
|
cmd3Layout.addRow("主变量:", self.pv_cmd3_Label)
|
|
|
|
|
|
cmd3Layout.addRow("主变量单位:", self.pv_cmd3_UnitLabel)
|
|
|
|
|
|
cmd3Layout.addRow("第二变量:", self.sv_cmd3_Label)
|
|
|
|
|
|
cmd3Layout.addRow("第二变量单位:", self.sv_cmd3_UnitLabel)
|
|
|
|
|
|
cmd3Layout.addRow("第三变量:", self.tv_cmd3_Label)
|
|
|
|
|
|
cmd3Layout.addRow("第三变量单位:", self.tv_cmd3_UnitLabel)
|
|
|
|
|
|
cmd3Layout.addRow("第四变量:", self.qv_cmd3_Label)
|
|
|
|
|
|
cmd3Layout.addRow("第四变量单位:", self.qv_cmd3_UnitLabel)
|
|
|
|
|
|
self.readCmd3Button = QPushButton("读取")
|
|
|
|
|
|
self.readCmd3Button.clicked.connect(lambda: self.readVariableInfo('readDynamicVariablesAndLoopCurrent'))
|
|
|
|
|
|
cmd3Layout.addRow(self.readCmd3Button)
|
|
|
|
|
|
cmd3Group.setLayout(cmd3Layout)
|
|
|
|
|
|
|
|
|
|
|
|
topLayout.addWidget(cmd1Group)
|
|
|
|
|
|
topLayout.addWidget(cmd2Group)
|
|
|
|
|
|
topLayout.addWidget(cmd3Group)
|
|
|
|
|
|
mainLayout.addLayout(topLayout)
|
|
|
|
|
|
|
|
|
|
|
|
# --- Raw Data Group ---
|
|
|
|
|
|
rawDataGroup = QGroupBox("原始数据")
|
|
|
|
|
|
rawDataLayout = QVBoxLayout()
|
|
|
|
|
|
self.rawDataText = QTextEdit()
|
|
|
|
|
|
self.rawDataText.setReadOnly(True)
|
|
|
|
|
|
self.rawDataText.setFont(QFont("Consolas", 9))
|
|
|
|
|
|
self.rawDataText.setLineWrapMode(QTextEdit.NoWrap)
|
|
|
|
|
|
rawDataLayout.addWidget(self.rawDataText)
|
|
|
|
|
|
rawDataGroup.setLayout(rawDataLayout)
|
|
|
|
|
|
|
|
|
|
|
|
mainLayout.addWidget(rawDataGroup)
|
|
|
|
|
|
|
|
|
|
|
|
# --- Global Controls ---
|
|
|
|
|
|
controlLayout = QHBoxLayout()
|
|
|
|
|
|
self.clearButton = QPushButton("清除所有显示")
|
|
|
|
|
|
self.clearButton.clicked.connect(self.clearAllDisplays)
|
|
|
|
|
|
controlLayout.addStretch()
|
|
|
|
|
|
controlLayout.addWidget(self.clearButton)
|
|
|
|
|
|
mainLayout.addLayout(controlLayout)
|
|
|
|
|
|
|
|
|
|
|
|
self.setHartComm(None) # Disable buttons initially
|
|
|
|
|
|
|
|
|
|
|
|
def setHartComm(self, hartComm):
|
|
|
|
|
|
self.hartComm = hartComm
|
|
|
|
|
|
is_connected = hartComm is not None
|
|
|
|
|
|
self.readCmd1Button.setEnabled(is_connected)
|
|
|
|
|
|
self.readCmd2Button.setEnabled(is_connected)
|
|
|
|
|
|
self.readCmd3Button.setEnabled(is_connected)
|
|
|
|
|
|
|
|
|
|
|
|
def readVariableInfo(self, command_name):
|
|
|
|
|
|
if not self.hartComm:
|
|
|
|
|
|
QMessageBox.warning(self, "警告", "未连接到HART设备!")
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
if command_name in self.readThreads and self.readThreads[command_name].isRunning():
|
|
|
|
|
|
self.readThreads[command_name].stop()
|
|
|
|
|
|
self.readThreads[command_name].wait()
|
|
|
|
|
|
|
|
|
|
|
|
thread = ReadVariableThread(self.hartComm, command_name)
|
|
|
|
|
|
thread.infoReceived.connect(self.updateVariableInfo)
|
|
|
|
|
|
thread.errorOccurred.connect(self.handleError)
|
|
|
|
|
|
thread.finished.connect(lambda: self.onReadFinished(command_name))
|
|
|
|
|
|
|
|
|
|
|
|
self.readThreads[command_name] = thread
|
|
|
|
|
|
button = self.getButtonForCommand(command_name)
|
|
|
|
|
|
if button:
|
|
|
|
|
|
button.setText("读取中...")
|
|
|
|
|
|
button.setEnabled(False)
|
|
|
|
|
|
thread.start()
|
|
|
|
|
|
|
|
|
|
|
|
def updateVariableInfo(self, msg, command_name):
|
|
|
|
|
|
self.appendRawData(msg)
|
|
|
|
|
|
|
|
|
|
|
|
if command_name == 'readPrimaryVariable':
|
|
|
|
|
|
pv = msg.get('primary_variable')
|
|
|
|
|
|
unit_code = msg.get('primary_variable_units')
|
|
|
|
|
|
self.pvLabel.setText(f"{pv:.4f}" if pv is not None else "--")
|
|
|
|
|
|
self.pvUnitLabel.setText(common.get_unit_description(unit_code))
|
|
|
|
|
|
|
|
|
|
|
|
elif command_name == 'readLoopCurrentAndPercent':
|
|
|
|
|
|
current = msg.get('analog_signal')
|
|
|
|
|
|
percent = msg.get('primary_variable')
|
|
|
|
|
|
self.loopCurrentLabel_cmd2.setText(f"{current:.4f} mA" if current is not None else "--")
|
|
|
|
|
|
self.percentRangeLabel.setText(f"{percent:.2f} %" if percent is not None else "--")
|
|
|
|
|
|
|
|
|
|
|
|
elif command_name == 'readDynamicVariablesAndLoopCurrent':
|
|
|
|
|
|
current = msg.get('analog_signal')
|
|
|
|
|
|
self.loopCurrentLabel_cmd3.setText(f"{current:.4f} mA" if current is not None else "--")
|
|
|
|
|
|
|
|
|
|
|
|
pv = msg.get('primary_variable')
|
|
|
|
|
|
pv_unit = msg.get('primary_variable_units')
|
|
|
|
|
|
self.pv_cmd3_Label.setText(f"{pv:.4f}" if pv is not None else "--")
|
|
|
|
|
|
self.pv_cmd3_UnitLabel.setText(common.get_unit_description(pv_unit))
|
|
|
|
|
|
|
|
|
|
|
|
sv = msg.get('secondary_variable')
|
|
|
|
|
|
sv_unit = msg.get('secondary_variable_units')
|
|
|
|
|
|
self.sv_cmd3_Label.setText(f"{sv:.4f}" if sv is not None else "--")
|
|
|
|
|
|
self.sv_cmd3_UnitLabel.setText(common.get_unit_description(sv_unit))
|
|
|
|
|
|
|
|
|
|
|
|
tv = msg.get('tertiary_variable')
|
|
|
|
|
|
tv_unit = msg.get('tertiary_variable_units')
|
|
|
|
|
|
self.tv_cmd3_Label.setText(f"{tv:.4f}" if tv is not None else "--")
|
|
|
|
|
|
self.tv_cmd3_UnitLabel.setText(common.get_unit_description(tv_unit))
|
|
|
|
|
|
|
|
|
|
|
|
qv = msg.get('quaternary_variable')
|
|
|
|
|
|
qv_unit = msg.get('quaternary_variable_units')
|
|
|
|
|
|
self.qv_cmd3_Label.setText(f"{qv:.4f}" if qv is not None else "--")
|
|
|
|
|
|
self.qv_cmd3_UnitLabel.setText(common.get_unit_description(qv_unit))
|
|
|
|
|
|
|
|
|
|
|
|
def appendRawData(self, msg):
|
|
|
|
|
|
# self.rawDataText.append(\"--- 接收到的报文: {msg.get('command_name', 'N/A')} ---\")
|
|
|
|
|
|
for key, value in msg.items():
|
|
|
|
|
|
if isinstance(value, bytes):
|
|
|
|
|
|
value_str = ' '.join(f'{b:02x}' for b in value)
|
|
|
|
|
|
self.rawDataText.append(f"{key}: {value_str} (hex)")
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.rawDataText.append(f"{key}: {value}")
|
|
|
|
|
|
self.rawDataText.append("\\n")
|
|
|
|
|
|
self.rawDataText.moveCursor(QTextCursor.End)
|
|
|
|
|
|
|
|
|
|
|
|
def handleError(self, errorMsg, command_name):
|
|
|
|
|
|
QMessageBox.critical(self, "错误", f"执行 {command_name} 时出错:\\n{errorMsg}")
|
|
|
|
|
|
|
|
|
|
|
|
def onReadFinished(self, command_name):
|
|
|
|
|
|
button = self.getButtonForCommand(command_name)
|
|
|
|
|
|
if button:
|
|
|
|
|
|
button.setText("读取")
|
|
|
|
|
|
button.setEnabled(self.hartComm is not None)
|
|
|
|
|
|
|
|
|
|
|
|
def getButtonForCommand(self, command_name):
|
|
|
|
|
|
if command_name == 'readPrimaryVariable':
|
|
|
|
|
|
return self.readCmd1Button
|
|
|
|
|
|
elif command_name == 'readLoopCurrentAndPercent':
|
|
|
|
|
|
return self.readCmd2Button
|
|
|
|
|
|
elif command_name == 'readDynamicVariablesAndLoopCurrent':
|
|
|
|
|
|
return self.readCmd3Button
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def clearAllDisplays(self):
|
|
|
|
|
|
# Cmd 1
|
|
|
|
|
|
self.pvLabel.setText("--")
|
|
|
|
|
|
self.pvUnitLabel.setText("--")
|
|
|
|
|
|
# Cmd 2
|
|
|
|
|
|
self.loopCurrentLabel_cmd2.setText("--")
|
|
|
|
|
|
self.percentRangeLabel.setText("--")
|
|
|
|
|
|
# Cmd 3
|
|
|
|
|
|
self.loopCurrentLabel_cmd3.setText("--")
|
|
|
|
|
|
self.pv_cmd3_Label.setText("--")
|
|
|
|
|
|
self.pv_cmd3_UnitLabel.setText("--")
|
|
|
|
|
|
self.sv_cmd3_Label.setText("--")
|
|
|
|
|
|
self.sv_cmd3_UnitLabel.setText("--")
|
|
|
|
|
|
self.tv_cmd3_Label.setText("--")
|
|
|
|
|
|
self.tv_cmd3_UnitLabel.setText("--")
|
|
|
|
|
|
self.qv_cmd3_Label.setText("--")
|
|
|
|
|
|
self.qv_cmd3_UnitLabel.setText("--")
|
|
|
|
|
|
|
|
|
|
|
|
self.rawDataText.clear()
|