import sys import os sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import threading import time import queue import json import math from core.system_state import SystemState,FeedStatus,Upper_Door_Position,PD_StatusEnum from hardware.relay import RelayController from hardware.inverter import InverterController from hardware.transmitter import TransmitterController from config.ini_manager import ini_manager from hardware.upper_plc import OmronFinsPollingService from vision.visual_callback_dq import VisualCallback from opc.opcua_client_feed import OpcuaClientFeed from busisness.blls import ArtifactBll,PDRecordBll,FreqRecordBll from busisness.models import ArtifactInfoModel,PDRecordModel,FreqRecordModel from core.pd_task_service import PD_TaskService from vision.charge_3cls.charge_utils import run_stable_charge_loop from core.pd_volume import PdVolume from core.core_utils import CoreUtils from datetime import datetime, timedelta class FeedingControlSystem: def __init__(self): print('FeedingControlSystem初始化') self.state = SystemState() self.prev_pd_volume=0.0 self.pd_task_service = PD_TaskService() self.pd_record_bll=PDRecordBll() self.freq_record_bll=FreqRecordBll() # 初始化硬件控制器 self.relay_controller = RelayController( host=ini_manager.relay_host, port=ini_manager.relay_port ) self.inverter_controller = InverterController() self.transmitter_controller = TransmitterController(self.relay_controller) self.plc_service = OmronFinsPollingService(ini_manager.upper_plc_ip, ini_manager.upper_plc_port) # 初始化下料控制器 self.feeding_controller = VisualCallback( relay_controller=self.relay_controller, transmitter_controller=self.transmitter_controller, state=self.state ) self.plc_service.register_data_callback(self.feeding_controller.on_plc_update) #小屏修改过屏幕 self.vf_auto_mode=True # 初始化 OPC UA 客户端 self.opcua_client_feed = OpcuaClientFeed(notify_callback=self.on_opcua_notify) # 线程管理 self.feed_thread = None self.vf_thread = None self.arch_thread = None self.api_thread = None self.pd_jbl_thread = None # 初始化 OPC 队列监听线程,用于处理队列中的数据 self.opc_queue_thread = None #处理频率上传线程 self.freq_thread=None def initialize(self)->bool: """初始化系统""" print("初始化控制系统...") # self.check_device_connectivity() #启用上料斗PLC self.plc_service.start_polling(interval=2.0) #启用下料线程 self.start_feed_thread() #启用变频器线程 self.start_vf_thread() #启用破拱线程 self.start_arch_thread() #启用推送模型数据线程 self.feeding_controller.start_visual_thread() #启用API(对接PD API数据),线程 self.start_api_thread() #启用派单线程 self.start_pd_thread() # 启动OPC队列处理线程,维护连接的断开重连等 self.opcua_client_feed.start() #开启接收通知数据(queue线程) self.opcua_client_feed.start_accept() self.start_opc_queue_thread() #启用频率上传线程 self.start_freq_thread() print("控制系统初始化完成") return True def start_feed_thread(self): "下料线程控制,主要控制下料斗(视觉控制)以及上料斗" self.feed_thread = threading.Thread( target=self.feeding_controller._run_feed, daemon=True ) self.feed_thread.start() def start_opc_queue_thread(self): """启动OPC队列处理线程(从控制系统中获取数据,通过OPC外发)""" print('启动OPC队列处理线程') self.opc_queue_thread = threading.Thread( target=self._process_opc_queue, daemon=True, name='send_queue_processor' ) self.opc_queue_thread.start() def start_api_thread(self): """启动PD线程""" # print('启动API处理线程,从API获取未浇筑数据') self.api_thread = threading.Thread( target=self._process_api_db, daemon=True, name='api_thread' ) self.api_thread.start() def start_vf_thread(self): """启动变频器控制线程(控制变频器开始启动,以及频率变换)""" # print('启动API处理线程,从API获取未浇筑数据') self.vf_thread = threading.Thread( target=self._process_vf, daemon=True, name='vf_thread' ) self.vf_thread.start() def _process_vf(self): _begin_time=None _wait_times=300 _start_wait_seconds=None _is_start=False #处理振捣频率 while self.state.running: try: # if self.feeding_controller._is_finish_ratio>=0.6: # self.inverter_controller.set_frequency(230) # else: # self.inverter_controller.set_frequency(220) if self.state.vf_status in [1,2]: if _begin_time is None : print("----浇筑即将启动-----") if _start_wait_seconds is None: #记录盖板对齐时间 _start_wait_seconds=time.time() if self.state._mould_finish_ratio>=0.05: _is_charge= run_stable_charge_loop() if _is_charge is not None: print(f'------------已插好振捣棒: {_is_charge}-------------') time.sleep(2) # _elasped_time=time.time()-_start_wait_seconds # if _elasped_time<10: # time.sleep(10-_elasped_time) self.inverter_controller.control('start',230) _is_start=True print("----浇筑已经启动-----") _begin_time=time.time() self.state._mould_frequency=230 self.state._mould_vibrate_status=True if self.state.vf_status==2: print("----振捣270s-----") _wait_time=270 else: print("----振捣300秒-----") _wait_time=300 else: print("----下料重量小于46KG,暂时不振捣-----") else: if self.state._mould_finish_ratio>=0.3 and self.state._mould_finish_ratio<0.4: if self.state._mould_frequency!=230: self.inverter_controller.set_frequency(230) self.state._mould_frequency=230 elif self.state._mould_finish_ratio>=0.4: if self.state._mould_frequency!=230: self.inverter_controller.set_frequency(230) self.state._mould_frequency=230 elif self.state.vf_status==3 and _begin_time is not None: if time.time()-_begin_time>=_wait_time: if self.vf_auto_mode: self.inverter_controller.control('stop') _is_start=False self.state._mould_vibrate_status=False _begin_time=None _start_wait_seconds=None except Exception as e: print(f"处理变频器数据时发生错误: {e}") time.sleep(2) def _process_api_db(self): from service.mould_service import app_web_service """处理API队列中的数据""" # 初始化三个列表用于跟踪ArtifactActionID processed_artifact_actions = [] # 已处理的ArtifactActionID列表 processed_artifact_ids = [] # 已处理的ArtifactActionID列表 processed_pd_records = [] # 已插入PDRecord表的ArtifactActionID列表 processed_pd_ids=[] _model_task=None artifact_bll=ArtifactBll() pdrecord_bll=PDRecordBll() print('启动API处理线程,从API获取未浇筑数据') while self.state.running: try: not_poured = app_web_service.get_not_pour_artifacts() if not_poured: _is_refresh=False for item in reversed(not_poured): if item.MouldCode is None or item.MouldCode == '': continue _is_artifactid=True # 检查MouldCode是否已处理 if item.MouldCode in processed_artifact_actions: #print(f"待浇筑:MouldCode {item.MouldCode} 已处理,跳过") #处理过了。判断是否更新 if item.ArtifactID is None or item.ArtifactID == '': _is_artifactid=False if item.ArtifactID in processed_artifact_ids: # print(f"待浇筑:ArtifactID {item.ArtifactID} 已处理,跳过") _is_artifactid=False if _is_artifactid: _model_data = ArtifactInfoModel(**item.__dict__) _ret=artifact_bll.save_artifact_task(_model_data) if _ret > 0: # 标记为已处理 _is_refresh=True # self.state._sys_segment_refresh=1 processed_artifact_actions.append(item.MouldCode) if len(processed_artifact_actions) > 10: processed_artifact_actions.pop(0) if item.ArtifactID: processed_artifact_ids.append(item.ArtifactID) if len(processed_artifact_ids) > 10: processed_artifact_ids.pop(0) # 限制最多保存3条记录,删除最旧的 #print(f"待浇筑:已处理MouldCode {item.MouldCode} ArtifactID {item.ArtifactID}") if item.MouldCode in processed_pd_records: #print(f"派单:MouldCode {item.MouldCode} 已处理,跳过") if item.ArtifactID is None or item.ArtifactID == '': continue if item.ArtifactID in processed_pd_ids: #print(f"待浇筑:ArtifactID {item.ArtifactID} 已处理,跳过") continue _pd_record_data=None if item.ArtifactID: if item.BetonTaskID is not None and item.BetonTaskID != '': #获取taskid if _model_task is None or item.BetonTaskID != _model_task.TaskID: _model_task = app_web_service.get_task_info(item.BetonTaskID) if _model_task is None: print(f"异常:BetonTaskID {item.BetonTaskID} 不存在,跳过") continue _pd_record_data = PDRecordModel( ArtifactID=item.ArtifactID, ArtifactActionID=item.ArtifactActionID, TaskID=_model_task.TaskID, ProjectName=_model_task.ProjectName, ProduceMixID=_model_task.ProduceMixID, BetonGrade=_model_task.BetonGrade, BetonVolume=item.BetonVolume, BetonVolume2=PdVolume.get_volume_expect(item.MouldCode), MouldCode=item.MouldCode, SkeletonID=item.SkeletonID, RingTypeCode=item.RingTypeCode, SizeSpecification=item.SizeSpecification, BuriedDepth=item.BuriedDepth, BlockNumber=item.BlockNumber, PlannedVolume=_model_task.PlannedVolume ) else: _pd_record_data = PDRecordModel( MouldCode=item.MouldCode, BetonVolume2=PdVolume.get_volume_expect(item.MouldCode), ) if _pd_record_data is None: continue _ret=pdrecord_bll.save_PD_record(_pd_record_data) if _ret > 0: # self.state._sys_segment_refresh=1 _is_refresh=True # 标记为已处理 processed_pd_records.append(item.MouldCode) # 限制最多保存3条记录,删除最旧的 if len(processed_pd_records) > 10: processed_pd_records.pop(0) if item.ArtifactID: processed_pd_ids.append(item.ArtifactID) if len(processed_pd_ids) > 10: processed_pd_ids.pop(0) #print(f"派单:已处理MouldCode {item.MouldCode} ArtifactID {item.ArtifactID}") if _is_refresh: self.state._sys_segment_refresh=1 except Exception as e: print(f"处理MouldCode {item.MouldCode} 时发生错误: {e}") time.sleep(5) def _process_opc_queue(self): """处理OPC队列中的数据""" while self.state.running: try: # 从队列中获取数据,设置超时以允许线程退出 item = self.state.opc_queue.get(timeout=1) if item: public_name, value = item # 这里可以添加实际的OPC处理逻辑 print(f"控制程序opc数据上传: {public_name} = {value}") self.opcua_client_feed.write_value_by_name(public_name, value) # 标记任务完成 self.state.opc_queue.task_done() except queue.Empty: # 队列为空,继续循环 continue except Exception as e: print(f"OPC队列处理错误: {e}") def angle_visual_callback(self, current_angle, overflow_detected, mould_aligned): """角度视觉回调""" self.feeding_controller.angle_visual_callback(current_angle, overflow_detected, mould_aligned) def diff_visual_callback(self, current_diff,current_area): """差异视觉回调""" self.feeding_controller.diff_visual_callback(current_diff,current_area) def shutdown(self): """关闭系统""" self.feeding_controller.shutdown() self.stop() def start_arch_thread(self): """启动系统监控和要料""" print('振动和要料监控线程启动') #启动振动线程 self.arch_thread = threading.Thread( target=self.feeding_controller._arch_loop, daemon=True, name='arch' ) self.arch_thread.start() def check_device_connectivity(self) -> bool: """检查关键设备连接状态""" try: # 检查网络继电器连接 test_response = self.relay_controller.send_command(self.relay_controller.read_status_command) if not test_response: print("网络继电器连接失败") return False # 检查变频器连接 if not self.relay_controller.modbus_client.connect(): print("无法连接到网络继电器Modbus服务") return False # 检查下料斗变送器连接 test_weight = self.transmitter_controller.read_data(2) if test_weight is None: print("下料斗变送器连接失败") return False self.relay_controller.modbus_client.close() return True except Exception as e: print(f"设备连接检查失败: {e}") return False def start_pd_thread(self): """启动PD线程""" print('启动派单处理线程,从API获取未浇筑数据') self.pd_jbl_thread = threading.Thread( target=self._process_pd_jbl, daemon=True, name='pd_jbl_thread' ) self.pd_jbl_thread.start() def _process_pd_jbl(self): # pass #根据当前浇筑块进行最近一块的派单 # _start_time=None while self.state.running: #设置的派单模式 if self.state.pd_set_mode==1: #增加生产阶段检测, try: if self.state.pd_status==PD_StatusEnum.PD_Ready: #L1对齐排L2和F的。L2对齐派F块,L2完成派B1块 # if _start_time is None: # _start_time=time.time() _isSuccess=self.send_pd_data() if _isSuccess: self.state.pd_status=PD_StatusEnum.PD_Send_Finish # _start_time=None # elif time.time()-_start_time>60: # print('派单超时,人工介入') # self.pd_record_bll.start_pd(PD_StatusEnum.PD_TimeOut.value,_pdrecord.MouldCode,0) # self.state.pd_status=PD_StatusEnum.PD_TimeOut # _start_time=None elif self.state.pd_status==PD_StatusEnum.PD_TimeOut: self.pd_record_bll.start_pd(PD_StatusEnum.PD_TimeOut.value,self.state.pd_mould_code,-1) self.state.pd_status=PD_StatusEnum.PD_Not except Exception as e: print(f"派单处理错误: {e}") self.state.pd_status=PD_StatusEnum.PD_Error _start_time=None else: print('已开启手动派单,或未设置派单模式') print('已开启手动派单,或未设置派单模式') time.sleep(5) def send_pd_data(self): """ 发送PD数据到OPC队列 :param _pd_status: PD状态 """ # 构建PD数据 # print("send_pd_data:1") _cur_mould=self.state.current_mould if _cur_mould is not None: # print("send_pd_data:2") if _cur_mould.MouldCode: _pdrecords = self.pd_record_bll.get_last_pds(_cur_mould.MouldCode,2) if _pdrecords and len(_pdrecords)>1: _pdrecord=_pdrecords[1] # print(f"send_pd_data:3,{_pdrecord.MouldCode}") _block_number=CoreUtils.get_number_by_mould_code(_pdrecord.MouldCode) _size=CoreUtils.get_size_by_mould_code(_pdrecord.MouldCode) self.state.pd_mould_code=_pdrecord.MouldCode _pd_flag=1 if not _pdrecord.TaskID: #查找最近的未浇筑块 print(f"未扫描,使用最近的未浇筑块,{_size}") _nearrecord=self.pd_record_bll.get_near_pd_scan(_size) if _nearrecord is not None: _pd_flag=2 _pdrecord.ArtifactActionID=0 _pdrecord.TaskID=_nearrecord.TaskID _pdrecord.ProduceMixID=_nearrecord.ProduceMixID _pdrecord.PlannedVolume=_nearrecord.PlannedVolume print(f"未扫描,使用最近的未浇筑块,{_size},派单:{_pdrecord.TaskID},配比号:{_pdrecord.ProduceMixID}") # print(f"send_pd_data:5,{_pdrecord.TaskID}") if _pdrecord.TaskID: if _pdrecord.Status==1: # print(f"send_pd_data:6,{_pdrecord.TaskID}") # tasks, artifact_list, send_list, half_volume = self.pd_task_service.process_not_pour_info(_pdrecords) # if tasks and len(tasks)>0: # task=tasks[0] # # task['MouldCode']=_pdrecord.MouldCode # #更新派单表 # _ret_flag=self.pd_record_bll.start_pd(_pdrecord.MouldCode,task['beton_volume']) # if _ret_flag: # _opc_pd={ # "ID":task['id'], # "ArtifactActionID":task['artifact_id'], # "TaskID":task['beton_task_id'], # "ProduceMixID":task['produce_mix_id'], # "BetonVolume":task['beton_volume'], # "PlannedVolume":task['planned_volume'] # } # print(f"{self.state.current_mould.MouldCode}派单:{_pdrecord.MouldCode},数据:{_opc_pd}") # self.state.opc_queue.put_nowait(('pd_data', json.dumps(_opc_pd))) _fact_volumn=0 if _pdrecord.BetonVolumeUpd>0.8: #当前已经修改过方量 _fact_volumn=_pdrecord.BetonVolumeUpd print(f"当前已经修改过方量,方量:{_fact_volumn}") if _block_number=='F' and _fact_volumn<0.8: print(f'{_pdrecord.MouldCode} F块,不发送派单数据') self.pd_record_bll.start_pd(PD_StatusEnum.PD_Send_Finish.value,_pdrecord.MouldCode,0) else: if _fact_volumn<0.8: # _fact_volumn=0 if _size=='6600*1500': _fact_volumn=PdVolume.get_fact_volume(self.transmitter_controller,_pdrecord.MouldCode) elif _size=='6600*1200': _fact_volumn=PdVolume.get_fact_volume_12(self.transmitter_controller,_pdrecord.MouldCode) if _fact_volumn is not None and _fact_volumn>0: #更新派单表 # print(f"send_pd_data:7,{_pdrecord.MouldCode},{_fact_volumn}") _ret_flag=self.pd_record_bll.start_pd(PD_StatusEnum.PD_Send_Finish.value,_pdrecord.MouldCode,_fact_volumn) _cur_code=_pdrecords[0].MouldCode if _ret_flag: _opc_pd={ "ID":_pdrecord.ID, "ArtifactActionID":_pdrecord.ArtifactActionID, "MouldCodePD":_pdrecord.MouldCode, "MouldCode":_cur_code, "TaskID":_pdrecord.TaskID, "ProduceMixID":_pdrecord.ProduceMixID, "BetonVolume":round(_fact_volumn,1), "PlannedVolume":round(_pdrecord.PlannedVolume,1), "Flag":_pd_flag, "Msg":"已扫码" if _pd_flag==1 else "未扫码", "Time":time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), } print(f"{self.state.current_mould.MouldCode}派单:{_pdrecord.MouldCode}-{_pdrecord.BlockNumber},数据:{_opc_pd}") self.state.opc_queue.put_nowait(('pd_data', json.dumps(_opc_pd))) return True else: print(f'{_pdrecord.MouldCode} 未派单,当前状态为:{_pdrecord.Status}') return True else: print(f'{_pdrecord.MouldCode} 未获取到数据-(等待扫码)') return False else: print(f'接口数据为空') return False else: print(f'当前浇筑模具为空') return False else: return False def start_led(self): """启动LED流程""" self.led_thread = threading.Thread( target=self._start_led, name="LED", daemon=True ) self.led_thread.start() def _start_led(self): """启动LED流程""" from service.mould_service import app_web_service while self.state.running: led_info = app_web_service.get_pouring_led() if led_info: if self.state.mould_code==led_info.MouldCode: led_info.RingTypeCode=led_info.RingTypeCode led_info.UpperWeight=self.state._upper_weight led_info.LowerWeight=self.state._lower_weight led_info.VibrationFrequency=self.state._mould_frequency #发送到LED屏 time.sleep(2) def on_opcua_notify(self,var_name,val): """OPC UA通知回调""" try: if var_name=='pd_set_mode': self.state.pd_set_mode=val elif var_name=='pd_set_volume' and val: if self.state.pd_status!=PD_StatusEnum.PD_Ready: _notify=json.loads(val) _ret=self.pd_record_bll.update_PD_record_byid(_notify['ID'],{'BetonVolumeUpd':_notify['Volume']}) if _ret: #刷新派单表 try: self.state.opc_queue.put_nowait(('sys_pd_refresh',1)) except queue.Full: pass else: print('当前状态为派单中,不能修改方量') print('当前状态为派单中,不能修改方量') # self.state._db_pd_set_volume=val elif (var_name=='pd_notify_finish' or var_name=='pd_notify') and val: # self.state._db_pd_notify=val _notify=json.loads(val) if 'ID' in _notify and 'Time' in _notify and _notify['ID'] and _notify['Time']: if _notify['Flag']==5: #搅拌楼下料完毕 _ret=self.pd_record_bll.update_pd_notify(_notify['ID'],{'GStatus':5,'Status':5,'FBetonVolume':_notify['BetonVolume'],'ErpID':_notify['ErpID'],'EndTime':time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())}) if _ret: _time=datetime.strptime(_notify['Time'], '%Y-%m-%d %H:%M:%S') if _time+timedelta(seconds=10)>=datetime.now(): # 发送命令 # from service.jbl_service import app_jbl_service # _is_jb_finished=app_jbl_service.is_finish_jb() if self.state._upper_door_position==Upper_Door_Position.JBL: print("发送命令:搅拌楼--->振捣室") print("发送命令:搅拌楼--->振捣室") time.sleep(3) self.relay_controller.control_upper_to_zd() else: print("搅拌楼下料未完成,或者料斗未在搅拌楼") print("搅拌楼下料未完成,或者料斗未在搅拌楼") else: print("pd_notify:时间已过期") print("pd_notify:时间已过期") else: print("pd_notify:更新失败") print("pd_notify:更新失败") else: self.pd_record_bll.update_pd_notify(_notify['ID'],{'GStatus':_notify['Flag'],'ErpID':_notify['ErpID']}) else: print(f'pd_notify:ID数据为空或Time数据为空') print(f'pd_notify:ID数据为空或Time数据为空') print(f'on_opcua_notify收到,var_name:{var_name},val:{val}') except Exception as e: print(f'on_opcua_notify处理异常,var_name:{var_name},val:{val},e:{e}') def start_freq_thread(self): """启动频率上传线程""" self.freq_thread = threading.Thread( target=self._start_freq, name="Freq", daemon=True ) self.freq_thread.start() def _start_freq(self): """启动频率上传线程""" while self.state.running: # 上传频率 #未上传到IOT平台,保存到数据库 _cur_mould=self.state.current_mould if _cur_mould: _code=_cur_mould.ArtifactID _id=_cur_mould.ArtifactActionID _mcode=_cur_mould.MouldCode # freq=self.inverter_controller.read_frequency() # freq=self.state._mould_frequency freq=0 if freq>0: self.freq_record_bll.insert_freq_record(FreqRecordModel(ArtifactID=_code,ArtifactActionID=_id,MouldCode=_mcode,Freq=freq)) else: print('振捣频率未保存(因为当前浇筑的模具号为空)') time.sleep(1) @property def _is_finish(self): """检查系统是否完成""" return self.state._feed_status==FeedStatus.FFinished @property def _is_finish_ratio(self): """完成比例""" return self.state._mould_finish_ratio @property def vibrate_status(self): """检查系统是否运行""" return self.state._mould_vibrate_status def set_vf_mode(self,is_auto=False): """设置变频器为自动模式""" self.vf_auto_mode=is_auto def stop(self): """停止系统""" print("停止控制系统...") self.state.running = False # 等待线程结束 if self.opc_queue_thread: self.opc_queue_thread.join() if self.vf_thread: self.vf_thread.join() if self.api_thread: self.api_thread.join() if self.pd_jbl_thread: self.pd_jbl_thread.join() if self.feed_thread: self.feed_thread.join() if self.arch_thread: self.arch_thread.join() if self.plc_service: self.plc_service.stop_polling() self.opcua_client_feed.stop_run() self.feeding_controller.shutdown() # 释放摄像头资源 # self.camera_controller.release() print("控制系统已停止") if __name__ == "__main__": system = FeedingControlSystem() system.initialize() system.opcua_client_feed.subscribe_node(system.state.feed_status) time.sleep(2) system.state._upper_weight=1000 while True: time.sleep(1)