系统诊断增加设备检测

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

@ -80,6 +80,25 @@ class DBHelper:
messages = cursor.fetchall() # 结果:[(content, is_processed, create_time, last_modified), ...]
conn.close()
return messages[::-1] # 反转后按时间正序排列
def clean_expired_messages(self, days_to_keep=30):
"""删除超过指定天数(默认30天, 一个月)的过期消息,并压缩数据库"""
from datetime import datetime, timedelta
# 计算过期时间(当前时间 - days_to_keep天
expire_time = (datetime.now() - timedelta(days=days_to_keep)).strftime("%Y-%m-%d %H:%M:%S")
conn = sqlite3.connect(self.db_name)
cursor = conn.cursor()
# 删除过期消息
cursor.execute('''
DELETE FROM messages
WHERE create_time < ?
''', (expire_time,))
conn.commit()
# 压缩数据库,释放删除后的空闲空间
cursor.execute("VACUUM")
conn.commit()
conn.close()
if __name__ == "__main__":

View File

@ -1,22 +1,22 @@
# camera_config.ini
# 相关的摄像头的配置文件
[上位料斗]
ip = 192.168.250.60
ip = 192.168.250.61
port = 554
username = admin
password = XJ123456
channel = 101
channel = 102
[下位料斗]
ip = 192.168.250.61
port = 554
username = admin
password = XJ123456
channel = 101
channel = 102
[模具车]
ip = 192.168.250.61
ip = 192.168.250.60
port = 554
username = admin
password = XJ123456
channel = 101
channel = 102

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)

View File

@ -332,6 +332,7 @@ class MainWindow(QWidget):
def closeEvent(self, e):
"""窗口关闭时的回调"""
self.hide() # 隐藏界面
self.about_to_close.emit()
super().closeEvent(e)

View File

