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