Files
Feeding_control_system/core/system.py
2026-03-13 21:04:19 +08:00

655 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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

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
from core.system_state import SystemState,FeedStatus,Upper_Door_Position
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
class FeedingControlSystem:
def __init__(self):
print('FeedingControlSystem初始化')
self.state = SystemState()
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:
_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:
if self.state._mould_frequency!=220:
self.inverter_controller.set_frequency(220)
self.state._mould_frequency=220
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:
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:
# 标记为已处理
self.state._sys_segment_refresh=1
processed_artifact_actions.append(item.MouldCode)
if len(processed_artifact_actions) > 4:
processed_artifact_actions.pop(0)
if item.ArtifactID:
processed_artifact_ids.append(item.ArtifactID)
if len(processed_artifact_ids) > 4:
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,
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
)
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
# 标记为已处理
processed_pd_records.append(item.MouldCode)
# 限制最多保存3条记录删除最旧的
if len(processed_pd_records) > 4:
processed_pd_records.pop(0)
if item.ArtifactID:
processed_pd_ids.append(item.ArtifactID)
if len(processed_pd_ids) > 4:
processed_pd_ids.pop(0)
#print(f"派单:已处理MouldCode {item.MouldCode} ArtifactID {item.ArtifactID}")
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
#根据当前浇筑块进行最近一块的派单
_isFinish=False
_start_time=None
while self.state.running:
#设置的派单模式
if self.state.pd_set_mode==1:
#增加生产阶段检测,
if self.state._feed_status==FeedStatus.FCheckGB:
if not _isFinish:
if _start_time is None:
_start_time=time.time()
_isSuccess=self.send_pd_data()
if _isSuccess:
_isFinish=True
if time.time()-_start_time>60:
print('派单超时,人工介入')
_isFinish=True
elif self.state._feed_status==FeedStatus.FFinished:
_start_time=None
_isFinish=False
time.sleep(5)
def send_pd_data(self):
"""
发送PD数据到OPC队列
"""
# 构建PD数据
_cur_mould=self.state.current_mould
if _cur_mould is not None:
if _cur_mould.MouldCode:
_pdrecords = self.pd_record_bll.get_last_pds(_cur_mould.MouldCode,3)
if _pdrecords:
_pdrecord=_pdrecords[0]
if _pdrecord.TaskID:
if _pdrecord.Status==1:
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)))
if _pdrecord.BlockNumber=='F':
print(f'{_pdrecord.MouldCode} F块不发送派单数据')
self.pd_record_bll.start_pd(_pdrecord.MouldCode,0)
else:
_fact_volumn=self.get_fact_volumn(_pdrecord.MouldCode,_pdrecord.BlockNumber)
if _fact_volumn>0:
#更新派单表
_ret_flag=self.pd_record_bll.start_pd(_pdrecord.MouldCode,_fact_volumn)
if _ret_flag:
_opc_pd={
"ID":_pdrecord.ID,
"ArtifactActionID":_pdrecord.ArtifactActionID,
"TaskID":_pdrecord.TaskID,
"ProduceMixID":_pdrecord.ProduceMixID,
"BetonVolume":_fact_volumn,
"PlannedVolume":_pdrecord.PlannedVolume
}
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 False
else:
print(f'{_pdrecord.MouldCode} 未获取到数据-(等待扫码)')
return False
else:
print(f'接口数据为空')
return False
else:
return None
def get_fact_volumn(self,mould_code:str,block_number:str='') -> float:
"""获取实际派单发量"""
# _now_volume=0
_pd_volume=0
_init_lower_weight=100#self.transmitter_controller.read_data(2)
if _init_lower_weight is None:
print(f'获取重量异常,上料斗传感器错误')
print(f'获取重量异常,上料斗传感器错误')
return None
print(f'get_fact_volumn当前重量{_init_lower_weight}')
_per_volume=2480
_left_volume=_init_lower_weight/_per_volume
if not block_number and '-' in mould_code:
block_number = mould_code.split('-')[0][-2:]
if block_number=='B1':
_left_volume=_left_volume-0.54
if _left_volume>0:
_pd_volume=1.9-_left_volume
_pd_volume=math.ceil(_pd_volume*10)/10
else:
_pd_volume=1.9
elif block_number in ['B2','B3']:
_pd_volume=1.9
elif block_number=='L1':
#浇B3
_pd_volume=2
self.prev_pd_volume=2
#调整
elif block_number=='L2':
#2.4方大约L2和F的量
_pd_volume=2.4
# if _weight>1300:
#留0.15 math.floor(_now_volume*10)/10 保留一位小数,丢掉其他的
if self.prev_pd_volume>0:
_pd_volume=_pd_volume-(_left_volume+self.prev_pd_volume-1.86)
else:
#不知道上一块叫的多少
_pd_volume=_pd_volume-(_left_volume+2-1.86)
_pd_volume=math.ceil(_pd_volume*10)/10+0.1
if _pd_volume>2.1:
_pd_volume=2.1
elif _pd_volume<0.8:
_pd_volume=0.8
return _pd_volume
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(app_set_config.led_interval)
def on_opcua_notify(self,var_name,val):
"""OPC UA通知回调"""
if var_name=='pd_set_mode':
self.state.pd_set_mode=val
elif var_name=='pd_set_volume' and val:
_notify=json.loads(val)
_ret=self.pd_record_bll.update_PD_record_byid(_notify['ID'],{'FBetonVolume':_notify['Volume']})
if _ret:
#刷新派单表
self.state.opc_queue.put_nowait(('sys_pd_refresh',1))
# self.state._db_pd_set_volume=val
elif var_name=='pd_notify' and val:
# self.state._db_pd_notify=val
_notify=json.loads(val)
if _notify['ID']:
if _notify['Flag']==3:
#生产完毕
_ret=self.pd_record_bll.update_PD_record_byid(_notify['ID'],{'GStatus':3,'Status':5})
# if _ret:
#发送命令
# if self.state._upper_door_position==Upper_Door_Position.JBL:
# self.relay_controller.control_upper_to_zd()
else:
self.pd_record_bll.update_PD_record_byid(_notify['ID'],{'GStatus':_notify['Flag']})
print(f'on_opcua_notify收到,var_name:{var_name},val:{val}')
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
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)