from pickle import FALSE from re import S from cv2.gapi import ov from config.settings import app_set_config from core.relay_feed import RelayFeedController from hardware.relay import RelayController from hardware.transmitter import TransmitterController import time import threading from datetime import datetime import logging import queue from hardware.upper_plc import OmronFinsPollingService from vision.muju_cls.muju_utils import run_stable_classification_loop from vision.camera_picture import save_camera_picture from busisness.blls import ArtifactBll,PDRecordBll from busisness.models import ArtifactInfoModel,PDRecordModel from service.mould_service import app_web_service from core.system_state import SystemState,FeedStatus,Upper_Door_Position from dataclasses import asdict import json import math class VisualCallback: # 类变量,用于存储实例引用,实现单例检测 _instance = None _lock = threading.Lock() # def __new__(cls,*args, **kwargs): # """检测实例是否存在,实现单例模式""" # with cls._lock: # if cls._instance is None: # cls._instance = super().__new__(cls) # return cls._instance def __init__(self, relay_controller:RelayController, transmitter_controller:TransmitterController, state:SystemState=None): """初始化视觉回调处理器""" # 避免重复初始化 if hasattr(self, '_initialized') and self._initialized: return self.relay_controller = relay_controller self.transmitter_controller = transmitter_controller self.relay_feed=RelayFeedController(relay_controller,state) self.pd_record_bll=PDRecordBll() self.state=state # 线程安全的参数传递 self._new_data_available = threading.Event() self._is_processing = threading.Lock() #diff参数 self._is_processing_diff = threading.Lock() self._new_data_diff = threading.Event() self._current_diff=0 self._current_diff_area=[] self._is_diff_save=False self._stop_event = threading.Event() # 添加下料斗门控制锁,防止两个线程同时控制 self._door_control_lock = threading.Lock() # 记录当前控制门的线程名称,用于调试 self._current_controlling_thread = None #是否启动后的第一个模具 self._is_first_module=True #上一次派单方量 self.prev_pd_volume=0.0 self.init_val() # self._setup_logging_2() #F块完成重量的70%,控制夹脚,F块多于这个比例就没有记录了(注意) self._max_f_angle_ratio=0.7 #完成多少,调整角度比例 ,多于0.8就没记录了(注意) self._max_angle_radio=0.8 #重量大于95%,停留时间2秒,其他的1秒 self._weight_ratio_955=0.955 #完成多少,忽略未浇筑满 self._max_ignore_radio=0.8 self._mould_accept_aligned=None self._mould_before_aligned=False #模具开始浇筑时间 self._time_mould_begin='' #模具结束浇筑时间 self._time_mould_end='' #记录当前模具信息model self._cur_mould_model=None self.plc_valid_data=[5,37] self.plc_valid_data_jbl=[66,98] # self.db_queue=queue.Queue() # 获取视觉数据线程(angle_visual_callback推送的数据),并进行处理,注意处理视觉进行夹角控制 self.callback_thread = None self.diff_thread = None def init_val(self): #初始化值 """初始化视觉回调处理器""" self.angle_mode = "normal" self.overflow = False self.is_start_visual=False # 线程安全的参数传递 self._current_angle = None self._overflow_detected = None # 新增标志位:指示safe_control_lower_close是否正在执行 self._is_safe_closing = False self._is_feed_start=True #未浇筑满时间,用于确定是否进入未浇筑满 self._before_finish_time=None #进入未浇筑满状态标志位 self._is_before_finish=False #是否浇筑满标志位 self._is_finish=False #用于保存diff标志位 # self._is_diff_save=False #用于判断当前判断是否对齐(diff) self._is_diff_unaligned=False self._diff_f_val=0 self._diff_f_area=[] #浇筑完成比例(重量) self._is_finish_ratio=0 #下料阶段,汇总时用枚举 self._is_feed_stage=0 #振动相关参数 self._last_arch_one_weight=0 self._last_arch_two_weight=0 self._last_arch_three_weight=0 self._last_arch_four_weight=0 self._last_arch_five_weight=0 self._last_arch_time=0 #是否为F块 self._is_small_f=None #采集数据用,下料重量=之前下的重量+最后一阶段(下-->模具车)重量差 #记录最后一次下料斗到模具车前的重量 self._finish_weight=0 #记录最后一次下料斗到车初始重量 self._inital_finish_lweight=0 #记录视觉停止下料时的重量(计算后面加了多少) self._last_lower_weight=0 #每片开始下料斗的重量 self._init_lower_weight=0 # 初始化控制间隔和堆料状态跟踪属性 self._last_overflow_state = False self._last_control_time = 0 self._is_running=True self._is_stop_one_seconds=False self._initialized = True self.plc_data=None self._mould_need_weight=0 #点动等级 self._point_speed_grade=0 self._point_weight=0 def start_visual_thread(self)->bool: """浇筑状态回调线程""" self.callback_thread = threading.Thread( target=self._run_thread_loop, daemon=True ) self.callback_thread.start() #diff线程值传递后处理 self.diff_thread = threading.Thread( target=self._diff_temp, daemon=True ) self.diff_thread.start() return True def angle_visual_callback(self, current_angle, overflow_detected, mould_aligned): """ 视觉控制主逻辑,供外部推送数据 使用单个持续运行的线程,通过参数设置传递数据 如果线程正在处理数据,则丢弃此次推送 """ #print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到推送数据") # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 if not self._is_processing.acquire(blocking=False): print("回调线程仍在执行,丢弃此次推送数据") return try: # 更新参数 if overflow_detected is not None: # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到溢料:{overflow_detected}") self._overflow_detected = overflow_detected if current_angle is not None: #print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到角度:{current_angle}") self._current_angle = current_angle if mould_aligned is not None: #print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到对齐:{mould_aligned}") self._mould_accept_aligned=mould_aligned # 通知线程有新数据可用 self._new_data_available.set() finally: # 释放处理锁 self._is_processing.release() def diff_visual_callback(self, current_diff,current_area): """ 视觉模型diff回调 """ #print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到推送数据") # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 if not self._is_processing_diff.acquire(blocking=False): print("222回调线程仍在执行,丢弃此次推送数据") return try: # 更新参数 if current_diff is not None: # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到diff:{current_diff}") self._current_diff = current_diff self._diff_f_val=current_diff if current_area is not None: # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到area:{current_area}") self._current_diff_area = current_area self._diff_f_area = current_area # 通知线程有新数据可用 self._new_data_diff.set() finally: # 释放处理锁 self._is_processing_diff.release() def _diff_temp(self): """ 接受视觉回调数据 线程主循环,持续运行 等待新数据,然后调用处理方法 """ _temp_diff_count=0 _temp_area_count=0 _temp_diff_str2='' _temp_area_str2='' while not self._stop_event.is_set(): # print('-----等待diff 数据------') # 等待新数据可用 self._new_data_diff.wait() # 重置事件 self._new_data_diff.clear() #_is_diff_save是否完成此片 if self._is_diff_save: # print('-----进入diff 数据------') #完成了此片,然后是对齐状态 if not self._is_diff_unaligned: # 处理数据 # print('-----进入对齐数据------') if self._current_diff is not None and self._current_diff_area is not None: _timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if _temp_diff_count<=10 and self._current_diff!=0: _temp_diff_str=f"diff , {_timestamp} , {self._current_diff}\n" _temp_diff_count+=1 with open('weight.txt', 'a') as f: f.write(_temp_diff_str+'\n') # print('-----保存成功(diff 数据)------') if _temp_area_count<=10 and self._current_diff_area!=[]: _temp_area_str=f"area , {_timestamp} , {str(self._current_diff_area)}\n" _temp_area_count+=1 with open('weight.txt', 'a') as f: f.write(_temp_area_str+'\n') # print('-----保存成功(area 数据)------') if _temp_diff_count>=10 and _temp_area_count>=10: self._is_diff_save=False time.sleep(1) continue # else: #变成了未对齐,拉起盖板后,重新计数 # if _temp_diff_count>=10 and _temp_area_count>=10: # _temp_diff_count=0 # _temp_area_count=0 # self._is_diff_save=False # _temp_diff_str='' # _temp_area_str='' self._current_diff=0 self._current_diff_area=[] _temp_diff_count=0 _temp_area_count=0 _temp_diff_str='' _temp_area_str='' time.sleep(1) def _arch_loop(self): """破拱线程""" while self._is_running: try: current_time = time.time() # 检查下料斗破拱(只有在下料过程中才检查) if self._is_feed_stage==1: # 下料斗--》模具车 _arch_weight = self.transmitter_controller.read_data(2) if _arch_weight is not None and _arch_weight>0: # 检查重量变化是否过慢 _weight_changed=abs(_arch_weight - self._last_arch_one_weight) #_last_arch_one_weight默认为0,一开始就进入振动 print(f'---------------第一阶段,重量变化:{_weight_changed}------------------') if (_weight_changed< 200) and \ (current_time - self._last_arch_time) >= 2: self._last_arch_time = current_time print('---------------------第一阶段振动5秒(小于200KG)-----------------') self.relay_feed.control_arch_lower_open_sync(5) self._last_arch_one_weight = _arch_weight continue self._last_arch_one_weight = _arch_weight elif self._is_feed_stage==2: #上料斗到下料斗,料多,谨慎振动 _arch_weight = self.transmitter_controller.read_data(1) if _arch_weight is not None and _arch_weight>0: # 检查重量变化是否过慢 _weight_changed=abs(_arch_weight - self._last_arch_two_weight) print(f'---------------第二阶段,重量变化:{_weight_changed}------------------') if (_weight_changed < 100) and \ (current_time - self._last_arch_time) >= 2: self._last_arch_time = current_time #print('---------------------第二阶段振动3秒-----------------') # self.relay_feed.control_arch_upper_open_sync(3) self._last_arch_two_weight = _arch_weight continue self._last_arch_two_weight = _arch_weight elif self._is_feed_stage==3: #第二次下料斗-》模具车 _arch_weight = self.transmitter_controller.read_data(2) if _arch_weight is not None and _arch_weight>0: #刚开始不需要振动,料太多 if self._last_arch_three_weight>0: _weight_changed=abs(_arch_weight - self._last_arch_three_weight) # 检查重量变化是否过慢 print(f'---------------第三阶段,重量变化:{_weight_changed}------------------') if (_weight_changed < 100) and \ (current_time - self._last_arch_time) >= 2: self._last_arch_time = current_time print('---------------------第三阶段振动5秒(小于100KG)-----------------') self.relay_feed.control_arch_lower_open_sync(5) self._last_arch_three_weight = _arch_weight continue self._last_arch_three_weight = _arch_weight elif self._is_feed_stage==4: #上料斗--》下料斗 _arch_weight = self.transmitter_controller.read_data(1) if _arch_weight is not None and _arch_weight>0: # 检查重量变化是否过慢 _weight_changed=abs(_arch_weight - self._last_arch_four_weight) print(f'---------------第二阶段,重量变化:{_weight_changed}------------------') if (_weight_changed < 200) and \ (current_time - self._last_arch_time) > 2: self._last_arch_time = current_time print('---------------------第四阶段振动5秒-----------------') #重量不准,暂时不振动 # self.relay_feed.control_arch_upper_open_sync(5) self._last_arch_four_weight = _arch_weight continue self._last_arch_four_weight = _arch_weight elif self._is_feed_stage==5: #下料斗->模具车 _arch_weight = self.transmitter_controller.read_data(2) if _arch_weight is not None and _arch_weight>0: if self._last_arch_five_weight>0: _weight_changed=abs(_arch_weight - self._last_arch_five_weight) print(f'---------------第五阶段,重量变化:{_weight_changed}------------------') _min_arch_weight=20 if self._is_finish_ratio= 2: self._last_arch_time = current_time print(f'---------------------第五阶段振动3秒(小于{_min_arch_weight}kg))-----------------') self.relay_feed.control_arch_lower_open_sync(3) self._last_arch_five_weight = _arch_weight continue self._last_arch_five_weight = _arch_weight # 更新最后读取时间 self._last_arch_time = current_time time.sleep(2) except Exception as e: print(f"监控线程错误: {e}") def _aligned_get_times(self,flag): """ 获取对齐,1为对齐,0为未对齐 """ _current_times=time.time() _temp_aligned_count=0 if flag==1: while time.time()-_current_times<=2: # print(f'-------------{self._mould_accept_aligned}-----------------') if self._mould_accept_aligned=='盖板对齐': _temp_aligned_count=_temp_aligned_count+1 else: _temp_aligned_count=0 if _temp_aligned_count>0: print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板对齐,次数:{_temp_aligned_count}-----------------') time.sleep(0.2) self._mould_accept_aligned='' if _temp_aligned_count>=8: return True else: return False elif flag==2: while time.time()-_current_times<=5: if self._mould_accept_aligned=='盖板未对齐': _temp_aligned_count=_temp_aligned_count+1 else: _temp_aligned_count=0 if _temp_aligned_count>0: print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板未对齐,次数:{_temp_aligned_count}-----------------') time.sleep(0.2) self._mould_accept_aligned='' if _temp_aligned_count>=20: self._is_diff_unaligned=True return True else: self._is_diff_unaligned=False return False def _no_aligned_diff(self): """ diff 未对齐检测 """ _current_times=time.time() _temp_aligned_count=0 while time.time()-_current_times<=1: # print(f'-------------{self._mould_accept_aligned}-----------------') if self._mould_accept_aligned=='盖板未对齐': _temp_aligned_count=_temp_aligned_count+1 else: _temp_aligned_count=0 # print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板对齐,次数:{_temp_aligned_count}-----------------') time.sleep(0.2) if _temp_aligned_count>=3: return True else: return False def _run_thread_loop(self): """ 接受视觉回调数据 线程主循环,持续运行 等待新数据,然后调用处理方法 """ while not self._stop_event.is_set(): # 等待新数据可用 self._new_data_available.wait() # 重置事件 self._new_data_available.clear() # 获取当前参数(使用临时变量避免被其他线程修改) current_angle = self._current_angle overflow_detected = self._overflow_detected self._is_feed_start=True if self.is_start_visual: # 处理数据 self._process_angle_callback(current_angle, overflow_detected) time.sleep(0.1) def _run_feed(self): _is_api_request=True while True: # print("------------已启动----------------") if self._is_feed_start: # if self.plc_data==5: #_is_finish_ratio完成 比例,根据重量过滤一下 if self._is_first_module and self._overflow_detected=='未堆料': #第一次打开 ,未堆料,检测对齐 if _is_api_request: self.get_current_mould() _is_api_request=False _is_aligned=self._aligned_get_times(1) if _is_aligned: _is_api_request=True print('------------启动程序后,进入第一块-------------') self._is_first_module=False self._mould_before_aligned=True if self._cur_mould_model: self.state._db_mould_status={ 'mould_code':self._cur_mould_model.MouldCode, 'status':2, 'weight':0, } _current_weight=self.transmitter_controller.read_data(2) if _current_weight: self._init_lower_weight=_current_weight else: print('------------获取上料斗重量失败-------------') return self.state._feed_status=FeedStatus.FCheckGB # self.is_start_visual=True self.run_feed_all() elif self._is_finish and self._is_finish_ratio>=0.7: #后续流程--》检查到未对齐,--》后又对齐+未堆料 #print('------------------进入连续块检测------------------') if self._mould_before_aligned: #未对齐,检测对齐 _is_not_aligned=self._aligned_get_times(2) if _is_not_aligned: #标志位 self._mould_before_aligned=False #print('------------连续盖板未对齐-------------') else: if _is_api_request: self.get_current_mould() _is_api_request=False _is_aligned=self._aligned_get_times(1) if _is_aligned and self._overflow_detected=='未堆料': print('------------进入连续生产-------------') self._mould_before_aligned=True _is_api_request=True _current_weight=self.transmitter_controller.read_data(2) if not _current_weight: print('------------获取上料斗重量失败-------------') return # print('-----------进入连续块111111-----------') # self.is_start_visual=True if self._last_lower_weight>0: with open('weight.txt', 'a') as f: f.write(f"补料:{self._last_lower_weight-_current_weight}\n\n"+"="*32) self.init_val() self._init_lower_weight=_current_weight self.state._feed_status=FeedStatus.FCheckGB self.run_feed_all() # else: # print("-----------上料斗未就位----------------") # print("---------3--上料斗未就位----------------") time.sleep(0.2) def safe_control_lower_close(self,duration=3): """线程安全的下料斗关闭方法""" thread_name = threading.current_thread().name # print(f"[{thread_name}] 尝试关闭下料斗...") # 设置标志位,指示正在执行安全关闭操作 self._is_safe_closing = True try: with self._door_control_lock: self._current_controlling_thread = thread_name print(f"关闭下料斗{duration}秒") self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') time.sleep(duration) self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') self._current_controlling_thread = None #print(f"[{thread_name}] 释放下料斗控制权") finally: # 无论成功失败,都要重置标志位 self._is_safe_closing = False def close_lower_door_visual(self): """关闭下料斗门""" self.is_start_visual=False #休眠让视觉先结束 time.sleep(0.5) self.safe_control_lower_close() def _visual_close(self): self.is_start_visual=False self._is_finish=True self.state.vf_status=3 self.state._feed_status=FeedStatus.FFinished self._is_feed_stage=0 print(f'--------进入关闭(浇筑满)-----------') self.safe_control_lower_close(3) print(f'--------浇筑完成-----------') if self._cur_mould_model: self.state._db_mould_status={ 'mould_code':self._cur_mould_model.MouldCode, 'status':3, 'weight':self.state._mould_finish_weight, } # try: # self.db_queue.put_nowait({ # "f":self._is_small_f, # "Status": 3 # }) # except queue.Full: # print("数据库队列已满,无法添加数据") #记录重量 _current_weight=self.transmitter_controller.read_data(2) if _current_weight is not None: self._last_lower_weight=_current_weight self._finish_weight= self._finish_weight+(self._inital_finish_lweight-_current_weight) with open('weight.txt', 'a') as f: timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if self._is_small_f: if self._cur_mould_model: f.write(f"{self._time_mould_begin},{timestamp},{self._cur_mould_model.MouldCode},F,{self._finish_weight}\n") else: f.write(f"{self._time_mould_begin},{timestamp},F,{self._finish_weight}\n") else: if self._cur_mould_model: f.write(f"{self._time_mould_begin},{timestamp},{self._cur_mould_model.MouldCode},B,{self._finish_weight}\n") else: f.write(f"{self._time_mould_begin},{timestamp},B,{self._finish_weight}\n") #开启保存diff self._is_diff_save=True #保存图片 save_camera_picture() def run_feed_all(self): """ 全流程下料:包括判断模具类型 """ _is_f= run_stable_classification_loop() print(f'------------已判断出模具类型: {_is_f}-------------') if _is_f is not None: if _is_f=='模具车1': self._is_small_f=True print('-------------F块模具--------------') # print('-------------F块模具--------------') # print('-------------F块模具--------------') # self.send_pd_data() self.run_feed_f() elif _is_f=='模具车2': self._is_small_f=False print('-------------B-L模具---------------') # self.send_pd_data() self.run_feed() if self._is_small_f is None: print('-----------未判断出模具类型--------------') return def run_feed_f(self): """第一阶段下料:下料斗向模具车下料(低速)""" print("--------------------开始下料(F块)--------------------") self._time_mould_begin=datetime.now().strftime("%Y-%m-%d %H:%M:%S") loc_mitter=self.transmitter_controller max_weight_none=5 cur_weight_none=0 initial_lower_weight=self._init_lower_weight first_finish_weight=0 self._finish_weight=first_finish_weight self._inital_finish_lweight=initial_lower_weight self._mould_need_weight=0.54*2416 need_total_weight=self._mould_need_weight if initial_lower_weight>100: self.state._feed_status=FeedStatus.FFeed5 self.state.vf_status=2 if not self._is_finish: self.is_start_visual=True self._is_feed_stage=5 while not self._is_finish: current_weight = loc_mitter.read_data(2) if current_weight is None: cur_weight_none+=1 if cur_weight_none>max_weight_none: #如果重量连续5次为None,认为下料斗未就位,跳出循环 print('------------f下到模具车,下料斗重量异常----------------') print('------------f下到模具车,下料斗重量异常----------------') self.close_lower_door_visual() return #视觉处理关闭,异常的话重量没有生效 continue cur_weight_none=0 first_finish_weight=initial_lower_weight-current_weight self._is_finish_ratio=(first_finish_weight)/need_total_weight self.state._mould_finish_weight=first_finish_weight self.state._mould_finish_ratio=self._is_finish_ratio print(f'------------已下料比例: {self._is_finish_ratio}-------------') # if self._is_finish_ratio>=1: #关5秒 #大于0.7后不再检测了,直接交给视觉控制夹脚 # print(f'------------已下料比例: {self._is_finish_ratio}-------------') # break # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') time.sleep(0.5) # initial_lower_weight=_current_lower_weight print(f'------------已下料(F): {first_finish_weight}kg-------------') print(f'------------已下料(F): {first_finish_weight}kg-------------') # print(f'------------已完成-------------') def run_feed(self): """第一阶段下料:下料斗向模具车下料(低速)""" print("--------------------开始下料(普通块)--------------------") self._time_mould_begin=datetime.now().strftime("%Y-%m-%d %H:%M:%S") loc_relay=self.relay_feed loc_mitter=self.transmitter_controller max_weight_none=5 cur_weight_none=0 initial_lower_weight=self._init_lower_weight # initial_upper_weight=loc_mitter.read_data(1) first_finish_weight=0 self._mould_need_weight=1.91*2416 need_total_weight=self._mould_need_weight # start_time=None self.is_start_visual=True self.state._feed_status=FeedStatus.FFeed1 self.state.vf_status=1 if initial_lower_weight>100: #下料斗的料全部下完 self._is_feed_stage=1 while not self._is_finish: current_weight = loc_mitter.read_data(2) if current_weight is None: cur_weight_none+=1 if cur_weight_none>max_weight_none: print("-----------下料斗重量异常(第一次下到模具车)--------------") self.close_lower_door_visual() return continue cur_weight_none=0 self.state._mould_finish_weight=initial_lower_weight-current_weight self._is_finish_ratio= self.state._mould_finish_weight/need_total_weight self.state._mould_finish_ratio=self._is_finish_ratio print(f'------------已下料比例: {self._is_finish_ratio}-------------') if current_weight<250 and current_weight>0: # if current_weight>100: #100,上面粘贴的,振动一下 # loc_relay.control_arch_lower_open_async(5) self.close_lower_door_visual() break time.sleep(1) _current_lower_weight=loc_mitter.read_data(2) if _current_lower_weight is None: print("-------下料斗重量异常---------") return first_finish_weight=initial_lower_weight-_current_lower_weight # initial_lower_weight=_current_lower_weight print(f'------------已下料(第一次): {first_finish_weight}kg-------------') print(f'------------已下料(第一次): {first_finish_weight}kg-------------') self._is_feed_stage=0 while self.plc_data not in self.plc_valid_data: #print('------------上料斗未就位----------------') # print('------------上料斗未就位----------------') time.sleep(1) if self.plc_data in self.plc_valid_data: print(f'------------上料斗就位(上料斗往下料斗阶段)-------------') #打开上料斗出砼门,开5就,开三分之一下 loc_relay.control_upper_open_sync(6) self._is_feed_stage=2 loc_time_count=1 upper_open_time=time.time() self.state._upper_door_closed=1 while not self._is_finish: current_upper_weight = loc_mitter.read_data(1) if current_upper_weight is None: cur_weight_none+=1 if cur_weight_none>max_weight_none: #如果重量连续5次为None,认为上料斗未就位,跳出循环 print('------------第一次上到下,上料斗重量异常----------------') print('------------第一次上到下,上料斗重量异常----------------') self.state._upper_door_closed=0 loc_relay.control_upper_close_sync(5+loc_time_count) return continue cur_weight_none=0 _two_lower_weight=loc_mitter.read_data(2) if _two_lower_weight is None: _two_lower_weight=0 if (current_upper_weight<3200 and current_upper_weight>0) or _two_lower_weight>3200: #关5秒,loc_time_count多关一秒 self.state._upper_door_closed=0 loc_relay.control_upper_close_sync(5+loc_time_count) break else: if time.time()-upper_open_time>=4: if loc_time_count<6: upper_open_time=time.time() loc_relay.control_upper_open_sync(0.8) loc_time_count=loc_time_count+0.8 else: time.sleep(0.5) else: loc_relay.control_upper_close_sync(6+loc_time_count) self.state._upper_door_closed=0 self.is_start_visual=True initial_lower_weight=loc_mitter.read_data(2) if initial_lower_weight is None: print("-------下料斗重量异常(第二次下料到模具车)---------") return self._is_feed_stage=3 while not self._is_finish: current_weight = loc_mitter.read_data(2) if current_weight is None: cur_weight_none+=1 if cur_weight_none>max_weight_none: print("-------下料斗重量异常(第二次下料到模具车)---------") self.close_lower_door_visual() return continue cur_weight_none=0 self.state._mould_finish_weight=first_finish_weight+initial_lower_weight-current_weight self._is_finish_ratio=self.state._mould_finish_weight/need_total_weight self.state._mould_finish_ratio=self._is_finish_ratio if current_weight<250: # if current_weight>100: #100,上面粘贴的,振动一下 # loc_relay.control_arch_lower_open_async(5) self.close_lower_door_visual() break # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') time.sleep(0.5) _current_lower_weight=loc_mitter.read_data(2) if _current_lower_weight is None: print("-------下料斗重量异常(第二次下到模)---------") return first_finish_weight=first_finish_weight+initial_lower_weight-_current_lower_weight print(f'------------已下料(第二次): {first_finish_weight}kg-------------') print(f'------------已下料(第二次): {first_finish_weight}kg-------------') self._is_feed_stage=0 if self.plc_data in self.plc_valid_data: self.state._upper_door_closed=2 #第二次上料斗向下料斗转移 loc_relay.control_upper_open_sync(12) loc_time_count=1 upper_open_time=time.time() upper_open_time_2=None #第二次到下料斗还需要的量 #loc_left_need_weight=need_total_weight-first_finish_weight #initial_upper_weight=loc_mitter.read_data(1) #start_time=None self._is_feed_stage=4 while not self._is_finish: # print(f'------------上料斗向下料斗转移22222-------------') current_upper_weight = loc_mitter.read_data(1) if current_upper_weight is None: cur_weight_none+=1 if cur_weight_none>max_weight_none: #如果重量连续5次为None,认为上料斗未就位,跳出循环 print('------------第二次上到下,上料斗重量异常----------------') print('------------第二次上到下,上料斗重量异常----------------') self.state._upper_door_closed=0 loc_relay.control_upper_close_sync(15) return continue cur_weight_none=0 if (current_upper_weight<600 and current_upper_weight>0) or upper_open_time_2 is not None: if upper_open_time_2 is None: upper_open_time_2=time.time() if current_upper_weight<400 or time.time()-upper_open_time_2>5: loc_relay.control_arch_upper_open_async(5) # loc_relay.control_arch_upper_open() loc_relay.control_upper_open_sync(5) self.state._upper_door_closed=2 # start_time=None #5秒后关闭 loc_relay.control_upper_close_after()#control_upper_close_sync(8+loc_time_count) break time.sleep(0.5) else: if time.time()-upper_open_time>=1: # if loc_time_count<6: upper_open_time=time.time() loc_relay.control_upper_open_sync(1.2) loc_time_count=loc_time_count+1 else: time.sleep(0.5) else: loc_relay.control_upper_close_sync(15) self.state._upper_door_closed=0 # time.sleep(0.4) #第三次下料斗转移到模具车 if not self._is_finish: self.is_start_visual=True initial_lower_weight=loc_mitter.read_data(2) self._finish_weight=first_finish_weight self._inital_finish_lweight=initial_lower_weight if initial_lower_weight is None: print("-------下料斗重量异常(第三次下到模具车)---------") return self._is_feed_stage=5 while not self._is_finish: current_weight = loc_mitter.read_data(2) if current_weight is None: cur_weight_none+=1 if cur_weight_none>max_weight_none: #重量异常退出 print('------------第三次下到模具车,下料斗重量异常----------------') self.close_lower_door_visual() return continue cur_weight_none=0 second_finish_weight=initial_lower_weight-current_weight self.state._mould_finish_weight=second_finish_weight+first_finish_weight self._is_finish_ratio=self.state._mould_finish_weight/need_total_weight self.state._mould_finish_ratio=self._is_finish_ratio print(f'------------已下料比例: {self._is_finish_ratio}-------------') # if self._is_finish_ratio>=1: #关5秒 # print(f'------------已下料比例: {self._is_finish_ratio}-------------') # break # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') time.sleep(0.5) # _current_lower_weight=loc_mitter.read_data(2) # first_finish_weight=first_finish_weight+initial_lower_weight-_current_lower_weight # print(f'------------已下料: {first_finish_weight}kg-------------') # print(f'------------已下料: {first_finish_weight}kg-------------') # print(f'------------已完成-------------') def _process_angle_callback(self, current_angle, overflow_detected): """ 实时精细控制 - 基于PID思想,无固定间隔 """ try: # 记录控制时间戳(用于微分计算,而非限制) current_time = time.time() # 确保所有PID相关属性都被正确初始化 if not hasattr(self, '_last_control_time'): self._last_control_time = current_time if not hasattr(self, '_last_error'): self._last_error = 0 if not hasattr(self, '_error_integral'): self._error_integral = 0 # print(f"{self.angle_mode}") self.overflow = overflow_detected in ["大堆料", "小堆料"] if current_angle is None: return print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 角度11: {current_angle:.2f}°,{overflow_detected},diff_f_val:{self._diff_f_val},diff_f_area:{self._diff_f_area}") if self._is_small_f: if self._is_finish_ratio>=1.02: print('重量达到最大比例,浇筑满关闭') self._visual_close() return elif self._is_finish_ratio>=0.9: if (self._diff_f_val>=427 and self._diff_f_val<=450): print('------------diff到达浇筑满-------------') self._visual_close() return elif (len(self._diff_f_area)>0 and self._diff_f_area[-1]>=33400 and self._diff_f_area[-1]<=34500): print('------------area到达浇筑满-------------') self._visual_close() return else: if self._is_finish_ratio>=1.01: print('重量达到最大比例,浇筑满关闭') self._visual_close() return elif self._is_finish_ratio>=0.93: if (self._diff_f_val>=460 and self._diff_f_val<=510): print('------------diff到达浇筑满-------------') self._visual_close() return if (len(self._diff_f_area)>0 and self._diff_f_area[-1]>=38200 and self._diff_f_area[-1]<=41000): print('------------area到达浇筑满-------------') self._visual_close() return if overflow_detected == "未浇筑满" or self._is_before_finish: if self._before_finish_time is None: self._before_finish_time=current_time self.safe_control_lower_close(1) print('-----------------关闭(未浇筑满)--------------------') # time.sleep(3) else: if not self._is_stop_one_seconds: #根据角度来计算还需要多久完全关闭 if current_angle>=20: self.safe_control_lower_close(2) elif current_angle>=10 and current_angle<20: self.safe_control_lower_close(1) elif current_angle>=6 and current_angle<10: self.safe_control_lower_close(0.5) self._is_stop_one_seconds=True elif current_angle>7: #点动状态下,如果关闭后角度大于7度,关紧 self.safe_control_lower_close(0.2) _open_time=0.3 _sleep_time=0.3 _close_time=0.5 if overflow_detected=='浇筑满': if self._is_small_f: self._visual_close() return else: if self._diff_f_val>=410 and self._diff_f_val<450: #排除这个范围的关闭 print(f'浇筑满状态,diff_f_val:{self._diff_f_val},不关闭') _open_time=0.5 _sleep_time=0.3 _close_time=0.7 else: self._visual_close() return # print(f'--------已关闭已关闭-----------') elif overflow_detected=="大堆料": print(f'--------未浇筑满,大堆料-----------') elif overflow_detected=="小堆料": print(f'--------未浇筑满,小堆料-----------') _open_time=0.5 _sleep_time=0.3 _close_time=0.7 else: if self._is_small_f: _open_time=0.6 _sleep_time=0.3 _close_time=0.8 else: if self._is_finish_ratio<0.9: _open_time=1 _sleep_time=0.3 _close_time=1.2 #之前慢的参数 # _open_time=0.8 # _sleep_time=0.3 # _close_time=1 elif self._is_finish_ratio<0.95 and self._is_finish_ratio>=0.9: if self._point_weight>=10: _open_time=0.7 _sleep_time=0.3 _close_time=0.9 else: #之前慢的参数 # _open_time=0.8 # _sleep_time=0.3 # _close_time=1 _open_time=0.9 _sleep_time=0.3 _close_time=1.1 else: _open_time=0.6 _sleep_time=0.3 _close_time=0.8 if self._point_speed_grade==1: if _open_time>0.6: _open_time=0.6 _sleep_time=0.3 _close_time=0.8 elif self._point_speed_grade==2: if _open_time>0.5: _open_time=0.5 _sleep_time=0.3 _close_time=0.7 elif self._point_speed_grade==3: if _open_time>0.4: _open_time=0.4 _sleep_time=0.3 _close_time=0.6 elif self._point_speed_grade==4: if _open_time>0.3: _open_time=0.3 _sleep_time=0.3 _close_time=0.5 _last_finish_ratio=self._is_finish_ratio print(f'--------比例开始:{_last_finish_ratio}-----------') self._pulse_control('open',_open_time) time.sleep(_sleep_time) self._pulse_control('close',_close_time) print(f'--------比例结束:{self._is_finish_ratio}-----------') self._point_weight=(self._is_finish_ratio-_last_finish_ratio)*self._mould_need_weight print(f'--------流速:{self._point_weight}-----------') if self._is_small_f: time.sleep(2.5) else: # if self._is_finish_ratio>= 0.93: # time.sleep(2) # print('--------重量已到95.5%,需要2秒休息-----------') # else: time.sleep(1) #下得过快,需要2秒休息 if self._point_weight>=65: time.sleep(5) self._point_speed_grade=4 elif self._point_weight>=50 and self._point_weight<65: time.sleep(4) self._point_speed_grade=3 elif self._point_weight>=35 and self._point_weight<50: time.sleep(3) self._point_speed_grade=2 elif self._point_weight>=25 and self._point_weight<35: time.sleep(2) self._point_speed_grade=1 elif self._point_weight>=15 and self._point_weight<25: time.sleep(1) self._point_speed_grade=0 else: self._point_speed_grade=0 self._is_before_finish=True if self._is_finish_ratio<=self._max_ignore_radio: #如果重量未达到最大忽略角度,需要跳出 self._is_before_finish=False return elif overflow_detected == "浇筑满": self._visual_close() return else: self._before_finish_time=None #2160KG if self._is_finish_ratio>=0.85 and not self._is_small_f: if overflow_detected == "大堆料": TARGET_ANGLE = 5.0 # 大堆料时控制在15度左右 elif overflow_detected == "小堆料": TARGET_ANGLE = 15.0 # 小堆料时控制在35度左右 else: TARGET_ANGLE = 35.0 # 12.25由25--》35 elif (self._is_finish_ratio>0.7 and self._is_small_f): if overflow_detected == "大堆料": TARGET_ANGLE = 5.0 # 大堆料时控制在15度左右 elif overflow_detected == "小堆料": TARGET_ANGLE = 15.0 # 小堆料时控制在35度左右 else: TARGET_ANGLE = 35.0 # 12.25由25--》35 else: if self._is_feed_stage==1 or self._is_feed_stage==3: #根据溢料状态动态调整目标角度 if overflow_detected == "大堆料": if not self.state.mould_vibrate_status: TARGET_ANGLE = 15.0 # 临时控制变频器堆料时很小 else: TARGET_ANGLE = 35.0 # 大堆料时控制在15度左右 elif overflow_detected == "小堆料": TARGET_ANGLE = 55.0 # 小堆料时控制在35度左右 else: TARGET_ANGLE = 55.0 # 未溢料时开到最大56度 else: if self._is_small_f: #根据溢料状态动态调整目标角度 if overflow_detected == "大堆料": TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 elif overflow_detected == "小堆料": TARGET_ANGLE = 25.0 # 小堆料时控制在35度左右 else: TARGET_ANGLE = 45.0 # 未溢料时开到最大56度 else: #根据溢料状态动态调整目标角度 if overflow_detected == "大堆料": TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 elif overflow_detected == "小堆料": TARGET_ANGLE = 55.0 # 小堆料时控制在35度左右 else: TARGET_ANGLE = 55.0 # 未溢料时开到最大56度 # 确保目标角度在硬件范围内(5-56度) TARGET_ANGLE = max(5.0, min(56.0, TARGET_ANGLE)) # PID控制参数 KP = 0.2 # 比例系数 KI = 0 # 积分系数 KD = 0 # 微分系数 # KP = 0.15 # 比例系数 # KI = 0.008 # 积分系数 # KD = 0.08 # 微分系数 # if TARGET_ANGLE <= 25.0: # KP, KI, KD = 0.18, 0.008, 0.08 # 小角度,强控制 # elif TARGET_ANGLE <= 40.0: # KP, KI, KD = 0.15, 0.01, 0.06 # 中角度 # else: # KP, KI, KD = 0.12, 0.012, 0.04 # 大角度,温和控制 # 计算误差 error = current_angle - TARGET_ANGLE dt = current_time - self._last_control_time # 积分项(抗饱和) self._error_integral += error * dt self._error_integral = max(min(self._error_integral, 50), -50) # 积分限幅 # 微分项 error_derivative = (error - self._last_error) / dt if dt > 0 else 0 # PID输出 pid_output = (KP * error + KI * self._error_integral + KD * error_derivative) #print(f"📊 PID计算: 误差={error:.2f}°, 积分={self._error_integral:.2f}, " # f"微分={error_derivative:.2f}, 输出={pid_output:.2f}") # 更新历史值 self._last_error = error self._last_control_time = current_time # 状态机 + PID控制 if self.angle_mode == "normal": self._normal_mode_advanced(current_angle, pid_output,TARGET_ANGLE) elif self.angle_mode == "reducing": self._reducing_mode_advanced(current_angle, pid_output, TARGET_ANGLE) elif self.angle_mode == "maintaining": self._maintaining_mode_advanced(current_angle, pid_output, TARGET_ANGLE) except Exception as e: print("处理视觉回调时发生异常: ") print("处理视觉回调时发生异常: ") print("处理视觉回调时发生异常: ") print(f"处理视觉回调时发生异常: {e}") def _normal_mode_advanced(self, current_angle, pid_output,target_angle): """高级正常模式控制""" if self.overflow: self.angle_mode = "reducing" print("检测到溢料,切换到减小模式") return # 🎯 修复1: 添加强制控制机制 # 基于PID输出的智能控制 control_threshold = 2 # 从2.0减小到0.5,提高灵敏度 if abs(pid_output) > control_threshold: if pid_output > 0: # 需要减小角度(关门) pulse_time = min(0.3, pid_output * 0.1) self._pulse_control("close", pulse_time) print(f"正常模式: 角度偏高{pid_output:.1f},关门{pulse_time:.2f}秒") else: # 需要增大角度(开门) pulse_time = min(0.3, abs(pid_output) * 0.1) self._pulse_control("open", pulse_time) print(f"正常模式: 角度偏低{abs(pid_output):.1f},开门{pulse_time:.2f}秒") else: # 在死区内,保持静止 error = current_angle - target_angle abs_error = abs(error) # 强制控制:如果误差超过5度,强制控制 if abs_error > 5: if error > 0: # 当前角度 > 目标角度,需要关门 pulse_time=0.1 # 根据误差计算脉冲时间 self._pulse_control("close", pulse_time) #print(f"🚨 强制关门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") else: # 当前角度 < 目标角度,需要开门 pulse_time =0.1 self._pulse_control("open", pulse_time) #print(f"🚨 强制开门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") return else: self._stop_door() print(f"正常模式: 角度在目标范围内,保持静止") def _reducing_mode_advanced(self, current_angle, pid_output, target_angle): """高级减小模式控制""" if not self.overflow: if current_angle <= target_angle + 5.0: self.angle_mode = "normal" print("溢料消除且角度合适,返回正常模式") else: # 缓慢恢复 self._pulse_control("close", 0.1) return # 有溢料,积极减小角度 if current_angle > target_angle: # 使用PID输出计算控制量 pulse_time = min(0.5, max(0.1, pid_output * 0.15)) self._pulse_control("close", pulse_time) # print(f"减小模式: 积极关门{pulse_time:.2f}秒,PID输出:{pid_output:.1f}") else: self.angle_mode = "maintaining" print("角度已达标,进入维持模式") def _maintaining_mode_advanced(self, current_angle, pid_output, target_angle): """高级维持模式控制""" if not self.overflow: self.angle_mode = "normal" print("溢料消除,返回正常模式") return # 精确维持控制 dead_zone = 1.5 # 更小的死区 if abs(pid_output) > dead_zone: pulse_time = min(0.2, abs(pid_output) * 0.05) # 更精细的控制 if pid_output > 0: self._pulse_control("close", pulse_time) print(f"维持模式: 微调关门{pulse_time:.2f}秒") else: self._pulse_control("open", pulse_time) print(f"维持模式: 微调开门{pulse_time:.2f}秒") else: self._stop_door() print("维持模式: 角度精确控制中") def _pulse_control(self, action, duration): """统一的脉冲控制方法""" # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 if self._is_safe_closing: thread_name = threading.current_thread().name print(f"[{thread_name}] safe_control_lower_close正在执行,跳过脉冲控制 {action}") return if duration <= 0: return thread_name = threading.current_thread().name #print(f"[{thread_name}] 尝试脉冲控制 {action},时长 {duration:.2f}秒...") with self._door_control_lock: self._current_controlling_thread = thread_name # print(f"[{thread_name}] 获得下料斗控制权,执行脉冲控制") if action == "open": self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') time.sleep(duration) self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') print(f"[{thread_name}] 开门脉冲: {duration:.2f}秒") else: # close self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') time.sleep(duration) self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') print(f"[{thread_name}] 关门脉冲: {duration:.2f}秒") self._current_controlling_thread = None #print(f"[{thread_name}] 释放下料斗控制权") def _stop_door(self): """停止门运动""" # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 if self._is_safe_closing: thread_name = threading.current_thread().name print(f"[{thread_name}] safe_control_lower_close正在执行,跳过停止门运动操作") return thread_name = threading.current_thread().name #print(f"[{thread_name}] 尝试停止门运动...") with self._door_control_lock: self._current_controlling_thread = thread_name #print(f"[{thread_name}] 获得下料斗控制权,执行停止操作") self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') self._current_controlling_thread = None #print(f"[{thread_name}] 释放下料斗控制权") def _open_door(self, duration=0.5): """打开门""" self._pulse_control("open", 0.3) def _close_door(self, duration=0.5): """关闭门""" self._pulse_control("close", 1) def on_plc_update(self,data: int, binary: str): #4即将振捣室5振捣室 64即将搅拌楼 66到达搅拌楼 # print(f"[数据回调] 数值: 0x{data:02X} | 十进制: {data:3d} | 二进制: {binary}") self.plc_data=data if self.plc_data in self.plc_valid_data: if self.state._upper_door_position!=Upper_Door_Position.ZDS: self.state._upper_door_position=Upper_Door_Position.ZDS elif self.plc_data in self.plc_valid_data_jbl: if self.state._upper_door_position!=Upper_Door_Position.JBL: self.state._upper_door_position=Upper_Door_Position.JBL else: if self.state._upper_door_position!=Upper_Door_Position.Returning: self.state._upper_door_position=Upper_Door_Position.Returning # @classmethod # def instance_exists(cls): """检测实例是否存在""" # return cls._instance is not None def shutdown(self): """关闭线程,清理资源""" # 设置停止事件 self._stop_event.set() # 唤醒线程以便它能检测到停止事件 self._new_data_available.set() self._is_running=False self._is_finish=True self.is_start_visual=False # #关闭下料斗 # self.safe_control_lower_close() # 等待线程结束 if self.callback_thread and self.callback_thread.is_alive(): self.callback_thread.join(timeout=1.0) if self.diff_thread and self.diff_thread.is_alive(): self.diff_thread.join(timeout=1.0) def get_current_mould(self): """获取当前要浇筑的管片""" _not_poured=app_web_service.get_not_pour_artifacts() if _not_poured is not None and len(_not_poured)>=1: _cur_poured_model=_not_poured[-1] if _cur_poured_model.MouldCode: self._cur_mould_model=_cur_poured_model self.state.current_mould=_cur_poured_model print(f'当前要浇筑的管片 {json.dumps(asdict(_cur_poured_model), ensure_ascii=False)}') else: print('当前没有未浇筑的管片') def __del__(self): """析构函数,确保线程安全关闭""" self.shutdown() # 创建默认实例 # visual_callback_instance = VisualCallback() # 兼容层,保持原来的函数调用方式可用 # def angle_visual_callback(current_angle, overflow_detected): # """ # 兼容旧版本的函数调用方式 # 将调用转发到默认实例的angle_visual_callback方法 # """ # visual_callback_instance.angle_visual_callback(current_angle, overflow_detected)