stage_one

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

View File

@ -0,0 +1,94 @@
# config/settings.py
import os
class Settings:
def __init__(self):
# 项目根目录
self.project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
self.test_need_weight=2000
# 网络继电器配置
self.relay_host = '192.168.250.62'
self.relay_port = 50000
self.debug_feeding=False
#调试模式上,网络继点器禁用,模型推理启用
self.debug_mode=False
# 摄像头配置
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
self.camera_configs = {
# 'cam1': {
# 'type': 'ip',
# 'ip': '192.168.250.60',
# 'port': 554,
# 'username': 'admin',
# 'password': 'XJ123456',
# 'channel': 1
# },
'cam2': {
'type': 'ip',
'ip': '192.168.250.61',
'port': 554,
'username': 'admin',
'password': 'XJ123456',
'channel': 1
}
}
# 下料控制参数
self.min_required_weight = 500 # 模具车最小需要重量(kg)
self.target_vehicle_weight = 5000 # 目标模具车重量(kg)
self.upper_buffer_weight = 500 # 上料斗缓冲重量(kg)
self.single_batch_weight = 2500 # 单次下料重量(kg)
# 角度控制参数
self.target_angle = 30.0 # 目标角度
self.min_angle = 10.0 # 最小角度
self.max_angle = 80.0 # 最大角度
self.angle_threshold = 50.0 # 角度阈值
self.angle_tolerance = 5.0 # 角度容差
# 变频器配置
self.inverter_max_frequency = 400.0 # 频率最大值
self.frequencies = [220.0, 230.0, 240.0] # 下料阶段频率Hz
# 模型路径配置
self.models_dir = os.path.join(self.project_root, 'vision')
self.angle_model_path = os.path.join(self.models_dir, 'obb_angle_model', 'obb.rknn')
self.overflow_model_path = os.path.join(self.models_dir,'overflow_model', 'yiliao_cls.rknn')
# self.alignment_model_path = os.path.join(self.models_dir, 'align_model', 'yolov11_cls_640v6.rknn')
# ROI路径配置
self.roi_file_path = os.path.join(self.models_dir, 'overflow_model', 'roi_coordinates', '1_rois.txt')
# 系统控制参数
self.visual_check_interval = 1.0 # 视觉检查间隔(秒)
self.alignment_check_interval = 0.5 # 对齐检查间隔(秒)
self.max_error_count = 3 # 最大错误计数
self.lower_feeding_interval = 0.1 # 下料轮询间隔(秒)
# RFID配置
self.rfid_host = '192.168.1.190'
self.rfid_port = 6000
#是否在线生产
self.is_online_control = True # 是否API在线
#最后一块进行尾数控制
# self.block_numbers=['B1','B2','B3','L1','L2','F']
#需核实上下位漏斗容量
self.max_upper_volume = 2.4 # 上料斗容量(方)
#下料到下料斗最大下到多少,并非最大容量
self.max_lower_volume = 2.2 # 下料斗容量(方)
#led
self.led_interval = 2 # LED闪烁间隔(秒)
app_set_config = Settings()

View File

