Compare commits

..

2 Commits

36 changed files with 2856 additions and 505 deletions

View File

@ -5,7 +5,7 @@
# sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from dataclasses import fields
from typing import List, Optional, Dict, Any
from datetime import datetime
from datetime import datetime,timedelta
from busisness.models import ArtifactInfoModel, PDRecordModel,FreqRecordModel
from busisness.dals import ArtifactDal, PDRecordDal,FreqRecordDal
@ -52,24 +52,29 @@ class ArtifactBll:
if _artifactid is None:
return self.dal.insert_artifact(model)
else:
if not _artifactid and model.ArtifactID:
update_dict={
'ArtifactID':model.ArtifactID,
'ArtifactActionID':model.ArtifactActionID,
'ArtifactIDVice1':model.ArtifactIDVice1,
'ProduceRingNumber':model.ProduceRingNumber,
'SkeletonID':model.SkeletonID,
'RingTypeCode':model.RingTypeCode,
'SizeSpecification':model.SizeSpecification,
'BuriedDepth':model.BuriedDepth,
'BlockNumber':model.BlockNumber,
'HoleRingMarking':model.HoleRingMarking,
'GroutingPipeMarking':model.GroutingPipeMarking,
'PolypropyleneFiberMarking':model.PolypropyleneFiberMarking,
'BetonVolume':model.BetonVolume,
'BetonTaskID':model.BetonTaskID,
}
return self.dal.update_by_modulecode(model.MouldCode, update_dict)
if model.ArtifactID:
if not _artifactid:
#不存在ArtifactID,更新
update_dict={
'ArtifactID':model.ArtifactID,
'ArtifactActionID':model.ArtifactActionID,
'ArtifactIDVice1':model.ArtifactIDVice1,
'ProduceRingNumber':model.ProduceRingNumber,
'SkeletonID':model.SkeletonID,
'RingTypeCode':model.RingTypeCode,
'SizeSpecification':model.SizeSpecification,
'BuriedDepth':model.BuriedDepth,
'BlockNumber':model.BlockNumber,
'HoleRingMarking':model.HoleRingMarking,
'GroutingPipeMarking':model.GroutingPipeMarking,
'PolypropyleneFiberMarking':model.PolypropyleneFiberMarking,
'BetonVolume':model.BetonVolume,
'BetonTaskID':model.BetonTaskID,
}
return self.dal.update_by_modulecode(model.MouldCode, update_dict)
else:
#已经存在了ArtifactID不处理
return 1
return 1
@ -113,6 +118,16 @@ class PDRecordBll:
"""获取当前浇筑的后三片的派据(有可能其中一项为空,但需要保证第一条有记录)"""
return self.dal.get_last_pds(top,mould_code)
def get_near_pd_scan(self,size:str) -> PDRecordModel:
"""获取最近的派单信息"""
_target_time = datetime.now() - timedelta(hours=4)
where=f"SizeSpecification='{size}' and CreateTime>='{_target_time}'"
_pdrecords=self.dal.get_top_pd(1,"ID desc",where)
if _pdrecords:
return _pdrecords[0]
else:
return None
def insert_PD_record(self,model:PDRecordModel) -> bool:
"""保存PD官片任务(存在更新,不存在插入)"""
if self.dal.exists_artifactid(model.ArtifactActionID):
@ -125,9 +140,12 @@ class PDRecordBll:
"""完成管片生产"""
return self.dal.update_by_modulecode(code, {'Status': status,'EndTime':datetime.now()})
def start_pd(self, code: str,volumn:float) -> bool:
"""开始管片生产"""
return self.dal.update_by_modulecode(code, {'Status': 2,'GStatus':1,'OptTime':datetime.now(),'FBetonVolume':volumn})
def start_pd(self, status:int,code: str,volumn:float) -> bool:
"""开始管片生产,-1不更方量"""
if volumn==-1:
return self.dal.update_by_modulecode(code, {'Status': status,'GStatus':1,'OptTime':datetime.now()})
else:
return self.dal.update_by_modulecode(code, {'Status': status,'GStatus':1,'OptTime':datetime.now(),'FBetonVolume':volumn})
def update_PD_record(self,artifact_action_id: int, update_fields: dict) -> bool:
"""更新PD官片任务状态"""
@ -137,32 +155,44 @@ class PDRecordBll:
"""更新PD官片任务状态"""
return self.dal.update_pd_byid(id, update_fields)
def update_pd_notify(self,id: int, update_fields: dict) -> bool:
"""更新PD官片任务状态"""
return self.dal.update_pd_notify(id, update_fields)
def save_PD_record(self,model:PDRecordModel) -> int:
"""保存PD官片任务(存在ModuleCode更新不存在插入存在ArtifactID不处理)"""
_artifactid=self.dal.get_artifactid(model.MouldCode)
if _artifactid is None:
if model.TaskID:
model.ScanTime=datetime.now()
return self.dal.insert_PD_record(model)
else:
if not _artifactid and model.ArtifactID:
update_dict={
'ArtifactID':model.ArtifactID,
'ArtifactActionID':model.ArtifactActionID,
'TaskID':model.TaskID,
'ProjectName':model.ProjectName,
'ProduceMixID':model.ProduceMixID,
'BetonVolume':model.BetonVolume,
'BetonGrade':model.BetonGrade,
'BuriedDepth':model.BuriedDepth,
'SkeletonID':model.SkeletonID,
'RingTypeCode':model.RingTypeCode,
'SizeSpecification':model.SizeSpecification,
'BlockNumber':model.BlockNumber,
'BetonVolume':model.BetonVolume,
'GStatus':model.GStatus,
'PlannedVolume':model.PlannedVolume
}
return self.dal.update_by_modulecode(model.MouldCode, update_dict)
if model.ArtifactID:
if not _artifactid:
#不存在ArtifactID更新PDRecord表
update_dict={
'ArtifactID':model.ArtifactID,
'ArtifactActionID':model.ArtifactActionID,
'TaskID':model.TaskID,
'ProjectName':model.ProjectName,
'ProduceMixID':model.ProduceMixID,
'BetonVolume':model.BetonVolume,
'BetonGrade':model.BetonGrade,
'BuriedDepth':model.BuriedDepth,
'SkeletonID':model.SkeletonID,
'RingTypeCode':model.RingTypeCode,
'SizeSpecification':model.SizeSpecification,
'BlockNumber':model.BlockNumber,
'BetonVolume':model.BetonVolume,
'GStatus':model.GStatus,
'PlannedVolume':model.PlannedVolume,
'ScanTime':datetime.now(),
}
return self.dal.update_by_modulecode(model.MouldCode, update_dict)
else:
#已经存在了ArtifactID不处理
return 1
return 1

View File

