0209整理

This commit is contained in:
2026-02-10 10:18:17 +08:00
parent d6ad01274a
commit 6e74eaf206
62 changed files with 838 additions and 9136 deletions

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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("--------------------开始下料(普通块)--------------------")

View File

@ -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)

View File

@ -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()
#获取diffdiff_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

View File

@ -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()
#获取diffdiff_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()

View File

@ -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