@ -0,0 +1,219 @@
# hardware/relay.py
import socket
import binascii
import time
import threading
from pymodbus.client import ModbusTcpClient
from pymodbus.exceptions import ModbusException
from config.settings import app_set_config
class RelayController:
# 继电器映射
RING = 'ring' # DO1 - 响铃
UPPER_TO_JBL = 'upper_to_jbl' # DO2 - 上料斗到搅拌楼
UPPER_TO_ZD = 'upper_to_zd' # DO3 - 上料斗到振捣室
# DOOR_UPPER = 'door_upper' # DO0 - 上料斗滑动
DOOR_LOWER_OPEN = 'door_lower_open' # DO1 - 下料斗出砼门开角度
DOOR_LOWER_CLOSE = 'door_lower_close' # DO2 - 下料斗出砼门关角度角度在7.5以下可关闭信号)
DOOR_UPPER_OPEN = 'door_upper_open' # DO3 - 上料斗开
DOOR_UPPER_CLOSE = 'door_upper_close' # DO4 - 上料斗关
BREAK_ARCH_UPPER = 'break_arch_upper' # DO3 - 上料斗震动
BREAK_ARCH_LOWER = 'break_arch_lower' # DO4 - 下料斗震动
DIRECT_LOWER_FRONT = 'direct_lower_front' # DO5 - 下料斗前
DIRECT_LOWER_BEHIND = 'direct_lower_behind' # DO6 - 下料斗后
DIRECT_LOWER_TOP = 'direct_lower_top' # DO7 - 下料斗上
DIRECT_LOWER_BELOW = 'direct_lower_below' # DO8 - 下料斗下
def __init__(self, host='192.168.250.62', port=50000):
self.host = host
self.port = port
self.modbus_client = ModbusTcpClient(host, port=port)
#遥1 DO 7 左 DO8 右 角度 摇2DO 15下 13上 12 往后 14往前 下料斗DO7开 D09关
# 继电器命令原始Socket
self.relay_commands = {
self.RING: {'open': '00000000000601050000FF00', 'close': '000000000006010500000000'},
self.UPPER_TO_JBL: {'open': '00000000000601050001FF00', 'close': '000000000006010500010000'},
self.UPPER_TO_ZD: {'open': '00000000000601050002FF00', 'close': '000000000006010500020000'},
self.DOOR_LOWER_OPEN: {'open': '00000000000601050006FF00', 'close': '000000000006010500060000'},
self.DOOR_LOWER_CLOSE: {'open': '00000000000601050008FF00', 'close':'000000000006010500080000'},
self.DOOR_UPPER_OPEN: {'open': '00000000000601050003FF00', 'close': '000000000006010500030000'},
self.DOOR_UPPER_CLOSE: {'open': '00000000000601050004FF00', 'close': '000000000006010500040000'},
self.BREAK_ARCH_UPPER: {'open': '0000000000060105000AFF00', 'close': '0000000000060105000A0000'},
self.BREAK_ARCH_LOWER: {'open': '00000000000601050005FF00', 'close': '000000000006010500050000'},
self.DIRECT_LOWER_FRONT: {'open': '0000000000060105000DFF00', 'close': '0000000000060105000D0000'},
self.DIRECT_LOWER_BEHIND: {'open': '0000000000060105000BFF00', 'close': '0000000000060105000B0000'},
self.DIRECT_LOWER_TOP: {'open': '0000000000060105000CFF00', 'close': '0000000000060105000C0000'},
self.DIRECT_LOWER_BELOW: {'open': '0000000000060105000EFF00', 'close': '0000000000060105000E0000'}
}
# 读取状态命令
self.read_status_command = '000000000006010100000008'
# 设备位映射
self.device_bit_map = {
self.RING: 0,
self.UPPER_TO_JBL: 1,
self.UPPER_TO_ZD: 2,
self.BREAK_ARCH_UPPER: 3,
self.BREAK_ARCH_LOWER: 4
}
def send_command(self, command_hex):
"""发送原始Socket命令"""
if app_set_config.debug_mode:
return None
try:
byte_data = binascii.unhexlify(command_hex)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((self.host, self.port))
sock.send(byte_data)
response = sock.recv(1024)
print(f"收到继电器响应: {binascii.hexlify(response)}")
return response
except Exception as e:
print(f"继电器通信错误: {e}")
return None
def get_status(self):
"""获取继电器状态"""
response = self.send_command(self.read_status_command)
status_dict = {}
if response and len(response) >= 10:
status_byte = response[9]
status_bin = f"{status_byte:08b}"[::-1]
for key, bit_index in self.device_bit_map.items():
status_dict[key] = status_bin[bit_index] == '1'
else:
print("读取继电器状态失败")
return status_dict
def control(self, device, action):
"""控制继电器"""
if device in self.relay_commands and action in self.relay_commands[device]:
print(f"发送控制继电器命令 {device} {action}")
self.send_command(self.relay_commands[device][action])
else:
print(f"无效设备或动作: {device}, {action}")
def control_upper_close(self):
"""控制上料斗关"""
# 关闭上料斗出砼门
self.control(self.DOOR_UPPER_OPEN, 'close')
self.control(self.DOOR_UPPER_CLOSE, 'open')
# 异步5秒后关闭
threading.Thread(target=self._close_upper_s, daemon=True,name="close_upper_s").start()
def control_lower_close(self):
"""控制下料斗关"""
# 关闭下料斗出砼门
self.control(self.DOOR_LOWER_OPEN, 'close')
self.control(self.DOOR_LOWER_CLOSE, 'open')
time.sleep(5)
self.control(self.DOOR_LOWER_CLOSE, 'close')
# 异步5秒后关闭
# threading.Thread(target=self._close_lower_5s, daemon=True,name="close_lower_5s").start()
def control_upper_open_sync(self):
self.control(self.DOOR_UPPER_CLOSE, 'close')
self.control(self.DOOR_UPPER_OPEN, 'open')
def control_upper_close_sync(self):
self.control(self.DOOR_LOWER_OPEN, 'close')
self.control(self.DOOR_LOWER_CLOSE, 'open')
def control_upper_open(self):
#关闭信号才能生效
self.control(self.DOOR_UPPER_CLOSE, 'close')
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.2)
self.control(self.DOOR_UPPER_OPEN, 'close')
time.sleep(1)
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.2)
self.control(self.DOOR_UPPER_OPEN, 'close')
time.sleep(1)
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.5)
self.control(self.DOOR_UPPER_OPEN, 'close')
time.sleep(0.1)
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.5)
self.control(self.DOOR_UPPER_OPEN, 'close')
time.sleep(0.1)
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.5)
self.control(self.DOOR_UPPER_OPEN, 'close')
time.sleep(0.1)
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.5)
self.control(self.DOOR_UPPER_OPEN, 'close')
time.sleep(0.1)
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.5)
self.control(self.DOOR_UPPER_OPEN, 'close')
#保持8秒
time.sleep(8)
#8秒后再开5秒
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.5)
self.control(self.DOOR_UPPER_OPEN, 'close')
time.sleep(0.1)
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.5)
self.control(self.DOOR_UPPER_OPEN, 'close')
time.sleep(0.1)
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.5)
self.control(self.DOOR_UPPER_OPEN, 'close')
time.sleep(0.1)
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.5)
self.control(self.DOOR_UPPER_OPEN, 'close')
time.sleep(0.1)
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.5)
self.control(self.DOOR_UPPER_OPEN, 'close')
time.sleep(0.1)
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.5)
self.control(self.DOOR_UPPER_OPEN, 'close')
time.sleep(0.1)
self.control(self.DOOR_UPPER_OPEN, 'open')
time.sleep(0.5)
self.control(self.DOOR_UPPER_OPEN, 'close')
def control_ring_open(self):
"""控制下料斗关"""
# 关闭下料斗出砼门
self.control(self.RING, 'open')
# 异步5秒后关闭
threading.Thread(target=self._close_ring, daemon=True,name="close_ring").start()
def _close_upper_s(self):
time.sleep(16)
self.control(self.DOOR_UPPER_CLOSE, 'close')
print("上料斗关闭完成")
def _close_lower_5s(self):
time.sleep(6)
self.control(self.DOOR_LOWER_CLOSE, 'close')
def _close_ring(self):
time.sleep(3)
self.control(self.RING, 'close')
def close_all(self):
"""关闭所有继电器"""
self.control(self.UPPER_TO_JBL, 'close')
self.control(self.UPPER_TO_ZD, 'close')
self.control(self.BREAK_ARCH_UPPER, 'close')
self.control(self.BREAK_ARCH_LOWER, 'close')
self.control(self.RING, 'close')
self.control(self.DOOR_LOWER_OPEN, 'close')
self.control(self.DOOR_LOWER_CLOSE, 'close')
self.control(self.DOOR_UPPER_OPEN, 'close')
self.control(self.DOOR_UPPER_CLOSE, 'close')

View File

@ -0,0 +1,222 @@
from config.settings import app_set_config
from hardware.relay import RelayController
import time
import threading
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):
"""
视觉控制主逻辑,供外部推送数据
使用单个持续运行的线程,通过参数设置传递数据
如果线程正在处理数据,则丢弃此次推送
"""
# 尝试获取处理锁,若失败则说明正在处理,丢弃数据
if not self._is_processing.acquire(blocking=False):
print("回调线程仍在执行,丢弃此次推送数据")
return
try:
# 更新参数
self._current_angle = current_angle
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.1)
def _process_angle_callback(self, current_angle, overflow_detected):
"""
内部方法,实际处理视觉回调逻辑
在异步线程中执行
"""
try:
# print('current_angle:', current_angle, 'overflow_detected:', overflow_detected)
# return
# 检测溢出状态
self.overflow = overflow_detected in ["大堆料", "小堆料"]
if current_angle is None:
print("无法获取当前角度,跳过本次调整")
return
print(f"当前角度: {current_angle:.2f}°")
if overflow_detected != "浇筑满":
# 状态机控制逻辑
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_OPEN, 'close')
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:
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("进入维持模式")
# 关门时间
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.2)
# 开门时间
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.25)
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)