@ -233,8 +233,8 @@ class PDRecordDal(BaseDal):
# 确保top为正整数
if not isinstance(top, int) or top <= 0:
raise ValueError("top参数必须是正整数")
# _target_time = datetime.now() - timedelta(hours=4)
_target_time ='2026-02-06 00:00:00'
_target_time = datetime.now() - timedelta(hours=4)
# _target_time ='2026-03-31 00:00:00'
# 查询当前浇筑模具编号的前一条记录
sql = f"""SELECT * FROM PDRecord WHERE CreateTime>? and ID>(
select ID FROM PDRecord WHERE MouldCode=? and CreateTime>?
@ -330,12 +330,23 @@ class PDRecordDal(BaseDal):
print(f"更新PD管片任务失败: {e}")
return False
def update_pd_notify(self, id: int, update_data: dict) -> bool:
"""更新PD官片任务记录"""
try:
# 构建WHERE条件
where_condition = f"ID={id} and Status<>5"
# 使用update方法更新数据
affected_rows = self.db_dao.update("PDRecord", update_data, where_condition)
return affected_rows > 0
except Exception as e:
raise
def update_by_modulecode(self, module_code: str, update_data: dict) -> bool:
"""更新构件派单记录"""
try:
# 构建WHERE条件
_target_time = datetime.now() - timedelta(hours=4)
_target_time ='2026-02-06 00:00:00'
# _target_time ='2026-02-06 00:00:00'
where_condition = f"MouldCode='{module_code}' and CreateTime>='{_target_time}'"
# 使用update方法更新数据
affected_rows = self.db_dao.update("PDRecord", update_data, where_condition)

View File

@ -254,8 +254,12 @@ class PDRecordModel:
ProjectName: str=""
#生产配合比编号
ProduceMixID: str=""
#派单方量
#设计方量
BetonVolume: float=0.0
#计划派单方量
BetonVolume2: float=0.0
#修改后的方量
BetonVolumeUpd: float=0.0
#实际派单方量
FBetonVolume: float=0.0
#实际派单方量
@ -288,6 +292,11 @@ class PDRecordModel:
EndTime: str=""
#派单时间(下发)
OptTime: str=""
#扫描时间
ScanTime: str=""
#ErpID
ErpID: str=""
@dataclass
@ -348,4 +357,4 @@ class LEDInfo:
# 白班拆模强度文本描述
DayStrengthValue: str
# 夜班拆模强度文本描述
NihtStrengthValue: str
NihtStrengthValue: str

View File

@ -116,6 +116,11 @@ class IniManager:
def api_base_url(self):
"""获取API基础URL"""
return self._read_config_value('api', 'base_url', self._DEFAULT_API_BASE_URL, str)
@property
def jbl_api_url(self):
"""获取搅拌楼API基础URL"""
return self._read_config_value('api', 'jbl_api_url', 'http://10.6.242.111:5002', str)
@property
def api_login_url(self):

View File

@ -31,6 +31,7 @@ sys_segment_refresh = 2:sys,2:segment_refresh
sys_pd_refresh = 2:sys,2:pd_refresh
pd_data = 2:pd,2:pd_data
pd_notify = 2:pd,2:pd_notify
pd_notify_finish = 2:pd,2:pd_notify_finish
pd_set_mode = 2:pd,2:set_mode
pd_set_volume = 2:pd,2:set_volume

25
core/core_utils.py Normal file
View File

@ -0,0 +1,25 @@
class CoreUtils:
@staticmethod
def get_number_by_mould_code(mould_code:str=''):
"""获取分块号"""
"""根据模块码获取块号"""
if '-' in mould_code:
block_number = mould_code.split('-')[0][-2:]
else:
block_number=''
return block_number
@staticmethod
def get_size_by_mould_code(mould_code:str=''):
"""获取模具尺寸"""
if mould_code.startswith('SHR'):
return '6600*1500'
elif mould_code.startswith('SHZ'):
return '6600*1200'
else:
return '0'

157
core/pd_volume.py Normal file
View File

@ -0,0 +1,157 @@
from hardware.transmitter import TransmitterController
from core.core_utils import CoreUtils
import math
class PdVolume:
@staticmethod
def get_fact_volume_12(transmitter_controller:TransmitterController,mould_code:str='') -> float:
"""获取实际派单发量"""
# _now_volume=0
block_number = mould_code.split('-')[0][-2:]
if not block_number:
print('分块号不能为空')
return 0
_pd_volume=0
_init_lower_weight=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 block_number=='B1':
_left_volume=_left_volume-0.5
if _left_volume>0:
_pd_volume=1.6-_left_volume
_pd_volume=math.ceil(_pd_volume*10)/10
else:
_pd_volume=1.6
elif block_number in ['B3','L1']:
_pd_volume=1.5
elif block_number=='B2':
#浇B3
_pd_volume=1.6
#调整
elif block_number=='L2':
if _init_lower_weight>950 and _init_lower_weight<=1350:
_pd_volume=1.5
elif _init_lower_weight>1350 and _init_lower_weight<=1600:
_pd_volume=1.4
elif _init_lower_weight<=950 and _init_lower_weight>800:
_pd_volume=1.6
else:
#1.9方大约L2和F的量
_pd_volume=1.9
# if _weight>1300:
#留0.15 math.floor(_now_volume*10)/10 保留一位小数,丢掉其他的
# if prev_pd_volume>0:
# _pd_volume=_pd_volume-(_left_volume+prev_pd_volume-1.86)
# else:
#不知道上一块叫的多少
_pd_volume=_pd_volume-(_left_volume+1.6-1.46)
_pd_volume=math.ceil(_pd_volume*10)/10+0.1
# _pd_volume=math.ceil(_pd_volume*10)/10
if _pd_volume>2.1:
_pd_volume=2.1
elif _pd_volume<0.8:
_pd_volume=0.8
return _pd_volume
@staticmethod
def get_fact_volume(transmitter_controller:TransmitterController,mould_code:str='') -> float:
"""获取实际派单发量"""
# _now_volume=0
block_number = mould_code.split('-')[0][-2:]
if not block_number:
print('分块号不能为空')
return 0
_pd_volume=0
_init_lower_weight=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 block_number=='B1':
_left_volume=_left_volume-0.6
if _left_volume>0:
_pd_volume=2-_left_volume
_pd_volume=math.ceil(_pd_volume*10)/10
else:
_pd_volume=2
elif block_number in ['B3','L1']:
_pd_volume=1.9
elif block_number=='B2':
#浇B3
_pd_volume=2
#调整
elif block_number=='L2':
if _init_lower_weight>1300 and _init_lower_weight<=1700:
_pd_volume=1.9
elif _init_lower_weight>1700 and _init_lower_weight<=1950:
_pd_volume=1.8
elif _init_lower_weight<=1300 and _init_lower_weight>1150:
_pd_volume=2
else:
#2.4方大约L2和F的量
_pd_volume=2.4
# if _weight>1300:
#留0.15 math.floor(_now_volume*10)/10 保留一位小数,丢掉其他的
# if prev_pd_volume>0:
# _pd_volume=_pd_volume-(_left_volume+prev_pd_volume-1.86)
# else:
#不知道上一块叫的多少
_pd_volume=_pd_volume-(_left_volume+1.9-1.86)
_pd_volume=math.ceil(_pd_volume*10)/10+0.1
# _pd_volume=math.ceil(_pd_volume*10)/10
if _pd_volume>2.2:
_pd_volume=2.2
elif _pd_volume<0.8:
_pd_volume=0.8
return _pd_volume
@staticmethod
def get_volume_expect(mould_code:str=''):
"""获取模具预期方量"""
_block_number=CoreUtils.get_number_by_mould_code(mould_code)
_size=CoreUtils.get_size_by_mould_code(mould_code)
_volume_expect=0.0
if _size=='6600*1500':
if _block_number=='B1':
_volume_expect=2
elif _block_number=='B2':
_volume_expect=2
elif _block_number=='B3':
_volume_expect=1.9
elif _block_number=='L1':
_volume_expect=1.9
elif _block_number=='L2':
_volume_expect=0
elif _size=='6600*1200':
if _block_number=='B1':
_volume_expect=1.6
elif _block_number=='B2':
_volume_expect=1.6
elif _block_number=='B3':
_volume_expect=1.5
elif _block_number=='L1':
_volume_expect=1.5
elif _block_number=='L2':
_volume_expect=0
return _volume_expect

View File

@ -5,10 +5,11 @@ import time
import threading
from pymodbus.exceptions import ModbusException
from config.settings import app_set_config
from hardware.relay import RelayController
#下料过程控制
class RelayFeedController:
def __init__(self, relay,state):
def __init__(self, relay:RelayController,state):
self.relay = relay
self.state = state
# 添加线程锁,保护对下料斗控制的并发访问
@ -17,28 +18,28 @@ class RelayFeedController:
def control_upper_close_after(self):
"""控制上料斗关在几秒后"""
# 关闭上料斗出砼门
self.relay.control(self.DOOR_UPPER_OPEN, 'close')
self.relay.control(self.relay.DOOR_UPPER_OPEN, 'close')
# 异步5秒后关闭
threading.Thread(target=self._close_upper_after_s, daemon=True,name="close_upper_after_s").start()
def control_upper_close_sync(self,duration=5):
self.relay.control(self.DOOR_UPPER_OPEN, 'close')
self.relay.control(self.DOOR_UPPER_CLOSE, 'open')
self.relay.control(self.relay.DOOR_UPPER_OPEN, 'close')
self.relay.control(self.relay.DOOR_UPPER_CLOSE, 'open')
time.sleep(duration)
self.relay.control(self.DOOR_UPPER_CLOSE, 'close')
self.relay.control(self.relay.DOOR_UPPER_CLOSE, 'close')
def control_upper_open_sync(self,duration):
self.relay.control(self.DOOR_UPPER_CLOSE, 'close')
self.relay.control(self.DOOR_UPPER_OPEN, 'open')
self.relay.control(self.relay.DOOR_UPPER_CLOSE, 'close')
self.relay.control(self.relay.DOOR_UPPER_OPEN, 'open')
time.sleep(duration)
self.relay.control(self.DOOR_UPPER_OPEN, 'close')
self.relay.control(self.relay.DOOR_UPPER_OPEN, 'close')
def control_ring_open(self):
"""控制下料斗关"""
# 关闭下料斗出砼门
self.relay.control(self.RING, 'open')
self.relay.control(self.relay.RING, 'open')
# 异步5秒后关闭
threading.Thread(target=self._close_ring, daemon=True,name="_close_ring").start()
@ -50,53 +51,60 @@ class RelayFeedController:
# time.sleep(5)
self.relay.control_arch_upper_open_sync(5)
self.relay.control(self.DOOR_UPPER_CLOSE, 'open')
self.relay.control(self.relay.DOOR_UPPER_CLOSE, 'open')
time.sleep(1)
self.relay.control(self.DOOR_UPPER_CLOSE, 'close')
self.relay.control(self.relay.DOOR_UPPER_CLOSE, 'close')
self.relay.control_arch_upper_open_sync(5)
# self.relay.control_arch_upper_open_sync(5)
self.relay.control_arch_upper_open_async(8)
self.relay.control(self.DOOR_UPPER_CLOSE, 'open')
self.relay.control(self.relay.DOOR_UPPER_CLOSE, 'open')
time.sleep(20)
self.relay.control(self.DOOR_UPPER_CLOSE, 'close')
self.relay.control(self.relay.DOOR_UPPER_CLOSE, 'close')
if self.state.pd_set_mode==1:
print('上料斗到搅拌楼')
self.relay.control_upper_to_jbl()
else:
print('非自动派单,手动上料斗到搅拌楼')
print('非自动派单,手动上料斗到搅拌楼')
print("上料斗关闭完成")
def _close_lower_5s(self):
time.sleep(6)
self.relay.control(self.DOOR_LOWER_CLOSE, 'close')
self.relay.control(self.relay.DOOR_LOWER_CLOSE, 'close')
def _close_ring(self):
time.sleep(3)
self.relay.control(self.RING, 'close')
self.relay.control(self.relay.RING, 'close')
def control_arch_lower_open(self):
"""控制下料斗关"""
# 关闭下料斗出砼门
self.relay.control(self.BREAK_ARCH_LOWER, 'open')
self.relay.control(self.relay.BREAK_ARCH_LOWER, 'open')
# 异步5秒后关闭
threading.Thread(target=self._close_break_arch_lower, daemon=True,name="_close_break_arch_lower").start()
def control_arch_lower_open_sync(self,duration):
"""控制下料斗振动"""
self.state._lower_is_arch = True
self.relay.control(self.BREAK_ARCH_LOWER, 'open')
self.relay.control(self.relay.BREAK_ARCH_LOWER, 'open')
# 异步5秒后关闭
time.sleep(duration)
self.relay.control(self.BREAK_ARCH_LOWER, 'close')
self.relay.control(self.relay.BREAK_ARCH_LOWER, 'close')
self.state._lower_is_arch = False
def control_arch_upper_open_sync(self,duration):
"""控制下料斗振动"""
self.state._upper_is_arch=True
self.relay.control(self.BREAK_ARCH_UPPER, 'open')
self.relay.control(self.relay.BREAK_ARCH_UPPER, 'open')
# 异步5秒后关闭
time.sleep(duration)
self.relay.control(self.BREAK_ARCH_UPPER, 'close')
self.relay.control(self.relay.BREAK_ARCH_UPPER, 'close')
self.state._upper_is_arch=False
def _close_break_arch_lower(self):
time.sleep(3)
self.relay.control(self.BREAK_ARCH_LOWER, 'close')
self.relay.control(self.relay.BREAK_ARCH_LOWER, 'close')
def control_arch_upper_open_async(self,delay_seconds: float = 15):
@ -106,14 +114,14 @@ class RelayFeedController:
delay_seconds: 延迟关闭时间默认15秒
"""
# 关闭下料斗出砼门
self.relay.control(self.BREAK_ARCH_UPPER, 'open')
self.relay.control(self.relay.BREAK_ARCH_UPPER, 'open')
# 异步5秒后关闭
threading.Thread(target=lambda d: self._close_break_arch_upper(delay_seconds),args=(delay_seconds,), daemon=True, name="_close_break_arch_upper").start()
def _close_break_arch_upper(self, delay_seconds: float = 15):
time.sleep(delay_seconds)
print(f"上料斗振动关闭完成,延迟{delay_seconds}")
self.relay.control(self.BREAK_ARCH_UPPER, 'close')
self.relay.control(self.relay.BREAK_ARCH_UPPER, 'close')
def control_arch_lower_open_async(self,delay_seconds: float = 15):
"""异步控制上料斗振动
@ -122,12 +130,12 @@ class RelayFeedController:
delay_seconds: 延迟关闭时间默认15秒
"""
# 关闭下料斗出砼门
self.relay.control(self.BREAK_ARCH_LOWER, 'open')
self.relay.control(self.relay.BREAK_ARCH_LOWER, 'open')
# 异步5秒后关闭
threading.Thread(target=lambda d: self._close_break_arch_lower(delay_seconds),args=(delay_seconds,), daemon=True, name="_close_break_arch_lower").start()
def _close_break_arch_lower(self, delay_seconds: float = 15):
time.sleep(delay_seconds)
print(f"下料斗振动关闭完成,延迟{delay_seconds}")
self.relay.control(self.BREAK_ARCH_LOWER, 'close')
self.relay.control(self.relay.BREAK_ARCH_LOWER, 'close')

