stage_one

This commit is contained in:
2025-12-12 18:00:14 +08:00
parent dc4ef9002e
commit b8b9679bc8
55 changed files with 6541 additions and 459 deletions

View File

@ -0,0 +1,277 @@
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)