You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

316 lines
14 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox,
QLabel, QPushButton, QFormLayout, QMessageBox,
QComboBox, QDoubleSpinBox, QCompleter)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from protocol.HART import common
class ReadInfoThread(QThread):
"""读取或写入信息的线程类"""
infoReceived = pyqtSignal(dict, str)
errorOccurred = pyqtSignal(str, str)
def __init__(self, hartComm, command_name, params=None):
super().__init__()
self.hartComm = hartComm
self.command_name = command_name
self.params = params if params is not None else {}
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(**self.params)
if self._is_running:
msg = response[0] if isinstance(response, list) and response else response
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 HartSensorConfigWidget(QWidget):
def __init__(self, parent=None):
super(HartSensorConfigWidget, self).__init__(parent)
self.hartComm = None
self.readThreads = {}
self.units_map = common.UNITS_CODE
self.reverse_units_map = common.REVERSE_UNITS_CODE
self.transfer_fn_map = common.TRANSFER_FUNCTION_CODE
self.reverse_transfer_fn_map = common.REVERSE_TRANSFER_FUNCTION_CODE
self.initUI()
def initUI(self):
mainLayout = QVBoxLayout(self)
displayLayout = QHBoxLayout()
pvInfoGroup = QGroupBox("传感器信息 (Command 14)")
pvInfoForm = QFormLayout()
self.serialNoLabel = QLabel("--")
self.sensorUpperLimitLabel = QLabel("--")
self.sensorLowerLimitLabel = QLabel("--")
self.sensorUnitLabel = QLabel("--")
self.minSpanLabel = QLabel("--")
pvInfoForm.addRow("传感器序列号:", self.serialNoLabel)
pvInfoForm.addRow("传感器上限:", self.sensorUpperLimitLabel)
pvInfoForm.addRow("传感器下限:", self.sensorLowerLimitLabel)
pvInfoForm.addRow("传感器单位:", self.sensorUnitLabel)
pvInfoForm.addRow("最小量程:", self.minSpanLabel)
self.readPvInfoButton = QPushButton("读取传感器信息")
self.readPvInfoButton.clicked.connect(lambda: self.readInfo('readPrimaryVariableInformation'))
pvInfoForm.addRow(self.readPvInfoButton)
pvInfoGroup.setLayout(pvInfoForm)
outputInfoGroup = QGroupBox("输出信息 (Command 15)")
outputInfoForm = QFormLayout()
self.alarmCodeLabel = QLabel("--")
self.transferFnLabel = QLabel("--")
self.pvRangeUnitLabel = QLabel("--")
self.pvUpperRangeLabel = QLabel("--")
self.pvLowerRangeLabel = QLabel("--")
self.dampingLabel = QLabel("--")
self.writeProtectLabel = QLabel("--")
self.privateLabelLabel = QLabel("--")
outputInfoForm.addRow("报警动作:", self.alarmCodeLabel)
outputInfoForm.addRow("输出特性:", self.transferFnLabel)
outputInfoForm.addRow("PV量程单位:", self.pvRangeUnitLabel)
outputInfoForm.addRow("PV上限值:", self.pvUpperRangeLabel)
outputInfoForm.addRow("PV下限值:", self.pvLowerRangeLabel)
outputInfoForm.addRow("阻尼值(s):", self.dampingLabel)
outputInfoForm.addRow("写保护:", self.writeProtectLabel)
outputInfoForm.addRow("私有标签:", self.privateLabelLabel)
self.readOutputInfoButton = QPushButton("读取输出信息")
self.readOutputInfoButton.clicked.connect(lambda: self.readInfo('readOutputInformation'))
outputInfoForm.addRow(self.readOutputInfoButton)
outputInfoGroup.setLayout(outputInfoForm)
displayLayout.addWidget(pvInfoGroup)
displayLayout.addWidget(outputInfoGroup)
mainLayout.addLayout(displayLayout)
configLayout = QHBoxLayout()
writeGroup = QGroupBox("写入配置")
writeForm = QFormLayout()
self.rangeUnitComboBox = QComboBox()
self.rangeUnitComboBox.setEditable(True)
self.rangeUnitComboBox.addItems(sorted(self.units_map.values()))
completer = QCompleter(self.rangeUnitComboBox.model())
completer.setFilterMode(Qt.MatchContains)
completer.setCaseSensitivity(Qt.CaseInsensitive)
self.rangeUnitComboBox.setCompleter(completer)
self.upperRangeSpinBox = QDoubleSpinBox()
self.upperRangeSpinBox.setRange(-999999.0, 999999.0)
self.upperRangeSpinBox.setDecimals(4)
self.lowerRangeSpinBox = QDoubleSpinBox()
self.lowerRangeSpinBox.setRange(-999999.0, 999999.0)
self.lowerRangeSpinBox.setDecimals(4)
self.dampingSpinBox = QDoubleSpinBox()
self.dampingSpinBox.setRange(0, 100)
self.dampingSpinBox.setDecimals(2)
self.transferFnComboBox = QComboBox()
self.transferFnComboBox.addItems(self.transfer_fn_map.values())
self.setRangeButton = QPushButton("写入量程 (Cmd 35)")
self.setRangeButton.clicked.connect(self.onSetRange)
self.setDampingButton = QPushButton("写入阻尼 (Cmd 34)")
self.setDampingButton.clicked.connect(self.onSetDamping)
self.setTransferFnButton = QPushButton("写入输出函数 (Cmd 54)")
self.setTransferFnButton.clicked.connect(self.onSetTransferFunction)
writeForm.addRow("量程单位:", self.rangeUnitComboBox)
writeForm.addRow("量程上限:", self.upperRangeSpinBox)
writeForm.addRow("量程下限:", self.lowerRangeSpinBox)
writeForm.addRow(self.setRangeButton)
writeForm.addRow("阻尼(s):", self.dampingSpinBox)
writeForm.addRow(self.setDampingButton)
writeForm.addRow("输出函数:", self.transferFnComboBox)
writeForm.addRow(self.setTransferFnButton)
writeGroup.setLayout(writeForm)
configLayout.addWidget(writeGroup)
mainLayout.addLayout(configLayout)
self.setHartComm(None)
def setHartComm(self, hartComm):
self.hartComm = hartComm
is_connected = hartComm is not None
self.readPvInfoButton.setEnabled(is_connected)
self.readOutputInfoButton.setEnabled(is_connected)
self.setRangeButton.setEnabled(is_connected)
self.setDampingButton.setEnabled(is_connected)
self.setTransferFnButton.setEnabled(is_connected)
def readInfo(self, command_name):
if not self.hartComm:
QMessageBox.warning(self, "警告", "未连接到HART设备")
return
if command_name in self.readThreads and self.readThreads[command_name].isRunning():
return
thread = ReadInfoThread(self.hartComm, command_name)
thread.infoReceived.connect(self.updateInfo)
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.setProperty("original_text", button.text())
button.setText("读取中...")
button.setEnabled(False)
thread.start()
def updateInfo(self, msg, command_name):
if command_name == 'readPrimaryVariableInformation':
serial_no = msg.get('serial_no')
self.serialNoLabel.setText(f"{serial_no.hex().upper()}" if serial_no else "--")
self.sensorUpperLimitLabel.setText(f"{msg.get('upper_limit', '--'):.4f}")
self.sensorLowerLimitLabel.setText(f"{msg.get('lower_limit', '--'):.4f}")
self.sensorUnitLabel.setText(common.get_unit_description(msg.get('sensor_limits_code')))
self.minSpanLabel.setText(f"{msg.get('min_span', '--'):.4f}")
elif command_name == 'readOutputInformation':
self.alarmCodeLabel.setText(self.getAlarmCodeDescription(msg.get('alarm_code')))
transfer_code = msg.get('transfer_fn_code')
transfer_text = self.getTransferFunctionDescription(transfer_code)
self.transferFnLabel.setText(transfer_text)
if transfer_text in self.reverse_transfer_fn_map:
self.transferFnComboBox.setCurrentText(transfer_text)
unit_code = msg.get('primary_variable_range_code')
unit_text = common.get_unit_description(unit_code)
self.pvRangeUnitLabel.setText(unit_text)
if unit_text in self.reverse_units_map:
self.rangeUnitComboBox.setCurrentText(unit_text)
upper_val = msg.get('upper_range_value')
lower_val = msg.get('lower_range_value')
self.pvUpperRangeLabel.setText(f"{upper_val:.4f}" if upper_val is not None else "--")
self.pvLowerRangeLabel.setText(f"{lower_val:.4f}" if lower_val is not None else "--")
if upper_val is not None: self.upperRangeSpinBox.setValue(upper_val)
if lower_val is not None: self.lowerRangeSpinBox.setValue(lower_val)
damping_val = msg.get('damping_value')
self.dampingLabel.setText(f"{damping_val:.2f}" if damping_val is not None else "--")
if damping_val is not None: self.dampingSpinBox.setValue(damping_val)
self.writeProtectLabel.setText(self.getWriteProtectDescription(msg.get('write_protect')))
private_label = msg.get('private_label')
self.privateLabelLabel.setText(f"{private_label}" if private_label is not None else "--")
def onSetRange(self):
upper = self.upperRangeSpinBox.value()
lower = self.lowerRangeSpinBox.value()
unit_text = self.rangeUnitComboBox.currentText()
unit_code = self.reverse_units_map.get(unit_text)
if unit_code is None:
QMessageBox.warning(self, "输入错误", "无效的单位。请从列表中选择或正确输入。")
return
if upper <= lower:
QMessageBox.warning(self, "输入错误", "量程上限必须大于下限。")
return
self.startWriteThread('writePrimaryVariableRange', {'unitsCode': unit_code, 'upperRange': upper, 'lowerRange': lower})
def onSetDamping(self):
damping = self.dampingSpinBox.value()
self.startWriteThread('writePrimaryVariableDamping', {'dampingTime': damping})
def onSetTransferFunction(self):
fn_text = self.transferFnComboBox.currentText()
fn_code = self.reverse_transfer_fn_map.get(fn_text)
if fn_code is None:
QMessageBox.warning(self, "输入错误", "无效的输出函数。")
return
self.startWriteThread('writePrimaryVariableOutputFunction', {'transferFunctionCode': fn_code})
def startWriteThread(self, command_name, params):
if not self.hartComm:
QMessageBox.warning(self, "警告", "未连接到HART设备")
return
if command_name in self.readThreads and self.readThreads[command_name].isRunning():
return
thread = ReadInfoThread(self.hartComm, command_name, params)
thread.infoReceived.connect(self.onWriteSuccess)
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.setProperty("original_text", button.text())
button.setText("写入中...")
button.setEnabled(False)
thread.start()
def onWriteSuccess(self, msg, command_name):
QMessageBox.information(self, "成功", f"命令 {command_name} 执行成功。")
self.readInfo('readOutputInformation')
def getAlarmCodeDescription(self, code):
return {0: "高报", 1: "低报", 2: "保持最后输出"}.get(code, f"未知代码({code})")
def getTransferFunctionDescription(self, code):
return self.transfer_fn_map.get(code, f"未知代码({code})")
def getWriteProtectDescription(self, code):
return {0: "未保护", 1: "已保护"}.get(code, f"未知代码({code})")
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:
original_text = button.property("original_text")
if original_text:
button.setText(original_text)
button.setEnabled(self.hartComm is not None)
def getButtonForCommand(self, command_name):
if command_name == 'readPrimaryVariableInformation':
return self.readPvInfoButton
elif command_name == 'readOutputInformation':
return self.readOutputInfoButton
elif command_name == 'writePrimaryVariableRange':
return self.setRangeButton
elif command_name == 'writePrimaryVariableDamping':
return self.setDampingButton
elif command_name == 'writePrimaryVariableOutputFunction':
return self.setTransferFnButton
return None