View File

@ -5,7 +5,8 @@ import threading
import time
import queue
import json
from core.system_state import SystemState,FeedStatus,Upper_Door_Position
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
@ -16,12 +17,16 @@ 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()
@ -154,9 +159,14 @@ class FeedingControlSystem:
#记录盖板对齐时间
_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)
_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("----浇筑已经启动-----")
@ -172,10 +182,10 @@ class FeedingControlSystem:
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
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)
@ -208,6 +218,7 @@ class FeedingControlSystem:
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 == '':
@ -228,13 +239,14 @@ class FeedingControlSystem:
_ret=artifact_bll.save_artifact_task(_model_data)
if _ret > 0:
# 标记为已处理
self.state._sys_segment_refresh=1
_is_refresh=True
# self.state._sys_segment_refresh=1
processed_artifact_actions.append(item.MouldCode)
if len(processed_artifact_actions) > 4:
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) > 4:
if len(processed_artifact_ids) > 10:
processed_artifact_ids.pop(0)
# 限制最多保存3条记录删除最旧的
@ -266,6 +278,7 @@ class FeedingControlSystem:
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,
@ -276,25 +289,30 @@ class FeedingControlSystem:
)
else:
_pd_record_data = PDRecordModel(
MouldCode=item.MouldCode
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
# self.state._sys_segment_refresh=1
_is_refresh=True
# 标记为已处理
processed_pd_records.append(item.MouldCode)
# 限制最多保存3条记录删除最旧的
if len(processed_pd_records) > 4:
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) > 4:
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}")
@ -382,41 +400,73 @@ class FeedingControlSystem:
def _process_pd_jbl(self):
# pass
#根据当前浇筑块进行最近一块的派单
_isFinish=False
_start_time=None
# _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()
#增加生产阶段检测,
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:
_isFinish=True
if time.time()-_start_time>60:
print('派单超时,人工介入')
_isFinish=True
elif self.state._feed_status==FeedStatus.FFinished:
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
_isFinish=False
else:
print('已开启手动派单,或未设置派单模式')
print('已开启手动派单,或未设置派单模式')
time.sleep(5)
def send_pd_data(self):
"""
发送PD数据到OPC队列
:param _pd_status: PD状态
"""
# 构建PD数据
# print("send_pd_data1")
_cur_mould=self.state.current_mould
if _cur_mould is not None:
# print("send_pd_data2")
if _cur_mould.MouldCode:
_pdrecords = self.pd_record_bll.get_last_pds(_cur_mould.MouldCode,3)
if _pdrecords:
_pdrecord=_pdrecords[0]
_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_data3,{_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_data5,{_pdrecord.TaskID}")
if _pdrecord.TaskID:
if _pdrecord.Status==1:
tasks, artifact_list, send_list, half_volume = self.pd_task_service.process_not_pour_info(_pdrecords)
# print(f"send_pd_data6,{_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
@ -433,22 +483,39 @@ class FeedingControlSystem:
# }
# 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':
_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(_pdrecord.MouldCode,0)
self.pd_record_bll.start_pd(PD_StatusEnum.PD_Send_Finish.value,_pdrecord.MouldCode,0)
else:
_fact_volumn=self.get_fact_volumn(_pdrecord.MouldCode,_pdrecord.BlockNumber)
if _fact_volumn>0:
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:
#更新派单表
_ret_flag=self.pd_record_bll.start_pd(_pdrecord.MouldCode,_fact_volumn)
# print(f"send_pd_data7,{_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":_fact_volumn,
"PlannedVolume":_pdrecord.PlannedVolume
"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)))
@ -456,66 +523,19 @@ class FeedingControlSystem:
return True
else:
print(f'{_pdrecord.MouldCode} 未派单,当前状态为:{_pdrecord.Status}')
return False
return True
else:
print(f'{_pdrecord.MouldCode} 未获取到数据-(等待扫码)')
return False
else:
print(f'接口数据为空')
return False
else:
print(f'当前浇筑模具为空')
return False
else:
return None
return False
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(
@ -540,33 +560,62 @@ class FeedingControlSystem:
#发送到LED屏
time.sleep(app_set_config.led_interval)
time.sleep(2)
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()
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:
self.pd_record_bll.update_PD_record_byid(_notify['ID'],{'GStatus':_notify['Flag']})
print(f'on_opcua_notify收到,var_name:{var_name},val:{val}')
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):
"""启动频率上传线程"""
@ -588,7 +637,8 @@ class FeedingControlSystem:
_id=_cur_mould.ArtifactActionID
_mcode=_cur_mould.MouldCode
# freq=self.inverter_controller.read_frequency()
freq=self.state._mould_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:

View File

@ -51,6 +51,11 @@ class SystemState:
#当前模具状态
self._feed_status=FeedStatus.FNone
#控制派单状态1为可以派单 0未派单 2完成派单
self.pd_status=PD_StatusEnum.PD_Not
#记录当前派单编号
self.pd_mould_code=''
#下料比例变频
self.vf_frequencys=[{'radio':0,'fre':230},{'radio':0.3,'fre':230},{'radio':0.6,'fre':230}]
@ -59,6 +64,7 @@ class SystemState:
self.mould_vibrate_time=0
#当前浇筑模具ArtifactInfo对象需要属性ArtifactID,ArtifactActionID,MouldCode RFID情况下绑定
self.current_mould=None
self.current_block_number=''
# 视觉系统状态
@ -91,18 +97,14 @@ class SystemState:
self.save_db(public_name,value) #有影响的话改成异步
else:
if public_name=='mould_finish_weight':
if time.time()-self.opc_timestamp>=1:
self.opc_queue.put_nowait((public_name, value))
self.opc_timestamp=time.time()
return
elif public_name=='mould_finish_ratio':
if time.time()-self.opc_timestamp>=1:
if time.time()-self.opc_timestamp>=1 or value>=1:
if value>1:
value=1
self.opc_queue.put_nowait((public_name, value))
self.opc_queue.put_nowait(('mould_finish_weight', self._mould_finish_weight))
self.opc_timestamp=time.time()
elif value>=1:
#保证比例能到100%
self.opc_queue.put_nowait((public_name, value))
self.opc_queue.put_nowait(('mould_finish_weight', self._mould_finish_weight))
self.opc_timestamp=time.time()
else:
self.opc_queue.put_nowait((public_name, value))
except queue.Full:
@ -117,16 +119,21 @@ class SystemState:
def save_db(self,public_name,val):
if not val:
return
if public_name=="db_mould_status":
_code=val['mould_code']
if _code:
_status=val['status']
if _status==3:
#完成生产
self.bll_artifact.finish_produce(_code,val['weight'])
elif _status==2:
#开始生产
self.bll_artifact.start_produce(_code)
try:
if public_name=="db_mould_status":
_code=val['mould_code']
if _code:
_status=val['status']
if _status==3:
#完成生产
self.bll_artifact.finish_produce(_code,val['weight'])
self.opc_queue.put_nowait(('sys_segment_refresh',1))
elif _status==2:
#开始生产
self.bll_artifact.start_produce(_code)
self.opc_queue.put_nowait(('sys_segment_refresh',1))
except queue.Full:
pass
class FeedStatus(IntEnum):
@ -155,11 +162,32 @@ class Upper_Door_Position(IntEnum):
ZDS = 2
Returning = 3
class PD_StatusEnum(IntEnum):
# 预定派单
PD_Ready = 1
# 不派单
PD_Not = 0
# 完成派单下发
PD_Send_Finish = 2
#派单超时
PD_TimeOut=3
#未扫码
PD_UnScan=4
#已完成
PD_Finish=5
#派单错误
PD_Error=1001
# class Upper_PLC_Status(IntEnum):
# # 即将振捣室
# PLC_ZDS_Ready = 4
# #到达振捣室
# #到达
# PLC_ZDS_Finish = 5
# #即将搅拌楼
# PLC_JBL_Ready = 64

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -97,7 +97,7 @@ class InverterController:
self.inverter.write_register(0x2000, 1) # 1=正转运行
print("启动变频器")
elif action == 'stop':
self.inverter.write_register(0x2000, 5) # 6=减速停机,5自由停机
self.inverter.write_register(0x2000, 6) # 6=减速停机,5自由停机
print("停止变频器")
_ret=True
else:

224
hardware/metric_device.py Normal file
View File

@ -0,0 +1,224 @@
# metric_device.py
import socket
import threading
import time
import statistics
from abc import ABC, abstractmethod
from typing import Optional, Dict, Any, List
from dataclasses import dataclass, asdict
@dataclass
class DeviceReading:
"""标准化设备读数"""
weight: Optional[int] # 重量None 表示 OL/ER
raw_data: bytes # 原始字节
status: str # 'ST', 'US', 'OL', 'ER'
timestamp: float # 本地接收时间time.time()
rtt_ms: float # 网络往返延迟(毫秒)
is_valid: bool # 是否含有效重量
def to_dict(self) -> Dict[str, Any]:
return {
**asdict(self),
"timestamp_iso": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(self.timestamp)),
"weight_kg": self.weight / 1000.0 if self.weight is not None else None,
}
class MetricDevice(ABC):
"""抽象计量设备基类 —— 支持 get() + RTT 统计"""
def __init__(self, ip: str, port: int, name: str = ""):
self.ip = ip
self.port = port
self.name = name or f"{ip}:{port}"
self._latest_reading: Optional[DeviceReading] = None
self._lock = threading.Lock()
# RTT 统计(滑动窗口)
self._rtt_window: List[float] = []
self._rtt_max_len = 100 # 保留最近 100 次 RTT
self._rtt_stats = {
"min_ms": float('inf'),
"max_ms": 0.0,
"avg_ms": 0.0,
"count": 0,
}
def _update_rtt(self, rtt_ms: float):
"""更新 RTT 统计"""
self._rtt_window.append(rtt_ms)
if len(self._rtt_window) > self._rtt_max_len:
self._rtt_window.pop(0)
rtt_list = self._rtt_window
self._rtt_stats.update({
"min_ms": min(rtt_list) if rtt_list else 0,
"max_ms": max(rtt_list) if rtt_list else 0,
"avg_ms": statistics.mean(rtt_list) if rtt_list else 0,
"count": len(rtt_list),
})
def get_rtt_stats(self) -> Dict[str, float]:
"""获取 RTT 统计信息"""
with self._lock:
return self._rtt_stats.copy()
def get(self) -> Optional[Dict[str, Any]]:
""" 统一 get() 接口:返回最新读数(含 RTT"""
with self._lock:
if self._latest_reading is None:
return None
return self._latest_reading.to_dict()
@abstractmethod
def _connect_and_run(self):
"""子类实现连接与数据接收循环"""
pass
def start(self):
"""启动后台线程"""
thread = threading.Thread(target=self._connect_and_run, daemon=True, name=f"Dev-{self.name}")
thread.start()
def _update_reading(self, reading: DeviceReading):
"""线程安全更新最新读数"""
with self._lock:
self._latest_reading = reading
class TCPScaleDevice(MetricDevice):
"""TCP ASCII 协议称重设备"""
def __init__(self, ip: str, port: int, name: str = ""):
super().__init__(ip, port, name)
self._buffer = b""
self._socket: Optional[socket.socket] = None
self._running = True
self._valid_weight = None # 有效重量
def _parse_line(self, line: bytes) -> Optional[DeviceReading]:
try:
clean = line.strip()
if not clean:
return None
parts = clean.split(b',')
if len(parts) < 3:
return DeviceReading(
weight=None, raw_data=clean, status="??",
timestamp=time.time(), rtt_ms=0.0, is_valid=False
)
status = parts[0].decode('ascii', errors='replace').upper()
mode = parts[1].decode('ascii', errors='replace').upper()
weight_str = parts[2].decode('ascii', errors='replace')
weight = None
is_valid = False
if weight_str.replace('+', '').replace('-', '').isdigit():
try:
weight = int(weight_str)
is_valid = True
except ValueError:
pass
return DeviceReading(
weight=weight,
raw_data=clean,
status=status,
timestamp=time.time(),
rtt_ms=0.0, # 稍后更新
is_valid=is_valid,
)
except Exception:
return None
def _extract_lines(self, data: bytes) -> List[bytes]:
"""自适应行分割"""
self._buffer += data
lines = []
if b'\r\n' in self._buffer or b'\n' in self._buffer:
norm = self._buffer.replace(b'\r\n', b'\n')
parts = norm.split(b'\n')
*complete, self._buffer = parts
lines = [line for line in complete if line]
else:
# 无换行符:整包作为单行
if self._buffer:
lines = [self._buffer]
self._buffer = b""
return lines
def _connect_and_run(self):
while self._running:
try:
# 重连逻辑
if self._socket is None:
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._socket.settimeout(5.0)
self._socket.connect((self.ip, self.port))
# 接收数据
t0 = time.perf_counter()
data = self._socket.recv(1024)
recv_rtt = (time.perf_counter() - t0) * 1000
if not data:
break
# 更新 RTT
self._update_rtt(recv_rtt * 2)
# 解析
lines = self._extract_lines(data)
for line in lines:
reading = self._parse_line(line)
if reading:
reading.rtt_ms = recv_rtt * 2 # 近似 RTT
self._update_reading(reading)
if reading.is_valid: # 重量有效
self._update_valid_weight(reading.weight)
except socket.timeout:
continue
except (OSError, socket.error) as e:
print(f"[{self.name}] Error: {e}")
if self._socket:
self._socket.close()
self._socket = None
time.sleep(2)
except Exception as e:
print(f"[{self.name}] Fatal: {e}")
break
if self._socket:
self._socket.close()
def _update_valid_weight(self, weight: int):
with self._lock:
self._valid_weight = weight
def get_valid_weight(self) -> Optional[int]:
with self._lock:
return self._valid_weight
# ===== 设计模式:设备工厂 + 管理器 =====
class DeviceManager:
"""设备工厂 + 单例管理"""
_instances: Dict[str, MetricDevice] = {}
_lock = threading.Lock()
@classmethod
def get_device(cls, ip: str, port: int, name: str = "") -> MetricDevice:
""" 工厂方法:按 (ip, port) 单例返回设备"""
key = f"{ip}:{port}"
with cls._lock:
if key not in cls._instances:
device = TCPScaleDevice(ip, port, name)
device.start()
cls._instances[key] = device
return cls._instances[key]
@classmethod
def get_all_devices(cls) -> List[MetricDevice]:
return list(cls._instances.values())

