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 420f04a..4f40a7c 100644 Binary files a/db/messages.db and b/db/messages.db differ diff --git a/db/three-record.db b/db/three-record.db new file mode 100644 index 0000000..3090224 Binary files /dev/null and b/db/three-record.db differ diff --git a/db/three.db b/db/three.db index f57ab3f..c8256b7 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 a6bb6c3..31447ed 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 abcfaf8..7e8f069 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 9690e21..c43cd9b 100644 Binary files a/doc/table表设计.doc and b/doc/table表设计.doc differ diff --git a/doc/~$控制程序对接.docx b/doc/~$控制程序对接.docx new file mode 100644 index 0000000..db76db1 Binary files /dev/null and b/doc/~$控制程序对接.docx differ diff --git a/doc/控制程序对接.docx b/doc/控制程序对接.docx index 45c63cd..1ad232e 100644 Binary files a/doc/控制程序对接.docx and b/doc/控制程序对接.docx differ diff --git a/hardware/inverter.py b/hardware/inverter.py index 5ee50e7..8e235b5 100644 --- a/hardware/inverter.py +++ b/hardware/inverter.py @@ -54,9 +54,9 @@ class InverterController: frequency_value = self.inverter.read_register(0x7310) _ret=frequency_value / 100 else: - print(f"读取频率{frequency}失败") + print(f"读取频率失败") except Exception as e: - print(f"读取频率{frequency}异常:{e}") + print(f"读取频率异常:{e}") finally: if self.inverter: self.inverter.serial.close() diff --git a/hardware/relay.py b/hardware/relay.py index eca3895..f980c31 100644 --- a/hardware/relay.py +++ b/hardware/relay.py @@ -145,18 +145,6 @@ class RelayController: time.sleep(duration) self.control(self.DOOR_UPPER_OPEN, 'close') - def control_upper_close_sync(self,duration): - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试执行上料斗同步关闭,实际操作下料斗") - - with self.door_control_lock: - print(f"[{thread_name}] 获得下料斗控制锁,执行同步关闭操作") - self.control(self.DOOR_UPPER_OPEN, 'close') - self.control(self.DOOR_UPPER_CLOSE, 'open') - time.sleep(duration) - self.control(self.DOOR_UPPER_CLOSE, 'close') - print(f"[{thread_name}] 同步关闭操作完成,释放控制锁") - def control_upper_open(self): #关闭信号才能生效 self.control(self.DOOR_UPPER_CLOSE, 'close') @@ -314,7 +302,16 @@ class RelayController: print(f"下料斗振动关闭完成,延迟{delay_seconds}秒") self.control(self.BREAK_ARCH_LOWER, 'close') - + + def control_upper_to_jbl(self): + """控制上料斗到搅拌楼""" + # 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') def close_all(self): """关闭所有继电器""" diff --git a/opc/opcua_client_feed.py b/opc/opcua_client_feed.py index ef3e2ec..750b30e 100644 --- a/opc/opcua_client_feed.py +++ b/opc/opcua_client_feed.py @@ -3,53 +3,86 @@ from opcua import Client, ua import time from datetime import datetime import configparser -from threading import Thread - -# class OpcuaUiSignal: - # value_changed = Signal(str, str, object) - # opc_disconnected = Signal(str) # OPC服务断开信号,参数:断开原因 - # opc_reconnected = Signal() # OPC重连成功信号 - # opc_log = Signal(str) # OPC运行日志信号,参数:日志信息 - +from threading import Thread,RLock +import logging +import queue + +logging.getLogger("opcua").setLevel(logging.WARNING) +logging.getLogger("opcua.client.ua_client").setLevel(logging.WARNING) +logging.getLogger("opcua.uaprotocol").setLevel(logging.WARNING) +#控制程序opcua 需要需要在opc_config中配置_f结尾的key才会收到通知 # Opcua回调处理器 class SubscriptionHandler: - def __init__(self): + def __init__(self,parent): self.node_id_to_name = {} - # self.opc_signal = opc_signal + #设置派单模式(1自动派单 2手动派单0未知) + # self.pd_set_mode=0 + # #设置方量{ID:派单表ID,Volumn:修改后方量} + # self.pd_set_volumn='' + # #派单通知数据{ErpID:ID:派单表ID,Flag:状态,ErrorMsg:失败异常信息 } + # self.pd_notify='' + # 添加队列和锁 + self.notification_queue = queue.Queue() + self.processing_thread = Thread(target=self._process_notifications, daemon=True,name="opcua_recv_thread") + self.lock = RLock() # 可重入锁 + self.parent=parent + self.notify_callback=self.parent.notify_callback + + def start_accept(self): + self.processing_thread.start() def datachange_notification(self, node, val, data): try: node_id = node.nodeid.to_string() var_name = self.node_id_to_name.get(node_id) - # self.opc_signal.value_changed.emit(node_id, var_name, val) + print(f"node_id: {node_id}, var_name: {var_name}, val: {val}") + self.notification_queue.put_nowait((var_name, val)) + # setattr(self, var_name, val) + # if self.notify_callback: + # self.notify_callback(var_name,val) except Exception as e: err_msg = f"opcua解析值变化事件失败: {e}" - # self.opc_signal.opc_log.emit(err_msg) + print(err_msg) + + def _process_notifications(self): + """后台线程处理通知(串行执行)""" + while self.parent.is_running: + try: + var_name, val = self.notification_queue.get(timeout=1) + # 使用锁保护共享资源 + with self.lock: + self.notify_callback(var_name,val) + self.notification_queue.task_done() + except queue.Empty: + continue + except Exception as e: + print(f"处理通知失败: {e}") + class OpcuaClientFeed(Thread): - def __init__(self, parent=None): + def __init__(self,notify_callback=None, parent=None): super().__init__(parent) - self.server_url = "" - self.client = None + + self.name="opcua_feed_client" + self.target_var_paths = [] + #订阅配置文件中的参数 + self.subscription_name=["pd_notify","pd_set_mode","pd_set_volume"] + # self.server_url = "" + self.read_opc_config() # 读取配置文件 + self.client = Client(self.server_url) # 初始化opc客户端 + self.notify_callback=notify_callback + self.connected = False self.subscription = None self.monitored_items = [] self.is_running = True # 线程运行标志位 self.node_id_mapping = {} # node_id 和 可读变量名的映射表 self.is_reconnect_tip_sent = False # 重连失败提示是否已发送 + self.handler = SubscriptionHandler(self) - # self.opc_signal = OpcuaUiSignal() - # self.handler = SubscriptionHandler(self.opc_signal) - self.handler = SubscriptionHandler() - - self.target_var_paths = [] - - # 参数 - self.heartbeat_interval = None # 心跳检测间隔 - self.reconnect_interval = None # 首次/掉线重连间隔 - self.sub_interval = None # 订阅间隔 (单位:ms) - + def start_accept(self): + self.handler.start_accept() def stop_run(self): """停止线程+断开连接""" @@ -65,7 +98,6 @@ class OpcuaClientFeed(Thread): self.connected = True msg = f"成功连接到OPCUA服务器: {self.server_url}" print(msg) - # self.opc_signal.opc_log.emit(msg) self.is_reconnect_tip_sent = False return True except Exception as e: @@ -73,7 +105,7 @@ class OpcuaClientFeed(Thread): err_msg = f"连接OPCUA服务器失败: {e}" print(err_msg) if not self.is_reconnect_tip_sent: - # self.opc_signal.opc_log.emit(err_msg) + print(err_msg) # 标记为已发送,后续不重复在UI上显示 self.is_reconnect_tip_sent = True return False @@ -111,41 +143,37 @@ class OpcuaClientFeed(Thread): if not self.connected: return False try: - # self.opc_signal.opc_log.emit("开始构建nodeid映射表...") objects_node = self.client.get_objects_node() self.handler.node_id_to_name = self.node_id_mapping for var_name, path_list in self.target_var_paths: target_node = objects_node.get_child(path_list) node_id = target_node.nodeid.to_string() self.node_id_mapping[node_id] = var_name - # self.opc_signal.opc_log.emit("nodeid映射表构建成功") + print("nodeid映射表构建成功") return True except Exception as e: err_msg = f"构建{var_name}映射表失败: {e}" print(err_msg) - # self.opc_signal.opc_log.emit(err_msg) return False def create_multi_subscription(self, interval=None): """订阅多个变量(基于映射表的nodeid)""" if not self.connected: return - if not self.node_id_mapping and not self.build_node_id_mapping(): + if not self.node_id_mapping: return try: interval = int(interval) if interval else self.sub_interval self.subscription = self.client.create_subscription(interval, self.handler) - # self.opc_signal.opc_log.emit(f"opcua订阅创建成功(间隔:{interval}ms)") - for node_id, var_name in self.node_id_mapping.items(): - var_node = self.client.get_node(node_id) - monitored_item = self.subscription.subscribe_data_change(var_node) - self.monitored_items.append(monitored_item) - # self.opc_signal.opc_log.emit(f"已订阅变量: {var_name} (nodeid: {node_id})") - print(f"已订阅变量: {var_name} (nodeid: {node_id})") + for node_id, var_name in self.node_id_mapping.items(): + if var_name in self.subscription_name: + var_node = self.client.get_node(node_id) + monitored_item = self.subscription.subscribe_data_change(var_node) + self.monitored_items.append(monitored_item) + print(f"已订阅变量: {var_name} (nodeid: {node_id})") except Exception as e: err_msg = f"创建批量订阅失败: {e}" print(err_msg) - # self.opc_signal.opc_log.emit(err_msg) def read_opc_config(self, cfg_path = "config/opc_config.ini"): """读取OPC配置文件, 初始化所有参数和节点列表""" @@ -157,23 +185,19 @@ class OpcuaClientFeed(Thread): self.heartbeat_interval = cfg.getint("OPC_SERVER_CONFIG", "heartbeat_interval") self.reconnect_interval = cfg.getint("OPC_SERVER_CONFIG", "reconnect_interval") self.sub_interval = cfg.getint("OPC_SERVER_CONFIG", "sub_interval") - + # 2. 读取OPC节点配置 node_section = cfg["OPC_NODE_LIST"] for readable_name, node_path_str in node_section.items(): node_path_list = node_path_str.split(",") self.target_var_paths.append( (readable_name, node_path_list) ) - # print("target_var_paths", self.target_var_paths) + print("target_var_paths", self.target_var_paths) except Exception as e: print(f"读取配置文件失败: {e},使用默认配置启动!") self.server_url = "opc.tcp://localhost:4840/zjsh_feed/server/" self.heartbeat_interval = 4 self.reconnect_interval = 2 self.sub_interval = 500 - self.target_var_paths = [ - ("upper_weight", ["2:upper", "2:upper_weight"]), - ("lower_weight", ["2:lower", "2:lower_weight"]) - ] # 参数合法性检验 self.heartbeat_interval = self.heartbeat_interval if isinstance(self.heartbeat_interval, int) and self.heartbeat_interval >=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