Files
Feeding_control_system/controller/main_controller.py

243 lines
9.9 KiB
Python
Raw Normal View History

2026-01-11 18:00:32 +08:00
from PySide6.QtCore import QTimer, Signal, QObject, Qt
from PySide6.QtWidgets import QApplication # 用于获取主线程
2025-11-01 18:52:19 +08:00
import threading
from view.main_window import MainWindow
2025-10-31 18:52:31 +08:00
from .camera_controller import CameraController
from .bottom_control_controller import BottomControlController
from .hopper_controller import HopperController
2026-01-11 18:00:32 +08:00
from .hopper_controller import UpperHopperPosition
from common.constant_config_manager import ConfigManager
from service.msg_recorder import MessageRecorder
2026-01-11 18:00:32 +08:00
from service.opcua_ui_client import OpcuaUiClient
from service.artifact_query_thread import ArtifactInfoQueryThread # 管片任务查询
from busisness.models import ArtifactInfoModel
from typing import List
2025-10-18 18:29:40 +08:00
class MainController:
def __init__(self):
# 主界面
self.main_window = MainWindow()
self.msg_recorder = MessageRecorder()
2026-01-11 18:00:32 +08:00
self.msg_recorder.normal_record("开始自动智能浇筑系统") # 记录系统状态消息
2025-11-17 00:05:40 +08:00
# 初始化子界面和控制器
2025-10-18 18:29:40 +08:00
self._initSubViews()
2025-10-31 18:52:31 +08:00
self._initSubControllers()
2025-10-18 18:29:40 +08:00
2026-01-11 18:00:32 +08:00
# 加载配置管理器
self.config_manager = ConfigManager()
# 启动消息数据库(messages.db)定时清理任务
self.start_msg_database_clean_task()
self.MSG_CLEAN_INTERVAL = None
# opcua客户端
self.opc_client = OpcuaUiClient()
self.opc_client.start()
2026-01-11 18:00:32 +08:00
# 连接信号
self.__connectSignals()
2025-10-18 18:29:40 +08:00
def showMainWindow(self):
2026-01-11 18:00:32 +08:00
# self.main_window.showFullScreen()
self.main_window.show()
2025-10-18 18:29:40 +08:00
2025-10-31 18:52:31 +08:00
def _initSubControllers(self):
# 右侧视频显示控制模块
2025-10-31 18:52:31 +08:00
self.camera_controller = CameraController(
video_view=self.main_window.vibration_video
)
# 底部按钮控制模块
2025-10-31 18:52:31 +08:00
self.bottom_control_controller = BottomControlController(
bottom_control_widget=self.main_window.bottom_control_widget,
main_window=self.main_window
)
# 料斗控制模块(包括 夹爪开合、拱等按钮)
self.hopper_controller = HopperController(
hopper_view = self.main_window.hopper_widget,
conveyor_view = self.main_window.conveyor_system_widget
)
2025-10-18 18:29:40 +08:00
def _initSubViews(self):
pass
def __connectSignals(self):
self.main_window.about_to_close.connect(self.handleMainWindowClose) # 处理主界面关闭
2026-01-11 18:00:32 +08:00
self.config_manager.msg_clean_interval_changed.connect(self.onMsgDbCleanIntervalChanged) # 消息清理间隔改变
self.opc_client.opc_signal.value_changed.connect(self._onOpcValueChanged, Qt.QueuedConnection) # opcua服务器值改变
self.opc_client.opc_signal.opc_log.connect(self.msg_recorder.normal_record, Qt.QueuedConnection) # opcua客户端日志
2026-01-11 18:00:32 +08:00
def handleMainWindowClose(self):
"""主界面关闭"""
self.msg_recorder.normal_record("关闭自动智能浇筑系统")
2025-11-21 14:55:52 +08:00
# 停止系统底部控制器中的线程
if hasattr(self, 'bottom_control_controller'):
self.bottom_control_controller.stop_threads()
2026-01-11 18:00:32 +08:00
# 停止opc客户端
if hasattr(self, 'opc_client'):
self.opc_client.stop_run()
2026-01-11 18:00:32 +08:00
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 _onOpcValueChanged(self, node_id, var_name, new_value):
2026-01-11 18:00:32 +08:00
"""
OPCUA值变化时的UI更新函数
"""
try:
update_method = getattr(self, f"_update_{var_name}", None)
if update_method:
update_method(new_value)
2026-01-11 18:00:32 +08:00
except Exception as e:
print(f"_onOpcValueChanged: 界面更新失败: {e}")
2026-01-11 18:00:32 +08:00
import traceback
traceback.print_exc()
def convert_to_ampm(self, time_str: str) -> str:
2026-01-11 18:00:32 +08:00
"""时间格式转换: 转换为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 "--:--"
def onUpdateUiByArtifactInfo(self, artifact_list:List[ArtifactInfoModel]):
2026-01-11 18:00:32 +08:00
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 = self.convert_to_ampm(artifact.BeginTime)
2026-01-11 18:00:32 +08:00
self.main_window.segment_task_widget.set_task_time(f"task{index}", time_str) # 开始时间
self.main_window.SetSegmentTaskDetails(f"task{index}", artifact) # 更新管片任务详情
# 将opc服务中的 segment_tasks的值复原为 0以便下次触发管片更新
self.opc_client.write_value_by_name("segment_tasks", 0)
# ======================== OPCUA值更新界面方法 ======================
def _update_upper_weight(self, val):
# 更新上料斗重量
self.hopper_controller.onUpdateUpperHopperWeight(val)
def _update_lower_weight(self, val):
# 更新下料斗重量
self.hopper_controller.onUpdateLowerHopperWeight(val)
def _update_upper_volume(self, val):
# 更新上料斗方量
self.hopper_controller.onUpdateUpperHopperVolume(val)
def _update_production_progress(self, val):
# 更新生产进度限制为100, 进度为去掉百分号之后的整数
progress = val
self.main_window.arc_progress.setProgress(progress)
self.main_window.production_progress.setProgress(progress)
def _update_lower_clamp_angle(self, val):
# 更新下料斗夹爪角度
self.hopper_controller.onUpdateLowerClampAngle(val)
def _update_upper_clamp_status(self, val):
# 更新上料斗夹爪状态 0表示关闭 1表示打开
self.hopper_controller.onUpdateUpperClampStatus(val)
def _update_upper_hopper_position(self, val):
# 更新上料斗位置 5表示料斗到位到达振捣室处 66表示在搅拌楼处
self.hopper_controller.onUpdateUpperHopperPosition(val)
if val == UpperHopperPosition.MIXING_TOWER.value:
self.main_window.mixer_widget.startBladeMix()
else:
self.main_window.mixer_widget.stopBladeMix()
def _update_segment_tasks(self, val):
if val: # 需要更新管片任务
"""更新左侧的管片任务"""
if hasattr(self, "query_thread") and self.query_thread.isRunning():
return
# 1. 管片信息查询线程
self.query_thread = ArtifactInfoQueryThread()
# 2. 主线程更新管片任务UI
self.query_thread.query_finished.connect(self.onUpdateUiByArtifactInfo)
# 3. 查询管片信息错误
self.query_thread.query_error.connect(self.onQueryArtifactInfoError)
self.query_thread.start()
2026-01-11 18:00:32 +08:00
def onQueryArtifactInfoError(self, error_msg:str):
# 查询管片信息失败预警
self.msg_recorder.warning_record(error_msg)
def _update_vibration_frequency(self, val):
# 更新振捣频率
self.main_window.frequency_button_group.set_selected_frequency(val)