View File

@ -305,13 +305,13 @@ class RelayController:
def control_upper_to_jbl(self):
"""控制上料斗到搅拌楼"""
# self.control(self.UPPER_TO_ZD, 'close')
# self.control(self.UPPER_TO_JBL, 'open')
self.control(self.UPPER_TO_ZD, 'close')
self.control(self.UPPER_TO_JBL, 'open')
def control_upper_to_zd(self):
"""控制上料斗到料斗"""
# self.control(self.UPPER_TO_JBL, 'close')
# self.control(self.UPPER_TO_ZD, 'open')
self.control(self.UPPER_TO_JBL, 'close')
self.control(self.UPPER_TO_ZD, 'open')
def close_all(self):
"""关闭所有继电器"""
@ -324,3 +324,5 @@ class RelayController:
self.control(self.DOOR_LOWER_CLOSE, 'close')
self.control(self.DOOR_UPPER_OPEN, 'close')
self.control(self.DOOR_UPPER_CLOSE, 'close')

View File

@ -1,200 +0,0 @@
# hardware/transmitter.py
import socket
import threading
from config.ini_manager import ini_manager
from config.settings import app_set_config
import time
class TransmitterController:
def __init__(self):
self.upper_ip = ini_manager.upper_transmitter_ip
self.upper_port = ini_manager.upper_transmitter_port
self.lower_ip = ini_manager.lower_transmitter_ip
self.lower_port = ini_manager.lower_transmitter_port
# 存储最新重量值
self.latest_weights = {1: None, 2: None}
# 存储连接状态
self.connection_status = {1: False, 2: False}
# 线程控制
self.running = True
self.threads = {}
# 连接配置
self.TIMEOUT = 5 # 连接超时时间
self.BUFFER_SIZE = 1024
# 启动后台接收线程
self._start_receiver_threads()
def _start_receiver_threads(self):
"""启动后台接收线程"""
for transmitter_id in [1, 2]:
if (transmitter_id == 1 and self.upper_ip and self.upper_port) or \
(transmitter_id == 2 and self.lower_ip and self.lower_port):
thread = threading.Thread(
target=self._continuous_receiver,
args=(transmitter_id,),
daemon=True,
name=f'transmitter_receiver_{transmitter_id}'
)
thread.start()
self.threads[transmitter_id] = thread
print(f"启动变送器 {transmitter_id} 后台接收线程")
def _continuous_receiver(self, transmitter_id):
"""后台持续接收数据的线程函数"""
while self.running:
IP = None
PORT = None
if transmitter_id == 1:
IP = self.upper_ip
PORT = self.upper_port
elif transmitter_id == 2:
IP = self.lower_ip
PORT = self.lower_port
if not IP or not PORT:
time.sleep(5)
continue
sock = None
try:
# 创建连接
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(self.TIMEOUT)
sock.connect((IP, PORT))
self.connection_status[transmitter_id] = True
print(f"变送器 {transmitter_id} 连接成功: {IP}:{PORT}")
# 持续接收数据
while self.running:
try:
data = sock.recv(self.BUFFER_SIZE)
if data:
# 提取有效数据包
packet = self.get_latest_valid_packet(data)
if packet:
# 解析重量
weight = self.parse_weight(packet)
if weight is not None:
self.latest_weights[transmitter_id] = weight
# 可选:打印接收到的重量
# print(f"变送器 {transmitter_id} 重量: {weight}")
else:
# 连接关闭
print(f"变送器 {transmitter_id} 连接关闭")
break
except socket.timeout:
# 超时是正常的,继续接收
continue
except Exception as e:
print(f"接收数据异常: {e}")
break
except ConnectionRefusedError:
print(f"变送器 {transmitter_id} 连接失败:{IP}:{PORT} 拒绝连接")
except Exception as e:
print(f"变送器 {transmitter_id} 异常:{e}")
finally:
self.connection_status[transmitter_id] = False
if sock:
try:
sock.close()
except:
pass
# 重试间隔
time.sleep(3)
# 直接读取 变送器返回的数据(从缓存中获取)
def read_data_sub(self, transmitter_id):
"""
Args: transmitter_id 为1 表示上料斗, 为2 表示下料斗
return: 读取成功返回重量 weight: int, 失败返回 None
"""
# 直接返回缓存的最新重量值
return self.latest_weights.get(transmitter_id)
def get_connection_status(self, transmitter_id):
"""
获取变送器连接状态
Args: transmitter_id 为1 表示上料斗, 为2 表示下料斗
return: 连接状态 bool
"""
return self.connection_status.get(transmitter_id, False)
def stop(self):
"""停止后台线程"""
self.running = False
# 等待线程结束
for thread in self.threads.values():
if thread.is_alive():
thread.join(timeout=2)
print("变送器后台接收线程已停止")
def get_latest_valid_packet(self, raw_data):
"""
解决TCP粘包:
从原始数据中,筛选所有有效包,返回最新的一个有效包
有效包标准: 1. 能UTF-8解码 2. 按逗号拆分≥3个字段 3. 第三个字段含数字(重量)
"""
DELIMITER = b'\r\n'
# 1. 按分隔符拆分,过滤空包
packets = [p for p in raw_data.split(DELIMITER) if p]
if not packets:
return None
valid_packets = []
for packet in packets:
try:
# 过滤无效ASCII字符只保留可见字符
valid_chars = [c for c in packet if 32 <= c <= 126]
filtered_packet = bytes(valid_chars)
# 2. 验证解码
data_str = filtered_packet.decode('utf-8').strip()
# 3. 验证字段数量
parts = data_str.split(',')
if len(parts) < 3:
continue
# 4. 验证重量字段含数字
weight_part = parts[2].strip()
if not any(char.isdigit() for char in weight_part):
continue
# 满足所有条件,加入有效包列表
valid_packets.append(packet)
except (UnicodeDecodeError, IndexError):
# 解码失败或字段异常,跳过该包
continue
# 返回最后一个有效包最新无有效包则返回None
return valid_packets[-1] if valid_packets else None
def parse_weight(self, packet_data):
"""解析重量函数:提取重量数值(如从 b'ST,NT,+0000175\r\n' 中提取 175)"""
try:
data_str = packet_data.decode('utf-8').strip()
parts = data_str.split(',')
# 确保有完整的数据包,三个字段
if len(parts) < 3:
print(f"parse_weight: 包格式错误(字段不足):{data_str}")
return None
weight_part = parts[2].strip()
return int(''.join(filter(str.isdigit, weight_part)))
except (IndexError, ValueError, UnicodeDecodeError) as e:
# print(f"数据解析失败:{e},原始数据包:{packet_data}")
return None
def read_data(self,transmitter_id):
"""获取重量函数根据变送器ID获取当前重量,三次"""
max_try_times=5
try_times=0
while try_times<max_try_times:
weight=self.read_data_sub(transmitter_id)
if weight is not None:
return weight
try_times+=1
print(f'-----获取重量异常-------------- transmitter_id: {transmitter_id}')
print(f'-----获取重量异常-------------- transmitter_id: {transmitter_id}')
return None

