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.

385 lines
16 KiB
Python

1 month ago
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
HART设备配置界面模块
实现HART设备参数配置功能包括查询和修改仪表基本信息标签描述及出厂日期装配号等
"""
import sys
import time
from datetime import datetime
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox,
QLabel, QLineEdit, QPushButton, QGridLayout,
QTabWidget, QMessageBox, QDateEdit, QSpinBox)
from PyQt5.QtCore import Qt, pyqtSignal, QDate
from PyQt5.QtGui import QFont
from protocol.HART import tools
class HartDeviceConfigWidget(QWidget):
"""
HART设备配置界面类
实现HART设备参数配置功能包括查询和修改仪表基本信息标签描述及出厂日期装配号等
"""
def __init__(self):
"""
初始化设备配置界面
"""
super().__init__()
# 初始化HART通信对象
self.hartComm = None
self.connectionWidget = None
# 初始化界面
self.initUI()
def initUI(self):
"""
初始化用户界面
"""
# 创建主布局
self.mainLayout = QVBoxLayout(self)
self.mainLayout.setContentsMargins(10, 10, 10, 10)
# --- 1. 仪表基本信息区域 ---
infoGroup = QGroupBox("仪表基本信息")
infoLayout = QGridLayout(infoGroup)
self.messageLabel = QLabel("设备消息:")
self.messageEdit = QLineEdit()
self.messageEdit.setMaxLength(32)
infoLayout.addWidget(self.messageLabel, 0, 0)
infoLayout.addWidget(self.messageEdit, 0, 1)
self.queryBasicInfoBtn = QPushButton("查询信息")
self.updateBasicInfoBtn = QPushButton("更新信息")
infoButtonLayout = QHBoxLayout()
infoButtonLayout.addWidget(self.queryBasicInfoBtn)
infoButtonLayout.addWidget(self.updateBasicInfoBtn)
self.mainLayout.addWidget(infoGroup)
self.mainLayout.addLayout(infoButtonLayout)
# --- 2. 仪表标签与描述区域 ---
tagGroup = QGroupBox("仪表标签与描述")
tagLayout = QGridLayout(tagGroup)
self.tagLabel = QLabel("设备标签:")
self.tagEdit = QLineEdit()
self.tagEdit.setMaxLength(8)
tagLayout.addWidget(self.tagLabel, 0, 0)
tagLayout.addWidget(self.tagEdit, 0, 1)
self.descLabel = QLabel("设备描述:")
self.descEdit = QLineEdit()
self.descEdit.setMaxLength(16)
tagLayout.addWidget(self.descLabel, 1, 0)
tagLayout.addWidget(self.descEdit, 1, 1)
self.dateLabel = QLabel("出厂日期:")
self.dateEdit = QDateEdit()
self.dateEdit.setDisplayFormat("dd/MM/yyyy")
self.dateEdit.setCalendarPopup(True)
self.dateEdit.setDate(QDate.currentDate())
tagLayout.addWidget(self.dateLabel, 2, 0)
tagLayout.addWidget(self.dateEdit, 2, 1)
self.queryTagDescBtn = QPushButton("查询信息")
self.updateTagDescBtn = QPushButton("更新信息")
tagButtonLayout = QHBoxLayout()
tagButtonLayout.addWidget(self.queryTagDescBtn)
tagButtonLayout.addWidget(self.updateTagDescBtn)
self.mainLayout.addWidget(tagGroup)
self.mainLayout.addLayout(tagButtonLayout)
# --- 3. 仪表装配号区域 ---
assemblyGroup = QGroupBox("仪表装配号")
assemblyLayout = QGridLayout(assemblyGroup)
self.assemblyLabel = QLabel("装配号:")
self.assemblyEdit = QSpinBox()
self.assemblyEdit.setRange(0, 16777215)
self.assemblyEdit.setButtonSymbols(QSpinBox.NoButtons)
assemblyLayout.addWidget(self.assemblyLabel, 0, 0)
assemblyLayout.addWidget(self.assemblyEdit, 0, 1)
self.queryAssemblyBtn = QPushButton("查询装配号")
self.updateAssemblyBtn = QPushButton("更新装配号")
assemblyButtonLayout = QHBoxLayout()
assemblyButtonLayout.addWidget(self.queryAssemblyBtn)
assemblyButtonLayout.addWidget(self.updateAssemblyBtn)
self.mainLayout.addWidget(assemblyGroup)
self.mainLayout.addLayout(assemblyButtonLayout)
# --- 4. 轮询地址修改区域 ---
pollingGroup = QGroupBox("轮询地址修改 (仅短帧)")
pollingLayout = QGridLayout(pollingGroup)
self.pollingAddressLabel = QLabel("新轮询地址:")
self.pollingAddressSpinBox = QSpinBox()
self.pollingAddressSpinBox.setRange(0, 63)
pollingLayout.addWidget(self.pollingAddressLabel, 0, 0)
pollingLayout.addWidget(self.pollingAddressSpinBox, 0, 1)
self.updatePollingAddressBtn = QPushButton("更新轮询地址")
pollingButtonLayout = QHBoxLayout()
pollingButtonLayout.addWidget(self.updatePollingAddressBtn)
self.mainLayout.addWidget(pollingGroup)
self.mainLayout.addLayout(pollingButtonLayout)
self.mainLayout.addStretch(1)
# --- 连接所有信号和槽 ---
self.queryBasicInfoBtn.clicked.connect(self.queryBasicInfo)
self.updateBasicInfoBtn.clicked.connect(self.updateBasicInfo)
self.queryTagDescBtn.clicked.connect(self.queryTagDesc)
self.updateTagDescBtn.clicked.connect(self.updateTagDesc)
self.queryAssemblyBtn.clicked.connect(self.queryAssembly)
self.updateAssemblyBtn.clicked.connect(self.updateAssembly)
self.updatePollingAddressBtn.clicked.connect(self.updatePollingAddress)
def setHartComm(self, hartComm):
"""
设置HART通信对象
Args:
hartComm: HART通信对象
"""
self.hartComm = hartComm
def setConnectionWidget(self, widget):
"""设置连接窗口的引用"""
self.connectionWidget = widget
def queryBasicInfo(self):
"""
查询仪表基本信息 - command 12
"""
if not self.hartComm or not self.hartComm.isConnected():
QMessageBox.warning(self, "警告", "设备未连接,请先连接设备")
return
try:
# 发送查询命令
result = self.hartComm.readMessage()
# print(result)
# 处理响应数据
if result:
# 兼容处理如果result是列表则取第一个元素
msg = result[0] if isinstance(result, list) and len(result) > 0 else result
if isinstance(msg, dict):
message_data = msg.get('message')
if isinstance(message_data, bytes):
try:
# 使用新的 unpack_packed_ascii 函数
message_str = tools.unpack_packed_ascii(message_data, 32)
self.messageEdit.setText(message_str.strip())
except Exception as e:
# 如果解码失败,显示原始数据的十六进制表示和错误
message_hex = message_data.hex()
self.messageEdit.setText(f"HEX: {message_hex}")
QMessageBox.critical(self, "解码错误", f"解码消息失败: {e}")
return
QMessageBox.warning(self, "警告", "未能获取设备基本信息")
except Exception as e:
QMessageBox.critical(self, "错误", f"查询设备基本信息失败: {str(e)}")
def updateBasicInfo(self):
"""
修改仪表基本信息 - command 17
"""
if not self.hartComm or not self.hartComm.isConnected():
QMessageBox.warning(self, "警告", "设备未连接,请先连接设备")
return
try:
# 获取消息内容
message_text = self.messageEdit.text()
# 直接将字符串传递给通信函数,由底层负责打包
result = self.hartComm.writeMessage(message_text)
# 处理响应数据
if result and len(result) > 0:
QMessageBox.information(self, "成功", "成功更新设备基本信息")
else:
QMessageBox.warning(self, "警告", "未能更新设备基本信息")
except Exception as e:
QMessageBox.critical(self, "错误", f"更新设备基本信息失败: {str(e)}")
def queryTagDesc(self):
"""
查询仪表标签描述及出厂日期 - command 13
"""
if not self.hartComm or not self.hartComm.isConnected():
QMessageBox.warning(self, "警告", "设备未连接,请先连接设备")
return
try:
# 发送查询命令
result = self.hartComm.readTagDescriptorDate()
# 处理响应数据
if result:
# 兼容处理如果result是列表则取第一个元素
msg = result[0] if isinstance(result, list) and len(result) > 0 else result
if isinstance(msg, dict) and msg.get("status") == "success":
tag_str = msg.get("device_tag_name", "")
desc_str = msg.get("device_descriptor", "")
date_dict = msg.get("date", {})
self.tagEdit.setText(tag_str.strip())
self.descEdit.setText(desc_str.strip())
if isinstance(date_dict, dict) and all(k in date_dict for k in ['day', 'month', 'year']):
day = date_dict.get('day', 1)
month = date_dict.get('month', 1)
year = date_dict.get('year', 1900)
self.dateEdit.setDate(QDate(year, month, day))
QMessageBox.information(self, "成功", "成功读取设备标签、描述及日期信息")
return
elif isinstance(msg, dict) and 'error' in msg:
QMessageBox.warning(self, "查询失败", f"未能获取设备标签、描述及日期信息: {msg.get('error')}")
return
QMessageBox.warning(self, "警告", "未能获取设备标签、描述及日期信息")
except Exception as e:
QMessageBox.critical(self, "错误", f"查询设备标签、描述及日期信息失败: {str(e)}")
def updateTagDesc(self):
"""
修改仪表标签描述及出厂日期 - command 18
"""
if not self.hartComm or not self.hartComm.isConnected():
QMessageBox.warning(self, "警告", "设备未连接,请先连接设备")
return
try:
# 获取标签和描述
tag_text = self.tagEdit.text()
descriptor_text = self.descEdit.text()
date = self.dateEdit.date()
# According to the spec, year should be (actual_year - 1900)
year_for_protocol = date.year() - 1900
if not (0 <= year_for_protocol <= 255):
QMessageBox.warning(self, "年份错误", "年份必须在1900年至2155年之间")
return
date_tuple = (date.day(), date.month(), year_for_protocol)
# 直接将字符串传递给通信函数,由底层负责打包
result = self.hartComm.writeTagDescriptorDate(tag_text, descriptor_text, date_tuple)
# 处理响应数据
if result and len(result) > 0:
QMessageBox.information(self, "成功", "成功更新设备标签、描述及日期信息")
else:
QMessageBox.warning(self, "警告", "未能更新设备标签、描述及日期信息")
except Exception as e:
QMessageBox.critical(self, "错误", f"更新设备标签、描述及日期信息失败: {str(e)}")
def queryAssembly(self):
"""
查询仪表装配号 - command 16
"""
if not self.hartComm or not self.hartComm.isConnected():
QMessageBox.warning(self, "警告", "设备未连接,请先连接设备")
return
try:
# 发送查询命令
result = self.hartComm.readFinalAssemblyNumber()
# 处理响应数据
if result:
# 兼容处理如果result是列表则取第一个元素
msg = result[0] if isinstance(result, list) and len(result) > 0 else result
if isinstance(msg, dict) and msg.get("status") == "success":
assembly_no = msg.get("final_assembly_no", 0)
self.assemblyEdit.setValue(assembly_no)
QMessageBox.information(self, "成功", f"成功读取设备装配号: {assembly_no}")
return
elif isinstance(msg, dict) and 'error' in msg:
QMessageBox.warning(self, "查询失败", f"未能获取设备装配号: {msg.get('error')}")
return
QMessageBox.warning(self, "警告", "未能获取设备装配号")
except Exception as e:
QMessageBox.critical(self, "错误", f"查询设备装配号失败: {str(e)}")
def updateAssembly(self):
"""
修改仪表装配号 - command 19
"""
if not self.hartComm or not self.hartComm.isConnected():
QMessageBox.warning(self, "警告", "设备未连接,请先连接设备")
return
try:
# 获取装配号
assembly_number = self.assemblyEdit.value()
# 发送更新命令
result = self.hartComm.writeFinalAssemblyNumber(assembly_number)
# 处理响应数据
if result and len(result) > 0:
QMessageBox.information(self, "成功", "成功更新设备装配号")
else:
QMessageBox.warning(self, "警告", "未能更新设备装配号")
except Exception as e:
QMessageBox.critical(self, "错误", f"更新设备装配号失败: {str(e)}")
def updatePollingAddress(self):
"""
修改设备轮询地址 - command 6
"""
if not self.hartComm or not self.hartComm.isConnected():
QMessageBox.warning(self, "警告", "设备未连接,请先连接设备")
return
try:
new_address = self.pollingAddressSpinBox.value()
current_address_bytes = self.hartComm.getCurrentAddress()
if current_address_bytes and len(current_address_bytes) == 1:
current_address = current_address_bytes[0] & 0x3F
if new_address == current_address:
QMessageBox.information(self, "提示", "新地址与当前地址相同,无需修改。")
return
reply = QMessageBox.question(self, '确认修改',
f"确定要将设备的轮询地址修改为 {new_address} 吗?\n"
"修改成功后将使用新地址自动重新连接。",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
result = self.hartComm.write_polling_address(new_address)
if result:
QMessageBox.information(self, "成功", f"成功将轮询地址修改为 {new_address}\n现在将使用新地址重新连接。")
if self.connectionWidget:
self.connectionWidget.disconnectDevice(silent=True)
self.connectionWidget.pollingAddressSpinBox.setValue(new_address)
self.connectionWidget.connectDevice()
else:
QMessageBox.warning(self, "警告", "未能更新轮询地址。")
except ValueError as ve:
QMessageBox.critical(self, "地址错误", f"轮询地址设置错误: {str(ve)}")
except Exception as e:
QMessageBox.critical(self, "错误", f"更新轮询地址失败: {str(e)}")