@ -140,7 +140,7 @@ class HopperWidget(QWidget):
self.upper_weight_label.move(outer_width//2 - 60, outer_height//2 - 46)
# 额外文字(上位)
self.upper_extra_label = QLabel("2.0方(预估)", self.upper_bg_widget)
self.upper_extra_label = QLabel("2.00", self.upper_bg_widget)
self.upper_extra_label.setAlignment(Qt.AlignCenter)
# #262c38 #16ffff #131427 #003669
self.upper_extra_label.setStyleSheet("background: none; background-color: #003669; color: #16ffff; font-size: 18px;")
@ -359,7 +359,8 @@ class HopperWidget(QWidget):
"""Args:
volume : 传入多少方
"""
self.upper_extra_label.setText(f"{volume}方(预估)")
volume_rounded = round(volume, 2) # 四舍五入,保留两位小数
self.upper_extra_label.setText(f"{volume_rounded:.2f}")
# 上料斗夹爪开合角度设置
def setUpperHopperClampAngle(self, angle: float):
@ -413,8 +414,9 @@ class HopperWidget(QWidget):
"""Args:
angle : 传入多少度 (单位°)
"""
self.lower_extra_label.setText(f"开: {angle}°") # 设置下料斗角度标签
self.lower_clamp_widget.set_angle(angle) # 设置下料斗夹爪开合角度
angle_rounded = round(angle, 2) # 四舍五入,保留两位小数
self.lower_extra_label.setText(f"开: {angle_rounded:.2f}°") # 设置下料斗角度标签
self.lower_clamp_widget.set_angle(angle_rounded) # 设置下料斗夹爪开合角度
# ------------------------------
# 设置上料斗状态0=绿1=黄2=红)

View File

@ -80,6 +80,9 @@ class MixerWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
# 状态变量:标记搅拌桨是否正在旋转
self.is_mixing = False # 初始状态为未旋转
# 两个搅拌桨的转动的动画引用
self.animations = [] # 保存动画引用
@ -157,12 +160,20 @@ class MixerWidget(QWidget):
# 搅拌桨开始搅拌
def startBladeMix(self, duration=700):
if self.is_mixing: # 搅拌桨已经在旋转
return
self.animations.clear()
# 备注duration控制搅拌桨旋转的速度值越小旋转得越快
self._start_animation(self.blade1, duration)
self._start_animation(self.blade2, duration)
self.is_mixing = True # 更新搅拌桨状态为旋转中
def stopBladeMix(self):
if not self.is_mixing: # 搅拌桨已经停止
return
for animation in self.animations:
animation.stop()
@ -170,3 +181,5 @@ class MixerWidget(QWidget):
self.blade1.reset_to_original()
if self.blade2:
self.blade2.reset_to_original()
self.is_mixing = False # 更新搅拌桨状态为停止

View File

@ -61,7 +61,6 @@ class SystemCenterDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self._init_ui()
self.init_animations() # 初始化动画
def _init_ui(self):
# 弹窗基础设置
@ -103,47 +102,6 @@ class SystemCenterDialog(QDialog):
painter.drawPixmap(self.rect(), self.background)
super().paintEvent(event)
def init_animations(self):
"""初始化显示动画(可根据喜好选择或组合)"""
# 1. 淡入动画透明度从0→1
self.opacity_anim = QPropertyAnimation(self, b"windowOpacity")
self.opacity_anim.setDuration(300) # 动画时长300ms
self.opacity_anim.setStartValue(0.0)
self.opacity_anim.setEndValue(1.0)
self.opacity_anim.setEasingCurve(QEasingCurve.InOutCubic) # 缓动曲线(平滑加速减速)
# 2. 缩放动画从80%→100%大小)
self.scale_anim = QPropertyAnimation(self, b"geometry")
self.scale_anim.setDuration(300)
# 起点和终点在显示时动态设置(依赖当前弹窗位置)
self.scale_anim.setEasingCurve(QEasingCurve.OutBack) # 带弹性的缓动曲线(弹出感)
# 3. 组合动画(同时执行淡入+缩放)
from PySide6.QtCore import QParallelAnimationGroup
self.anim_group = QParallelAnimationGroup(self)
self.anim_group.addAnimation(self.opacity_anim)
self.anim_group.addAnimation(self.scale_anim)
def showEvent(self, event):
"""重写显示事件,每次显示时启动动画"""
# 必须先调用父类showEvent否则弹窗无法正常显示
super().showEvent(event)
# 动态设置缩放动画的起点(基于当前弹窗位置和大小)
current_geometry = self.geometry() # 弹窗当前位置和大小已通过move设置
# 起点缩小到80%,并保持中心位置不变
start_rect = QRect(
current_geometry.center().x() - current_geometry.width() * 0.4,
current_geometry.center().y() - current_geometry.height() * 0.4,
int(current_geometry.width() * 0.8),
int(current_geometry.height() * 0.8)
)
self.scale_anim.setStartValue(start_rect)
self.scale_anim.setEndValue(current_geometry) # 终点:原始大小
# 启动组合动画
self.anim_group.start()
if __name__ == "__main__":
app = QApplication(sys.argv)
dialog = SystemCenterDialog()

View File

@ -66,44 +66,47 @@ class CustomDropdown(QWidget):
)
# 2. 可点击的箭头标签QLabel
self.arrow_label = QLabel()
self.arrow_pixmap = QPixmap(arrow_img_path)
self.arrow_label.setStyleSheet("background-image: url(" ");")
self.arrow_label.setPixmap(
self.arrow_pixmap.scaled(12, 9, Qt.KeepAspectRatio, Qt.SmoothTransformation)
)
self.arrow_label.setCursor(Qt.PointingHandCursor)
self.main_layout.addWidget(self.arrow_label, alignment=Qt.AlignTop)
# self.arrow_label = QLabel()
# self.arrow_pixmap = QPixmap(arrow_img_path)
# self.arrow_label.setStyleSheet("background-image: url(" ");")
# self.arrow_label.setPixmap(
# self.arrow_pixmap.scaled(12, 9, Qt.KeepAspectRatio, Qt.SmoothTransformation)
# )
# self.arrow_label.setCursor(Qt.PointingHandCursor)
# self.main_layout.addWidget(self.arrow_label, alignment=Qt.AlignTop)
# 3. 下拉选项列表(默认选中第一个)
self.list_widget = QListWidget()
self.list_widget.setWindowFlags(Qt.Popup)
# 取消了下拉框 2026/1/11
# self.list_widget = QListWidget()
# self.list_widget.setWindowFlags(Qt.Popup)
# 设置选项字体
font = QFont()
font.setPixelSize(16)
# 添加所有的下拉选项
for option in options:
item = QListWidgetItem(option)
item.setTextAlignment(Qt.AlignLeft)
item.setFont(font)
self.list_widget.addItem(item)
self.list_widget.setCurrentRow(0) # 默认选中第一项
self.list_widget.itemClicked.connect(self.select_option)
# 取消了下拉框 2026/1/11
# for option in options:
# item = QListWidgetItem(option)
# item.setTextAlignment(Qt.AlignLeft)
# item.setFont(font)
# self.list_widget.addItem(item)
# self.list_widget.setCurrentRow(0) # 默认选中第一项
# self.list_widget.itemClicked.connect(self.select_option)
# 双保险监听:全局焦点变化 + 事件过滤
self.app = QApplication.instance()
self.app.focusChanged.connect(self.on_focus_changed)
self.list_widget.installEventFilter(self)
# 取消了下拉框 2026/1/11
# self.app = QApplication.instance()
# self.app.focusChanged.connect(self.on_focus_changed)
# self.list_widget.installEventFilter(self)
def mousePressEvent(self, event):
"""重写鼠标点击事件实现QLabel点击功能"""
# 判断点击是否在result_label或arrow_label区域内
# if self.result_label.underMouse() or self.arrow_label.underMouse():
# self.toggle_expand()
if self.arrow_label.underMouse():
self.toggle_expand()
# if self.arrow_label.underMouse(): # 取消了下拉箭头 2026/1/11
# self.toggle_expand()
super().mousePressEvent(event) # 传递事件,不影响其他组件
def toggle_expand(self):
@ -173,15 +176,19 @@ class CustomDropdown(QWidget):
# 获取当前选中的设备名
def get_selected_device(self):
return self.result_label.text()
# 设置选中的设备名
def set_selected_device(self, device_name:str):
self.result_label.setText(device_name)
class SystemDiagnosticsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setWindowOpacity(0.0)
self.max_row = 8 # 最大行数为8行
self.max_col = 4 # 最大列数为4列
self._init_ui()
self.init_animations()
def _init_ui(self):
# 无边框模式
@ -206,7 +213,7 @@ class SystemDiagnosticsDialog(QDialog):
circle_warning_path = ImagePaths.SYSTEM_DIAGNOSTICS_STATUS_YELLOW # 警告状态
circle_error_path = ImagePaths.SYSTEM_DIAGNOSTICS_STATUS_RED # 异常状态
ms_box_path = ImagePaths.SYSTEM_DIAGNOSTICS_MS_BG
dropdown_arrow_path = ImagePaths.SYSTEM_DIAGNOSTICS_DROPDOWN_ARROW
dropdown_arrow_path = ImagePaths.SYSTEM_DIAGNOSTICS_DROPDOWN_ARROW # 箭头图标
# 字体设置
ms_font = QFont()
@ -214,8 +221,8 @@ class SystemDiagnosticsDialog(QDialog):
ms_color = QColor("#14abea")
# 生成小框
for row in range(8):
for col in range(4):
for row in range(self.max_row):
for col in range(self.max_col):
box_container = QWidget()
box_container.setObjectName(f"box_{row}_{col}")
box_container.setStyleSheet(
@ -249,6 +256,7 @@ class SystemDiagnosticsDialog(QDialog):
ms_layout.setContentsMargins(6, 0, 0, 0)
ms_edit = QLineEdit("5ms")
ms_edit.setFont(ms_font)
ms_edit.setReadOnly(True) # 禁用外部点击输入
ms_edit.setStyleSheet(
f"""
background: none;
@ -284,44 +292,6 @@ class SystemDiagnosticsDialog(QDialog):
grid_layout.addWidget(box_container, row, col)
def init_animations(self):
"""初始化显示动画:从下方滑入 + 淡入"""
# 1. 透明度动画从0→1与系统中心一致但时长不同
self.opacity_anim = QPropertyAnimation(self, b"windowOpacity")
self.opacity_anim.setDuration(400)
self.opacity_anim.setStartValue(0.0)
self.opacity_anim.setEndValue(1.0)
self.opacity_anim.setEasingCurve(QEasingCurve.OutCubic) # 缓动曲线不同
# 2. 位置动画从下方100px滑入目标位置核心差异点
self.pos_anim = QPropertyAnimation(self, b"geometry")
self.pos_anim.setDuration(400)
self.pos_anim.setEasingCurve(QEasingCurve.OutQuart) # 滑入效果更自然
# 3. 组合动画(同时执行滑入和淡入)
self.anim_group = QParallelAnimationGroup(self)
self.anim_group.addAnimation(self.opacity_anim)
self.anim_group.addAnimation(self.pos_anim)
def showEvent(self, event):
super().showEvent(event) # 先调用父类方法
# 动态计算动画起点在当前位置下方100px保持宽度和高度不变
current_geometry = self.geometry() # 当前位置和尺寸需提前用move设置
# 起点y坐标增加100px从下方滑入x和尺寸不变
start_rect = QRect(
current_geometry.x(),
current_geometry.y() + 100, # 下方100px
current_geometry.width(),
current_geometry.height()
)
# 设置动画起点和终点
self.pos_anim.setStartValue(start_rect)
self.pos_anim.setEndValue(current_geometry) # 终点:目标位置
# 启动动画
self.anim_group.start()
def paintEvent(self, event):
"""重写绘制事件,手动在透明背景上绘制图片"""
if not self.bg_pixmap.isNull():
@ -345,15 +315,22 @@ class SystemDiagnosticsDialog(QDialog):
# ========== 对外接口:获取选中的设备名 ==========
def get_selected_device(self, row, col):
"""获取指定行列的选中设备名"""
"""获取指定行列的选中设备名, 行号和列号都从0开始"""
box = self.findChild(QWidget, f"box_{row}_{col}")
if box and hasattr(box, "dropdown"):
return box.dropdown.get_selected_device()
return None
# ========== 对外接口:设置选中的设备名 ==========
def set_selected_device(self, row, col, device_name:str):
"""设置指定行列的选中设备名, 行号和列号都从0开始"""
box = self.findChild(QWidget, f"box_{row}_{col}")
if box and hasattr(box, "dropdown"):
return box.dropdown.set_selected_device(device_name)
# ========== 对外接口:获取毫秒值 ==========
def get_ms_value(self, row, col):
"""获取指定行列的毫秒值如“5ms”"""
"""获取指定行列的毫秒值如“5ms”, 行号和列号都从0开始"""
box = self.findChild(QWidget, f"box_{row}_{col}")
if box and hasattr(box, "ms_edit"):
# return box.ms_edit.text()
@ -366,6 +343,13 @@ class SystemDiagnosticsDialog(QDialog):
return number_match.group(1)
return None
# ========== 对外接口:设置毫秒值 ==========
def set_ms_value(self, row, col, ms:int):
"""设置指定行列的毫秒值, 行号和列号都从0开始"""
box = self.findChild(QWidget, f"box_{row}_{col}")
if box and hasattr(box, "ms_edit"):
box.ms_edit.setText(f"{ms}ms")
if __name__ == "__main__":

View File

@ -4,6 +4,8 @@ from PySide6.QtCore import Qt
from PySide6.QtGui import QDoubleValidator
import sys
from common.constant_config_manager import ConfigManager
"""
调整计划方量, 左侧减按钮, 右侧加按钮
这里的 最小值、最大值、初始值 需要读取配置文件来决定
@ -27,13 +29,25 @@ class CustomLineEdit(QLineEdit):
self.setText(f"{value:.1f}")
self.setCursorPosition(0) # 光标移到最前面 (保证数值显示完整)
# 更新输入框的默认文本
def updateDefaultText(self, text:str):
self.default_text = text
class ValueAdjuster(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.min_value = 0.0 # 最小值
self.max_value = 99.0 # 最大值
self.value = 2.5 # 初始值 (需要显示一位数字)
self.config_manager = ConfigManager()
self.config_manager.adjuster_params_changed.connect(self.on_adjuster_params_changed) # 绑定参数变化槽函数
# self.min_value = 0.0 # 最小值
# self.max_value = 99.0 # 最大值
# self.value = 2.5 # 初始值 (需要显示一位数字)
self.min_value = self.config_manager.get_adjuster_min() # 最小值
self.max_value = self.config_manager.get_adjuster_max() # 最大值
self.value = self.config_manager.get_adjuster_initial() # 初始值 (需要显示一位小数)
self.setFixedSize(102, 32)
@ -120,6 +134,26 @@ class ValueAdjuster(QWidget):
layout.addWidget(self.line_edit)
layout.addWidget(self.plus_btn)
def on_adjuster_params_changed(self, new_min, new_max, new_initial):
"""配置文件中的min、max、initial变化时, 更新控件范围"""
# print("on_adjuster_params_changed:", new_min, new_max, new_initial)
# 1、更新最小值和最大值
if self.min_value != new_min or self.max_value != new_max:
self.min_value = new_min
self.max_value = new_max
self.line_edit.setValidator(QDoubleValidator(self.min_value, self.max_value, 1, self))
# 2、确保当前值在新范围内不在当前范围内就更新当前值
if self.value < new_min:
self.value = new_min
self.line_edit.setText(f"{self.value:.1f}")
elif self.value > new_max:
self.value = new_max
self.line_edit.setText(f"{self.value:.1f}")
# 3、更新输入编辑框默认值
self.line_edit.updateDefaultText(f"{new_initial:.1f}")
def on_minus_clicked(self):
"""减0.1"""
new_value = self.value - 0.1

View File

@ -172,13 +172,20 @@ class CameraModule(QWidget):
video_display_signal = Signal(str, bool)
def __init__(
self, camera_name="摄像头", rtsp_url="", need_rotate_180=True, parent=None
self, camera_name="摄像头", rtsp_url="", need_rotate_180=True, show_ai = False, parent=None
):
super().__init__(parent)
self.setObjectName("cameraModule")
self.camera_name = camera_name
self.rtsp_url = rtsp_url
self.need_rotate_180 = need_rotate_180 # 画面是否需要旋转180度后显示
self.show_ai = show_ai # 是否需要展示ai为True会多出AI显示按钮
# 初始化AI显示相关变量为None
self.ai_display_group = None
self.ai_display_label = None
self.ai_display_switch = None
self.setup_ui()
def setup_ui(self):
@ -195,20 +202,23 @@ class CameraModule(QWidget):
self.title_label = QLabel()
self.title_label.setAlignment(Qt.AlignLeft)
self.title_label.setText(f"{self.camera_name}视频")
# self.title_label.setFixedWidth(212)
self.title_label.setFixedSize(212, 21)
self.title_label.setObjectName("cameraTitleLabel")
# background-image: url({ImagePaths.VIDEO_TITLE_BACKGROUND});
# min-width: 126px;
self.title_label.setStyleSheet(
f"""
#cameraTitleLabel {{
font-size: 18px;
color: #16ffff;
background-image: url({ImagePaths.VIDEO_TITLE_BACKGROUND});
min-width: 180px;
padding-left: 12px;
}}
"""
)
# 创建【显示标签+开关】组合容器
# 1、创建【显示标签+开关】组合容器
self.display_group = QWidget() # 容器:包裹标签和开关
display_group_layout = QHBoxLayout(self.display_group)
display_group_layout.setContentsMargins(0, 0, 0, 0) # 容器内边距为0
@ -239,9 +249,43 @@ class CameraModule(QWidget):
display_group_layout.addWidget(self.display_label)
display_group_layout.addWidget(self.display_switch, alignment=Qt.AlignLeft)
if self.show_ai: # 当需要显示AI时才生成
# 2、创建【AI算法标签+开关】组合容器
self.ai_display_group = QWidget() # 容器包裹AI显示标签和开关
ai_display_group_layout = QHBoxLayout(self.ai_display_group)
ai_display_group_layout.setContentsMargins(0, 0, 0, 0) # 容器内边距为0
ai_display_group_layout.setSpacing(0)
# AI算法显示标签
self.ai_display_label = QLabel("AI算法")
self.ai_display_label.setObjectName("aiDisplayLabel")
# font-weight: bold;
self.ai_display_label.setStyleSheet("""
#aiDisplayLabel {
font-size: 18px;
color: #16ffff;
margin: 0px;
padding-left: 2px;
}
""")
# self.ai_display_label.setFixedWidth(40)
self.ai_display_label.setAlignment(Qt.AlignVCenter | Qt.AlignLeft)
# AI开关
self.ai_display_switch = SwitchButton()
self.ai_display_switch.setChecked(False)
# self.ai_display_switch.switched.connect(self.onDisplayButtonSwitched) # 在camera_controller中处理
# 将AI显示标签和开关添加到组合容器布局
ai_display_group_layout.addWidget(self.ai_display_label)
ai_display_group_layout.addWidget(self.ai_display_switch, alignment=Qt.AlignLeft)
# 添加到标题布局
title_layout.addWidget(self.title_label, alignment=Qt.AlignLeft)
title_layout.addWidget(self.display_group, alignment=Qt.AlignLeft)
title_layout.addWidget(self.display_group, alignment=Qt.AlignRight)
if self.show_ai: # 需要添加 AI显示控件
title_layout.addWidget(self.ai_display_group, alignment=Qt.AlignLeft)
self.title_label.setFixedSize(139, 21) # 调整 xxx视频标签的宽度
# 视频显示容器:使用堆叠布局,让重连按钮和视频标签分层显示
self.video_container = QWidget()
@ -313,7 +357,7 @@ class CameraModule(QWidget):
# 显示开关切换槽函数
def onDisplayButtonSwitched(self, state:bool):
# 显示开关打开state 为 True显示开关关闭state 为 False
# 显示开关打开state 为 True; 显示开关关闭state 为 False
if state:
self.stacked_widget.setCurrentWidget(self.raw_label) # 视频显示标签
self.stacked_widget.setHidden(False)
@ -403,8 +447,8 @@ class VibrationVideoWidget(QWidget):
# 需要修改为相应的地址!!!
# 注在camera_controller中设置地址url
self.cam1 = CameraModule("上位料斗")
self.cam2 = CameraModule("下位料斗")
self.cam3 = CameraModule("模具车")
self.cam2 = CameraModule("下位料斗", show_ai=False)
self.cam3 = CameraModule("模具车", need_rotate_180=False)
self.setup_ui()
self.connect_signals()