#!/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()