View File

@ -0,0 +1,32 @@
# hardware/transmitter.py
from pymodbus.exceptions import ModbusException
import socket
from config.ini_manager import ini_manager
from config.settings import app_set_config
import time
from metric_device import DeviceManager
from enum import Enum, unique
@unique
class HopperType(Enum):
UPPER = 1 # 上料斗
LOWER = 2 # 下料斗
class TransmitterController:
def __init__(self, relay_controller=None):
self.relay_controller = relay_controller
UPPER_HOPPER_IP = ini_manager.upper_transmitter_ip
UPPER_HOPPER_PORT = ini_manager.upper_transmitter_port
LOWER_HOPPER_IP = ini_manager.lower_transmitter_ip
LOWER_HOPPER_PORT = ini_manager.lower_transmitter_port
self.upper_scale = DeviceManager.get_device(UPPER_HOPPER_IP, UPPER_HOPPER_PORT, "upper_scale") # 上料斗
self.lower_scale = DeviceManager.get_device(LOWER_HOPPER_IP, LOWER_HOPPER_PORT, "lower_scale") # 下料斗
def read_data(self, transmitter_id):
"""获取重量函数:
Args: transmitter_id 为指定的上/下料斗的id
"""
if transmitter_id == HopperType.UPPER:
return self.upper_scale.get_valid_weight()
elif transmitter_id == HopperType.LOWER:
return self.lower_scale.get_valid_weight()

View File

@ -66,7 +66,7 @@ class OpcuaClientFeed(Thread):
self.name="opcua_feed_client"
self.target_var_paths = []
#订阅配置文件中的参数
self.subscription_name=["pd_notify","pd_set_mode","pd_set_volume"]
self.subscription_name=["pd_notify","pd_notify_finish","pd_set_mode","pd_set_volume"]
# self.server_url = ""
self.read_opc_config() # 读取配置文件
@ -88,7 +88,7 @@ class OpcuaClientFeed(Thread):
"""停止线程+断开连接"""
self.is_running = False
self.disconnect()
self.wait()
# self.wait()
print("opcua客户端线程已退出")
def connect(self):

View File

@ -83,6 +83,7 @@ class SimpleOPCUAServer:
self.feed_status = self.mould.add_variable(self.namespace, "feed_status", ua.Variant(0, ua.VariantType.Int16))
self.pd_data=self.pd.add_variable(self.namespace, "pd_data", ua.Variant("", ua.VariantType.String))
self.pd_notify=self.pd.add_variable(self.namespace, "pd_notify", ua.Variant("", ua.VariantType.String))
self.pd_notify_finish=self.pd.add_variable(self.namespace, "pd_notify_finish", ua.Variant("", ua.VariantType.String))
self.pd_set_mode=self.pd.add_variable(self.namespace, "set_mode", ua.Variant(0, ua.VariantType.Int16))
self.pd_set_volume=self.pd.add_variable(self.namespace, "set_volume", ua.Variant("", ua.VariantType.String))
@ -106,6 +107,7 @@ class SimpleOPCUAServer:
self.feed_status.set_writable(True)
self.pd_data.set_writable(True)
self.pd_notify.set_writable(True)
self.pd_notify_finish.set_writable(True)
self.pd_set_mode.set_writable(True)
self.pd_set_volume.set_writable(True)
self.sys_set_mode.set_writable(True)

View File

