0209整理
This commit is contained in:
137
vision/.py
137
vision/.py
@ -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()
|
||||
@ -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()
|
||||
|
||||
|
||||
@ -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()
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -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("--------------------开始下料(普通块)--------------------")
|
||||
|
||||
@ -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)
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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()
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user