From 5e8f2917385774883ef014b05030265f76d41ddd Mon Sep 17 00:00:00 2001 From: yanganjie Date: Tue, 7 Apr 2026 16:07:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=E6=98=BE=E7=A4=BA(=E7=94=9F=E4=BA=A7=E8=BF=9B=E5=BA=A6?= =?UTF-8?q?=E6=9D=A1=E3=80=81=E4=B8=8A=E6=96=99=E6=96=97=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E7=8A=B6=E6=80=81)=E3=80=81=E5=A2=9E=E5=8A=A0=E4=BA=86?= =?UTF-8?q?=E5=B0=86=E8=A7=92=E5=BA=A6=E4=BF=A1=E6=81=AF=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?opc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/opc_config.ini | 1 + controller/hopper_controller.py | 97 ++++++++----- controller/main_controller.py | 160 ++++++++++++++------- service/opcua_angle_uploader.py | 128 +++++++++++++++++ view/main_window.py | 4 +- view/widgets/hopper_widget.py | 2 +- view/widgets/production_progress_widget.py | 8 +- view/widgets/task_widget.py | 4 +- 8 files changed, 312 insertions(+), 92 deletions(-) create mode 100644 service/opcua_angle_uploader.py diff --git a/config/opc_config.ini b/config/opc_config.ini index 78584b1..926d457 100644 --- a/config/opc_config.ini +++ b/config/opc_config.ini @@ -22,6 +22,7 @@ lower_is_arch = 2:lower,2:lower_is_arch lower_angle = 2:lower,2:lower_angle mould_finish_weight = 2:mould,2:mould_finish_weight mould_need_weight = 2:mould,2:mould_need_weight +mould_finish_ratio=2:mould,2:mould_finish_ratio mould_frequency = 2:mould,2:mould_frequency mould_vibrate_status = 2:mould,2:mould_vibrate_status feed_status = 2:mould,2:feed_status diff --git a/controller/hopper_controller.py b/controller/hopper_controller.py index 92ba38f..d132fa4 100644 --- a/controller/hopper_controller.py +++ b/controller/hopper_controller.py @@ -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): diff --git a/controller/main_controller.py b/controller/main_controller.py index 4451339..7520abe 100644 --- a/controller/main_controller.py +++ b/controller/main_controller.py @@ -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) # 关闭,手动派单 - # 修改系统配置文件中的 派单状态为手动派单... + # 修改系统配置文件中的 派单状态为手动派单 diff --git a/service/opcua_angle_uploader.py b/service/opcua_angle_uploader.py new file mode 100644 index 0000000..6bf0c79 --- /dev/null +++ b/service/opcua_angle_uploader.py @@ -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 \ No newline at end of file diff --git a/view/main_window.py b/view/main_window.py index dae6a89..2a4f567 100644 --- a/view/main_window.py +++ b/view/main_window.py @@ -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)) diff --git a/view/widgets/hopper_widget.py b/view/widgets/hopper_widget.py index d4fc522..027d29c 100644 --- a/view/widgets/hopper_widget.py +++ b/view/widgets/hopper_widget.py @@ -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""" diff --git a/view/widgets/production_progress_widget.py b/view/widgets/production_progress_widget.py index c766307..a70883f 100644 --- a/view/widgets/production_progress_widget.py +++ b/view/widgets/production_progress_widget.py @@ -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: diff --git a/view/widgets/task_widget.py b/view/widgets/task_widget.py index 4e94046..a0be894 100644 --- a/view/widgets/task_widget.py +++ b/view/widgets/task_widget.py @@ -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)