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)