@ -14370,139 +14370,139 @@ qt_resource_struct = b"\
\x00\x00\x00\x10\x00\x02\x00\x00\x00C\x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x05\xb8\x00\x00\x00\x00\x00\x01\x00\x02\xdd\x16\
\x00\x00\x01\x9c\x94G\xc6\x9e\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x04\x02\x00\x00\x00\x00\x00\x01\x00\x01\xfb\xa6\
\x00\x00\x01\x9c\x94G\xc6\x9f\
\x00\x00\x01\x9c\xec\x0b\x8a \
\x00\x00\x05(\x00\x00\x00\x00\x00\x01\x00\x02\xbc.\
\x00\x00\x01\x9c\x94G\xc6\xa6\
\x00\x00\x01\x9c\xec\x0b\x8a%\
\x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x00+8\
\x00\x00\x01\x9c\x94G\xc6\x9f\
\x00\x00\x01\x9c\xec\x0b\x8a \
\x00\x00\x00T\x00\x00\x00\x00\x00\x01\x00\x00\x11r\
\x00\x00\x01\x9c\x94G\xc6\xa0\
\x00\x00\x01\x9c\xec\x0b\x8a \
\x00\x00\x05\xd0\x00\x00\x00\x00\x00\x01\x00\x02\xddk\
\x00\x00\x01\x9c\x94G\xc6\xa1\
\x00\x00\x01\x9c\xec\x0b\x8a\x22\
\x00\x00\x02\xa6\x00\x00\x00\x00\x00\x01\x00\x01\x08\xf0\
\x00\x00\x01\x9c\x94G\xc6\xa1\
\x00\x00\x01\x9c\xec\x0b\x8a\x22\
\x00\x00\x00:\x00\x00\x00\x00\x00\x01\x00\x00\x01\x05\
\x00\x00\x01\x9c\x94G\xc6\xa0\
\x00\x00\x01\x9c\xec\x0b\x8a \
\x00\x00\x02\xd8\x00\x00\x00\x00\x00\x01\x00\x01\x1d/\
\x00\x00\x01\x9c\x94G\xc6\xa0\
\x00\x00\x01\x9c\xec\x0b\x8a \
\x00\x00\x01\xda\x00\x00\x00\x00\x00\x01\x00\x00\x84\x83\
\x00\x00\x01\x9c\x94G\xc6\xa6\
\x00\x00\x01\x9c\xec\x0b\x8a%\
\x00\x00\x03\x8e\x00\x00\x00\x00\x00\x01\x00\x01N/\
\x00\x00\x01\x9c\x94G\xc6\x9f\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x04\x84\x00\x00\x00\x00\x00\x01\x00\x02\x92\xa4\
\x00\x00\x01\x9c\x94G\xc6\x9f\
\x00\x00\x01\x9c\xec\x0b\x8a \
\x00\x00\x01$\x00\x00\x00\x00\x00\x01\x00\x00-\x82\
\x00\x00\x01\x9c\x94G\xc6\x9a\
\x00\x00\x01\x9c\xec\x0b\x8a\x1d\
\x00\x00\x02x\x00\x00\x00\x00\x00\x01\x00\x00\xf5\xe3\
\x00\x00\x01\x9c\x94G\xc6\xa8\
\x00\x00\x01\x9c\xec\x0b\x8a%\
\x00\x00\x01L\x00\x00\x00\x00\x00\x01\x00\x00c\xfb\
\x00\x00\x01\x9c\x94G\xc6\xa3\
\x00\x00\x01\x9c\xec\x0b\x8a\x22\
\x00\x00\x02\x92\x00\x00\x00\x00\x00\x01\x00\x01\x02\xaa\
\x00\x00\x01\x9c\x94G\xc6\xa3\
\x00\x00\x01\x9c\xec\x0b\x8a#\
\x00\x00\x03\x02\x00\x00\x00\x00\x00\x01\x00\x01%\xbb\
\x00\x00\x01\x9c\x94G\xc6\xaa\
\x00\x00\x01\x9c\xec\x0b\x8a&\
\x00\x00\x05p\x00\x00\x00\x00\x00\x01\x00\x02\xc7\x9f\
\x00\x00\x01\x9c\x94G\xc6\xa4\
\x00\x00\x01\x9c\xec\x0b\x8a$\
\x00\x00\x03\xae\x00\x00\x00\x00\x00\x01\x00\x01Q\xcb\
\x00\x00\x01\x9c\x94G\xc6\xab\
\x00\x00\x01\x9c\xec\x0b\x8a(\
\x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x9c\x94G\xc6\xa4\
\x00\x00\x01\x9c\xec\x0b\x8a#\
\x00\x00\x04\xc0\x00\x00\x00\x00\x00\x01\x00\x02\x96\x82\
\x00\x00\x01\x9c\x94G\xc6\xa1\
\x00\x00\x01\x9c\xec\x0b\x8a!\
\x00\x00\x00\x88\x00\x00\x00\x00\x00\x01\x00\x00 \x01\
\x00\x00\x01\x9c\x94G\xc6\xa6\
\x00\x00\x01\x9c\xec\x0b\x8a%\
\x00\x00\x06\x9a\x00\x00\x00\x00\x00\x01\x00\x03Yo\
\x00\x00\x01\x9c\x94G\xc6\xa4\
\x00\x00\x01\x9c\xec\x0b\x8a#\
\x00\x00\x04\xa4\x00\x00\x00\x00\x00\x01\x00\x02\x94\xf7\
\x00\x00\x01\x9c\x94G\xc6\xa0\
\x00\x00\x01\x9c\xec\x0b\x8a!\
\x00\x00\x03^\x00\x00\x00\x00\x00\x01\x00\x010\x95\
\x00\x00\x01\x9c\x94G\xc6\xa7\
\x00\x00\x01\x9c\xec\x0b\x8a%\
\x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x00((\
\x00\x00\x01\x9c\x94G\xc6\xa1\
\x00\x00\x01\x9c\xec\x0b\x8a!\
\x00\x00\x06l\x00\x00\x00\x00\x00\x01\x00\x036\xa7\
\x00\x00\x01\x9c\x94G\xc6\xa6\
\x00\x00\x01\x9c\xec\x0b\x8a$\
\x00\x00\x02\x18\x00\x00\x00\x00\x00\x01\x00\x00\xe1c\
\x00\x00\x01\x9c\x94G\xc6\x9b\
\x00\x00\x01\x9c\xec\x0b\x8a\x1d\
\x00\x00\x02`\x00\x00\x00\x00\x00\x01\x00\x00\xf4\xe4\
\x00\x00\x01\x9c\x94G\xc6\xa4\
\x00\x00\x01\x9c\xec\x0b\x8a#\
\x00\x00\x03\xc4\x00\x00\x00\x00\x00\x01\x00\x01\xa8}\
\x00\x00\x01\x9c\x94G\xc6\xa5\
\x00\x00\x01\x9c\xec\x0b\x8a$\
\x00\x00\x03>\x00\x00\x00\x00\x00\x01\x00\x01/\xe2\
\x00\x00\x01\x9c\x94G\xc6\x9b\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x01\xf8\x00\x00\x00\x00\x00\x01\x00\x00\xe0\xd7\
\x00\x00\x01\x9c\x94G\xc6\x9d\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x01\x80\x00\x00\x00\x00\x00\x01\x00\x00z\x9d\
\x00\x00\x01\x9c\x94G\xc6\x9d\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x05\xea\x00\x00\x00\x00\x00\x01\x00\x02\xeb\xec\
\x00\x00\x01\x9c\x94G\xc6\x9d\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x06T\x00\x00\x00\x00\x00\x01\x00\x032L\
\x00\x00\x01\x9c\x94G\xc6\xaa\
\x00\x00\x01\x9c\xec\x0b\x8a&\
\x00\x00\x06\x86\x00\x00\x00\x00\x00\x01\x00\x03?\x19\
\x00\x00\x01\x9c\x94G\xc6\xa2\
\x00\x00\x01\x9c\xec\x0b\x8a\x22\
\x00\x00\x04h\x00\x00\x00\x00\x00\x01\x00\x02\x8a\xb0\
\x00\x00\x01\x9c\x94G\xc6\xa8\
\x00\x00\x01\x9c\xec\x0b\x8a&\
\x00\x00\x05>\x00\x00\x00\x00\x00\x01\x00\x02\xbc\xd1\
\x00\x00\x01\x9c\x94G\xc6\xa8\
\x00\x00\x01\x9c\xec\x0b\x8a&\
\x00\x00\x05\x0c\x00\x00\x00\x00\x00\x01\x00\x02\xb9\x8e\
\x00\x00\x01\x9c\x94G\xc6\xa0\
\x00\x00\x01\x9c\xec\x0b\x8a \
\x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00#\xb3\
\x00\x00\x01\x9c\x94G\xc6\xa0\
\x00\x00\x01\x9c\xec\x0b\x8a \
\x00\x00\x00\x9e\x00\x00\x00\x00\x00\x01\x00\x00!\x14\
\x00\x00\x01\x9c\x94G\xc6\x9d\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x06\xb2\x00\x00\x00\x00\x00\x01\x00\x03_2\
\x00\x00\x01\x9c\x94G\xc6\xa3\
\x00\x00\x01\x9c\xec\x0b\x8a\x22\
\x00\x00\x01`\x00\x00\x00\x00\x00\x01\x00\x00rC\
\x00\x00\x01\x9c\x94G\xc6\xa8\
\x00\x00\x01\x9c\xec\x0b\x8a&\
\x00\x00\x064\x00\x00\x00\x00\x00\x01\x00\x03)\xfc\
\x00\x00\x01\x9c\x94G\xc6\xa8\
\x00\x00\x01\x9c\xec\x0b\x8a&\
\x00\x00\x05\xa6\x00\x00\x00\x00\x00\x01\x00\x02\xd7\xf5\
\x00\x00\x01\x9c\x94G\xc6\xa5\
\x00\x00\x01\x9c\xec\x0b\x8a$\
\x00\x00\x01\xa0\x00\x00\x00\x00\x00\x01\x00\x00{\x07\
\x00\x00\x01\x9c\x94G\xc6\xa7\
\x00\x00\x01\x9c\xec\x0b\x8a%\
\x00\x00\x03\x1e\x00\x00\x00\x00\x00\x01\x00\x01&\xcb\
\x00\x00\x01\x9c\x94G\xc6\xa7\
\x00\x00\x01\x9c\xec\x0b\x8a%\
\x00\x00\x04\xdc\x00\x00\x00\x00\x00\x01\x00\x02\x98\x0d\
\x00\x00\x01\x9c\x94G\xc6\xa5\
\x00\x00\x01\x9c\xec\x0b\x8a$\
\x00\x00\x06\x0a\x00\x00\x00\x00\x00\x01\x00\x02\xecg\
\x00\x00\x01\x9c\x94G\xc6\xa1\
\x00\x00\x01\x9c\xec\x0b\x8a!\
\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x84.\
\x00\x00\x01\x9c\x94G\xc6\x9b\
\x00\x00\x01\x9c\xec\x0b\x8a\x1d\
\x00\x00\x04\xf2\x00\x00\x00\x00\x00\x01\x00\x02\x9c\x95\
\x00\x00\x01\x9c\x94G\xc6\x9e\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x04L\x00\x00\x00\x00\x00\x01\x00\x02h\xd9\
\x00\x00\x01\x9c\x94G\xc6\x9b\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x03\xd6\x00\x00\x00\x00\x00\x01\x00\x01\xaa\x0a\
\x00\x00\x01\x9c\x94G\xc6\xa8\
\x00\x00\x01\x9c\xec\x0b\x8a%\
\x00\x00\x00n\x00\x00\x00\x00\x00\x01\x00\x00\x1d\x5c\
\x00\x00\x01\x9c\x94G\xc6\xaa\
\x00\x00\x01\x9c\xec\x0b\x8a&\
\x00\x00\x05\x88\x00\x00\x00\x00\x00\x01\x00\x02\xc8\xac\
\x00\x00\x01\x9c\x94G\xc6\xa6\
\x00\x00\x01\x9c\xec\x0b\x8a%\
\x00\x00\x02\xea\x00\x00\x00\x00\x00\x01\x00\x01\x22\x08\
\x00\x00\x01\x9c\x94G\xc6\x9f\
\x00\x00\x01\x9c\xec\x0b\x8a \
\x00\x00\x06 \x00\x00\x00\x00\x00\x01\x00\x03&\x04\
\x00\x00\x01\x9c\x94G\xc6\x9e\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x020\x00\x00\x00\x00\x00\x01\x00\x00\xea\x99\
\x00\x00\x01\x9c\x94G\xc6\xa4\
\x00\x00\x01\x9c\xec\x0b\x8a#\
\x00\x00\x05Z\x00\x00\x00\x00\x00\x01\x00\x02\xc5\x8e\
\x00\x00\x01\x9c\x94G\xc6\xa7\
\x00\x00\x01\x9c\xec\x0b\x8a%\
\x00\x00\x04\x22\x00\x00\x00\x00\x00\x01\x00\x01\xffL\
\x00\x00\x01\x9c\x94G\xc6\xa2\
\x00\x00\x01\x9c\xec\x0b\x8a\x22\
\x00\x00\x01:\x00\x00\x00\x00\x00\x01\x00\x00.\xdc\
\x00\x00\x01\x9c\x94G\xc6\xa2\
\x00\x00\x01\x9c\xec\x0b\x8a\x22\
\x00\x00\x03\xf0\x00\x00\x00\x00\x00\x01\x00\x01\xabj\
\x00\x00\x01\x9c\x94G\xc6\xa2\
\x00\x00\x01\x9c\xec\x0b\x8a\x22\
\x00\x00\x03v\x00\x00\x00\x00\x00\x01\x00\x01I\x0f\
\x00\x00\x01\x9c\x94G\xc6\xa3\
\x00\x00\x01\x9c\xec\x0b\x8a\x22\
\x00\x00\x02H\x00\x00\x00\x00\x00\x01\x00\x00\xf4\x8f\
\x00\x00\x01\x9c\x94G\xc6\x9e\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x02\xc0\x00\x00\x00\x00\x00\x01\x00\x01\x1c\xda\
\x00\x00\x01\x9c\x94G\xc6\x9d\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x044\x00\x00\x00\x00\x00\x01\x00\x02h\x84\
\x00\x00\x01\x9c\x94G\xc6\x9e\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
\x00\x00\x00\xb4\x00\x00\x00\x00\x00\x01\x00\x00#^\
\x00\x00\x01\x9c\x94G\xc6\x9e\
\x00\x00\x01\x9c\xec\x0b\x8a\x1e\
"
def qInitResources():

56
service/jbl_service.py Normal file
View File

@ -0,0 +1,56 @@
import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from service.api_http_client import BaseHttpClient
from config.ini_manager import ini_manager
class JBLService(BaseHttpClient):
"""搅拌楼服务类提供搅拌楼服务相关的API调用"""
def __init__(self):
"""初始化搅拌楼服务"""
super().__init__()
self._host = ini_manager.jbl_api_url
def is_finish_jb(self)->bool:
"""
获取搅拌机状态信息
Returns:
是否下料完成
"""
url = f"{self._host}/API/flag"
# 调用API获取数据
response_data = self.request(method='GET', url=url, timeout=ini_manager.api_timeout,
retries=ini_manager.api_max_retries, retry_interval=ini_manager.api_retry_interval)
# 解析数据
data = response_data.get('records', {})
if not data:
print(f"未获取到搅拌机记录信息")
return None
_flag=data.get('Flag', '')
if _flag=='1xnwq)A':
print(f"搅拌机下料完成")
return True
else:
print(f"搅拌机状态:{_flag}, 下料未完成")
return False
app_jbl_service = JBLService()
if __name__ == "__main__":
# 创建楼服务实例
status = app_jbl_service.is_finish_jb()
if status:
print("搅拌机下料完成")
else:
print("搅拌机下料未完成")

View File

