系统诊断增加设备检测

This commit is contained in:
2026-01-11 18:00:32 +08:00
parent f860c5a216
commit b40ea0112a
13 changed files with 537 additions and 247 deletions

View File

@ -8,6 +8,8 @@ from view.widgets.message_popup_widget import MessagePopupWidget
from service.msg_recorder import MessageRecorder
from service.msg_query_thread import MsgQueryThread
from service.device_monitor_thread import DeviceMonitorThread
"""
控制主界面底部的所有按钮, 包括系统诊断、系统中心等的行为。
以及 版本信息相关弹窗, 如: v1.0
@ -20,11 +22,11 @@ class BottomControlController:
# 系统诊断弹窗
self.system_diagnostics_dialog = SystemDiagnosticsDialog(self.main_window)
self._init_system_diagnostics_dialog_hide_animations()
self.current_diagnostics_row = 0 # 系统诊断弹窗的行号从0开始
self.current_diagnostics_col = 0 # 系统诊断弹窗的列号从0开始
# 系统中心弹窗
self.system_center_dialog = SystemCenterDialog(self.main_window)
self._init_system_center_dialog_hide_animations()
# ===================== 消息列表相关 ====================================
# 系统状态消息列表控件
@ -40,6 +42,11 @@ class BottomControlController:
self.msg_query_thread.start() # 启动线程
# =======================================================================
# ===================== 设备检测(系统诊断相关) ====================================
self.device_monitor = DeviceMonitorThread()
self.device_monitor.start()
# =======================================================================
# 绑定主界面底部按钮的信号(如:系统诊断按钮等,点击触发弹窗)
self._bind_buttons()
# 绑定弹窗中按钮的信号
@ -51,20 +58,17 @@ class BottomControlController:
# 清空当前消息列表(避免重复)
target_widget = self.status_msg_widget if message_type == 1 else self.warning_msg_widget
target_widget.list_widget.clear()
# target_widget.messages.clear()
# 添加新消息 (第三个为 create_time, 第四个为 last_modified)
for content, is_processed, create_time, _ in messages:
# 从create_time中提取时分秒
# time_part = create_time.split()[1] # 空格分隔之后的[1]为时分秒
# 需要添加到消息列表的消息格式为 时分秒 + 消息原始内容
# msg_list_content = f"{create_time} {content}"
target_widget.add_message(content, is_processed = bool(is_processed), msg_time = create_time)
def stop_threads(self):
"""停止当前控制器中的所有线程"""
if hasattr(self, 'msg_query_thread') and self.msg_query_thread.isRunning():
self.msg_query_thread.stop()
if hasattr(self, 'device_monitor') and self.device_monitor.isRunning():
self.device_monitor.stop_thread()
def _bind_buttons(self):
# 底部系统中心按钮 → 触发弹窗显示/隐藏
@ -78,6 +82,9 @@ class BottomControlController:
# 底部预警消息列表按钮 → 触发预警消息列表显示/隐藏
self.bottom_control_widget.warning_list_btn.clicked.connect(self.toggle_system_warning_msg_list)
# 设备检测结果显示(底部的系统诊断按钮处显示)
self.device_monitor.state_result.connect( self.bottom_control_widget.set_system_status, Qt.QueuedConnection)
def _bind_dialog_signals(self):
"""绑定弹窗按钮的信号"""
@ -86,45 +93,17 @@ class BottomControlController:
self.system_center_dialog.data_center_clicked.connect(self.handle_data_center)
self.system_center_dialog.user_center_clicked.connect(self.handle_user_center)
def _init_system_center_dialog_hide_animations(self):
"""初始化系统中心弹窗隐藏动画(淡出+缩小,与显示动画对应)"""
# 1. 淡出动画
self.hide_opacity_anim = QPropertyAnimation(self.system_center_dialog, b"windowOpacity", self.system_center_dialog)
self.hide_opacity_anim.setDuration(200)
self.hide_opacity_anim.setStartValue(1.0)
self.hide_opacity_anim.setEndValue(0.0)
# 2. 缩小动画
self.hide_scale_anim = QPropertyAnimation(self.system_center_dialog, b"geometry", self.system_center_dialog)
self.hide_scale_anim.setDuration(200)
self.hide_scale_anim.setEasingCurve(QEasingCurve.InBack) # 收缩感
# 3. 组合动画(父对象设为弹窗)
self.hide_anim_group = QParallelAnimationGroup(self.system_center_dialog)
self.hide_anim_group.addAnimation(self.hide_opacity_anim)
self.hide_anim_group.addAnimation(self.hide_scale_anim)
# 动画结束后,强制隐藏弹窗
self.hide_anim_group.finished.connect(self.system_center_dialog.hide)
# 系统诊断弹窗的信号
self.device_monitor.check_finished.connect(self.reset_diagnostics_row_col) # 重置系统诊断的行号和列号
self.device_monitor.connect_success.connect(self._handle_diagnostics_connect_success) # 设备正常
self.device_monitor.connect_failed.connect(self._handle_diagnostics_connect_failed) # 设备异常
# ------------------- 系统中心弹窗逻辑-------------------
def toggle_system_center_dialog(self):
"""切换系统中心弹窗的显示/隐藏状态"""
if self.system_center_dialog.isVisible():
# 已显示 → 隐藏
# self.system_center_dialog.hide()
# 动态设置缩小动画的起点和终点(基于当前弹窗位置)
current_geo = self.system_center_dialog.geometry()
end_rect = QRect(
current_geo.center().x() - current_geo.width() * 0.4,
current_geo.center().y() - current_geo.height() * 0.4,
int(current_geo.width() * 0.8),
int(current_geo.height() * 0.8)
)
self.hide_scale_anim.setStartValue(current_geo)
self.hide_scale_anim.setEndValue(end_rect)
# 启动隐藏动画 (隐藏动画结束,自动隐藏 系统中心弹窗)
self.hide_anim_group.start()
self.system_center_dialog.hide()
else:
# 未显示 → 计算位置并显示
self._calc_system_center_dialog_position() # 每次显示都计算位置
@ -143,7 +122,7 @@ class BottomControlController:
# 计算弹窗坐标
btn_width = btn.width()
dialog_size = self.system_center_dialog.size()
dialog_x = btn_pos_rel_main.x() + (btn_width - dialog_size.width()) // 2
dialog_x = btn_pos_rel_main.x() + (btn_width - dialog_size.width()) // 2
dialog_y = btn_pos_rel_main.y() - dialog_size.height()
# 设置弹窗位置
@ -166,86 +145,121 @@ class BottomControlController:
# print("执行用户中心逻辑:如切换用户、修改密码等")
# ------------------- 系统诊断弹窗逻辑-------------------
def _init_system_diagnostics_dialog_hide_animations(self):
"""初始化系统诊断弹窗隐藏动画(与显示动画反向:滑出+淡出)"""
# 1. 淡出动画(与显示动画时长一致)
self.dia_hide_opacity_anim = QPropertyAnimation(
self.system_diagnostics_dialog, b"windowOpacity", self.system_diagnostics_dialog
)
self.dia_hide_opacity_anim.setDuration(300) # 显示动画为400ms
self.dia_hide_opacity_anim.setStartValue(1.0)
self.dia_hide_opacity_anim.setEndValue(0.0)
# 2. 位置动画从当前位置滑出到下方100px与显示动画反向
self.dia_hide_pos_anim = QPropertyAnimation(
self.system_diagnostics_dialog, b"geometry", self.system_diagnostics_dialog
)
self.dia_hide_pos_anim.setDuration(300)
self.dia_hide_pos_anim.setEasingCurve(QEasingCurve.InQuart) # 滑出曲线与显示反向
# 3. 组合动画(同时执行滑出和淡出)
self.dia_hide_anim_group = QParallelAnimationGroup(self.system_diagnostics_dialog)
self.dia_hide_anim_group.addAnimation(self.dia_hide_opacity_anim)
self.dia_hide_anim_group.addAnimation(self.dia_hide_pos_anim)
# 动画结束后强制隐藏弹窗
self.dia_hide_anim_group.finished.connect(self.system_diagnostics_dialog.hide)
def toggle_system_diagnostics_dialog(self):
"""切换系统诊断弹窗的显示/隐藏状态"""
if self.system_diagnostics_dialog.isVisible():
# 已显示 → 执行隐藏动画
self._start_diagnostics_hide_animation()
# 已显示 → 执行隐藏
self.system_diagnostics_dialog.hide()
else:
# 未显示 → 计算位置并显示(触发显示动画)
# 立即执行设备检测
self.device_monitor.force_immediate_check()
# 未显示 → 计算位置并显示
self._calc_system_diagnostics_dialog_position()
self.system_diagnostics_dialog.show()
def _calc_system_diagnostics_dialog_position(self):
"""计算系统诊断弹窗位置(显示在系统诊断按钮上方)"""
btn = self.bottom_control_widget.diagnosis_btn # 诊断按钮
bottom_widget = self.bottom_control_widget
btn_pos = btn.mapToGlobal(QPoint(0, 0))
dialog_x = btn_pos.x()
dialog_y = btn_pos.y() - self.system_diagnostics_dialog.height()
# 计算按钮相对于主窗口的位置
bottom_pos_rel_main = bottom_widget.pos()
btn_pos_rel_bottom = btn.pos()
btn_pos_rel_main = bottom_pos_rel_main + btn_pos_rel_bottom
# 设置弹窗位置(动画会基于此位置执行滑入效果)
# 计算弹窗坐标
dialog_size = self.system_diagnostics_dialog.size()
dialog_x = btn_pos_rel_main.x()
dialog_y = btn_pos_rel_main.y() - dialog_size.height()
# 设置弹窗位置
self.system_diagnostics_dialog.move(dialog_x, dialog_y)
def _start_diagnostics_hide_animation(self):
"""启动系统诊断弹窗的隐藏动画(滑出+淡出)"""
current_geo = self.system_diagnostics_dialog.geometry() # 当前位置和尺寸
# 计算隐藏动画终点当前位置下方100px与显示动画起点对应
end_rect = QRect(
current_geo.x(),
current_geo.y() + 100, # 向下滑出100px
current_geo.width(),
current_geo.height()
)
# 设置动画参数并启动
self.dia_hide_pos_anim.setStartValue(current_geo)
self.dia_hide_pos_anim.setEndValue(end_rect)
self.dia_hide_anim_group.start()
def get_diagnostics_row_col(self):
"""获取系统诊断弹窗的 行号 和 列号, 都从0开始"""
diagnostics_row = self.current_diagnostics_row
diagnostics_col = self.current_diagnostics_col
self.current_diagnostics_col += 1
if self.current_diagnostics_col == self.system_diagnostics_dialog.max_col:
self.current_diagnostics_row += 1
self.current_diagnostics_col = 0
if self.current_diagnostics_row == self.system_diagnostics_dialog.max_row:
self.current_diagnostics_row = 0
return diagnostics_row, diagnostics_col
def reset_diagnostics_row_col(self):
"""重置系统诊断弹窗的 行号 和 列号 为0"""
self.current_diagnostics_row = 0
self.current_diagnostics_col = 0
def _handle_diagnostics_connect_success(self, device_name:str, delay:int):
"""处理系统诊断弹窗: 设备连接成功 (设备检测正常)"""
row, col = self.get_diagnostics_row_col()
self.system_diagnostics_dialog.set_selected_device(row, col, device_name)
self.system_diagnostics_dialog.set_ms_value(row, col, delay)
if delay <= self.device_monitor.warning_delay:
self.system_diagnostics_dialog.set_circle_status(row, col, "normal")
else:
self.system_diagnostics_dialog.set_circle_status(row, col, "warning")
def _handle_diagnostics_connect_failed(self, device_name:str):
"""处理系统诊断弹窗: 设备检测异常"""
row, col = self.get_diagnostics_row_col()
self.system_diagnostics_dialog.set_selected_device(row, col, device_name)
self.system_diagnostics_dialog.set_ms_value(row, col, -1)
self.system_diagnostics_dialog.set_circle_status(row, col, "error")
# ================== 系统状态消息列表: 显示系统消息 ===================
def toggle_system_status_msg_list(self):
"""系统状态消息按钮点击之后 显示系统消息"""
"""切换系统状态消息弹窗的显示/隐藏状态"""
if not self.status_msg_widget.isVisible():
btn_pos = self.bottom_control_widget.status_msg_btn.mapToGlobal(QPoint(0, 0))
popup_x = btn_pos.x()
popup_y = btn_pos.y() - self.status_msg_widget.height()
self.status_msg_widget.move(popup_x, popup_y)
self._calc_system_status_msg_position()
self.status_msg_widget.show()
else:
self.status_msg_widget.close()
self.status_msg_widget.hide()
def _calc_system_status_msg_position(self):
"""计算系统状态消息弹窗位置, 并move移动"""
btn = self.bottom_control_widget.status_msg_btn
bottom_widget = self.bottom_control_widget
# 计算按钮相对于主窗口的位置
bottom_pos_rel_main = bottom_widget.pos()
btn_pos_rel_bottom = btn.pos()
btn_pos_rel_main = bottom_pos_rel_main + btn_pos_rel_bottom
# 计算弹窗坐标
dialog_size = self.status_msg_widget.size()
dialog_x = btn_pos_rel_main.x()
dialog_y = btn_pos_rel_main.y() - dialog_size.height()
# 设置弹窗位置
self.status_msg_widget.move(dialog_x, dialog_y)
# ================== 预警消息列表: 显示预警消息 ===================
def toggle_system_warning_msg_list(self):
"""预警消息列表按钮点击之后 显示预警消息"""
"""切换预警消息列表弹窗的显示/隐藏状态"""
if not self.warning_msg_widget.isVisible():
btn_pos = self.bottom_control_widget.warning_list_btn.mapToGlobal(QPoint(0, 0))
popup_x = btn_pos.x()
popup_y = btn_pos.y() - self.warning_msg_widget.height()
self.warning_msg_widget.move(popup_x, popup_y)
self._calc_system_warning_msg_position()
self.warning_msg_widget.show()
else:
self.warning_msg_widget.close()
self.warning_msg_widget.hide()
def _calc_system_warning_msg_position(self):
"""计算预警消息列表弹窗位置, 并move移动"""
btn = self.bottom_control_widget.warning_list_btn
bottom_widget = self.bottom_control_widget
# 计算按钮相对于主窗口的位置
bottom_pos_rel_main = bottom_widget.pos()
btn_pos_rel_bottom = btn.pos()
btn_pos_rel_main = bottom_pos_rel_main + btn_pos_rel_bottom
# 计算弹窗坐标
dialog_size = self.warning_msg_widget.size()
dialog_x = btn_pos_rel_main.x()
dialog_y = btn_pos_rel_main.y() - dialog_size.height()
# 设置弹窗位置
self.warning_msg_widget.move(dialog_x, dialog_y)

View File

@ -13,10 +13,6 @@ class CameraController:
cam1_config = load_camera_config("上位料斗")
cam2_config = load_camera_config("下位料斗")
cam3_config = load_camera_config("模具车")
# print("rtsp_url1:", cam1_config["rtsp_url"])
# print("rtsp_url2:", cam2_config["rtsp_url"])
# print("rtsp_url3:", cam3_config["rtsp_url"])
# 设置 camera_urls
self.video_view.set_camera_urls(

View File

@ -4,6 +4,15 @@ from hardware.transmitter import TransmitterController
from hardware.relay import RelayController
from view.widgets.hopper_widget import HopperWidget
from view.widgets.conveyor_system_widget import ConveyorSystemWidget
from enum import Enum
class UpperHopperPosition(Enum):
"""上料斗位置
- MIXING_TOWER: 搅拌楼处,对应数值 66
- VIBRATION_CHAMBER: 振捣室处,对应数值 5
"""
MIXING_TOWER = 66 # 搅拌楼处
VIBRATION_CHAMBER = 5 # 振捣室处
# 信号类:后台线程向主线程传递数据
class HopperSignals(QObject):
@ -18,30 +27,30 @@ class HopperController:
# 下料斗夹爪测试用例数据
# 注意:目前只控制 下料斗的夹爪角度变化
self.angle = 10 # 夹爪当前角度
self.max_angle = 60 # 夹爪最大张开角度
self.min_angle = 10 # 夹爪最小张开角度
self.angle = 10.33 # 夹爪当前角度
self.max_angle = 60.00 # 夹爪最大张开角度
self.min_angle = 10.00 # 夹爪最小张开角度
self.is_add = True # 角度增加/减小 控制
self.timer_angle = QTimer() # 角度更新定时器
self.timer_angle.setInterval(1000) # 1秒更新一次角度
# 重量读取定时器
self.timer_weight = QTimer() # 重量读取定时器
self.timer_weight.setInterval(2000) # 每2秒读取一次重量
# self.timer_weight = QTimer() # 重量读取定时器
# self.timer_weight.setInterval(2000) # 每2秒读取一次重量
# 绑定信号
self._connect_signals()
# 开启定时器
self.timer_angle.start()
self.timer_weight.start()
# self.timer_weight.start()
def _connect_signals(self):
# 更新上料斗重量
self.signals.upper_weight_updated.connect(self.onUpdateUpperHopperWeight)
# 上料斗重量定时读取
self.timer_weight.timeout.connect(self.handleReadUpperHopperWeight)
# self.timer_weight.timeout.connect(self.handleReadUpperHopperWeight)
# 下料斗夹爪定时更新
self.timer_angle.timeout.connect(self.handleLowerClampAngleUpdate)
@ -62,9 +71,9 @@ class HopperController:
"""处理下料斗夹爪开合"""
# 角度增减逻辑
if self.is_add:
self.angle += 1
self.angle += 1.22
else:
self.angle -= 1
self.angle -= 1.33
# 边界控制
if self.angle > self.max_angle:
@ -118,7 +127,35 @@ class HopperController:
# 上料斗 夹爪 "开"按钮点击
print("hopper_controller: onUpperClampOpenBottonClicked")
# 测试上料斗夹爪6秒打开60度
self.hopper_view.upper_clamp_widget.testAnimation(target_angle=60, duration=6)
self.hopper_view.upper_clamp_widget.testAnimation(target_angle=60, duration=10)
@Slot(int)
def onUpdateUpperClampStatus(self, status:int):
# 上料斗夹爪状态 1表示打开0表示关闭
if status:
# 执行上料斗夹爪打开动画
self.hopper_view.upper_clamp_widget.testAnimation(target_angle=60, duration=10)
else:
# 执行上料斗夹爪关闭动画
self.hopper_view.upper_clamp_widget.testAnimation(target_angle=0, duration=10)
def onUpdateUpperHopperPosition(self, position:int):
# 上料斗位置
if position == UpperHopperPosition.MIXING_TOWER.value: # 上料斗到达搅拌楼值为66
# 上料斗在搅拌楼下
self.hopper_view.hideUpperHopper() # 隐藏非传送带处的上料斗
self.conveyor_view.moveHopperBelowMixer() # 传送带处的上料斗移动到搅料楼下
self.conveyor_view.showHopper() # 显示传送带处的上料斗
elif position == UpperHopperPosition.VIBRATION_CHAMBER.value: # 上料斗就绪到达振捣室值为5
# 上料斗在振捣室处
self.conveyor_view.hideHopper() # 隐藏传送带处的上料斗
self.hopper_view.upper_clamp_widget.set_angle(0) # 上料斗夹爪角度设置为0此时上料斗一定是关闭的
self.hopper_view.showUpperHopper() # 显示非传送带处的上料斗
else:
# 上料斗在途中
self.hopper_view.hideUpperHopper() # 隐藏非传送带处的上料斗 (下料斗处对应的上料斗叫非传送带处上料斗)
self.conveyor_view.moveHopperToTransition() # 传送带处上料斗移动到中间过渡位置
self.conveyor_view.showHopper() # 显示传送带处的上料斗
@Slot(bool)
def onUpperArchBreaking(self, status:bool):

View File

@ -1,4 +1,4 @@
from PySide6.QtCore import QTimer, Signal, QObject # 导入Qt核心类
from PySide6.QtCore import QTimer, Signal, QObject, Qt
from PySide6.QtWidgets import QApplication # 用于获取主线程
import threading
from hardware import transmitter
@ -6,8 +6,16 @@ from view.main_window import MainWindow
from .camera_controller import CameraController
from .bottom_control_controller import BottomControlController
from .hopper_controller import HopperController
from .hopper_controller import UpperHopperPosition
from service.msg_recorder import MessageRecorder
from common.constant_config_manager import ConfigManager
from service.opcua_ui_client import OpcuaUiClient
from service.artifact_query_thread import ArtifactInfoQueryThread # 管片任务查询
from busisness.models import ArtifactInfoModel
from typing import List
class MainController:
def __init__(self):
@ -15,18 +23,29 @@ class MainController:
self.main_window = MainWindow()
self.msg_recorder = MessageRecorder()
self.msg_recorder.normal_record("开始自动智能浇筑系统")
self.msg_recorder.normal_record("开始自动智能浇筑系统") # 记录系统状态消息
# 初始化子界面和控制器
self._initSubViews()
self._initSubControllers()
# 加载配置管理器
self.config_manager = ConfigManager()
# 启动消息数据库(messages.db)定时清理任务
self.start_msg_database_clean_task()
self.MSG_CLEAN_INTERVAL = None
# opcua客户端
self.opc_client = OpcuaUiClient()
self._start_opc_client()
# 连接信号
self.__connectSignals()
def showMainWindow(self):
self.main_window.showFullScreen()
# self.main_window.show()
# self.main_window.showFullScreen()
self.main_window.show()
self.main_window.dispatch_task_widget.set_task_time("task1","15:44 PM")
self.main_window.dispatch_task_widget.set_task_time("task2","17:37 PM")
self.main_window.segment_task_widget.set_task_time("task1","15:38 PM")
@ -58,9 +77,178 @@ class MainController:
def __connectSignals(self):
self.main_window.about_to_close.connect(self.handleMainWindowClose) # 处理主界面关闭
self.config_manager.msg_clean_interval_changed.connect(self.onMsgDbCleanIntervalChanged) # 消息清理间隔改变
self.opc_client.opc_signal.value_changed.connect(self._update_opc_value_to_ui, Qt.QueuedConnection) # opcua服务器值改变
def handleMainWindowClose(self):
"""主界面关闭"""
self.msg_recorder.normal_record("关闭自动智能浇筑系统")
self.msg_recorder.normal_record("关闭自动智能浇筑系统") # 记录系统状态消息
# 停止系统底部控制器中的线程
if hasattr(self, 'bottom_control_controller'):
self.bottom_control_controller.stop_threads()
self.bottom_control_controller.stop_threads()
# 停止opc客户端
if hasattr(self, 'opc_client'):
self._stop_opc_client()
def start_msg_database_clean_task(self):
"""启动清理消息数据库(messages.db)中过期消息的定时任务"""
from PySide6.QtCore import QTimer, QDateTime, QDate, QTime
self.MSG_CLEAN_INTERVAL = self.config_manager.get_clean_interval() # 获取消息清理间隔 (单位: 秒)
def clean_task():
from common.msg_db_helper import DBHelper
DBHelper().clean_expired_messages(days_to_keep=self.config_manager.get_msg_keep_days())
def start_weekly_clean_at_midnight():
# 1. 计算当前时间到下一个凌晨0点的毫秒数
now = QDateTime.currentDateTime()
tomorrow = QDate.currentDate().addDays(1) # 明天
next_midnight = QDateTime(tomorrow, QTime(0, 0, 0)) # 明天00:00:00
msecs_to_midnight = now.msecsTo(next_midnight) # 距离下一个凌晨的毫秒数
# 2. 首次触发:到零点后执行一次清理
clean_timer = QTimer()
clean_timer.timeout.connect(clean_task)
single_shot_timer = QTimer()
single_shot_timer.setSingleShot(True)
def first_clean():
clean_task() # 执行首次清理
# 之后每隔CLEAN_INTERVAL触发一次消息清理
clean_timer.start(self.MSG_CLEAN_INTERVAL)
# 启动一次性定时器, 进行首次清理
single_shot_timer.timeout.connect(first_clean)
single_shot_timer.start(msecs_to_midnight)
return clean_timer, single_shot_timer
# 启动定时器,并保存引用
self.msg_db_clean_timer, self.single_shot_timer = start_weekly_clean_at_midnight()
def onMsgDbCleanIntervalChanged(self, new_interval):
"""当消息数据清理间隔变化时,更新定时器"""
if self.MSG_CLEAN_INTERVAL == new_interval:
return # 清理间隔未变
if hasattr(self, 'single_shot_timer') and self.single_shot_timer is not None:
self.single_shot_timer.stop()
self.single_shot_timer.deleteLater()
self.single_shot_timer = None
if hasattr(self, 'msg_db_clean_timer') and self.msg_db_clean_timer is not None:
self.msg_db_clean_timer.stop() # 停止周期性触发消息清理
self.msg_db_clean_timer.deleteLater()
self.msg_db_clean_timer = None
# 用新间隔重新启动清理任务
self.start_msg_database_clean_task()
def _update_opc_value_to_ui(self, node_id, var_name, new_value):
"""
OPCUA值变化时的UI更新函数
"""
try:
if var_name == "upper_weight":
# 更新上料斗重量
self.hopper_controller.onUpdateUpperHopperWeight(new_value)
elif var_name == "lower_weight":
# 更新下料斗重量
self.hopper_controller.onUpdateLowerHopperWeight(new_value)
elif var_name == "upper_volume":
# 更新上料斗方量
self.hopper_controller.onUpdateUpperHopperVolume(new_value)
elif var_name == "production_progress":
progress = min(new_value, 100) # 限制为100, 进度为去掉百分号之后的整数
self.main_window.arc_progress.setProgress(progress)
self.main_window.production_progress.setProgress(progress)
elif var_name == "lower_clamp_angle":
# 更新下料斗夹爪角度
self.hopper_controller.onUpdateLowerClampAngle(new_value)
elif var_name == "upper_clamp_status":
# 更新上料斗夹爪状态 0表示关闭 1表示打开
self.hopper_controller.onUpdateUpperClampStatus(new_value)
elif var_name == "upper_hopper_position":
# 更新上料斗位置 5表示料斗到位到达振捣室处 66表示在搅拌楼处
self.hopper_controller.onUpdateUpperHopperPosition(new_value)
if new_value == UpperHopperPosition.MIXING_TOWER.value:
# 到达搅拌楼开启搅拌桨旋转
self.main_window.mixer_widget.startBladeMix()
else:
self.main_window.mixer_widget.stopBladeMix()
elif var_name == "update_segment_tasks":
need_update = new_value
if need_update: # 需要更新管片任务
self._update_segment_tasks()
except Exception as e:
print(f"_update_opc_value_to_ui: 界面更新失败: {e}")
import traceback
traceback.print_exc()
def _start_opc_client(self):
"""启动OPC UA客户端"""
import time
self.opc_retry_exit = threading.Event()
def opc_worker():
# 连接服务器
while not self.opc_retry_exit.is_set():
if self.opc_client.connect():
# 创建订阅
self.opc_client.create_multi_subscription(interval=500)
break # 连接成功,退出重连
time.sleep(2)
# 启动子线程运行OPCUA逻辑
opc_thread = threading.Thread(target=opc_worker, daemon=True)
opc_thread.start()
def _stop_opc_client(self):
"""停止OPC UA客户端"""
if hasattr(self, 'opc_retry_exit'):
self.opc_retry_exit.set()
if hasattr(self, 'opc_client') and self.opc_client.connected:
self.opc_client.disconnect()
def _update_segment_tasks(self):
"""更新左侧的管片任务"""
# 1. 管片信息查询线程
query_thread = ArtifactInfoQueryThread()
# 2. 主线程更新管片任务UI
query_thread.query_finished.connect(self.onUpdateUiByArtifactInfo)
# 3. 查询管片信息错误
query_thread.query_error.connect(self.onQueryArtifactInfoError)
query_thread.start()
def onUpdateUiByArtifactInfo(self, artifact_list:List[ArtifactInfoModel]):
def convert_to_ampm(time_str: str) -> str:
"""时间格式转换: 转换为AM/PM形式"""
from datetime import datetime
time_formats = [
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d %H:%M:%S.%f"
]
for fmt in time_formats:
try:
dt = datetime.strptime(time_str, fmt)
return dt.strftime("%I:%M%p")
except ValueError:
continue
return "--:--"
for index, artifact in enumerate(artifact_list, 1):
if artifact.MouldCode:
self.main_window.segment_task_widget.set_task_id(f"task{index}", artifact.MouldCode) # 模具号
if artifact.BetonVolume:
self.main_window.segment_task_widget.set_task_volume(f"task{index}", artifact.BetonVolume) # 浇筑方量
if artifact.BeginTime:
time_str = convert_to_ampm(artifact.BeginTime)
self.main_window.segment_task_widget.set_task_time(f"task{index}", time_str) # 开始时间
def onQueryArtifactInfoError(self, error_msg:str):
# 查询管片信息失败预警
self.msg_recorder.warning_record(error_msg)