Files
Feeding_control_system/vision/visual_callback copy 3.py
2025-12-12 18:00:14 +08:00

274 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)