Merge branch 'feature/main_ui' of http://www.xj-robot.com:3000/xiongyi/Feeding_control_system into feature/main_ui
This commit is contained in:
@ -1,18 +1,20 @@
|
||||
from PySide6.QtCore import QTimer, Signal, QObject, Slot
|
||||
import threading
|
||||
from hardware.transmitter import TransmitterController
|
||||
from hardware.relay import RelayController
|
||||
# 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: 搅拌楼处,对应数值 1
|
||||
- VIBRATION_CHAMBER: 振捣室处,对应数值 2
|
||||
- IN_TRANSIT: 途中,对应数值 3
|
||||
"""
|
||||
MIXING_TOWER = 66 # 搅拌楼处
|
||||
VIBRATION_CHAMBER = 5 # 振捣室处
|
||||
MIXING_TOWER = 1 # 搅拌楼处
|
||||
VIBRATION_CHAMBER = 2 # 振捣室处
|
||||
IN_TRANSIT = 3 # 运输途中
|
||||
|
||||
# 信号类:后台线程向主线程传递数据
|
||||
class HopperSignals(QObject):
|
||||
@ -42,7 +44,7 @@ class HopperController:
|
||||
self._connect_signals()
|
||||
|
||||
# 开启定时器
|
||||
self.timer_angle.start()
|
||||
# self.timer_angle.start()
|
||||
# self.timer_weight.start()
|
||||
|
||||
def _connect_signals(self):
|
||||
@ -58,14 +60,14 @@ class HopperController:
|
||||
# 上料斗 "开"按钮点击
|
||||
self.hopper_view.upper_open_btn.clicked.connect(self.onUpperClampOpenBottonClicked)
|
||||
|
||||
# 上料斗 "破拱"按钮
|
||||
self.hopper_view.upper_arch_breaking_signal.connect(self.onUpperArchBreaking)
|
||||
# 上料斗 "破拱"按钮点击
|
||||
self.hopper_view.upper_arch_breaking_signal.connect(self.onUpperArchBreakingClicked)
|
||||
|
||||
# 下料斗 "开"按钮点击
|
||||
self.hopper_view.lower_open_btn.clicked.connect(self.onLowerClampOpenBottonClicked)
|
||||
|
||||
# 下料斗 "破拱"按钮
|
||||
self.hopper_view.lower_arch_breaking_signal.connect(self.onLowerArchBreaking)
|
||||
# 下料斗 "破拱"按钮点击
|
||||
self.hopper_view.lower_arch_breaking_signal.connect(self.onLowerArchBreakingClicked)
|
||||
|
||||
def handleLowerClampAngleUpdate(self):
|
||||
"""处理下料斗夹爪开合"""
|
||||
@ -102,15 +104,16 @@ class HopperController:
|
||||
@Slot()
|
||||
def handleReadUpperHopperWeight(self):
|
||||
# 后台读取上料斗重量
|
||||
def upper_weight_task():
|
||||
loc_tra = TransmitterController(RelayController())
|
||||
# 上料斗重量 (目前只有上料斗安装变送器, 可以读取到重量)
|
||||
upper_weight = loc_tra.read_data(1)
|
||||
# 发送信号到主线程更新UI
|
||||
if upper_weight is not None:
|
||||
self.signals.upper_weight_updated.emit(upper_weight)
|
||||
# def upper_weight_task():
|
||||
# loc_tra = TransmitterController(RelayController())
|
||||
# # 上料斗重量 (目前只有上料斗安装变送器, 可以读取到重量)
|
||||
# upper_weight = loc_tra.read_data(1)
|
||||
# # 发送信号到主线程更新UI
|
||||
# if upper_weight is not None:
|
||||
# self.signals.upper_weight_updated.emit(upper_weight)
|
||||
|
||||
threading.Thread(target=upper_weight_task, daemon=True).start()
|
||||
# threading.Thread(target=upper_weight_task, daemon=True).start()
|
||||
pass
|
||||
|
||||
@Slot(float)
|
||||
def onUpdateLowerClampAngle(self, angle:float):
|
||||
@ -131,36 +134,51 @@ class HopperController:
|
||||
|
||||
@Slot(int)
|
||||
def onUpdateUpperClampStatus(self, status:int):
|
||||
# 上料斗夹爪状态 1表示打开,0表示关闭
|
||||
if status:
|
||||
# 执行上料斗夹爪打开动画
|
||||
# 上料斗夹爪状态 (1:半开,2全开,0:关闭)
|
||||
if status == 1:
|
||||
# 上料斗门半开
|
||||
self.hopper_view.upper_clamp_widget.testAnimation(target_angle=30, duration=5)
|
||||
elif status == 2:
|
||||
# 上料斗门全开
|
||||
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)
|
||||
elif status == 0:
|
||||
# 上料斗门关闭
|
||||
self.hopper_view.upper_clamp_widget.testAnimation(target_angle=0, duration=6)
|
||||
|
||||
def onUpdateUpperHopperPosition(self, position:int):
|
||||
# 上料斗位置
|
||||
if position == UpperHopperPosition.MIXING_TOWER.value: # 上料斗到达搅拌楼,值为66
|
||||
if position == UpperHopperPosition.MIXING_TOWER.value: # 上料斗到达搅拌楼
|
||||
# 上料斗在搅拌楼下
|
||||
self.hopper_view.hideUpperHopper() # 隐藏非传送带处的上料斗
|
||||
self.conveyor_view.moveHopperBelowMixer() # 传送带处的上料斗移动到搅料楼下
|
||||
self.conveyor_view.showHopper() # 显示传送带处的上料斗
|
||||
elif position == UpperHopperPosition.VIBRATION_CHAMBER.value: # 上料斗就绪,到达振捣室,值为5
|
||||
elif position == UpperHopperPosition.VIBRATION_CHAMBER.value: # 上料斗就绪,到达振捣室
|
||||
# 上料斗在振捣室处
|
||||
self.conveyor_view.hideHopper() # 隐藏传送带处的上料斗
|
||||
self.hopper_view.upper_clamp_widget.set_angle(0) # 上料斗夹爪角度设置为0,此时上料斗一定是关闭的
|
||||
self.hopper_view.showUpperHopper() # 显示非传送带处的上料斗
|
||||
else:
|
||||
elif position == UpperHopperPosition.IN_TRANSIT.value:
|
||||
# 上料斗在途中
|
||||
self.hopper_view.hideUpperHopper() # 隐藏非传送带处的上料斗 (下料斗处对应的上料斗叫非传送带处上料斗)
|
||||
self.conveyor_view.moveHopperToTransition() # 传送带处上料斗移动到中间过渡位置
|
||||
self.conveyor_view.showHopper() # 显示传送带处的上料斗
|
||||
|
||||
@Slot(bool)
|
||||
def onUpperArchBreaking(self, status:bool):
|
||||
"""上料斗破拱: status 为True表示 开启破拱, 为False表示 关闭破拱"""
|
||||
print("hopper_controller: onUpperArchBreaking ", status)
|
||||
def onUpperArchBreakingStatusChanged(self, status:bool):
|
||||
"""上料斗破拱状态改变: status 为True表示 破拱状态, 为False表示 不破拱状态"""
|
||||
if status == True: # 破拱状态
|
||||
self.hopper_view.upper_arch_breaking_status = True
|
||||
self.hopper_view.upper_arch_label.setHidden(False)
|
||||
else: # 不破拱状态
|
||||
self.hopper_view.upper_arch_breaking_status = False
|
||||
self.hopper_view.upper_arch_label.setHidden(True)
|
||||
|
||||
@Slot(bool)
|
||||
def onUpperArchBreakingClicked(self, status:bool):
|
||||
"""上料斗破拱按钮点击: status 为True表示 开启破拱, 为False表示 关闭破拱"""
|
||||
print("hopper_controller: onUpperArchBreakingClicked ", status)
|
||||
# 这里需要控制网络继电器,开启上料斗破拱(可能还需要同步opc的上料斗破拱状态)
|
||||
|
||||
|
||||
@Slot(int)
|
||||
def onUpperHopperStatusChanged(self, status:int):
|
||||
@ -179,9 +197,20 @@ class HopperController:
|
||||
print("hopper_controller: onLowerClampOpenBottonClicked")
|
||||
|
||||
@Slot(bool)
|
||||
def onLowerArchBreaking(self, status:bool):
|
||||
"""下料斗破拱: status 为True表示 开启破拱, 为False表示 关闭破拱"""
|
||||
print("hopper_controller: onLowerArchBreaking ", status)
|
||||
def onLowerArchBreakingStatusChanged(self, status:bool):
|
||||
"""下料斗破拱状态改变: status 为True表示 破拱状态, 为False表示 不破拱状态"""
|
||||
if status == True: # 破拱状态
|
||||
self.hopper_view.lower_arch_breaking_status = True
|
||||
self.hopper_view.lower_arch_label.setHidden(False)
|
||||
else: # 不破拱状态
|
||||
self.hopper_view.lower_arch_breaking_status = False
|
||||
self.hopper_view.lower_arch_label.setHidden(True)
|
||||
|
||||
@Slot(bool)
|
||||
def onLowerArchBreakingClicked(self, status:bool):
|
||||
"""下料斗破拱按钮点击: status 为True表示 开启破拱, 为False表示 关闭破拱"""
|
||||
print("hopper_controller: onLowerArchBreakingClicked ", status)
|
||||
# 这里需要控制网络继电器,开启下料斗破拱(可能还需要同步opc的下料斗破拱状态)
|
||||
|
||||
@Slot(int)
|
||||
def onLowerHopperStatusChanged(self, status:int):
|
||||
|
||||
@ -15,7 +15,13 @@ from service.pdrecord_query_thread import PDRecordQueryThread # 派单任务查
|
||||
from busisness.models import ArtifactInfoModel
|
||||
from busisness.models import PDRecordModel
|
||||
from typing import List
|
||||
from enum import Enum
|
||||
|
||||
class FeedStatus(Enum):
|
||||
"""下料状态
|
||||
- FEED_FINISH: 下料完成,对应数值 11
|
||||
"""
|
||||
FEED_FINISH = 11 # 下料完成
|
||||
|
||||
class MainController:
|
||||
def __init__(self):
|
||||
@ -40,6 +46,15 @@ class MainController:
|
||||
self.opc_client = OpcuaUiClient()
|
||||
self.opc_client.start()
|
||||
|
||||
# 刷新派单任务和管片任务定时器
|
||||
self.refresh_task_timer = QTimer()
|
||||
self.refresh_task_timer.setInterval(2000)
|
||||
self.refresh_task_timer.timeout.connect(self._refresh_segment_and_pd_task)
|
||||
self.refresh_task_timer.start()
|
||||
|
||||
# 下料状态
|
||||
self.feed_status = None
|
||||
|
||||
# 连接信号
|
||||
self.__connectSignals()
|
||||
|
||||
@ -86,10 +101,30 @@ class MainController:
|
||||
# 主界面的计划表单中的计划方量修改控制
|
||||
self.main_window.plan_volume_modified_signal.connect(self.handleVolumeModified) # 计划方量修改
|
||||
|
||||
def _refresh_segment_and_pd_task(self):
|
||||
"""
|
||||
定时器槽函数: 每2秒执行一次
|
||||
写入OPC节点触发管片任务/派单任务刷新
|
||||
"""
|
||||
try:
|
||||
# 1. 触发管片任务刷新
|
||||
self._update_sys_segment_refresh(1)
|
||||
# 2. 触发派单任务刷新
|
||||
self._update_sys_pd_refresh(1)
|
||||
except Exception as e:
|
||||
# 捕获异常并记录,避免定时器崩溃
|
||||
error_msg = f"定时刷新管片/派单任务失败:{str(e)}"
|
||||
self.msg_recorder.warning_record(error_msg)
|
||||
|
||||
def handleMainWindowClose(self):
|
||||
"""主界面关闭"""
|
||||
self.msg_recorder.normal_record("关闭自动智能浇筑系统")
|
||||
|
||||
# 停止管片任务和派单任务定时器
|
||||
if hasattr(self, 'refresh_task_timer') and self.refresh_task_timer.isActive():
|
||||
self.refresh_task_timer.stop()
|
||||
self.refresh_task_timer.deleteLater()
|
||||
|
||||
# 停止系统底部控制器中的线程
|
||||
if hasattr(self, 'bottom_control_controller'):
|
||||
self.bottom_control_controller.stop_threads()
|
||||
@ -102,13 +137,13 @@ class MainController:
|
||||
pd_mode: 1,自动派单 2,手动派单
|
||||
"""
|
||||
if auto_status: # 自动派单
|
||||
self.opc_client.write_value_by_name("pd_mode", 1)
|
||||
self.opc_client.write_value_by_name("pd_set_mode", 1)
|
||||
else: # 手动派单
|
||||
self.opc_client.write_value_by_name("pd_mode", 2)
|
||||
self.opc_client.write_value_by_name("pd_set_mode", 2)
|
||||
|
||||
def handleVolumeModified(self, volume_json_str:str):
|
||||
"""处理 修改方量 (计划表单中 和 派单详情中)"""
|
||||
self.opc_client.write_value_by_name("pd_plan_volume", volume_json_str)
|
||||
self.opc_client.write_value_by_name("pd_set_volume", volume_json_str)
|
||||
|
||||
def start_msg_database_clean_task(self):
|
||||
"""启动清理消息数据库(messages.db)中过期消息的定时任务"""
|
||||
@ -181,51 +216,96 @@ class MainController:
|
||||
def onUpdateUiByArtifactInfo(self, artifact_list:List[ArtifactInfoModel]):
|
||||
# 更新管片任务
|
||||
self.main_window.update_segment_tasks(artifact_list)
|
||||
# 将opc服务中的 segment_tasks的值复原为 0,以便下次触发管片更新
|
||||
self.opc_client.write_value_by_name("segment_tasks", 0)
|
||||
# 将opc服务中的 sys_segment_refresh的值复原为 0,以便下次触发管片更新
|
||||
self.opc_client.write_value_by_name("sys_segment_refresh", 0)
|
||||
|
||||
def onUpdateUiByPDRecord(self, pdrecord_list:List[PDRecordModel]):
|
||||
# 更新派单任务
|
||||
self.main_window.update_dispatch_tasks(pdrecord_list)
|
||||
# 将opc服务中的 dispatch_tasks的值复原为 0,以便下次触发派单更新
|
||||
self.opc_client.write_value_by_name("dispatch_tasks", 0)
|
||||
# 将opc服务中的 sys_pd_refresh的值复原为 0,以便下次触发派单更新
|
||||
self.opc_client.write_value_by_name("sys_pd_refresh", 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_is_arch(self, val):
|
||||
# 更新上料斗是否破拱
|
||||
self.hopper_controller.onUpperArchBreakingStatusChanged(val)
|
||||
|
||||
def _update_upper_door_closed(self, val:int):
|
||||
# 更新上料斗门的闭合状态
|
||||
# (1:半开,2全开,0:关闭)
|
||||
self.hopper_controller.onUpdateUpperClampStatus(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表示在搅拌楼处
|
||||
def _update_upper_door_position(self, val):
|
||||
# 更新上料斗位置 2表示料斗到位,到达振捣室处 1表示在搅拌楼处
|
||||
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):
|
||||
# ============================= 下料斗相关 ==============
|
||||
def _update_lower_weight(self, val):
|
||||
# 更新下料斗重量
|
||||
self.hopper_controller.onUpdateLowerHopperWeight(val)
|
||||
|
||||
def _update_lower_is_arch(self, val:bool):
|
||||
# 更新下料斗是否破拱
|
||||
self.hopper_controller.onLowerArchBreakingStatusChanged(val)
|
||||
|
||||
def _update_lower_angle(self, val):
|
||||
# 更新下料斗夹爪角度
|
||||
self.hopper_controller.onUpdateLowerClampAngle(val)
|
||||
|
||||
# ========================== 模具车相关 ==============
|
||||
def _update_mould_finish_weight(self, finish_weight:int):
|
||||
# 更新模具车中的下料重量
|
||||
# finish_weight:已下料重量
|
||||
self.main_window.arc_progress.setWeight(finish_weight)
|
||||
|
||||
def _update_mould_finish_ratio(self, val:float):
|
||||
# 更新生产进度,限制为100, progress进度为去掉百分号之后的整数
|
||||
# 注意: 此时的val是浮点数的比例,需要保留两位小数后乘以100后得到progress
|
||||
if self.feed_status == FeedStatus.FEED_FINISH.value:
|
||||
return # 下料完成,不需要更新进度条,就是100%
|
||||
progress = round(val, 2) * 100 # 保留两位小数后乘100
|
||||
self.main_window.arc_progress.setProgress(progress)
|
||||
self.main_window.production_progress.setProgress(progress)
|
||||
|
||||
def _update_mould_frequency(self, val):
|
||||
# 更新振捣频率
|
||||
self.main_window.frequency_button_group.set_selected_frequency(val) # 频率选择按钮上显示的选中的频率
|
||||
self.main_window.arc_progress.setFrequency(val) # 模具车上显示的振捣频率
|
||||
|
||||
def _update_mould_vibrate_status(self, vibrate_status:bool):
|
||||
# 更新模具车上显示的振捣状态
|
||||
# vibrate_status: False:未振捣、True:振捣中
|
||||
if vibrate_status:
|
||||
self.main_window.arc_progress.setState("振捣中")
|
||||
else:
|
||||
self.main_window.arc_progress.setState("未振捣")
|
||||
self.opc_client.write_value_by_name("mould_frequency", 0) # 将振捣频率设置为0
|
||||
|
||||
def _update_feed_status(self, status:int):
|
||||
# 下料(浇筑)状态
|
||||
self.feed_status = status # 更新下料状态
|
||||
if status == FeedStatus.FEED_FINISH.value:
|
||||
# 下料完成
|
||||
# 1、将生产进度条调整为100%的状态
|
||||
self.main_window.arc_progress.setProgress(100)
|
||||
self.main_window.production_progress.setProgress(100)
|
||||
|
||||
|
||||
# ===================================== 系统状态相关 ====================
|
||||
def _update_sys_segment_refresh(self, val):
|
||||
if val: # 需要更新管片任务
|
||||
"""更新左侧的管片任务"""
|
||||
if hasattr(self, "segment_query_thread") and self.segment_query_thread.isRunning():
|
||||
@ -238,7 +318,7 @@ class MainController:
|
||||
self.segment_query_thread.query_error.connect(self.onQueryInfoError)
|
||||
self.segment_query_thread.start()
|
||||
|
||||
def _update_dispatch_tasks(self, val):
|
||||
def _update_sys_pd_refresh(self, val):
|
||||
if val: # 需要更新派单任务
|
||||
"""更新右侧的派单任务"""
|
||||
if hasattr(self, "dispatch_query_thread") and self.dispatch_query_thread.isRunning():
|
||||
@ -255,26 +335,8 @@ class MainController:
|
||||
# 查询信息失败预警
|
||||
self.msg_recorder.warning_record(error_msg)
|
||||
|
||||
def _update_vibration_frequency(self, val):
|
||||
# 更新振捣频率
|
||||
self.main_window.frequency_button_group.set_selected_frequency(val) # 频率选择按钮上显示的选中的频率
|
||||
self.main_window.arc_progress.setFrequency(val) # 模具车上显示的振捣频率
|
||||
|
||||
def _update_mould_vibrate_status(self, vibrate_status:bool):
|
||||
# 更新模具车上显示的振捣状态
|
||||
# vibrate_status: False:未振捣、True:振捣中
|
||||
if vibrate_status:
|
||||
self.main_window.arc_progress.setState("振捣中")
|
||||
else:
|
||||
self.main_window.arc_progress.setState("未振捣")
|
||||
self.opc_client.write_value_by_name("vibration_frequency", 0) # 将振捣频率设置为0
|
||||
|
||||
def _update_mould_finish_weight(self, finish_weight:int):
|
||||
# 更新模具车中的下料重量
|
||||
# finish_weight:已下料重量
|
||||
self.main_window.arc_progress.setWeight(finish_weight)
|
||||
|
||||
def _update_pd_mode(self, mode:int):
|
||||
# ========================================= 派单相关 =====================
|
||||
def _update_pd_set_mode(self, mode:int):
|
||||
# 更新计划表单中的 派单模式(主界面下发状态的下面的切换开关),自动派单/手动派单
|
||||
mode_mapping = {
|
||||
1: "自动派单",
|
||||
@ -288,4 +350,4 @@ class MainController:
|
||||
# 修改系统配置文件中的 派单状态为自动派单...
|
||||
else:
|
||||
self.main_window.plan_table_widget.set_auto_dispatch(False) # 关闭,手动派单
|
||||
# 修改系统配置文件中的 派单状态为手动派单...
|
||||
# 修改系统配置文件中的 派单状态为手动派单
|
||||
|
||||
128
service/opcua_angle_uploader.py
Normal file
128
service/opcua_angle_uploader.py
Normal file
@ -0,0 +1,128 @@
|
||||
from opcua import Client, ua
|
||||
import traceback
|
||||
import threading
|
||||
import time
|
||||
|
||||
class OPCAngleUploader:
|
||||
"""下料斗夹爪角度上传OPC"""
|
||||
def __init__(self, opc_server_url="opc.tcp://localhost:4840/zjsh_feed/server/",
|
||||
angle_node_id = "ns=2;i=13", upload_interval=0.5,
|
||||
angle_threshold=5.0, angle_min = 3.0):
|
||||
# OPC服务器配置
|
||||
self.opc_server_url = opc_server_url
|
||||
self.upload_interval = upload_interval # 固定0.5秒上传
|
||||
self.angle_threshold = angle_threshold # 角度变化阈值(超过阈值才更新角度)
|
||||
self.angle_min = angle_min # 角度最小值(低于该值为误判的角度,不更新)
|
||||
|
||||
# 配置 lower_angle 节点ID(ns=2;i=13)
|
||||
self.angle_node_id = angle_node_id # lower_angle节点ID
|
||||
self.angle_node = None # 缓存节点对象,避免重复获取
|
||||
|
||||
# 线程安全相关
|
||||
self.latest_angle = None # 最新角度缓存
|
||||
self.last_uploaded_angle = None # 上一次成功上传opc的角度
|
||||
self.angle_lock = threading.Lock() # 角度读写锁
|
||||
self.exit_event = threading.Event() # 线程退出信号
|
||||
self.upload_thread = None # 上传线程
|
||||
|
||||
# OPC客户端状态
|
||||
self.opc_client = None
|
||||
self.is_connected = False
|
||||
|
||||
def connect_opc(self):
|
||||
"""连接OPC服务器, 获取lower_angle节点对象"""
|
||||
try:
|
||||
# 关闭旧连接
|
||||
if self.opc_client:
|
||||
try:
|
||||
self.opc_client.disconnect()
|
||||
except:
|
||||
pass
|
||||
|
||||
# 初始化客户端并连接
|
||||
self.opc_client = Client(self.opc_server_url)
|
||||
self.opc_client.connect()
|
||||
|
||||
# 获取目标节点(仅获取一次,提升效率)
|
||||
self.angle_node = self.opc_client.get_node(self.angle_node_id)
|
||||
self.is_connected = True
|
||||
# print(f"[OPC] 连接成功,已获取节点:{self.angle_node_id}(lower_angle)")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[OPC] 连接/获取节点失败:{e}")
|
||||
self.is_connected = False
|
||||
self.angle_node = None
|
||||
return False
|
||||
|
||||
def update_angle(self, angle):
|
||||
"""线程安全更新最新角度值"""
|
||||
with self.angle_lock:
|
||||
self.latest_angle = angle
|
||||
|
||||
def _upload_worker(self):
|
||||
"""上传线程核心: 每隔0.5秒写入角度节点"""
|
||||
# 初始连接(失败则5秒重试)
|
||||
while not self.exit_event.is_set() and not self.connect_opc():
|
||||
# print(f"[OPC] 初始连接失败,5秒后重试...")
|
||||
time.sleep(5)
|
||||
|
||||
# 循环上传
|
||||
while not self.exit_event.is_set():
|
||||
try:
|
||||
# 连接失效则重连
|
||||
if not self.is_connected or not self.angle_node:
|
||||
self.connect_opc()
|
||||
time.sleep(1)
|
||||
continue
|
||||
|
||||
# 线程安全读取角度
|
||||
with self.angle_lock:
|
||||
current_angle = self.latest_angle
|
||||
last_angle = self.last_uploaded_angle
|
||||
if current_angle is None:
|
||||
time.sleep(self.upload_interval)
|
||||
continue
|
||||
|
||||
need_upload = False
|
||||
if last_angle is None:
|
||||
need_upload = True
|
||||
else:
|
||||
angle_diff = abs(float(current_angle) - float(last_angle))
|
||||
if angle_diff >= self.angle_threshold:
|
||||
need_upload = True
|
||||
|
||||
# 写入角度到OPC节点
|
||||
if need_upload and current_angle > self.angle_min:
|
||||
angle_variant = ua.Variant(float(current_angle), ua.VariantType.Float)
|
||||
self.angle_node.set_value(angle_variant)
|
||||
# 更新上一次上传的角度
|
||||
with self.angle_lock:
|
||||
self.last_uploaded_angle = current_angle
|
||||
|
||||
# 0.5秒间隔
|
||||
time.sleep(self.upload_interval)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[OPC] 上传异常:{e}")
|
||||
traceback.print_exc()
|
||||
self.is_connected = False # 标记连接失效,触发重连
|
||||
time.sleep(1)
|
||||
|
||||
def start_upload(self):
|
||||
"""启动上传线程(守护线程,随进程退出)"""
|
||||
if not self.upload_thread or not self.upload_thread.is_alive():
|
||||
self.exit_event.clear()
|
||||
self.upload_thread = threading.Thread(target=self._upload_worker, daemon=True)
|
||||
self.upload_thread.start()
|
||||
|
||||
def stop_upload(self):
|
||||
"""停止线程并关闭OPC连接"""
|
||||
self.exit_event.set()
|
||||
if self.upload_thread and self.upload_thread.is_alive():
|
||||
self.upload_thread.join(timeout=2)
|
||||
if self.opc_client:
|
||||
try:
|
||||
self.opc_client.disconnect()
|
||||
except:
|
||||
pass
|
||||
self.is_connected = False
|
||||
@ -208,7 +208,7 @@ class MainWindow(QWidget):
|
||||
|
||||
if artifact.MouldCode: # 更新模具号
|
||||
self.segment_task_widget.set_task_id(f"task{index}", artifact.MouldCode)
|
||||
if artifact.BetonVolume: # 更新浇筑方量
|
||||
if artifact.BetonVolume is not None: # 更新浇筑方量
|
||||
self.segment_task_widget.set_task_volume(f"task{index}", artifact.BetonVolume)
|
||||
if artifact.BeginTime: # 更新时间 (管片任务的开始时间)
|
||||
# print("artifact.BeginTime: ", artifact.BeginTime)
|
||||
@ -285,7 +285,7 @@ class MainWindow(QWidget):
|
||||
# 设置派单任务的tasks信息
|
||||
if record.MouldCode:
|
||||
self.dispatch_task_widget.set_task_id(task_name, record.MouldCode)
|
||||
if record.FBetonVolume:
|
||||
if record.FBetonVolume is not None:
|
||||
self.dispatch_task_widget.set_task_volume(task_name, record.FBetonVolume)
|
||||
if record.CreateTime:
|
||||
self.dispatch_task_widget.set_task_time(task_name, self.convert_to_ampm(record.CreateTime))
|
||||
|
||||
@ -193,7 +193,7 @@ class HopperWidget(QWidget):
|
||||
# 此时,点击按钮为关闭破拱
|
||||
self.lower_arch_breaking_status = False
|
||||
self.lower_arch_label.setHidden(True)
|
||||
self.upper_arch_breaking_signal.emit(self.lower_arch_breaking_status)
|
||||
self.lower_arch_breaking_signal.emit(self.lower_arch_breaking_status)
|
||||
|
||||
def create_lower_hopper(self):
|
||||
"""创建下位料斗Widget"""
|
||||
|
||||
@ -89,11 +89,11 @@ class LinearProductionProgress(QWidget):
|
||||
self.fg_label.move(0, 0)
|
||||
self.fg_label.raise_()
|
||||
|
||||
# 百分比标签(宽33px,高19px,右偏9px)
|
||||
# 百分比标签(宽39px,高19px,右偏9px)
|
||||
self.percent_label = QLabel(self)
|
||||
self.percent_label.setText("0%")
|
||||
self.percent_label.setAlignment(Qt.AlignCenter)
|
||||
self.percent_label.setFixedSize(33, 19)
|
||||
self.percent_label.setFixedSize(39, 19)
|
||||
# self.percent_label.setStyleSheet(
|
||||
# """
|
||||
# color: white;
|
||||
@ -135,9 +135,9 @@ class LinearProductionProgress(QWidget):
|
||||
fg_width = int(450 * (self._progress + 4) / 100)
|
||||
self.fg_label.setFixedWidth(fg_width)
|
||||
|
||||
# 计算百分比标签位置:进度条右边缘 - 9px(偏移) - 标签宽度(33px)
|
||||
# 计算百分比标签位置:进度条右边缘 - 9px(偏移) - 标签宽度(39px)
|
||||
if fg_width > 60: # 当上层进度条宽度大于60px,开始移动
|
||||
label_x = fg_width - 9 - 33
|
||||
label_x = fg_width - 9 - 39
|
||||
# 移动百分比标签
|
||||
self.percent_label.move(label_x, 0)
|
||||
else:
|
||||
|
||||
@ -131,8 +131,8 @@ class TaskWidget(QWidget):
|
||||
# 水平布局2:方量 + / + 时间 + 状态图标
|
||||
row2_layout = QHBoxLayout()
|
||||
# 方量标签
|
||||
volume_label = QLabel("方量 200")
|
||||
volume_label.setStyleSheet("color: #a1c1d7; font-size: 14px;padding-left: 6px;")
|
||||
volume_label = QLabel("方量 99")
|
||||
volume_label.setStyleSheet("color: #a1c1d7; font-size: 14px;padding-left: 1px;")
|
||||
controls["volume_label"] = volume_label
|
||||
row2_layout.addWidget(volume_label)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user