From 8aeaffa885bea6dcf3140807a3397bf54458f59e Mon Sep 17 00:00:00 2001 From: fujinliang Date: Fri, 13 Mar 2026 21:04:19 +0800 Subject: [PATCH] =?UTF-8?q?0313=E7=95=8C=E9=9D=A2=E5=AF=B9=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- busisness/blls.py | 30 ++- busisness/dals.py | 68 +++++- busisness/models.py | 23 +- common/helpers.py | 43 ++++ common/sqlite_handler.py | 17 +- common/util_time.py | 158 ++++++++++++ config/opc_config.ini | 25 +- core/__init__.py | 2 +- core/pd_task_service.py | 396 +++++++++++++++++++++++++++++++ core/relay_feed.py | 133 +++++++++++ core/system.py | 259 +++++++++++++++++--- core/system_state.py | 127 +++++----- db/messages.db | Bin 20480 -> 24576 bytes db/three-record.db | Bin 0 -> 20480 bytes db/three.db | Bin 262144 -> 344064 bytes db/three.db-shm | Bin 32768 -> 32768 bytes db/three.db-wal | Bin 4152 -> 354352 bytes doc/table表设计.doc | Bin 78336 -> 80384 bytes doc/~$控制程序对接.docx | Bin 0 -> 162 bytes doc/控制程序对接.docx | Bin 19607 -> 18999 bytes hardware/inverter.py | 4 +- hardware/relay.py | 23 +- opc/opcua_client_feed.py | 151 +++++++----- opc/opcua_server.py | 83 ++----- pytest.ini | 2 + resources/resources_rc.py | 134 +++++------ tests/pd_system.py | 6 +- tests/test_example.py | 100 ++++++++ tests/weight-app.py | 4 +- vision/muju_cls/muju_cls_rknn.py | 2 +- vision/muju_cls/muju_utils.py | 2 +- vision/visual_callback_dq.py | 166 ++++++------- vision/visual_callback_dq0209.py | 2 +- 33 files changed, 1541 insertions(+), 419 deletions(-) create mode 100644 common/helpers.py create mode 100644 common/util_time.py create mode 100644 core/pd_task_service.py create mode 100644 core/relay_feed.py create mode 100644 db/three-record.db create mode 100644 doc/~$控制程序对接.docx create mode 100644 pytest.ini create mode 100644 tests/test_example.py diff --git a/busisness/blls.py b/busisness/blls.py index 0155fa6..535c28b 100644 --- a/busisness/blls.py +++ b/busisness/blls.py @@ -6,8 +6,8 @@ from dataclasses import fields from typing import List, Optional, Dict, Any from datetime import datetime -from busisness.models import ArtifactInfoModel, PDRecordModel -from busisness.dals import ArtifactDal, PDRecordDal +from busisness.models import ArtifactInfoModel, PDRecordModel,FreqRecordModel +from busisness.dals import ArtifactDal, PDRecordDal,FreqRecordDal class ArtifactBll: @@ -109,9 +109,9 @@ class PDRecordBll: """获取PD官片任务数据""" return self.dal.get_top_pd(5, "ID desc") - def get_last_pds(self,mould_code:str) -> List[PDRecordModel]: + def get_last_pds(self,mould_code:str,top=1) -> List[PDRecordModel]: """获取当前浇筑的后三片的派据(有可能其中一项为空,但需要保证第一条有记录)""" - return self.dal.get_last_pds(1,mould_code) + return self.dal.get_last_pds(top,mould_code) def insert_PD_record(self,model:PDRecordModel) -> bool: """保存PD官片任务(存在更新,不存在插入)""" @@ -127,12 +127,16 @@ class PDRecordBll: def start_pd(self, code: str,volumn:float) -> bool: """开始管片生产""" - return self.dal.update_by_modulecode(code, {'Status': 2,'OptTime':datetime.now(),'FBetonVolume':volumn}) + return self.dal.update_by_modulecode(code, {'Status': 2,'GStatus':1,'OptTime':datetime.now(),'FBetonVolume':volumn}) def update_PD_record(self,artifact_action_id: int, update_fields: dict) -> bool: """更新PD官片任务状态""" return self.dal.update_pd(artifact_action_id, update_fields) + def update_PD_record_byid(self,id: int, update_fields: dict) -> bool: + """更新PD官片任务状态""" + return self.dal.update_pd_byid(id, update_fields) + def save_PD_record(self,model:PDRecordModel) -> int: """保存PD官片任务(存在ModuleCode更新,不存在插入,存在ArtifactID不处理)""" @@ -155,13 +159,27 @@ class PDRecordBll: '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) return 1 - +class FreqRecordBll: + def __init__(self): + """初始化数据访问层,创建数据库连接""" + # 假设数据库文件在db目录下 + self.dal = FreqRecordDal() + pass + + def get_freqs_sync(self) -> List[FreqRecordModel]: + """获取所有未同步的频率记录""" + return self.dal.get_all_sync() + + def insert_freq_record(self,model:FreqRecordModel) -> int: + """插入一条频率记录""" + return self.dal.insert_record(model) if __name__ == "__main__": # artifact_dal = ArtifactBll() diff --git a/busisness/dals.py b/busisness/dals.py index f796f35..f86066b 100644 --- a/busisness/dals.py +++ b/busisness/dals.py @@ -4,7 +4,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from dataclasses import fields from typing import List, Optional, Dict, Any from datetime import datetime, timedelta -from busisness.models import ArtifactInfoModel,PDRecordModel +from busisness.models import ArtifactInfoModel,PDRecordModel,FreqRecordModel from common.sqlite_handler import SQLiteHandler @@ -20,6 +20,7 @@ class BaseDal: """初始化数据访问层,创建数据库连接""" # 假设数据库文件在db目录下 self.db_dao = SQLiteHandler.get_instance("db/three.db", max_readers=50, busy_timeout=4000) + self.db_dao_record = SQLiteHandler.get_instance("db/three-record.db", max_readers=50, busy_timeout=4000) class ArtifactDal(BaseDal): def __init__(self): @@ -202,6 +203,7 @@ class PDRecordDal(BaseDal): pdrecord = PDRecordModel() pdrecord.ID=row["ID"] pdrecord.ArtifactID=row["ArtifactID"] + pdrecord.ArtifactActionID=row["ArtifactActionID"] pdrecord.TaskID=row["TaskID"] pdrecord.ProjectName=row["ProjectName"] pdrecord.ProduceMixID=row["ProduceMixID"] @@ -231,12 +233,12 @@ class PDRecordDal(BaseDal): # 确保top为正整数 if not isinstance(top, int) or top <= 0: raise ValueError("top参数必须是正整数") - _target_time = datetime.now() - timedelta(hours=4) - # 查询指定数量的记录,按ID降序排列 + # _target_time = datetime.now() - timedelta(hours=4) + _target_time ='2026-02-06 00:00:00' + # 查询当前浇筑模具编号的前一条记录 sql = f"""SELECT * FROM PDRecord WHERE CreateTime>? and ID>( -select ID FROM PDRecord WHERE MouldCode=? and CreateTime>? -) -order by ID asc LIMIT ?""" + select ID FROM PDRecord WHERE MouldCode=? and CreateTime>? + ) order by ID asc LIMIT ?""" results = self.db_dao.execute_read(sql, (_target_time,mould_code,_target_time,top)) pdrecords = [] @@ -316,17 +318,67 @@ order by ID asc LIMIT ?""" print(f"更新PD管片任务失败: {e}") return False - def update_by_modulecode(self, module_code: str, update_data: dict) -> int: + def update_pd_byid(self, id: int, update_data: dict) -> bool: + """更新PD官片任务记录""" + try: + # 构建WHERE条件 + where_condition = f"ID={id}" + # 使用update方法更新数据 + affected_rows = self.db_dao.update("PDRecord", update_data, where_condition) + return affected_rows > 0 + except Exception as e: + print(f"更新PD管片任务失败: {e}") + return False + + 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' where_condition = f"MouldCode='{module_code}' and CreateTime>='{_target_time}'" # 使用update方法更新数据 affected_rows = self.db_dao.update("PDRecord", update_data, where_condition) - return affected_rows + return affected_rows > 0 except Exception as e: print(f"更新PD官片任务失败: {e}") + return False + +class FreqRecordDal(BaseDal): + def __init__(self): + super().__init__() + + def get_all_sync(self) -> List[FreqRecordModel]: + """获取未同步的记录""" + try: + # 查询所有记录 + sql = "SELECT * FROM FreqRecord WHERE IsSync=0" + results = self.db_dao.execute_read(sql) + + # 将查询结果转换为FreqRecord对象列表 + artifacts = [] + for row in results: + # 过滤字典,只保留模型中定义的字段 + # filtered_data = filter_dict_for_model(dict(row), ArtifactInfoModel) + artifact = FreqRecordModel(**row) + artifacts.append(artifact) + + return artifacts + except Exception as e: + print(f"获取所有频率记录失败: {e}") + return [] + + + def insert_record(self, model: FreqRecordModel) -> int: + """插入一条频率记录""" + try: + # 使用insert方法插入数据 + model.OptTime=datetime.now() + model.__dict__.pop("ID", None) + row_id = self.db_dao_record.insert("FreqRecord", model.__dict__) + return row_id + except Exception as e: + print(f"插入频率记录失败: {e}") return 0 diff --git a/busisness/models.py b/busisness/models.py index 20edaf7..e412352 100644 --- a/busisness/models.py +++ b/busisness/models.py @@ -276,9 +276,9 @@ class PDRecordModel: BlockNumber: str="" # 派单模式(1自动派单 2手动派单0未知 ) Mode: int=0 - # 派单状态(1计划中2已下发0未知),默认1 + # 派单状态((1计划中2已下发0未知 3已超时 4未扫码)),默认1 Status: int=1 - #搅拌生产状态() + #搅拌生产状态(搅拌生产状态(1未进行生产,2正在生产3生产完毕,4生产中断5已插入搅拌系统6已取消) GStatus: int=0 #数据来源(1 api 2离线) Source: int=1 @@ -289,6 +289,25 @@ class PDRecordModel: #派单时间(下发) OptTime: str="" + +@dataclass +class FreqRecordModel: + """频率记录""" + ID: int=0 + #模具编号 + ArtifactID: str="" + #模具ID + ArtifactActionID: int=0 + #模具编号 + MouldCode: str="" + #骨架编号 + Freq: float=0.0 + #是否已同步 0否 1是 + IsSync: int=0 + #记录时间 + OptTime: str="" + + @dataclass class LEDInfo: """LED信息模型""" diff --git a/common/helpers.py b/common/helpers.py new file mode 100644 index 0000000..6cba2f8 --- /dev/null +++ b/common/helpers.py @@ -0,0 +1,43 @@ +"""工具函数模块""" + + +def get_f_block_positions(artifact_list): + """获取artifact_list中F块的位置""" + positions = [] + for i, artifact in enumerate(artifact_list): + if artifact.get("BlockNumber") == "F": + positions.append(i) + return positions + + +def cleanup_old_timestamps(artifact_timestamps, current_artifact_ids=None, max_age_hours=24): + """ + 清理过期的时间戳记录 + + Args: + artifact_timestamps: 时间戳字典 + current_artifact_ids: 当前活跃的artifact_id集合,可选 + max_age_hours: 时间戳最大保留时间(小时),默认24小时 + """ + from datetime import datetime + + current_time = datetime.now() + expired_ids = [] + + # 遍历所有存储的时间戳记录 + for artifact_id, timestamp in artifact_timestamps.items(): + # 如果提供了当前活跃ID列表,且该ID不在当前活跃列表中,则检查是否过期 + if current_artifact_ids is None or artifact_id not in current_artifact_ids: + age = current_time - timestamp + # 如果超过最大保留时间,则标记为过期 + if age.total_seconds() > max_age_hours * 3600: + expired_ids.append(artifact_id) + + # 删除过期的时间戳记录 + for artifact_id in expired_ids: + del artifact_timestamps[artifact_id] + + if expired_ids: + print(f"清理了 {len(expired_ids)} 个过期的时间戳记录: {expired_ids}") + + return len(expired_ids) diff --git a/common/sqlite_handler.py b/common/sqlite_handler.py index 3c71444..c624c65 100644 --- a/common/sqlite_handler.py +++ b/common/sqlite_handler.py @@ -108,14 +108,16 @@ class SQLiteHandler: """SQLite数据库操作通用类(单例模式)""" _lock = threading.Lock() # 单例锁 - _instance = None + _instances = {} @classmethod - def get_instance(cls, *args, **kwargs): - if cls._instance is None: + def get_instance(cls,db_path, *args, **kwargs): + # 使用文件路径作为键 + key = db_path + if key not in cls._instances: with cls._lock: - if cls._instance is None: # 双重检查 - cls._instance = cls(*args, **kwargs) - return cls._instance + if key not in cls._instances: # 双重检查 + cls._instances[key] = cls(db_path, *args, **kwargs) + return cls._instances[key] def __init__(self, db_path: str = "three.db", max_readers: int = 10, busy_timeout: int = 5000): """ @@ -160,7 +162,7 @@ class SQLiteHandler: try: # 创建临时连接来设置参数 conn = sqlite3.connect(self.db_path, **self._connection_params) - + # 启用WAL模式(Write-Ahead Logging) cursor = conn.execute("PRAGMA journal_mode = WAL") journal_mode = cursor.fetchone()[0] @@ -184,6 +186,7 @@ class SQLiteHandler: def _create_connection(self) -> sqlite3.Connection: """创建新的数据库连接""" conn = sqlite3.connect(self.db_path, **self._connection_params) + conn.set_trace_callback(lambda x: print(x)) conn.row_factory = sqlite3.Row return conn diff --git a/common/util_time.py b/common/util_time.py new file mode 100644 index 0000000..da117dd --- /dev/null +++ b/common/util_time.py @@ -0,0 +1,158 @@ +import time + +class MyTimer: + + @staticmethod + def gMyGetTickCount(): + ts = time.time() + return int(ts * 1000) # Convert to milliseconds + +# CTon class equivalent in Python +class CTon: + def __init__(self): + #已经经过的时间 + self.m_unET = 0 + #上一次的输入状态(布尔值) + self.m_bLastI = False + #当前的输入状态(布尔值) + self.m_bIn = False + #暂停状态(布尔值) + self.m_bPause = False + #完成状态(布尔值) + self.m_bOver = True + #预设时间(延时时间,毫秒) + self.m_unPT = 0 + #开始时间戳(毫秒) + self.m_unStartTime = 0 + #暂停时已经经过的时间(毫秒) + self.m_unPauseET = 0 + + def GetET(self): + if self.m_bIn: + nET = self.m_unPT + (self.m_unStartTime - MyTimer.gMyGetTickCount()) + return max(nET, 0) + else: + return 0 + + def SetReset(self): + self.m_bIn = False + self.m_bLastI = False + self.m_bPause = False + + def SetPause(self, value): + if self.m_bIn: + self.m_bPause = value + if self.m_bPause: + self.m_unPauseET = MyTimer.gMyGetTickCount() - self.m_unStartTime + + def SetOver(self, value): + self.m_bOver = value + + def GetStartTime(self): + return self.m_unStartTime + + """ + CTon.Q() 方法实现了一个 通电延时定时器 ,其工作原理如下: + 1. 启动 :当输入信号 value_i 变为 True 时,记录当前时间作为开始时间 + 2. 计时 :从开始时间开始,累计经过的时间 + 3. 完成 :当经过的时间达到预设时间 value_pt 时,输出变为 True + 4. 重置 :当输入信号变为 False 时,定时器重置,输出变为 False + 5. 暂停/恢复 :支持暂停功能,暂停后恢复时从暂停点继续计时 + """ + def Q(self, value_i, value_pt): + self.m_bIn = value_i + self.m_unPT = value_pt + un_tick = MyTimer.gMyGetTickCount() + + if self.m_bOver and self.m_bIn: + self.m_unStartTime = un_tick - self.m_unPT + self.m_bOver = False + + if self.m_bPause and self.m_bIn: + self.m_unStartTime = un_tick - self.m_unPauseET + + if self.m_bIn != self.m_bLastI: + self.m_bLastI = self.m_bIn + if self.m_bIn: + self.m_unStartTime = un_tick + self.m_bPause = False + + return self.m_bIn and (un_tick >= (self.m_unStartTime + self.m_unPT)) + +# CClockPulse class equivalent in Python +class CClockPulse: + def __init__(self): + self.m_bFirstOut = True + self.m_bTonAOut = False + self.m_bTonBOut = False + self.m_cTonA = CTon() + self.m_cTonB = CTon() + + def Q(self, value_i, run_time, stop_time): + if self.m_bFirstOut: + self.m_bTonAOut = self.m_cTonA.Q(not self.m_bTonBOut and value_i, run_time) + self.m_bTonBOut = self.m_cTonB.Q(self.m_bTonAOut and value_i, stop_time) + return not self.m_bTonAOut and value_i + else: + self.m_bTonAOut = self.m_cTonA.Q(not self.m_bTonBOut and value_i, stop_time) + self.m_bTonBOut = self.m_cTonB.Q(self.m_bTonAOut and value_i, run_time) + return self.m_bTonAOut and value_i + +# CDelayOut class equivalent in Python +class CDelayOut: + def __init__(self): + self.m_cOutTon = CTon() + self.m_cmWaitTon = CTon() + + def Reset(self): + self.m_cOutTon.SetReset() + self.m_cmWaitTon.SetReset() + + def Q(self, value_i, wait_time, out_time): + if self.m_cmWaitTon.Q(value_i, wait_time): + if self.m_cOutTon.Q(True, out_time): + self.m_cOutTon.SetReset() + self.m_cmWaitTon.SetReset() + value_i = False + return False + return True + return False + +# CRisOrFall class equivalent in Python +class CRisOrFall: + def __init__(self): + self.m_bTemp = False + + def Q(self, value_i, ris_or_fall): + result = False + if value_i != self.m_bTemp: + if ris_or_fall and value_i: # Rising edge + result = True + if not ris_or_fall and not value_i: # Falling edge + result = True + self.m_bTemp = value_i + return result + +# CTof class equivalent in Python +class CTof: + def __init__(self): + self.m_cDelayTon = CTon() + self.m_bValue = False + self.m_cRis = CRisOrFall() + + def SetReset(self): + self.m_bValue = False + self.m_cDelayTon.SetReset() + + def Q(self, value_i, delay_time): + if self.m_cRis.Q(value_i, False): + self.m_cDelayTon.SetReset() + self.m_bValue = True + if self.m_cDelayTon.Q(self.m_bValue, delay_time): + self.m_bValue = False + self.m_cDelayTon.SetReset() + return value_i or self.m_bValue + +# Utility function +def gGetNowTime(): + return int(time.time()) diff --git a/config/opc_config.ini b/config/opc_config.ini index 78af530..78584b1 100644 --- a/config/opc_config.ini +++ b/config/opc_config.ini @@ -13,10 +13,25 @@ sub_interval = 500 [OPC_NODE_LIST] upper_weight = 2:upper,2:upper_weight +upper_is_arch = 2:upper,2:upper_is_arch +upper_door_closed = 2:upper,2:upper_door_closed +upper_volume = 2:upper,2:upper_volume +upper_door_position = 2:upper,2:upper_door_position lower_weight = 2:lower,2:lower_weight +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_frequency = 2:mould,2:mould_frequency +mould_vibrate_status = 2:mould,2:mould_vibrate_status +feed_status = 2:mould,2:feed_status +sys_set_mode = 2:sys,2:set_mode +sys_segment_refresh = 2:sys,2:segment_refresh +sys_pd_refresh = 2:sys,2:pd_refresh pd_data = 2:pd,2:pd_data -upper_hopper_position = 2:upper,2:upper_hopper_position -upper_clamp_status = 2:upper,2:upper_clamp_status -vibration_frequency=2:vibration_frequency -production_progress=2:production_progress -segment_tasks=2:segment_tasks \ No newline at end of file +pd_notify = 2:pd,2:pd_notify +pd_set_mode = 2:pd,2:set_mode +pd_set_volume = 2:pd,2:set_volume + + + diff --git a/core/__init__.py b/core/__init__.py index 89583c2..554195c 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -3,6 +3,6 @@ 包含系统核心控制逻辑和状态管理 """ from .system import FeedingControlSystem -from .state import SystemState +from .system_state import SystemState __all__ = ['FeedingControlSystem', 'SystemState'] \ No newline at end of file diff --git a/core/pd_task_service.py b/core/pd_task_service.py new file mode 100644 index 0000000..39e1b36 --- /dev/null +++ b/core/pd_task_service.py @@ -0,0 +1,396 @@ +"""任务处理服务""" +import sys +import os +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) + +from datetime import datetime +from common.helpers import get_f_block_positions +import time +# from busisness.blls import PDRecordBll + + +class PD_TaskService: + def __init__(self): + # self.api_client = APIClient() + self.half_volume = [0, 0] + self.task_before = {"block_number":None, "beton_volume":None, "artifact_id":None} + self.artifact_timestamps = {} + # self.tcp_server = tcp_server + # self.pd_record_bll=PDRecordBll() + # from config.settings import TCP_CLIENT_HOST, TCP_CLIENT_PORT + # self.data_client = OPCClient(url=f'opc.tcp://{TCP_CLIENT_HOST}:{TCP_CLIENT_PORT}') + # self.data_client.start() + + def process_not_pour_info(self,artifact_list): + """处理未浇筑信息""" + #artifact_list[{'ArtifactActionID','BetonTaskID','BetonVolume','BetonTaskID'},{},{}] + # artifact_list = self.api_client.get_not_pour_info() + #mould_code 当前正在浇筑的模具编号 + + #artifact_list = self.pd_record_bll.get_last_pds(mould_code,3) + + # 如果API调用失败,返回空列表而不是抛出异常 + if artifact_list is None: + return [], [], [], self.half_volume + + if not artifact_list: + return [], [], [], self.half_volume + + artifact_list = [person.__dict__ for person in artifact_list] + # 处理F块信息 + f_blocks_info = self._process_f_blocks(artifact_list) + f_blocks = f_blocks_info["f_blocks"] + f_block_count = f_blocks_info["f_block_count"] + total_f_volume = f_blocks_info["total_f_volume"] + f_positions = f_blocks_info["f_positions"] + + # 处理当前任务 + + #task{BetonGrade,MixID} + current_task = self._process_current_task(artifact_list[0]) + + + # 如果获取任务信息失败,则返回空结果 + if current_task is None or not current_task.get("artifact_id"): + return [], [], [], self.half_volume + + # 根据F块情况处理任务 + task_result = self._handle_tasks_by_f_blocks( + f_block_count, f_positions, current_task, + f_blocks, total_f_volume, artifact_list + ) + # 更新上一个任务信息 + self.task_before = { + "beton_task_id": current_task["beton_task_id"], + "beton_volume": round(current_task["beton_volume"],1), + "artifact_id": current_task["artifact_id"], + "block_number": current_task["block_number"] + } + return task_result + + def _process_f_blocks(self, artifact_list): + """处理F块相关信息""" + f_blocks = [artifact for artifact in artifact_list if artifact.get("BlockNumber") == "F"] + f_block_count = len(f_blocks) + total_f_volume = sum(artifact["BetonVolume"] for artifact in f_blocks) + f_positions = get_f_block_positions(artifact_list) + + return { + "f_blocks": f_blocks, + "f_block_count": f_block_count, + "total_f_volume": total_f_volume, + "f_positions": f_positions + } + + def _process_current_task(self, latest_artifact): + """处理当前任务信息""" + #task_data = self.api_client.get_task_info(latest_artifact["BetonTaskID"]) + + # 如果API调用失败,返回None + #if task_data is None: + # return None + #"beton_task_id": latest_artifact["BetonTaskID"], + return { + "beton_task_id": latest_artifact["TaskID"], + "beton_volume": latest_artifact["BetonVolume"], + "artifact_id": latest_artifact["ArtifactActionID"], + "block_number": latest_artifact.get("BlockNumber", ""), + "produce_mix_id": latest_artifact.get("ProduceMixID", ""), + "planned_volume": latest_artifact.get("PlannedVolume", 0), + "id": latest_artifact.get("ID", 0), + "task_data": latest_artifact + } + + def _handle_tasks_by_f_blocks(self, f_block_count, f_positions, current_task, + f_blocks, total_f_volume, artifact_list): + """根据F块数量和位置处理任务""" + # 多个F块情况 + if f_block_count > 2: + return self._handle_multiple_f_blocks(current_task, total_f_volume, artifact_list) + + # 两个F块情况 + elif f_block_count == 2: + return self._handle_two_f_blocks(f_positions, current_task, total_f_volume, artifact_list) + + # 一个F块情况 + elif f_block_count == 1: + return self._handle_single_f_block(f_positions, current_task, f_blocks, + total_f_volume, artifact_list) + + # 无F块情况 + elif f_block_count == 0: + return self._handle_no_f_blocks(current_task, artifact_list) + + else: + print("报警") + return [], [], [], self.half_volume + + def _handle_multiple_f_blocks(self, current_task, total_f_volume, artifact_list): + """处理多个F块的情况""" + if self.task_before.get("block_number") == "F": + print("报警:,超出正常补块逻辑") + else: + adjusted_volume = total_f_volume - self.half_volume[0] + + tasks = [{ + "beton_task_id": current_task["beton_task_id"], + "beton_volume": adjusted_volume, + "artifact_id": current_task["artifact_id"], + "block_number": current_task["block_number"], + "produce_mix_id": current_task.get("produce_mix_id", ""), + "planned_volume": current_task.get("planned_volume", 0), + "id": current_task.get("id", 0) + }] + + send_list = [{ + "beton_task_id": current_task["beton_task_id"], + "beton_volume": adjusted_volume, + "artifact_id": current_task["artifact_id"], + "block_number": current_task["block_number"], + "beton_grade": current_task["task_data"]["BetonGrade"], + "mix_id": current_task["task_data"]["ProduceMixID"], + "time": self.artifact_timestamps.get(current_task["artifact_id"], datetime.now()) + }] + + # 更新时间戳 + #self._update_artifact_timestamps(send_list) + + return tasks, artifact_list, send_list, self.half_volume + + def _handle_two_f_blocks(self, f_positions, current_task, total_f_volume, artifact_list): + """处理两个F块的情况""" + if f_positions == [0, 1] and self.task_before.get("block_number") == "F": + adjusted_volume = 0 + block_number = "补方" + elif f_positions == [1, 2]: + adjusted_volume = artifact_list[0]["BetonVolume"] + block_number = current_task["block_number"] + elif f_positions == [0, 1] and self.task_before.get("block_number") != "F": + adjusted_volume = total_f_volume - self.half_volume[0] + block_number = current_task["block_number"] + else: + print("报警") + + tasks = [{ + "beton_task_id": current_task["beton_task_id"], + "beton_volume": adjusted_volume, + "artifact_id": current_task["artifact_id"], + "block_number": block_number, + "produce_mix_id": current_task.get("produce_mix_id", ""), + "planned_volume": current_task.get("planned_volume", 0), + "id": current_task.get("id", 0) + }] + + send_list = [{ + "beton_task_id": current_task["beton_task_id"], + "beton_volume": adjusted_volume, + "artifact_id": current_task["artifact_id"], + "block_number": block_number, + "beton_grade": current_task["task_data"]["BetonGrade"], + "mix_id": current_task["task_data"]["ProduceMixID"], + "time": self.artifact_timestamps.get(current_task["artifact_id"], datetime.now()) + }] + + # 更新时间戳 + #self._update_artifact_timestamps(send_list) + + return tasks, artifact_list, send_list, self.half_volume + + def _handle_single_f_block(self, f_positions, current_task, f_blocks, total_f_volume, artifact_list): + """处理单个F块的情况""" + if f_positions == [2]: + f_volume = f_blocks[0].get("BetonVolume") if f_blocks else 0 + self.half_volume[0] = 0.1#round(total_f_volume / 2, 2)#暂时修改为0.1 + self.half_volume[1] = 0.1#f_volume - self.half_volume[0]#暂时修改 + adjusted_volume = round(current_task["beton_volume"],1) + self.half_volume[0] + elif f_positions == [1]: + adjusted_volume = round(current_task["beton_volume"],1) + self.half_volume[1] + elif f_positions == [0]: + adjusted_volume = 0 + else: + adjusted_volume = round(current_task["beton_volume"],1) + + block_number = "补方" if f_positions == [0] else current_task["block_number"] + + tasks = [{ + "beton_task_id": current_task["beton_task_id"], + "beton_volume": adjusted_volume, + "artifact_id": current_task["artifact_id"], + "block_number": block_number, + "produce_mix_id": current_task.get("produce_mix_id", ""), + "planned_volume": current_task.get("planned_volume", 0), + "id": current_task.get("id", 0) + }] + + send_list = [{ + "beton_task_id": current_task["beton_task_id"], + "beton_volume": adjusted_volume, + "artifact_id": current_task["artifact_id"], + "block_number": block_number, + "beton_grade": current_task["task_data"]["BetonGrade"], + "mix_id": current_task["task_data"]["ProduceMixID"], + "time": self.artifact_timestamps.get(current_task["artifact_id"], datetime.now()) + }] + # 更新时间戳 + #self._update_artifact_timestamps(send_list) + + return tasks, artifact_list, send_list, self.half_volume + + def _handle_no_f_blocks(self, current_task, artifact_list): + """处理无F块的情况""" + tasks = [{ + "beton_task_id": current_task["beton_task_id"], + "beton_volume": round(current_task["beton_volume"],1), + "artifact_id": current_task["artifact_id"], + "block_number": current_task["block_number"], + "produce_mix_id": current_task.get("produce_mix_id", ""), + "planned_volume": current_task.get("planned_volume", 0), + "id": current_task.get("id", 0), + }] + + send_list = [{ + "beton_task_id": current_task["beton_task_id"], + "beton_volume": round(current_task["beton_volume"],1), + "artifact_id": current_task["artifact_id"], + "block_number": current_task["block_number"], + "beton_grade": current_task["task_data"]["BetonGrade"], + "mix_id": current_task["task_data"]["ProduceMixID"], + "time": self.artifact_timestamps.get(current_task["artifact_id"], datetime.now()) + }] + + # 更新时间戳 + #self._update_artifact_timestamps(send_list) + + return tasks, artifact_list, send_list, self.half_volume + + + def _update_artifact_timestamps(self, send_list): + """更新artifact时间戳""" + current_artifact_ids = {item["artifact_id"] for item in send_list} + for artifact_id in current_artifact_ids: + if artifact_id not in self.artifact_timestamps: + self.artifact_timestamps[artifact_id] = datetime.now() + + def insert_into_produce_table(self, connection, task_info, beton_volume, erp_id, artifact_id, half_volume, status): + """插入数据到Produce表""" + sql_db = SQLServerDB() + if status == 1: + # 准备插入数据 + insert_data = { + "ErpID": erp_id, + "Code": task_info["TaskID"], + "DatTim": datetime.now(), + "Recipe": task_info["ProduceMixID"], + "MorRec": "", + "ProdMete": beton_volume, + "MorMete": 0.0, # 砂浆方量,根据实际需求填写 + "TotVehs": 0, # 累计车次,根据实际需求填写 + "TotMete": task_info["PlannedVolume"], # 累计方量 + "Qualitor": "", # 质检员,根据实际需求填写 + "Acceptor": "", # 现场验收,根据实际需求填写 + "Attamper": "", # 调度员,根据实际需求填写 + "Flag": "1", # 标识,根据实际需求填写 + "Note": "" # 备注,根据实际需求填写 + } + + sql_db.insert_produce_data(insert_data) + print(f"数据已成功插入到Produce表中,ERP ID: {erp_id}") + + try: + # 参数包括: MISID(即erp_id), Flag, TaskID, ProduceMixID, ProjectName, BetonGrade, 调整后的方量 + self.save_to_custom_table( + misid=erp_id, + flag="已插入", # 初始Flag值 + task_id=task_info["TaskID"], + produce_mix_id=task_info["ProduceMixID"], + project_name=task_info["ProjectName"], + beton_grade=task_info["BetonGrade"], + adjusted_volume=round(beton_volume,1), + artifact_id=artifact_id, + half_volume=half_volume, + # 已经调整后的方量 + ) + print(f"任务 {erp_id} 的数据已保存到自定义数据表") + except Exception as e: + print(f"调用保存函数时出错: {e}") + + if self.tcp_server: + try: + time.sleep(2) + task_data = { + "erp_id": erp_id, # 车号,相当于序号 + "task_id": task_info["TaskID"], # 任务单号 + "artifact_id": artifact_id, + "produce_mix_id": task_info["ProduceMixID"], # 配比号 + "project_name": task_info["ProjectName"], # 任务名 + "beton_grade": task_info["BetonGrade"], # 砼强度 + "adjusted_volume": round(beton_volume,1), # 方量 + "flag": "已插入", # 状态 + "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") # 时间 + } + self.tcp_server.send_data(task_data) + print(f"任务 {artifact_id} 的数据已发送给TCP客户端") + except Exception as e: + print(f"发送数据给TCP客户端时出错: {e}") + + return erp_id + else: + try: + # 参数包括: MISID(即erp_id), Flag, TaskID, ProduceMixID, ProjectName, BetonGrade, 调整后的方量 + self.save_to_custom_table( + misid=erp_id, + flag="已插入", # 初始Flag值 + task_id=task_info["TaskID"], + produce_mix_id=task_info["ProduceMixID"], + project_name=task_info["ProjectName"], + beton_grade=task_info["BetonGrade"], + adjusted_volume=round(beton_volume,1), + artifact_id=artifact_id, + half_volume=half_volume, + ) + print(f"任务 {erp_id} 的数据已保存到自定义数据表") + except Exception as e: + print(f"调用保存函数时出错: {e}") + + + return erp_id + + def save_to_custom_table(self, misid, flag, task_id, produce_mix_id, project_name, beton_grade, adjusted_volume, artifact_id,half_volume): + try: + task_data = { + "erp_id": misid, + "task_id": task_id, + "artifact_id": artifact_id, + "produce_mix_id": produce_mix_id, + "project_name": project_name, + "beton_grade": beton_grade, + "adjusted_volume": adjusted_volume, + "flag": flag, + "half_volume":half_volume, + "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + # self.data_client.send_data(task_data) + print(f"任务 {artifact_id} 的数据已发送到另一台电脑") + except Exception as e: + print(f"发送数据到另一台电脑时出错: {e}") + print(f"原计划保存到自定义数据表: MISID={misid}, Flag={flag}, TaskID={task_id}, 调整后方量={adjusted_volume}") + + def update_custom_table_status(self, erp_id, status): + try: + status_data = { + "cmd": "update_status", + "erp_id": erp_id, + "status": status, + "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + # self.data_client.send_data(status_data) + print(f"任务状态更新已发送到另一台电脑: ERP ID={erp_id}, 状态={status}") + except Exception as e: + print(f"发送状态更新到另一台电脑时出错: {e}") + print(f"原计划更新自定义数据表状态: ERP ID={erp_id}, 状态={status}") + +if __name__ == "__main__": + pd_task_service = PD_TaskService() + ret=pd_task_service.process_not_pour_info('SHR2B3-5') + i=100 + \ No newline at end of file diff --git a/core/relay_feed.py b/core/relay_feed.py new file mode 100644 index 0000000..13dcc61 --- /dev/null +++ b/core/relay_feed.py @@ -0,0 +1,133 @@ +# hardware/relay.py +import socket +import binascii +import time +import threading +from pymodbus.exceptions import ModbusException +from config.settings import app_set_config + +#下料过程控制 +class RelayFeedController: + def __init__(self, relay,state): + self.relay = relay + self.state = state + # 添加线程锁,保护对下料斗控制的并发访问 + self.door_control_lock = threading.Lock() + + def control_upper_close_after(self): + """控制上料斗关在几秒后""" + # 关闭上料斗出砼门 + self.relay.control(self.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') + time.sleep(duration) + self.relay.control(self.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') + time.sleep(duration) + self.relay.control(self.DOOR_UPPER_OPEN, 'close') + + + def control_ring_open(self): + """控制下料斗关""" + # 关闭下料斗出砼门 + self.relay.control(self.RING, 'open') + # 异步5秒后关闭 + threading.Thread(target=self._close_ring, daemon=True,name="_close_ring").start() + + + def _close_upper_after_s(self): + """ + 异步5秒后关闭上料斗20秒 + """ + + # time.sleep(5) + self.relay.control_arch_upper_open_sync(5) + self.relay.control(self.DOOR_UPPER_CLOSE, 'open') + time.sleep(1) + self.relay.control(self.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') + time.sleep(20) + self.relay.control(self.DOOR_UPPER_CLOSE, 'close') + print("上料斗关闭完成") + + def _close_lower_5s(self): + time.sleep(6) + self.relay.control(self.DOOR_LOWER_CLOSE, 'close') + + def _close_ring(self): + time.sleep(3) + self.relay.control(self.RING, 'close') + + def control_arch_lower_open(self): + """控制下料斗关""" + # 关闭下料斗出砼门 + self.relay.control(self.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') + # 异步5秒后关闭 + time.sleep(duration) + self.relay.control(self.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') + # 异步5秒后关闭 + time.sleep(duration) + self.relay.control(self.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') + + + def control_arch_upper_open_async(self,delay_seconds: float = 15): + """异步控制上料斗振动 + + Args: + delay_seconds: 延迟关闭时间(秒),默认15秒 + """ + # 关闭下料斗出砼门 + self.relay.control(self.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') + + def control_arch_lower_open_async(self,delay_seconds: float = 15): + """异步控制上料斗振动 + + Args: + delay_seconds: 延迟关闭时间(秒),默认15秒 + """ + # 关闭下料斗出砼门 + self.relay.control(self.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') + diff --git a/core/system.py b/core/system.py index 25e3c22..2331c17 100644 --- a/core/system.py +++ b/core/system.py @@ -4,7 +4,8 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import threading import time import queue -from core.system_state import SystemState,FeedStatus +import json +from core.system_state import SystemState,FeedStatus,Upper_Door_Position from hardware.relay import RelayController from hardware.inverter import InverterController from hardware.transmitter import TransmitterController @@ -12,14 +13,20 @@ from config.ini_manager import ini_manager from hardware.upper_plc import OmronFinsPollingService from vision.visual_callback_dq import VisualCallback from opc.opcua_client_feed import OpcuaClientFeed -from busisness.blls import ArtifactBll,PDRecordBll -from busisness.models import ArtifactInfoModel,PDRecordModel +from busisness.blls import ArtifactBll,PDRecordBll,FreqRecordBll +from busisness.models import ArtifactInfoModel,PDRecordModel,FreqRecordModel +from core.pd_task_service import PD_TaskService class FeedingControlSystem: def __init__(self): print('FeedingControlSystem初始化') self.state = SystemState() + self.pd_task_service = PD_TaskService() + self.pd_record_bll=PDRecordBll() + self.freq_record_bll=FreqRecordBll() + + # 初始化硬件控制器 self.relay_controller = RelayController( @@ -44,7 +51,7 @@ class FeedingControlSystem: #小屏修改过屏幕 self.vf_auto_mode=True # 初始化 OPC UA 客户端 - self.opcua_client_feed = OpcuaClientFeed() + self.opcua_client_feed = OpcuaClientFeed(notify_callback=self.on_opcua_notify) # 线程管理 self.feed_thread = None @@ -55,6 +62,9 @@ class FeedingControlSystem: # 初始化 OPC 队列监听线程,用于处理队列中的数据 self.opc_queue_thread = None + #处理频率上传线程 + self.freq_thread=None + def initialize(self)->bool: """初始化系统""" print("初始化控制系统...") @@ -75,8 +85,12 @@ class FeedingControlSystem: self.start_pd_thread() # 启动OPC队列处理线程,维护连接的断开重连等 self.opcua_client_feed.start() + #开启接收通知数据(queue线程) + self.opcua_client_feed.start_accept() self.start_opc_queue_thread() + #启用频率上传线程 + self.start_freq_thread() print("控制系统初始化完成") return True @@ -95,7 +109,7 @@ class FeedingControlSystem: self.opc_queue_thread = threading.Thread( target=self._process_opc_queue, daemon=True, - name='opc_queue_processor' + name='send_queue_processor' ) self.opc_queue_thread.start() @@ -123,6 +137,9 @@ class FeedingControlSystem: _begin_time=None _wait_times=300 _start_wait_seconds=None + _is_start=False + #处理振捣频率 + while self.state.running: try: # if self.feeding_controller._is_finish_ratio>=0.6: @@ -136,12 +153,13 @@ class FeedingControlSystem: if _start_wait_seconds is None: #记录盖板对齐时间 _start_wait_seconds=time.time() - if self.feeding_controller._is_finish_ratio>=0.02: + if self.state._mould_finish_ratio>=0.05: _elasped_time=time.time()-_start_wait_seconds if _elasped_time<10: time.sleep(10-_elasped_time) self.inverter_controller.control('start',230) - print("----振捣已经启动-----") + _is_start=True + print("----浇筑已经启动-----") _begin_time=time.time() self.state._mould_frequency=230 self.state._mould_vibrate_status=True @@ -152,12 +170,21 @@ class FeedingControlSystem: print("----振捣300秒-----") _wait_time=300 else: - print("----下料重量小于46KG,暂时不振捣-----") - # else: + print("----下料重量小于46KG,暂时不振捣-----") + else: + if self.state._mould_finish_ratio>=0.3: + if self.state._mould_frequency!=220: + self.inverter_controller.set_frequency(220) + self.state._mould_frequency=220 + elif self.state._mould_finish_ratio>=0.4: + if self.state._mould_frequency!=230: + self.inverter_controller.set_frequency(230) + self.state._mould_frequency=230 elif self.state.vf_status==3 and _begin_time is not None: if time.time()-_begin_time>=_wait_time: if self.vf_auto_mode: self.inverter_controller.control('stop') + _is_start=False self.state._mould_vibrate_status=False _begin_time=None _start_wait_seconds=None @@ -201,6 +228,7 @@ class FeedingControlSystem: _ret=artifact_bll.save_artifact_task(_model_data) if _ret > 0: # 标记为已处理 + self.state._sys_segment_refresh=1 processed_artifact_actions.append(item.MouldCode) if len(processed_artifact_actions) > 4: processed_artifact_actions.pop(0) @@ -255,6 +283,7 @@ class FeedingControlSystem: continue _ret=pdrecord_bll.save_PD_record(_pd_record_data) if _ret > 0: + self.state._sys_segment_refresh=1 # 标记为已处理 processed_pd_records.append(item.MouldCode) # 限制最多保存3条记录,删除最旧的 @@ -280,7 +309,7 @@ class FeedingControlSystem: if item: public_name, value = item # 这里可以添加实际的OPC处理逻辑 - print(f"Processing OPC update: {public_name} = {value}") + print(f"控制程序opc数据上传: {public_name} = {value}") self.opcua_client_feed.write_value_by_name(public_name, value) # 标记任务完成 self.state.opc_queue.task_done() @@ -356,22 +385,137 @@ class FeedingControlSystem: _isFinish=False _start_time=None while self.state.running: - #增加生产阶段检测, - if self.state._feed_status==FeedStatus.FCheckGB: - if not _isFinish: - if _start_time is None: - _start_time=time.time() - _isSuccess=self.feeding_controller.send_pd_data() - if _isSuccess: - _isFinish=True - if time.time()-_start_time>60: - print('派单超时,人工介入') - _isFinish=True - elif self.state._feed_status==FeedStatus.FFinished: - _start_time=None - _isFinish=False + #设置的派单模式 + if self.state.pd_set_mode==1: + #增加生产阶段检测, + if self.state._feed_status==FeedStatus.FCheckGB: + if not _isFinish: + if _start_time is None: + _start_time=time.time() + _isSuccess=self.send_pd_data() + if _isSuccess: + _isFinish=True + if time.time()-_start_time>60: + print('派单超时,人工介入') + _isFinish=True + elif self.state._feed_status==FeedStatus.FFinished: + _start_time=None + _isFinish=False time.sleep(5) + def send_pd_data(self): + """ + 发送PD数据到OPC队列 + """ + # 构建PD数据 + _cur_mould=self.state.current_mould + if _cur_mould is not None: + if _cur_mould.MouldCode: + _pdrecords = self.pd_record_bll.get_last_pds(_cur_mould.MouldCode,3) + if _pdrecords: + _pdrecord=_pdrecords[0] + if _pdrecord.TaskID: + if _pdrecord.Status==1: + tasks, artifact_list, send_list, half_volume = self.pd_task_service.process_not_pour_info(_pdrecords) + # if tasks and len(tasks)>0: + # task=tasks[0] + # # task['MouldCode']=_pdrecord.MouldCode + # #更新派单表 + # _ret_flag=self.pd_record_bll.start_pd(_pdrecord.MouldCode,task['beton_volume']) + # if _ret_flag: + # _opc_pd={ + # "ID":task['id'], + # "ArtifactActionID":task['artifact_id'], + # "TaskID":task['beton_task_id'], + # "ProduceMixID":task['produce_mix_id'], + # "BetonVolume":task['beton_volume'], + # "PlannedVolume":task['planned_volume'] + # } + # print(f"{self.state.current_mould.MouldCode}派单:{_pdrecord.MouldCode},数据:{_opc_pd}") + # self.state.opc_queue.put_nowait(('pd_data', json.dumps(_opc_pd))) + if _pdrecord.BlockNumber=='F': + print(f'{_pdrecord.MouldCode} F块,不发送派单数据') + self.pd_record_bll.start_pd(_pdrecord.MouldCode,0) + else: + _fact_volumn=self.get_fact_volumn(_pdrecord.MouldCode,_pdrecord.BlockNumber) + if _fact_volumn>0: + #更新派单表 + _ret_flag=self.pd_record_bll.start_pd(_pdrecord.MouldCode,_fact_volumn) + if _ret_flag: + _opc_pd={ + "ID":_pdrecord.ID, + "ArtifactActionID":_pdrecord.ArtifactActionID, + "TaskID":_pdrecord.TaskID, + "ProduceMixID":_pdrecord.ProduceMixID, + "BetonVolume":_fact_volumn, + "PlannedVolume":_pdrecord.PlannedVolume + } + print(f"{self.state.current_mould.MouldCode}派单:{_pdrecord.MouldCode}-{_pdrecord.BlockNumber},数据:{_opc_pd}") + self.state.opc_queue.put_nowait(('pd_data', json.dumps(_opc_pd))) + + return True + else: + print(f'{_pdrecord.MouldCode} 未派单,当前状态为:{_pdrecord.Status}') + return False + else: + print(f'{_pdrecord.MouldCode} 未获取到数据-(等待扫码)') + return False + else: + print(f'接口数据为空') + return False + else: + return None + + def get_fact_volumn(self,mould_code:str,block_number:str='') -> float: + """获取实际派单发量""" + # _now_volume=0 + _pd_volume=0 + _init_lower_weight=100#self.transmitter_controller.read_data(2) + if _init_lower_weight is None: + print(f'获取重量异常,上料斗传感器错误') + print(f'获取重量异常,上料斗传感器错误') + return None + print(f'get_fact_volumn当前重量:{_init_lower_weight}') + _per_volume=2480 + _left_volume=_init_lower_weight/_per_volume + + if not block_number and '-' in mould_code: + block_number = mould_code.split('-')[0][-2:] + if block_number=='B1': + _left_volume=_left_volume-0.54 + if _left_volume>0: + _pd_volume=1.9-_left_volume + _pd_volume=math.ceil(_pd_volume*10)/10 + else: + _pd_volume=1.9 + elif block_number in ['B2','B3']: + _pd_volume=1.9 + elif block_number=='L1': + #浇B3 + _pd_volume=2 + self.prev_pd_volume=2 + #调整 + elif block_number=='L2': + #2.4方,大约L2和F的量 + _pd_volume=2.4 + # if _weight>1300: + #留0.15 math.floor(_now_volume*10)/10 保留一位小数,丢掉其他的 + if self.prev_pd_volume>0: + _pd_volume=_pd_volume-(_left_volume+self.prev_pd_volume-1.86) + else: + #不知道上一块叫的多少 + _pd_volume=_pd_volume-(_left_volume+2-1.86) + _pd_volume=math.ceil(_pd_volume*10)/10+0.1 + if _pd_volume>2.1: + _pd_volume=2.1 + elif _pd_volume<0.8: + _pd_volume=0.8 + + + + return _pd_volume + + def start_led(self): """启动LED流程""" self.led_thread = threading.Thread( @@ -388,8 +532,8 @@ class FeedingControlSystem: while self.state.running: led_info = app_web_service.get_pouring_led() if led_info: - if self.state.current_artifact.MouldCode==led_info.MouldCode: - led_info.RingTypeCode=self.state.current_artifact.RingTypeCode + if self.state.mould_code==led_info.MouldCode: + led_info.RingTypeCode=led_info.RingTypeCode led_info.UpperWeight=self.state._upper_weight led_info.LowerWeight=self.state._lower_weight led_info.VibrationFrequency=self.state._mould_frequency @@ -398,16 +542,68 @@ class FeedingControlSystem: time.sleep(app_set_config.led_interval) + def on_opcua_notify(self,var_name,val): + """OPC UA通知回调""" + if var_name=='pd_set_mode': + self.state.pd_set_mode=val + elif var_name=='pd_set_volume' and val: + _notify=json.loads(val) + _ret=self.pd_record_bll.update_PD_record_byid(_notify['ID'],{'FBetonVolume':_notify['Volume']}) + if _ret: + #刷新派单表 + self.state.opc_queue.put_nowait(('sys_pd_refresh',1)) + # self.state._db_pd_set_volume=val + elif var_name=='pd_notify' and val: + # self.state._db_pd_notify=val + _notify=json.loads(val) + if _notify['ID']: + if _notify['Flag']==3: + #生产完毕 + _ret=self.pd_record_bll.update_PD_record_byid(_notify['ID'],{'GStatus':3,'Status':5}) + # if _ret: + #发送命令 + # if self.state._upper_door_position==Upper_Door_Position.JBL: + # self.relay_controller.control_upper_to_zd() + else: + self.pd_record_bll.update_PD_record_byid(_notify['ID'],{'GStatus':_notify['Flag']}) + print(f'on_opcua_notify收到,var_name:{var_name},val:{val}') + + def start_freq_thread(self): + """启动频率上传线程""" + self.freq_thread = threading.Thread( + target=self._start_freq, + name="Freq", + daemon=True + ) + self.freq_thread.start() + + def _start_freq(self): + """启动频率上传线程""" + while self.state.running: + # 上传频率 + #未上传到IOT平台,保存到数据库 + _cur_mould=self.state.current_mould + if _cur_mould: + _code=_cur_mould.ArtifactID + _id=_cur_mould.ArtifactActionID + _mcode=_cur_mould.MouldCode + # freq=self.inverter_controller.read_frequency() + freq=self.state._mould_frequency + if freq>0: + self.freq_record_bll.insert_freq_record(FreqRecordModel(ArtifactID=_code,ArtifactActionID=_id,MouldCode=_mcode,Freq=freq)) + else: + print('振捣频率未保存(因为当前浇筑的模具号为空)') + time.sleep(1) @property def _is_finish(self): - """检查系统是否运行""" - return self.feeding_controller._is_finish + """检查系统是否完成""" + return self.state._feed_status==FeedStatus.FFinished @property def _is_finish_ratio(self): - """检查系统是否运行""" - return self.feeding_controller._is_finish_ratio + """完成比例""" + return self.state._mould_finish_ratio @property def vibrate_status(self): @@ -439,6 +635,8 @@ class FeedingControlSystem: self.arch_thread.join() if self.plc_service: self.plc_service.stop_polling() + + self.opcua_client_feed.stop_run() self.feeding_controller.shutdown() # 释放摄像头资源 # self.camera_controller.release() @@ -448,6 +646,7 @@ class FeedingControlSystem: if __name__ == "__main__": system = FeedingControlSystem() system.initialize() + system.opcua_client_feed.subscribe_node(system.state.feed_status) time.sleep(2) system.state._upper_weight=1000 diff --git a/core/system_state.py b/core/system_state.py index 6afc367..70f7ce7 100644 --- a/core/system_state.py +++ b/core/system_state.py @@ -2,7 +2,9 @@ import threading from enum import IntEnum import queue import json +import time from dataclasses import asdict +from typing_extensions import Optional from busisness.blls import ArtifactBll,PDRecordBll class SystemState: @@ -18,47 +20,61 @@ class SystemState: # 系统运行状态 self.running = True - # 上料斗控制相关 - self._upper_door_position = Upper_Door_Position.Default # default(在搅拌楼下接料), over_lower(在下料斗上方), returning(返回中) - # 是否破拱 - self._upper_is_arch_=False - self._upper_door_closed=True + # 上料斗是否振动(True:是,False:否) + self._upper_is_arch=False + #上料斗门关闭状态(0:关闭,1:半开,2全开) + self._upper_door_closed=0 + #上料斗重量 self._upper_weight=0 + #上料斗剩余方量 self._upper_volume=0.0 + # 上料斗控制相关 + self._upper_door_position = Upper_Door_Position.JBL + + #低料斗是否振动(True:是,False:否) + self._lower_is_arch=False + self._lower_weight=0 + + #完成下料的总重量 + self._mould_finish_weight=0 + #时间戳,记录上一次发送到OPC的时间,不用每次都传 + self.opc_timestamp=time.time() + #需要下料的总重量 + # self._mould_need_weight=0 + self._mould_finish_ratio=0 + #当前振捣频率 + self._mould_frequency=230 + #模具是否捣动中(True:是,False:否) + self._mould_vibrate_status=False #True振捣中False未振捣 + + #当前模具状态 + self._feed_status=FeedStatus.FNone + #下料比例变频 self.vf_frequencys=[{'radio':0,'fre':230},{'radio':0.3,'fre':230},{'radio':0.6,'fre':230}] - #使用 - self._mould_frequency=230 - self._mould_vibrate_status=False #True振动中False未振动 + + #记录模具开始振动的时间 self.mould_vibrate_time=0 - #下料斗状态 - self._lower_is_arch_=False - self._lower_weight=0 + #当前浇筑模具,ArtifactInfo对象需要属性:ArtifactID,ArtifactActionID,MouldCode (RFID情况下绑定) + self.current_mould=None - #模具车状态 - self._mould_weight=0 - - #需要下料的总重量 - self._mould_need_weight=0 - #完成下料的总重量 - self._mould_finish_weight=0 - + # 视觉系统状态 self.overflow_detected = "0" # 堆料检测 - #当前生产的管片 - self.current_artifact=None - #当前生产状态 - self._feed_status=FeedStatus.FNone - self.bll_artifact=ArtifactBll() - self.bll_pdrecord=PDRecordBll() - #记录正在生产code模具编号,status:2正生产3完成生成,weight:完成重量 - self._db_status={'code':'','status':1,'weight':0} + #记录生产的模具编号,更新到数据库的管片生成任务状态,status:2正生产3完成生成,weight:完成重量 + self._db_mould_status={'mould_code':'','status':1,'weight':0} #派单数据发送到OPC - self._pd_data='' + # self._pd_data='' + #设置派单模式(1自动派单 2手动派单0未知) + self.pd_set_mode=0 + #管片刷新通知UI + self._sys_segment_refresh=0 + #派单刷新通知UI + self._sys_pd_refresh=0 #变频器相关 #是否启动变频器0未1普通块启动2F块启动 3结束 @@ -74,18 +90,19 @@ class SystemState: if public_name.startswith('db'): self.save_db(public_name,value) #有影响的话改成异步 else: - if public_name=="pd_data": - #更新派单表 - if hasattr(value,'MouldCode'): - self.bll_pdrecord.start_pd(value.MouldCode,value.FBetonVolume) - _opc_pd={ - "ArtifactID":value.ArtifactID, - "TaskID":value.TaskID, - "ProduceMixID":value.ProduceMixID, - "BetonVolume":value.FBetonVolume, - "PlannedVolume":value.PlannedVolume - } - self.opc_queue.put_nowait((public_name, json.dumps(_opc_pd))) + 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() + elif public_name=='mould_finish_ratio': + if time.time()-self.opc_timestamp>=1: + self.opc_queue.put_nowait((public_name, value)) + 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: @@ -100,8 +117,8 @@ class SystemState: def save_db(self,public_name,val): if not val: return - if public_name=="db_status": - _code=val['code'] + if public_name=="db_mould_status": + _code=val['mould_code'] if _code: _status=val['status'] if _status==3: @@ -133,18 +150,18 @@ class FeedStatus(IntEnum): FFinished = 11 class Upper_Door_Position(IntEnum): - # default(在搅拌楼下接料), over_lower(在下料斗上方), returning(返回中) - Default = 0 - Over_Lower = 1 - Returning = 2 + # JBL(在搅拌楼), ZDS(在振捣室), Returning(返回中) + JBL = 1 + ZDS = 2 + Returning = 3 -class Upper_PLC_Status(IntEnum): - # 即将振捣室 - PLC_ZDS_Ready = 4 - #到达振捣室 - PLC_ZDS_Finish = 5 - #即将搅拌楼 - PLC_JBL_Ready = 64 - #到达搅拌楼 - PLC_JBL_Finish = 66 +# class Upper_PLC_Status(IntEnum): +# # 即将振捣室 +# PLC_ZDS_Ready = 4 +# #到达振捣室 +# PLC_ZDS_Finish = 5 +# #即将搅拌楼 +# PLC_JBL_Ready = 64 +# #到达搅拌楼 +# PLC_JBL_Finish = 66 diff --git a/db/messages.db b/db/messages.db index 420f04a6d6d5f2baccab2f4b602faa2bba79b7d8..4f40a7cc241f591bf68fd8c38f63b385c0d2fdb3 100644 GIT binary patch literal 24576 zcmeHPdu$xV8Q(jf@BCatDY&#v*`_TdV8{Eo^JDwM2?osMg>wlBDVW1P#|GPHe3!UD zDOo~DLK>3Nkc5ziyifvdOhQ7FI!+N1tt!NSRi*yXs%pvZIZ~ylNKLCgP`{bo+1*>) zWOqYyRkbJE=lkjAH}n1Go7vgR&Q!E&K^yfdYX7fdYX7fdYX7fdYX7fdYX7fdYX7 z{uNjpj@I6GTWCct^>BYW`&d8R(RF6ikF8D*b~!(m%5YvTjnTuFPOAJ(%N?ZOfC~!bR}AbpHIwUo-0M%soDoHmfne z&T;kioFH?9_|~?+juT~WBsDzPH@Iq2eSO)^q2UZ@l}%gir9|8vZFjaVouB0RI?Q

}}9q zISKz^blur=FZ*@blVx*ae~N97-4*?3^l0><=yj#PDt)}PF7n67 zi;=dHf0X>B1s6M?ZlqQ7?$_ z!wF4tLXw4MAag=Upk$Y7nORbj6A77bvXdDQ;(d_CTVy^_NJb#k;DJo27X@sl)F?}m zPF4V+Ih4$WW_rm+uu)DJW)?uGo|3uH%q%II9Z&$FI!flHWr8demdti)PDg5c;Y&vStn&WbaMEg3wod zkbxfv#y}V9but2>nLfxw80dI%gjtD_5eR+R51C}^0KH`*h7jk6Oq9jAqh%OEH~Ass zWyzr#i~uQtzT|^UXp!TNmJtZe@Jt4N5trjFb~6nKebEmYFUQS+jynLzFoeF~gG_3Y zg#t4LnLz0C-pHCbp;3;*WN49D5W3M1SzMN!WEeu9qhzioM}ag$f?2|xwrvoqrDU!q zN4+EuS9HPbO~Owgbc1Iy5KMGvWC$IvIx0bw0?X zX4|DsAY=qWpYcaVmsBvHQ8K;>UF(Mo3vTwNPKF_Ljb}0#h;Vf((9D2PjUO_YRxPK6 zmH`=t5KGD2jQ~;-Mu4?&!J5H9#}KNfWbQ@)l!VoGqeC+TLRCJ<;&AD-u18NPJD%xZTH148AL%!6ilWoIs2a0dVxhESOgGOy*d**=K>dsv^@g8eo>gNQUq2?*l<)A$(;_7`gQ zKNRy;xfZ^F7Jmmh1^&%vzKyGVX7I0#R z6B=0o4ZDdnY~wcd`Bx|2*{i+qn!54WqQ&!;wrYE}sL$_LUp^v3qNVd1LC!OwY$fk$aq=y@(sAUQWEq9x@QP^aX)aHZ~_C7FU0X9Q7 zps@8+&z1M}4YmyrXNDo6gl`lc;PTs#pBsB2|H`PgZ^Ojs{`}}pZO>T#@SECRMce#h z{>+arjQ>E}xlcRsl1}^K+Gp&h;7mW5O{aj+GHTtWQd~?^bTXWq>x9r!N_MFf7e~p0 z4%hhwl%!;?S|;!$Uj}A7oMS}zZIHq)x!Ss0wd168M}s8PL9KHol}Sc2j|<7-Eed;Y zajc8O$pV~HXKN6F&|)9fL1tOPIQ0W(cSv^I}t+)+gv26;QH%ya}1#-YMmFUUx|Pn452u+&Wlt~(m4Pzgzx`vX4Wt@Z`5?K@3MDSk5(_Q`lxDm zRe9yMii;KdD&+D%m)~3VyRvV^CSuRT64CK!PxQvp4@#Fr{u4PC>5AM?vc9A_d^mhh z=tAf~Xesj^kX`bH6o%U1qKBk25O z=plPaqlXG0^b{rYA{CrEnM}q@E4~+FIzwfNM1KG1#ONXQ?2ptP7hs-k9ZkN87cyS6!wYYbTK~c<>+Dpp~q>j zc<5qW(wN$*bp%q_?_OQ&V2`XB?{eV#1#>pGL1=_p=fdY~Wb|Ph1rS<8$y`WfFKJ!{ z&6dFt@YR&eUCT%u4j;5OXHwCElDQbO5^tfwR^RH>#OQ(ZyVe^IV(hN(90gr;P44g>8i2t7v0ya~;l3n7z? zZ%z*R7aGP4JjSA+`wVC#b47)ozG5^;wQ()9PG)&D2yH;!12N`F?1Y6oxaT))!8@?T z59>H>(^_rMCc{Hdy`?D!)I(deqX*T~r|A+M?|Q?g5={J-Ru+`(_t^s$#S|4OwU0s< z6mOS*dV5D6xpKbo$&#>rQ1eyanAepsS9q2)NXvTRl8^mMeP`XosT114wc6X8_4q&? zXZJa9s~tz-=}K@0S-PjHy7U delta 132 zcmZoTz}T>Wae_2!C<6ln>qG^6!B7UhE+JkZkBRpv1D`nW(anMip}d=q^4c?TG4lTg zN-(hU|K7}J@Rpw+$YwDHsb%E<#K8XvsPX~-!~hOH9!6$a#@y86;>7gS;*d=SECS4m fOphnCJ2-DFoXN!G9WuGmM|biEXU@&i&QEy&3hg3P diff --git a/db/three-record.db b/db/three-record.db new file mode 100644 index 0000000000000000000000000000000000000000..3090224365481238cf46b4bbcfd965268a4602d2 GIT binary patch literal 20480 zcmeI&O>Wab6bJCJ+d$fq8dhBaFJ8539r+_EZ4eS}>lO>gDUP$qsxrwGSxOw@xDsrT z5c_fm&cgb)$c3k*=x>tgdhL`2tWV=5P$## zAaI8SK7U%gzg8>?UyhRCZI>U$eBkkRKkiIhxqZ(veT#f^&#_3J24q?ih0nb1MARiIzFl6;r6icF>1>cMhSn%<8f#Ged33`%M69( z4e5#a;tqY^_^3B2a4gqzmci?XQj%ApijgKmY;| zfB*y_009U<00I#BYk|_z^jJ`ud?++5i8d^o>bB(hCX%AOHafKmY;|fB*y_009U<00RF=KoRro(b8if zZ)$qum8b6+N~NQ#`nIAPie`|mR*iDCvR&GJwxg<=BFr1Ekyftim8!l|()C@fT=@fd Cn~?zk literal 0 HcmV?d00001 diff --git a/db/three.db b/db/three.db index f57ab3ffd9dc914f26064561f43ff571850d1b07..c8256b73650cfbe95197c5a7283b8548d88fd3cb 100644 GIT binary patch delta 8629 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 L-YnYTEV=c6WNK+? delta 406 zcmZo@5N&7>m>?~f$H2hg0L6?zT5Y0^F=O7wg!%l8yqg4A1jIP`c^UZM@?YfN%s-vK zl0SyuhF^xCce7x?Cq6ztUVdg-rT~|q)a3l4l+9B5uL7jln9efruj5(E^^1KsYaoy# z1vC{1*cWaV6j;x+`D~MktT49_11p0S14lgTCZ;#MazLimL`Mldrb7_55VBQYh+SM( zma)sQBrz!`6=n^Lmf76DN||x9|FQzc%|DkEv2K=X*v!Pjz`(=dmie&}8;3H3Fhj(2_6N+0EDRBBiqmx-FzYZ|HET?t_=8DC$;VLF%*epVOxM6j z*T77{(A3J%*viOU&)me+(8#zkwpnGm;(R8h?Ykc^Yq2t0Hfv26%w&<8F7=RwPXw&o z$kNKtM9;+B(!$Kbs#z0ks+^AzhN+fTrsjG^=H?a_M%&|_F)!i-2IhPI$qE930Pk&Q AH2?qr diff --git a/db/three.db-shm b/db/three.db-shm index a6bb6c3f704e068cdd7c28ca51ff8636750c9215..31447ede7bf0b101afc3b4a3332f6d46ef96d239 100644 GIT binary patch literal 32768 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 diff --git a/doc/table表设计.doc b/doc/table表设计.doc index 9690e214588330d6d1172e808e46ff826b59bc2d..c43cd9beaf4a23025c6cc8c698bad4d877473a46 100644 GIT binary patch literal 80384 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 literal 78336 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 E{}C4XEdT%j diff --git a/doc/~$控制程序对接.docx b/doc/~$控制程序对接.docx new file mode 100644 index 0000000000000000000000000000000000000000..db76db17f3a59705ddee1b7fc531605d7480f4e8 GIT binary patch literal 162 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 literal 19607 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 Qz=1 else 4 self.reconnect_interval = self.reconnect_interval if isinstance(self.reconnect_interval, int) and self.reconnect_interval >=1 else 2 @@ -185,26 +209,31 @@ class OpcuaClientFeed(Thread): :param var_readable_name: 变量可读名称(如"upper_weight") :param value: 要写入的值 """ + max_wait = 30 # 最大等待30秒 + wait_count = 0 + while (not self.node_id_mapping and wait_count < max_wait): + time.sleep(0.5) + wait_count += 1 + if wait_count % 2 == 0: # 每2秒打印一次 + print(f"等待OPC连接中...({wait_count/2}秒)") + if not self.connected: - # self.opc_signal.opc_log.emit(f"{var_readable_name}写入失败: OPC服务未连接") + print(f"{var_readable_name}写入失败: OPC服务未连接") return + target_node_id = None for node_id, name in self.node_id_mapping.items(): if name == var_readable_name: target_node_id = node_id break if not target_node_id: - # self.opc_signal.opc_log.emit(f"写入失败:未找到变量名 {var_readable_name} 对应的nodeid") return try: target_node = self.client.get_node(target_node_id) - # variant = ua.Variant(float(value), ua.VariantType.Double) target_node.set_value(value) - # self.opc_signal.opc_log.emit(f"写入成功:{var_readable_name} = {value}") except Exception as e: err_msg = f"opcua写入值失败: {e}" print(err_msg) - # self.opc_signal.opc_log.emit(err_msg) # ===== 心跳检测函数 ===== def _heartbeat_check(self): @@ -215,45 +244,38 @@ class OpcuaClientFeed(Thread): except Exception as e: err_msg = f"心跳检测失败, OPCUA服务已断开 {e}" print(err_msg) - # self.opc_signal.opc_log.emit(err_msg) return False # ===== 掉线重连函数 ===== def _auto_reconnect(self): """掉线后自动重连+重建映射+恢复订阅""" - # self.opc_signal.opc_disconnected.emit("OPC服务掉线, 开始自动重连...") + print("OPC服务掉线, 开始自动重连...") try: self.disconnect() except Exception as e: print(f"_auto_reconnect: 断开旧连接时出现异常: {e}") while self.is_running: - # self.opc_signal.opc_log.emit(f"重试连接OPC服务器: {self.server_url}") + print(f"重试连接OPC服务器: {self.server_url}") if self.connect(): self.build_node_id_mapping() self.create_multi_subscription() - # self.opc_signal.opc_reconnected.emit() - # self.opc_signal.opc_log.emit("OPCUA服务器重连成功, 所有订阅已恢复正常") print("OPCUA服务器重连成功, 所有订阅已恢复正常") break time.sleep(self.reconnect_interval) def _init_connect_with_retry(self): """连接opc服务器""" - # self.opc_signal.opc_log.emit("OPC客户端初始化, 开始连接服务器...") print("OPC客户端初始化, 开始连接服务器...") while self.is_running: if self.connect(): self.build_node_id_mapping() self.create_multi_subscription() break - # self.opc_signal.opc_log.emit(f"连接OPCUA服务器失败, {self.reconnect_interval}秒后重试...") + print(f"连接OPCUA服务器失败, {self.reconnect_interval}秒后重试...") time.sleep(self.reconnect_interval) def run(self) -> None: """opcua客户端线程主函数""" - self.read_opc_config() # 读取配置文件 - self.client = Client(self.server_url) # 初始化opc客户端 - # 连接opc服务器 self._init_connect_with_retry() @@ -269,6 +291,9 @@ class OpcuaClientFeed(Thread): if __name__ == "__main__": opcua_client = OpcuaClientFeed() - opcua_client.run() - opcua_client.write_value_by_name("upper_weight", 100.0) + opcua_client.start() + opcua_client.write_value_by_name("pd_set_mode", 1) + + while True: + time.sleep(1) diff --git a/opc/opcua_server.py b/opc/opcua_server.py index bb3ea46..7f23fbc 100644 --- a/opc/opcua_server.py +++ b/opc/opcua_server.py @@ -61,24 +61,36 @@ class SimpleOPCUAServer: """创建OPC UA变量""" # 创建变量时显式指定数据类型和初始值 #上料斗 + # 上料斗 self.upper_weight = self.upper.add_variable(self.namespace, "upper_weight", ua.Variant(0.0, ua.VariantType.Float)) self.upper_is_arch = self.upper.add_variable(self.namespace, "upper_is_arch", ua.Variant(False, ua.VariantType.Boolean)) - self.upper_door_closed = self.upper.add_variable(self.namespace, "upper_door_closed", ua.Variant(False, ua.VariantType.Boolean)) + self.upper_door_closed = self.upper.add_variable(self.namespace, "upper_door_closed", ua.Variant(False, ua.VariantType.Int16)) self.upper_volume = self.upper.add_variable(self.namespace, "upper_volume", ua.Variant(0.0, ua.VariantType.Float)) self.upper_door_position = self.upper.add_variable(self.namespace, "upper_door_position", ua.Variant(0, ua.VariantType.Int16)) - #下料斗 + # 下料斗 self.lower_weight = self.lower.add_variable(self.namespace, "lower_weight", ua.Variant(0.0, ua.VariantType.Float)) self.lower_is_arch = self.lower.add_variable(self.namespace, "lower_is_arch", ua.Variant(False, ua.VariantType.Boolean)) + self.lower_angle = self.lower.add_variable(self.namespace, "lower_angle", ua.Variant(0.0, ua.VariantType.Float)) - #模具车 + # 模具车 self.mould_finish_weight = self.mould.add_variable(self.namespace, "mould_finish_weight", ua.Variant(0.0, ua.VariantType.Float)) self.mould_need_weight = self.mould.add_variable(self.namespace, "mould_need_weight", ua.Variant(0.0, ua.VariantType.Float)) + self.mould_finish_ratio = self.mould.add_variable(self.namespace, "mould_finish_ratio", ua.Variant(0.0, ua.VariantType.Float)) + self.mould_frequency = self.mould.add_variable(self.namespace, "mould_frequency", ua.Variant(230, ua.VariantType.Int32)) self.mould_vibrate_status = self.mould.add_variable(self.namespace, "mould_vibrate_status", ua.Variant(False, ua.VariantType.Boolean)) self.feed_status = self.mould.add_variable(self.namespace, "feed_status", ua.Variant(0, ua.VariantType.Int16)) self.pd_data=self.pd.add_variable(self.namespace, "pd_data", ua.Variant("", ua.VariantType.String)) + self.pd_notify=self.pd.add_variable(self.namespace, "pd_notify", ua.Variant("", ua.VariantType.String)) + self.pd_set_mode=self.pd.add_variable(self.namespace, "set_mode", ua.Variant(0, ua.VariantType.Int16)) + self.pd_set_volume=self.pd.add_variable(self.namespace, "set_volume", ua.Variant("", ua.VariantType.String)) + # 系统对象 + self.sys_set_mode=self.sys.add_variable(self.namespace, "set_mode", ua.Variant(0, ua.VariantType.Int16)) + self.sys_segment_refresh=self.sys.add_variable(self.namespace, "segment_refresh", ua.Variant(0, ua.VariantType.Int16)) + self.sys_pd_refresh=self.sys.add_variable(self.namespace, "pd_refresh", ua.Variant(0, ua.VariantType.Int16)) + # 在创建变量后立即设置可写权限(不需要等待服务器启动) self.upper_weight.set_writable(True) self.lower_weight.set_writable(True) @@ -93,64 +105,19 @@ class SimpleOPCUAServer: self.mould_vibrate_status.set_writable(True) self.feed_status.set_writable(True) self.pd_data.set_writable(True) + self.pd_notify.set_writable(True) + self.pd_set_mode.set_writable(True) + self.pd_set_volume.set_writable(True) + self.sys_set_mode.set_writable(True) + self.mould_finish_ratio.set_writable(True) + self.sys_segment_refresh.set_writable(True) + self.sys_pd_refresh.set_writable(True) + self.lower_angle.set_writable(True) - print("[变量创建] 变量创建完成,AccessLevel权限已设置") - # 验证并打印当前的AccessLevel属性 - # try: - # al = self.upper_weight.get_attribute(ua.AttributeIds.AccessLevel) - # ual = self.upper_weight.get_attribute(ua.AttributeIds.UserAccessLevel) - # print(f"[变量创建] upper_weight AccessLevel: {al.Value.Value}, UserAccessLevel: {ual.Value.Value}") - - # al2 = self.lower_weight.get_attribute(ua.AttributeIds.AccessLevel) - # ual2 = self.lower_weight.get_attribute(ua.AttributeIds.UserAccessLevel) - # print(f"[变量创建] lower_weight AccessLevel: {al2.Value.Value}, UserAccessLevel: {ual2.Value.Value}") - - # except Exception as e: - # print(f"[变量创建] 获取权限属性失败: {e}") - - def setup_variable_permissions(self): - """设置变量权限 - 在服务器启动后调用""" - try: - # 重新设置变量为可写,确保权限生效 - self.upper_weight.set_writable(True) - self.lower_weight.set_writable(True) - print("[权限设置] 变量权限已重新设置") - - # 验证权限 - try: - al = self.upper_weight.get_attribute(ua.AttributeIds.AccessLevel) - ual = self.upper_weight.get_attribute(ua.AttributeIds.UserAccessLevel) - print(f"[权限设置] upper_weight AccessLevel: {al.Value.Value}, UserAccessLevel: {ual.Value.Value}") - except Exception as e: - print(f"[权限设置] 验证失败: {e}") - - except Exception as e: - print(f"[权限设置] 设置权限失败: {e}") - print("[权限设置] 尝试强制设置...") - - - # def setup_state_listeners(self): - # """设置状态监听器 - 事件驱动更新""" - # if hasattr(self.state, 'state_updated'): - # self.state.state_updated.connect(self.on_state_changed) - # print("状态监听器已设置 - 事件驱动模式") - - # def on_state_changed(self, property_name, value): - # """状态变化时的回调函数""" - # try: - # # 根据属性名更新对应的OPC UA变量 - # if property_name == "upper_weight": - # self.upper_weight.set_value(value) - # elif property_name == "lower_weight": - # self.lower_weight.set_value(value) - - # # 可以在这里添加更多状态映射 - # print(f"状态更新: {property_name} = {value}") - - # except Exception as e: - # print(f"状态更新错误: {e}") + print("[变量创建] 变量创建完成") + def start(self): """启动服务器""" try: diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..bc71038 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +addopts = -s -v \ No newline at end of file diff --git a/resources/resources_rc.py b/resources/resources_rc.py index a07b775..e436a82 100644 --- a/resources/resources_rc.py +++ b/resources/resources_rc.py @@ -14370,139 +14370,139 @@ qt_resource_struct = b"\ \x00\x00\x00\x10\x00\x02\x00\x00\x00C\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x05\xb8\x00\x00\x00\x00\x00\x01\x00\x02\xdd\x16\ -\x00\x00\x01\x9a\x80)\xd9\x84\ +\x00\x00\x01\x9c\x94G\xc6\x9e\ \x00\x00\x04\x02\x00\x00\x00\x00\x00\x01\x00\x01\xfb\xa6\ -\x00\x00\x01\x9a\x80)\xd3W\ +\x00\x00\x01\x9c\x94G\xc6\x9f\ \x00\x00\x05(\x00\x00\x00\x00\x00\x01\x00\x02\xbc.\ -\x00\x00\x01\x9a\x80)\xd8a\ +\x00\x00\x01\x9c\x94G\xc6\xa6\ \x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x00+8\ -\x00\x00\x01\x9a\x80)\xdc9\ +\x00\x00\x01\x9c\x94G\xc6\x9f\ \x00\x00\x00T\x00\x00\x00\x00\x00\x01\x00\x00\x11r\ -\x00\x00\x01\x9a\x80)\xd8\xb7\ +\x00\x00\x01\x9c\x94G\xc6\xa0\ \x00\x00\x05\xd0\x00\x00\x00\x00\x00\x01\x00\x02\xddk\ -\x00\x00\x01\x9a\x80)\xda\x98\ +\x00\x00\x01\x9c\x94G\xc6\xa1\ \x00\x00\x02\xa6\x00\x00\x00\x00\x00\x01\x00\x01\x08\xf0\ -\x00\x00\x01\x9a\x80)\xd9b\ +\x00\x00\x01\x9c\x94G\xc6\xa1\ \x00\x00\x00:\x00\x00\x00\x00\x00\x01\x00\x00\x01\x05\ -\x00\x00\x01\x9a\x80)\xd6\x9c\ +\x00\x00\x01\x9c\x94G\xc6\xa0\ \x00\x00\x02\xd8\x00\x00\x00\x00\x00\x01\x00\x01\x1d/\ -\x00\x00\x01\x9a\x80)\xdc\x8a\ +\x00\x00\x01\x9c\x94G\xc6\xa0\ \x00\x00\x01\xda\x00\x00\x00\x00\x00\x01\x00\x00\x84\x83\ -\x00\x00\x01\x9a\x80)\xdd\x04\ +\x00\x00\x01\x9c\x94G\xc6\xa6\ \x00\x00\x03\x8e\x00\x00\x00\x00\x00\x01\x00\x01N/\ -\x00\x00\x01\x9a\x80)\xd8\xfc\ +\x00\x00\x01\x9c\x94G\xc6\x9f\ \x00\x00\x04\x84\x00\x00\x00\x00\x00\x01\x00\x02\x92\xa4\ -\x00\x00\x01\x9a\x80)\xd9\xff\ +\x00\x00\x01\x9c\x94G\xc6\x9f\ \x00\x00\x01$\x00\x00\x00\x00\x00\x01\x00\x00-\x82\ -\x00\x00\x01\x9a\x80)\xdaB\ +\x00\x00\x01\x9c\x94G\xc6\x9a\ \x00\x00\x02x\x00\x00\x00\x00\x00\x01\x00\x00\xf5\xe3\ -\x00\x00\x01\x9a\x80)\xda]\ +\x00\x00\x01\x9c\x94G\xc6\xa8\ \x00\x00\x01L\x00\x00\x00\x00\x00\x01\x00\x00c\xfb\ -\x00\x00\x01\x9a\x80)\xdb\xf3\ +\x00\x00\x01\x9c\x94G\xc6\xa3\ \x00\x00\x02\x92\x00\x00\x00\x00\x00\x01\x00\x01\x02\xaa\ -\x00\x00\x01\x9a\x80)\xd4\x9b\ +\x00\x00\x01\x9c\x94G\xc6\xa3\ \x00\x00\x03\x02\x00\x00\x00\x00\x00\x01\x00\x01%\xbb\ -\x00\x00\x01\x9a\x80)\xda\xf5\ +\x00\x00\x01\x9c\x94G\xc6\xaa\ \x00\x00\x05p\x00\x00\x00\x00\x00\x01\x00\x02\xc7\x9f\ -\x00\x00\x01\x9a\x80)\xd3\xfd\ +\x00\x00\x01\x9c\x94G\xc6\xa4\ \x00\x00\x03\xae\x00\x00\x00\x00\x00\x01\x00\x01Q\xcb\ -\x00\x00\x01\x9a\x80)\xd7\x04\ +\x00\x00\x01\x9c\x94G\xc6\xab\ \x00\x00\x00\x22\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ -\x00\x00\x01\x9a\x80)\xd8A\ +\x00\x00\x01\x9c\x94G\xc6\xa4\ \x00\x00\x04\xc0\x00\x00\x00\x00\x00\x01\x00\x02\x96\x82\ -\x00\x00\x01\x9a\x80)\xddw\ +\x00\x00\x01\x9c\x94G\xc6\xa1\ \x00\x00\x00\x88\x00\x00\x00\x00\x00\x01\x00\x00 \x01\ -\x00\x00\x01\x9a\x80)\xd67\ +\x00\x00\x01\x9c\x94G\xc6\xa6\ \x00\x00\x06\x9a\x00\x00\x00\x00\x00\x01\x00\x03Yo\ -\x00\x00\x01\x9a\x80)\xdbu\ +\x00\x00\x01\x9c\x94G\xc6\xa4\ \x00\x00\x04\xa4\x00\x00\x00\x00\x00\x01\x00\x02\x94\xf7\ -\x00\x00\x01\x9a\x80)\xd9\x1e\ +\x00\x00\x01\x9c\x94G\xc6\xa0\ \x00\x00\x03^\x00\x00\x00\x00\x00\x01\x00\x010\x95\ -\x00\x00\x01\x9a\x80)\xda|\ +\x00\x00\x01\x9c\x94G\xc6\xa7\ \x00\x00\x00\xe8\x00\x00\x00\x00\x00\x01\x00\x00((\ -\x00\x00\x01\x9a\x80)\xd4Y\ +\x00\x00\x01\x9c\x94G\xc6\xa1\ \x00\x00\x06l\x00\x00\x00\x00\x00\x01\x00\x036\xa7\ -\x00\x00\x01\x9a\x80)\xdcp\ +\x00\x00\x01\x9c\x94G\xc6\xa6\ \x00\x00\x02\x18\x00\x00\x00\x00\x00\x01\x00\x00\xe1c\ -\x00\x00\x01\x9a\x80)\xdd;\ +\x00\x00\x01\x9c\x94G\xc6\x9b\ \x00\x00\x02`\x00\x00\x00\x00\x00\x01\x00\x00\xf4\xe4\ -\x00\x00\x01\x9a\x80)\xd4\xba\ +\x00\x00\x01\x9c\x94G\xc6\xa4\ \x00\x00\x03\xc4\x00\x00\x00\x00\x00\x01\x00\x01\xa8}\ -\x00\x00\x01\x9a\x80)\xd5\x9f\ +\x00\x00\x01\x9c\x94G\xc6\xa5\ \x00\x00\x03>\x00\x00\x00\x00\x00\x01\x00\x01/\xe2\ -\x00\x00\x01\x9a\x80)\xd9\xa3\ +\x00\x00\x01\x9c\x94G\xc6\x9b\ \x00\x00\x01\xf8\x00\x00\x00\x00\x00\x01\x00\x00\xe0\xd7\ -\x00\x00\x01\x9a\x80)\xdd\x8f\ +\x00\x00\x01\x9c\x94G\xc6\x9d\ \x00\x00\x01\x80\x00\x00\x00\x00\x00\x01\x00\x00z\x9d\ -\x00\x00\x01\x9a\x80)\xdb\xb6\ +\x00\x00\x01\x9c\x94G\xc6\x9d\ \x00\x00\x05\xea\x00\x00\x00\x00\x00\x01\x00\x02\xeb\xec\ -\x00\x00\x01\x9a\x80)\xd3\x96\ +\x00\x00\x01\x9c\x94G\xc6\x9d\ \x00\x00\x06T\x00\x00\x00\x00\x00\x01\x00\x032L\ -\x00\x00\x01\x9a\x80)\xd5|\ +\x00\x00\x01\x9c\x94G\xc6\xaa\ \x00\x00\x06\x86\x00\x00\x00\x00\x00\x01\x00\x03?\x19\ -\x00\x00\x01\x9a\x80)\xd4\xfa\ +\x00\x00\x01\x9c\x94G\xc6\xa2\ \x00\x00\x04h\x00\x00\x00\x00\x00\x01\x00\x02\x8a\xb0\ -\x00\x00\x01\x9a\x80)\xd7t\ +\x00\x00\x01\x9c\x94G\xc6\xa8\ \x00\x00\x05>\x00\x00\x00\x00\x00\x01\x00\x02\xbc\xd1\ -\x00\x00\x01\x9a\x80)\xda#\ +\x00\x00\x01\x9c\x94G\xc6\xa8\ \x00\x00\x05\x0c\x00\x00\x00\x00\x00\x01\x00\x02\xb9\x8e\ -\x00\x00\x01\x9a\x80)\xd4;\ +\x00\x00\x01\x9c\x94G\xc6\xa0\ \x00\x00\x00\xcc\x00\x00\x00\x00\x00\x01\x00\x00#\xb3\ -\x00\x00\x01\x9a\x80)\xdc\x17\ +\x00\x00\x01\x9c\x94G\xc6\xa0\ \x00\x00\x00\x9e\x00\x00\x00\x00\x00\x01\x00\x00!\x14\ -\x00\x00\x01\x9a\x80)\xd6z\ +\x00\x00\x01\x9c\x94G\xc6\x9d\ \x00\x00\x06\xb2\x00\x00\x00\x00\x00\x01\x00\x03_2\ -\x00\x00\x01\x9a\x80)\xda\xda\ +\x00\x00\x01\x9c\x94G\xc6\xa3\ \x00\x00\x01`\x00\x00\x00\x00\x00\x01\x00\x00rC\ -\x00\x00\x01\x9a\x80)\xd9?\ +\x00\x00\x01\x9c\x94G\xc6\xa8\ \x00\x00\x064\x00\x00\x00\x00\x00\x01\x00\x03)\xfc\ -\x00\x00\x01\x9a\x80)\xdb\x19\ +\x00\x00\x01\x9c\x94G\xc6\xa8\ \x00\x00\x05\xa6\x00\x00\x00\x00\x00\x01\x00\x02\xd7\xf5\ -\x00\x00\x01\x9a\x80)\xddX\ +\x00\x00\x01\x9c\x94G\xc6\xa5\ \x00\x00\x01\xa0\x00\x00\x00\x00\x00\x01\x00\x00{\x07\ -\x00\x00\x01\x9a\x80)\xd5\xc2\ +\x00\x00\x01\x9c\x94G\xc6\xa7\ \x00\x00\x03\x1e\x00\x00\x00\x00\x00\x01\x00\x01&\xcb\ -\x00\x00\x01\x9a\x80)\xdb\xd3\ +\x00\x00\x01\x9c\x94G\xc6\xa7\ \x00\x00\x04\xdc\x00\x00\x00\x00\x00\x01\x00\x02\x98\x0d\ -\x00\x00\x01\x9a\x80)\xdcT\ +\x00\x00\x01\x9c\x94G\xc6\xa5\ \x00\x00\x06\x0a\x00\x00\x00\x00\x00\x01\x00\x02\xecg\ -\x00\x00\x01\x9a\x80)\xd8\x9b\ +\x00\x00\x01\x9c\x94G\xc6\xa1\ \x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x84.\ -\x00\x00\x01\x9a\x80)\xdbU\ +\x00\x00\x01\x9c\x94G\xc6\x9b\ \x00\x00\x04\xf2\x00\x00\x00\x00\x00\x01\x00\x02\x9c\x95\ -\x00\x00\x01\x9a\x80)\xd7O\ +\x00\x00\x01\x9c\x94G\xc6\x9e\ \x00\x00\x04L\x00\x00\x00\x00\x00\x01\x00\x02h\xd9\ -\x00\x00\x01\x9a\x80)\xd7*\ +\x00\x00\x01\x9c\x94G\xc6\x9b\ \x00\x00\x03\xd6\x00\x00\x00\x00\x00\x01\x00\x01\xaa\x0a\ -\x00\x00\x01\x9a\x80)\xd4\x1c\ +\x00\x00\x01\x9c\x94G\xc6\xa8\ \x00\x00\x00n\x00\x00\x00\x00\x00\x01\x00\x00\x1d\x5c\ -\x00\x00\x01\x9a\x80)\xd3\xdc\ +\x00\x00\x01\x9c\x94G\xc6\xaa\ \x00\x00\x05\x88\x00\x00\x00\x00\x00\x01\x00\x02\xc8\xac\ -\x00\x00\x01\x9a\x80)\xd6\xdf\ +\x00\x00\x01\x9c\x94G\xc6\xa6\ \x00\x00\x02\xea\x00\x00\x00\x00\x00\x01\x00\x01\x22\x08\ -\x00\x00\x01\x9a\x80)\xd8\x03\ +\x00\x00\x01\x9c\x94G\xc6\x9f\ \x00\x00\x06 \x00\x00\x00\x00\x00\x01\x00\x03&\x04\ -\x00\x00\x01\x9a\x80)\xd6\x06\ +\x00\x00\x01\x9c\x94G\xc6\x9e\ \x00\x00\x020\x00\x00\x00\x00\x00\x01\x00\x00\xea\x99\ -\x00\x00\x01\x9a\x80)\xd5\x1a\ +\x00\x00\x01\x9c\x94G\xc6\xa4\ \x00\x00\x05Z\x00\x00\x00\x00\x00\x01\x00\x02\xc5\x8e\ -\x00\x00\x01\x9a\x80)\xd6\xbe\ +\x00\x00\x01\x9c\x94G\xc6\xa7\ \x00\x00\x04\x22\x00\x00\x00\x00\x00\x01\x00\x01\xffL\ -\x00\x00\x01\x9a\x80)\xda\xbe\ +\x00\x00\x01\x9c\x94G\xc6\xa2\ \x00\x00\x01:\x00\x00\x00\x00\x00\x01\x00\x00.\xdc\ -\x00\x00\x01\x9a\x80)\xdd\x22\ +\x00\x00\x01\x9c\x94G\xc6\xa2\ \x00\x00\x03\xf0\x00\x00\x00\x00\x00\x01\x00\x01\xabj\ -\x00\x00\x01\x9a\x80)\xd5\xe3\ +\x00\x00\x01\x9c\x94G\xc6\xa2\ \x00\x00\x03v\x00\x00\x00\x00\x00\x01\x00\x01I\x0f\ -\x00\x00\x01\x9a\x80)\xd9\xc9\ +\x00\x00\x01\x9c\x94G\xc6\xa3\ \x00\x00\x02H\x00\x00\x00\x00\x00\x01\x00\x00\xf4\x8f\ -\x00\x00\x01\x9a\x80)\xd8\xda\ +\x00\x00\x01\x9c\x94G\xc6\x9e\ \x00\x00\x02\xc0\x00\x00\x00\x00\x00\x01\x00\x01\x1c\xda\ -\x00\x00\x01\x9a\x80)\xd4x\ +\x00\x00\x01\x9c\x94G\xc6\x9d\ \x00\x00\x044\x00\x00\x00\x00\x00\x01\x00\x02h\x84\ -\x00\x00\x01\x9a\x80)\xd7\x99\ +\x00\x00\x01\x9c\x94G\xc6\x9e\ \x00\x00\x00\xb4\x00\x00\x00\x00\x00\x01\x00\x00#^\ -\x00\x00\x01\x9a\x80)\xd57\ +\x00\x00\x01\x9c\x94G\xc6\x9e\ " def qInitResources(): diff --git a/tests/pd_system.py b/tests/pd_system.py index 0c5d53d..3f88157 100644 --- a/tests/pd_system.py +++ b/tests/pd_system.py @@ -18,8 +18,8 @@ from busisness.models import ArtifactInfoModel,PDRecordModel class FeedingControlSystem: def __init__(self): - print('FeedingControlSystem初始化') - self.pd_record_bll=PDRecordBll() + print('派单测试初始化') + # self.pd_record_bll=PDRecordBll() def send_pd_data(self): @@ -56,7 +56,7 @@ class FeedingControlSystem: print(f'接口数据异常') return False - def get_fact_volumn(self,mould_code:str,block_number:str='',_weight:float=0) -> float: + def get_fact_volumn(self,block_number:str='') -> float: """获取实际派单发量""" _now_volumn=0 _pd_volumn=0 diff --git a/tests/test_example.py b/tests/test_example.py new file mode 100644 index 0000000..c5b98e6 --- /dev/null +++ b/tests/test_example.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +""" +pytest 测试示例 +""" +import sys +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 import FeedingControlSystem +from busisness.models import ArtifactInfo,FreqRecordModel +from busisness.blls import FreqRecordBll + +system=FeedingControlSystem() +system.state.current_mould=ArtifactInfo(MouldCode="SHR2B3-5") + +def test_pd_record_send(): + """测试派单记录发送""" + system.send_pd_data() + +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 add(a, b): + """简单的加法函数""" + return a + b + + +def test_add(): + """测试加法函数""" + assert add(1, 2) == 4 + assert add(0, 0) == 0 + assert add(-1, 1) == 0 + assert add(100, 200) == 300 + + +def multiply(a, b): + """简单的乘法函数""" + return a * b + + +def test_multiply(): + """测试乘法函数""" + assert multiply(2, 3) == 6 + assert multiply(0, 5) == 0 + assert multiply(-2, 3) == -6 + assert multiply(10, 10) == 100 + + +class Calculator: + """简单的计算器类""" + + def add(self, a, b): + return a + b + + def subtract(self, a, b): + return a - b + + +def test_calculator(): + """测试计算器类""" + calc = Calculator() + assert calc.add(1, 2) == 3 + assert calc.subtract(5, 2) == 3 + assert calc.subtract(2, 5) == -3 + + +@pytest.mark.parametrize("a, b, expected", [ + (1, 2, 3), + (0, 0, 0), + (-1, 1, 0), + (100, 200, 300) +]) +def test_add_parametrized(a, b, expected): + """参数化测试加法函数""" + assert add(a, b) == expected + + +if __name__ == "__main__": + # 运行所有测试 + + system.opcua_client_feed.start() + system.start_opc_queue_thread() + + test_pd_record_send() + # pytest.main([f'{__file__}::test_api_process']) + # pytest.main([f'{__file__}::test_freq_record_send']) + while True: + time.sleep(1) + + \ No newline at end of file diff --git a/tests/weight-app.py b/tests/weight-app.py index 8e03229..d0a815a 100644 --- a/tests/weight-app.py +++ b/tests/weight-app.py @@ -1,8 +1,8 @@ # 读取原始数据文件 import datetime -input_file = r"C:\Users\fujin\Desktop\fsdownload\weight.txt" # 原始数据文件路径 -output_file = r"C:\Users\fujin\Desktop\fsdownload\filtered_B_records.txt" # 输出结果文件路径 (使用原始字符串避免Unicode转义错误) +input_file = r"C:\Users\xj-robot\Desktop\weight.txt" # 原始数据文件路径 +output_file = r"C:\Users\xj-robot\Desktop\filtered_B_records.txt" # 输出结果文件路径 (使用原始字符串避免Unicode转义错误) # 存储包含"B"标记的记录 filtered_records = [] diff --git a/vision/muju_cls/muju_cls_rknn.py b/vision/muju_cls/muju_cls_rknn.py index d4b4a51..65eff79 100644 --- a/vision/muju_cls/muju_cls_rknn.py +++ b/vision/muju_cls/muju_cls_rknn.py @@ -1,7 +1,6 @@ import os import cv2 import numpy as np -from rknnlite.api import RKNNLite from collections import deque @@ -63,6 +62,7 @@ _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 diff --git a/vision/muju_cls/muju_utils.py b/vision/muju_cls/muju_utils.py index 61b7d7f..c2f00b3 100644 --- a/vision/muju_cls/muju_utils.py +++ b/vision/muju_cls/muju_utils.py @@ -1,6 +1,6 @@ import os import cv2 -from rknnlite.api import RKNNLite +#from rknnlite.api import RKNNLite import time # classify_single_image, StableClassJudge, CLASS_NAMES 已在 muju_cls_rknn 中定义 diff --git a/vision/visual_callback_dq.py b/vision/visual_callback_dq.py index ef94117..b76f449 100644 --- a/vision/visual_callback_dq.py +++ b/vision/visual_callback_dq.py @@ -3,6 +3,7 @@ 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 @@ -16,7 +17,7 @@ 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 +from core.system_state import SystemState,FeedStatus,Upper_Door_Position from dataclasses import asdict import json import math @@ -44,6 +45,8 @@ class VisualCallback: 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 @@ -67,6 +70,8 @@ class VisualCallback: self._current_controlling_thread = None #是否启动后的第一个模具 self._is_first_module=True + #上一次派单方量 + self.prev_pd_volume=0.0 self.init_val() # self._setup_logging_2() @@ -88,6 +93,8 @@ class VisualCallback: 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推送的数据),并进行处理,注意处理视觉进行夹角控制 @@ -308,7 +315,7 @@ class VisualCallback: (current_time - self._last_arch_time) >= 2: self._last_arch_time = current_time print('---------------------第一阶段振动5秒(小于200KG)-----------------') - self.relay_controller.control_arch_lower_open_sync(5) + self.relay_feed.control_arch_lower_open_sync(5) self._last_arch_one_weight = _arch_weight continue self._last_arch_one_weight = _arch_weight @@ -322,8 +329,8 @@ class VisualCallback: if (_weight_changed < 100) and \ (current_time - self._last_arch_time) >= 2: self._last_arch_time = current_time - print('---------------------第二阶段振动3秒-----------------') - self.relay_controller.control_arch_upper_open_sync(3) + #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 @@ -339,7 +346,7 @@ class VisualCallback: (current_time - self._last_arch_time) >= 2: self._last_arch_time = current_time print('---------------------第三阶段振动5秒(小于100KG)-----------------') - self.relay_controller.control_arch_lower_open_sync(5) + self.relay_feed.control_arch_lower_open_sync(5) self._last_arch_three_weight = _arch_weight continue self._last_arch_three_weight = _arch_weight @@ -355,7 +362,7 @@ class VisualCallback: self._last_arch_time = current_time print('---------------------第四阶段振动5秒-----------------') #重量不准,暂时不振动 - # self.relay_controller.control_arch_upper_open_sync(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 @@ -372,7 +379,7 @@ class VisualCallback: (current_time - self._last_arch_time) >= 2: self._last_arch_time = current_time print(f'---------------------第五阶段振动3秒(小于{_min_arch_weight}kg))-----------------') - self.relay_controller.control_arch_lower_open_sync(3) + self.relay_feed.control_arch_lower_open_sync(3) self._last_arch_five_weight = _arch_weight continue self._last_arch_five_weight = _arch_weight @@ -486,6 +493,13 @@ class VisualCallback: 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 @@ -575,6 +589,14 @@ class VisualCallback: 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, @@ -633,7 +655,6 @@ class VisualCallback: """第一阶段下料:下料斗向模具车下料(低速)""" print("--------------------开始下料(F块)--------------------") self._time_mould_begin=datetime.now().strftime("%Y-%m-%d %H:%M:%S") - # loc_relay=self.relay_controller loc_mitter=self.transmitter_controller max_weight_none=5 cur_weight_none=0 @@ -664,6 +685,8 @@ class VisualCallback: 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秒 @@ -683,7 +706,7 @@ class VisualCallback: """第一阶段下料:下料斗向模具车下料(低速)""" print("--------------------开始下料(普通块)--------------------") self._time_mould_begin=datetime.now().strftime("%Y-%m-%d %H:%M:%S") - loc_relay=self.relay_controller + loc_relay=self.relay_feed loc_mitter=self.transmitter_controller max_weight_none=5 cur_weight_none=0 @@ -710,12 +733,15 @@ class VisualCallback: return continue cur_weight_none=0 - self._is_finish_ratio=(initial_lower_weight-current_weight)/need_total_weight + 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,上面粘贴的,振动一下 - # self.relay_controller.control_arch_lower_open_async(5) + # loc_relay.control_arch_lower_open_async(5) self.close_lower_door_visual() break time.sleep(1) @@ -729,12 +755,12 @@ class VisualCallback: print(f'------------已下料(第一次): {first_finish_weight}kg-------------') self._is_feed_stage=0 - while self.plc_data not in [5,37]: + while self.plc_data not in self.plc_valid_data: #print('------------上料斗未就位----------------') # print('------------上料斗未就位----------------') time.sleep(1) - if self.plc_data==5 or self.plc_data==37: + if self.plc_data in self.plc_valid_data: print(f'------------上料斗就位(上料斗往下料斗阶段)-------------') #打开上料斗出砼门,开5就,开三分之一下 @@ -742,7 +768,7 @@ class VisualCallback: 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: @@ -751,7 +777,8 @@ class VisualCallback: #如果重量连续5次为None,认为上料斗未就位,跳出循环 print('------------第一次上到下,上料斗重量异常----------------') print('------------第一次上到下,上料斗重量异常----------------') - loc_relay.control_upper_close_sync(5+loc_time_count) + self.state._upper_door_closed=0 + loc_relay.control_upper_close_sync(5+loc_time_count) return continue cur_weight_none=0 @@ -760,6 +787,7 @@ class VisualCallback: _two_lower_weight=0 if (current_upper_weight<3200 and current_upper_weight>0) or _two_lower_weight>3200: #关5秒,loc_time_count多关一秒 + self.state._upper_door_closed=0 loc_relay.control_upper_close_sync(5+loc_time_count) break else: @@ -772,7 +800,7 @@ class VisualCallback: 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: @@ -789,11 +817,13 @@ class VisualCallback: return continue cur_weight_none=0 - self._is_finish_ratio=(first_finish_weight+initial_lower_weight-current_weight)/need_total_weight + 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,上面粘贴的,振动一下 - # self.relay_controller.control_arch_lower_open_async(5) + # loc_relay.control_arch_lower_open_async(5) self.close_lower_door_visual() break # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') @@ -807,7 +837,8 @@ class VisualCallback: print(f'------------已下料(第二次): {first_finish_weight}kg-------------') self._is_feed_stage=0 - if self.plc_data==5 or self.plc_data==37: + 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 @@ -818,6 +849,7 @@ class VisualCallback: #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) @@ -827,8 +859,9 @@ class VisualCallback: #如果重量连续5次为None,认为上料斗未就位,跳出循环 print('------------第二次上到下,上料斗重量异常----------------') print('------------第二次上到下,上料斗重量异常----------------') + self.state._upper_door_closed=0 loc_relay.control_upper_close_sync(15) - break + return continue cur_weight_none=0 if (current_upper_weight<600 and current_upper_weight>0) or upper_open_time_2 is not None: @@ -838,6 +871,7 @@ class VisualCallback: 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) @@ -853,6 +887,8 @@ class VisualCallback: time.sleep(0.5) else: loc_relay.control_upper_close_sync(15) + + self.state._upper_door_closed=0 # time.sleep(0.4) #第三次下料斗转移到模具车 @@ -877,7 +913,9 @@ class VisualCallback: continue cur_weight_none=0 second_finish_weight=initial_lower_weight-current_weight - self._is_finish_ratio=(second_finish_weight+first_finish_weight)/need_total_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秒 @@ -1340,6 +1378,15 @@ class VisualCallback: #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): @@ -1360,82 +1407,12 @@ class VisualCallback: # self.safe_control_lower_close() # 等待线程结束 - if self.callback_thread.is_alive(): + if self.callback_thread and self.callback_thread.is_alive(): self.callback_thread.join(timeout=1.0) - if self.diff_thread: + if self.diff_thread and self.diff_thread.is_alive(): self.diff_thread.join(timeout=1.0) - # self.relay_controller._close_lower_5s - - def send_pd_data(self): - """ - 发送PD数据到OPC队列 - """ - # 构建PD数据 - _cur_mould=self._cur_mould_model - if _cur_mould is not None: - if _cur_mould.MouldCode: - _pdrecords = self.pd_record_bll.get_last_pds(_cur_mould.MouldCode) - if _pdrecords: - _pdrecord=_pdrecords[0] - if _pdrecord.TaskID: - if _pdrecord.BlockNumber=='F': - print(f'{_pdrecord.MouldCode} F块,不发送派单数据') - print(f'{_pdrecord.MouldCode} F块,不发送派单数据') - print(f'{_pdrecord.MouldCode} F块,不发送派单数据') - return True - _fact_volumn=self.get_fact_volumn(_pdrecord.MouldCode,_pdrecord.BlockNumber) - if _fact_volumn>0: - _pdrecord.FBetonVolume=_fact_volumn - print(f'{_pdrecord.MouldCode}-{_pdrecord.BlockNumber} 实际派单方量:{_fact_volumn},{_fact_volumn},{_fact_volumn}') - print(f'{_pdrecord.MouldCode}-{_pdrecord.BlockNumber} 实际派单方量:{_fact_volumn},{_fact_volumn},{_fact_volumn}') - print(f'{_pdrecord.MouldCode}-{_pdrecord.BlockNumber} 实际派单方量:{_fact_volumn},{_fact_volumn},{_fact_volumn}') - self.state._pd_data=_pdrecord - return True - else: - return False - else: - print(f'{_pdrecord.MouldCode} 未获取到数据-(等待扫码)') - return False - else: - print(f'接口数据异常') - return False - else: - return None - def get_fact_volumn(self,mould_code:str,block_number:str='') -> float: - """获取实际派单发量""" - _now_volumn=0 - _pd_volumn=0 - print(f'get_fact_volumn当前重量:{self._init_lower_weight}') - _now_volumn=self._init_lower_weight/2500 - if not block_number and '-' in mould_code: - block_number = mould_code.split('-')[0][-2:] - if block_number in ['B1','B2','B3']: - _pd_volumn=1.9 - elif block_number=='L1': - _pd_volumn=2.0 - if _now_volumn>0.5: - #保证至少0.5方 - _pd_volumn=1.9-_now_volumn+0.5 - _pd_volumn=math.ceil(_pd_volumn*10)/10 - - if _pd_volumn<0.8: - _pd_volumn=0.8 - #调整 - elif block_number=='L2': - #2.4方,大约L2和F的量 - _pd_volumn=2.4 - # if _weight>1300: - #留0.15 math.floor(_now_volumn*10)/10 保留一位小数,丢掉其他的 - _pd_volumn=_pd_volumn-math.floor(_now_volumn*10)/10+0.1 - _pd_volumn=math.ceil(_pd_volumn*10)/10 - if _pd_volumn>2.1: - _pd_volumn=2.1 - elif _pd_volumn<0.8: - _pd_volumn=0.8 - - return _pd_volumn - + def get_current_mould(self): """获取当前要浇筑的管片""" _not_poured=app_web_service.get_not_pour_artifacts() @@ -1443,6 +1420,7 @@ class VisualCallback: _cur_poured_model=_not_poured[-1] if _cur_poured_model.MouldCode: self._cur_mould_model=_cur_poured_model + self.state.current_mould=_cur_poured_model print(f'当前要浇筑的管片 {json.dumps(asdict(_cur_poured_model), ensure_ascii=False)}') else: print('当前没有未浇筑的管片') diff --git a/vision/visual_callback_dq0209.py b/vision/visual_callback_dq0209.py index 299ecf4..9efd3b3 100644 --- a/vision/visual_callback_dq0209.py +++ b/vision/visual_callback_dq0209.py @@ -1417,7 +1417,7 @@ class VisualCallback: print(f'{_pdrecord.MouldCode}-{_pdrecord.BlockNumber} 实际派单方量:{_fact_volumn},{_fact_volumn},{_fact_volumn}') print(f'{_pdrecord.MouldCode}-{_pdrecord.BlockNumber} 实际派单方量:{_fact_volumn},{_fact_volumn},{_fact_volumn}') print(f'{_pdrecord.MouldCode}-{_pdrecord.BlockNumber} 实际派单方量:{_fact_volumn},{_fact_volumn},{_fact_volumn}') - self.state._pd_data=_pdrecord + # self.state._pd_data=_pdrecord return True else: return False