Files
Feeding_control_system/controller/main_controller.py

254 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from PySide6.QtCore import QTimer, Signal, QObject, Qt
from PySide6.QtWidgets import QApplication # 用于获取主线程
import threading
from hardware import transmitter
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):
# 主界面
self.main_window = MainWindow()
self.msg_recorder = MessageRecorder()
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.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")
self.main_window.segment_task_widget.set_task_time("task2","17:24 PM")
def _initSubControllers(self):
# 右侧视频显示控制模块
self.camera_controller = CameraController(
video_view=self.main_window.vibration_video
)
# 底部按钮控制模块
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
)
def _initSubViews(self):
pass
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("关闭自动智能浇筑系统") # 记录系统状态消息
# 停止系统底部控制器中的线程
if hasattr(self, 'bottom_control_controller'):
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)