@ -1,10 +1,11 @@
[api]
max_retries = 3
retry_interval = 1.0
timeout = 30.0
timeout = 3
auth_timeout = 43200
base_url = https://www.shnthy.com:9154
login_model = {"Program": 11, "SC": "1000000001", "loginName": "leduser", "password": "bfcda35cf4eba92d4583931bbe4ff72ffdfa8b5c9c4b72611bd33f5babee069d"}
jbl_api_url=http://10.6.242.111:5002
[app]
log_path = logs/app.log

143
tests/pd_service.py Normal file
View File

@ -0,0 +1,143 @@
import sys
import os
import math
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from hardware.transmitter import TransmitterController
class PdService:
def __init__(self):
print('派单测试初始化')
self.transmitter_controller=TransmitterController()
# self.pd_record_bll=PDRecordBll()
def get_fact_volumn_12(self,block_number:str='') -> float:
"""获取实际派单发量"""
# _now_volume=0
if not block_number:
print('分块号不能为空')
return
_pd_volume=0
_init_lower_weight=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 block_number=='B1':
_left_volume=_left_volume-0.5
if _left_volume>0:
_pd_volume=1.6-_left_volume
_pd_volume=math.ceil(_pd_volume*10)/10
else:
_pd_volume=1.6
elif block_number in ['B3','L1']:
_pd_volume=1.5
elif block_number=='B2':
#浇B3
_pd_volume=1.6
# self.prev_pd_volume=2
#调整
elif block_number=='L2':
if _init_lower_weight>950 and _init_lower_weight<=1350:
_pd_volume=1.5
elif _init_lower_weight>1350 and _init_lower_weight<=1600:
_pd_volume=1.4
elif _init_lower_weight<=950 and _init_lower_weight>800:
_pd_volume=1.6
else:
#1.9方大约L2和F的量
_pd_volume=1.9
# if _weight>1300:
#留0.15 math.floor(_now_volume*10)/10 保留一位小数,丢掉其他的
# if prev_pd_volume>0:
# _pd_volume=_pd_volume-(_left_volume+prev_pd_volume-1.86)
# else:
#不知道上一块叫的多少
_pd_volume=_pd_volume-(_left_volume+1.6-1.46)
_pd_volume=math.ceil(_pd_volume*10)/10+0.1
# _pd_volume=math.ceil(_pd_volume*10)/10
if _pd_volume>2.1:
_pd_volume=2.1
elif _pd_volume<0.8:
_pd_volume=0.8
return _pd_volume
def get_fact_volumn(self,block_number:str='') -> float:
"""获取实际派单发量"""
# _now_volume=0
if not block_number:
print('分块号不能为空')
return
_pd_volume=0
_init_lower_weight=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 block_number=='B1':
_left_volume=_left_volume-0.6
if _left_volume>0:
_pd_volume=2-_left_volume
_pd_volume=math.ceil(_pd_volume*10)/10
else:
_pd_volume=2
elif block_number in ['B3','L1']:
_pd_volume=1.9
elif block_number=='B2':
#浇B3
_pd_volume=2
# self.prev_pd_volume=2
#调整
elif block_number=='L2':
if _init_lower_weight>1250 and _init_lower_weight<=1650:
_pd_volume=1.9
elif _init_lower_weight>1650 and _init_lower_weight<=1900:
_pd_volume=1.8
elif _init_lower_weight<=1250 and _init_lower_weight>1100:
_pd_volume=2
else:
#2.4方大约L2和F的量
_pd_volume=2.4
# if _weight>1300:
#留0.15 math.floor(_now_volume*10)/10 保留一位小数,丢掉其他的
# if prev_pd_volume>0:
# _pd_volume=_pd_volume-(_left_volume+prev_pd_volume-1.86)
# else:
#不知道上一块叫的多少
_pd_volume=_pd_volume-(_left_volume+1.9-1.86)
_pd_volume=math.ceil(_pd_volume*10)/10+0.1
# _pd_volume=math.ceil(_pd_volume*10)/10
if _pd_volume>2.1:
_pd_volume=2.1
elif _pd_volume<0.8:
_pd_volume=0.8
return _pd_volume
if __name__ == "__main__":
system = PdService()
volume= system.get_fact_volumn('L2',1.9)
print("派单方量:",volume)

View File

@ -7,13 +7,14 @@ import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import pytest
import time
from core.system_state import SystemState
from core.system_state import SystemState,PD_StatusEnum
from core.system import FeedingControlSystem
from busisness.models import ArtifactInfo,FreqRecordModel
from busisness.blls import FreqRecordBll
from hardware.relay import RelayController
system=FeedingControlSystem()
system.state.current_mould=ArtifactInfo(MouldCode="SHR2B3-5")
system.state.current_mould=ArtifactInfo(MouldCode="SHR2F-8")
def test_pd_record_send():
"""测试派单记录发送"""
@ -22,12 +23,24 @@ def test_pd_record_send():
def test_api_process():
system._process_api_db()
def test_freq_record_send():
"""测试频率记录发送"""
bll=FreqRecordBll()
record_id=bll.insert_freq_record(FreqRecordModel(ArtifactID='12',ArtifactActionID=1,MouldCode="SHR2B3-5",Freq=100.0))
assert record_id > 0
def test_upper_to_jbl():
"""测试上料斗到搅拌楼"""
relay = RelayController()
relay.control_upper_to_jbl()
def test_upper_to_zd():
"""测试上料斗到料斗"""
relay = RelayController()
relay.control_upper_to_zd()
def add(a, b):
@ -89,11 +102,21 @@ if __name__ == "__main__":
# 运行所有测试
system.opcua_client_feed.start()
system.start_opc_queue_thread()
test_pd_record_send()
system.opcua_client_feed.start_accept()
# system.start_opc_queue_thread()
# system.start_pd_thread()
# time.sleep(10)
# system.state.pd_status=PD_StatusEnum.PD_Ready
# test_pd_record_send()
# pytest.main([f'{__file__}::test_api_process'])
# pytest.main([f'{__file__}::test_freq_record_send'])
# pytest.main([f'{__file__}::test_upper_to_jbl'])
# relay = RelayController()
# relay.control(relay.UPPER_TO_ZD, 'close')
# relay.control(relay.UPPER_TO_JBL, 'close')
# pytest.main([f'{__file__}::test_upper_to_zd'])
while True:
time.sleep(1)

Binary file not shown.

View File

@ -0,0 +1,131 @@
import os
import cv2
import numpy as np
from collections import deque
class StableClassJudge:
"""
连续三帧稳定判决器:
- class0 / class1 连续 3 帧 -> 输出
- class2 -> 清空计数,重新统计
"""
def __init__(self, stable_frames=3, ignore_class=2):
self.stable_frames = stable_frames
self.ignore_class = ignore_class
self.buffer = deque(maxlen=stable_frames)
def reset(self):
self.buffer.clear()
def update(self, class_id):
if class_id == self.ignore_class:
self.reset()
return None
self.buffer.append(class_id)
if len(self.buffer) < self.stable_frames:
return None
if len(set(self.buffer)) == 1:
stable_class = self.buffer[0]
self.reset()
return stable_class
return None
# ---------------------------
# 三分类映射
# ---------------------------
CLASS_NAMES = {
0: "插好",
1: "未插好",
2: "有遮挡"
}
# ---------------------------
# RKNN 全局实例(只加载一次)
# ---------------------------
_global_rknn = None
def init_rknn_model(model_path):
from rknnlite.api import RKNNLite
global _global_rknn
if _global_rknn is not None:
return _global_rknn
rknn = RKNNLite(verbose=False)
ret = rknn.load_rknn(model_path)
if ret != 0:
raise RuntimeError(f"Load RKNN failed: {ret}")
ret = rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0)
if ret != 0:
raise RuntimeError(f"Init runtime failed: {ret}")
_global_rknn = rknn
print(f"[INFO] RKNN 模型加载成功:{model_path}")
return rknn
# ---------------------------
# 预处理(输入 uint8RKNN 内部转 float32
# ---------------------------
def resize_stretch(image, size=640):
return cv2.resize(image, (size, size))
def preprocess_image_for_rknn(img, size=640):
# 输入必须是 uint8 [0,255],即使模型是 float32
img_resized = resize_stretch(img, size=size)
img_rgb = cv2.cvtColor(img_resized, cv2.COLOR_BGR2RGB)
input_tensor = np.expand_dims(img_rgb, axis=0).astype(np.uint8) # NHWC, uint8
return np.ascontiguousarray(input_tensor)
# ---------------------------
# 单次 RKNN 推理三分类float32 模型)
# ---------------------------
def rknn_classify_preprocessed(input_tensor, model_path):
rknn = init_rknn_model(model_path)
outs = rknn.inference([input_tensor])
# 直接得到 logits
probs = outs[0].flatten().astype(np.float32) # shape: (3,)
class_id = int(np.argmax(probs))
return class_id, probs
# ---------------------------
# 单张图片推理
# ---------------------------
def classify_single_image(frame, model_path, size=640):
if frame is None:
raise FileNotFoundError("输入帧为空")
input_tensor = preprocess_image_for_rknn(frame, size=size)
class_id, probs = rknn_classify_preprocessed(input_tensor, model_path)
class_name = CLASS_NAMES.get(class_id, f"未知类别 ({class_id})")
return {
"class_id": class_id,
"class": class_name,
"score": round(float(probs[class_id]), 4),
"raw": probs.tolist()
}
# ---------------------------
# 示例调用
# ---------------------------
if __name__ == "__main__":
model_path = "charge0324.rknn"
image_path = "class2.jpg"
frame = cv2.imread(image_path)
if frame is None:
raise FileNotFoundError(f"无法读取图片:{image_path}")
result = classify_single_image(frame, model_path)
print("[RESULT]", result)

View File

