diff --git a/11.jpg b/11.jpg deleted file mode 100644 index 51fe6d9..0000000 Binary files a/11.jpg and /dev/null differ diff --git a/Fedding.py b/Fedding.py deleted file mode 100644 index 378608d..0000000 --- a/Fedding.py +++ /dev/null @@ -1,1153 +0,0 @@ -import socket -import binascii -import time -import threading -import struct -import cv2 -import os -from pymodbus.client import ModbusTcpClient -from pymodbus.exceptions import ModbusException -# 添加视觉模块路径 -import sys - -sys.path.append(os.path.join(os.path.dirname(__file__), 'src', 'vision')) -# 导入视觉处理模块 -from vision.anger_caculate import predict_obb_best_angle -from vision.resize_tuili_image_main import classify_image_weighted, load_global_rois, crop_and_resize, YOLO - - -class FeedingControlSystem: - def __init__(self, relay_host='192.168.0.18', relay_port=50000): - # 网络继电器配置 - self.relay_host = relay_host - self.relay_port = relay_port - self.relay_modbus_client = ModbusTcpClient(relay_host, port=relay_port) - - # 继电器映射 - self.DOOR_UPPER = 'door_upper' # DO0 - 上料斗滑动 - self.DOOR_LOWER_1 = 'door_lower_1' # DO1 - 上料斗出砼门 - self.DOOR_LOWER_2 = 'door_lower_2' # DO2 - 下料斗出砼门 - self.BREAK_ARCH_UPPER = 'break_arch_upper' # DO3 - 上料斗破拱 - self.BREAK_ARCH_LOWER = 'break_arch_lower' # DO4 - 下料斗破拱 - - # 继电器命令(原始Socket)mudbus TCP模式 - self.relay_commands = { - self.DOOR_UPPER: {'open': '00000000000601050000FF00', 'close': '000000000006010500000000'}, - self.DOOR_LOWER_1: {'open': '00000000000601050001FF00', 'close': '000000000006010500010000'}, - self.DOOR_LOWER_2: {'open': '00000000000601050002FF00', 'close': '000000000006010500020000'}, - self.BREAK_ARCH_UPPER: {'open': '00000000000601050003FF00', 'close': '000000000006010500030000'}, - self.BREAK_ARCH_LOWER: {'open': '00000000000601050004FF00', 'close': '000000000006010500040000'} - } - - # 读取状态命令 - self.read_status_command = '000000000006010100000008' - - # 设备位映射 - self.device_bit_map = { - self.DOOR_UPPER: 0, - self.DOOR_LOWER_1: 1, - self.DOOR_LOWER_2: 2, - self.BREAK_ARCH_UPPER: 3, - self.BREAK_ARCH_LOWER: 4 - } - - # 变频器配置(Modbus RTU 协议) - self.inverter_config = { - 'slave_id': 1, - 'frequency_register': 0x01, # 2001H - 'start_register': 0x00, # 2000H - 'stop_register': 0x00, # 2000H(用于停机) - 'start_command': 0x0013, # 正转点动运行 - 'stop_command': 0x0001 # 停机 - } - - # 变送器配置(Modbus RTU) - self.transmitter_config = { - 1: { # 上料斗 - 'slave_id': 1, - 'weight_register': 0x01, - 'register_count': 2 - },#发出去的内容01 03 00 01 00 02 - 2: { # 下料斗 - 'slave_id': 2, - 'weight_register': 0x01, - 'register_count': 2 - }#发出去的内容02 03 00 01 00 02 - } - - # 系统状态 - self._running = False - self._monitor_thread = None - self._visual_control_thread = None - self._alignment_check_thread = None - - # 下料控制相关 - self.min_required_weight = 500 # 模具车最小需要重量(kg) - self.upper_door_position = 'default' # default(在搅拌楼下接料), over_lower(在下料斗上方), returning(返回中) - self.lower_feeding_stage = 0 # 0:未下料, 1:第一阶段, 2:第二阶段, 3:等待上料, 4:等待模具车对齐 - self.lower_feeding_cycle = 0 # 下料斗下料循环次数 (1, 2, 3) - self.upper_feeding_count = 0 # 上料斗已下料次数 (0, 1, 2) - self.last_upper_weight = 0 - self.last_lower_weight = 0 - self.last_weight_time = time.time() - self.target_vehicle_weight = 5000 # 目标模具车重量(kg) - self.upper_buffer_weight = 500 # 上料斗缓冲重量(kg),每次下料多下这么多 - self.single_batch_weight = 2500 # 单次下料重量(kg) - - - #夹角状态 - self.angle_control_mode = "normal" # 角度控制模式: normal, reducing, maintaining, recovery - - # 错误计数 - self.upper_weight_error_count = 0 - self.lower_weight_error_count = 0 - self.max_error_count = 3 - - # 下料阶段频率(Hz) - self.inverter_max_frequency = 400.0#频率最大值 - self.frequencies = [220.0, 230.0, 240.0] - - # 视觉系统接口 - self.overflow_detected = False # 堆料检测 - self.door_opening_large = False # 夹角 - self.vehicle_aligned = False # 模具车是否对齐 - - # 视觉控制参数 - self.angle_threshold = 60.0 # 角度阈值,超过此值认为开口过大 - self.target_angle = 20.0 # 目标角度 - self.min_angle = 10.0 # 最小角度 - self.max_angle = 80.0 # 最大角度 - self.angle_tolerance = 5.0 # 角度容差 - self.visual_control_enabled = True # 视觉控制使能 - self.last_angle = None # 上次检测角度 - self.visual_check_interval = 1.0 # 视觉检查间隔(秒) - self.alignment_check_interval = 0.5 # 对齐检查间隔(秒) - - # 模型路径配置 - self.angle_model_path = "vision/models/angle.pt" - self.overflow_model_path = "vision/models/overflow.pt" - self.alignment_model_path = "vision/models/alig.pt" # 模具车对齐检测模型 - self.roi_file_path = "vision/roi_coordinates/1_rois.txt" - - # 模型实例 - self.angle_model = None # 夹角检测模型实例 - self.overflow_model = None # 堆料检测模型实例 - self.alignment_model = None # 对齐检测模型实例 - - # 摄像头相关配置 - self.camera = None - self.camera_type = "ip" - self.camera_ip = "192.168.1.51" - self.camera_port = 554 - self.camera_username = "admin" - self.camera_password = "XJ123456" - self.camera_channel = 1 - # self.current_image_path = "current_frame.jpg" - - def set_camera_config(self, camera_type="ip", ip=None, port=None, username=None, password=None, channel=1): - """ - 设置摄像头配置 - :param ip: 网络摄像头IP地址 - :param port: 网络摄像头端口 - :param username: 网络摄像头用户名 - :param password: 网络摄像头密码 - :param channel: 摄像头通道号 - """ - self.camera_type = camera_type - if ip: - self.camera_ip = ip - if port: - self.camera_port = port - if username: - self.camera_username = username - if password: - self.camera_password = password - self.camera_channel = channel - - def set_angle_parameters(self, target_angle=20.0, min_angle=10.0, max_angle=80.0, threshold=60.0): - """ - 设置角度控制参数 - """ - self.target_angle = target_angle - self.min_angle = min_angle - self.max_angle = max_angle - self.angle_threshold = threshold - - def set_feeding_parameters(self, target_vehicle_weight=5000, upper_buffer_weight=500, single_batch_weight=2500): - """ - 设置下料参数 - :param target_vehicle_weight: 目标模具车重量(kg) - :param upper_buffer_weight: 上料斗缓冲重量(kg) - :param single_batch_weight: 单次下料重量(kg) - """ - self.target_vehicle_weight = target_vehicle_weight - self.upper_buffer_weight = upper_buffer_weight - self.single_batch_weight = single_batch_weight - - def load_all_models(self): - """ - 加载所有视觉检测模型 - """ - success = True - - # 加载夹角检测模型 - try: - if not os.path.exists(self.angle_model_path): - print(f"夹角检测模型不存在: {self.angle_model_path}") - success = False - else: - # 注意:angle.pt模型通过predict_obb_best_angle函数使用,不需要预加载 - print(f"夹角检测模型路径: {self.angle_model_path}") - except Exception as e: - print(f"检查夹角检测模型失败: {e}") - success = False - - # 加载堆料检测模型 - try: - if not os.path.exists(self.overflow_model_path): - print(f"堆料检测模型不存在: {self.overflow_model_path}") - success = False - else: - self.overflow_model = YOLO(self.overflow_model_path) - print(f"成功加载堆料检测模型: {self.overflow_model_path}") - except Exception as e: - print(f"加载堆料检测模型失败: {e}") - success = False - - # 加载对齐检测模型 - try: - if not os.path.exists(self.alignment_model_path): - print(f"对齐检测模型不存在: {self.alignment_model_path}") - success = False - else: - self.alignment_model = YOLO(self.alignment_model_path) - print(f"成功加载对齐检测模型: {self.alignment_model_path}") - except Exception as e: - print(f"加载对齐检测模型失败: {e}") - success = False - - return success - - def send_relay_command(self, command_hex): - """发送原始Socket命令""" - try: - byte_data = binascii.unhexlify(command_hex) - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - sock.connect((self.relay_host, self.relay_port)) - sock.send(byte_data) - response = sock.recv(1024) - print(f"收到继电器响应: {binascii.hexlify(response)}") - return response - except Exception as e: - print(f"继电器通信错误: {e}") - return None - - def get_relay_status(self): - """获取继电器状态""" - response = self.send_relay_command(self.read_status_command) - status_dict = {} - - if response and len(response) >= 10: - status_byte = response[9] - status_bin = f"{status_byte:08b}"[::-1] - for key, bit_index in self.device_bit_map.items(): - status_dict[key] = status_bin[bit_index] == '1' - else: - print("读取继电器状态失败") - - return status_dict - - def control_relay(self, device, action): - """控制继电器""" - if device in self.relay_commands and action in self.relay_commands[device]: - print(f"控制继电器 {device} {action}") - self.send_relay_command(self.relay_commands[device][action]) - time.sleep(0.1) - else: - print(f"无效设备或动作: {device}, {action}") - - def read_transmitter_data_via_relay(self, transmitter_id): - """读取变送器数据(Modbus TCP 转 RS485)""" - try: - if transmitter_id not in self.transmitter_config: - print(f"无效变送器ID: {transmitter_id}") - return None - - config = self.transmitter_config[transmitter_id] - - if not self.relay_modbus_client.connect(): - print("无法连接网络继电器Modbus服务") - return None - - result = self.relay_modbus_client.read_holding_registers( - address=config['weight_register'], - count=config['register_count'], - slave=config['slave_id']#转发给哪台变送器 - ) - - if isinstance(result, Exception): - print(f"读取变送器 {transmitter_id} 失败: {result}") - return None - - # 根据图片示例,正确解析数据 - if config['register_count'] == 2:#读两个寄存器 - # 获取原始字节数组 - raw_data = result.registers - # 组合成32位整数 - weight = (raw_data[0] << 16) + raw_data[1] - weight = weight / 1000.0 # 单位转换为千克 - elif config['register_count'] == 1: - weight = float(result.registers[0]) - else: - print(f"不支持的寄存器数量: {config['register_count']}") - return None - - print(f"变送器 {transmitter_id} 读取重量: {weight}kg") - return weight - - except ModbusException as e: - print(f"Modbus通信错误: {e}") - return None - except Exception as e: - print(f"数据解析错误: {e}") - return None - finally: - self.relay_modbus_client.close() - - def set_inverter_frequency_via_relay(self, frequency): - """设置变频器频率""" - try: - if not self.relay_modbus_client.connect(): - print("无法连接网络继电器Modbus服务") - return False - - # 使用最大频率变量计算百分比 - percentage = frequency / self.inverter_max_frequency # 得到 0~1 的比例 - value = int(percentage * 10000) # 转换为 -10000 ~ 10000 的整数 - - # 限制范围 - value = max(-10000, min(10000, value)) - - result = self.relay_modbus_client.write_register( - self.inverter_config['frequency_register'], - value, - slave=self.inverter_config['slave_id'] - ) - - if isinstance(result, Exception): - print(f"设置频率失败: {result}") - return False - - print(f"设置变频器频率为 {frequency}Hz") - return True - except ModbusException as e: - print(f"变频器Modbus通信错误: {e}") - return False - finally: - self.relay_modbus_client.close() - - def control_inverter_via_relay(self, action): - try: - if not self.relay_modbus_client.connect(): - print("无法连接网络继电器Modbus服务") - return False - - if action == 'start': - result = self.relay_modbus_client.write_register( - address=self.inverter_config['start_register'], - value=self.inverter_config['start_command'], - slave=self.inverter_config['slave_id'] - ) - print("启动变频器") - elif action == 'stop': - result = self.relay_modbus_client.write_register( - address=self.inverter_config['start_register'], - value=self.inverter_config['stop_command'], - slave=self.inverter_config['slave_id'] - ) - print("停止变频器") - else: - print(f"无效操作: {action}") - return False - - if isinstance(result, Exception): - print(f"控制失败: {result}") - return False - - return True - except ModbusException as e: - print(f"变频器控制错误: {e}") - return False - finally: - self.relay_modbus_client.close() - - def check_upper_material_request(self): - """检查是否需要要料""" - current_weight = self.read_transmitter_data_via_relay(1) - - if current_weight is None: - self.upper_weight_error_count += 1 - print(f"上料斗重量读取失败,错误计数: {self.upper_weight_error_count}") - if self.upper_weight_error_count >= self.max_error_count: - print("警告:上料斗传感器连续读取失败,请检查连接") - return False - - self.upper_weight_error_count = 0 - # 判断是否需要要料:当前重量 < 目标重量 + 缓冲重量 - if current_weight < (self.single_batch_weight + self.min_required_weight): - print("上料斗重量不足,通知搅拌楼要料") - self.request_material_from_mixing_building() # 请求搅拌楼下料 - return True - return False - - def request_material_from_mixing_building(self): - """ - 请求搅拌楼下料(待完善) - TODO: 与同事对接通信协议 - """ - print("发送要料请求至搅拌楼...") - self.return_upper_door_to_default() - # 这里需要与同事对接具体的通信方式 - # 可能是Modbus写寄存器、TCP通信、HTTP请求等 - pass - - def wait_for_mixing_building_material(self): - """ - 等待搅拌楼下料完成(待完善) - TODO: 与同事对接信号接收 - """ - print("等待搅拌楼下料完成...") - # 这里需要与同事对接具体的信号接收方式 - # 可能是Modbus读寄存器、TCP通信、HTTP请求等 - # 模拟等待 - time.sleep(5) - print("搅拌楼下料完成") - self.move_upper_door_over_lower() - return True - - def move_upper_door_over_lower(self): - """移动上料斗到下料斗上方""" - print("移动上料斗到下料斗上方") - self.control_relay(self.DOOR_UPPER, 'open') - self.upper_door_position = 'over_lower' - - def return_upper_door(self): - """返回上料斗到搅拌楼""" - print("上料斗返回搅拌楼") - self.control_relay(self.DOOR_UPPER, 'close') - self.upper_door_position = 'returning' - - def return_upper_door_to_default(self): - """上料斗回到默认位置(搅拌楼下接料位置)""" - print("上料斗回到默认位置") - self.control_relay(self.DOOR_UPPER, 'close') - self.upper_door_position = 'default' - - def start_lower_feeding(self): - """开始分步下料""" - if self.lower_feeding_stage != 0: - print("下料已在进行中") - return - - # 检查关键设备是否可连接 - if not self._check_device_connectivity(): - print("关键设备连接失败,无法开始下料") - return - - print("开始分步下料过程") - # 重置计数器 - self.lower_feeding_cycle = 0 # 用于记录三阶段下料次数 - self.upper_feeding_count = 0 # 用于记录上料次数 - - # 第一次上料(总共需要上料2次) - self.transfer_material_from_upper_to_lower() - - # 等待模具车对齐并开始第一轮下料 - self.lower_feeding_stage = 4 # 从等待模具车对齐开始 - self.wait_for_vehicle_alignment() - - def _check_device_connectivity(self): - """检查关键设备连接状态""" - try: - # 检查网络继电器连接 - test_response = self.send_relay_command(self.read_status_command) - if not test_response: - print("网络继电器连接失败") - return False - - # 检查变频器连接 - if not self.relay_modbus_client.connect(): - print("无法连接到网络继电器Modbus服务") - return False - - # 尝试读取变频器一个寄存器(测试连接) - test_result = self.relay_modbus_client.read_holding_registers( - address=0x00, - count=1, - slave=self.inverter_config['slave_id'] - ) - - if isinstance(test_result, Exception): - print("变频器连接测试失败") - return False - - # 检查下料斗变送器连接 - test_weight = self.read_transmitter_data_via_relay(2) - if test_weight is None: - print("下料斗变送器连接失败") - return False - - self.relay_modbus_client.close() - return True - except Exception as e: - print(f"设备连接检查失败: {e}") - return False - - def transfer_material_from_upper_to_lower(self): - """上料斗向下料斗下料(基于上料斗重量传感器控制)""" - print(f"上料斗向下料斗下料 (第 {self.upper_feeding_count + 1} 次)") - - # 记录下料前的重量 - initial_upper_weight = self.read_transmitter_data_via_relay(1) - - # 如果无法读取重量,直接报错 - if initial_upper_weight is None: - raise Exception("无法读取上料斗重量传感器数据,下料操作终止") - - target_upper_weight = initial_upper_weight - self.single_batch_weight - target_upper_weight = max(target_upper_weight, 0) # 确保不低于0 - - print(f"上料斗初始重量: {initial_upper_weight:.2f}kg, 目标重量: {target_upper_weight:.2f}kg") - - # 确保下料斗出砼门关闭 - self.control_relay(self.DOOR_LOWER_2, 'close') - # 打开上料斗出砼门 - self.control_relay(self.DOOR_LOWER_1, 'open') - - # 等待物料流入下料斗,基于上料斗重量变化控制 - start_time = time.time() - timeout = 30 # 30秒超时 - - while time.time() - start_time < timeout: - current_upper_weight = self.read_transmitter_data_via_relay(1) - - # 如果无法读取重量,继续尝试 - if current_upper_weight is None: - print("无法读取上料斗重量,继续尝试...") - time.sleep(1) - continue - - print(f"上料斗当前重量: {current_upper_weight:.2f}kg") - - # 如果达到目标重量,则关闭上料斗出砼门 - if current_upper_weight <= target_upper_weight + 50: # 允许50kg的误差范围 - print(f"达到目标重量,当前重量: {current_upper_weight:.2f}kg") - break - elif time.time() - start_time > 25: # 如果25秒后重量变化过小 - weight_change = initial_upper_weight - current_upper_weight - if weight_change < 100: # 如果重量变化小于100kg - print("重量变化过小,可能存在堵塞,交由监控系统处理...") - # 不再在这里直接处理破拱,而是依靠监控系统处理 - break - - time.sleep(1) - - # 关闭上料斗出砼门 - self.control_relay(self.DOOR_LOWER_1, 'close') - - # 验证下料结果 - final_upper_weight = self.read_transmitter_data_via_relay(1) - if final_upper_weight is not None: - actual_transferred = initial_upper_weight - final_upper_weight - print(f"实际下料重量: {actual_transferred:.2f}kg") - - # 增加上料计数 - self.upper_feeding_count += 1 - print("上料斗下料完成") - - # 关闭上料斗出砼门 - self.control_relay(self.DOOR_LOWER_1, 'close') - - # 验证下料结果 - final_upper_weight = self.read_transmitter_data_via_relay(1) - if final_upper_weight is not None: - actual_transferred = initial_upper_weight - final_upper_weight - print(f"实际下料重量: {actual_transferred:.2f}kg") - - # 增加上料计数 - self.upper_feeding_count += 1 - print("上料斗下料完成") - - def wait_for_vehicle_alignment(self): - """等待模具车对齐""" - print("等待模具车对齐...") - self.lower_feeding_stage = 4 - - while self.lower_feeding_stage == 4 and self._running: - if self.vehicle_aligned: - print("模具车已对齐,开始下料") - self.lower_feeding_stage = 1 - self.feeding_stage_one() - break - time.sleep(self.alignment_check_interval) - - def feeding_stage_one(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("开始第一阶段下料:下料斗低速下料") - self.set_inverter_frequency_via_relay(self.frequencies[0]) - self.control_inverter_via_relay('start') - - # 确保上料斗出砼门关闭 - self.control_relay(self.DOOR_LOWER_1, 'close') - # 打开下料斗出砼门 - self.control_relay(self.DOOR_LOWER_2, 'open') - - start_time = time.time() - initial_weight = self.read_transmitter_data_via_relay(2) - if initial_weight is None: - print("无法获取初始重量,取消下料") - self.finish_feeding_process() # 直接结束整个流程,而不是当前批次 - return - - target_weight = initial_weight + self.single_batch_weight - - while self.lower_feeding_stage == 1: - current_weight = self.read_transmitter_data_via_relay(2) - if current_weight is None: - self.lower_weight_error_count += 1 - if self.lower_weight_error_count >= self.max_error_count: - print("下料斗传感器连续读取失败,停止下料") - self.finish_feeding_process() # 直接结束整个流程 - return - else: - self.lower_weight_error_count = 0 - - if (current_weight is not None and current_weight >= target_weight) or (time.time() - start_time) > 30: - self.lower_feeding_stage = 2 - self.feeding_stage_two() - break - time.sleep(2) - - def feeding_stage_two(self): - """第二阶段下料:下料斗向模具车下料(中速)""" - print("开始第二阶段下料:下料斗中速下料") - self.set_inverter_frequency_via_relay(self.frequencies[1]) - - # 保持下料斗出砼门打开 - self.control_relay(self.DOOR_LOWER_2, 'open') - # 确保上料斗出砼门关闭 - self.control_relay(self.DOOR_LOWER_1, 'close') - - start_time = time.time() - initial_weight = self.read_transmitter_data_via_relay(2) - if initial_weight is None: - print("无法获取初始重量,取消下料") - self.finish_feeding_process() # 直接结束整个流程 - return - - target_weight = initial_weight + self.single_batch_weight - - while self.lower_feeding_stage == 2: - current_weight = self.read_transmitter_data_via_relay(2) - if current_weight is None: - self.lower_weight_error_count += 1 - if self.lower_weight_error_count >= self.max_error_count: - print("下料斗传感器连续读取失败,停止下料") - self.finish_feeding_process() # 直接结束整个流程 - return - else: - self.lower_weight_error_count = 0 - - if (current_weight is not None and current_weight >= target_weight) or (time.time() - start_time) > 30: - self.lower_feeding_stage = 3 - self.feeding_stage_three() - break - time.sleep(2) - - def feeding_stage_three(self): - """第三阶段下料:下料斗向模具车下料(高速)""" - print("开始第三阶段下料:下料斗高速下料") - self.set_inverter_frequency_via_relay(self.frequencies[2]) - - # 保持下料斗出砼门打开 - self.control_relay(self.DOOR_LOWER_2, 'open') - # 确保上料斗出砼门关闭 - self.control_relay(self.DOOR_LOWER_1, 'close') - - start_time = time.time() - initial_weight = self.read_transmitter_data_via_relay(2) - if initial_weight is None: - print("无法获取初始重量,取消下料") - self.finish_feeding_process() # 直接结束整个流程 - return - - target_weight = initial_weight + self.single_batch_weight - - while self.lower_feeding_stage == 3: - current_weight = self.read_transmitter_data_via_relay(2) - if current_weight is None: - self.lower_weight_error_count += 1 - if self.lower_weight_error_count >= self.max_error_count: - print("下料斗传感器连续读取失败,停止下料") - self.finish_feeding_process() # 直接结束整个流程 - return - else: - self.lower_weight_error_count = 0 - - if (current_weight is not None and current_weight >= target_weight) or (time.time() - start_time) > 30: - self.lower_feeding_stage = 4 - self.finish_current_batch() - break - time.sleep(2) - - def finish_current_batch(self): - """完成当前批次下料""" - print("当前批次下料完成,关闭出砼门") - self.control_inverter_via_relay('stop') - self.control_relay(self.DOOR_LOWER_1, 'close') - self.control_relay(self.DOOR_LOWER_2, 'close') - - - # 增加三阶段下料轮次计数 - self.lower_feeding_cycle += 1 - - # 检查是否完成两轮三阶段下料(总共5吨) - if self.lower_feeding_cycle >= 2: - # 完成整个5吨下料任务 - print("完成两轮三阶段下料,5吨下料任务完成") - self.finish_feeding_process() - return - - # 如果只完成一轮三阶段下料,进行第二次上料 - print("第一轮三阶段下料完成,准备第二次上料") - # 上料斗第二次向下料斗下料 - try: - self.transfer_material_from_upper_to_lower() - except Exception as e: - print(f"第二次上料失败: {e}") - print("停止下料流程") - self.finish_feeding_process() # 出现严重错误时结束整个流程 - return - - # 继续等待当前模具车对齐(不需要重新等待对齐,因为是同一辆模具车) - print("第二次上料完成,继续三阶段下料") - self.lower_feeding_stage = 1 # 直接进入第一阶段下料 - self.feeding_stage_one() # 开始第二轮第一阶段下料 - - - def finish_feeding_process(self): - """完成整个下料流程""" - print("整个下料流程完成") - self.lower_feeding_stage = 0 - self.lower_feeding_cycle = 0 - self.upper_feeding_count = 0 - self.return_upper_door_to_default() - - def handle_overflow_control(self, overflow_detected, door_opening_large): - """处理溢料控制""" - if overflow_detected and door_opening_large: - print("检测到溢料且出砼门开口较大,调小出砼门") - self.control_relay(self.DOOR_LOWER_1, 'close') - time.sleep(0.1) - self.control_relay(self.DOOR_LOWER_1, 'open') - time.sleep(0.1) - - def is_lower_door_open(self): - """检查出砼门是否打开""" - return self.lower_feeding_stage in [1, 2] # 只有在下料阶段才认为门是打开的 - - def check_arch_blocking(self): - """检查是否需要破拱""" - current_time = time.time() - - # 检查下料斗破拱(只有在下料过程中才检查) - if self.lower_feeding_stage in [1, 2, 3]: # 在所有下料阶段检查 - lower_weight = self.read_transmitter_data_via_relay(2) - if lower_weight is not None: - # 检查重量变化是否过慢(小于0.1kg变化且时间超过10秒) - if (abs(lower_weight - self.last_lower_weight) < 0.1) and \ - (current_time - self.last_weight_time) > 10: - print("下料斗可能堵塞,启动破拱") - self.control_relay(self.BREAK_ARCH_LOWER, 'open') - time.sleep(2) - self.control_relay(self.BREAK_ARCH_LOWER, 'close') - - self.last_lower_weight = lower_weight - - # 检查上料斗破拱(在上料斗向下料斗下料时检查) - if (self.upper_door_position == 'over_lower' and - self.lower_feeding_stage in [0, 1, 2, 3, 4]): # 在任何阶段都可能需要上料斗破拱 - upper_weight = self.read_transmitter_data_via_relay(1) - if upper_weight is not None: - # 检查重量变化是否过慢(小于0.1kg变化且时间超过10秒) - if (abs(upper_weight - self.last_upper_weight) < 0.1) and \ - (current_time - self.last_weight_time) > 10: - print("上料斗可能堵塞,启动破拱") - self.control_relay(self.BREAK_ARCH_UPPER, 'open') - time.sleep(2) - self.control_relay(self.BREAK_ARCH_UPPER, 'close') - - self.last_upper_weight = upper_weight - - # 更新最后读取时间 - if (self.read_transmitter_data_via_relay(1) is not None or - self.read_transmitter_data_via_relay(2) is not None): - self.last_weight_time = current_time - - def monitor_system(self): - """监控系统状态""" - while self._running: - try: - self.check_upper_material_request() - self.check_arch_blocking() - time.sleep(1) - except Exception as e: - print(f"监控线程错误: {e}") - - def setup_camera_capture(self, camera_index=0): - """ - 设置摄像头捕获 - :param camera_index: USB摄像头索引或IP摄像头配置 - """ - try: - rtsp_url = f"rtsp://{self.camera_username}:{self.camera_password}@{self.camera_ip}:{self.camera_port}/streaming/channels/{self.camera_channel}01" - self.camera = cv2.VideoCapture(rtsp_url) - - if not self.camera.isOpened(): - print(f"无法打开网络摄像头: {rtsp_url}") - return False - print(f"网络摄像头初始化成功,地址: {rtsp_url}") - return True - except Exception as e: - print(f"摄像头设置失败: {e}") - return False - - def capture_current_frame(self): - """捕获当前帧并返回numpy数组""" - try: - if self.camera is None: - print("摄像头未初始化") - return None - - ret, frame = self.camera.read() - if ret: - return frame - else: - print("无法捕获图像帧") - return None - except Exception as e: - print(f"图像捕获失败: {e}") - return None - - def detect_overflow_from_image(self, image_array): - """通过图像检测是否溢料(接受numpy数组)""" - try: - # 检查模型是否已加载 - if self.overflow_model is None: - print("堆料检测模型未加载") - return False - - rois = load_global_rois(self.roi_file_path) - - if not rois: - print("没有有效的ROI配置") - return False - - if image_array is None: - print("输入图像为空") - return False - - crops = crop_and_resize(image_array, rois, 640) - for roi_resized, _ in crops: - final_class, _, _, _ = classify_image_weighted(roi_resized, self.overflow_model, threshold=0.4) - if "大堆料" in final_class or "小堆料" in final_class: - return True - - return False - except Exception as e: - print(f"溢料检测失败: {e}") - return False - - def detect_vehicle_alignment(self, image_array): - """通过图像检测模具车是否对齐(接受numpy数组)""" - try: - # 检查模型是否已加载 - if self.alignment_model is None: - print("对齐检测模型未加载") - return False - - if image_array is None: - print("输入图像为空") - return False - - # 直接使用模型进行推理 - results = self.alignment_model(image_array) - pared_probs = results[0].probs.data.cpu().numpy().flatten() - - # 类别0: 未对齐, 类别1: 对齐 - class_id = int(pared_probs.argmax()) - confidence = float(pared_probs[class_id]) - - # 只有当对齐且置信度>95%时才认为对齐 - if class_id == 1 and confidence > 0.95: - return True - return False - except Exception as e: - print(f"对齐检测失败: {e}") - return False - - def get_current_door_angle(self, image=None, image_path=None): - """ - 通过视觉系统获取当前出砼门角度 - :param image: 图像数组(numpy array) - :param image_path: 图片路径 - """ - try: - # 检查模型是否已加载 - if self.angle_model is None: - print("夹角检测模型未加载") - return None - - angle_deg, _ = predict_obb_best_angle( - model=self.angle_model, # 传递预加载的模型实例 - image=image, # 传递图像数组 - image_path=image_path # 或传递图像路径 - ) - return angle_deg - except Exception as e: - print(f"角度检测失败: {e}") - return None - - def alignment_check_loop(self): - """ - 模具车对齐检查循环 - """ - while self._running: - try: - # 只在需要检查对齐时才检查 - if self.lower_feeding_stage == 4: - current_frame = self.capture_current_frame() - if current_frame is not None: - self.vehicle_aligned = self.detect_vehicle_alignment(current_frame) - if self.vehicle_aligned: - print("检测到模具车对齐") - else: - print("模具车未对齐") - time.sleep(self.alignment_check_interval) - except Exception as e: - print(f"对齐检查循环错误: {e}") - time.sleep(self.alignment_check_interval) - - def visual_control_loop(self): - """ - 视觉控制主循环 - """ - - while self._running and self.visual_control_enabled: - try: - current_frame = self.capture_current_frame() - if current_frame is None: - print("无法获取当前图像,跳过本次调整") - time.sleep(self.visual_check_interval) - continue - - # 检测是否溢料 - overflow = self.detect_overflow_from_image(current_frame) - - # 获取当前角度 - current_angle = self.get_current_door_angle(image=current_frame) - - if current_angle is None: - print("无法获取当前角度,跳过本次调整") - time.sleep(self.visual_check_interval) - continue - - print(f"当前角度: {current_angle:.2f}°, 溢料状态: {overflow}, 控制模式: {self.angle_control_mode}") - - # 状态机控制逻辑 - if self.angle_control_mode == "normal": - # 正常模式 - if overflow and current_angle > self.angle_threshold: - # 检测到堆料且角度过大,进入角度减小模式 - print("检测到堆料且角度过大,关闭出砼门开始减小角度") - self.control_relay(self.DOOR_LOWER_2, 'close') - self.angle_control_mode = "reducing" - else: - # 保持正常开门 - self.control_relay(self.DOOR_LOWER_2, 'open') - - elif self.angle_control_mode == "reducing": - # 角度减小模式 - if current_angle <= self.target_angle + self.angle_tolerance: - # 角度已达到目标范围 - if overflow: - # 仍有堆料,进入维持模式 - print(f"角度已降至{current_angle:.2f}°,仍有堆料,进入维持模式") - self.angle_control_mode = "maintaining" - self.control_relay(self.DOOR_LOWER_2, 'open') # 先打开门 - else: - # 无堆料,恢复正常模式 - print(f"角度已降至{current_angle:.2f}°,无堆料,恢复正常模式") - self.control_relay(self.DOOR_LOWER_2, 'open') - self.angle_control_mode = "recovery" - - elif self.angle_control_mode == "maintaining": - # 维持模式 - 使用脉冲控制 - if not overflow: - # 堆料已消除,恢复正常模式 - print("堆料已消除,恢复正常模式") - self.control_relay(self.DOOR_LOWER_2, 'open') - self.angle_control_mode = "recovery" - else: - # 继续维持角度控制 - self.pulse_control_door_for_maintaining() - - elif self.angle_control_mode == "recovery":#打开夹爪的过程中又堆料了 - # 恢复模式 - 逐步打开门 - if overflow: - # 又出现堆料,回到角度减小模式 - print("恢复过程中又检测到堆料,回到角度减小模式") - self.angle_control_mode = "maintaining" - else: - # 堆料已消除,恢复正常模式 - print("堆料已消除,恢复正常模式") - self.control_relay(self.DOOR_LOWER_2, 'open') - self.angle_control_mode = "normal" - - self.last_angle = current_angle - time.sleep(self.visual_check_interval) - - except Exception as e: - print(f"视觉控制循环错误: {e}") - time.sleep(self.visual_check_interval) - - def pulse_control_door_for_maintaining(self): - """ - 用于维持模式的脉冲控制 - 保持角度在目标范围内 - """ - print("执行维持脉冲控制") - # 关门1秒 - self.control_relay(self.DOOR_LOWER_2, 'close') - time.sleep(1.0) - # 开门1秒 - self.control_relay(self.DOOR_LOWER_2, 'open') - time.sleep(1.0) - - def start_visual_control(self): - """ - 启动视觉控制线程 - """ - if not self.visual_control_enabled: - print("视觉控制未启用") - return - - print("启动视觉控制线程") - self._visual_control_thread = threading.Thread( - target=self.visual_control_loop, - daemon=True - ) - self._visual_control_thread.start() - return self._visual_control_thread - - def start_alignment_check(self): - """ - 启动模具车对齐检查线程 - """ - print("启动模具车对齐检查线程") - self._alignment_check_thread = threading.Thread( - target=self.alignment_check_loop, - daemon=True - ) - self._alignment_check_thread.start() - return self._alignment_check_thread - - def start(self): - """启动系统""" - if self._running: - print("系统已在运行") - return - print("启动控制系统") - self._running = True - self._monitor_thread = threading.Thread(target=self.monitor_system, daemon=True) - self._monitor_thread.start() - - def stop(self): - """停止系统""" - if not self._running: - print("系统未在运行") - return - print("停止控制系统") - self._running = False - if self._monitor_thread is not None: - self._monitor_thread.join() - if self._visual_control_thread is not None: - self._visual_control_thread.join() - if self._alignment_check_thread is not None: - self._alignment_check_thread.join() - if self.camera is not None: - self.camera.release() - print("控制系统已停止") - - -# 使用示例 -if __name__ == "__main__": - system = FeedingControlSystem(relay_host='192.168.0.18', relay_port=50000) - - # 设置角度控制参数 - system.set_angle_parameters( - target_angle=20.0, - min_angle=10.0, - max_angle=80.0, - threshold=60.0 - ) - - # 设置下料参数 - system.set_feeding_parameters( - target_vehicle_weight=5000, # 5吨 - upper_buffer_weight=500, # 0.5吨缓冲 - single_batch_weight=2500 # 每次下2.5吨 - ) - - # 设置摄像头配置 - system.set_camera_config( - camera_type="ip", - ip="192.168.1.51", - port=554, - username="admin", - password="XJ123456", - channel=1 - ) - - # 初始化摄像头 - if not system.setup_camera_capture(): - print("摄像头初始化失败") - exit(1) - - # 加载所有模型 - if not system.load_all_models(): - print("模型加载失败") - exit(1) - - # 启动系统监控 - system.start() - - # 启动视觉控制 - system.start_visual_control() - - # 启动对齐检查 - system.start_alignment_check() - - print("系统准备就绪,5秒后开始下料...") - time.sleep(5) - system.start_lower_feeding() # 启动下料流程 - - try: - while True: - time.sleep(1) - except KeyboardInterrupt: - print("收到停止信号") - except Exception as e: - print(f"系统错误: {e}") - finally: - system.stop() diff --git a/core/state.py b/core/state.py deleted file mode 100644 index d520eba..0000000 --- a/core/state.py +++ /dev/null @@ -1,109 +0,0 @@ -from PySide6.QtCore import Signal, QObject -import threading -from enum import IntEnum - -class SystemState(QObject): - """状态中以_开头的属性会发送到OPC通知,不需要的不要加_开头""" - state_updated=Signal(str,object) - def __init__(self): - super().__init__() - # - self._watched_props = [] - self.lock = threading.RLock() - - # 系统运行状态 - self.running = True - - # 上料斗控制相关 - self._upper_door_position = 'default' # default(在搅拌楼下接料), over_lower(在下料斗上方), returning(返回中) - # 是否破拱 - self._upper_is_arch_=False - self._upper_door_closed=True - self._upper_weight=0 - self._upper_volume=0.0 - - #下料斗状态想着 - self.lower_feeding_stage = 0 # 0:未下料, 1:第一阶段, 2:第二阶段, 3:第三阶段, 4:等待模具车对齐 - self._lower_is_arch_=False - self._lower_weight=0 - self._lower_angle=0.0 - - #模具车状态 - self._mould_weight=0 - self._mould_frequency=220 - self._mould_vibrate_status=0 #1振动中0未振动 - #记录模具开始振动的时间 - self.mould_vibrate_time=0 - - self.lower_feeding_cycle = 0 # 下料斗下料循环次数 - self.upper_feeding_count = 0 # 上料斗已下料次数 - self.upper_feeding_max = 2 #上料斗最大下料次数 - - # 重量相关 - self.last_upper_weight = 0 - self.last_lower_weight = 0 - self.last_weight_time = 0 - #需要下料的总重量 - self._mould_need_weight=0 - #完成下料的总重量 - self._mould_finish_weight=0 - - self.initial_upper_weight=0 - self.initial_lower_weight=0 - - - # 错误计数 - self.upper_weight_error_count = 0 - self.lower_weight_error_count = 0 - - # 视觉系统状态 - self.angle_control_mode = "normal" # 角度控制模式: normal, reducing, maintaining, recovery - self.overflow_detected = "0" # 堆料检测 - self.current_finish_status=False # 当前是否完成浇筑满 - self.door_opening_large = False # 夹角 - self.vehicle_aligned = False # 模具车是否对齐 - self.last_angle = None # 上次检测角度 - - #当前RFID的内容格式为 模块编号,分块号,尺寸规格,方量 - self.rfid_current=None - - #当前生产的管片 - self.current_artifact=None - #当前生产状态 - self._feed_status=FeedStatus.FNone - #每方重量 - self.density=2416.4 - # - self._watched_props = [k for k in self.__dict__ if k.startswith('_')] - - def __setattr__(self, name, value): - super().__setattr__(name, value) - if name in self._watched_props: - with self.lock: - public_name = name.lstrip('_') - self.state_updated.emit(public_name, value) - - -class FeedStatus(IntEnum): - #初始值 - FNone = 0 - # 检查模车(模车到位) - FCheckM = 1 - #RFID检测或匹配 - FRFID=2, - # 开始(管片待生产任务) - FApiCheck = 3 - # 检查盖板(盖板到位) - FCheckGB = 4 - # 上料到下料(上料斗到下料斗) - FUpperToLower=5 - #下料1 - FFeed1 = 6 - # 下料2 - FFeed2 = 7 - # 下料3 - FFeed3 = 8 - #完成(管片生产完成) - FFinished = 11 - - FFeed=12 \ No newline at end of file diff --git a/core/system copy.py b/core/system copy.py deleted file mode 100644 index f28e608..0000000 --- a/core/system copy.py +++ /dev/null @@ -1,288 +0,0 @@ -# core/system.py -import threading -import time -import cv2 -from core.state import SystemState -from hardware.relay import RelayController -from hardware.inverter import InverterController -from hardware.transmitter import TransmitterController -from hardware.RFID.rfid_service import rfid_service -from vision.camera import DualCameraController -from vision.detector import VisionDetector -from feeding.controller import FeedingController -from service.mould_service import app_web_service -from config.settings import app_set_config - - -class FeedingControlSystem: - def __init__(self): - self.state = SystemState() - - # 初始化硬件控制器 - self.relay_controller = RelayController( - host=app_set_config.relay_host, - port=app_set_config.relay_port - ) - - self.inverter_controller = InverterController(self.relay_controller) - self.transmitter_controller = TransmitterController(self.relay_controller) - - # 初始化视觉系统 - self.camera_controller = DualCameraController(app_set_config.camera_configs) - - self.vision_detector = VisionDetector() - - # 初始化RFID控制器 - self.rfid_controller = rfid_service( - host=app_set_config.rfid_host, - port=app_set_config.rfid_port - ) - # 初始化下料控制器 - self.feeding_controller = FeedingController( - self.relay_controller, - self.inverter_controller, - self.transmitter_controller, - self.vision_detector, - self.camera_controller, - self.rfid_controller, - self.state - ) - - # 线程管理 - self.monitor_thread = None - self.visual_control_thread = None - self.alignment_check_thread = None - self.lower_feeding_thread = None - self.led_thread = None - - def initialize(self): - """初始化系统""" - print("初始化控制系统...") - - # self.check_device_connectivity() - - # self.camera_controller.start_cameras() - # if not app_set_config.debug_feeding: - # 启动系统监控(要料,破拱)线程 - self.start_monitoring() - - # 启动视觉控制(角度、溢出)线程 - # self.start_visual_control() - - # 启动对齐检查线程 - self.start_alignment_check() - - # 启动下料线程 - self.start_lower_feeding() - #LED屏 - # self.start_led() - - print("控制系统初始化完成") - - def start_monitoring(self): - """启动系统监控""" - print('振动和要料监控线程启动') - self.monitor_thread = threading.Thread( - target=self._monitor_loop, - daemon=True, - name='monitor' - ) - self.monitor_thread.start() - - def _monitor_loop(self): - """监控循环""" - while self.state.running: - try: - # self.feeding_controller.check_upper_material_request() - self.feeding_controller.check_arch_blocking() - time.sleep(1) - except Exception as e: - print(f"监控线程错误: {e}") - - def start_visual_control(self): - """启动视觉控制""" - print('视觉控制线程启动') - self.visual_control_thread = threading.Thread( - target=self._visual_control_loop, - daemon=True, - name='visual_control' - ) - self.visual_control_thread.start() - - def _visual_control_loop(self): - """视觉控制循环""" - while self.state.running: - try: - # print('visual_control') - current_frame = self.camera_controller.get_single_latest_frame() - if current_frame is not None: - # 执行视觉控制逻辑 - self.feeding_controller.visual_control(current_frame) - time.sleep(app_set_config.visual_check_interval) - except Exception as e: - print(f"视觉控制循环错误: {e}") - time.sleep(app_set_config.visual_check_interval) - - def start_alignment_check(self): - """启动对齐检查""" - print('对齐检查线程启动') - self.alignment_check_thread = threading.Thread( - target=self._alignment_check_loop, - daemon=True, - name='align_check' - ) - self.alignment_check_thread.start() - - def _alignment_check_loop(self): - """对齐检查循环""" - loc_align_status=False - loc_before_status=None - while self.state.running: - try: - if self.state.lower_feeding_stage == 4: # 等待对齐阶段 - current_frame = self.camera_controller.get_single_latest_frame() - if current_frame is not None: - self.state.vehicle_aligned = self.alignment_check_status() - if self.state.vehicle_aligned: - # loc_count+=1 - print("检测到模具车对齐") - else: - print("模具车未对齐") - # time.sleep(app_set_config.alignment_check_interval) - # loc_align_status=self.alignment_check_status() - # if loc_align_status and not loc_before_status: - # print("模具车由未对齐到对齐") - # self.state.vehicle_aligned=True - # elif not loc_align_status and loc_before_status: - # print("模具车由对齐到未对齐") - # self.state.vehicle_aligned=False - - # if loc_before_status!=loc_align_status: - # loc_before_status=loc_align_status - - except Exception as e: - print(f"对齐检查循环错误: {e}") - finally: - time.sleep(app_set_config.alignment_check_interval) - - - def alignment_check_status(self)->bool: - """对齐检查循环""" - loc_aligned=False - loc_count=0 - for i in range(4): - try: - current_frame = self.camera_controller.get_single_latest_frame() - if current_frame is not None: - loc_aligned = self.vision_detector.detect_vehicle_alignment(current_frame) - if loc_aligned: - loc_count+=1 - print("检测到模具车对齐") - else: - loc_count=0 - print("模具车未对齐") - time.sleep(app_set_config.alignment_check_interval) - except Exception as e: - print(f"对齐检查循环错误: {e}") - time.sleep(app_set_config.alignment_check_interval) - - if loc_count>=3: - loc_aligned=True - else: - loc_aligned=False - return loc_aligned - - def start_lower_feeding(self): - """启动下料流程""" - self.lower_feeding_thread = threading.Thread( - target=self._start_lower_feeding, - name="Feeding", - daemon=True - ) - self.lower_feeding_thread.start() - - def _start_lower_feeding(self): - """启动下料流程""" - while self.state.running: - self.feeding_controller.start_feeding() - time.sleep(app_set_config.lower_feeding_interval) - - def start_led(self): - """启动LED流程""" - self.led_thread = threading.Thread( - target=self._start_led, - name="LED", - daemon=True - ) - self.led_thread.start() - - def _start_led(self): - """启动LED流程""" - 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 - led_info.UpperWeight=self.state._upper_weight - led_info.LowerWeight=self.state._lower_weight - led_info.VibrationFrequency=self.state._mould_frequency - - #发送到LED屏 - - time.sleep(app_set_config.led_interval) - - def check_device_connectivity(self) -> bool: - """检查关键设备连接状态""" - try: - # 检查网络继电器连接 - test_response = self.relay_controller.send_command(self.relay_controller.read_status_command) - if not test_response: - print("网络继电器连接失败") - return False - - # 检查变频器连接 - if not self.relay_controller.modbus_client.connect(): - print("无法连接到网络继电器Modbus服务") - return False - - # 尝试读取变频器一个寄存器(测试连接) - # test_result = self.relay_controller.modbus_client.read_holding_registers( - # address=0x00, - # count=1, - # slave=self.inverter_controller.config['slave_id'] - # ) - - # if isinstance(test_result, Exception): - # print("变频器连接测试失败") - # return False - - # 检查下料斗变送器连接 - test_weight = self.transmitter_controller.read_data(2) - if test_weight is None: - print("下料斗变送器连接失败") - return False - - self.relay_controller.modbus_client.close() - return True - except Exception as e: - print(f"设备连接检查失败: {e}") - return False - - def stop(self): - """停止系统""" - print("停止控制系统...") - self.state.running = False - - # 等待线程结束 - if self.monitor_thread: - self.monitor_thread.join() - if self.visual_control_thread: - self.visual_control_thread.join() - if self.alignment_check_thread: - self.alignment_check_thread.join() - if self.lower_feeding_thread: - self.lower_feeding_thread.join() - - # 释放摄像头资源 - self.camera_controller.release() - print("控制系统已停止") diff --git a/core/system.py b/core/system.py index 0e70505..25e3c22 100644 --- a/core/system.py +++ b/core/system.py @@ -27,62 +27,70 @@ class FeedingControlSystem: port=ini_manager.relay_port ) - self.inverter_controller = InverterController() self.transmitter_controller = TransmitterController(self.relay_controller) + + self.plc_service = OmronFinsPollingService(ini_manager.upper_plc_ip, ini_manager.upper_plc_port) + + # 初始化下料控制器 + self.feeding_controller = VisualCallback( + relay_controller=self.relay_controller, + transmitter_controller=self.transmitter_controller, + state=self.state + ) + + self.plc_service.register_data_callback(self.feeding_controller.on_plc_update) + #小屏修改过屏幕 self.vf_auto_mode=True # 初始化 OPC UA 客户端 self.opcua_client_feed = OpcuaClientFeed() - - # 初始化 RFID 控制器 - # self.rfid_controller = rfid_service( - # host=app_set_config.rfid_host, - # port=app_set_config.rfid_port - # ) - - # self.plc_service = OmronFinsPollingService(ini_manager.upper_plc_ip, ini_manager.upper_plc_port) - - # 初始化下料控制器 - self.feeding_controller = VisualCallback(self.state) - - # 初始化 OPC 队列监听线程 - self.opc_queue_thread = None # 线程管理 - self.monitor_thread = None - self.visual_control_thread = None - self.alignment_check_thread = None - self.lower_feeding_thread = None - self.led_thread = None + self.feed_thread = None + self.vf_thread = None + self.arch_thread = None + self.api_thread = None + self.pd_jbl_thread = None + # 初始化 OPC 队列监听线程,用于处理队列中的数据 + self.opc_queue_thread = None - def initialize(self): + def initialize(self)->bool: """初始化系统""" print("初始化控制系统...") - # self.check_device_connectivity() - # self.camera_controller.start_cameras() - # self.start_monitoring() - # 启动下料线程 - # self.start_lower_feeding() - # 启动OPC队列处理线程 - # self.opcua_client_feed.start() - - # self.start_opc_queue_thread() - - #启用API线程 - self.start_api_thread() - + #启用上料斗PLC + self.plc_service.start_polling(interval=2.0) + #启用下料线程 + self.start_feed_thread() + #启用变频器线程 self.start_vf_thread() - # self.feeding_controller.get_current_mould() - # self.feeding_controller._cur_mould_model.MouldCode='SHR2L1-5' - # self.feeding_controller.send_pd_data() + #启用破拱线程 + self.start_arch_thread() + #启用推送模型数据线程 + self.feeding_controller.start_visual_thread() + #启用API(对接PD API数据),线程 + self.start_api_thread() #启用派单线程 - # self.start_pd_thread() + self.start_pd_thread() + # 启动OPC队列处理线程,维护连接的断开重连等 + self.opcua_client_feed.start() + + self.start_opc_queue_thread() + print("控制系统初始化完成") - + return True + + def start_feed_thread(self): + "下料线程控制,主要控制下料斗(视觉控制)以及上料斗" + self.feed_thread = threading.Thread( + target=self.feeding_controller._run_feed, + daemon=True + ) + self.feed_thread.start() + def start_opc_queue_thread(self): - """启动OPC队列处理线程""" + """启动OPC队列处理线程(从控制系统中获取数据,通过OPC外发)""" print('启动OPC队列处理线程') self.opc_queue_thread = threading.Thread( target=self._process_opc_queue, @@ -102,7 +110,7 @@ class FeedingControlSystem: self.api_thread.start() def start_vf_thread(self): - """启动变频器控制线程""" + """启动变频器控制线程(控制变频器开始启动,以及频率变换)""" # print('启动API处理线程,从API获取未浇筑数据') self.vf_thread = threading.Thread( target=self._process_vf, @@ -157,7 +165,6 @@ class FeedingControlSystem: print(f"处理变频器数据时发生错误: {e}") time.sleep(2) - def _process_api_db(self): from service.mould_service import app_web_service """处理API队列中的数据""" @@ -296,41 +303,16 @@ class FeedingControlSystem: self.feeding_controller.shutdown() self.stop() - - def start_monitoring(self): - """启动系统监控""" + def start_arch_thread(self): + """启动系统监控和要料""" print('振动和要料监控线程启动') - self.monitor_thread = threading.Thread( - target=self._monitor_loop, + #启动振动线程 + self.arch_thread = threading.Thread( + target=self.feeding_controller._arch_loop, daemon=True, - name='monitor' - ) - self.monitor_thread.start() - - def _monitor_loop(self): - """监控循环""" - while self.state.running: - try: - # self.feeding_controller.check_upper_material_request() - self.feeding_controller.check_arch_blocking() - time.sleep(1) - except Exception as e: - print(f"监控线程错误: {e}") - - def start_lower_feeding(self): - """启动下料流程""" - self.lower_feeding_thread = threading.Thread( - target=self._start_lower_feeding, - name="Feeding", - daemon=True - ) - self.lower_feeding_thread.start() - - def _start_lower_feeding(self): - """启动下料流程""" - while self.state.running: - self.feeding_controller.start_feeding() - time.sleep(app_set_config.lower_feeding_interval) + name='arch' + ) + self.arch_thread.start() def check_device_connectivity(self) -> bool: """检查关键设备连接状态""" @@ -346,17 +328,6 @@ class FeedingControlSystem: print("无法连接到网络继电器Modbus服务") return False - # 尝试读取变频器一个寄存器(测试连接) - # test_result = self.relay_controller.modbus_client.read_holding_registers( - # address=0x00, - # count=1, - # slave=self.inverter_controller.config['slave_id'] - # ) - - # if isinstance(test_result, Exception): - # print("变频器连接测试失败") - # return False - # 检查下料斗变送器连接 test_weight = self.transmitter_controller.read_data(2) if test_weight is None: @@ -368,8 +339,7 @@ class FeedingControlSystem: except Exception as e: print(f"设备连接检查失败: {e}") return False - - + def start_pd_thread(self): """启动PD线程""" print('启动派单处理线程,从API获取未浇筑数据') @@ -401,7 +371,34 @@ class FeedingControlSystem: _start_time=None _isFinish=False time.sleep(5) - + + def start_led(self): + """启动LED流程""" + self.led_thread = threading.Thread( + target=self._start_led, + name="LED", + daemon=True + ) + self.led_thread.start() + + def _start_led(self): + """启动LED流程""" + from service.mould_service import app_web_service + + while self.state.running: + led_info = app_web_service.get_pouring_led() + if led_info: + if self.state.current_artifact.MouldCode==led_info.MouldCode: + led_info.RingTypeCode=self.state.current_artifact.RingTypeCode + led_info.UpperWeight=self.state._upper_weight + led_info.LowerWeight=self.state._lower_weight + led_info.VibrationFrequency=self.state._mould_frequency + + #发送到LED屏 + + time.sleep(app_set_config.led_interval) + + @property def _is_finish(self): """检查系统是否运行""" @@ -428,20 +425,21 @@ class FeedingControlSystem: self.state.running = False # 等待线程结束 - if self.monitor_thread: - self.monitor_thread.join() - if self.visual_control_thread: - self.visual_control_thread.join() - if self.alignment_check_thread: - self.alignment_check_thread.join() - if self.lower_feeding_thread: - self.lower_feeding_thread.join() if self.opc_queue_thread: self.opc_queue_thread.join() if self.vf_thread: self.vf_thread.join() if self.api_thread: self.api_thread.join() + if self.pd_jbl_thread: + self.pd_jbl_thread.join() + if self.feed_thread: + self.feed_thread.join() + if self.arch_thread: + self.arch_thread.join() + if self.plc_service: + self.plc_service.stop_polling() + self.feeding_controller.shutdown() # 释放摄像头资源 # self.camera_controller.release() print("控制系统已停止") diff --git a/core/system_state.py b/core/system_state.py index 96a50aa..6afc367 100644 --- a/core/system_state.py +++ b/core/system_state.py @@ -25,6 +25,7 @@ class SystemState: self._upper_door_closed=True self._upper_weight=0 self._upper_volume=0.0 + #下料比例变频 self.vf_frequencys=[{'radio':0,'fre':230},{'radio':0.3,'fre':230},{'radio':0.6,'fre':230}] #使用 @@ -32,55 +33,26 @@ class SystemState: self._mould_vibrate_status=False #True振动中False未振动 #记录模具开始振动的时间 self.mould_vibrate_time=0 - - #下料斗状态想着 - self.lower_feeding_stage = 0 # 0:未下料, 1:第一阶段, 2:第二阶段, 3:第三阶段, 4:等待模具车对齐 + #下料斗状态 self._lower_is_arch_=False self._lower_weight=0 - self._lower_angle=0.0 #模具车状态 self._mould_weight=0 - - - self.lower_feeding_cycle = 0 # 下料斗下料循环次数 - self.upper_feeding_count = 0 # 上料斗已下料次数 - self.upper_feeding_max = 2 #上料斗最大下料次数 - # 重量相关 - self.last_upper_weight = 0 - self.last_lower_weight = 0 - self.last_weight_time = 0 #需要下料的总重量 self._mould_need_weight=0 #完成下料的总重量 self._mould_finish_weight=0 - - self.initial_upper_weight=0 - self.initial_lower_weight=0 - - - # 错误计数 - self.upper_weight_error_count = 0 - self.lower_weight_error_count = 0 - + # 视觉系统状态 - self.angle_control_mode = "normal" # 角度控制模式: normal, reducing, maintaining, recovery self.overflow_detected = "0" # 堆料检测 - self.current_finish_status=False # 当前是否完成浇筑满 - self.door_opening_large = False # 夹角 - self.vehicle_aligned = False # 模具车是否对齐 - self.last_angle = None # 上次检测角度 - - #当前RFID的内容格式为 模块编号,分块号,尺寸规格,方量 - self.rfid_current=None #当前生产的管片 self.current_artifact=None #当前生产状态 self._feed_status=FeedStatus.FNone - #每方重量 - self.density=2416.4 + self.bll_artifact=ArtifactBll() self.bll_pdrecord=PDRecordBll() #记录正在生产code模具编号,status:2正生产3完成生成,weight:完成重量 diff --git a/db/messages.db b/db/messages.db index 0cf6ae7..420f04a 100644 Binary files a/db/messages.db and b/db/messages.db differ diff --git a/db/three.db b/db/three.db index 5177849..f57ab3f 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 new file mode 100644 index 0000000..a6bb6c3 Binary files /dev/null and b/db/three.db-shm differ diff --git a/db/three.db-wal b/db/three.db-wal new file mode 100644 index 0000000..abcfaf8 Binary files /dev/null and b/db/three.db-wal differ diff --git a/feeding/__init__.py b/feeding/__init__.py deleted file mode 100644 index 144cb4c..0000000 --- a/feeding/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# feeding/__init__.py -""" -下料控制模块 -包含下料流程控制和管理 -""" -from .process import FeedingProcess -from .controller import FeedingController - -__all__ = ['FeedingProcess', 'FeedingController'] diff --git a/feeding/__pycache__/__init__.cpython-39.pyc b/feeding/__pycache__/__init__.cpython-39.pyc deleted file mode 100644 index 386b49b..0000000 Binary files a/feeding/__pycache__/__init__.cpython-39.pyc and /dev/null differ diff --git a/feeding/__pycache__/controller.cpython-39.pyc b/feeding/__pycache__/controller.cpython-39.pyc deleted file mode 100644 index 311175d..0000000 Binary files a/feeding/__pycache__/controller.cpython-39.pyc and /dev/null differ diff --git a/feeding/__pycache__/process.cpython-39.pyc b/feeding/__pycache__/process.cpython-39.pyc deleted file mode 100644 index a48cb05..0000000 Binary files a/feeding/__pycache__/process.cpython-39.pyc and /dev/null differ diff --git a/feeding/controller.py b/feeding/controller.py deleted file mode 100644 index d89ee3d..0000000 --- a/feeding/controller.py +++ /dev/null @@ -1,1195 +0,0 @@ - -from pickle import FALSE -from cv2.gapi import ov -from core.system_state import FeedStatus,Upper_Door_Position,SystemState,Upper_PLC_Status -from hardware.relay import RelayController -from hardware.transmitter import TransmitterController -import time -import threading -from datetime import datetime -import logging -import queue -from hardware.upper_plc import OmronFinsPollingService -from vision.muju_cls.muju_utils import run_stable_classification_loop -from vision.camera_picture import save_camera_picture - -class FeedingController: - # 下料控制 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self, relay_controller, inverter_controller, - transmitter_controller,plc_service, state): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.relay_controller = relay_controller - self.inverter_controller = inverter_controller - self.transmitter_controller = transmitter_controller - self.plc_service = plc_service - self.state=state - - # 线程安全的参数传递 - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - - #diff参数 - self._is_processing_diff = threading.Lock() - self._new_data_diff = threading.Event() - self._current_diff=0 - self._current_diff_area=[] - self._is_diff_save=False - self._stop_event = threading.Event() - - # 添加下料斗门控制锁,防止两个线程同时控制 - self._door_control_lock = threading.Lock() - # 记录当前控制门的线程名称,用于调试 - self._current_controlling_thread = None - #是否启动后的第一个模具 - self._is_first_module=True - self.init_val() - # self._setup_logging_2() - #F块完成重量的70%,控制夹脚,F块多于这个比例就没有记录了(注意) - self._max_f_angle_ratio=0.7 - #完成多少,调整角度比例 ,多于0.8就没记录了(注意) - self._max_angle_radio=0.8 - - #重量大于95%,停留时间2秒,其他的1秒 - self._weight_ratio_955=0.955 - #完成多少,忽略未浇筑满 - self._max_ignore_radio=0.5 - - self._mould_accept_aligned=None - self._mould_before_aligned=False - #模具开始浇筑时间 - self._time_mould_begin='' - #模具结束浇筑时间 - self._time_mould_end='' - #重量判断为空次数 - self.max_weight_none=5 - #当前为空的次数 - self.cur_weight_none=0 - - self.state._mould_need_weight=1 - - - def start_feed_thread(self): - - self.plc_service.register_data_callback(self.on_plc_update) - # self.plc_service.register_status_callback(self.on_status_change) - self.plc_service.start_polling(interval=2.0) - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - self.feed_thread = threading.Thread( - target=self._run_feed, - daemon=True - ) - self.feed_thread.start() - - """启动系统监控""" - self.monitor_thread = threading.Thread( - target=self._monitor_loop, - daemon=True, - name='monitor' - ) - self.monitor_thread.start() - - - self.diff_thread = threading.Thread( - target=self._diff_temp, - daemon=True - ) - self.diff_thread.start() - - def init_val(self): - #初始化值 - """初始化视觉回调处理器""" - self.angle_mode = "normal" - self.overflow = False - self.is_start_visual=False - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - # 新增标志位:指示safe_control_lower_close是否正在执行 - self._is_safe_closing = False - - self._is_feed_start=True - #未浇筑满时间,用于确定是否进入未浇筑满 - self._before_finish_time=None - #进入未浇筑满状态标志位 - self._is_before_finish=False - #是否浇筑满标志位 - self._is_finish=False - - #用于保存diff标志位 - # self._is_diff_save=False - #用于判断当前判断是否对齐(diff) - self._is_diff_unaligned=False - - #浇筑完成比例(重量) - self._is_finish_ratio=0 - - #下料阶段,用于控制下料斗的振动阶段 - self._is_feed_stage=0 - self._feed_status=FeedStatus.FNone - #振动相关参数 - self._last_arch_one_weight=0 - self._last_arch_two_weight=0 - self._last_arch_three_weight=0 - self._last_arch_four_weight=0 - self._last_arch_five_weight=0 - self._last_arch_time=0 - #是否为F块 - self._is_small_f=None - #采集数据用,下料重量=之前下的重量+最后一阶段(下-->模具车)重量差 - #记录最后一次下料斗到模具车前的重量 - self._mould_finish_weight=0 - - #记录最后一次下料斗到车初始重量 - self._inital_finish_lweight=0 - #记录视觉停止下料时的重量(计算后面加了多少) - self._last_lower_weight=0 - - # 初始化控制间隔和堆料状态跟踪属性 - self._last_overflow_state = False - self._last_control_time = 0 - self._is_running=True - self._initialized = True - self.plc_data=None - - #用于下料阶段初始值 - self._first_lower_weight_one=0 - self._is_first_lower_one=True - self._first_lower_weight_two=0 - self._is_first_lower_two=True - self._first_lower_weight_three=0 - self._is_first_lower_three=True - - def angle_visual_callback(self, current_angle, overflow_detected, mould_aligned): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - #print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到推送数据") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - - if overflow_detected is not None: - # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到溢料:{overflow_detected}") - self._overflow_detected = overflow_detected - if current_angle is not None: - #print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到角度:{current_angle}") - self._current_angle = current_angle - if mould_aligned is not None: - #print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到对齐:{mould_aligned}") - self._mould_accept_aligned=mould_aligned - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - def diff_visual_callback(self, current_diff,current_area): - """ - 视觉模型diff回调 - """ - #print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到推送数据") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing_diff.acquire(blocking=False): - print("222回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - if current_diff is not None: - # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到diff:{current_diff}") - self._current_diff = current_diff - if current_area is not None: - # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到area:{current_area}") - self._current_diff_area = current_area - # 通知线程有新数据可用 - self._new_data_diff.set() - finally: - # 释放处理锁 - self._is_processing_diff.release() - - def _diff_temp(self): - """ - 接受视觉回调数据 - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - _temp_diff_count=0 - _temp_area_count=0 - _temp_diff_str2='' - _temp_area_str2='' - while not self._stop_event.is_set(): - # print('-----等待diff 数据------') - # 等待新数据可用 - self._new_data_diff.wait() - # 重置事件 - self._new_data_diff.clear() - #_is_diff_save是否完成此片 - if self._is_diff_save: - # print('-----进入diff 数据------') - #完成了此片,然后是对齐状态 - if not self._is_diff_unaligned: - # 处理数据 - # print('-----进入对齐数据------') - if self._current_diff is not None and self._current_diff_area is not None: - _timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - if _temp_diff_count<=10 and self._current_diff!=0: - _temp_diff_str=f"diff , {_timestamp} , {self._current_diff}\n" - _temp_diff_count+=1 - with open('weight.txt', 'a') as f: - f.write(_temp_diff_str+'\n') - # print('-----保存成功(diff 数据)------') - - if _temp_area_count<=10 and self._current_diff_area!=[]: - _temp_area_str=f"area , {_timestamp} , {str(self._current_diff_area)}\n" - _temp_area_count+=1 - with open('weight.txt', 'a') as f: - f.write(_temp_area_str+'\n') - # print('-----保存成功(area 数据)------') - if _temp_diff_count>=10 and _temp_area_count>=10: - self._is_diff_save=False - time.sleep(1) - continue - # else: - #变成了未对齐,拉起盖板后,重新计数 - # if _temp_diff_count>=10 and _temp_area_count>=10: - # _temp_diff_count=0 - # _temp_area_count=0 - # self._is_diff_save=False - # _temp_diff_str='' - # _temp_area_str='' - - self._current_diff=0 - self._current_diff_area=[] - _temp_diff_count=0 - _temp_area_count=0 - _temp_diff_str='' - _temp_area_str='' - time.sleep(1) - - def _monitor_loop(self): - """监控循环""" - while self._is_running: - try: - current_time = time.time() - # 检查下料斗破拱(只有在下料过程中才检查) - if self._is_feed_stage==1: # 下料斗--》模具车 - _arch_weight = self.transmitter_controller.read_data(2) - if _arch_weight is not None and _arch_weight>0: - # 检查重量变化是否过慢 - _weight_changed=abs(_arch_weight - self._last_arch_one_weight) - #_last_arch_one_weight默认为0,一开始就进入振动 - print(f'---------------第一阶段,重量变化:{_weight_changed}------------------') - if (_weight_changed< 200) and \ - (current_time - self._last_arch_time) >= 2: - self._last_arch_time = current_time - print('---------------------第一阶段振动5秒(小于200KG)-----------------') - self.relay_controller.control_arch_lower_open_sync(5) - self._last_arch_one_weight = _arch_weight - continue - self._last_arch_one_weight = _arch_weight - - elif self._is_feed_stage==2: #上料斗到下料斗,料多,谨慎振动 - _arch_weight = self.transmitter_controller.read_data(1) - if _arch_weight is not None and _arch_weight>0: - # 检查重量变化是否过慢 - _weight_changed=abs(_arch_weight - self._last_arch_two_weight) - print(f'---------------第二阶段,重量变化:{_weight_changed}------------------') - if (_weight_changed < 100) and \ - (current_time - self._last_arch_time) >= 2: - self._last_arch_time = current_time - print('---------------------第二阶段振动3秒-----------------') - self.relay_controller.control_arch_upper_open_sync(3) - self._last_arch_two_weight = _arch_weight - continue - self._last_arch_two_weight = _arch_weight - elif self._is_feed_stage==3: #第二次下料斗-》模具车 - _arch_weight = self.transmitter_controller.read_data(2) - if _arch_weight is not None and _arch_weight>0: - #刚开始不需要振动,料太多 - if self._last_arch_three_weight>0: - _weight_changed=abs(_arch_weight - self._last_arch_three_weight) - # 检查重量变化是否过慢 - print(f'---------------第三阶段,重量变化:{_weight_changed}------------------') - if (_weight_changed < 100) and \ - (current_time - self._last_arch_time) >= 2: - self._last_arch_time = current_time - print('---------------------第三阶段振动5秒(小于100KG)-----------------') - self.relay_controller.control_arch_lower_open_sync(5) - self._last_arch_three_weight = _arch_weight - continue - self._last_arch_three_weight = _arch_weight - - elif self._is_feed_stage==4: #上料斗--》下料斗 - _arch_weight = self.transmitter_controller.read_data(1) - if _arch_weight is not None and _arch_weight>0: - # 检查重量变化是否过慢 - _weight_changed=abs(_arch_weight - self._last_arch_four_weight) - print(f'---------------第二阶段,重量变化:{_weight_changed}------------------') - if (_weight_changed < 200) and \ - (current_time - self._last_arch_time) > 2: - self._last_arch_time = current_time - print('---------------------第四阶段振动5秒-----------------') - self.relay_controller.control_arch_upper_open_sync(5) - self._last_arch_four_weight = _arch_weight - continue - self._last_arch_four_weight = _arch_weight - elif self._is_feed_stage==5: #下料斗->模具车 - _arch_weight = self.transmitter_controller.read_data(2) - if _arch_weight is not None and _arch_weight>0: - if self._last_arch_five_weight>0: - _weight_changed=abs(_arch_weight - self._last_arch_five_weight) - print(f'---------------第五阶段,重量变化:{_weight_changed}------------------') - _min_arch_weight=20 - if self._is_finish_ratio= 2: - self._last_arch_time = current_time - print(f'---------------------第五阶段振动3秒(小于{_min_arch_weight}kg))-----------------') - self.relay_controller.control_arch_lower_open_sync(3) - self._last_arch_five_weight = _arch_weight - continue - self._last_arch_five_weight = _arch_weight - - # 更新最后读取时间 - self._last_arch_time = current_time - time.sleep(2) - except Exception as e: - print(f"监控线程错误: {e}") - - def _aligned_get_times(self,flag): - """ - 获取对齐,1为对齐,0为未对齐 - """ - _current_times=time.time() - _temp_aligned_count=0 - if flag==1: - while time.time()-_current_times<=2: - print(f'-------------{self._mould_accept_aligned}-----------------') - if self._mould_accept_aligned=='盖板对齐': - _temp_aligned_count=_temp_aligned_count+1 - else: - _temp_aligned_count=0 - print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板对齐,次数:{_temp_aligned_count}-----------------') - time.sleep(0.2) - self._mould_accept_aligned='' - if _temp_aligned_count>=8: - return True - else: - return False - elif flag==2: - while time.time()-_current_times<=5: - - if self._mould_accept_aligned=='盖板未对齐': - _temp_aligned_count=_temp_aligned_count+1 - else: - _temp_aligned_count=0 - print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板未对齐,次数:{_temp_aligned_count}-----------------') - - time.sleep(0.2) - - self._mould_accept_aligned='' - if _temp_aligned_count>=20: - self._is_diff_unaligned=True - return True - else: - self._is_diff_unaligned=False - return False - - def _no_aligned_diff(self): - """ - diff 未对齐检测 - """ - _current_times=time.time() - _temp_aligned_count=0 - while time.time()-_current_times<=1: - # print(f'-------------{self._mould_accept_aligned}-----------------') - if self._mould_accept_aligned=='盖板未对齐': - _temp_aligned_count=_temp_aligned_count+1 - else: - _temp_aligned_count=0 - # print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板对齐,次数:{_temp_aligned_count}-----------------') - time.sleep(0.2) - if _temp_aligned_count>=3: - return True - else: - return False - - def _run_thread_loop(self): - """ - 接受视觉回调数据 - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - self._is_feed_start=True - - if self.is_start_visual: - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.1) - - def _run_feed(self): - - while True: - # print("------------已启动----------------") - if self._is_feed_start: - - # if self.plc_data==5: - #_is_finish_ratio完成 比例,根据重量过滤一下 - - if self._is_first_module and self._overflow_detected=='未堆料': - #第一次打开 ,未堆料,检测对齐 - _is_aligned=self._aligned_get_times(1) - if _is_aligned: - print('------------进入第一块111111-------------') - self._is_first_module=False - self._mould_before_aligned=True - # self.is_start_visual=True - self.run_feed_all() - elif self._is_finish and self._is_finish_ratio>=0.7: - #后续流程--》检查到未对齐,--》后又对齐+未堆料 - print('------------------进入连续块检测------------------') - if self._mould_before_aligned: - #未对齐,检测对齐 - _is_not_aligned=self._aligned_get_times(2) - if _is_not_aligned: - #标志位 - self._mould_before_aligned=False - print('------------连续盖板未对齐-------------') - else: - _is_aligned=self._aligned_get_times(1) - if _is_aligned and self._overflow_detected=='未堆料': - print('------------连续盖板已对齐-------------') - self._mould_before_aligned=True - print('-----------进入连续块111111-----------') - # self.is_start_visual=True - if self._last_lower_weight>0: - _current_weight=self.transmitter_controller.read_data(2) - if _current_weight is not None: - with open('weight.txt', 'a') as f: - f.write(f"补料:{self._last_lower_weight-_current_weight}\n\n"+"="*32) - self.init_val() - self.run_feed_all() - - # else: - # print("-----------上料斗未就位----------------") - # print("---------3--上料斗未就位----------------") - - time.sleep(0.2) - - def safe_control_lower_close(self,duration=3): - """线程安全的下料斗关闭方法""" - thread_name = threading.current_thread().name - # print(f"[{thread_name}] 尝试关闭下料斗...") - # 设置标志位,指示正在执行安全关闭操作 - self._is_safe_closing = True - try: - with self._door_control_lock: - self._current_controlling_thread = thread_name - #print(f"[{thread_name}] 获得下料斗控制权,执行关闭操作") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self._current_controlling_thread = None - #print(f"[{thread_name}] 释放下料斗控制权") - finally: - # 无论成功失败,都要重置标志位 - self._is_safe_closing = False - - def close_lower_door_visual(self): - """关闭下料斗门""" - self.is_start_visual=False - time.sleep(0.5) - self.safe_control_lower_close() - - def _visual_close(self): - self.is_start_visual=False - self._is_finish=True - self._is_feed_stage=0 - print(f'--------进入关闭(浇筑满)-----------') - self.safe_control_lower_close(3) - print(f'--------关闭完成-----------') - # try: - # self.db_queue.put_nowait({ - # "f":self._is_small_f, - # "Status": 3 - # }) - # except queue.Full: - # print("数据库队列已满,无法添加数据") - #记录重量 - _current_weight=self.transmitter_controller.read_data(2) - if _current_weight is not None: - self._last_lower_weight=_current_weight - self.state._mould_finish_weight= self.state._mould_finish_weight+(self._inital_finish_lweight-_current_weight) - with open('weight.txt', 'a') as f: - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - if self._is_small_f: - f.write(f"{self._time_mould_begin},{timestamp},F,{self.state._mould_finish_weight}\n") - else: - f.write(f"{self._time_mould_begin},{timestamp},B,{self.state._mould_finish_weight}\n") - #开启保存diff - self._is_diff_save=True - - #保存图片 - save_camera_picture() - - def run_feed_all(self): - """ - 全流程下料:包括判断模具类型 - """ - _is_f= run_stable_classification_loop() - print(f'------------已判断出模具类型: {_is_f}-------------') - if _is_f is not None: - if _is_f=='模具车1': - self._is_small_f=True - print('-------------F块模具--------------') - print('-------------F块模具--------------') - print('-------------F块模具--------------') - self._time_mould_begin=datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.state._mould_need_weight=0.54*2416 - self.state._mould_finish_weight=0 - self.run_feed_f() - elif _is_f=='模具车2': - self._is_small_f=False - self._time_mould_begin=datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.state._mould_need_weight=1.91*2416 - self.state._mould_finish_weight=0 - self.run_feed() - print('-------------其他模具---------------') - - if self._is_small_f is None: - print('-----------未判断出模具类型--------------') - return - - def run_feed_f(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("--------------------开始下料(F块)--------------------") - # loc_relay=self.relay_controller - loc_mitter=self.transmitter_controller - - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-----f上料斗重量异常-----") - return - #初始下料斗重量 - self._inital_finish_lweight=initial_lower_weight - if initial_lower_weight>100: - if not self._is_finish: - self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-----f上料斗重量异常2-----") - return - self._is_feed_stage=5 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - self.cur_weight_none+=1 - if self.cur_weight_none>self.max_weight_none: - #如果重量连续5次为None,认为下料斗未就位,跳出循环 - print('------------f下到模具车,下料斗重量异常----------------') - print('------------f下到模具车,下料斗重量异常----------------') - self.close_lower_door_visual() - return - #视觉处理关闭,异常的话重量没有生效 - continue - self.cur_weight_none=0 - self.state._mould_finish_weight=initial_lower_weight-current_weight - self._is_finish_ratio=(self.state._mould_finish_weight)/self.state._mould_need_weight - print(f'------------已下料比例: {self._is_finish_ratio}-------------') - if self._is_finish_ratio>self._max_f_angle_ratio: - #关5秒 - #大于0.7后不再检测了,直接交给视觉控制夹脚 - # print(f'------------已下料比例: {self._is_finish_ratio}-------------') - break - - time.sleep(1) - # initial_lower_weight=_current_lower_weight - print(f'------------已下料(F): {self.state._mould_finish_weight}kg-------------') - print(f'------------已下料(F): {self.state._mould_finish_weight}kg-------------') - - print(f'------------已完成-------------') - - def run_feed(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("--------------------开始下料(普通块)--------------------") - loc_relay=self.relay_controller - loc_mitter=self.transmitter_controller - - if self._feed_status==FeedStatus.FFeed1: - self.is_start_visual=True - if self._is_first_lower_one: - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("---------------下料斗重量异常----------------") - return - self._first_lower_weight_one=initial_lower_weight - self._is_first_lower_one=False - - if self._first_lower_weight_one>100: - #下料斗的料全部下完 - self._is_feed_stage=1 - - current_weight = loc_mitter.read_data(2) - if current_weight is None: - self.cur_weight_none+=1 - if self.cur_weight_none>self.max_weight_none: - print("-----------下料斗重量异常(第一次下到模具车)--------------") - self.close_lower_door_visual() - return - #continue - self.cur_weight_none=0 - if current_weight<250 and current_weight>0: - self.close_lower_door_visual() - self._feed_status=FeedStatus.FFeed2 - time.sleep(1) - elif self._feed_status==FeedStatus.FFeed2: - # if self._is - _current_lower_weight=loc_mitter.read_data(2) - if _current_lower_weight is None: - print("-------下料斗重量异常---------") - return - self.state._mould_finish_weight=initial_lower_weight-_current_lower_weight - # initial_lower_weight=_current_lower_weight - print(f'------------已下料(第一次): {self.state._mould_finish_weight}kg-------------') - print(f'------------已下料(第一次): {self.state._mould_finish_weight}kg-------------') - - - self._is_feed_stage=0 - - while self.plc_data!=Upper_PLC_Status.PLC_ZDS_Finish: - print('------------上料斗未就位----------------') - print('------------上料斗未就位----------------') - time.sleep(1) - - if self.plc_data==Upper_PLC_Status.PLC_ZDS_Finish: - print(f'------------上料斗向下料斗转移(留3000KG)-------------') - #打开上料斗出砼门,开5就,开三分之一下 - - loc_relay.control_upper_open_sync(6) - self._is_feed_stage=2 - loc_time_count=1 - upper_open_time=time.time() - - while not self._is_finish: - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight is None: - self.cur_weight_none+=1 - if self.cur_weight_none>self.max_weight_none: - #如果重量连续5次为None,认为上料斗未就位,跳出循环 - print('------------第一次上到下,上料斗重量异常----------------') - print('------------第一次上到下,上料斗重量异常----------------') - loc_relay.control_upper_close_sync(5+loc_time_count) - return - continue - self.cur_weight_none=0 - _two_lower_weight=loc_mitter.read_data(2) - if _two_lower_weight is None: - _two_lower_weight=0 - if (current_upper_weight<3000 and current_upper_weight>0) or _two_lower_weight>3200: - #关5秒,loc_time_count多关一秒 - loc_relay.control_upper_close_sync(5+loc_time_count) - break - else: - if time.time()-upper_open_time>5: - if loc_time_count<6: - upper_open_time=time.time() - loc_relay.control_upper_open_sync(0.8) - loc_time_count=loc_time_count+0.8 - else: - time.sleep(0.5) - else: - loc_relay.control_upper_close_sync(6+loc_time_count) - - self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-------下料斗重量异常(第二次下料到模具车)---------") - return - self._is_feed_stage=3 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - self.cur_weight_none+=1 - if self.cur_weight_none>self.max_weight_none: - print("-------下料斗重量异常(第二次下料到模具车)---------") - self.close_lower_door_visual() - return - continue - self.cur_weight_none=0 - # second_finish_weight=initial_lower_weight-current_weight - if current_weight<250: - self.close_lower_door_visual() - break - time.sleep(1) - _current_lower_weight=loc_mitter.read_data(2) - if _current_lower_weight is None: - print("-------下料斗重量异常(第二次下到模)---------") - return - self.state._mould_finish_weight+=initial_lower_weight-_current_lower_weight - print(f'------------已下料(第二次): {self.state._mould_finish_weight}kg-------------') - print(f'------------已下料(第二次): {self.state._mould_finish_weight}kg-------------') - - self._is_feed_stage=0 - if self.plc_data==Upper_PLC_Status.PLC_ZDS_Finish: - #第二次上料斗向下料斗转移 - loc_relay.control_upper_open_sync(12) - loc_time_count=1 - upper_open_time=time.time() - upper_open_time_2=None - #第二次到下料斗还需要的量 - # initial_upper_weight=loc_mitter.read_data(1) - # start_time=None - self._is_feed_stage=4 - while not self._is_finish: - # print(f'------------上料斗向下料斗转移22222-------------') - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight is None: - self.cur_weight_none+=1 - if self.cur_weight_none>self.max_weight_none: - #如果重量连续5次为None,认为上料斗未就位,跳出循环 - print('------------第二次上到下,上料斗重量异常----------------') - print('------------第二次上到下,上料斗重量异常----------------') - loc_relay.control_upper_close_sync(15) - break - continue - self.cur_weight_none=0 - if (current_upper_weight<600 and current_upper_weight>0) or upper_open_time_2 is not None: - if upper_open_time_2 is None: - upper_open_time_2=time.time() - if current_upper_weight<400 or time.time()-upper_open_time_2>5: - loc_relay.control_arch_upper_open_async(5) - # loc_relay.control_arch_upper_open() - loc_relay.control_upper_open_sync(5) - # start_time=None - #5秒后关闭 - loc_relay.control_upper_close_after()#control_upper_close_sync(8+loc_time_count) - break - time.sleep(1) - else: - if time.time()-upper_open_time>2: - # if loc_time_count<6: - upper_open_time=time.time() - loc_relay.control_upper_open_sync(1.2) - loc_time_count=loc_time_count+1 - else: - time.sleep(0.5) - else: - loc_relay.control_upper_close_sync(15) - # time.sleep(0.4) - - #第三次下料斗转移到模具车 - if not self._is_finish: - self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - self._inital_finish_lweight=initial_lower_weight - if initial_lower_weight is None: - print("-------下料斗重量异常(第三次下到模具车)---------") - return - self._is_feed_stage=5 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - self.cur_weight_none+=1 - if self.cur_weight_none>self.max_weight_none: - #重量异常退出 - print('------------第三次下到模具车,下料斗重量异常----------------') - self.close_lower_door_visual() - return - continue - self.cur_weight_none=0 - second_finish_weight=initial_lower_weight-current_weight - self._is_finish_ratio=(second_finish_weight+self.state._mould_finish_weight)/self.state._mould_need_weight - print(f'------------已下料比例: {self._is_finish_ratio}-------------') - if self._is_finish_ratio>=1: - #关5秒 - # print(f'------------已下料比例: {self._is_finish_ratio}-------------') - break - - time.sleep(1) - - # _current_lower_weight=loc_mitter.read_data(2) - - - print(f'------------已完成-------------') - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 实时精细控制 - 基于PID思想,无固定间隔 - """ - try: - # 记录控制时间戳(用于微分计算,而非限制) - current_time = time.time() - # 确保所有PID相关属性都被正确初始化 - if not hasattr(self, '_last_control_time'): - self._last_control_time = current_time - if not hasattr(self, '_last_error'): - self._last_error = 0 - if not hasattr(self, '_error_integral'): - self._error_integral = 0 - - # print(f"{self.angle_mode}") - self.overflow = overflow_detected in ["大堆料", "小堆料"] - - if current_angle is None: - return - - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 角度11: {current_angle:.2f}°,{overflow_detected}") - - if overflow_detected == "未浇筑满" or self._is_before_finish: - if self._before_finish_time is None: - self._before_finish_time=current_time - self.safe_control_lower_close(3) - print('-----------------关闭(未浇筑满)--------------------') - # time.sleep(3) - else: - if overflow_detected=='浇筑满': - self._visual_close() - return - # print(f'--------已关闭已关闭-----------') - elif overflow_detected=="大堆料": - print(f'--------未浇筑满,大堆料-----------') - self._pulse_control('open',0.3) - time.sleep(0.3) - self._pulse_control('close',0.4) - print(f'--------比例:{self._is_finish_ratio}-----------') - if self._is_finish_ratio>= self._weight_ratio_955: - time.sleep(2) - else: - time.sleep(1) - self._is_before_finish=True - else: - # self._pulse_control('open',0.5) - # time.sleep(0.3) - # self._pulse_control('close',0.6) - # print(f'--------比例:{self._is_finish_ratio}-----------') - self._pulse_control('open',0.6) - time.sleep(0.3) - self._pulse_control('close',0.7) - if self._is_finish_ratio>= self._weight_ratio_955: - time.sleep(2) - else: - time.sleep(1) - self._is_before_finish=True - if self._is_finish_ratio<=self._max_ignore_radio: - #如果重量未达到最大忽略角度,需要跳出 - self._is_before_finish=False - return - elif overflow_detected == "浇筑满": - self._visual_close() - return - else: - self._before_finish_time=None - if self._is_finish_ratio>=self._max_angle_radio or (self._is_finish_ratio>self._max_f_angle_ratio and self._is_small_f): - if overflow_detected == "大堆料": - TARGET_ANGLE = 5.0 # 大堆料时控制在15度左右 - elif overflow_detected == "小堆料": - TARGET_ANGLE = 15.0 # 小堆料时控制在35度左右 - else: - TARGET_ANGLE = 35.0 # 12.25由25--》35 - else: - if self._is_feed_stage==1 or self._is_feed_stage==3: - #根据溢料状态动态调整目标角度 - if overflow_detected == "大堆料": - TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 - elif overflow_detected == "小堆料": - TARGET_ANGLE = 45.0 # 小堆料时控制在35度左右 - else: - TARGET_ANGLE = 55.0 # 未溢料时开到最大56度 - else: - #根据溢料状态动态调整目标角度 - if overflow_detected == "大堆料": - TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 - elif overflow_detected == "小堆料": - TARGET_ANGLE = 25.0 # 小堆料时控制在35度左右 - else: - TARGET_ANGLE = 45.0 # 未溢料时开到最大56度 - - # 确保目标角度在硬件范围内(5-56度) - TARGET_ANGLE = max(5.0, min(56.0, TARGET_ANGLE)) - - # PID控制参数 - KP = 0.2 # 比例系数 - KI = 0 # 积分系数 - KD = 0 # 微分系数 - # KP = 0.15 # 比例系数 - # KI = 0.008 # 积分系数 - # KD = 0.08 # 微分系数 - # if TARGET_ANGLE <= 25.0: - # KP, KI, KD = 0.18, 0.008, 0.08 # 小角度,强控制 - # elif TARGET_ANGLE <= 40.0: - # KP, KI, KD = 0.15, 0.01, 0.06 # 中角度 - # else: - # KP, KI, KD = 0.12, 0.012, 0.04 # 大角度,温和控制 - # 计算误差 - error = current_angle - TARGET_ANGLE - dt = current_time - self._last_control_time - - # 积分项(抗饱和) - self._error_integral += error * dt - self._error_integral = max(min(self._error_integral, 50), -50) # 积分限幅 - - # 微分项 - error_derivative = (error - self._last_error) / dt if dt > 0 else 0 - - # PID输出 - pid_output = (KP * error + KI * self._error_integral + KD * error_derivative) - print(f"📊 PID计算: 误差={error:.2f}°, 积分={self._error_integral:.2f}, " - f"微分={error_derivative:.2f}, 输出={pid_output:.2f}") - # 更新历史值 - self._last_error = error - self._last_control_time = current_time - - # 状态机 + PID控制 - - if self.angle_mode == "normal": - self._normal_mode_advanced(current_angle, pid_output,TARGET_ANGLE) - - elif self.angle_mode == "reducing": - self._reducing_mode_advanced(current_angle, pid_output, TARGET_ANGLE) - - elif self.angle_mode == "maintaining": - self._maintaining_mode_advanced(current_angle, pid_output, TARGET_ANGLE) - - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - def _normal_mode_advanced(self, current_angle, pid_output,target_angle): - """高级正常模式控制""" - if self.overflow: - self.angle_mode = "reducing" - print("检测到溢料,切换到减小模式") - return - - # 🎯 修复1: 添加强制控制机制 - - - # 基于PID输出的智能控制 - control_threshold = 2 # 从2.0减小到0.5,提高灵敏度 - - if abs(pid_output) > control_threshold: - if pid_output > 0: - # 需要减小角度(关门) - pulse_time = min(0.3, pid_output * 0.1) - self._pulse_control("close", pulse_time) - print(f"正常模式: 角度偏高{pid_output:.1f},关门{pulse_time:.2f}秒") - else: - # 需要增大角度(开门) - pulse_time = min(0.3, abs(pid_output) * 0.1) - self._pulse_control("open", pulse_time) - print(f"正常模式: 角度偏低{abs(pid_output):.1f},开门{pulse_time:.2f}秒") - else: - # 在死区内,保持静止 - error = current_angle - target_angle - abs_error = abs(error) - - # 强制控制:如果误差超过5度,强制控制 - if abs_error > 5: - if error > 0: # 当前角度 > 目标角度,需要关门 - pulse_time=0.1 # 根据误差计算脉冲时间 - self._pulse_control("close", pulse_time) - print(f"🚨 强制关门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") - else: # 当前角度 < 目标角度,需要开门 - pulse_time =0.1 - self._pulse_control("open", pulse_time) - print(f"🚨 强制开门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") - return - else: - self._stop_door() - print(f"正常模式: 角度在目标范围内,保持静止") - - def _reducing_mode_advanced(self, current_angle, pid_output, target_angle): - """高级减小模式控制""" - if not self.overflow: - if current_angle <= target_angle + 5.0: - self.angle_mode = "normal" - print("溢料消除且角度合适,返回正常模式") - else: - # 缓慢恢复 - self._pulse_control("close", 0.1) - return - - # 有溢料,积极减小角度 - if current_angle > target_angle: - # 使用PID输出计算控制量 - pulse_time = min(0.5, max(0.1, pid_output * 0.15)) - self._pulse_control("close", pulse_time) - print(f"减小模式: 积极关门{pulse_time:.2f}秒,PID输出:{pid_output:.1f}") - else: - self.angle_mode = "maintaining" - print("角度已达标,进入维持模式") - - def _maintaining_mode_advanced(self, current_angle, pid_output, target_angle): - """高级维持模式控制""" - if not self.overflow: - self.angle_mode = "normal" - print("溢料消除,返回正常模式") - return - - # 精确维持控制 - dead_zone = 1.5 # 更小的死区 - - if abs(pid_output) > dead_zone: - pulse_time = min(0.2, abs(pid_output) * 0.05) # 更精细的控制 - - if pid_output > 0: - self._pulse_control("close", pulse_time) - print(f"维持模式: 微调关门{pulse_time:.2f}秒") - else: - self._pulse_control("open", pulse_time) - print(f"维持模式: 微调开门{pulse_time:.2f}秒") - else: - self._stop_door() - print("维持模式: 角度精确控制中") - - def _pulse_control(self, action, duration): - """统一的脉冲控制方法""" - # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 - if self._is_safe_closing: - thread_name = threading.current_thread().name - print(f"[{thread_name}] safe_control_lower_close正在执行,跳过脉冲控制 {action}") - return - - if duration <= 0: - return - - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试脉冲控制 {action},时长 {duration:.2f}秒...") - - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行脉冲控制") - - if action == "open": - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - print(f"[{thread_name}] 开门脉冲: {duration:.2f}秒") - else: # close - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - print(f"[{thread_name}] 关门脉冲: {duration:.2f}秒") - - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - - def _stop_door(self): - """停止门运动""" - # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 - if self._is_safe_closing: - thread_name = threading.current_thread().name - print(f"[{thread_name}] safe_control_lower_close正在执行,跳过停止门运动操作") - return - - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试停止门运动...") - - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行停止操作") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - - def _open_door(self, duration=0.5): - """打开门""" - self._pulse_control("open", 0.3) - - def _close_door(self, duration=0.5): - """关闭门""" - self._pulse_control("close", 1) - - def on_plc_update(self,data: int, binary: str): - #4即将振捣室5振捣室 64即将搅拌楼 66到达搅拌楼 - # print(f"[数据回调] 数值: 0x{data:02X} | 十进制: {data:3d} | 二进制: {binary}") - self.plc_data=data - self.state.upper_door_position=data - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - - self._is_running=False - self._is_finish=True - self.is_start_visual=False - # #关闭下料斗 - # self.safe_control_lower_close() - if self.plc_service: - self.plc_service.stop_polling() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - - if self.feed_thread.is_alive(): - self.feed_thread.join(timeout=1.0) - - if self.monitor_thread.is_alive(): - self.monitor_thread.join(timeout=1.0) - - # self.relay_controller._close_lower_5s - - def __del__(self): - """析构函数,确保线程安全关闭""" - self.shutdown() - -# 创建默认实例 -# visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -# def angle_visual_callback(current_angle, overflow_detected): -# """ -# 兼容旧版本的函数调用方式 -# 将调用转发到默认实例的angle_visual_callback方法 -# """ -# visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) \ No newline at end of file diff --git a/feeding/process copy.py b/feeding/process copy.py deleted file mode 100644 index e33dd97..0000000 --- a/feeding/process copy.py +++ /dev/null @@ -1,167 +0,0 @@ -from enum import IntEnum -from core.state import FeedStatus -from service.mould_service import MouldService -from busisness.blls import ArtifactBll -from busisness.models import ArtifactInfoModel,ArtifactInfo -import time -from datetime import datetime -from hardware.RFID.rfid_service import rfid_service -from config.settings import app_set_config - -class FeedingProcess: - def __init__(self, relay_controller, inverter_controller, - transmitter_controller, vision_detector, - camera_controller, state): - self.relay_controller = relay_controller - self.artifact_bll = ArtifactBll() - self.mould_service = MouldService() - self.inverter_controller = inverter_controller - self.transmitter_controller = transmitter_controller - self.vision_detector = vision_detector - self.camera_controller = camera_controller - self.state = state - self.state._feed_status = FeedStatus.FNone - - #标志位用,是否是第一次运行 - self.is_first_flag=True - - def start_feeding(self): - loc_state=self.state - loc_state._upper_weight=self.transmitter_controller.read_data(1) - loc_state._lower_weight=self.transmitter_controller.read_data(2) - # loc_state._upper_volume=round(loc_state._upper_weight/self.state.density,1) - """开始生产管片""" - if loc_state._feed_status == FeedStatus.FNone: - return - elif loc_state._feed_status == FeedStatus.FCheckM: - print("---------------初始化数据------------------") - loc_state._mould_need_weight= 1200 - loc_state._feed_status = FeedStatus.FFeed - return - elif loc_state._feed_status == FeedStatus.FFeed: - print("----------------下料------------------") - loc_state.mould_vibrate_time=time.time() - loc_state._mould_frequency=app_set_config.frequencies[0] - loc_state._mould_vibrate_status=1 - #上料斗重量 - loc_state.initial_upper_weight=loc_state._upper_weight - #下料斗重量 - loc_state.initial_lower_weight=loc_state._lower_weight - self.feeding_stage(loc_state) - # if loc_state._mould_need_weight>loc_state.initial_lower_weight: - # self.transfer_material_from_upper_to_lower(loc_state,loc_state.initial_upper_weight,loc_state.initial_lower_weight) - # loc_state._feed_status = FeedStatus.FFeed1 - # else: - # loc_state._feed_status = FeedStatus.FFeed1 - return - elif loc_state._feed_status == FeedStatus.FFinished: - """完成当前批次下料""" - print("振捣完成") - print("关闭所有网络继电器") - self.relay_controller.close_all() - return - - def _start_feeding_stage(self): - """启动指定下料阶段""" - """开始分步下料""" - print("开始分步下料过程") - self.transfer_material_from_upper_to_lower() - - def transfer_material_from_upper_to_lower(self,loc_state,initial_upper_weight,initial_lower_weight): - - """target_upper_weight:转移后剩下的上料斗重量""" - # 如果低于单次,全部卸掉 - _max_lower_weight=app_set_config.max_lower_volume*loc_state.density - # if (initial_lower_weight+feed_weight>_max_lower_weight): - feed_weight=_max_lower_weight-initial_lower_weight - - target_upper_weight=initial_upper_weight-feed_weight - target_upper_weight = max(target_upper_weight, 0) - # 确保下料斗出砼门关闭,同步关5秒 - self.relay_controller.control_lower_close() - # 打开上料斗出砼门 - # self.relay_controller.control_upper_open() - #一直打开3秒 - self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'open') - time.sleep(3) - loc_state._upper_door_closed=False - # 等待物料流入下料斗,基于上料斗重量变化控制 - - start_time = time.time() - # timeout = 30 # 30秒超时 - - while loc_state.running: - # self.relay_controller.control_upper_open_sync() - self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'close') - current_upper_weight = self.transmitter_controller.read_data(1) - # 如果无法读取重量,继续尝试 - if current_upper_weight is None: - print("无法读取上料斗重量,继续尝试...") - time.sleep(1) - continue - - loc_state._upper_weight=current_upper_weight - loc_state._upper_volume=round(loc_state._upper_weight/self.state.density,1) - print(f"上料斗当前重量: {current_upper_weight:.2f}kg") - - # 如果达到目标重量,则关闭上料斗出砼门 - if current_upper_weight <= target_upper_weight + 50: # 允许50kg的误差范围 - print(f"达到目标重量,当前重量: {current_upper_weight:.2f}kg") - print(f"花费时间 {time.time() - start_time:.2f}秒") - break - # time.sleep(1) - # self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.2) - - loc_state._upper_door_closed=True - # 关闭上料斗出砼门d - self.relay_controller.control_upper_close() - - #测试用 - print("上料斗下料完成") - - def feeding_stage(self,loc_state): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("开始下料") - # self.relay_controller.control - first_finish_weight=0 - while True: - current_weight = loc_state._lower_weight - loc_state._mould_finish_weight=loc_state.initial_lower_weight-current_weight - first_finish_weight=loc_state._mould_finish_weight - if current_weight<100: - #关5秒 - self.relay_controller.control_lower_close() - break - - time.sleep(1) - - #打开上料斗出砼门 - self.relay_controller.control_upper_open_sync(5) - while True: - - if loc_state._upper_weight<3000: - #关5秒 - self.relay_controller.control_upper_close() - break - loc_state.initial_lower_weight=loc_state.lower_weight - while True: - current_weight = loc_state._lower_weight - loc_state._mould_finish_weight=first_finish_weight+loc_state.initial_lower_weight-current_weight - if current_weight<100: - #关5秒 - self.relay_controller.control_lower_close() - break - - time.sleep(1) - - - - - - - def return_upper_door_to_default(self): - """上料斗回到默认位置(搅拌楼下接料位置)""" - print("上料斗回到默认位置") - # self.relay_controller.control(self.relay_controller.UPPER_TO_JBL, 'open') - self.state._upper_door_position = 'default' diff --git a/feeding/process.py b/feeding/process.py deleted file mode 100644 index 5eeff0d..0000000 --- a/feeding/process.py +++ /dev/null @@ -1,359 +0,0 @@ - -from pickle import FALSE -from cv2.gapi import ov -from config.settings import app_set_config -from hardware.relay import RelayController -from hardware.transmitter import TransmitterController -import time -import threading -from datetime import datetime -import logging -import queue -from hardware.upper_plc import OmronFinsPollingService -from vision.muju_cls.muju_utils import run_stable_classification_loop -from vision.camera_picture import save_camera_picture - -class FeedingProcess: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self, relay_controller, inverter_controller, - transmitter_controller, state): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.relay_controller = relay_controller - self.inverter_controller = inverter_controller - self.transmitter_controller = transmitter_controller - - # 线程安全的参数传递 - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - - #diff参数 - self._is_processing_diff = threading.Lock() - self._new_data_diff = threading.Event() - self._current_diff=0 - self._current_diff_area=[] - self._is_diff_save=False - - - self._stop_event = threading.Event() - - # 添加下料斗门控制锁,防止两个线程同时控制 - self._door_control_lock = threading.Lock() - # 记录当前控制门的线程名称,用于调试 - self._current_controlling_thread = None - #是否启动后的第一个模具 - self._is_first_module=True - self.init_val() - # self._setup_logging_2() - #F块完成重量的70%,控制夹脚,F块多于这个比例就没有记录了(注意) - self._max_f_angle_ratio=0.7 - #完成多少,调整角度比例 ,多于0.8就没记录了(注意) - self._max_angle_radio=0.8 - - #重量大于95%,停留时间2秒,其他的1秒 - self._weight_ratio_955=0.955 - #完成多少,忽略未浇筑满 - self._max_ignore_radio=0.5 - - self._mould_accept_aligned=None - self._mould_before_aligned=False - #模具开始浇筑时间 - self._time_mould_begin='' - #模具结束浇筑时间 - self._time_mould_end='' - - - def run_feed_f(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - 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 - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-----f上料斗重量异常-----") - return - first_finish_weight=0 - self._finish_weight=first_finish_weight - self._inital_finish_lweight=initial_lower_weight - need_total_weight=0.54*2416 - if initial_lower_weight>100: - if not self._is_finish: - self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-----f上料斗重量异常2-----") - return - self._is_feed_stage=5 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - cur_weight_none+=1 - if cur_weight_none>max_weight_none: - #如果重量连续5次为None,认为下料斗未就位,跳出循环 - print('------------f下到模具车,下料斗重量异常----------------') - print('------------f下到模具车,下料斗重量异常----------------') - self.close_lower_door_visual() - return - #视觉处理关闭,异常的话重量没有生效 - continue - cur_weight_none=0 - first_finish_weight=initial_lower_weight-current_weight - self._is_finish_ratio=(first_finish_weight)/need_total_weight - print(f'------------已下料比例: {self._is_finish_ratio}-------------') - if self._is_finish_ratio>self._max_f_angle_ratio: - #关5秒 - #大于0.7后不再检测了,直接交给视觉控制夹脚 - # print(f'------------已下料比例: {self._is_finish_ratio}-------------') - break - - # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) - # initial_lower_weight=_current_lower_weight - print(f'------------已下料(F): {first_finish_weight}kg-------------') - print(f'------------已下料(F): {first_finish_weight}kg-------------') - - print(f'------------已完成-------------') - - def run_feed(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("--------------------开始下料(普通块)--------------------") - self._time_mould_begin=datetime.now().strftime("%Y-%m-%d %H:%M:%S") - loc_relay=self.relay_controller - loc_mitter=self.transmitter_controller - max_weight_none=5 - cur_weight_none=0 - - initial_lower_weight=loc_mitter.read_data(2) - # initial_upper_weight=loc_mitter.read_data(1) - if initial_lower_weight is None: - print("---------------下料斗重量异常----------------") - return - first_finish_weight=0 - need_total_weight=1.91*2416 - # start_time=None - self.is_start_visual=True - if initial_lower_weight>100: - #下料斗的料全部下完 - self._is_feed_stage=1 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - cur_weight_none+=1 - if cur_weight_none>max_weight_none: - print("-----------下料斗重量异常(第一次下到模具车)--------------") - self.close_lower_door_visual() - return - continue - cur_weight_none=0 - if current_weight<250 and current_weight>0: - self.close_lower_door_visual() - break - time.sleep(1) - _current_lower_weight=loc_mitter.read_data(2) - if _current_lower_weight is None: - print("-------下料斗重量异常---------") - return - first_finish_weight=initial_lower_weight-_current_lower_weight - # initial_lower_weight=_current_lower_weight - print(f'------------已下料(第一次): {first_finish_weight}kg-------------') - print(f'------------已下料(第一次): {first_finish_weight}kg-------------') - self._is_feed_stage=0 - - while self.plc_data!=5: - print('------------上料斗未就位----------------') - print('------------上料斗未就位----------------') - time.sleep(1) - - if self.plc_data==5: - print(f'------------上料斗向下料斗转移(留3000KG)-------------') - #打开上料斗出砼门,开5就,开三分之一下 - - loc_relay.control_upper_open_sync(6) - self._is_feed_stage=2 - loc_time_count=1 - upper_open_time=time.time() - - while not self._is_finish: - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight is None: - cur_weight_none+=1 - if cur_weight_none>max_weight_none: - #如果重量连续5次为None,认为上料斗未就位,跳出循环 - print('------------第一次上到下,上料斗重量异常----------------') - print('------------第一次上到下,上料斗重量异常----------------') - loc_relay.control_upper_close_sync(5+loc_time_count) - return - continue - cur_weight_none=0 - _two_lower_weight=loc_mitter.read_data(2) - if _two_lower_weight is None: - _two_lower_weight=0 - if (current_upper_weight<3000 and current_upper_weight>0) or _two_lower_weight>3200: - #关5秒,loc_time_count多关一秒 - loc_relay.control_upper_close_sync(5+loc_time_count) - break - else: - if time.time()-upper_open_time>5: - if loc_time_count<6: - upper_open_time=time.time() - loc_relay.control_upper_open_sync(0.8) - loc_time_count=loc_time_count+0.8 - else: - time.sleep(0.5) - else: - loc_relay.control_upper_close_sync(6+loc_time_count) - - self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-------下料斗重量异常(第二次下料到模具车)---------") - return - self._is_feed_stage=3 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - cur_weight_none+=1 - if cur_weight_none>max_weight_none: - print("-------下料斗重量异常(第二次下料到模具车)---------") - self.close_lower_door_visual() - return - continue - cur_weight_none=0 - # second_finish_weight=initial_lower_weight-current_weight - if current_weight<250: - self.close_lower_door_visual() - break - # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) - _current_lower_weight=loc_mitter.read_data(2) - if _current_lower_weight is None: - print("-------下料斗重量异常(第二次下到模)---------") - return - first_finish_weight=first_finish_weight+initial_lower_weight-_current_lower_weight - print(f'------------已下料(第二次): {first_finish_weight}kg-------------') - print(f'------------已下料(第二次): {first_finish_weight}kg-------------') - - self._is_feed_stage=0 - if self.plc_data==5: - #第二次上料斗向下料斗转移 - loc_relay.control_upper_open_sync(12) - loc_time_count=1 - upper_open_time=time.time() - upper_open_time_2=None - #第二次到下料斗还需要的量 - #loc_left_need_weight=need_total_weight-first_finish_weight - # initial_upper_weight=loc_mitter.read_data(1) - # start_time=None - self._is_feed_stage=4 - while not self._is_finish: - # print(f'------------上料斗向下料斗转移22222-------------') - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight is None: - cur_weight_none+=1 - if cur_weight_none>max_weight_none: - #如果重量连续5次为None,认为上料斗未就位,跳出循环 - print('------------第二次上到下,上料斗重量异常----------------') - print('------------第二次上到下,上料斗重量异常----------------') - loc_relay.control_upper_close_sync(15) - break - continue - cur_weight_none=0 - if (current_upper_weight<600 and current_upper_weight>0) or upper_open_time_2 is not None: - if upper_open_time_2 is None: - upper_open_time_2=time.time() - if current_upper_weight<400 or time.time()-upper_open_time_2>5: - loc_relay.control_arch_upper_open_async(5) - # loc_relay.control_arch_upper_open() - loc_relay.control_upper_open_sync(5) - # start_time=None - #5秒后关闭 - loc_relay.control_upper_close_after()#control_upper_close_sync(8+loc_time_count) - break - time.sleep(1) - else: - if time.time()-upper_open_time>2: - # if loc_time_count<6: - upper_open_time=time.time() - loc_relay.control_upper_open_sync(1.2) - loc_time_count=loc_time_count+1 - else: - time.sleep(0.5) - else: - loc_relay.control_upper_close_sync(15) - # time.sleep(0.4) - - #第三次下料斗转移到模具车 - if not self._is_finish: - self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - self._finish_weight=first_finish_weight - self._inital_finish_lweight=initial_lower_weight - if initial_lower_weight is None: - print("-------下料斗重量异常(第三次下到模具车)---------") - return - self._is_feed_stage=5 - while not self._is_finish: - current_weight = loc_mitter.read_data(2) - if current_weight is None: - cur_weight_none+=1 - if cur_weight_none>max_weight_none: - #重量异常退出 - print('------------第三次下到模具车,下料斗重量异常----------------') - self.close_lower_door_visual() - return - continue - cur_weight_none=0 - second_finish_weight=initial_lower_weight-current_weight - self._is_finish_ratio=(second_finish_weight+first_finish_weight)/need_total_weight - print(f'------------已下料比例: {self._is_finish_ratio}-------------') - if self._is_finish_ratio>=1: - #关5秒 - # print(f'------------已下料比例: {self._is_finish_ratio}-------------') - break - - # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) - - # _current_lower_weight=loc_mitter.read_data(2) - # first_finish_weight=first_finish_weight+initial_lower_weight-_current_lower_weight - # print(f'------------已下料: {first_finish_weight}kg-------------') - # print(f'------------已下料: {first_finish_weight}kg-------------') - - - print(f'------------已完成-------------') - - - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - - -# 创建默认实例 -# visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -# def angle_visual_callback(current_angle, overflow_detected): -# """ -# 兼容旧版本的函数调用方式 -# 将调用转发到默认实例的angle_visual_callback方法 -# """ -# visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) \ No newline at end of file diff --git a/feeding/process_bak.py b/feeding/process_bak.py deleted file mode 100644 index 8690307..0000000 --- a/feeding/process_bak.py +++ /dev/null @@ -1,450 +0,0 @@ -from enum import IntEnum -from core.state import FeedStatus -from service.mould_service import MouldService -from busisness.blls import ArtifactBll -from busisness.models import ArtifactInfoModel,ArtifactInfo -import time -from datetime import datetime -from hardware.RFID.rfid_service import rfid_service -from config.settings import app_set_config - -class FeedingProcess22: - def __init__(self, relay_controller, inverter_controller, - transmitter_controller, vision_detector, - camera_controller, state): - self.relay_controller = relay_controller - self.artifact_bll = ArtifactBll() - self.mould_service = MouldService() - self.inverter_controller = inverter_controller - self.transmitter_controller = transmitter_controller - self.vision_detector = vision_detector - self.camera_controller = camera_controller - self.state = state - self.state._feed_status = FeedStatus.FCheckM - - #标志位用,是否是第一次运行 - self.is_first_flag=True - - #RFID服务 - self.rfid_service=rfid_service() - # self.rfid_service.callback_signal.connect(self._rfid_callback) - self.rfid_flag_succ=False - self.rfid_data='' - # self.rfid_service.start_receiver(self._rfid_callback) - - - def _rfid_callback(self,status,data): - try: - if status==1: - #成功读取到RFID标签 - #检查标识是否符号要求 - if data: - loc_array=data.split(',') - if len(loc_array)==4: - if self.state.current_artifact is None or loc_array[0]!=self.state.current_artifact.MouldCode: - self.rfid_flag_succ=True - self.state.current_artifact={ - 'MouldCode':loc_array[0], - 'BlockNumber':loc_array[1], - 'SizeSpecification':loc_array[2], - 'BetonVolume':float(loc_array[3]) - } - print(f"RFID-生产模具车号:{loc_array[0]}") - else: - print("RFID-生产模具车号与当前模具车号一致") - else: - print("RFID标签格式错误") - print(f"成功读取到RFID标签:{data}") - else: - self.rfid_flag_succ=False - print("读取RFID标签失败") - except Exception as e: - print(f"RFID回调处理异常: {e}") - - #读取失败 - - - - def start_feeding(self): - loc_state=self.state - loc_state._upper_weight=self.transmitter_controller.read_data(1) - loc_state._lower_weight=self.transmitter_controller.read_data(2) - loc_state._upper_volume=round(loc_state._upper_weight/self.state.density,1) - """开始生产管片""" - if loc_state._feed_status == FeedStatus.FNone: - # loc_state._feed_status = FeedStatus.FCheckM - return - elif loc_state._feed_status == FeedStatus.FCheckM: - """开始生产管片""" - print("检查盖板对齐,") - loc_state.lower_feeding_stage = 4 - - self.rfid_flag_succ=False - # if app_set_config.debug_feeding: - # loc_state._feed_status = FeedStatus.FApiCheck - if self.state.vehicle_aligned: - loc_state._feed_status = FeedStatus.FApiCheck - print("检查模车") - return - elif loc_state._feed_status == FeedStatus.FApiCheck: - print("————————————————生产已开始————————————————————") - # time.sleep(2) - #模拟数据 - module_obj=ArtifactInfoModel() - module_obj.ArtifactID="GR2B13082624F" - module_obj.MouldCode="SHR2B2-4" - module_obj.BetonVolume=1.9 - loc_modules=[module_obj] - # loc_modules =self.mould_service.get_not_pour_artifacts() - if loc_modules and loc_modules[0].ArtifactID : - # 取第一个未浇筑的管片 - #后续放入队列处理 - - loc_module = loc_modules[0] - #API - loc_module.Source = 1 - loc_module.BeginTime=datetime.now() - - # self.artifact_bll.insert_artifact_task(loc_module) - loc_state.current_artifact = loc_module - loc_state._mould_need_weight=loc_module.BetonVolume*self.state.density - print(f"已获取到未浇筑的管片:{loc_module.MouldCode}") - # self.artifact_bll.finish_artifact_task(loc_state.current_artifact.ArtifactID,1.92) - loc_state._feed_status = FeedStatus.FCheckGB - else: - #未读取到AIP接口数据. - print("未获取到未浇筑的管片") - loc_artifacting_task=self.artifact_bll.get_artifacting_task() - if loc_artifacting_task: - loc_state.current_artifact = loc_artifacting_task - loc_state._mould_need_weight=loc_artifacting_task.BetonVolume*self.state.density - loc_state._feed_status = FeedStatus.FCheckGB - else: - loc_state.current_artifact = None - return - elif loc_state._feed_status == FeedStatus.FRFID: - print("检查RFID") - - #RFID格式:模具编号,分块号,尺寸规格,方量 - while loc_state.running: - if self.rfid_flag_succ: - loc_state._feed_status = FeedStatus.FCheckGB - break - else: - time.sleep(1) - return - elif loc_state._feed_status == FeedStatus.FCheckGB: - # print("检查盖板对齐,") - # time.sleep(5) - loc_state._feed_status = FeedStatus.FUpperToLower - #计算本次生产需要的总重量 - print(f"本次生产需要的总重量:{self.state._mould_need_weight}") - return - elif loc_state._feed_status == FeedStatus.FUpperToLower: - print("上料斗向下料斗转移") - #上料斗重量 - loc_state.initial_upper_weight=loc_state._upper_weight - #下料斗重量 - loc_state.initial_lower_weight=loc_state._lower_weight - #需要的总重量 - # loc_state._mould_need_weight=loc_state.current_artifact.BetonVolume*loc_state.density - if loc_state._mould_need_weight > loc_state.initial_upper_weight + loc_state.initial_lower_weight: - # 等待上料斗重量增加(多久不够报警,可能出现F块不足的情况) - print('重量不够,需要增加') - return - - if loc_state._mould_need_weight>loc_state.initial_lower_weight: - if self.state._upper_door_position != 'over_lower': - #是否需要等待上料斗下料,如果下料斗够重量,则不需要等待 - return - else: - # 需要等待上料斗下料 - # 最后一块进行尾数控制 - # 最后一块F块,前面多要0.25,0.3,F块直接下料(先多下0.3后续) - # loc_FWeight=0.3*loc_state.density - # loc_feed_weight=loc_state.need_total_weight-loc_state.initial_lower_weight-loc_FWeight - self.transfer_material_from_upper_to_lower(loc_state,loc_state.initial_upper_weight,loc_state.initial_lower_weight) - #完成了上料斗重量转移才进入下料斗 - #测试返回 - loc_state._feed_status = FeedStatus.FFeed1 - # loc_state._feed_status = FeedStatus.FNone - else: - loc_state._feed_status = FeedStatus.FFeed1 - - # time.sleep(10) - return - elif loc_state._feed_status == FeedStatus.FFeed1: - #下料 - # self._start_feeding_stage() - self.feeding_stage_one(loc_state) - print("下料1") - return - elif loc_state._feed_status == FeedStatus.FFeed2: - #上料 - # self._start_feeding_stage() - self.feeding_stage_two(loc_state) - print("下料2") - return - elif loc_state._feed_status == FeedStatus.FFeed3: - #下料 - # self._start_feeding_stage() - self.feeding_stage_three(loc_state) - print("下料3") - return - elif loc_state._feed_status == FeedStatus.FFinished: - """完成当前批次下料""" - print("当前批次下料完成,关闭出砼门") - - if loc_state.overflow_detected=="浇筑满": - - # self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - # self.relay_controller.control_upper_close() - #更新数据库状态 - # self.artifact_bll.finish_artifact_task(loc_state.current_artifact.ArtifactID,loc_state._mould_finish_weight/loc_state.density) - print("浇筑完成") - - # loc_state._feed_status = FeedStatus.FCheckM - # if loc_state.mould_vibrate_time>0: - # while True: - # if loc_state.mould_vibrate_time-time.time()>=5*60: - # self.inverter_controller.control('stop') - # loc_state._mould_vibrate_status=0 - # break - # time.sleep(1) - print("振捣完成") - return - - def _start_feeding_stage(self): - """启动指定下料阶段""" - """开始分步下料""" - print("开始分步下料过程") - self.transfer_material_from_upper_to_lower() - - def transfer_material_from_upper_to_lower(self,loc_state,initial_upper_weight,initial_lower_weight): - - """target_upper_weight:转移后剩下的上料斗重量""" - # 如果低于单次,全部卸掉 - _max_lower_weight=app_set_config.max_lower_volume*loc_state.density - # if (initial_lower_weight+feed_weight>_max_lower_weight): - feed_weight=_max_lower_weight-initial_lower_weight - - target_upper_weight=initial_upper_weight-feed_weight - target_upper_weight = max(target_upper_weight, 0) - # 确保下料斗出砼门关闭 - self.relay_controller.control_lower_close() - # 打开上料斗出砼门 - # self.relay_controller.control_upper_open() - self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'open') - time.sleep(3) - loc_state._upper_door_closed=False - # 等待物料流入下料斗,基于上料斗重量变化控制 - - start_time = time.time() - # timeout = 30 # 30秒超时 - - while loc_state.running: - # self.relay_controller.control_upper_open_sync() - self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'close') - current_upper_weight = self.transmitter_controller.read_data(1) - # 如果无法读取重量,继续尝试 - if current_upper_weight is None: - print("无法读取上料斗重量,继续尝试...") - time.sleep(1) - continue - loc_state._upper_weight=current_upper_weight - loc_state._upper_volume=round(loc_state._upper_weight/self.state.density,1) - print(f"上料斗当前重量: {current_upper_weight:.2f}kg") - - # 如果达到目标重量,则关闭上料斗出砼门 - if current_upper_weight <= target_upper_weight + 50: # 允许50kg的误差范围 - print(f"达到目标重量,当前重量: {current_upper_weight:.2f}kg") - print(f"花费时间 {time.time() - start_time:.2f}秒") - break - elif time.time() - start_time > 25: # 如果25秒后重量变化过小 - weight_change = initial_upper_weight - current_upper_weight - if weight_change < 100: # 如果重量变化小于100kg - #需要增加报警处理 - print("重量变化过小,可能存在堵塞") - time.sleep(1) - self.relay_controller.control(self.relay_controller.DOOR_UPPER_OPEN, 'open') - time.sleep(0.2) - - loc_state._upper_door_closed=True - # 关闭上料斗出砼门 - self.relay_controller.control_upper_close() - - #测试用 - print("上料斗下料完成") - - def wait_for_vehicle_alignment(self): - """等待模具车对齐""" - print("等待模具车对齐...") - self.state.lower_feeding_stage = 4 - - import time - while self.state.lower_feeding_stage == 4 and self.state.running: - if self.state.vehicle_aligned: - print("模具车已对齐,开始下料") - self.state.lower_feeding_stage = 1 - # self.feeding_stage_one() - break - time.sleep(app_set_config.alignment_check_interval) - - def feeding_stage_one(self,loc_state): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("开始第一阶段下料:下料斗低速下料") - if self.is_first_flag: - # self.inverter_controller.set_frequency(app_set_config.frequencies[0]) - # self.inverter_controller.control('start') - loc_state.mould_vibrate_time=time() - loc_state._mould_frequency=app_set_config.frequencies[0] - loc_state._mould_vibrate_status=1 - - # 确保上料斗出砼门关闭 - # self.relay_controller.control(self.relay_controller.DOOR_UPPER_CLOSE, 'close') - # 打开下料斗出砼门 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - loc_cur_weight = loc_state._lower_weight - if loc_cur_weight is None: - #报警处理 - print("无法获取初始重量,取消下料") - # self.finish_feeding_process() - return - loc_state.initial_lower_weight=loc_cur_weight - self.is_first_flag=False - - - start_time = time.time() - - current_weight = loc_state._lower_weight - if current_weight is None: - #报警处理 - print("无法获取当前重量,取消下料") - # self.finish_feeding_process() - return - loc_state._mould_finish_weight=loc_state.initial_lower_weight-current_weight - target_weight = loc_state._mould_need_weight/3 - # or (time.time() - start_time) > 30 - if (current_weight is not None and loc_state._mould_finish_weight >= target_weight): - loc_state._feed_status = FeedStatus.FFeed2 - loc_state.lower_feeding_stage = 2 - self.is_first_flag=True - return - else: - time.sleep(1) - - def feeding_stage_two(self,loc_state): - """第二阶段下料:下料斗向模具车下料(中速)""" - if self.is_first_flag: - print("开始第二阶段下料:下料斗中速下料") - # self.inverter_controller.set_frequency(app_set_config.frequencies[1]) - # self.inverter_controller.control('start') - # 保持下料斗出砼门打开 - # self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - # 确保上料斗出砼门关闭 - self.relay_controller.control_upper_close() - - loc_state._mould_frequency=app_set_config.frequencies[1] - loc_state._mould_vibrate_status=1 - # loc_cur_weight = self.transmitter_controller.read_data(2) - # if loc_cur_weight is None: - # #报警处理 - # print("无法获取初始重量,取消下料") - # # self.finish_feeding_process() - # return - # loc_state.initial_lower_weight=loc_cur_weight - self.is_first_flag=False - - start_time = time.time() - current_weight = loc_state._lower_weight - if current_weight is None: - #报警处理 - print("无法获取当前重量,取消下料") - # self.finish_feeding_process() - return - loc_state._mould_finish_weight=loc_state.initial_lower_weight-current_weight - target_weight = (loc_state._mould_need_weight/3)*2 - # or (time.time() - start_time) > 30 - if (current_weight is not None and loc_state._mould_finish_weight >= target_weight): - loc_state._feed_status = FeedStatus.FFeed3 - loc_state.lower_feeding_stage = 3 - self.is_first_flag=True - return - else: - time.sleep(1) - - def feeding_stage_three(self,loc_state): - """第三阶段下料:下料斗向模具车下料(高速)""" - if self.is_first_flag: - print("开始第三阶段下料:下料斗高速下料") - # self.inverter_controller.set_frequency(app_set_config.frequencies[2]) - # self.inverter_controller.control('start') - loc_state._mould_frequency=app_set_config.frequencies[2] - # 保持下料斗出砼门打开 - # self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - # 确保上料斗出砼门关闭 - self.relay_controller.control_upper_close() - self.is_first_flag=False - - current_weight = loc_state._lower_weight - if current_weight is None: - #报警处理 - print("无法获取当前重量,取消下料") - # self.finish_feeding_process() - return - loc_state._mould_finish_weight=loc_state.initial_lower_weight-current_weight - target_weight = loc_state._mould_need_weight - - if (current_weight is not None and loc_state._mould_finish_weight >= target_weight): - loc_state._feed_status = FeedStatus.FFinished - loc_state.lower_feeding_stage = 5 - self.is_first_flag=True - return - else: - time.sleep(1) - - def finish_current_batch(self): - """完成当前批次下料""" - print("当前批次下料完成,关闭出砼门") - # self.inverter_controller.control('stop') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_1, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_2, 'close') - - # 增加三阶段下料轮次计数 - self.state.lower_feeding_cycle += 1 - - # 检查是否完成两轮三阶段下料 - if self.state.lower_feeding_cycle >= self.state.upper_feeding_max: - print("完成两轮三阶段下料,5吨下料任务完成") - self.finish_feeding_process() - return - - # 如果只完成一轮三阶段下料,进行第二次上料 - print("第一轮三阶段下料完成,准备第二次上料") - # 上料斗第二次向下料斗下料 - try: - self.transfer_material_from_upper_to_lower() - except Exception as e: - print(f"第二次上料失败: {e}") - print("停止下料流程") - self.finish_feeding_process() # 出现严重错误时结束整个流程 - return - - # 继续等待当前模具车对齐(不需要重新等待对齐,因为是同一辆模具车) - print("第二次上料完成,继续三阶段下料") - self.state.lower_feeding_stage = 1 # 直接进入第一阶段下料 - self.feeding_stage_one() # 开始第二轮第一阶段下料 - - def finish_feeding_process(self): - """完成整个下料流程""" - print("整个下料流程完成") - self.state.lower_feeding_stage = 0 - self.state.lower_feeding_cycle = 0 - self.state.upper_feeding_count = 0 - # self.return_upper_door_to_default() - - def return_upper_door_to_default(self): - """上料斗回到默认位置(搅拌楼下接料位置)""" - print("上料斗回到默认位置") - self.relay_controller.control(self.relay_controller.UPPER_TO_JBL, 'open') - self.state._upper_door_position = 'default' diff --git a/hardware/relay.py b/hardware/relay.py index 1deda92..eca3895 100644 --- a/hardware/relay.py +++ b/hardware/relay.py @@ -3,7 +3,6 @@ import socket import binascii import time import threading -from pymodbus.client import ModbusTcpClient from pymodbus.exceptions import ModbusException from config.settings import app_set_config @@ -28,7 +27,7 @@ class RelayController: def __init__(self, host='192.168.250.62', port=50000): self.host = host self.port = port - self.modbus_client = ModbusTcpClient(host, port=port) + # self.modbus_client = ModbusTcpClient(host, port=port) #遥1 DO 7 左 DO8 右 角度 摇2:DO 15下 13上 12 启动振捣 14停止振捣 下料斗DO7开 D09关 # 继电器命令(原始Socket) self.relay_commands = { diff --git a/main.py b/main.py deleted file mode 100644 index 1048d54..0000000 --- a/main.py +++ /dev/null @@ -1,183 +0,0 @@ -# main.py -import time -from config.settings import app_set_config -from core.system import FeedingControlSystem -from hardware import relay -from hardware.relay import RelayController -from hardware.inverter import InverterController -from hardware.transmitter import TransmitterController -import threading -import time -import cv2 -import vision.visual_callback_1203 as angle_visual - - -def main(): - # 加载配置 - # 初始化系统 - # angle_visual.angle_visual_callback(2,'未堆料') - replay_controller=RelayController() - # transmitter_controller=TransmitterController(replay_controller) - # upper_weight=transmitter_controller.read_data(2) - # print(upper_weight) - - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - - #5-10度徘徊 - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(0.1) - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - #5-15度徘徊 - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.3) - replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - time.sleep(0.3) - replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(0.4) - replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - time.sleep(3) - replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.3) - replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - time.sleep(0.3) - replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(0.4) - replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - # replay_controller.close_all() - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - - - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - # time.sleep(0.4) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(0.4) - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(4) - # while True: - # # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - # time.sleep(2) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.1) - - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - - # time.sleep(3) - - - - # inverter_controller=InverterController(replay_controller) - # inverter_controller.control('start') - - # inverter_controller.control('stop') - - - - # return - # replay_controller.control_upper_open() - #3秒开关 - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(1) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(1) - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - - # while True: - # time.sleep(1) - - # replay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - - # system = FeedingControlSystem() - - # system.state.vehicle_aligned=True - #假设在振捣室 - # system.state._upper_door_position='over_lower' - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # system.initialize() - - # system.state._feed_status=FeedStatus.FCheckM - - # cv2.namedWindow("控制系统", cv2.WND_PROP_VISIBLE) - # cv2.setWindowProperty("控制系统", cv2.WND_PROP_VISIBLE, 0) - # while True: - # key = cv2.waitKey(100) & 0xFF - # if key == ord('q') or key == 27: # 'q'键或ESC键 - # print("接收到退出信号,正在关闭系统...") - # break - - # time.sleep(1) # 减少CPU占用 - - # replay_controller.close_all() - - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_CLOSE, 'close') - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(5) - # system.relay_controller.control_upper_close() - - - # system.relay_controller.control(system.relay_controller, 'close') - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - - # system.camera_controller.start_cameras() - # loc_all_close=True - # if loc_all_close: - # relay=RelayController() - # relay.control(relay.UPPER_TO_JBL, 'close') - # relay.control(relay.UPPER_TO_ZD, 'close') - # relay.control(relay.DOOR_LOWER_OPEN, 'close') - # relay.control(relay.DOOR_LOWER_CLOSE, 'close') - # relay.control(relay.DOOR_UPPER_OPEN, 'close') - # relay.control(relay.DOOR_UPPER_CLOSE, 'close') - # relay.control(relay.BREAK_ARCH_UPPER, 'close') - # relay.control(relay.BREAK_ARCH_LOWER, 'close') - - - # time.sleep(2) - # system._alignment_check_loop() - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'close') - - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_CLOSE, 'close') - - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(5) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'close') - # system._visual_control_loop() - # system.transmitter_controller.test_upper_weight=2*2500 - # system.transmitter_controller.test_lower_weight=1000 - # system.state.vehicle_aligned=True - # # 启动调整线程 - # weight_thread = threading.Thread(target=adjust_weights, args=(system,), daemon=True) - - # weight_thread.start() - # system.state._upper_door_position='over_lower' - # system._start_lower_feeding() - - - -if __name__ == "__main__": - main() diff --git a/main2.py b/main2.py deleted file mode 100644 index 371555e..0000000 --- a/main2.py +++ /dev/null @@ -1,133 +0,0 @@ -# main.py -import time -from config.settings import app_set_config -from core.system import FeedingControlSystem -from hardware import relay -from hardware.relay import RelayController -import threading -import time -import cv2 -import os -from core.state import FeedStatus - - -def main(): - system = FeedingControlSystem() - system.relay_controller.close_all() - # 启动视觉控制 - # system.camera_controller.start_cameras() - # system.start_visual_control() - system.state._feed_status = FeedStatus.FCheckM - system.start_lower_feeding() - system.state.overflow_detected='未堆料' - system.initialize() - while True: - time.sleep(5) - - - # time.sleep(1) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - - # system.vision_detector.detect_angle() - # while True: - # system.feeding_controller.pulse_control_door_for_maintaining() - # time.sleep(5) - # system.state.vehicle_aligned=True - #假设在 fertilize room - # system.state._upper_door_position='over_lower' - # while True: - # system.feeding_controller.pulse_control_door_for_maintaining() - # time.sleep(4) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - - # 加载配置 - # 初始化系统 - - - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(1) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - # replay_controller.control_upper_open() - - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(5) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - - # image=cv2.imread(os.path.join(app_set_config.project_root,'test.jpeg')) - # image=cv2.flip(image, 0) - # cv2.imshow('test',image) - # cv2.waitKey(1) - - # replay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # system.state._feed_status=FeedStatus.FCheckM - - # cv2.namedWindow("控制系统", cv2.WND_PROP_VISIBLE) - # cv2.setWindowProperty("控制系统", cv2.WND_PROP_VISIBLE, 0) - # while True: - # key = cv2.waitKey(100) & 0xFF - # if key == ord('q') or key == 27: # 'q'键或ESC键 - # print("接收到退出信号,正在关闭系统...") - # break - - # time.sleep(1) # 减少CPU占用 - - # replay_controller.close_all() - - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_CLOSE, 'close') - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(5) - # system.relay_controller.control_upper_close() - - - # system.relay_controller.control(system.relay_controller, 'close') - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - - # system.camera_controller.start_cameras() - # loc_all_close=True - # if loc_all_close: - # relay=RelayController() - # relay.control(relay.UPPER_TO_JBL, 'close') - # relay.control(relay.UPPER_TO_ZD, 'close') - # relay.control(relay.DOOR_LOWER_OPEN, 'close') - # relay.control(relay.DOOR_LOWER_CLOSE, 'close') - # relay.control(relay.DOOR_UPPER_OPEN, 'close') - # relay.control(relay.DOOR_UPPER_CLOSE, 'close') - # relay.control(relay.BREAK_ARCH_UPPER, 'close') - # relay.control(relay.BREAK_ARCH_LOWER, 'close') - - - # time.sleep(2) - # system._alignment_check_loop() - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'close') - - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_CLOSE, 'close') - - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(5) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'close') - # system._visual_control_loop() - # system.transmitter_controller.test_upper_weight=2*2500 - # system.transmitter_controller.test_lower_weight=1000 - # system.state.vehicle_aligned=True - # # 启动调整线程 - # weight_thread = threading.Thread(target=adjust_weights, args=(system,), daemon=True) - - # weight_thread.start() - # system.state._upper_door_position='over_lower' - # system._start_lower_feeding() - - - -if __name__ == "__main__": - main() diff --git a/opc/opcua_server.py b/opc/opcua_server.py index 52988b7..bb3ea46 100644 --- a/opc/opcua_server.py +++ b/opc/opcua_server.py @@ -9,14 +9,14 @@ import time import random import threading from datetime import datetime -from core.system import SystemState -from config.ini_manager import ini_manager +# from core.system import SystemState +# from config.ini_manager import ini_manager class SimpleOPCUAServer: - def __init__(self, state, endpoint=ini_manager.opcua_endpoint, name="Feed_Server"): + # def __init__(self, state, endpoint=ini_manager.opcua_endpoint, name="Feed_Server"): + def __init__(self, endpoint='', name="Feed_Server"): """ 初始化OPC UA服务器 - Args: endpoint: 服务器端点地址 name: 服务器名称 @@ -24,7 +24,7 @@ class SimpleOPCUAServer: self.server = Server() self.server.set_endpoint(endpoint) self.server.set_server_name(name) - self.state = state + # self.state = state # 设置服务器命名空间 self.namespace = self.server.register_namespace("Feed_Control_System") @@ -38,46 +38,118 @@ class SimpleOPCUAServer: # 运行标志 self.running = False + # 订阅和监控项 + self.subscription = None + self.monitored_items = [] + + # 记录上次值用于检测变化 + self._last_values = {} + def create_object_structure(self): """创建OPC UA对象结构""" # 创建上料斗对象 self.upper = self.objects.add_object(self.namespace, "upper") self.lower=self.objects.add_object(self.namespace, "lower") self.sys=self.objects.add_object(self.namespace, "sys") + self.mould=self.objects.add_object(self.namespace, "mould") + self.pd=self.objects.add_object(self.namespace, "pd") # 创建变量 self.create_variables() def create_variables(self): """创建OPC UA变量""" - # 上料斗重量变量 - self.upper_weight = self.upper.add_variable(self.namespace, "upper_weight", 0.0) - self.lower_weight = self.lower.add_variable(self.namespace, "lower_weight", 0.0) + # 创建变量时显式指定数据类型和初始值 + #上料斗 + 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_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.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_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.upper_weight.set_writable(True) + self.lower_weight.set_writable(True) + self.upper_is_arch.set_writable(True) + self.upper_door_closed.set_writable(True) + self.upper_volume.set_writable(True) + self.upper_door_position.set_writable(True) + self.lower_is_arch.set_writable(True) + self.mould_finish_weight.set_writable(True) + self.mould_need_weight.set_writable(True) + self.mould_frequency.set_writable(True) + self.mould_vibrate_status.set_writable(True) + self.feed_status.set_writable(True) + self.pd_data.set_writable(True) - # 设置变量为可写 - # self.upper_weight.set_writable() - # self.lower_weight.set_writable() + print("[变量创建] 变量创建完成,AccessLevel权限已设置") - def setup_state_listeners(self): - """设置状态监听器 - 事件驱动更新""" - if hasattr(self.state, 'state_updated'): - self.state.state_updated.connect(self.on_state_changed) - print("状态监听器已设置 - 事件驱动模式") + # 验证并打印当前的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 on_state_changed(self, property_name, value): - """状态变化时的回调函数""" + def setup_variable_permissions(self): + """设置变量权限 - 在服务器启动后调用""" 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}") - + # 重新设置变量为可写,确保权限生效 + 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(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}") def start(self): """启动服务器""" @@ -85,15 +157,27 @@ class SimpleOPCUAServer: self.server.start() self.running = True print(f"服务器端点: opc.tcp://0.0.0.0:4840/freeopcua/server/") + print("=" * 60) + + # 【关键修复】在设置监听器之前,先设置变量权限 + # 这确保 AccessLevel 属性在客户端写入前已正确设置 + # self.setup_variable_permissions() + + print("=" * 60) + + # 设置客户端写入监听器 + # self.setup_write_listeners() + + print("=" * 60) # 初始化当前值 - if self.state: - self.upper_weight.set_value(self.state._upper_weight) - self.lower_weight.set_value(self.state._lower_weight) - print("已同步初始状态值") + # if self.state: + # self.upper_weight.set_value(self.state._upper_weight) + # self.lower_weight.set_value(self.state._lower_weight) + # print("已同步初始状态值") # 设置状态监听器 - 关键步骤! - self.setup_state_listeners() + # self.setup_state_listeners() # # 只有在没有状态系统时才使用模拟线程 # if not self.state: @@ -107,36 +191,35 @@ class SimpleOPCUAServer: def stop(self): """停止服务器""" + # 移除监听器 + # self.remove_write_listeners() + self.running = False self.server.stop() print("OPC UA服务器已停止") # 断开状态监听器 - if hasattr(self.state, 'state_updated'): - try: - self.state.state_updated.disconnect(self.on_state_changed) - except: - pass + # if hasattr(self.state, 'state_updated'): + # try: + # self.state.state_updated.disconnect(self.on_state_changed) + # except: + # pass def main(): """主函数""" # 创建系统状态实例 - state = SystemState() + # state = SystemState() # 创建并启动服务器 server = SimpleOPCUAServer( - state=state, endpoint="opc.tcp://0.0.0.0:4840/freeopcua/server/", name="工业自动化 OPC UA 服务器" ) try: - server.start() - - + server.start() print("服务器正在运行,按 Ctrl+C 停止...") - # 保持服务器运行 while True: time.sleep(1) diff --git a/opc/opcua_server_bak.py b/opc/opcua_server_bak.py new file mode 100644 index 0000000..52988b7 --- /dev/null +++ b/opc/opcua_server_bak.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python3 +""" +简单的OPC UA服务器示例 +用于工业自动化数据通信 +""" + +from opcua import Server, ua +import time +import random +import threading +from datetime import datetime +from core.system import SystemState +from config.ini_manager import ini_manager + +class SimpleOPCUAServer: + def __init__(self, state, endpoint=ini_manager.opcua_endpoint, name="Feed_Server"): + """ + 初始化OPC UA服务器 + + Args: + endpoint: 服务器端点地址 + name: 服务器名称 + """ + self.server = Server() + self.server.set_endpoint(endpoint) + self.server.set_server_name(name) + self.state = state + + # 设置服务器命名空间 + self.namespace = self.server.register_namespace("Feed_Control_System") + + # 获取对象节点 + self.objects = self.server.get_objects_node() + + # 创建自定义对象 + self.create_object_structure() + + # 运行标志 + self.running = False + + def create_object_structure(self): + """创建OPC UA对象结构""" + # 创建上料斗对象 + self.upper = self.objects.add_object(self.namespace, "upper") + self.lower=self.objects.add_object(self.namespace, "lower") + self.sys=self.objects.add_object(self.namespace, "sys") + + # 创建变量 + self.create_variables() + + def create_variables(self): + """创建OPC UA变量""" + # 上料斗重量变量 + self.upper_weight = self.upper.add_variable(self.namespace, "upper_weight", 0.0) + self.lower_weight = self.lower.add_variable(self.namespace, "lower_weight", 0.0) + + # 设置变量为可写 + # self.upper_weight.set_writable() + # self.lower_weight.set_writable() + + 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}") + + def start(self): + """启动服务器""" + try: + self.server.start() + self.running = True + print(f"服务器端点: opc.tcp://0.0.0.0:4840/freeopcua/server/") + + # 初始化当前值 + if self.state: + self.upper_weight.set_value(self.state._upper_weight) + self.lower_weight.set_value(self.state._lower_weight) + print("已同步初始状态值") + + # 设置状态监听器 - 关键步骤! + self.setup_state_listeners() + + # # 只有在没有状态系统时才使用模拟线程 + # if not self.state: + # print("使用模拟数据模式") + # self.simulation_thread = threading.Thread(target=self.simulate_data) + # self.simulation_thread.daemon = True + # self.simulation_thread.start() + + except Exception as e: + print(f"启动服务器失败: {e}") + + def stop(self): + """停止服务器""" + self.running = False + self.server.stop() + print("OPC UA服务器已停止") + + # 断开状态监听器 + if hasattr(self.state, 'state_updated'): + try: + self.state.state_updated.disconnect(self.on_state_changed) + except: + pass + + +def main(): + """主函数""" + # 创建系统状态实例 + state = SystemState() + + # 创建并启动服务器 + server = SimpleOPCUAServer( + state=state, + endpoint="opc.tcp://0.0.0.0:4840/freeopcua/server/", + name="工业自动化 OPC UA 服务器" + ) + + try: + server.start() + + + print("服务器正在运行,按 Ctrl+C 停止...") + + # 保持服务器运行 + while True: + time.sleep(1) + + except KeyboardInterrupt: + print("\n正在停止服务器...") + server.stop() + + except Exception as e: + print(f"服务器运行错误: {e}") + server.stop() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/opc/opcua_server_test.py b/opc/opcua_server_test.py deleted file mode 100644 index bb3ea46..0000000 --- a/opc/opcua_server_test.py +++ /dev/null @@ -1,237 +0,0 @@ -#!/usr/bin/env python3 -""" -简单的OPC UA服务器示例 -用于工业自动化数据通信 -""" - -from opcua import Server, ua -import time -import random -import threading -from datetime import datetime -# from core.system import SystemState -# from config.ini_manager import ini_manager - -class SimpleOPCUAServer: - # def __init__(self, state, endpoint=ini_manager.opcua_endpoint, name="Feed_Server"): - def __init__(self, endpoint='', name="Feed_Server"): - """ - 初始化OPC UA服务器 - Args: - endpoint: 服务器端点地址 - name: 服务器名称 - """ - self.server = Server() - self.server.set_endpoint(endpoint) - self.server.set_server_name(name) - # self.state = state - - # 设置服务器命名空间 - self.namespace = self.server.register_namespace("Feed_Control_System") - - # 获取对象节点 - self.objects = self.server.get_objects_node() - - # 创建自定义对象 - self.create_object_structure() - - # 运行标志 - self.running = False - - # 订阅和监控项 - self.subscription = None - self.monitored_items = [] - - # 记录上次值用于检测变化 - self._last_values = {} - - def create_object_structure(self): - """创建OPC UA对象结构""" - # 创建上料斗对象 - self.upper = self.objects.add_object(self.namespace, "upper") - self.lower=self.objects.add_object(self.namespace, "lower") - self.sys=self.objects.add_object(self.namespace, "sys") - self.mould=self.objects.add_object(self.namespace, "mould") - self.pd=self.objects.add_object(self.namespace, "pd") - - # 创建变量 - self.create_variables() - - def create_variables(self): - """创建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_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.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_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.upper_weight.set_writable(True) - self.lower_weight.set_writable(True) - self.upper_is_arch.set_writable(True) - self.upper_door_closed.set_writable(True) - self.upper_volume.set_writable(True) - self.upper_door_position.set_writable(True) - self.lower_is_arch.set_writable(True) - self.mould_finish_weight.set_writable(True) - self.mould_need_weight.set_writable(True) - self.mould_frequency.set_writable(True) - self.mould_vibrate_status.set_writable(True) - self.feed_status.set_writable(True) - self.pd_data.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}") - - def start(self): - """启动服务器""" - try: - self.server.start() - self.running = True - print(f"服务器端点: opc.tcp://0.0.0.0:4840/freeopcua/server/") - print("=" * 60) - - # 【关键修复】在设置监听器之前,先设置变量权限 - # 这确保 AccessLevel 属性在客户端写入前已正确设置 - # self.setup_variable_permissions() - - print("=" * 60) - - # 设置客户端写入监听器 - # self.setup_write_listeners() - - print("=" * 60) - - # 初始化当前值 - # if self.state: - # self.upper_weight.set_value(self.state._upper_weight) - # self.lower_weight.set_value(self.state._lower_weight) - # print("已同步初始状态值") - - # 设置状态监听器 - 关键步骤! - # self.setup_state_listeners() - - # # 只有在没有状态系统时才使用模拟线程 - # if not self.state: - # print("使用模拟数据模式") - # self.simulation_thread = threading.Thread(target=self.simulate_data) - # self.simulation_thread.daemon = True - # self.simulation_thread.start() - - except Exception as e: - print(f"启动服务器失败: {e}") - - def stop(self): - """停止服务器""" - # 移除监听器 - # self.remove_write_listeners() - - self.running = False - self.server.stop() - print("OPC UA服务器已停止") - - # 断开状态监听器 - # if hasattr(self.state, 'state_updated'): - # try: - # self.state.state_updated.disconnect(self.on_state_changed) - # except: - # pass - - -def main(): - """主函数""" - # 创建系统状态实例 - # state = SystemState() - - # 创建并启动服务器 - server = SimpleOPCUAServer( - endpoint="opc.tcp://0.0.0.0:4840/freeopcua/server/", - name="工业自动化 OPC UA 服务器" - ) - - try: - server.start() - print("服务器正在运行,按 Ctrl+C 停止...") - # 保持服务器运行 - while True: - time.sleep(1) - - except KeyboardInterrupt: - print("\n正在停止服务器...") - server.stop() - - except Exception as e: - print(f"服务器运行错误: {e}") - server.stop() - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/test.py b/test.py deleted file mode 100644 index de26e5b..0000000 --- a/test.py +++ /dev/null @@ -1,32 +0,0 @@ -import socket - -# 设备信息 -IP = "192.168.250.63" -PORT = 502 -TIMEOUT = 5 # 超时时间(秒) - -# 创建TCP socket -with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - try: - s.settimeout(TIMEOUT) # 设置超时,避免一直阻塞 - # 连接设备 - s.connect((IP, PORT)) - print(f"✅ 已通过TCP连接到 {IP}:{PORT}") - - # 尝试接收数据(不发送任何请求,纯等待) - print("等待设备发送数据...(若5秒内无响应则超时)") - data = s.recv(1024) # 最多接收1024字节 - - if data: - # 打印收到的原始数据(16进制和字节列表) - # print(f"收到数据(16进制):{data.hex()}") - print(f"收到数据(字节列表):{list(data)}") - else: - print("❌ 未收到任何数据(设备未主动发送)") - - except ConnectionRefusedError: - print(f"❌ 连接失败:{IP}:{PORT} 拒绝连接(设备离线/端口错误)") - except socket.timeout: - print(f"❌ 超时:{TIMEOUT}秒内未收到设备数据(设备未主动发送)") - except Exception as e: - print(f"❌ 发生错误:{str(e)}") \ No newline at end of file diff --git a/test_angle.py b/test_angle.py deleted file mode 100644 index 60f4d6b..0000000 --- a/test_angle.py +++ /dev/null @@ -1,193 +0,0 @@ -# main.py -import time -from config.settings import app_set_config -from core.system import FeedingControlSystem -from hardware import relay -from hardware.relay import RelayController -import threading -import time -import cv2 -import os -import sys -import io - - -class OutputRedirector(io.StringIO): - """ - 自定义输出重定向器,用于处理所有线程的输出 - 确保输入提示能在其他线程输出后重新显示,且保留用户当前输入 - """ - def __init__(self, original_stdout): - super().__init__() - self.original_stdout = original_stdout - self.input_prompt = "请输入新值:" - self.input_thread_active = False - self.current_input = "" # 跟踪用户当前输入 - self.lock = threading.Lock() - - def write(self, text): - with self.lock: - # 写入原始输出 - self.original_stdout.write(text) - self.original_stdout.flush() - - # 如果输入线程活跃,并且输出是换行符,重新显示输入提示和当前输入 - if self.input_thread_active and '\n' in text: - # 清除当前行并重新显示输入提示和用户当前输入 - self.original_stdout.write("\r" + " " * 100 + "\r") - self.original_stdout.write(f"{self.input_prompt}{self.current_input}") - self.original_stdout.flush() - - def flush(self): - with self.lock: - self.original_stdout.flush() - - -def main(): - # 加载配置 - # 初始化系统 - - - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(1) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - # replay_controller.control_upper_open() - - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(5) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - - # image=cv2.imread(os.path.join(app_set_config.project_root,'test.jpeg')) - # image=cv2.flip(image, 0) - # cv2.imshow('test',image) - # cv2.waitKey(1) - - # replay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - - system = FeedingControlSystem() - # system.vision_detector.detect_angle() - # while True: - # system.feeding_controller.pulse_control_door_for_maintaining() - # time.sleep(5) - - # 初始设置 - system.state.vehicle_aligned=True - #假设在 fertilize room - system.state._upper_door_position='over_lower' - - # 创建输出重定向器 - original_stdout = sys.stdout - output_redirector = OutputRedirector(original_stdout) - sys.stdout = output_redirector - - # 创建输入线程函数 - def input_thread(): - import sys - import threading - - # 初始提示只显示一次 - initial_prompt = """ - 输入线程启动提示 - -------------------------- - 可以随时输入新值来更新系统状态 - 格式:需求重量,完成重量,溢出状态 - 例如:500,300,大堆料 或 500,300,小堆料 或 500,300,未堆料 - 输入'q'退出程序 - -------------------------- - """ - print(initial_prompt) - - # 标记输入线程为活跃状态 - output_redirector.input_thread_active = True - - while True: - try: - import termios - import tty - - # 获取当前终端设置 - old_settings = termios.tcgetattr(sys.stdin) - try: - tty.setcbreak(sys.stdin.fileno()) # 设置为cbreak模式,允许逐字符读取 - - # 显示初始提示和当前输入 - sys.stdout.write("\r" + " " * 100 + "\r") - sys.stdout.write(f"{output_redirector.input_prompt}{output_redirector.current_input}") - sys.stdout.flush() - - while True: - char = sys.stdin.read(1) # 读取一个字符 - - if char == '\x03': # Ctrl+C - raise KeyboardInterrupt - elif char in ['\r', '\n']: # 回车键 - sys.stdout.write("\n") - sys.stdout.flush() - break - elif char == '\x7f': # 退格键 - if len(output_redirector.current_input) > 0: - output_redirector.current_input = output_redirector.current_input[:-1] - # 从显示中删除最后一个字符 - sys.stdout.write("\b \b") - sys.stdout.flush() - else: - output_redirector.current_input += char - sys.stdout.write(char) - sys.stdout.flush() - finally: - # 恢复终端设置 - termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) - - # 获取最终输入并重置当前输入 - user_input = output_redirector.current_input - output_redirector.current_input = "" # 重置为下一次输入准备 - - if user_input.lower() == 'q': - print("\n收到退出信号,正在关闭系统...") - output_redirector.input_thread_active = False - system.stop() - break - - # 分割输入值,处理不同参数数量 - input_parts = [part.strip() for part in user_input.split(',')] - if len(input_parts) >= 2: - # 更新基本参数 - system.state._mould_need_weight = float(input_parts[0]) - system.state._mould_finish_weight = float(input_parts[1]) - - if system.state._mould_finish_weight>400: - system.state.overflow_detected='大堆料' - - - # 输出更新结果,使用换行符分隔 - update_msg = f"\n已更新:\n 需求重量 = {system.state._mould_need_weight} kg\n 完成重量 = {system.state._mould_finish_weight} kg" - print(f"溢出状态 = {system.state.overflow_detected}") - print(update_msg) - else: - print("\n输入格式错误:至少需要输入需求重量和完成重量") - - except ValueError as e: - print(f"\n输入格式错误,请重新输入。错误信息:{e}") - except Exception as e: - print(f"\n发生错误:{e}") - output_redirector.input_thread_active = False - break - - # 启动输入线程 - input_thread = threading.Thread(target=input_thread, daemon=True) - input_thread.start() - system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # 启动视觉控制 - system.camera_controller.start_cameras() - system.start_visual_control() - - # 主线程保持运行 - while True: - # print(f'当前重量22:{system.state._mould_finish_weight:.2f}kg, 目标重量:{system.state._mould_need_weight:.2f}kg') - time.sleep(1) - - -if __name__ == "__main__": - main() diff --git a/test_dal_debug.py b/test_dal_debug.py deleted file mode 100644 index 0127847..0000000 --- a/test_dal_debug.py +++ /dev/null @@ -1,31 +0,0 @@ -from busisness.dals import ArtifactDal - -# 测试 exists_by_module_code 方法 -dal = ArtifactDal() - -# 测试1: 不存在的模具编号 -print("=== 测试1: 不存在的模具编号 ===") -result = dal.exists_by_module_code("不存在的模具编号") -print(f"结果: {result}") -print() - -# 测试2: 存在的模具编号(如果有数据的话) -print("=== 测试2: 存在的模具编号 ===") -result = dal.exists_by_module_code("TEST001") -print(f"结果: {result}") -print() - -# 测试3: 直接查询数据库验证 -print("=== 测试3: 直接SQL查询验证 ===") -db_dao = dal.db_dao -sql = "SELECT count(1) as cnt FROM ArtifactTask WHERE MouldCode = ?" -results = db_dao.execute_read(sql, ("不存在的模具编号",)) -print(f"查询结果类型: {type(results)}") -print(f"查询结果: {results}") -rows = list(results) -print(f"rows: {rows}") -if rows: - print(f"rows[0]: {rows[0]}") - print(f"rows[0][0]: {rows[0][0]}") -else: - print("rows为空列表") \ No newline at end of file diff --git a/test_door_close_startup.py b/test_door_close_startup.py deleted file mode 100644 index 7d90343..0000000 --- a/test_door_close_startup.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -测试脚本:模拟程序启动时门关闭的情况 -验证初始角度很小(门关闭)时的控制逻辑行为 -""" - -import time -from vision.visual_callback_1203 import VisualCallback -from datetime import datetime - -# 修改VisualCallback类,添加调试信息 -original_pulse_control = VisualCallback._pulse_control - -# 重写_pulse_control方法,添加详细调试信息 -def debug_pulse_control(self, action, duration): - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} [DEBUG] 准备执行 {action} 脉冲,持续 {duration:.2f} 秒") - result = original_pulse_control(self, action, duration) - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} [DEBUG] {action} 脉冲执行完成") - return result - -# 替换原始方法 -VisualCallback._pulse_control = debug_pulse_control - -def test_door_close_startup(): - """测试程序启动时门关闭的情况""" - print("=== 测试:程序启动时门关闭的情况 ===") - - # 创建VisualCallback实例(程序启动) - callback = VisualCallback() - - # 打印初始属性 - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 初始angle_mode: {callback.angle_mode}") - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 初始overflow: {callback.overflow}") - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 初始_last_overflow_state: {callback._last_overflow_state}") - - # 模拟初始状态:门关闭(角度0°),无堆料 - print(f"\n{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 发送状态:门关闭(角度0°),无堆料") - callback.angle_visual_callback(0, "未堆料") - - # 等待一段时间,观察控制行为 - for i in range(3): - time.sleep(1) - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 等待第{i+1}秒...") - - # 发送角度1°,无堆料 - print(f"\n{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 发送状态:角度1°,无堆料") - callback.angle_visual_callback(1, "未堆料") - - # 等待一段时间 - for i in range(3): - time.sleep(1) - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 等待第{i+1}秒...") - - # 发送角度6°,无堆料(超过MIN_ANGLE) - print(f"\n{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 发送状态:角度6°,无堆料") - callback.angle_visual_callback(6, "未堆料") - - # 等待一段时间 - for i in range(3): - time.sleep(1) - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 等待第{i+1}秒...") - - # 测试关闭机制 - callback.shutdown() - print("\n=== 测试完成 ===") - -if __name__ == "__main__": - test_door_close_startup() diff --git a/test_overflow_state_change.py b/test_overflow_state_change.py deleted file mode 100644 index e4fe7cc..0000000 --- a/test_overflow_state_change.py +++ /dev/null @@ -1,59 +0,0 @@ -import time -from vision.visual_callback_1203 import VisualCallback - -# 测试堆料状态变化处理 -def test_overflow_state_change(): - print("=== 测试堆料状态变化处理 ===") - - # 获取单例实例 - callback = VisualCallback() - - # VisualCallback初始化时会自动启动线程 - time.sleep(1) # 等待线程初始化 - - try: - # 1. 初始状态:非堆料,应该立即处理 - print("\n1. 初始状态 - 非堆料") - callback.angle_visual_callback(30, "未堆料") - time.sleep(0.5) - - # 2. 变为堆料状态,应该立即处理(不受间隔限制) - print("\n2. 变为堆料状态") - callback.angle_visual_callback(30, "小堆料") - time.sleep(0.5) - - # 3. 保持堆料状态,应该立即处理(堆料优先) - print("\n3. 保持堆料状态") - callback.angle_visual_callback(30, "小堆料") - time.sleep(0.5) - - # 4. 从堆料变为非堆料,应该立即处理(状态变化) - print("\n4. 从堆料变为非堆料(状态变化)") - callback.angle_visual_callback(30, "未堆料") - time.sleep(0.5) - - # 5. 再次非堆料,应该受2秒间隔限制 - print("\n5. 再次非堆料(无状态变化)") - callback.angle_visual_callback(30, "未堆料") - time.sleep(0.5) - - # 6. 等待2秒后再次非堆料,应该处理 - print("\n6. 等待2秒后再次非堆料") - time.sleep(2) - callback.angle_visual_callback(30, "未堆料") - time.sleep(0.5) - - # 7. 从非堆料变为大堆料,应该立即处理 - print("\n7. 从非堆料变为大堆料") - callback.angle_visual_callback(30, "大堆料") - time.sleep(0.5) - - finally: - # 关闭线程 - callback.shutdown() - time.sleep(1) - - print("\n=== 测试完成 ===") - -if __name__ == "__main__": - test_overflow_state_change() diff --git a/test_visiton.zip b/test_visiton.zip deleted file mode 100644 index 27239c8..0000000 Binary files a/test_visiton.zip and /dev/null differ diff --git a/test_visiton/config/settings.py b/test_visiton/config/settings.py deleted file mode 100644 index d5006ad..0000000 --- a/test_visiton/config/settings.py +++ /dev/null @@ -1,94 +0,0 @@ -# config/settings.py -import os - -class Settings: - def __init__(self): - # 项目根目录 - self.project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - self.test_need_weight=2000 - # 网络继电器配置 - self.relay_host = '192.168.250.62' - self.relay_port = 50000 - - self.debug_feeding=False - #调试模式上,网络继点器禁用,模型推理启用 - self.debug_mode=False - - # 摄像头配置 - self.camera_type = "ip" - self.camera_ip = "192.168.1.51" - self.camera_port = 554 - self.camera_username = "admin" - self.camera_password = "XJ123456" - self.camera_channel = 1 - - self.camera_configs = { - # 'cam1': { - # 'type': 'ip', - # 'ip': '192.168.250.60', - # 'port': 554, - # 'username': 'admin', - # 'password': 'XJ123456', - # 'channel': 1 - # }, - 'cam2': { - 'type': 'ip', - 'ip': '192.168.250.61', - 'port': 554, - 'username': 'admin', - 'password': 'XJ123456', - 'channel': 1 - } - } - - # 下料控制参数 - self.min_required_weight = 500 # 模具车最小需要重量(kg) - self.target_vehicle_weight = 5000 # 目标模具车重量(kg) - self.upper_buffer_weight = 500 # 上料斗缓冲重量(kg) - self.single_batch_weight = 2500 # 单次下料重量(kg) - - - # 角度控制参数 - self.target_angle = 30.0 # 目标角度 - self.min_angle = 10.0 # 最小角度 - self.max_angle = 80.0 # 最大角度 - self.angle_threshold = 50.0 # 角度阈值 - self.angle_tolerance = 5.0 # 角度容差 - - # 变频器配置 - self.inverter_max_frequency = 400.0 # 频率最大值 - self.frequencies = [220.0, 230.0, 240.0] # 下料阶段频率(Hz) - - # 模型路径配置 - self.models_dir = os.path.join(self.project_root, 'vision') - self.angle_model_path = os.path.join(self.models_dir, 'obb_angle_model', 'obb.rknn') - self.overflow_model_path = os.path.join(self.models_dir,'overflow_model', 'yiliao_cls.rknn') - # self.alignment_model_path = os.path.join(self.models_dir, 'align_model', 'yolov11_cls_640v6.rknn') - - # ROI路径配置 - self.roi_file_path = os.path.join(self.models_dir, 'overflow_model', 'roi_coordinates', '1_rois.txt') - - # 系统控制参数 - self.visual_check_interval = 1.0 # 视觉检查间隔(秒) - self.alignment_check_interval = 0.5 # 对齐检查间隔(秒) - self.max_error_count = 3 # 最大错误计数 - self.lower_feeding_interval = 0.1 # 下料轮询间隔(秒) - - # RFID配置 - self.rfid_host = '192.168.1.190' - self.rfid_port = 6000 - - #是否在线生产 - self.is_online_control = True # 是否API在线 - #最后一块进行尾数控制 - # self.block_numbers=['B1','B2','B3','L1','L2','F'] - #需核实上下位漏斗容量 - self.max_upper_volume = 2.4 # 上料斗容量(方) - #下料到下料斗最大下到多少,并非最大容量 - self.max_lower_volume = 2.2 # 下料斗容量(方) - - #led - self.led_interval = 2 # LED闪烁间隔(秒) - -app_set_config = Settings() - diff --git a/test_visiton/hardware/relay.py b/test_visiton/hardware/relay.py deleted file mode 100644 index fce64b6..0000000 --- a/test_visiton/hardware/relay.py +++ /dev/null @@ -1,219 +0,0 @@ -# hardware/relay.py -import socket -import binascii -import time -import threading -from pymodbus.client import ModbusTcpClient -from pymodbus.exceptions import ModbusException -from config.settings import app_set_config - - -class RelayController: - # 继电器映射 - RING = 'ring' # DO1 - 响铃 - UPPER_TO_JBL = 'upper_to_jbl' # DO2 - 上料斗到搅拌楼 - UPPER_TO_ZD = 'upper_to_zd' # DO3 - 上料斗到振捣室 - # DOOR_UPPER = 'door_upper' # DO0 - 上料斗滑动 - DOOR_LOWER_OPEN = 'door_lower_open' # DO1 - 下料斗出砼门开角度 - DOOR_LOWER_CLOSE = 'door_lower_close' # DO2 - 下料斗出砼门关角度(角度在7.5以下可关闭信号) - DOOR_UPPER_OPEN = 'door_upper_open' # DO3 - 上料斗开 - DOOR_UPPER_CLOSE = 'door_upper_close' # DO4 - 上料斗关 - BREAK_ARCH_UPPER = 'break_arch_upper' # DO3 - 上料斗震动 - BREAK_ARCH_LOWER = 'break_arch_lower' # DO4 - 下料斗震动 - DIRECT_LOWER_FRONT = 'direct_lower_front' # DO5 - 下料斗前 - DIRECT_LOWER_BEHIND = 'direct_lower_behind' # DO6 - 下料斗后 - DIRECT_LOWER_TOP = 'direct_lower_top' # DO7 - 下料斗上 - DIRECT_LOWER_BELOW = 'direct_lower_below' # DO8 - 下料斗下 - - def __init__(self, host='192.168.250.62', port=50000): - self.host = host - self.port = port - self.modbus_client = ModbusTcpClient(host, port=port) -#遥1 DO 7 左 DO8 右 角度 摇2:DO 15下 13上 12 往后 14往前 下料斗DO7开 D09关 - # 继电器命令(原始Socket) - self.relay_commands = { - self.RING: {'open': '00000000000601050000FF00', 'close': '000000000006010500000000'}, - self.UPPER_TO_JBL: {'open': '00000000000601050001FF00', 'close': '000000000006010500010000'}, - self.UPPER_TO_ZD: {'open': '00000000000601050002FF00', 'close': '000000000006010500020000'}, - self.DOOR_LOWER_OPEN: {'open': '00000000000601050006FF00', 'close': '000000000006010500060000'}, - self.DOOR_LOWER_CLOSE: {'open': '00000000000601050008FF00', 'close':'000000000006010500080000'}, - self.DOOR_UPPER_OPEN: {'open': '00000000000601050003FF00', 'close': '000000000006010500030000'}, - self.DOOR_UPPER_CLOSE: {'open': '00000000000601050004FF00', 'close': '000000000006010500040000'}, - self.BREAK_ARCH_UPPER: {'open': '0000000000060105000AFF00', 'close': '0000000000060105000A0000'}, - self.BREAK_ARCH_LOWER: {'open': '00000000000601050005FF00', 'close': '000000000006010500050000'}, - self.DIRECT_LOWER_FRONT: {'open': '0000000000060105000DFF00', 'close': '0000000000060105000D0000'}, - self.DIRECT_LOWER_BEHIND: {'open': '0000000000060105000BFF00', 'close': '0000000000060105000B0000'}, - self.DIRECT_LOWER_TOP: {'open': '0000000000060105000CFF00', 'close': '0000000000060105000C0000'}, - self.DIRECT_LOWER_BELOW: {'open': '0000000000060105000EFF00', 'close': '0000000000060105000E0000'} - } - - # 读取状态命令 - self.read_status_command = '000000000006010100000008' - - # 设备位映射 - self.device_bit_map = { - self.RING: 0, - self.UPPER_TO_JBL: 1, - self.UPPER_TO_ZD: 2, - self.BREAK_ARCH_UPPER: 3, - self.BREAK_ARCH_LOWER: 4 - } - - def send_command(self, command_hex): - """发送原始Socket命令""" - if app_set_config.debug_mode: - return None - - try: - byte_data = binascii.unhexlify(command_hex) - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - sock.connect((self.host, self.port)) - sock.send(byte_data) - response = sock.recv(1024) - print(f"收到继电器响应: {binascii.hexlify(response)}") - return response - except Exception as e: - print(f"继电器通信错误: {e}") - return None - - def get_status(self): - """获取继电器状态""" - response = self.send_command(self.read_status_command) - status_dict = {} - - if response and len(response) >= 10: - status_byte = response[9] - status_bin = f"{status_byte:08b}"[::-1] - for key, bit_index in self.device_bit_map.items(): - status_dict[key] = status_bin[bit_index] == '1' - else: - print("读取继电器状态失败") - - return status_dict - - def control(self, device, action): - """控制继电器""" - if device in self.relay_commands and action in self.relay_commands[device]: - print(f"发送控制继电器命令 {device} {action}") - self.send_command(self.relay_commands[device][action]) - else: - print(f"无效设备或动作: {device}, {action}") - - def control_upper_close(self): - """控制上料斗关""" - # 关闭上料斗出砼门 - self.control(self.DOOR_UPPER_OPEN, 'close') - self.control(self.DOOR_UPPER_CLOSE, 'open') - # 异步5秒后关闭 - threading.Thread(target=self._close_upper_s, daemon=True,name="close_upper_s").start() - - def control_lower_close(self): - """控制下料斗关""" - # 关闭下料斗出砼门 - self.control(self.DOOR_LOWER_OPEN, 'close') - self.control(self.DOOR_LOWER_CLOSE, 'open') - time.sleep(5) - self.control(self.DOOR_LOWER_CLOSE, 'close') - # 异步5秒后关闭 - # threading.Thread(target=self._close_lower_5s, daemon=True,name="close_lower_5s").start() - - def control_upper_open_sync(self): - self.control(self.DOOR_UPPER_CLOSE, 'close') - self.control(self.DOOR_UPPER_OPEN, 'open') - - def control_upper_close_sync(self): - self.control(self.DOOR_LOWER_OPEN, 'close') - self.control(self.DOOR_LOWER_CLOSE, 'open') - - def control_upper_open(self): - #关闭信号才能生效 - self.control(self.DOOR_UPPER_CLOSE, 'close') - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.2) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.2) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - #保持8秒 - time.sleep(8) - #8秒后再开5秒 - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - time.sleep(0.1) - self.control(self.DOOR_UPPER_OPEN, 'open') - time.sleep(0.5) - self.control(self.DOOR_UPPER_OPEN, 'close') - - def control_ring_open(self): - """控制下料斗关""" - # 关闭下料斗出砼门 - self.control(self.RING, 'open') - # 异步5秒后关闭 - threading.Thread(target=self._close_ring, daemon=True,name="close_ring").start() - - def _close_upper_s(self): - time.sleep(16) - self.control(self.DOOR_UPPER_CLOSE, 'close') - print("上料斗关闭完成") - - def _close_lower_5s(self): - time.sleep(6) - self.control(self.DOOR_LOWER_CLOSE, 'close') - - def _close_ring(self): - time.sleep(3) - self.control(self.RING, 'close') - - def close_all(self): - """关闭所有继电器""" - self.control(self.UPPER_TO_JBL, 'close') - self.control(self.UPPER_TO_ZD, 'close') - self.control(self.BREAK_ARCH_UPPER, 'close') - self.control(self.BREAK_ARCH_LOWER, 'close') - self.control(self.RING, 'close') - self.control(self.DOOR_LOWER_OPEN, 'close') - self.control(self.DOOR_LOWER_CLOSE, 'close') - self.control(self.DOOR_UPPER_OPEN, 'close') - self.control(self.DOOR_UPPER_CLOSE, 'close') diff --git a/test_visiton/vision/visual_callback.py b/test_visiton/vision/visual_callback.py deleted file mode 100644 index 7a708b2..0000000 --- a/test_visiton/vision/visual_callback.py +++ /dev/null @@ -1,222 +0,0 @@ - -from config.settings import app_set_config -from hardware.relay import RelayController -import time -import threading - -class VisualCallback: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.angle_mode = "normal" - self.relay_controller = RelayController() - self.init_weight = 100 - self.mould_finish_weight = 0 - self.mould_need_weight = 100 - self.finish_count = 0 - self.overflow = False - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - self._stop_event = threading.Event() - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - self._initialized = True - - def angle_visual_callback(self, current_angle, overflow_detected): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - self._current_angle = current_angle - self._overflow_detected = overflow_detected - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - - def _run_thread_loop(self): - """ - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.1) - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 内部方法,实际处理视觉回调逻辑 - 在异步线程中执行 - """ - try: - # print('current_angle:', current_angle, 'overflow_detected:', overflow_detected) - # return - # 检测溢出状态 - self.overflow = overflow_detected in ["大堆料", "小堆料"] - if current_angle is None: - print("无法获取当前角度,跳过本次调整") - return - - print(f"当前角度: {current_angle:.2f}°") - - if overflow_detected != "浇筑满": - # 状态机控制逻辑 - if self.angle_mode == "normal": - # 正常模式大于app_set_config.angle_threshold=60度 - if self.overflow: - self.angle_mode = "reducing" - else: - # 保持正常开门 - print(f'当前重量:{self.mould_finish_weight:.2f}kg, 目标重量:{self.mould_need_weight:.2f}kg') - if self.mould_need_weight > 0: - if self.mould_finish_weight / self.mould_need_weight >= 0.8: - print(f"完成重量占比{self.mould_finish_weight/self.mould_need_weight:.2f},半开出砼门") - # 半开出砼门 - if current_angle > app_set_config.target_angle: - # 角度已降至目标范围,关闭出砼门 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(0.3) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.32) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - else: - # 全开砼门 - if current_angle > app_set_config.angle_threshold: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - else: - # 全开砼门 - if current_angle > app_set_config.angle_threshold: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - elif self.angle_mode == "reducing": - # 角度减小模式 - if self.overflow: - if current_angle <= app_set_config.target_angle: - # 角度已达到目标范围,仍有堆料,进入维持模式 - print(f"角度已降至{current_angle:.2f}°,仍有堆料,进入维持模式") - if current_angle <= app_set_config.min_angle: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.1) - self.angle_mode = "maintaining" - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - else: - # 无堆料,恢复正常模式 - print(f"角度已降至{current_angle:.2f}°,无堆料,恢复正常模式") - self.angle_mode = "normal" - - elif self.angle_mode == "maintaining": - # 维持模式 - 使用脉冲控制 - if not self.overflow: - # 堆料已消除,恢复正常模式 - print("堆料已消除,恢复正常模式") - self.angle_mode = "normal" - else: - # 继续维持角度控制 - print("进入维持模式") - # 关门时间 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(0.2) - # 开门时间 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.25) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - - elif self.angle_mode == "recovery": - # 恢复模式 - 逐步打开门 - if self.overflow: - # 又出现堆料,回到角度减小模式 - print("恢复过程中又检测到堆料,回到角度减小模式") - self.angle_mode = "maintaining" - else: - # 堆料已消除,恢复正常模式 - print("堆料已消除,恢复正常模式") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.angle_mode = "normal" - else: - # 浇筑满,关闭下料门 - self.relay_controller.control_lower_close() - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - -# 创建默认实例 -visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -def angle_visual_callback(current_angle, overflow_detected): - """ - 兼容旧版本的函数调用方式 - 将调用转发到默认实例的angle_visual_callback方法 - """ - visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) diff --git a/test_visual_callback.py b/test_visual_callback.py deleted file mode 100644 index f15dbf2..0000000 --- a/test_visual_callback.py +++ /dev/null @@ -1,56 +0,0 @@ -import time -import threading -from vision.visual_callback import VisualCallback, angle_visual_callback - -# 创建VisualCallback实例 -visual_callback = VisualCallback() - -# 模拟safe_control_lower_close执行 -def simulate_safe_close(): - - - while True: - time.sleep(1) - """模拟safe_control_lower_close执行""" - print("\n=== 开始模拟safe_control_lower_close执行 ===") - - # 1. 首先,发送一些视觉回调数据,观察正常情况下的行为 - print("\n1. 正常运行阶段:") - - for i in range(5): - angle_visual_callback(50.0, "无堆料") - time.sleep(1) - - # 2. 模拟safe_control_lower_close开始执行 - print("\n2. 模拟safe_control_lower_close开始执行:") - visual_callback._is_safe_closing = True - - # 3. 再次发送视觉回调数据,观察是否会跳过relay操作 - print("\n3. safe_control_lower_close执行中:") - for i in range(5): - angle_visual_callback(50.0, "无堆料") - time.sleep(1) - - # 4. 模拟safe_control_lower_close执行完毕 - print("\n4. 模拟safe_control_lower_close执行完毕:") - visual_callback._is_safe_closing = False - - # 5. 再次发送视觉回调数据,观察是否恢复正常 - print("\n5. 恢复正常运行:") - for i in range(5): - angle_visual_callback(50.0, "无堆料") - time.sleep(1) - - print("\n=== 测试结束 ===") - visual_callback.shutdown() - -# 启动测试 -try: - simulate_safe_close() -except KeyboardInterrupt: - print("\n\n接收到Ctrl+C,正在停止服务...") -finally: - # 确保服务正确停止 - visual_callback.shutdown() - visual_callback.relay_controller.close_all() - print("服务已安全停止") diff --git a/test_weight.py b/test_weight.py deleted file mode 100644 index 9765dd9..0000000 --- a/test_weight.py +++ /dev/null @@ -1,26 +0,0 @@ -# main.py -import time -from datetime import datetime -from hardware import relay -from hardware.relay import RelayController -from hardware.transmitter import TransmitterController -import time - - -def main(): - relay_c = RelayController() - transmitter_c = TransmitterController(relay_c) - while True: - # data = '上料斗:' + str(int(transmitter_c.read_data(1))) + ',' +"下料斗:" + str(int(transmitter_c.read_data(2))) - data = str(int(transmitter_c.read_data(2))) - timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3] - output = f"[{timestamp}] {data}" - print(output) - # 保存到文件 - with open('output.log', 'a') as f: - f.write(output + '\n') - time.sleep(0.5) - - -if __name__ == "__main__": - main() diff --git a/tests/Investor485test.py b/tests/Investor485test.py deleted file mode 100644 index 2de5d74..0000000 --- a/tests/Investor485test.py +++ /dev/null @@ -1,259 +0,0 @@ -import serial -import time -import struct - - -class InovanceMD520: - def __init__(self, port='COM4', baudrate=9600, timeout=1): - """ - 初始化汇川MD520变频器通信 - :param port: 串口名称,Windows为COMx,Linux为/dev/ttyUSBx - :param baudrate: 波特率,默认9600 - :param timeout: 超时时间,秒 - """ - self.port = port - self.baudrate = baudrate - self.timeout = timeout - self.ser = None - - def connect(self): - """连接串口""" - try: - self.ser = serial.Serial( - port=self.port, - baudrate=self.baudrate, - bytesize=serial.EIGHTBITS, - parity=serial.PARITY_NONE, - stopbits=serial.STOPBITS_ONE, - timeout=self.timeout - ) - print(f"成功连接到串口 {self.port}") - return True - except serial.SerialException as e: - print(f"连接串口失败: {e}") - return False - - def disconnect(self): - """断开串口连接""" - if self.ser and self.ser.is_open: - self.ser.close() - print("串口连接已关闭") - - def calculate_crc(self, data): - """ - 计算Modbus CRC16校验码 - :param data: 字节数据 - :return: CRC校验码(低位在前,高位在后) - """ - crc = 0xFFFF - for byte in data: - crc ^= byte - for _ in range(8): - if crc & 0x0001: - crc = (crc >> 1) ^ 0xA001 - else: - crc = crc >> 1 - return struct.pack('> 8) & 0xFF, # 寄存器地址高字节 - register_addr & 0xFF, # 寄存器地址低字节 - (register_count >> 8) & 0xFF, # 寄存器数量高字节 - register_count & 0xFF # 寄存器数量低字节 - ]) - - # 计算CRC - crc = self.calculate_crc(cmd_data) - full_cmd = cmd_data + crc - - print(f"发送读取指令: {full_cmd.hex().upper()}") - - try: - self.ser.reset_input_buffer() - self.ser.write(full_cmd) - time.sleep(0.01) - - # 计算预期响应长度 - expected_length = 5 + 2 * register_count # 地址1 + 功能码1 + 字节数1 + 数据2*N + CRC2 - response = self.ser.read(expected_length) - - if len(response) < expected_length: - print(f"响应数据长度不足: {len(response)} 字节,期望 {expected_length} 字节") - return None - - print(f"收到响应: {response.hex().upper()}") - - # 验证CRC - received_crc = response[-2:] - calculated_crc = self.calculate_crc(response[:-2]) - if received_crc != calculated_crc: - print("CRC校验失败") - return None - - # 解析数据 - data_length = response[2] - data_bytes = response[3:3 + data_length] - - results = [] - for i in range(0, len(data_bytes), 2): - value = (data_bytes[i] << 8) | data_bytes[i + 1] - results.append(value) - - return results - - except Exception as e: - print(f"通信错误: {e}") - return None - - -def main(): - # 创建变频器对象 - inverter = InovanceMD520(port='COM3', baudrate=9600) - - # 连接串口 - if not inverter.connect(): - return - - try: - while True: - print("\n" + "=" * 50) - print("汇川MD520变频器频率查询") - print("=" * 50) - - # 查询运行频率 - frequency = inverter.query_frequency(slave_addr=0x01) - - if frequency is not None: - print(f"✅ 当前运行频率: {frequency:.2f} Hz") - else: - print("❌ 频率查询失败") - - # 可选:读取其他监控参数 - print("\n--- 其他监控参数 ---") - - # 读取母线电压 (地址1002H) - voltage_data = inverter.read_register(0x01, 0x1002) - if voltage_data: - voltage = voltage_data[0] / 10.0 # 单位0.1V - print(f"母线电压: {voltage:.1f} V") - - # 读取输出电压 (地址1003H) - output_voltage_data = inverter.read_register(0x01, 0x1003) - if output_voltage_data: - output_voltage = output_voltage_data[0] # 单位1V - print(f"输出电压: {output_voltage} V") - - # 读取输出电流 (地址1004H) - current_data = inverter.read_register(0x01, 0x1004) - if current_data: - current = current_data[0] / 100.0 # 单位0.01A - print(f"输出电流: {current:.2f} A") - - # 等待5秒后再次查询 - print("\n等待5秒后继续查询...") - time.sleep(5) - - except KeyboardInterrupt: - print("\n用户中断查询") - finally: - # 断开连接 - inverter.disconnect() - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/close_test.py b/tests/close_all_relay.py similarity index 100% rename from close_test.py rename to tests/close_all_relay.py diff --git a/test_led.py b/tests/led-app.py similarity index 100% rename from test_led.py rename to tests/led-app.py diff --git a/core/pd_system.py b/tests/pd_system.py similarity index 100% rename from core/pd_system.py rename to tests/pd_system.py diff --git a/tests/test_relay_controller.py b/tests/relay_controller_pytest.py similarity index 100% rename from tests/test_relay_controller.py rename to tests/relay_controller_pytest.py diff --git a/tests/test_rfid.py b/tests/rfid_app.py similarity index 100% rename from tests/test_rfid.py rename to tests/rfid_app.py diff --git a/tests/test_feeding_process.py b/tests/test_feeding_process.py deleted file mode 100644 index c45fe5c..0000000 --- a/tests/test_feeding_process.py +++ /dev/null @@ -1,54 +0,0 @@ -# tests/test_feeding_process.py -import unittest -from unittest.mock import patch, MagicMock -import sys -import os -from config.settings import app_set_config - -# 添加项目根目录到Python路径 -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) - -from feeding.process import FeedingProcess - - -class TestFeedingProcess(unittest.TestCase): - - @patch('feeding.process.RelayController') - @patch('feeding.process.InverterController') - @patch('feeding.process.TransmitterController') - def test_initialization(self, mock_transmitter, mock_inverter, mock_relay): - """测试初始化""" - # 创建模拟对象 - mock_relay_instance = MagicMock() - mock_relay.return_value = mock_relay_instance - - mock_inverter_instance = MagicMock() - mock_inverter.return_value = mock_inverter_instance - - mock_transmitter_instance = MagicMock() - mock_transmitter.return_value = mock_transmitter_instance - - # 创建系统实例 - system = FeedingProcess() - - # 验证初始化 - self.assertIsNotNone(system) - self.assertFalse(system.state.running) - - def test_set_feeding_parameters(self): - """测试设置下料参数""" - with patch('feeding.process.RelayController'), \ - patch('feeding.process.InverterController'), \ - patch('feeding.process.TransmitterController'): - system = FeedingProcess() - #修改参数 app_set_config.single_batch_weight = 1500 - app_set_config.min_required_weight = 300 - app_set_config.target_vehicle_weight = 3000 - - self.assertEqual(app_set_config.target_vehicle_weight, 3000) - self.assertEqual(app_set_config.min_required_weight, 300) - self.assertEqual(app_set_config.single_batch_weight, 1500) - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/test_inverter_controller.py b/tests/test_inverter_controller.py deleted file mode 100644 index d2c24ad..0000000 --- a/tests/test_inverter_controller.py +++ /dev/null @@ -1,161 +0,0 @@ -import unittest -from unittest.mock import patch, MagicMock -import sys -import os - -# 添加项目根目录到Python路径 -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) - -from hardware.inverter import InverterController -from pymodbus.exceptions import ModbusException - - -class TestInverterController(unittest.TestCase): - - def setUp(self): - """测试前的准备工作""" - # 创建模拟的继电器控制器 - self.mock_relay = MagicMock() - self.mock_relay.modbus_client = MagicMock() - - # 创建变频器控制器实例 - self.inverter = InverterController(relay_controller=self.mock_relay) - - def test_inverter_initialization(self): - """测试变频器控制器初始化""" - self.assertEqual(self.inverter.relay_controller, self.mock_relay) - self.assertEqual(self.inverter.max_frequency, 400.0) - - # 检查配置 - self.assertIn('slave_id', self.inverter.config) - self.assertIn('frequency_register', self.inverter.config) - self.assertIn('start_register', self.inverter.config) - self.assertIn('stop_register', self.inverter.config) - self.assertIn('start_command', self.inverter.config) - self.assertIn('stop_command', self.inverter.config) - - def test_set_frequency_success(self): - """测试设置频率成功""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - mock_result = MagicMock() - self.mock_relay.modbus_client.write_register.return_value = mock_result - - # 设置频率 - result = self.inverter.set_frequency(200.0) - - # 验证调用 - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.write_register.assert_called_once() - self.assertTrue(result) - - def test_set_frequency_connection_failed(self): - """测试设置频率时连接失败""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = False - - # 设置频率 - result = self.inverter.set_frequency(200.0) - - # 验证结果 - self.assertFalse(result) - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.write_register.assert_not_called() - - def test_set_frequency_modbus_exception(self): - """测试设置频率时Modbus异常""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - self.mock_relay.modbus_client.write_register.side_effect = ModbusException("Modbus错误") - - # 设置频率 - result = self.inverter.set_frequency(200.0) - - # 验证结果 - self.assertFalse(result) - - def test_set_frequency_value_clamping(self): - """测试频率值限制""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - mock_result = MagicMock() - self.mock_relay.modbus_client.write_register.return_value = mock_result - - # 测试超过最大值的频率 - result = self.inverter.set_frequency(500.0) # 超过400.0最大值 - - # 验证调用 - self.assertTrue(result) - self.mock_relay.modbus_client.write_register.assert_called_once() - - def test_control_start_success(self): - """测试启动变频器成功""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - mock_result = MagicMock() - self.mock_relay.modbus_client.write_register.return_value = mock_result - - # 启动变频器 - result = self.inverter.control('start') - - # 验证调用 - self.assertTrue(result) - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.write_register.assert_called_once() - - def test_control_stop_success(self): - """测试停止变频器成功""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - mock_result = MagicMock() - self.mock_relay.modbus_client.write_register.return_value = mock_result - - # 停止变频器 - result = self.inverter.control('stop') - - # 验证调用 - self.assertTrue(result) - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.write_register.assert_called_once() - - def test_control_invalid_action(self): - """测试无效的控制动作""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - - # 使用无效动作 - result = self.inverter.control('invalid_action') - - # 验证结果 - self.assertFalse(result) - self.mock_relay.modbus_client.connect.assert_not_called() - self.mock_relay.modbus_client.write_register.assert_not_called() - - def test_control_connection_failed(self): - """测试控制时连接失败""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = False - - # 启动变频器 - result = self.inverter.control('start') - - # 验证结果 - self.assertFalse(result) - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.write_register.assert_not_called() - - def test_control_modbus_exception(self): - """测试控制时Modbus异常""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - self.mock_relay.modbus_client.write_register.side_effect = ModbusException("Modbus错误") - - # 启动变频器 - result = self.inverter.control('start') - - # 验证结果 - self.assertFalse(result) - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/test_transmitter_controller.py b/tests/test_transmitter_controller.py deleted file mode 100644 index 8300026..0000000 --- a/tests/test_transmitter_controller.py +++ /dev/null @@ -1,165 +0,0 @@ -import unittest -from unittest.mock import patch, MagicMock -import sys -import os - -# 添加项目根目录到Python路径 -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) - -from hardware.transmitter import TransmitterController -from pymodbus.exceptions import ModbusException - - -class TestTransmitterController(unittest.TestCase): - - def setUp(self): - """测试前的准备工作""" - # 创建模拟的继电器控制器 - self.mock_relay = MagicMock() - self.mock_relay.modbus_client = MagicMock() - - # 创建变送器控制器实例 - self.transmitter = TransmitterController(relay_controller=self.mock_relay) - - def test_transmitter_initialization(self): - """测试变送器控制器初始化""" - self.assertEqual(self.transmitter.relay_controller, self.mock_relay) - - # 检查配置 - self.assertIn(1, self.transmitter.config) # 上料斗 - self.assertIn(2, self.transmitter.config) # 下料斗 - - # 检查上料斗配置 - upper_config = self.transmitter.config[1] - self.assertEqual(upper_config['slave_id'], 1) - self.assertEqual(upper_config['weight_register'], 0x01) - self.assertEqual(upper_config['register_count'], 2) - - # 检查下料斗配置 - lower_config = self.transmitter.config[2] - self.assertEqual(lower_config['slave_id'], 2) - self.assertEqual(lower_config['weight_register'], 0x01) - self.assertEqual(lower_config['register_count'], 2) - - def test_read_data_valid_transmitter_id(self): - """测试读取有效变送器ID的数据""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - - # 模拟读取结果 - mock_result = MagicMock() - mock_result.registers = [0, 1500] # 表示1500kg - self.mock_relay.modbus_client.read_holding_registers.return_value = mock_result - - # 读取上料斗数据 - weight = self.transmitter.read_data(1) - - # 验证调用 - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.read_holding_registers.assert_called_once() - self.assertEqual(weight, 1.5) # 1500kg / 1000 = 1.5kg - - def test_read_data_invalid_transmitter_id(self): - """测试读取无效变送器ID的数据""" - # 读取无效ID的数据 - weight = self.transmitter.read_data(99) - - # 验证结果 - self.assertIsNone(weight) - self.mock_relay.modbus_client.connect.assert_not_called() - self.mock_relay.modbus_client.read_holding_registers.assert_not_called() - - def test_read_data_connection_failed(self): - """测试读取数据时连接失败""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = False - - # 读取数据 - weight = self.transmitter.read_data(1) - - # 验证结果 - self.assertIsNone(weight) - self.mock_relay.modbus_client.connect.assert_called_once() - self.mock_relay.modbus_client.read_holding_registers.assert_not_called() - - def test_read_data_modbus_exception(self): - """测试读取数据时Modbus异常""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - self.mock_relay.modbus_client.read_holding_registers.side_effect = ModbusException("Modbus错误") - - # 读取数据 - weight = self.transmitter.read_data(1) - - # 验证结果 - self.assertIsNone(weight) - - def test_read_data_register_count_2(self): - """测试读取2个寄存器的数据""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - - # 模拟读取结果 (高位寄存器=1,低位寄存器=0xE848,表示128000) - mock_result = MagicMock() - mock_result.registers = [1, 0xE848] # 1 * 65536 + 59464 = 125000 - self.mock_relay.modbus_client.read_holding_registers.return_value = mock_result - - # 读取数据 - weight = self.transmitter.read_data(1) - - # 验证结果 (125000 / 1000 = 125kg) - self.assertEqual(weight, 125.0) - - def test_read_data_register_count_1(self): - """测试读取1个寄存器的数据(模拟配置变更)""" - # 临时修改配置以测试单寄存器读取 - self.transmitter.config[1]['register_count'] = 1 - - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - - # 模拟读取结果 - mock_result = MagicMock() - mock_result.registers = [1500] # 表示1500kg - self.mock_relay.modbus_client.read_holding_registers.return_value = mock_result - - # 读取数据 - weight = self.transmitter.read_data(1) - - # 验证结果 - self.assertEqual(weight, 1500.0) # 1500kg - - def test_read_data_invalid_register_count(self): - """测试不支持的寄存器数量""" - # 临时修改配置以测试无效寄存器数量 - self.transmitter.config[1]['register_count'] = 3 - - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - - # 模拟读取结果 - mock_result = MagicMock() - mock_result.registers = [0, 0, 0] - self.mock_relay.modbus_client.read_holding_registers.return_value = mock_result - - # 读取数据 - weight = self.transmitter.read_data(1) - - # 验证结果 - self.assertIsNone(weight) - - def test_read_data_result_is_exception(self): - """测试读取结果为异常对象""" - # 配置模拟对象 - self.mock_relay.modbus_client.connect.return_value = True - self.mock_relay.modbus_client.read_holding_registers.return_value = Exception("读取错误") - - # 读取数据 - weight = self.transmitter.read_data(1) - - # 验证结果 - self.assertIsNone(weight) - - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/tests/test_vision.py b/tests/test_vision.py deleted file mode 100644 index b17c457..0000000 --- a/tests/test_vision.py +++ /dev/null @@ -1,65 +0,0 @@ -import os -import sys -# 添加项目根目录到Python路径 -sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) -import unittest -from unittest.mock import MagicMock -from vision.camera import CameraController - -# from core.vision import Vision - -class TestVision(unittest.TestCase): - - def setUp(self): - self.jj=2 - # self.testclass2 = MagicMock() - # self.testclass = TestClass(self.testclass2) - - def test_capture_frame(self): - # 测试capture_frame方法 - camera=CameraController() - result = camera.capture_frame() - self.assertIsNone(result, msg="capture_frame方法测试失败") - camera.capture_frame_exec.assert_called_once() - - # def test_first(self): - # 测试TestClass的add方法 - # mock_testclass2 = MagicMock() - # # mock_testclass2.i = 1 - # # mock_testclass2.j = 2 - - # test_class = TestClass(mock_testclass2) - # result = test_class.add() - - # # 验证结果 - # self.assertEqual(result, 3, msg="add方法测试失败") - - # def test_second(self): - # 测试TestClass2的mock行为 - # mock_testclass2 = MagicMock(spec=TestClass2) - # mock_testclass2.sub.return_value = 1 - # result = mock_testclass2.sub() - - # # 测试返回值 - # self.assertEqual(result, 1, msg="sub方法测试失败") - # mock_testclass2.sub.assert_called_once() - - -class TestClass: - def __init__(self,testclass2): - self.testclass2 = testclass2 - pass - def add(self): - return self.testclass2.i + self.testclass2.j - -class TestClass2: - def __init__(self): - self.i = 1 - self.j = 2 - pass - - def sub(self): - return self.j - self.i - -if __name__ == '__main__': - unittest.main() \ No newline at end of file diff --git a/upper_plc.py b/tests/upper_plc.py similarity index 100% rename from upper_plc.py rename to tests/upper_plc.py diff --git a/tests/485test.py b/tests/vf_control.py similarity index 100% rename from tests/485test.py rename to tests/vf_control.py diff --git a/test copy.py b/tests/weight-app.py similarity index 100% rename from test copy.py rename to tests/weight-app.py diff --git a/upper_to_lower.py b/upper_to_lower.py deleted file mode 100644 index 57f27a8..0000000 --- a/upper_to_lower.py +++ /dev/null @@ -1,159 +0,0 @@ -# main.py -import time -from config.settings import app_set_config -from core.system import FeedingControlSystem -from hardware import relay -from hardware.relay import RelayController -from hardware.inverter import InverterController -from hardware.transmitter import TransmitterController -import threading -import time -import cv2 -import vision.visual_callback_1203 as angle_visual - - -def main(): - # 加载配置 - # 初始化系统 - # angle_visual.angle_visual_callback(2,'未堆料') - replay_controller=RelayController() - # transmitter_controller=TransmitterController(replay_controller) - # upper_weight=transmitter_controller.read_data(2) - # print(upper_weight) - - - - replay_controller.close_all() - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - - - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - # time.sleep(0.4) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(0.4) - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(4) - # while True: - # # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - # time.sleep(2) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.1) - - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(0.5) - # replay_controller.control(replay_controller.DOOR_UPPER_OPEN, 'close') - - # time.sleep(3) - - - - # inverter_controller=InverterController(replay_controller) - # inverter_controller.control('start') - - # inverter_controller.control('stop') - - - - # return - # replay_controller.control_upper_open() - #3秒开关 - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(1) - # replay_controller.control(replay_controller.DOOR_LOWER_OPEN, 'close') - - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(1) - # replay_controller.control(replay_controller.DOOR_LOWER_CLOSE, 'close') - - - # while True: - # time.sleep(1) - - # replay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - - # system = FeedingControlSystem() - - # system.state.vehicle_aligned=True - #假设在振捣室 - # system.state._upper_door_position='over_lower' - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # system.initialize() - - # system.state._feed_status=FeedStatus.FCheckM - - # cv2.namedWindow("控制系统", cv2.WND_PROP_VISIBLE) - # cv2.setWindowProperty("控制系统", cv2.WND_PROP_VISIBLE, 0) - # while True: - # key = cv2.waitKey(100) & 0xFF - # if key == ord('q') or key == 27: # 'q'键或ESC键 - # print("接收到退出信号,正在关闭系统...") - # break - - # time.sleep(1) # 减少CPU占用 - - # replay_controller.close_all() - - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_CLOSE, 'close') - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - # time.sleep(5) - # system.relay_controller.control_upper_close() - - - # system.relay_controller.control(system.relay_controller, 'close') - - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'open') - - # system.camera_controller.start_cameras() - # loc_all_close=True - # if loc_all_close: - # relay=RelayController() - # relay.control(relay.UPPER_TO_JBL, 'close') - # relay.control(relay.UPPER_TO_ZD, 'close') - # relay.control(relay.DOOR_LOWER_OPEN, 'close') - # relay.control(relay.DOOR_LOWER_CLOSE, 'close') - # relay.control(relay.DOOR_UPPER_OPEN, 'close') - # relay.control(relay.DOOR_UPPER_CLOSE, 'close') - # relay.control(relay.BREAK_ARCH_UPPER, 'close') - # relay.control(relay.BREAK_ARCH_LOWER, 'close') - - - # time.sleep(2) - # system._alignment_check_loop() - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_OPEN, 'open') - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_OPEN, 'close') - - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(2) - # system.relay_controller.control(system.relay_controller.DOOR_UPPER_CLOSE, 'close') - - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'open') - # time.sleep(5) - # system.relay_controller.control(system.relay_controller.DOOR_LOWER_CLOSE, 'close') - # system._visual_control_loop() - # system.transmitter_controller.test_upper_weight=2*2500 - # system.transmitter_controller.test_lower_weight=1000 - # system.state.vehicle_aligned=True - # # 启动调整线程 - # weight_thread = threading.Thread(target=adjust_weights, args=(system,), daemon=True) - - # weight_thread.start() - # system.state._upper_door_position='over_lower' - # system._start_lower_feeding() - - - -if __name__ == "__main__": - main() diff --git a/view/main_window.py b/view/main_window.py index 5a3ae92..cf46dfc 100644 --- a/view/main_window.py +++ b/view/main_window.py @@ -35,15 +35,16 @@ class MainWindow(QWidget): def __init__(self): super().__init__() self.initWindow() + # 保存管片任务信息的字典 task1: ArtifactInfoModel1.... (用于显示管片任务详情) + self.artifact_dict = {} + # 当前点击/选中的 管片任务详情对应的任务名(task1\task2\task3) (用于刷新选中的管片任务详情) + self.current_selected_segment_detail_name = None self.createSubWidgets() # 创建子部件 self.initSubWidgets() # 初始化子部件 self.setupLayout() # 设置布局 self.connectSignalToSlot() - # 保存管片任务信息的字典 task1: ArtifactInfoModel1.... (用于显示管片任务详情) - self.artifact_dict = {} - # 当前点击/选中的 管片任务详情对应的任务名(task1\task2\task3) (用于刷新选中的管片任务详情) - self.current_selected_segment_detail_name = None + # 安装事件过滤器,处理计划方量的 QLineEdit的失去焦点事件 self.installEventFilter(self) @@ -127,7 +128,7 @@ class MainWindow(QWidget): # self.dispatch_task_widget.set_task_id("task3", "PD0003") # 初始化 管片任务 和 派单任务显示的数据 - # self._init_segment_tasks() + self._init_segment_tasks() self._init_dispatch_tasks() def convert_to_ampm(self, time_str: str) -> str: diff --git a/vision/.py b/vision/.py deleted file mode 100644 index 7c4da50..0000000 --- a/vision/.py +++ /dev/null @@ -1,137 +0,0 @@ -# vision/camera.py -import cv2 - - -class CameraController: - def __init__(self): - self.camera = None - self.camera_type = "ip" - self.camera_ip = "192.168.1.51" - self.camera_port = 554 - self.camera_username = "admin" - self.camera_password = "XJ123456" - self.camera_channel = 1 - - def set_config(self, camera_type="ip", ip=None, port=None, username=None, password=None, channel=1): - """ - 设置摄像头配置 - """ - self.camera_type = camera_type - if ip: - self.camera_ip = ip - if port: - self.camera_port = port - if username: - self.camera_username = username - if password: - self.camera_password = password - self.camera_channel = channel - - def setup_capture(self, camera_index=0): - """ - 设置摄像头捕获 - """ - try: - rtsp_url = f"rtsp://{self.camera_username}:{self.camera_password}@{self.camera_ip}:{self.camera_port}/streaming/channels/{self.camera_channel}01" - self.camera = cv2.VideoCapture(rtsp_url) - - if not self.camera.isOpened(): - print(f"无法打开网络摄像头: {rtsp_url}") - return False - print(f"网络摄像头初始化成功,地址: {rtsp_url}") - return True - except Exception as e: - print(f"摄像头设置失败: {e}") - return False - - def capture_frame_exec(self): - """捕获当前帧并返回numpy数组,设置5秒总超时""" - try: - if self.camera is None: - print("摄像头未初始化") - return None - - # 设置总超时时间为5秒 - total_timeout = 5.0 # 5秒总超时时间 - start_time = time.time() - - # 跳20帧,获取最新图像 - frames_skipped = 0 - while frames_skipped < 20: - # 检查总超时 - if time.time() - start_time > total_timeout: - print("捕获图像总超时") - return None - self.camera.grab() - time.sleep(0.05) # 稍微增加延迟,确保有新帧到达 - frames_skipped += 1 - - # 尝试读取帧,使用同一超时计时器 - read_attempts = 0 - max_read_attempts = 3 - if self.camera.grab(): - while read_attempts < max_read_attempts: - # 使用同一个超时计时器检查 - if time.time() - start_time > total_timeout: - print("捕获图像总超时") - return None - - ret, frame = self.camera.retrieve() - if ret: - return frame - else: - print(f"尝试读取图像帧失败,重试 ({read_attempts+1}/{max_read_attempts})") - read_attempts += 1 - # 短暂延迟后重试 - time.sleep(0.05) - - print("多次尝试后仍无法捕获有效图像帧") - return None - except Exception as e: - print(f"图像捕获失败: {e}") - return None - - def capture_frame(self): - """捕获当前帧并返回numpy数组""" - try: - if self.camera is None: - # self.set_config() - self.setup_capture() - - - frame = self.capture_frame_exec() - if frame is not None: - return frame - else: - print("无法捕获图像帧") - return None - except Exception as e: - print(f"图像捕获失败: {e}") - return None - - def capture_frame_bak(self): - """捕获当前帧并返回numpy数组""" - try: - if self.camera is None: - print("摄像头未初始化") - return None - - ret, frame = self.camera.read() - if ret: - return frame - else: - print("无法捕获图像帧") - return None - except Exception as e: - print(f"图像捕获失败: {e}") - return None - - def release(self): - """释放摄像头资源""" - if self.camera is not None: - self.camera.release() - self.camera = None - - def __del__(self): - """析构函数,确保资源释放""" - self.release() diff --git a/vision/test_feed.py b/vision/test_feed.py deleted file mode 100644 index 809b9a5..0000000 --- a/vision/test_feed.py +++ /dev/null @@ -1,62 +0,0 @@ -import time -from hardware.relay import RelayController -from hardware.transmitter import TransmitterController -from test_feed import start_feeding - -def start_feeding(): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("开始下料") - # self.relay_controller.control - loc_relay=RelayController() - loc_mitter=TransmitterController(loc_relay) - initial_lower_weight=loc_mitter.read_data(2) - initial_upper_weight=loc_mitter.read_data(1) - first_finish_weight=0 - start_time=None - # mould_need_weight=4000 - while True: - current_weight = loc_mitter.read_data(2) - first_finish_weight=initial_lower_weight-current_weight - if current_weight<500: - #关5秒 - if start_time is None or time.time()-start_time>5: - start_time=time.time() - loc_relay.control_arch_lower_open() - - if current_weight<100: - start_time=None - loc_relay.control_lower_close() - break - print(f'------------已下料: {first_finish_weight}kg-------------') - time.sleep(1) - - #打开上料斗出砼门,开5就,开三分之一下 - loc_relay.control_upper_open_sync(5) - while True: - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight<3500: - #关5秒 - loc_relay.control_upper_close() - break - time.sleep(1) - - initial_lower_weight=loc_mitter.read_data(2) - while True: - current_weight = loc_mitter.read_data(2) - first_finish_weight=first_finish_weight+initial_lower_weight-current_weight - if current_weight<500: - #关5秒 - if start_time is None or time.time()-start_time>5: - start_time=time.time() - loc_relay.control_arch_lower_open() - if current_weight<100: - start_time=None - loc_relay.control_lower_close() - break - print(f'------------已下料: {first_finish_weight}kg-------------') - time.sleep(1) - -if __name__ == "__main__": - start_feeding() - - \ No newline at end of file diff --git a/vision/test_safe_close.py b/vision/test_safe_close.py deleted file mode 100644 index 4d0ddb2..0000000 --- a/vision/test_safe_close.py +++ /dev/null @@ -1,52 +0,0 @@ -import time -import threading -import sys -import os - -# 添加项目根目录到sys.path -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) - -from visual_callback import VisualCallback - -# 创建VisualCallback实例 -visual_callback = VisualCallback() - -# 模拟safe_control_lower_close执行 -def simulate_safe_close(): - """模拟safe_control_lower_close执行""" - print("\n=== 开始模拟safe_control_lower_close执行 ===") - # 直接调用safe_control_lower_close函数 - # 注意:这里需要访问_run_feed方法中的内部函数,所以我们需要一个间接的方式来测试 - # 我们可以通过修改标志位来模拟这个过程 - - # 1. 首先,让线程循环运行一段时间,观察正常情况下的行为 - print("\n1. 正常运行阶段 (5秒):") - time.sleep(5) - - # 2. 模拟safe_control_lower_close开始执行 - print("\n2. 模拟safe_control_lower_close开始执行:") - visual_callback._is_safe_closing = True - - # 3. 让线程循环运行一段时间,观察是否会跳过relay操作 - print("\n3. safe_control_lower_close执行中 (5秒):") - time.sleep(5) - - # 4. 模拟safe_control_lower_close执行完毕 - print("\n4. 模拟safe_control_lower_close执行完毕:") - visual_callback._is_safe_closing = False - - # 5. 再次观察正常运行 - print("\n5. 恢复正常运行 (5秒):") - time.sleep(5) - - print("\n=== 测试结束 ===") - -# 创建测试线程 -test_thread = threading.Thread(target=simulate_safe_close) -test_thread.start() - -# 主线程等待测试线程结束 -test_thread.join() - -# 关闭视觉回调实例 -visual_callback.shutdown() \ No newline at end of file diff --git a/vision/visual_callback copy 2.py b/vision/visual_callback copy 2.py deleted file mode 100644 index 4e7cd05..0000000 --- a/vision/visual_callback copy 2.py +++ /dev/null @@ -1,277 +0,0 @@ - -from config.settings import app_set_config -from hardware.relay import RelayController -import time -import threading -from datetime import datetime - -class VisualCallback: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.angle_mode = "normal" - self.relay_controller = RelayController() - self.init_weight = 100 - self.mould_finish_weight = 0 - self.mould_need_weight = 100 - self.finish_count = 0 - self.overflow = False - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - self._stop_event = threading.Event() - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - # 初始化控制间隔和堆料状态跟踪属性 - self._last_overflow_state = False - self._last_control_time = 0 - self._initialized = True - - def angle_visual_callback(self, current_angle, overflow_detected): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 回调:{current_angle}") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - self._current_angle = current_angle - if overflow_detected is not None: - self._overflow_detected = overflow_detected - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - - def _run_thread_loop(self): - """ - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.5) - - - - - def __del__(self): - self.relay_controller.close_all() - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 修复版控制逻辑 - 增加控制间隔和状态稳定性 - 堆料情况优先处理,非堆料情况保持适当控制间隔 - 堆料状态变化时立即处理,不受间隔限制 - """ - try: - # 先检查堆料状态 - is_overflow = overflow_detected in ["大堆料", "小堆料"] - self.overflow = is_overflow - - print(f"{self.angle_mode}") - - # 添加控制间隔检查 - current_time = time.time() - # 检查堆料状态是否发生变化 - state_changed = self._last_overflow_state != is_overflow - - if hasattr(self, '_last_control_time'): - time_since_last = current_time - self._last_control_time - # 防止抖动逻辑: - # 1. 非堆料且状态未变化:2秒控制间隔 - # 2. 其他情况(堆料或状态变化):0.5秒最小间隔,避免频繁操作 - if not is_overflow and not state_changed: - # 正常情况:2秒控制间隔 - if time_since_last < 2: - return - else: - # 堆料或状态变化情况:0.5秒最小间隔,防止抖动 - MIN_INTERVAL = 0.5 - if time_since_last < MIN_INTERVAL: - return - # 更新最后控制时间和堆料状态 - self._last_control_time = current_time - self._last_overflow_state = is_overflow - - if current_angle is None: - print("无法获取当前角度,跳过本次调整") - return - - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 当前角度: {current_angle:.2f}°") - - # 定义控制参数 - TARGET_ANGLE = 30.0 - UPPER_DEAD_ZONE = 35.0 # 上死区 - LOWER_DEAD_ZONE = 25.0 # 下死区 - MIN_ANGLE = 5.0 # 最小安全角度 - - # 状态机逻辑 - if self.angle_mode == "normal": - if self.overflow and current_angle > UPPER_DEAD_ZONE: - # 只有角度较高且有堆料时才切换到reducing - self.angle_mode = "reducing" - print("检测到堆料且角度偏高,切换到减小模式") - else: - # 正常模式:维持适当开度 - if current_angle < MIN_ANGLE: - # 角度过小,适当开门 - self._pulse_control("open", 0.3) - elif current_angle > 60.0: # 安全上限 - self._pulse_control("close", 0.5) - else: - # 在正常范围内,根据重量比例控制 - if hasattr(self, 'mould_need_weight') and hasattr(self, 'mould_finish_weight'): - weight_ratio = self.mould_finish_weight / self.mould_need_weight if self.mould_need_weight > 0 else 0 - if weight_ratio < 0.8: - # 需要大量下料,保持开门 - if current_angle < 40.0: - self._pulse_control("open", 0.2) - else: - # 接近完成,半开控制 - if current_angle > 15.0: - self._pulse_control("close", 0.1) - else: - # 默认保持中等开度 - if current_angle < 20.0: - self._pulse_control("open", 0.2) - elif current_angle > 40.0: - self._pulse_control("close", 0.2) - - elif self.angle_mode == "reducing": - if not self.overflow: - # 堆料消失,但需要角度达到安全范围才切换 - if current_angle <= TARGET_ANGLE + 5.0: # 留有余量 - self.angle_mode = "normal" - print("堆料消失且角度合适,返回正常模式") - else: - # 堆料消失但角度仍高,缓慢恢复 - self._pulse_control("close", 0.1) - else: - # 仍有堆料,继续减小角度 - if current_angle > TARGET_ANGLE: - # 角度仍高,继续关门 - pulse_time = min(0.5, (current_angle - TARGET_ANGLE) * 0.02) - self._pulse_control("close", pulse_time) - else: - # 角度已达标,切换到维持模式 - self.angle_mode = "maintaining" - print(f"角度已降至{current_angle:.1f}°,进入维持模式") - - elif self.angle_mode == "maintaining": - if not self.overflow: - self.angle_mode = "normal" - print("堆料消除,返回正常模式") - else: - # 维持模式精确控制 - error = current_angle - TARGET_ANGLE - dead_zone = 3.0 - - if abs(error) < dead_zone: - # 在死区内,停止动作 - self._stop_door() - print(f"角度{current_angle:.1f}°在目标附近,保持静止") - elif error > 0: - # 角度偏高,轻微关门 - pulse_time = min(0.3, error * 0.01) - self._pulse_control("close", pulse_time) - print(f"角度偏高{error:.1f}°,关门{pulse_time:.2f}秒") - else: - # 角度偏低,轻微开门 - pulse_time = min(0.3, abs(error) * 0.01) - self._pulse_control("open", pulse_time) - print(f"角度偏低{abs(error):.1f}°,开门{pulse_time:.2f}秒") - - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - def _pulse_control(self, action, duration): - """统一的脉冲控制方法""" - if duration <= 0: - return - - if action == "open": - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - print(f"开门脉冲: {duration:.2f}秒") - else: # close - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - print(f"关门脉冲: {duration:.2f}秒") - - def _stop_door(self): - """停止门运动""" - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - -# 创建默认实例 -visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -def angle_visual_callback(current_angle, overflow_detected): - """ - 兼容旧版本的函数调用方式 - 将调用转发到默认实例的angle_visual_callback方法 - """ - visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) diff --git a/vision/visual_callback copy 3.py b/vision/visual_callback copy 3.py deleted file mode 100644 index e40259c..0000000 --- a/vision/visual_callback copy 3.py +++ /dev/null @@ -1,273 +0,0 @@ - -from config.settings import app_set_config -from hardware.relay import RelayController -import time -import threading -from datetime import datetime - -class VisualCallback: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.angle_mode = "normal" - self.relay_controller = RelayController() - self.init_weight = 100 - self.mould_finish_weight = 0 - self.mould_need_weight = 100 - self.finish_count = 0 - self.overflow = False - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - self._stop_event = threading.Event() - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - # 初始化控制间隔和堆料状态跟踪属性 - self._last_overflow_state = False - self._last_control_time = 0 - self._initialized = True - - def angle_visual_callback(self, current_angle, overflow_detected): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 回调:{current_angle}") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - self._current_angle = current_angle - if overflow_detected is not None: - self._overflow_detected = overflow_detected - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - - def _run_thread_loop(self): - """ - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.5) - - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 修复版控制逻辑 - 增加控制间隔和状态稳定性 - 堆料情况优先处理,非堆料情况保持适当控制间隔 - 堆料状态变化时立即处理,不受间隔限制 - """ - try: - # 先检查堆料状态 - is_overflow = overflow_detected in ["大堆料", "小堆料"] - self.overflow = is_overflow - - print(f"{self.angle_mode}") - - # 添加控制间隔检查 - current_time = time.time() - # 检查堆料状态是否发生变化 - state_changed = self._last_overflow_state != is_overflow - - if hasattr(self, '_last_control_time'): - time_since_last = current_time - self._last_control_time - # 防止抖动逻辑: - # 1. 非堆料且状态未变化:2秒控制间隔 - # 2. 其他情况(堆料或状态变化):0.5秒最小间隔,避免频繁操作 - # if not is_overflow and not state_changed: - if 1==1: - # 正常情况:2秒控制间隔 - if time_since_last < 1: - return - else: - # 堆料或状态变化情况:0.5秒最小间隔,防止抖动 - MIN_INTERVAL = 0.5 - if time_since_last < MIN_INTERVAL: - return - # 更新最后控制时间和堆料状态 - self._last_control_time = current_time - self._last_overflow_state = is_overflow - - if current_angle is None: - print("无法获取当前角度,跳过本次调整") - return - - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 当前角度: {current_angle:.2f}°") - - # 定义控制参数 - TARGET_ANGLE = 30.0 - UPPER_DEAD_ZONE = 35.0 # 上死区 - LOWER_DEAD_ZONE = 25.0 # 下死区 - MIN_ANGLE = 5.0 # 最小安全角度 - - # 状态机逻辑 - if self.angle_mode == "normal": - if self.overflow and current_angle > UPPER_DEAD_ZONE: - # 只有角度较高且有堆料时才切换到reducing - self.angle_mode = "reducing" - print("检测到堆料且角度偏高,切换到减小模式") - else: - # 正常模式:维持适当开度 - if current_angle < MIN_ANGLE: - # 角度过小,适当开门 - self._pulse_control("open", 0.3) - elif current_angle > 60.0: # 安全上限 - self._pulse_control("close", 0.5) - else: - # 在正常范围内,根据重量比例控制 - if hasattr(self, 'mould_need_weight') and hasattr(self, 'mould_finish_weight'): - weight_ratio = self.mould_finish_weight / self.mould_need_weight if self.mould_need_weight > 0 else 0 - if weight_ratio < 0.8: - # 需要大量下料,保持开门 - if current_angle < 40.0: - self._pulse_control("open", 0.2) - else: - # 接近完成,半开控制 - if current_angle > 15.0: - self._pulse_control("close", 0.1) - else: - # 默认保持中等开度 - if current_angle < 20.0: - self._pulse_control("open", 0.2) - elif current_angle > 40.0: - self._pulse_control("close", 0.2) - - elif self.angle_mode == "reducing": - if not self.overflow: - # 堆料消失,但需要角度达到安全范围才切换 - if current_angle <= TARGET_ANGLE + 5.0: # 留有余量 - self.angle_mode = "normal" - print("堆料消失且角度合适,返回正常模式") - else: - # 堆料消失但角度仍高,缓慢恢复 - self._pulse_control("close", 0.1) - else: - # 仍有堆料,继续减小角度 - if current_angle > TARGET_ANGLE: - # 角度仍高,继续关门 - pulse_time = min(0.5, (current_angle - TARGET_ANGLE) * 0.02) - self._pulse_control("close", pulse_time) - else: - # 角度已达标,切换到维持模式 - self.angle_mode = "maintaining" - print(f"角度已降至{current_angle:.1f}°,进入维持模式") - - elif self.angle_mode == "maintaining": - if not self.overflow: - self.angle_mode = "normal" - print("堆料消除,返回正常模式") - else: - # 维持模式精确控制 - error = current_angle - TARGET_ANGLE - dead_zone = 3.0 - - if abs(error) < dead_zone: - # 在死区内,停止动作 - self._stop_door() - print(f"角度{current_angle:.1f}°在目标附近,保持静止") - elif error > 0: - # 角度偏高,轻微关门 - pulse_time = min(0.3, error * 0.01) - self._pulse_control("close", pulse_time) - print(f"角度偏高{error:.1f}°,关门{pulse_time:.2f}秒") - else: - # 角度偏低,轻微开门 - pulse_time = min(0.3, abs(error) * 0.01) - self._pulse_control("open", pulse_time) - print(f"角度偏低{abs(error):.1f}°,开门{pulse_time:.2f}秒") - - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - def _pulse_control(self, action, duration): - """统一的脉冲控制方法""" - if duration <= 0: - return - - if action == "open": - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - print(f"开门脉冲: {duration:.2f}秒") - else: # close - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - print(f"关门脉冲: {duration:.2f}秒") - - def _stop_door(self): - """停止门运动""" - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - -# 创建默认实例 -visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -def angle_visual_callback(current_angle, overflow_detected): - """ - 兼容旧版本的函数调用方式 - 将调用转发到默认实例的angle_visual_callback方法 - """ - visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) diff --git a/vision/visual_callback copy 4.py b/vision/visual_callback copy 4.py deleted file mode 100644 index d4a4a15..0000000 --- a/vision/visual_callback copy 4.py +++ /dev/null @@ -1,452 +0,0 @@ - -from config.settings import app_set_config -from hardware.relay import RelayController -from hardware.transmitter import TransmitterController -import time -import threading -from datetime import datetime - -class VisualCallback: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.angle_mode = "normal" - self.relay_controller = RelayController() - self.transmitter_controller = TransmitterController(self.relay_controller) - self.init_weight = 100 - self.mould_finish_weight = 0 - self.mould_need_weight = 100 - self.finish_count = 0 - self.overflow = False - self.is_start_visual=False - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - self._stop_event = threading.Event() - - # 添加下料斗门控制锁,防止两个线程同时控制 - self._door_control_lock = threading.Lock() - # 记录当前控制门的线程名称,用于调试 - self._current_controlling_thread = None - # 新增标志位:指示safe_control_lower_close是否正在执行 - self._is_safe_closing = False - - self._is_feed_start=False - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - - self.feed_thread = threading.Thread( - target=self._run_feed, - daemon=True - ) - self.feed_thread.start() - - - - # 初始化控制间隔和堆料状态跟踪属性 - self._last_overflow_state = False - self._last_control_time = 0 - self._initialized = True - - def angle_visual_callback(self, current_angle, overflow_detected): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到推送数据") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - - if overflow_detected is not None: - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到溢料:{overflow_detected}") - self._overflow_detected = overflow_detected - if current_angle is not None: - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到角度:{current_angle}") - self._current_angle = current_angle - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - - def _run_thread_loop(self): - """ - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - self._is_feed_start=True - if self.is_start_visual: - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.1) - def _run_feed(self): - while True: - print("------------已启动----------------") - if self._is_feed_start: - print("------------下料启动----------------") - print("------------下料启动----------------") - print("------------下料启动----------------") - self.run_feed() - break - time.sleep(0.5) - - - - def run_feed(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("--------------------开始下料--------------------") - loc_relay=self.relay_controller - loc_mitter=self.transmitter_controller - - initial_lower_weight=loc_mitter.read_data(2) - initial_upper_weight=loc_mitter.read_data(1) - first_finish_weight=0 - start_time=None - self.is_start_visual=True - - def safe_control_lower_close(): - """线程安全的下料斗关闭方法""" - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试关闭下料斗...") - # 设置标志位,指示正在执行安全关闭操作 - self._is_safe_closing = True - try: - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行关闭操作") - loc_relay.control_lower_close() - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - finally: - # 无论成功失败,都要重置标志位 - self._is_safe_closing = False - - while True: - loc_mitter.is_start_lower=True - current_weight = loc_mitter.read_data(2) - first_finish_weight=initial_lower_weight-current_weight - if current_weight<500: - # 破拱控制 - if start_time is None or time.time()-start_time>5: - start_time=time.time() - loc_relay.control_arch_lower_open() - - if current_weight<100: - start_time=None - self.is_start_visual=False - loc_mitter.is_start_lower=False - time.sleep(0.5) - safe_control_lower_close() - break - print(f'------------已下料: {first_finish_weight}kg-------------') - time.sleep(1) - - #打开上料斗出砼门,开5就,开三分之一下 - loc_relay.control_upper_open_sync(4) - upper_open_time=time.time() - while True: - print(f'------------上料斗向下料斗转移-------------') - loc_mitter.is_start_upper=True - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight<4000: - #关5秒 - loc_relay.control_upper_close() - loc_mitter.is_start_upper=False - break - else: - if time.time()-upper_open_time>2: - upper_open_time=time.time() - loc_relay.control_upper_open_sync(0.5) - else: - time.sleep(0.5) - # time.sleep(0.4) - - self.is_start_visual=True - loc_mitter.is_start_lower=False - loc_mitter.test_lower_weight=2000 - initial_lower_weight=loc_mitter.read_data(2) - - while True: - loc_mitter.is_start_lower=True - current_weight = loc_mitter.read_data(2) - second_finish_weight=initial_lower_weight-current_weight - if current_weight<500: - #关5秒 - if start_time is None or time.time()-start_time>5: - start_time=time.time() - loc_relay.control_arch_lower_open() - if current_weight<100: - start_time=None - self.is_start_visual=False - loc_mitter.is_start_lower=False - time.sleep(0.5) - - safe_control_lower_close() - break - print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) - - print(f'------------已完成-------------') - print(f'------------已完成-------------') - print(f'------------已完成-------------') - - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 实时精细控制 - 基于PID思想,无固定间隔 - """ - try: - # 记录控制时间戳(用于微分计算,而非限制) - current_time = time.time() - # 确保所有PID相关属性都被正确初始化 - if not hasattr(self, '_last_control_time'): - self._last_control_time = current_time - if not hasattr(self, '_last_error'): - self._last_error = 0 - if not hasattr(self, '_error_integral'): - self._error_integral = 0 - - print(f"{self.angle_mode}") - self.overflow = overflow_detected in ["大堆料", "小堆料"] - - if current_angle is None: - return - - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 当前角度: {current_angle:.2f}°") - - # 根据溢料状态动态调整目标角度 - if overflow_detected == "大堆料": - TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 - elif overflow_detected == "小堆料": - TARGET_ANGLE = 35.0 # 小堆料时控制在35度左右 - else: - TARGET_ANGLE = 56.0 # 未溢料时开到最大56度 - - # 确保目标角度在硬件范围内(5-56度) - TARGET_ANGLE = max(5.0, min(56.0, TARGET_ANGLE)) - - # PID控制参数 - KP = 0.1 # 比例系数 - KI = 0.01 # 积分系数 - KD = 0.05 # 微分系数 - - # 计算误差 - error = current_angle - TARGET_ANGLE - dt = current_time - self._last_control_time - - # 积分项(抗饱和) - self._error_integral += error * dt - self._error_integral = max(min(self._error_integral, 50), -50) # 积分限幅 - - # 微分项 - error_derivative = (error - self._last_error) / dt if dt > 0 else 0 - - # PID输出 - pid_output = (KP * error + KI * self._error_integral + KD * error_derivative) - - # 更新历史值 - self._last_error = error - self._last_control_time = current_time - - # 状态机 + PID控制 - - if self.angle_mode == "normal": - self._normal_mode_advanced(current_angle, pid_output) - - elif self.angle_mode == "reducing": - self._reducing_mode_advanced(current_angle, pid_output, TARGET_ANGLE) - - elif self.angle_mode == "maintaining": - self._maintaining_mode_advanced(current_angle, pid_output, TARGET_ANGLE) - - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - def _normal_mode_advanced(self, current_angle, pid_output): - """高级正常模式控制""" - if self.overflow: - self.angle_mode = "reducing" - print("检测到溢料,切换到减小模式") - return - - # 基于PID输出的智能控制 - control_threshold = 2.0 # 控制死区 - - if abs(pid_output) > control_threshold: - if pid_output > 0: - # 需要减小角度(关门) - pulse_time = min(0.3, pid_output * 0.1) - self._pulse_control("close", pulse_time) - print(f"正常模式: 角度偏高{pid_output:.1f},关门{pulse_time:.2f}秒") - else: - # 需要增大角度(开门) - pulse_time = min(0.3, abs(pid_output) * 0.1) - self._pulse_control("open", pulse_time) - print(f"正常模式: 角度偏低{abs(pid_output):.1f},开门{pulse_time:.2f}秒") - else: - # 在死区内,保持静止 - self._stop_door() - print(f"正常模式: 角度在目标范围内,保持静止") - - def _reducing_mode_advanced(self, current_angle, pid_output, target_angle): - """高级减小模式控制""" - if not self.overflow: - if current_angle <= target_angle + 5.0: - self.angle_mode = "normal" - print("溢料消除且角度合适,返回正常模式") - else: - # 缓慢恢复 - self._pulse_control("close", 0.1) - return - - # 有溢料,积极减小角度 - if current_angle > target_angle: - # 使用PID输出计算控制量 - pulse_time = min(0.5, max(0.1, pid_output * 0.15)) - self._pulse_control("close", pulse_time) - print(f"减小模式: 积极关门{pulse_time:.2f}秒,PID输出:{pid_output:.1f}") - else: - self.angle_mode = "maintaining" - print("角度已达标,进入维持模式") - - def _maintaining_mode_advanced(self, current_angle, pid_output, target_angle): - """高级维持模式控制""" - if not self.overflow: - self.angle_mode = "normal" - print("溢料消除,返回正常模式") - return - - # 精确维持控制 - dead_zone = 1.5 # 更小的死区 - - if abs(pid_output) > dead_zone: - pulse_time = min(0.2, abs(pid_output) * 0.05) # 更精细的控制 - - if pid_output > 0: - self._pulse_control("close", pulse_time) - print(f"维持模式: 微调关门{pulse_time:.2f}秒") - else: - self._pulse_control("open", pulse_time) - print(f"维持模式: 微调开门{pulse_time:.2f}秒") - else: - self._stop_door() - print("维持模式: 角度精确控制中") - - def _pulse_control(self, action, duration): - """统一的脉冲控制方法""" - # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 - if self._is_safe_closing: - thread_name = threading.current_thread().name - print(f"[{thread_name}] safe_control_lower_close正在执行,跳过脉冲控制 {action}") - return - - if duration <= 0: - return - - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试脉冲控制 {action},时长 {duration:.2f}秒...") - - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行脉冲控制") - - if action == "open": - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - print(f"[{thread_name}] 开门脉冲: {duration:.2f}秒") - else: # close - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - print(f"[{thread_name}] 关门脉冲: {duration:.2f}秒") - - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - - def _stop_door(self): - """停止门运动""" - # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 - if self._is_safe_closing: - thread_name = threading.current_thread().name - print(f"[{thread_name}] safe_control_lower_close正在执行,跳过停止门运动操作") - return - - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试停止门运动...") - - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行停止操作") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - -# 创建默认实例 -visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -def angle_visual_callback(current_angle, overflow_detected): - """ - 兼容旧版本的函数调用方式 - 将调用转发到默认实例的angle_visual_callback方法 - """ - visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) diff --git a/vision/visual_callback copy.py b/vision/visual_callback copy.py deleted file mode 100644 index 1beb39b..0000000 --- a/vision/visual_callback copy.py +++ /dev/null @@ -1,240 +0,0 @@ - -from config.settings import app_set_config -from hardware.relay import RelayController -import time -import threading -from datetime import datetime - -class VisualCallback: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.angle_mode = "normal" - self.relay_controller = RelayController() - self.init_weight = 100 - self.mould_finish_weight = 0 - self.mould_need_weight = 100 - self.finish_count = 0 - self.overflow = False - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - self._stop_event = threading.Event() - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - self._initialized = True - - def angle_visual_callback(self, current_angle, overflow_detected): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 回调:{current_angle}") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - self._current_angle = current_angle - if overflow_detected is not None: - self._overflow_detected = overflow_detected - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - - def _run_thread_loop(self): - """ - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.5) - - - - - def __del__(self): - self.relay_controller.close_all() - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 内部方法,实际处理视觉回调逻辑 - 在异步线程中执行 - """ - try: - # print('current_angle:', current_angle, 'overflow_detected:', overflow_detected) - # return - # 检测溢出状态 - print(f"{self.angle_mode}") - self.overflow = overflow_detected in ["大堆料", "小堆料"] - if current_angle is None: - print("无法获取当前角度,跳过本次调整") - return - - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 当前角度: {current_angle:.2f}°") - - if True: - # 状态机控制逻辑 - if self.angle_mode == "normal": - # 正常模式大于app_set_config.angle_threshold=60度 - if self.overflow: - self.angle_mode = "reducing" - else: - # 保持正常开门 - # print(f'当前重量:{self.mould_finish_weight:.2f}kg, 目标重量:{self.mould_need_weight:.2f}kg') - if self.mould_need_weight > 0: - if self.mould_finish_weight / self.mould_need_weight >= 0.8: - print(f"完成重量占比{self.mould_finish_weight/self.mould_need_weight:.2f},半开出砼门") - # 半开出砼门 - if current_angle > app_set_config.target_angle: - # 角度已降至目标范围,关闭出砼门 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(0.3) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.32) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - else: - # 全开砼门 - if current_angle > app_set_config.angle_threshold: - #self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE,'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE,'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN,'open') - else: - # 全开砼门 - if current_angle > app_set_config.angle_threshold: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - elif self.angle_mode == "reducing": - # 角度减小模式 - if self.overflow: - if current_angle <= app_set_config.target_angle: - # 角度已达到目标范围,仍有堆料,进入维持模式 - print(f"角度已降至{current_angle:.2f}°,仍有堆料,进入维持模式") - if current_angle <= app_set_config.min_angle: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(0.1) - self.angle_mode = "maintaining" - else: - print(f"角度大于30,继续关闭") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - else: - # 无堆料,恢复正常模式 - print(f"角度已降至{current_angle:.2f}°,无堆料,恢复正常模式") - self.angle_mode = "normal" - - elif self.angle_mode == "maintaining": - # 维持模式 - 使用脉冲控制 - if not self.overflow: - # 堆料已消除,恢复正常模式 - print("堆料已消除,恢复正常模式") - self.angle_mode = "normal" - else: - # 继续维持角度控制 - print("进入维持模式") - # 关门时间 - if current_angle > app_set_config.target_angle: - # 角度已降至目标范围,关闭出砼门 - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - #time.sleep(0.3) - # self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - else: - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - #time.sleep(0.32) - # self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - - elif self.angle_mode == "recovery": - # 恢复模式 - 逐步打开门 - if self.overflow: - # 又出现堆料,回到角度减小模式 - print("恢复过程中又检测到堆料,回到角度减小模式") - self.angle_mode = "maintaining" - else: - # 堆料已消除,恢复正常模式 - print("堆料已消除,恢复正常模式") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.angle_mode = "normal" - # else: - # 浇筑满,关闭下料门 - # self.relay_controller.control_lower_close() - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - -# 创建默认实例 -visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -def angle_visual_callback(current_angle, overflow_detected): - """ - 兼容旧版本的函数调用方式 - 将调用转发到默认实例的angle_visual_callback方法 - """ - visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) diff --git a/vision/visual_callback.py b/vision/visual_callback.py index 4ebbb1f..97b2982 100644 --- a/vision/visual_callback.py +++ b/vision/visual_callback.py @@ -298,7 +298,6 @@ class VisualCallback: timestamp = datetime.now().strftime("%H:%M:%S") f.write(f"{timestamp} - {self._finish_weight}\n") - def run_feed_all(self): """ 全流程下料:包括判断模具类型 @@ -374,7 +373,6 @@ class VisualCallback: print(f'------------已完成-------------') - def run_feed(self): """第一阶段下料:下料斗向模具车下料(低速)""" print("--------------------开始下料(普通块)--------------------") diff --git a/vision/visual_callback_1203.py b/vision/visual_callback_1203.py deleted file mode 100644 index 5476bde..0000000 --- a/vision/visual_callback_1203.py +++ /dev/null @@ -1,480 +0,0 @@ - -from cv2.gapi import ov -from config.settings import app_set_config -from hardware.relay import RelayController -from hardware.transmitter import TransmitterController -import time -import threading -from datetime import datetime - -class VisualCallback: - # 类变量,用于存储实例引用,实现单例检测 - _instance = None - _lock = threading.Lock() - - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def __init__(self): - """初始化视觉回调处理器""" - # 避免重复初始化 - if hasattr(self, '_initialized') and self._initialized: - return - - self.angle_mode = "normal" - self.relay_controller = RelayController() - self.transmitter_controller = TransmitterController(self.relay_controller) - self.init_weight = 100 - self.mould_finish_weight = 0 - self.mould_need_weight = 100 - self.finish_count = 0 - self.overflow = False - self.is_start_visual=True - - # 线程安全的参数传递 - self._current_angle = None - self._overflow_detected = None - self._new_data_available = threading.Event() - self._is_processing = threading.Lock() - self._stop_event = threading.Event() - - # 添加下料斗门控制锁,防止两个线程同时控制 - self._door_control_lock = threading.Lock() - # 记录当前控制门的线程名称,用于调试 - self._current_controlling_thread = None - # 新增标志位:指示safe_control_lower_close是否正在执行 - self._is_safe_closing = False - - self._is_feed_start=False - - # 创建并启动单个持续运行的线程 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - - self.feed_thread = threading.Thread( - target=self._run_feed, - daemon=True - ) - # self.feed_thread.start() - - self._before_finish_time=None - self._is_finish=False - - # 初始化控制间隔和堆料状态跟踪属性 - self._last_overflow_state = False - self._last_control_time = 0 - self._initialized = True - - def angle_visual_callback(self, current_angle, overflow_detected): - """ - 视觉控制主逻辑,供外部推送数据 - 使用单个持续运行的线程,通过参数设置传递数据 - 如果线程正在处理数据,则丢弃此次推送 - """ - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到推送数据") - # 尝试获取处理锁,若失败则说明正在处理,丢弃数据 - if not self._is_processing.acquire(blocking=False): - print("回调线程仍在执行,丢弃此次推送数据") - return - - try: - # 更新参数 - - if overflow_detected is not None: - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到溢料:{overflow_detected}") - self._overflow_detected = overflow_detected - if current_angle is not None: - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到角度:{current_angle}") - self._current_angle = current_angle - # 通知线程有新数据可用 - self._new_data_available.set() - finally: - # 释放处理锁 - self._is_processing.release() - - - def _run_thread_loop(self): - """ - 线程主循环,持续运行 - 等待新数据,然后调用处理方法 - """ - while not self._stop_event.is_set(): - # 等待新数据可用 - self._new_data_available.wait() - - # 重置事件 - self._new_data_available.clear() - - # 获取当前参数(使用临时变量避免被其他线程修改) - current_angle = self._current_angle - overflow_detected = self._overflow_detected - self._is_feed_start=True - if self.is_start_visual: - # 处理数据 - self._process_angle_callback(current_angle, overflow_detected) - time.sleep(0.1) - def _run_feed(self): - while True: - print("------------已启动----------------") - if self._is_feed_start: - print("------------下料启动----------------") - print("------------下料启动----------------") - print("------------下料启动----------------") - self.run_feed() - break - time.sleep(0.5) - - - def safe_control_lower_close(self,duration=3): - """线程安全的下料斗关闭方法""" - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试关闭下料斗...") - # 设置标志位,指示正在执行安全关闭操作 - self._is_safe_closing = True - try: - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行关闭操作") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - finally: - # 无论成功失败,都要重置标志位 - self._is_safe_closing = False - def run_feed(self): - """第一阶段下料:下料斗向模具车下料(低速)""" - print("--------------------开始下料--------------------") - loc_relay=self.relay_controller - loc_mitter=self.transmitter_controller - - initial_lower_weight=loc_mitter.read_data(2) - initial_upper_weight=loc_mitter.read_data(1) - first_finish_weight=0 - start_time=None - self.is_start_visual=True - - - - while True: - loc_mitter.is_start_lower=True - current_weight = loc_mitter.read_data(2) - first_finish_weight=initial_lower_weight-current_weight - if current_weight<500: - # 破拱控制 - if start_time is None or time.time()-start_time>5: - start_time=time.time() - loc_relay.control_arch_lower_open() - - if current_weight<100: - start_time=None - self.is_start_visual=False - loc_mitter.is_start_lower=False - time.sleep(0.5) - self.safe_control_lower_close() - break - print(f'------------已下料: {first_finish_weight}kg-------------') - time.sleep(1) - - #打开上料斗出砼门,开5就,开三分之一下 - loc_relay.control_upper_open_sync(6) - loc_time_count=1 - upper_open_time=time.time() - while True: - print(f'------------上料斗向下料斗转移-------------') - loc_mitter.is_start_upper=True - current_upper_weight = loc_mitter.read_data(1) - if current_upper_weight<4000: - #关5秒 - loc_relay.control_upper_close_sync(4+loc_time_count) - loc_mitter.is_start_upper=False - break - else: - if time.time()-upper_open_time>3: - upper_open_time=time.time() - loc_relay.control_upper_open_sync(0.5) - loc_time_count=loc_time_count+0.5 - else: - time.sleep(0.5) - # time.sleep(0.4) - - self.is_start_visual=True - loc_mitter.is_start_lower=False - loc_mitter.test_lower_weight=2000 - initial_lower_weight=loc_mitter.read_data(2) - - while True: - loc_mitter.is_start_lower=True - current_weight = loc_mitter.read_data(2) - second_finish_weight=initial_lower_weight-current_weight - if current_weight<500: - #关5秒 - if start_time is None or time.time()-start_time>5: - start_time=time.time() - loc_relay.control_arch_lower_open() - if current_weight<100: - start_time=None - self.is_start_visual=False - loc_mitter.is_start_lower=False - time.sleep(0.5) - - self.safe_control_lower_close() - break - print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) - - print(f'------------已完成-------------') - print(f'------------已完成-------------') - print(f'------------已完成-------------') - - - def _process_angle_callback(self, current_angle, overflow_detected): - """ - 实时精细控制 - 基于PID思想,无固定间隔 - """ - try: - # 记录控制时间戳(用于微分计算,而非限制) - current_time = time.time() - # 确保所有PID相关属性都被正确初始化 - if not hasattr(self, '_last_control_time'): - self._last_control_time = current_time - if not hasattr(self, '_last_error'): - self._last_error = 0 - if not hasattr(self, '_error_integral'): - self._error_integral = 0 - - print(f"{self.angle_mode}") - self.overflow = overflow_detected in ["大堆料", "小堆料"] - - if current_angle is None: - return - - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 当前角度: {current_angle:.2f}°") - - if overflow_detected == "未浇筑满": - if self._before_finish_time is None: - self._before_finish_time=current_time - self.safe_control_lower_close(1) - if time.time()-self._before_finish_time>3: - TARGET_ANGLE=25 - elif overflow_detected == "浇筑满": - self.is_start_visual=False - self._is_finish=True - self.safe_control_lower_close(3) - return - else: - TARGET_ANGLE=25 - # 根据溢料状态动态调整目标角度 - # if overflow_detected == "大堆料": - # TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 - # elif overflow_detected == "小堆料": - # TARGET_ANGLE = 25.0 # 小堆料时控制在35度左右 - # else: - # TARGET_ANGLE = 45.0 # 未溢料时开到最大56度 - - # 确保目标角度在硬件范围内(5-56度) - TARGET_ANGLE = max(5.0, min(56.0, TARGET_ANGLE)) - - # PID控制参数 - KP = 0.1 # 比例系数 - KI = 0.01 # 积分系数 - KD = 0.05 # 微分系数 - - # 计算误差 - error = current_angle - TARGET_ANGLE - dt = current_time - self._last_control_time - - # 积分项(抗饱和) - self._error_integral += error * dt - self._error_integral = max(min(self._error_integral, 50), -50) # 积分限幅 - - # 微分项 - error_derivative = (error - self._last_error) / dt if dt > 0 else 0 - - # PID输出 - pid_output = (KP * error + KI * self._error_integral + KD * error_derivative) - - # 更新历史值 - self._last_error = error - self._last_control_time = current_time - - # 状态机 + PID控制 - - if self.angle_mode == "normal": - self._normal_mode_advanced(current_angle, pid_output) - - elif self.angle_mode == "reducing": - self._reducing_mode_advanced(current_angle, pid_output, TARGET_ANGLE) - - elif self.angle_mode == "maintaining": - self._maintaining_mode_advanced(current_angle, pid_output, TARGET_ANGLE) - - except Exception as e: - print(f"处理视觉回调时发生异常: {e}") - - def _normal_mode_advanced(self, current_angle, pid_output): - """高级正常模式控制""" - if self.overflow: - self.angle_mode = "reducing" - print("检测到溢料,切换到减小模式") - return - - # 基于PID输出的智能控制 - control_threshold = 2.0 # 控制死区 - - if abs(pid_output) > control_threshold: - if pid_output > 0: - # 需要减小角度(关门) - pulse_time = min(0.3, pid_output * 0.1) - self._pulse_control("close", pulse_time) - print(f"正常模式: 角度偏高{pid_output:.1f},关门{pulse_time:.2f}秒") - else: - # 需要增大角度(开门) - pulse_time = min(0.3, abs(pid_output) * 0.1) - self._pulse_control("open", pulse_time) - print(f"正常模式: 角度偏低{abs(pid_output):.1f},开门{pulse_time:.2f}秒") - else: - # 在死区内,保持静止 - self._stop_door() - print(f"正常模式: 角度在目标范围内,保持静止") - - def _reducing_mode_advanced(self, current_angle, pid_output, target_angle): - """高级减小模式控制""" - if not self.overflow: - if current_angle <= target_angle + 5.0: - self.angle_mode = "normal" - print("溢料消除且角度合适,返回正常模式") - else: - # 缓慢恢复 - self._pulse_control("close", 0.1) - return - - # 有溢料,积极减小角度 - if current_angle > target_angle: - # 使用PID输出计算控制量 - pulse_time = min(0.5, max(0.1, pid_output * 0.15)) - self._pulse_control("close", pulse_time) - print(f"减小模式: 积极关门{pulse_time:.2f}秒,PID输出:{pid_output:.1f}") - else: - self.angle_mode = "maintaining" - print("角度已达标,进入维持模式") - - def _maintaining_mode_advanced(self, current_angle, pid_output, target_angle): - """高级维持模式控制""" - if not self.overflow: - self.angle_mode = "normal" - print("溢料消除,返回正常模式") - return - - # 精确维持控制 - dead_zone = 1.5 # 更小的死区 - - if abs(pid_output) > dead_zone: - pulse_time = min(0.2, abs(pid_output) * 0.05) # 更精细的控制 - - if pid_output > 0: - self._pulse_control("close", pulse_time) - print(f"维持模式: 微调关门{pulse_time:.2f}秒") - else: - self._pulse_control("open", pulse_time) - print(f"维持模式: 微调开门{pulse_time:.2f}秒") - else: - self._stop_door() - print("维持模式: 角度精确控制中") - - def _pulse_control(self, action, duration): - """统一的脉冲控制方法""" - # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 - if self._is_safe_closing: - thread_name = threading.current_thread().name - print(f"[{thread_name}] safe_control_lower_close正在执行,跳过脉冲控制 {action}") - return - - if duration <= 0: - return - - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试脉冲控制 {action},时长 {duration:.2f}秒...") - - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行脉冲控制") - - if action == "open": - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - print(f"[{thread_name}] 开门脉冲: {duration:.2f}秒") - else: # close - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') - time.sleep(duration) - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - print(f"[{thread_name}] 关门脉冲: {duration:.2f}秒") - - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - - def _stop_door(self): - """停止门运动""" - # 检查是否正在执行safe_control_lower_close,如果是则跳过relay操作 - if self._is_safe_closing: - thread_name = threading.current_thread().name - print(f"[{thread_name}] safe_control_lower_close正在执行,跳过停止门运动操作") - return - - thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试停止门运动...") - - with self._door_control_lock: - self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行停止操作") - self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') - self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') - self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") - - def _open_door(self, duration=0.5): - """打开门""" - self._pulse_control("open", 0.3) - - def _close_door(self, duration=0.5): - """关闭门""" - self._pulse_control("close", 1) - - @classmethod - def instance_exists(cls): - """检测实例是否存在""" - return cls._instance is not None - - def shutdown(self): - """关闭线程,清理资源""" - # 设置停止事件 - self._stop_event.set() - # 唤醒线程以便它能检测到停止事件 - self._new_data_available.set() - # 等待线程结束 - if self.callback_thread.is_alive(): - self.callback_thread.join(timeout=1.0) - -# 创建默认实例 -visual_callback_instance = VisualCallback() - -# 兼容层,保持原来的函数调用方式可用 -def angle_visual_callback(current_angle, overflow_detected): - """ - 兼容旧版本的函数调用方式 - 将调用转发到默认实例的angle_visual_callback方法 - """ - visual_callback_instance.angle_visual_callback(current_angle, overflow_detected) diff --git a/vision/visual_callback_dq.py b/vision/visual_callback_dq.py index 299ecf4..ef94117 100644 --- a/vision/visual_callback_dq.py +++ b/vision/visual_callback_dq.py @@ -33,14 +33,17 @@ class VisualCallback: # cls._instance = super().__new__(cls) # return cls._instance - def __init__(self,state:SystemState=None): + def __init__(self, + relay_controller:RelayController, + transmitter_controller:TransmitterController, + state:SystemState=None): """初始化视觉回调处理器""" # 避免重复初始化 if hasattr(self, '_initialized') and self._initialized: return - self.relay_controller = RelayController() - self.transmitter_controller = TransmitterController(self.relay_controller) + self.relay_controller = relay_controller + self.transmitter_controller = transmitter_controller self.pd_record_bll=PDRecordBll() self.state=state @@ -87,48 +90,9 @@ class VisualCallback: self._cur_mould_model=None # self.db_queue=queue.Queue() - # self.plc_data=5 - self.plc_service = OmronFinsPollingService("192.168.250.233") - self.plc_service.register_data_callback(self.on_plc_update) - # self.plc_service.register_status_callback(self.on_status_change) - self.plc_service.start_polling(interval=2.0) - - # 获取视觉数据线程(angle_visual_callback推送的数据),并进行处理,注意处理视觉进行夹角控制 - self.callback_thread = threading.Thread( - target=self._run_thread_loop, - daemon=True - ) - self.callback_thread.start() - - # 启动下料线程检测 - self.feed_thread = threading.Thread( - target=self._run_feed, - daemon=True - ) - self.feed_thread.start() - - #启动振动线程 - self.monitor_thread = threading.Thread( - target=self._monitor_loop, - daemon=True, - name='monitor' - ) - self.monitor_thread.start() - - #获取diff(diff_visual_callback推送的数据)数据线程 - self.diff_thread = threading.Thread( - target=self._diff_temp, - daemon=True - ) - self.diff_thread.start() - - """启动数据库监控""" - # self.db_thread = threading.Thread( - # target=self._monitor_db_loop, - # daemon=True, - # name='db_monitor' - # ) - # self.db_thread.start() + # 获取视觉数据线程(angle_visual_callback推送的数据),并进行处理,注意处理视觉进行夹角控制 + self.callback_thread = None + self.diff_thread = None def init_val(self): #初始化值 @@ -197,6 +161,20 @@ class VisualCallback: self._point_speed_grade=0 self._point_weight=0 + def start_visual_thread(self)->bool: + """浇筑状态回调线程""" + self.callback_thread = threading.Thread( + target=self._run_thread_loop, + daemon=True + ) + self.callback_thread.start() + #diff线程值传递后处理 + self.diff_thread = threading.Thread( + target=self._diff_temp, + daemon=True + ) + self.diff_thread.start() + return True def angle_visual_callback(self, current_angle, overflow_detected, mould_aligned): """ @@ -313,7 +291,7 @@ class VisualCallback: _temp_area_str='' time.sleep(1) - def _monitor_loop(self): + def _arch_loop(self): """破拱线程""" while self._is_running: try: @@ -1380,17 +1358,12 @@ class VisualCallback: self.is_start_visual=False # #关闭下料斗 # self.safe_control_lower_close() - if self.plc_service: - self.plc_service.stop_polling() + # 等待线程结束 if self.callback_thread.is_alive(): self.callback_thread.join(timeout=1.0) - - if self.feed_thread.is_alive(): - self.feed_thread.join(timeout=1.0) - - if self.monitor_thread.is_alive(): - self.monitor_thread.join(timeout=1.0) + if self.diff_thread: + self.diff_thread.join(timeout=1.0) # self.relay_controller._close_lower_5s diff --git a/vision/visual_callback_dq copy.py b/vision/visual_callback_dq0209.py similarity index 68% rename from vision/visual_callback_dq copy.py rename to vision/visual_callback_dq0209.py index 8129c7e..299ecf4 100644 --- a/vision/visual_callback_dq copy.py +++ b/vision/visual_callback_dq0209.py @@ -1,5 +1,6 @@ from pickle import FALSE +from re import S from cv2.gapi import ov from config.settings import app_set_config from hardware.relay import RelayController @@ -12,20 +13,27 @@ import queue from hardware.upper_plc import OmronFinsPollingService from vision.muju_cls.muju_utils import run_stable_classification_loop from vision.camera_picture import save_camera_picture +from busisness.blls import ArtifactBll,PDRecordBll +from busisness.models import ArtifactInfoModel,PDRecordModel +from service.mould_service import app_web_service +from core.system_state import SystemState,FeedStatus +from dataclasses import asdict +import json +import math class VisualCallback: # 类变量,用于存储实例引用,实现单例检测 _instance = None _lock = threading.Lock() - def __new__(cls): - """检测实例是否存在,实现单例模式""" - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance + # def __new__(cls,*args, **kwargs): + # """检测实例是否存在,实现单例模式""" + # with cls._lock: + # if cls._instance is None: + # cls._instance = super().__new__(cls) + # return cls._instance - def __init__(self): + def __init__(self,state:SystemState=None): """初始化视觉回调处理器""" # 避免重复初始化 if hasattr(self, '_initialized') and self._initialized: @@ -33,6 +41,8 @@ class VisualCallback: self.relay_controller = RelayController() self.transmitter_controller = TransmitterController(self.relay_controller) + self.pd_record_bll=PDRecordBll() + self.state=state # 线程安全的参数传递 self._new_data_available = threading.Event() @@ -54,6 +64,7 @@ class VisualCallback: self._current_controlling_thread = None #是否启动后的第一个模具 self._is_first_module=True + self.init_val() # self._setup_logging_2() #F块完成重量的70%,控制夹脚,F块多于这个比例就没有记录了(注意) @@ -64,7 +75,7 @@ class VisualCallback: #重量大于95%,停留时间2秒,其他的1秒 self._weight_ratio_955=0.955 #完成多少,忽略未浇筑满 - self._max_ignore_radio=0.5 + self._max_ignore_radio=0.8 self._mould_accept_aligned=None self._mould_before_aligned=False @@ -72,7 +83,8 @@ class VisualCallback: self._time_mould_begin='' #模具结束浇筑时间 self._time_mould_end='' - + #记录当前模具信息model + self._cur_mould_model=None # self.db_queue=queue.Queue() # self.plc_data=5 @@ -81,20 +93,21 @@ class VisualCallback: # self.plc_service.register_status_callback(self.on_status_change) self.plc_service.start_polling(interval=2.0) - # 创建并启动单个持续运行的线程 + # 获取视觉数据线程(angle_visual_callback推送的数据),并进行处理,注意处理视觉进行夹角控制 self.callback_thread = threading.Thread( target=self._run_thread_loop, daemon=True ) self.callback_thread.start() + # 启动下料线程检测 self.feed_thread = threading.Thread( target=self._run_feed, daemon=True ) self.feed_thread.start() - """启动系统监控""" + #启动振动线程 self.monitor_thread = threading.Thread( target=self._monitor_loop, daemon=True, @@ -102,7 +115,7 @@ class VisualCallback: ) self.monitor_thread.start() - + #获取diff(diff_visual_callback推送的数据)数据线程 self.diff_thread = threading.Thread( target=self._diff_temp, daemon=True @@ -143,6 +156,9 @@ class VisualCallback: #用于判断当前判断是否对齐(diff) self._is_diff_unaligned=False + self._diff_f_val=0 + self._diff_f_area=[] + #浇筑完成比例(重量) self._is_finish_ratio=0 @@ -165,14 +181,23 @@ class VisualCallback: self._inital_finish_lweight=0 #记录视觉停止下料时的重量(计算后面加了多少) self._last_lower_weight=0 + #每片开始下料斗的重量 + self._init_lower_weight=0 # 初始化控制间隔和堆料状态跟踪属性 self._last_overflow_state = False self._last_control_time = 0 self._is_running=True + self._is_stop_one_seconds=False self._initialized = True self.plc_data=None + self._mould_need_weight=0 + #点动等级 + self._point_speed_grade=0 + self._point_weight=0 + + def angle_visual_callback(self, current_angle, overflow_detected, mould_aligned): """ 视觉控制主逻辑,供外部推送数据 @@ -217,10 +242,12 @@ class VisualCallback: # 更新参数 if current_diff is not None: # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到diff:{current_diff}") - self._current_diff = current_diff + self._current_diff = current_diff + self._diff_f_val=current_diff if current_area is not None: # print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 收到area:{current_area}") - self._current_diff_area = current_area + self._current_diff_area = current_area + self._diff_f_area = current_area # 通知线程有新数据可用 self._new_data_diff.set() finally: @@ -287,7 +314,7 @@ class VisualCallback: time.sleep(1) def _monitor_loop(self): - """监控循环""" + """破拱线程""" while self._is_running: try: current_time = time.time() @@ -349,7 +376,8 @@ class VisualCallback: (current_time - self._last_arch_time) > 2: self._last_arch_time = current_time print('---------------------第四阶段振动5秒-----------------') - self.relay_controller.control_arch_upper_open_sync(5) + #重量不准,暂时不振动 + # self.relay_controller.control_arch_upper_open_sync(5) self._last_arch_four_weight = _arch_weight continue self._last_arch_four_weight = _arch_weight @@ -385,12 +413,13 @@ class VisualCallback: _temp_aligned_count=0 if flag==1: while time.time()-_current_times<=2: - print(f'-------------{self._mould_accept_aligned}-----------------') + # print(f'-------------{self._mould_accept_aligned}-----------------') if self._mould_accept_aligned=='盖板对齐': _temp_aligned_count=_temp_aligned_count+1 else: _temp_aligned_count=0 - print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板对齐,次数:{_temp_aligned_count}-----------------') + if _temp_aligned_count>0: + print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板对齐,次数:{_temp_aligned_count}-----------------') time.sleep(0.2) self._mould_accept_aligned='' if _temp_aligned_count>=8: @@ -404,8 +433,8 @@ class VisualCallback: _temp_aligned_count=_temp_aligned_count+1 else: _temp_aligned_count=0 - print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板未对齐,次数:{_temp_aligned_count}-----------------') - + if _temp_aligned_count>0: + print(f'-------------{datetime.now().strftime("%H:%M:%S")} 盖板未对齐,次数:{_temp_aligned_count}-----------------') time.sleep(0.2) self._mould_accept_aligned='' @@ -460,7 +489,7 @@ class VisualCallback: time.sleep(0.1) def _run_feed(self): - + _is_api_request=True while True: # print("------------已启动----------------") if self._is_feed_start: @@ -470,38 +499,62 @@ class VisualCallback: if self._is_first_module and self._overflow_detected=='未堆料': #第一次打开 ,未堆料,检测对齐 + if _is_api_request: + self.get_current_mould() + _is_api_request=False _is_aligned=self._aligned_get_times(1) if _is_aligned: - print('------------进入第一块111111-------------') + _is_api_request=True + print('------------启动程序后,进入第一块-------------') self._is_first_module=False self._mould_before_aligned=True + _current_weight=self.transmitter_controller.read_data(2) + if _current_weight: + self._init_lower_weight=_current_weight + else: + print('------------获取上料斗重量失败-------------') + return + + self.state._feed_status=FeedStatus.FCheckGB # self.is_start_visual=True self.run_feed_all() elif self._is_finish and self._is_finish_ratio>=0.7: #后续流程--》检查到未对齐,--》后又对齐+未堆料 - print('------------------进入连续块检测------------------') + #print('------------------进入连续块检测------------------') if self._mould_before_aligned: #未对齐,检测对齐 _is_not_aligned=self._aligned_get_times(2) if _is_not_aligned: #标志位 self._mould_before_aligned=False - print('------------连续盖板未对齐-------------') + #print('------------连续盖板未对齐-------------') else: - _is_aligned=self._aligned_get_times(1) - if _is_aligned and self._overflow_detected=='未堆料': - print('------------连续盖板已对齐-------------') - self._mould_before_aligned=True - print('-----------进入连续块111111-----------') - # self.is_start_visual=True - if self._last_lower_weight>0: - _current_weight=self.transmitter_controller.read_data(2) - if _current_weight is not None: - with open('weight.txt', 'a') as f: - f.write(f"补料:{self._last_lower_weight-_current_weight}\n\n"+"="*32) - self.init_val() - self.run_feed_all() - + if _is_api_request: + self.get_current_mould() + _is_api_request=False + _is_aligned=self._aligned_get_times(1) + if _is_aligned and self._overflow_detected=='未堆料': + print('------------进入连续生产-------------') + self._mould_before_aligned=True + _is_api_request=True + + _current_weight=self.transmitter_controller.read_data(2) + if not _current_weight: + print('------------获取上料斗重量失败-------------') + return + + + # print('-----------进入连续块111111-----------') + # self.is_start_visual=True + if self._last_lower_weight>0: + with open('weight.txt', 'a') as f: + f.write(f"补料:{self._last_lower_weight-_current_weight}\n\n"+"="*32) + + self.init_val() + self._init_lower_weight=_current_weight + self.state._feed_status=FeedStatus.FCheckGB + self.run_feed_all() + # else: # print("-----------上料斗未就位----------------") # print("---------3--上料斗未就位----------------") @@ -517,7 +570,7 @@ class VisualCallback: try: with self._door_control_lock: self._current_controlling_thread = thread_name - #print(f"[{thread_name}] 获得下料斗控制权,执行关闭操作") + print(f"关闭下料斗{duration}秒") self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'open') time.sleep(duration) @@ -538,10 +591,12 @@ class VisualCallback: def _visual_close(self): self.is_start_visual=False self._is_finish=True + self.state.vf_status=3 + self.state._feed_status=FeedStatus.FFinished self._is_feed_stage=0 print(f'--------进入关闭(浇筑满)-----------') self.safe_control_lower_close(3) - print(f'--------关闭完成-----------') + print(f'--------浇筑完成-----------') # try: # self.db_queue.put_nowait({ # "f":self._is_small_f, @@ -557,9 +612,15 @@ class VisualCallback: with open('weight.txt', 'a') as f: timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if self._is_small_f: - f.write(f"{self._time_mould_begin},{timestamp},F,{self._finish_weight}\n") + if self._cur_mould_model: + f.write(f"{self._time_mould_begin},{timestamp},{self._cur_mould_model.MouldCode},F,{self._finish_weight}\n") + else: + f.write(f"{self._time_mould_begin},{timestamp},F,{self._finish_weight}\n") else: - f.write(f"{self._time_mould_begin},{timestamp},B,{self._finish_weight}\n") + if self._cur_mould_model: + f.write(f"{self._time_mould_begin},{timestamp},{self._cur_mould_model.MouldCode},B,{self._finish_weight}\n") + else: + f.write(f"{self._time_mould_begin},{timestamp},B,{self._finish_weight}\n") #开启保存diff self._is_diff_save=True @@ -576,13 +637,15 @@ class VisualCallback: if _is_f=='模具车1': self._is_small_f=True print('-------------F块模具--------------') - print('-------------F块模具--------------') - print('-------------F块模具--------------') + # print('-------------F块模具--------------') + # print('-------------F块模具--------------') + # self.send_pd_data() self.run_feed_f() elif _is_f=='模具车2': self._is_small_f=False + print('-------------B-L模具---------------') + # self.send_pd_data() self.run_feed() - print('-------------其他模具---------------') if self._is_small_f is None: print('-----------未判断出模具类型--------------') @@ -596,21 +659,17 @@ class VisualCallback: loc_mitter=self.transmitter_controller max_weight_none=5 cur_weight_none=0 - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-----f上料斗重量异常-----") - return + initial_lower_weight=self._init_lower_weight first_finish_weight=0 self._finish_weight=first_finish_weight self._inital_finish_lweight=initial_lower_weight - need_total_weight=0.54*2416 + self._mould_need_weight=0.54*2416 + need_total_weight=self._mould_need_weight if initial_lower_weight>100: + self.state._feed_status=FeedStatus.FFeed5 + self.state.vf_status=2 if not self._is_finish: self.is_start_visual=True - initial_lower_weight=loc_mitter.read_data(2) - if initial_lower_weight is None: - print("-----f上料斗重量异常2-----") - return self._is_feed_stage=5 while not self._is_finish: current_weight = loc_mitter.read_data(2) @@ -628,19 +687,19 @@ class VisualCallback: first_finish_weight=initial_lower_weight-current_weight self._is_finish_ratio=(first_finish_weight)/need_total_weight print(f'------------已下料比例: {self._is_finish_ratio}-------------') - if self._is_finish_ratio>self._max_f_angle_ratio: + # if self._is_finish_ratio>=1: #关5秒 #大于0.7后不再检测了,直接交给视觉控制夹脚 # print(f'------------已下料比例: {self._is_finish_ratio}-------------') - break + # break # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) + time.sleep(0.5) # initial_lower_weight=_current_lower_weight print(f'------------已下料(F): {first_finish_weight}kg-------------') print(f'------------已下料(F): {first_finish_weight}kg-------------') - print(f'------------已完成-------------') + # print(f'------------已完成-------------') def run_feed(self): """第一阶段下料:下料斗向模具车下料(低速)""" @@ -651,15 +710,15 @@ class VisualCallback: max_weight_none=5 cur_weight_none=0 - initial_lower_weight=loc_mitter.read_data(2) + initial_lower_weight=self._init_lower_weight # initial_upper_weight=loc_mitter.read_data(1) - if initial_lower_weight is None: - print("---------------下料斗重量异常----------------") - return first_finish_weight=0 - need_total_weight=1.91*2416 + self._mould_need_weight=1.91*2416 + need_total_weight=self._mould_need_weight # start_time=None self.is_start_visual=True + self.state._feed_status=FeedStatus.FFeed1 + self.state.vf_status=1 if initial_lower_weight>100: #下料斗的料全部下完 self._is_feed_stage=1 @@ -673,7 +732,12 @@ class VisualCallback: return continue cur_weight_none=0 + self._is_finish_ratio=(initial_lower_weight-current_weight)/need_total_weight + 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) self.close_lower_door_visual() break time.sleep(1) @@ -687,13 +751,13 @@ class VisualCallback: print(f'------------已下料(第一次): {first_finish_weight}kg-------------') self._is_feed_stage=0 - while self.plc_data!=5: - print('------------上料斗未就位----------------') - print('------------上料斗未就位----------------') + while self.plc_data not in [5,37]: + #print('------------上料斗未就位----------------') + # print('------------上料斗未就位----------------') time.sleep(1) - if self.plc_data==5: - print(f'------------上料斗向下料斗转移(留3000KG)-------------') + if self.plc_data==5 or self.plc_data==37: + print(f'------------上料斗就位(上料斗往下料斗阶段)-------------') #打开上料斗出砼门,开5就,开三分之一下 loc_relay.control_upper_open_sync(6) @@ -716,12 +780,12 @@ class VisualCallback: _two_lower_weight=loc_mitter.read_data(2) if _two_lower_weight is None: _two_lower_weight=0 - if (current_upper_weight<3000 and current_upper_weight>0) or _two_lower_weight>3200: + if (current_upper_weight<3200 and current_upper_weight>0) or _two_lower_weight>3200: #关5秒,loc_time_count多关一秒 loc_relay.control_upper_close_sync(5+loc_time_count) break else: - if time.time()-upper_open_time>5: + if time.time()-upper_open_time>=4: if loc_time_count<6: upper_open_time=time.time() loc_relay.control_upper_open_sync(0.8) @@ -747,12 +811,15 @@ class VisualCallback: return continue cur_weight_none=0 - # second_finish_weight=initial_lower_weight-current_weight + self._is_finish_ratio=(first_finish_weight+initial_lower_weight-current_weight)/need_total_weight if current_weight<250: + # if current_weight>100: + #100,上面粘贴的,振动一下 + # self.relay_controller.control_arch_lower_open_async(5) self.close_lower_door_visual() break # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) + time.sleep(0.5) _current_lower_weight=loc_mitter.read_data(2) if _current_lower_weight is None: print("-------下料斗重量异常(第二次下到模)---------") @@ -762,7 +829,7 @@ class VisualCallback: print(f'------------已下料(第二次): {first_finish_weight}kg-------------') self._is_feed_stage=0 - if self.plc_data==5: + if self.plc_data==5 or self.plc_data==37: #第二次上料斗向下料斗转移 loc_relay.control_upper_open_sync(12) loc_time_count=1 @@ -770,8 +837,8 @@ class VisualCallback: upper_open_time_2=None #第二次到下料斗还需要的量 #loc_left_need_weight=need_total_weight-first_finish_weight - # initial_upper_weight=loc_mitter.read_data(1) - # start_time=None + #initial_upper_weight=loc_mitter.read_data(1) + #start_time=None self._is_feed_stage=4 while not self._is_finish: # print(f'------------上料斗向下料斗转移22222-------------') @@ -797,9 +864,9 @@ class VisualCallback: #5秒后关闭 loc_relay.control_upper_close_after()#control_upper_close_sync(8+loc_time_count) break - time.sleep(1) + time.sleep(0.5) else: - if time.time()-upper_open_time>2: + if time.time()-upper_open_time>=1: # if loc_time_count<6: upper_open_time=time.time() loc_relay.control_upper_open_sync(1.2) @@ -834,13 +901,13 @@ class VisualCallback: second_finish_weight=initial_lower_weight-current_weight self._is_finish_ratio=(second_finish_weight+first_finish_weight)/need_total_weight print(f'------------已下料比例: {self._is_finish_ratio}-------------') - if self._is_finish_ratio>=1: + # if self._is_finish_ratio>=1: #关5秒 # print(f'------------已下料比例: {self._is_finish_ratio}-------------') - break + # break # print(f'------------已下料: {first_finish_weight+second_finish_weight}kg-------------') - time.sleep(1) + time.sleep(0.5) # _current_lower_weight=loc_mitter.read_data(2) # first_finish_weight=first_finish_weight+initial_lower_weight-_current_lower_weight @@ -848,7 +915,7 @@ class VisualCallback: # print(f'------------已下料: {first_finish_weight}kg-------------') - print(f'------------已完成-------------') + # print(f'------------已完成-------------') def _process_angle_callback(self, current_angle, overflow_detected): """ @@ -871,43 +938,171 @@ class VisualCallback: if current_angle is None: return - print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 角度11: {current_angle:.2f}°,{overflow_detected}") + print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} 角度11: {current_angle:.2f}°,{overflow_detected},diff_f_val:{self._diff_f_val},diff_f_area:{self._diff_f_area}") + if self._is_small_f: + if self._is_finish_ratio>=1.02: + print('重量达到最大比例,浇筑满关闭') + self._visual_close() + return + elif self._is_finish_ratio>=0.9: + if (self._diff_f_val>=427 and self._diff_f_val<=450): + print('------------diff到达浇筑满-------------') + self._visual_close() + return + elif (len(self._diff_f_area)>0 and self._diff_f_area[-1]>=33400 and self._diff_f_area[-1]<=34500): + print('------------area到达浇筑满-------------') + self._visual_close() + return + else: + if self._is_finish_ratio>=1.01: + print('重量达到最大比例,浇筑满关闭') + self._visual_close() + return + elif self._is_finish_ratio>=0.93: + if (self._diff_f_val>=460 and self._diff_f_val<=510): + print('------------diff到达浇筑满-------------') + self._visual_close() + return + if (len(self._diff_f_area)>0 and self._diff_f_area[-1]>=38200 and self._diff_f_area[-1]<=41000): + print('------------area到达浇筑满-------------') + self._visual_close() + return + + if overflow_detected == "未浇筑满" or self._is_before_finish: if self._before_finish_time is None: self._before_finish_time=current_time - self.safe_control_lower_close(3) + self.safe_control_lower_close(1) print('-----------------关闭(未浇筑满)--------------------') # time.sleep(3) else: + if not self._is_stop_one_seconds: + #根据角度来计算还需要多久完全关闭 + if current_angle>=20: + self.safe_control_lower_close(2) + elif current_angle>=10 and current_angle<20: + self.safe_control_lower_close(1) + elif current_angle>=6 and current_angle<10: + self.safe_control_lower_close(0.5) + self._is_stop_one_seconds=True + elif current_angle>7: + #点动状态下,如果关闭后角度大于7度,关紧 + self.safe_control_lower_close(0.2) + _open_time=0.3 + _sleep_time=0.3 + _close_time=0.5 if overflow_detected=='浇筑满': - self._visual_close() - return + if self._is_small_f: + self._visual_close() + return + else: + if self._diff_f_val>=410 and self._diff_f_val<450: + #排除这个范围的关闭 + print(f'浇筑满状态,diff_f_val:{self._diff_f_val},不关闭') + _open_time=0.5 + _sleep_time=0.3 + _close_time=0.7 + else: + self._visual_close() + return + # print(f'--------已关闭已关闭-----------') elif overflow_detected=="大堆料": print(f'--------未浇筑满,大堆料-----------') - self._pulse_control('open',0.3) - time.sleep(0.3) - self._pulse_control('close',0.4) - print(f'--------比例:{self._is_finish_ratio}-----------') - if self._is_finish_ratio>= self._weight_ratio_955: - time.sleep(2) - else: - time.sleep(1) - self._is_before_finish=True + elif overflow_detected=="小堆料": + print(f'--------未浇筑满,小堆料-----------') + _open_time=0.5 + _sleep_time=0.3 + _close_time=0.7 else: - # self._pulse_control('open',0.5) - # time.sleep(0.3) - # self._pulse_control('close',0.6) - # print(f'--------比例:{self._is_finish_ratio}-----------') - self._pulse_control('open',0.6) - time.sleep(0.3) - self._pulse_control('close',0.7) - if self._is_finish_ratio>= self._weight_ratio_955: - time.sleep(2) + if self._is_small_f: + _open_time=0.6 + _sleep_time=0.3 + _close_time=0.8 else: + if self._is_finish_ratio<0.9: + _open_time=1 + _sleep_time=0.3 + _close_time=1.2 + #之前慢的参数 + # _open_time=0.8 + # _sleep_time=0.3 + # _close_time=1 + elif self._is_finish_ratio<0.95 and self._is_finish_ratio>=0.9: + if self._point_weight>=10: + _open_time=0.7 + _sleep_time=0.3 + _close_time=0.9 + else: + #之前慢的参数 + # _open_time=0.8 + # _sleep_time=0.3 + # _close_time=1 + _open_time=0.9 + _sleep_time=0.3 + _close_time=1.1 + else: + _open_time=0.6 + _sleep_time=0.3 + _close_time=0.8 + + if self._point_speed_grade==1: + if _open_time>0.6: + _open_time=0.6 + _sleep_time=0.3 + _close_time=0.8 + elif self._point_speed_grade==2: + if _open_time>0.5: + _open_time=0.5 + _sleep_time=0.3 + _close_time=0.7 + elif self._point_speed_grade==3: + if _open_time>0.4: + _open_time=0.4 + _sleep_time=0.3 + _close_time=0.6 + elif self._point_speed_grade==4: + if _open_time>0.3: + _open_time=0.3 + _sleep_time=0.3 + _close_time=0.5 + _last_finish_ratio=self._is_finish_ratio + print(f'--------比例开始:{_last_finish_ratio}-----------') + self._pulse_control('open',_open_time) + time.sleep(_sleep_time) + self._pulse_control('close',_close_time) + print(f'--------比例结束:{self._is_finish_ratio}-----------') + + self._point_weight=(self._is_finish_ratio-_last_finish_ratio)*self._mould_need_weight + print(f'--------流速:{self._point_weight}-----------') + if self._is_small_f: + time.sleep(2.5) + else: + # if self._is_finish_ratio>= 0.93: + # time.sleep(2) + # print('--------重量已到95.5%,需要2秒休息-----------') + # else: time.sleep(1) - self._is_before_finish=True + #下得过快,需要2秒休息 + if self._point_weight>=65: + time.sleep(5) + self._point_speed_grade=4 + elif self._point_weight>=50 and self._point_weight<65: + time.sleep(4) + self._point_speed_grade=3 + elif self._point_weight>=35 and self._point_weight<50: + time.sleep(3) + self._point_speed_grade=2 + elif self._point_weight>=25 and self._point_weight<35: + time.sleep(2) + self._point_speed_grade=1 + elif self._point_weight>=15 and self._point_weight<25: + time.sleep(1) + self._point_speed_grade=0 + else: + self._point_speed_grade=0 + self._is_before_finish=True if self._is_finish_ratio<=self._max_ignore_radio: #如果重量未达到最大忽略角度,需要跳出 self._is_before_finish=False @@ -917,7 +1112,15 @@ class VisualCallback: return else: self._before_finish_time=None - if self._is_finish_ratio>=self._max_angle_radio or (self._is_finish_ratio>self._max_f_angle_ratio and self._is_small_f): + #2160KG + if self._is_finish_ratio>=0.85 and not self._is_small_f: + if overflow_detected == "大堆料": + TARGET_ANGLE = 5.0 # 大堆料时控制在15度左右 + elif overflow_detected == "小堆料": + TARGET_ANGLE = 15.0 # 小堆料时控制在35度左右 + else: + TARGET_ANGLE = 35.0 # 12.25由25--》35 + elif (self._is_finish_ratio>0.7 and self._is_small_f): if overflow_detected == "大堆料": TARGET_ANGLE = 5.0 # 大堆料时控制在15度左右 elif overflow_detected == "小堆料": @@ -928,19 +1131,31 @@ class VisualCallback: if self._is_feed_stage==1 or self._is_feed_stage==3: #根据溢料状态动态调整目标角度 if overflow_detected == "大堆料": - TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 + if not self.state.mould_vibrate_status: + TARGET_ANGLE = 15.0 # 临时控制变频器堆料时很小 + else: + TARGET_ANGLE = 35.0 # 大堆料时控制在15度左右 elif overflow_detected == "小堆料": - TARGET_ANGLE = 45.0 # 小堆料时控制在35度左右 + TARGET_ANGLE = 55.0 # 小堆料时控制在35度左右 else: TARGET_ANGLE = 55.0 # 未溢料时开到最大56度 else: + if self._is_small_f: #根据溢料状态动态调整目标角度 - if overflow_detected == "大堆料": - TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 - elif overflow_detected == "小堆料": - TARGET_ANGLE = 25.0 # 小堆料时控制在35度左右 + if overflow_detected == "大堆料": + TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 + elif overflow_detected == "小堆料": + TARGET_ANGLE = 25.0 # 小堆料时控制在35度左右 + else: + TARGET_ANGLE = 45.0 # 未溢料时开到最大56度 else: - TARGET_ANGLE = 45.0 # 未溢料时开到最大56度 + #根据溢料状态动态调整目标角度 + if overflow_detected == "大堆料": + TARGET_ANGLE = 15.0 # 大堆料时控制在15度左右 + elif overflow_detected == "小堆料": + TARGET_ANGLE = 55.0 # 小堆料时控制在35度左右 + else: + TARGET_ANGLE = 55.0 # 未溢料时开到最大56度 # 确保目标角度在硬件范围内(5-56度) TARGET_ANGLE = max(5.0, min(56.0, TARGET_ANGLE)) @@ -971,8 +1186,8 @@ class VisualCallback: # PID输出 pid_output = (KP * error + KI * self._error_integral + KD * error_derivative) - print(f"📊 PID计算: 误差={error:.2f}°, 积分={self._error_integral:.2f}, " - f"微分={error_derivative:.2f}, 输出={pid_output:.2f}") + #print(f"📊 PID计算: 误差={error:.2f}°, 积分={self._error_integral:.2f}, " + # f"微分={error_derivative:.2f}, 输出={pid_output:.2f}") # 更新历史值 self._last_error = error self._last_control_time = current_time @@ -989,6 +1204,9 @@ class VisualCallback: self._maintaining_mode_advanced(current_angle, pid_output, TARGET_ANGLE) except Exception as e: + print("处理视觉回调时发生异常: ") + print("处理视觉回调时发生异常: ") + print("处理视觉回调时发生异常: ") print(f"处理视觉回调时发生异常: {e}") def _normal_mode_advanced(self, current_angle, pid_output,target_angle): @@ -1025,11 +1243,11 @@ class VisualCallback: if error > 0: # 当前角度 > 目标角度,需要关门 pulse_time=0.1 # 根据误差计算脉冲时间 self._pulse_control("close", pulse_time) - print(f"🚨 强制关门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") + #print(f"🚨 强制关门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") else: # 当前角度 < 目标角度,需要开门 pulse_time =0.1 self._pulse_control("open", pulse_time) - print(f"🚨 强制开门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") + #print(f"🚨 强制开门: 误差{abs_error:.1f}°过大,脉冲{pulse_time:.3f}s") return else: self._stop_door() @@ -1051,7 +1269,7 @@ class VisualCallback: # 使用PID输出计算控制量 pulse_time = min(0.5, max(0.1, pid_output * 0.15)) self._pulse_control("close", pulse_time) - print(f"减小模式: 积极关门{pulse_time:.2f}秒,PID输出:{pid_output:.1f}") + # print(f"减小模式: 积极关门{pulse_time:.2f}秒,PID输出:{pid_output:.1f}") else: self.angle_mode = "maintaining" print("角度已达标,进入维持模式") @@ -1091,11 +1309,11 @@ class VisualCallback: return thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试脉冲控制 {action},时长 {duration:.2f}秒...") + #print(f"[{thread_name}] 尝试脉冲控制 {action},时长 {duration:.2f}秒...") with self._door_control_lock: self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行脉冲控制") + # print(f"[{thread_name}] 获得下料斗控制权,执行脉冲控制") if action == "open": self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') @@ -1111,7 +1329,7 @@ class VisualCallback: print(f"[{thread_name}] 关门脉冲: {duration:.2f}秒") self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") + #print(f"[{thread_name}] 释放下料斗控制权") def _stop_door(self): """停止门运动""" @@ -1122,15 +1340,15 @@ class VisualCallback: return thread_name = threading.current_thread().name - print(f"[{thread_name}] 尝试停止门运动...") + #print(f"[{thread_name}] 尝试停止门运动...") with self._door_control_lock: self._current_controlling_thread = thread_name - print(f"[{thread_name}] 获得下料斗控制权,执行停止操作") + #print(f"[{thread_name}] 获得下料斗控制权,执行停止操作") self.relay_controller.control(self.relay_controller.DOOR_LOWER_OPEN, 'close') self.relay_controller.control(self.relay_controller.DOOR_LOWER_CLOSE, 'close') self._current_controlling_thread = None - print(f"[{thread_name}] 释放下料斗控制权") + #print(f"[{thread_name}] 释放下料斗控制权") def _open_door(self, duration=0.5): """打开门""" @@ -1145,10 +1363,10 @@ class VisualCallback: # print(f"[数据回调] 数值: 0x{data:02X} | 十进制: {data:3d} | 二进制: {binary}") self.plc_data=data - @classmethod - def instance_exists(cls): + # @classmethod + # def instance_exists(cls): """检测实例是否存在""" - return cls._instance is not None + # return cls._instance is not None def shutdown(self): """关闭线程,清理资源""" @@ -1176,6 +1394,86 @@ class VisualCallback: # 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() + if _not_poured is not None and len(_not_poured)>=1: + _cur_poured_model=_not_poured[-1] + if _cur_poured_model.MouldCode: + self._cur_mould_model=_cur_poured_model + print(f'当前要浇筑的管片 {json.dumps(asdict(_cur_poured_model), ensure_ascii=False)}') + else: + print('当前没有未浇筑的管片') + def __del__(self): """析构函数,确保线程安全关闭""" self.shutdown() diff --git a/vision/weight.txt b/vision/weight.txt deleted file mode 100644 index 1a54c9c..0000000 --- a/vision/weight.txt +++ /dev/null @@ -1,654 +0,0 @@ -2026-01-06 07:00:53,2026-01-06 07:06:04,B,3154 -diff , 2026-01-06 07:06:04 , 632 - -area , 2026-01-06 07:06:04 , [292.2259399848001, 223.00896842952304, 36723] - -diff , 2026-01-06 07:06:05 , 454 - -area , 2026-01-06 07:06:05 , [291.8715470887836, 223.00896842952304, 36503] - -diff , 2026-01-06 07:06:06 , 453 - -area , 2026-01-06 07:06:06 , [225.656819085974, 226.92069099136816, 36164] - -diff , 2026-01-06 07:06:07 , 456 - -area , 2026-01-06 07:06:07 , [229.40139493908924, 223.00896842952304, 36277] - -diff , 2026-01-06 07:06:08 , 457 - -area , 2026-01-06 07:06:08 , [226.947130407062, 223.00896842952304, 36278] - -diff , 2026-01-06 07:06:09 , 635 - -area , 2026-01-06 07:06:09 , [230.10649708341572, 223.8861317723811, 36710] - -diff , 2026-01-06 07:06:10 , 456 - -area , 2026-01-06 07:06:10 , [288.2568299277573, 227.343352662883, 37054] - -diff , 2026-01-06 07:06:11 , 453 - -area , 2026-01-06 07:06:11 , [227.30596120647607, 223.8861317723811, 36971] - -diff , 2026-01-06 07:06:12 , 453 - -area , 2026-01-06 07:06:12 , [230.10649708341572, 223.4390297150433, 37326] - -diff , 2026-01-06 07:06:13 , 455 - -area , 2026-01-06 07:06:13 , [233.15445524372893, 223.4390297150433, 36853] - -2026-01-06 07:24:40,2026-01-06 07:29:52,B,4623 -diff , 2026-01-06 07:29:53 , 458 - -area , 2026-01-06 07:29:53 , [288.1336495447902, 223.8861317723811, 36642] - -diff , 2026-01-06 07:29:54 , 459 - -area , 2026-01-06 07:29:54 , [226.59214461229675, 226.80828909014767, 36083] - -diff , 2026-01-06 07:29:55 , 461 - -area , 2026-01-06 07:29:55 , [284.6928169097352, 226.80828909014767, 36640] - -diff , 2026-01-06 07:29:56 , 461 - -area , 2026-01-06 07:29:56 , [226.59214461229675, 230.70760715676457, 36218] - -diff , 2026-01-06 07:29:57 , 464 - -area , 2026-01-06 07:29:57 , [281.18677067031444, 230.27375013231534, 36645] - -diff , 2026-01-06 07:29:58 , 463 - -area , 2026-01-06 07:29:58 , [280.71693928225994, 230.27375013231534, 36969] - -diff , 2026-01-06 07:29:59 , 462 - -area , 2026-01-06 07:29:59 , [277.2183255125822, 234.18368858654523, 36761] - -diff , 2026-01-06 07:30:00 , 463 - -area , 2026-01-06 07:30:00 , [219.1255347968374, 234.61031520374377, 36405] - -diff , 2026-01-06 07:30:01 , 467 - -area , 2026-01-06 07:30:01 , [222.8542124349459, 234.61031520374377, 36479] - -area , 2026-01-06 07:30:02 , [223.5799633240868, 231.1579546543878, 36852] - -diff , 2026-01-06 07:30:03 , 465 - -area , 2026-01-06 07:30:03 , [219.1255347968374, 234.61031520374377, 36812] - -补料:2 -2026-01-06 07:32:05,2026-01-06 07:36:39,B,4602 -补料:4 -2026-01-06 07:39:31,2026-01-06 07:41:51,F,1316 -补料:2 -2026-01-06 07:46:40,2026-01-06 07:51:03,B,4471 -补料:8 -2026-01-06 08:00:43,2026-01-06 08:05:26,B,4430 -补料:24 -2026-01-06 08:08:22,2026-01-06 08:13:52,B,4526 -补料:20 -2026-01-06 08:17:08,2026-01-06 08:21:25,B,4593 -补料:14 -2026-01-06 08:24:34,2026-01-06 08:29:49,B,4609 -补料:11 -2026-01-06 08:38:57,2026-01-06 08:44:04,B,4554 -diff , 2026-01-06 08:44:04 , 456 - -area , 2026-01-06 08:44:04 , [232.14219780126146, 218.8880992653552, 35193] - -diff , 2026-01-06 08:44:05 , 448 - -area , 2026-01-06 08:44:05 , [221.79495034828903, 222.80035906613796, 35124] - -diff , 2026-01-06 08:44:06 , 633 - -area , 2026-01-06 08:44:06 , [224.61077445216202, 222.80035906613796, 35255] - -diff , 2026-01-06 08:44:07 , 650 - -area , 2026-01-06 08:44:07 , [286.8449058289165, 212.9530464679949, 36314] - -diff , 2026-01-06 08:44:08 , 456 - -area , 2026-01-06 08:44:08 , [283.70583356709466, 228.70505022845472, 35764] - -diff , 2026-01-06 08:44:09 , 446 - -area , 2026-01-06 08:44:09 , [215.60380330597138, 225.94247055390008, 35407] - -diff , 2026-01-06 08:44:10 , 456 - -area , 2026-01-06 08:44:10 , [284.48198536990003, 229.65408770583642, 36252] - -diff , 2026-01-06 08:44:11 , 634 - -area , 2026-01-06 08:44:11 , [222.14409737825582, 225.94247055390008, 35237] - -diff , 2026-01-06 08:44:12 , 457 - -area , 2026-01-06 08:44:12 , [229.40139493908924, 223.4390297150433, 35324] - -diff , 2026-01-06 08:44:13 , 630 - -area , 2026-01-06 08:44:13 , [284.48198536990003, 225.94247055390008, 36088] - -补料:13 -2026-01-06 08:45:55,2026-01-06 08:49:57,B,4557 -补料:34 -2026-01-06 09:00:18,2026-01-06 09:04:15,B,4571 -diff , 2026-01-06 09:04:16 , 636 - -area , 2026-01-06 09:04:16 , [225.656819085974, 227.56098083810414, 36387] - -diff , 2026-01-06 09:04:17 , 637 - -area , 2026-01-06 09:04:17 , [222.8542124349459, 230.70760715676457, 36409] - -diff , 2026-01-06 09:04:18 , 632 - -area , 2026-01-06 09:04:18 , [229.40139493908924, 227.56098083810414, 36588] - -diff , 2026-01-06 09:04:19 , 624 - -area , 2026-01-06 09:04:19 , [219.1255347968374, 234.39496581624786, 36588] - -diff , 2026-01-06 09:04:20 , 450 - -area , 2026-01-06 09:04:20 , [226.947130407062, 230.70760715676457, 36692] - -diff , 2026-01-06 09:04:21 , 450 - -area , 2026-01-06 09:04:21 , [226.01327394646538, 227.7827912727386, 36614] - -diff , 2026-01-06 09:04:22 , 638 - -area , 2026-01-06 09:04:22 , [226.01327394646538, 227.343352662883, 36655] - -diff , 2026-01-06 09:04:23 , 627 - -area , 2026-01-06 09:04:23 , [222.8542124349459, 232.13142829009604, 36405] - -diff , 2026-01-06 09:04:24 , 642 - -area , 2026-01-06 09:04:24 , [222.8542124349459, 234.61031520374377, 36148] - -diff , 2026-01-06 09:04:25 , 451 - -area , 2026-01-06 09:04:25 , [291.7601754866486, 232.13142829009604, 36663] - -补料:10 -2026-01-06 09:07:13,2026-01-06 09:11:16,B,4589 -补料:6 -2026-01-06 09:22:40,2026-01-06 09:27:26,B,4489 -diff , 2026-01-06 09:27:27 , 630 - -area , 2026-01-06 09:27:27 , [290.6217472936256, 227.1299187689724, 35628] - -diff , 2026-01-06 09:27:28 , 625 - -area , 2026-01-06 09:27:28 , [291.7601754866486, 226.71568097509268, 35404] - -area , 2026-01-06 09:27:29 , [286.6304240655552, 227.56098083810414, 35718] - -diff , 2026-01-06 09:27:30 , 630 - -area , 2026-01-06 09:27:30 , [241.6112580158466, 223.6604569431083, 35214] - -diff , 2026-01-06 09:27:31 , 623 - -area , 2026-01-06 09:27:31 , [291.3485884640597, 223.6604569431083, 35546] - -diff , 2026-01-06 09:27:32 , 630 - -area , 2026-01-06 09:27:32 , [225.55043781824057, 226.71568097509268, 35207] - -area , 2026-01-06 09:27:33 , [224.61077445216202, 227.56098083810414, 35130] - -diff , 2026-01-06 09:27:34 , 630 - -area , 2026-01-06 09:27:34 , [237.80664414603726, 230.63390904201404, 35448] - -diff , 2026-01-06 09:27:35 , 627 - -area , 2026-01-06 09:27:35 , [295.7363690857112, 219.76350925483513, 35487] - -diff , 2026-01-06 09:27:36 , 621 - -area , 2026-01-06 09:27:36 , [287.7846417027844, 227.56098083810414, 35986] - -diff , 2026-01-06 09:27:37 , 629 - -area , 2026-01-06 09:27:37 , [224.61077445216202, 227.56098083810414, 35270] - -diff , 2026-01-06 09:27:38 , 618 - -补料:23 -2026-01-06 09:29:56,2026-01-06 09:34:04,B,4500 -补料:39 -2026-01-06 09:37:15,2026-01-06 09:41:23,B,4513 -补料:29 -2026-01-06 09:44:22,2026-01-06 09:48:35,B,4555 -补料:2 -2026-01-06 09:51:26,2026-01-06 09:55:19,B,4538 -补料:8 -2026-01-06 09:57:42,2026-01-06 09:59:52,F,1326 -补料:3 -2026-01-06 10:04:27,2026-01-06 10:10:54,B,4515 -补料:11 -2026-01-06 10:13:40,2026-01-06 10:19:42,B,4528 -补料:35 -2026-01-06 10:22:11,2026-01-06 10:26:25,B,4526 -补料:3 -2026-01-06 10:28:55,2026-01-06 10:32:54,B,4619 -补料:8 -2026-01-06 10:35:56,2026-01-06 10:40:40,B,4575 -补料:7 -2026-01-06 10:42:46,2026-01-06 10:45:18,F,1281 -补料:4 -2026-01-06 10:49:14,2026-01-06 10:53:45,B,4091 -补料:30 -2026-01-06 10:55:44,2026-01-06 10:59:57,B,4368 -补料:16 -2026-01-06 11:02:16,2026-01-06 11:07:24,B,4518 -补料:22 -2026-01-06 11:09:53,2026-01-06 11:15:08,B,4295 -补料:15 -2026-01-06 12:41:11,2026-01-06 12:47:05,B,4593 -diff , 2026-01-06 12:47:05 , 445 - -area , 2026-01-06 12:47:05 , [291.8715470887836, 218.47654336335515, 35671] - -diff , 2026-01-06 12:47:06 , 444 - -area , 2026-01-06 12:47:06 , [291.8715470887836, 218.47654336335515, 36236] - -diff , 2026-01-06 12:47:07 , 444 - -area , 2026-01-06 12:47:07 , [287.4665197896966, 222.39604313026794, 36312] - -diff , 2026-01-06 12:47:08 , 445 - -area , 2026-01-06 12:47:08 , [291.8715470887836, 218.47654336335515, 35942] - -diff , 2026-01-06 12:47:09 , 445 - -area , 2026-01-06 12:47:09 , [287.4665197896966, 222.39604313026794, 35242] - -diff , 2026-01-06 12:47:10 , 444 - -area , 2026-01-06 12:47:10 , [222.8542124349459, 220.0772591614136, 35374] - -diff , 2026-01-06 12:47:11 , 441 - -area , 2026-01-06 12:47:11 , [222.8542124349459, 220.0772591614136, 35156] - -diff , 2026-01-06 12:47:12 , 450 - -area , 2026-01-06 12:47:12 , [287.8975512226528, 219.86586820150143, 35537] - -diff , 2026-01-06 12:47:13 , 447 - -area , 2026-01-06 12:47:13 , [287.8975512226528, 222.39604313026794, 35955] - -diff , 2026-01-06 12:47:14 , 447 - -area , 2026-01-06 12:47:14 , [228.7116088002531, 219.86586820150143, 34972] - -补料:31 - -================================2026-01-06 12:57:50,2026-01-06 13:04:33,B,4616 -diff , 2026-01-06 13:04:33 , 443 - -area , 2026-01-06 13:04:33 , [295.8462438497403, 223.8861317723811, 36251] - -diff , 2026-01-06 13:04:34 , 445 - -area , 2026-01-06 13:04:34 , [295.8462438497403, 226.92069099136816, 36584] - -diff , 2026-01-06 13:04:35 , 444 - -area , 2026-01-06 13:04:35 , [295.4268098869837, 223.8861317723811, 36546] - -diff , 2026-01-06 13:04:36 , 444 - -area , 2026-01-06 13:04:36 , [295.8462438497403, 223.4390297150433, 36709] - -diff , 2026-01-06 13:04:38 , 439 - -area , 2026-01-06 13:04:38 , [291.8715470887836, 227.7827912727386, 36495] - -diff , 2026-01-06 13:04:39 , 439 - -area , 2026-01-06 13:04:39 , [295.8462438497403, 223.4390297150433, 36476] - -diff , 2026-01-06 13:04:40 , 443 - -area , 2026-01-06 13:04:40 , [295.8462438497403, 226.92069099136816, 37179] - -diff , 2026-01-06 13:04:41 , 445 - -area , 2026-01-06 13:04:41 , [291.44639301250584, 227.7827912727386, 36543] - -diff , 2026-01-06 13:04:42 , 445 - -area , 2026-01-06 13:04:42 , [291.8715470887836, 227.343352662883, 36556] - -diff , 2026-01-06 13:04:43 , 445 - -area , 2026-01-06 13:04:43 , [291.8715470887836, 227.7827912727386, 36611] - -补料:23 - -================================2026-01-06 13:06:34,2026-01-06 13:11:47,B,4620 -diff , 2026-01-06 13:11:47 , 446 - -area , 2026-01-06 13:11:47 , [295.7363690857112, 234.39496581624786, 37311] - -diff , 2026-01-06 13:11:48 , 445 - -area , 2026-01-06 13:11:48 , [219.1255347968374, 234.39496581624786, 36681] - -diff , 2026-01-06 13:11:49 , 447 - -area , 2026-01-06 13:11:49 , [222.28360263411244, 231.68297304722245, 36683] - -diff , 2026-01-06 13:11:50 , 446 - -area , 2026-01-06 13:11:50 , [222.8542124349459, 234.39496581624786, 36658] - -diff , 2026-01-06 13:11:51 , 448 - -area , 2026-01-06 13:11:51 , [226.01327394646538, 231.68297304722245, 36662] - -diff , 2026-01-06 13:11:53 , 451 - -area , 2026-01-06 13:11:53 , [222.8542124349459, 234.39496581624786, 36783] - -diff , 2026-01-06 13:11:54 , 447 - -area , 2026-01-06 13:11:54 , [300.38974682901545, 231.68297304722245, 37410] - -diff , 2026-01-06 13:11:55 , 450 - -area , 2026-01-06 13:11:55 , [295.8462438497403, 234.61031520374377, 37612] - -diff , 2026-01-06 13:11:56 , 448 - -area , 2026-01-06 13:11:56 , [299.1738624947039, 231.68297304722245, 37302] - -diff , 2026-01-06 13:11:57 , 447 - -area , 2026-01-06 13:11:57 , [299.5496619927988, 228.00877176108818, 37456] - -补料:7 - -================================2026-01-06 13:13:42,2026-01-06 13:16:06,F,1309 -diff , 2026-01-06 13:16:07 , 440 - -area , 2026-01-06 13:16:07 , [294.89828755013144, 219.10043359153812, 36251] - -diff , 2026-01-06 13:16:08 , 440 - -area , 2026-01-06 13:16:08 , [295.4268098869837, 219.10043359153812, 36394] - -diff , 2026-01-06 13:16:09 , 440 - -area , 2026-01-06 13:16:09 , [291.44639301250584, 223.00896842952304, 36546] - -diff , 2026-01-06 13:16:10 , 440 - -area , 2026-01-06 13:16:10 , [297.2742841215836, 219.53815158190613, 36711] - -diff , 2026-01-06 13:16:11 , 440 - -area , 2026-01-06 13:16:11 , [296.0760037557924, 219.53815158190613, 37139] - -diff , 2026-01-06 13:16:12 , 440 - -area , 2026-01-06 13:16:12 , [292.1044333795706, 223.4390297150433, 36799] - -diff , 2026-01-06 13:16:13 , 440 - -area , 2026-01-06 13:16:13 , [292.4790590794493, 223.4390297150433, 36876] - -diff , 2026-01-06 13:16:14 , 438 - -area , 2026-01-06 13:16:14 , [292.1044333795706, 223.4390297150433, 36701] - -diff , 2026-01-06 13:16:15 , 436 - -area , 2026-01-06 13:16:15 , [295.62983611266304, 219.53815158190613, 37240] - -diff , 2026-01-06 13:16:16 , 436 - -area , 2026-01-06 13:16:16 , [296.0760037557924, 219.53815158190613, 36894] - -补料:20 - -================================2026-01-06 13:20:23,2026-01-06 13:24:22,B,3787 -area , 2026-01-06 13:24:22 , [222.8542124349459, 219.9931817125249, 35708] - -diff , 2026-01-06 13:24:23 , 628 - -area , 2026-01-06 13:24:23 , [226.59214461229675, 223.00896842952304, 35809] - -diff , 2026-01-06 13:24:24 , 628 - -area , 2026-01-06 13:24:24 , [222.49719099350446, 226.71568097509268, 35928] - -diff , 2026-01-06 13:24:25 , 628 - -area , 2026-01-06 13:24:25 , [291.44639301250584, 219.9931817125249, 36442] - -diff , 2026-01-06 13:24:26 , 445 - -area , 2026-01-06 13:24:26 , [226.59214461229675, 223.00896842952304, 36080] - -diff , 2026-01-06 13:24:27 , 451 - -area , 2026-01-06 13:24:27 , [291.8715470887836, 223.00896842952304, 36566] - -diff , 2026-01-06 13:24:28 , 450 - -area , 2026-01-06 13:24:28 , [232.14219780126146, 226.71568097509268, 35976] - -diff , 2026-01-06 13:24:29 , 449 - -area , 2026-01-06 13:24:29 , [236.24775131204953, 223.00896842952304, 36068] - -diff , 2026-01-06 13:24:30 , 447 - -area , 2026-01-06 13:24:30 , [226.59214461229675, 223.00896842952304, 36061] - -diff , 2026-01-06 13:24:31 , 451 - -area , 2026-01-06 13:24:31 , [226.59214461229675, 223.00896842952304, 36082] - -diff , 2026-01-06 13:24:32 , 448 - -area , 2026-01-06 13:24:32 , [228.7116088002531, 226.92069099136816, 36133] - -补料:14 - -================================2026-01-06 13:27:18,2026-01-06 13:31:49,B,4587 -area , 2026-01-06 13:31:50 , [225.656819085974, 226.71568097509268, 36039] - -diff , 2026-01-06 13:31:51 , 446 - -area , 2026-01-06 13:31:51 , [288.1336495447902, 227.1299187689724, 36865] - -diff , 2026-01-06 13:31:52 , 446 - -area , 2026-01-06 13:31:52 , [229.7520402520944, 223.00896842952304, 36074] - -diff , 2026-01-06 13:31:53 , 447 - -area , 2026-01-06 13:31:53 , [222.8542124349459, 226.71568097509268, 36004] - -diff , 2026-01-06 13:31:54 , 447 - -area , 2026-01-06 13:31:54 , [288.1336495447902, 226.71568097509268, 36669] - -diff , 2026-01-06 13:31:55 , 449 - -area , 2026-01-06 13:31:55 , [298.51130631853795, 215.5388596054085, 36684] - -diff , 2026-01-06 13:31:56 , 449 - -area , 2026-01-06 13:31:56 , [287.8975512226528, 226.92069099136816, 36957] - -diff , 2026-01-06 13:31:57 , 449 - -area , 2026-01-06 13:31:57 , [292.2259399848001, 223.00896842952304, 36961] - -diff , 2026-01-06 13:31:59 , 450 - -area , 2026-01-06 13:31:59 , [288.1336495447902, 223.6604569431083, 36748] - -diff , 2026-01-06 13:32:00 , 450 - -area , 2026-01-06 13:32:00 , [291.2541845192958, 218.27734651126764, 36470] - -diff , 2026-01-06 13:32:01 , 451 - -area , 2026-01-06 13:32:01 , [291.44639301250584, 218.68013169924697, 36329] - -补料:23 - -================================2026-01-06 13:34:34,2026-01-06 13:38:21,B,4445 -area , 2026-01-06 13:38:22 , [226.59214461229675, 223.00896842952304, 36074] - -diff , 2026-01-06 13:38:23 , 446 - -area , 2026-01-06 13:38:23 , [226.59214461229675, 223.00896842952304, 36009] - -diff , 2026-01-06 13:38:24 , 446 - -area , 2026-01-06 13:38:24 , [255.93163149560078, 223.8861317723811, 36109] - -diff , 2026-01-06 13:38:25 , 445 - -area , 2026-01-06 13:38:25 , [259.763353843455, 226.92069099136816, 36393] - -diff , 2026-01-06 13:38:26 , 447 - -area , 2026-01-06 13:38:26 , [226.59214461229675, 223.4390297150433, 36062] - -diff , 2026-01-06 13:38:27 , 447 - -area , 2026-01-06 13:38:27 , [219.49259668608414, 223.8861317723811, 36108] - -diff , 2026-01-06 13:38:28 , 447 - -area , 2026-01-06 13:38:28 , [222.8542124349459, 223.8861317723811, 36100] - -diff , 2026-01-06 13:38:29 , 447 - -area , 2026-01-06 13:38:29 , [222.8542124349459, 223.8861317723811, 36432] - -diff , 2026-01-06 13:38:30 , 447 - -area , 2026-01-06 13:38:30 , [227.30596120647607, 220.4654167891191, 36374] - -diff , 2026-01-06 13:38:31 , 449 - -area , 2026-01-06 13:38:31 , [226.59214461229675, 223.00896842952304, 36393] - -diff , 2026-01-06 13:38:32 , 449 - -area , 2026-01-06 13:38:32 , [222.49719099350446, 223.8861317723811, 36231] - -补料:13 - -================================2026-01-06 13:41:18,2026-01-06 13:46:19,B,4585 -diff , 2026-01-06 13:46:19 , 445 - -area , 2026-01-06 13:46:19 , [299.81661061388843, 217.55459085020476, 36444] - -diff , 2026-01-06 13:46:20 , 446 - -area , 2026-01-06 13:46:20 , [296.98484809834997, 223.4390297150433, 36393] - -diff , 2026-01-06 13:46:21 , 569 - -area , 2026-01-06 13:46:21 , [222.8542124349459, 223.8861317723811, 36042] - -diff , 2026-01-06 13:46:22 , 569 - -area , 2026-01-06 13:46:22 , [293.025596151599, 227.343352662883, 36691] - -diff , 2026-01-06 13:46:23 , 569 - -area , 2026-01-06 13:46:23 , [288.51343122981297, 231.0411218809327, 36527] - -diff , 2026-01-06 13:46:24 , 446 - -area , 2026-01-06 13:46:24 , [292.6106628269038, 223.8861317723811, 36411] - -diff , 2026-01-06 13:46:25 , 446 - -area , 2026-01-06 13:46:25 , [255.93163149560078, 227.343352662883, 36103] - -diff , 2026-01-06 13:46:26 , 449 - -area , 2026-01-06 13:46:26 , [241.6112580158466, 227.56098083810414, 35767] - -diff , 2026-01-06 13:46:27 , 450 - -area , 2026-01-06 13:46:27 , [293.025596151599, 227.343352662883, 36360] - -diff , 2026-01-06 13:46:28 , 570 - -area , 2026-01-06 13:46:28 , [252.10513679812237, 231.25094594401122, 35970] - -补料:5 - -================================2026-01-06 13:48:52,2026-01-06 13:53:47,B,3040 -area , 2026-01-06 13:53:48 , [253.3081127796739, 234.61031520374377, 39025] - -area , 2026-01-06 13:53:49 , [226.01327394646538, 239.40342520523802, 39706] - -area , 2026-01-06 13:53:50 , [278.4977558257876, 239.17566765873153, 39501] - -area , 2026-01-06 13:53:51 , [226.37358503146962, 242.42524621004307, 39117] - -area , 2026-01-06 13:53:52 , [277.09384691833196, 239.17566765873153, 39129] - -area , 2026-01-06 13:53:53 , [230.10649708341572, 239.17566765873153, 39036] - -area , 2026-01-06 13:53:54 , [233.8482413874434, 235.74774654278247, 38873] - -diff , 2026-01-06 13:53:55 , 484 - -area , 2026-01-06 13:53:55 , [237.59840066801797, 235.74774654278247, 40196] - -diff , 2026-01-06 13:53:56 , 484 - -area , 2026-01-06 13:53:56 , [227.1057022621845, 243.0740627874558, 39249] - -diff , 2026-01-06 13:53:57 , 479 - -area , 2026-01-06 13:53:57 , [227.1057022621845, 243.0740627874558, 39206] - -diff , 2026-01-06 13:53:58 , 480 - -area , 2026-01-06 13:53:58 , [227.1057022621845, 243.0740627874558, 39102] - -diff , 2026-01-06 13:53:59 , 485 - -diff , 2026-01-06 13:54:00 , 484 - -diff , 2026-01-06 13:54:01 , 484 - -diff , 2026-01-06 13:54:02 , 484 - -diff , 2026-01-06 13:54:03 , 483 - -diff , 2026-01-06 13:54:04 , 481 -