系统诊断增加设备检测
This commit is contained in:
@ -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)
|
||||
@ -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(
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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)
|
||||
|
||||
Reference in New Issue
Block a user