@ -0,0 +1,88 @@
import os
import cv2
#from rknnlite.api import RKNNLite
import time
# classify_single_image, StableClassJudge, CLASS_NAMES 已在 muju_cls_rknn 中定义
from .charge_cls_rknn import classify_single_image, StableClassJudge, CLASS_NAMES
# 获取当前文件所在目录的绝对路径
current_dir = os.path.dirname(os.path.abspath(__file__))
def run_stable_charge_loop():
"""
image_source: cv2.VideoCapture 对象
"""
_ret=None
# 使用相对于当前文件的绝对路径
model_path = os.path.join(current_dir, "charge0324.rknn")
# roi_file = os.path.join(current_dir, "roi_coordinates", "muju_roi.txt")
RTSP_URL = "rtsp://admin:XJ123456@192.168.250.60:554/streaming/channels/101"
stable_frames=5
print(f"正在连接 RTSP 流: {RTSP_URL}")
cap =None
try:
cap = cv2.VideoCapture(RTSP_URL)
# 降低 RTSP 延迟(部分摄像头支持)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
if not cap.isOpened():
print("无法打开 RTSP 流,请检查网络、账号密码或 URL")
return None
print("RTSP 流连接成功,开始推理...")
judge = StableClassJudge(
stable_frames=stable_frames,
ignore_class=2 # 忽略“有遮挡”类别参与稳定判断
)
if not hasattr(cap, "read"):
raise TypeError("image_source 必须是 cv2.VideoCapture 实例")
_max_count=10
while True:
_max_count=_max_count-1
ret, frame = cap.read()
if not ret:
print("无法读取视频帧(可能是流断开或结束)")
continue
# 上下左右翻转
# frame = cv2.flip(frame, -1)
# ---------------------------
# 单帧推理
# ---------------------------
result = classify_single_image(frame, model_path)
class_id = result["class_id"]
class_name = result["class"]
score = result["score"]
print(f"[FRAME] {class_name} | conf={score:.3f}")
if score>0.8:
# ---------------------------
# 稳定判断
# ---------------------------
stable_class_id = judge.update(class_id)
if stable_class_id is not None:
_ret=CLASS_NAMES[stable_class_id]
if _ret is None:
print("-------当前振捣棒检测为空,继续等待稳定------")
continue
if _ret=="插好":
break
print(f"-------当前振捣棒检测为:{_ret},继续等待稳定------")
else:
print("-------当前振捣棒检测为空,继续等待稳定------")
time.sleep(0.1)
finally:
if cap is not None:
cap.release()
return _ret

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

View File

@ -17,7 +17,8 @@ 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 core.system_state import SystemState,FeedStatus,Upper_Door_Position,PD_StatusEnum
from core.core_utils import CoreUtils
from dataclasses import asdict
import json
import math
@ -301,6 +302,9 @@ class VisualCallback:
def _arch_loop(self):
"""破拱线程"""
while self._is_running:
if self._is_finish:
time.sleep(2)
continue
try:
current_time = time.time()
# 检查下料斗破拱(只有在下料过程中才检查)
@ -314,8 +318,9 @@ class VisualCallback:
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)
print('---------------------第一阶段振动3小于200KG-----------------')
if self._current_angle>25:
self.relay_feed.control_arch_lower_open_sync(3)
self._last_arch_one_weight = _arch_weight
continue
self._last_arch_one_weight = _arch_weight
@ -345,8 +350,9 @@ class VisualCallback:
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)
print('---------------------第三阶段振动3秒(小于100KG-----------------')
if self._current_angle>25:
self.relay_feed.control_arch_lower_open_sync(3)
self._last_arch_three_weight = _arch_weight
continue
self._last_arch_three_weight = _arch_weight
@ -379,7 +385,8 @@ class VisualCallback:
(current_time - self._last_arch_time) >= 2:
self._last_arch_time = current_time
print(f'---------------------第五阶段振动3秒(小于{_min_arch_weight}kg)-----------------')
self.relay_feed.control_arch_lower_open_sync(3)
if self._current_angle>25:
self.relay_feed.control_arch_lower_open_sync(3)
self._last_arch_five_weight = _arch_weight
continue
self._last_arch_five_weight = _arch_weight
@ -475,7 +482,7 @@ class VisualCallback:
def _run_feed(self):
_is_api_request=True
while True:
while self.state.running:
# print("------------已启动----------------")
if self._is_feed_start:
@ -490,15 +497,23 @@ class VisualCallback:
_is_aligned=self._aligned_get_times(1)
if _is_aligned:
_is_api_request=True
if self.state.pd_status==PD_StatusEnum.PD_Ready:
self.state.pd_status=PD_StatusEnum.PD_TimeOut
print('------------启动程序后,进入第一块-------------')
self._is_first_module=False
self._mould_before_aligned=True
if self._cur_mould_model:
self.state._db_mould_status={
self.state.current_mould=self._cur_mould_model
self.state.current_block_number=CoreUtils.get_number_by_mould_code(self._cur_mould_model.MouldCode)
self.state._db_mould_status={
'mould_code':self._cur_mould_model.MouldCode,
'status':2,
'weight':0,
}
else:
self.state.current_mould=None
self.state.current_block_number=''
_current_weight=self.transmitter_controller.read_data(2)
if _current_weight:
@ -508,6 +523,8 @@ class VisualCallback:
return
self.state._feed_status=FeedStatus.FCheckGB
# if not self.state.current_block_number=='F':
# self.state.pd_status=PD_StatusEnum.PD_Ready
# self.is_start_visual=True
self.run_feed_all()
elif self._is_finish and self._is_finish_ratio>=0.7:
@ -519,6 +536,8 @@ class VisualCallback:
if _is_not_aligned:
#标志位
self._mould_before_aligned=False
print('------------连续盖板未对齐完成,进入派单标志-------------')
self.state.pd_status=PD_StatusEnum.PD_Ready
#print('------------连续盖板未对齐-------------')
else:
if _is_api_request:
@ -529,6 +548,19 @@ class VisualCallback:
print('------------进入连续生产-------------')
self._mould_before_aligned=True
_is_api_request=True
if self.state.pd_status==PD_StatusEnum.PD_Ready:
self.state.pd_status=PD_StatusEnum.PD_TimeOut
if self._cur_mould_model:
self.state.current_mould=self._cur_mould_model
self.state.current_block_number=CoreUtils.get_number_by_mould_code(self._cur_mould_model.MouldCode)
self.state._db_mould_status={
'mould_code':self._cur_mould_model.MouldCode,
'status':2,
'weight':0,
}
else:
self.state.current_mould=None
self.state.current_block_number=''
_current_weight=self.transmitter_controller.read_data(2)
if not _current_weight:
@ -545,6 +577,9 @@ class VisualCallback:
self.init_val()
self._init_lower_weight=_current_weight
self.state._feed_status=FeedStatus.FCheckGB
# if not self.state.current_block_number=='F':
#这里需要判断状态是否发送完成,如果没发送完成,应该报警(时间太长),
# self.state.pd_status=PD_StatusEnum.PD_Ready
self.run_feed_all()
# else:
@ -665,9 +700,9 @@ class VisualCallback:
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.state._feed_status=FeedStatus.FFeed5
self.state.vf_status=2
self.is_start_visual=True
self._is_feed_stage=5
while not self._is_finish:
@ -716,11 +751,13 @@ class VisualCallback:
first_finish_weight=0
self._mould_need_weight=1.91*2416
need_total_weight=self._mould_need_weight
if self._is_finish:
return
# 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_start_visual=True
#下料斗的料全部下完
self._is_feed_stage=1
while not self._is_finish:
@ -745,6 +782,8 @@ class VisualCallback:
self.close_lower_door_visual()
break
time.sleep(1)
self.is_start_visual=False
_current_lower_weight=loc_mitter.read_data(2)
if _current_lower_weight is None:
print("-------下料斗重量异常---------")
@ -759,7 +798,8 @@ class VisualCallback:
#print('------------上料斗未就位----------------')
# print('------------上料斗未就位----------------')
time.sleep(1)
if self._is_finish:
return
if self.plc_data in self.plc_valid_data:
print(f'------------上料斗就位(上料斗往下料斗阶段)-------------')
#打开上料斗出砼门开5就开三分之一下
@ -801,6 +841,8 @@ class VisualCallback:
else:
loc_relay.control_upper_close_sync(6+loc_time_count)
self.state._upper_door_closed=0
if self._is_finish:
return
self.is_start_visual=True
initial_lower_weight=loc_mitter.read_data(2)
if initial_lower_weight is None:
@ -828,14 +870,17 @@ class VisualCallback:
break
# print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------')
time.sleep(0.5)
self.is_start_visual=False
_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-------------')
if self._is_finish:
return
self._is_feed_stage=0
if self.plc_data in self.plc_valid_data:
self.state._upper_door_closed=2
@ -1147,7 +1192,7 @@ class VisualCallback:
if self._is_feed_stage==1 or self._is_feed_stage==3:
#根据溢料状态动态调整目标角度
if overflow_detected == "大堆料":
if not self.state.mould_vibrate_status:
if not self.state._mould_vibrate_status:
TARGET_ANGLE = 15.0 # 临时控制变频器堆料时很小
else:
TARGET_ANGLE = 35.0 # 大堆料时控制在15度左右
@ -1379,11 +1424,13 @@ class VisualCallback:
# 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:
if self.state._upper_door_position!=Upper_Door_Position.ZDS:
self.state._upper_door_position=Upper_Door_Position.ZDS
self.relay_controller.control(self.relay_controller.UPPER_TO_ZD, 'close')
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
self.relay_controller.control(self.relay_controller.UPPER_TO_JBL, 'close')
else:
if self.state._upper_door_position!=Upper_Door_Position.Returning:
self.state._upper_door_position=Upper_Door_Position.Returning
@ -1415,14 +1462,20 @@ class VisualCallback:
def get_current_mould(self):
"""获取当前要浇筑的管片"""
# if self.state.pd_status==PD_StatusEnum.PD_Ready:
# self.state.pd_status=PD_StatusEnum.PD_TimeOut
_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
# self.state.current_mould=_cur_poured_model
# self.state.current_block_number=CoreUtils.get_number_by_mould_code(_cur_poured_model.MouldCode)
print(f'当前要浇筑的管片 {json.dumps(asdict(_cur_poured_model), ensure_ascii=False)}')
else:
self._cur_mould_model=None
# self.state.current_mould=None
# self.state.current_block_number=''
print('当前没有未浇筑的管片')
def __del__(self):

View File

@ -741,6 +741,8 @@ class VisualCallback:
self.close_lower_door_visual()
break
time.sleep(1)
self.is_start_visual=False
_current_lower_weight=loc_mitter.read_data(2)
if _current_lower_weight is None:
print("-------下料斗重量异常---------")
@ -1131,7 +1133,7 @@ class VisualCallback:
if self._is_feed_stage==1 or self._is_feed_stage==3:
#根据溢料状态动态调整目标角度
if overflow_detected == "大堆料":
if not self.state.mould_vibrate_status:
if not self.state._mould_vibrate_status:
TARGET_ANGLE = 15.0 # 临时控制变频器堆料时很小
else:
TARGET_ANGLE = 35.0 # 大堆料时控制在15度左右

File diff suppressed because it is too large Load Diff