chen.llin
11 小时以前 310a1841c3ba3331529d5791605ef1fba24c3ecd
倾斜度
6个文件已添加
2个文件已修改
707 ■■■■■ 已修改文件
src/main/java/com/rfid/uhf288/Device.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/controller/CrnController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/core/properties/ScheduleProperties.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/entity/CrnTiltRecord.java 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/mapper/CrnTiltRecordMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/service/CrnTiltRecordService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/service/impl/CrnTiltRecordServiceImpl.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/task/CrnTiltRecordTask.java 416 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/rfid/uhf288/Device.java
@@ -48,9 +48,9 @@
            // 方法1: 使用绝对路径直接加载DLL
            try {
            // 使用真实路径加载DLL
            String projectBaseDir = "D:\\apache-tomcat-8.5.57\\webapps";
            String mainDllPath = projectBaseDir + "\\wcs\\WEB-INF\\classes\\lib\\" + mainDllName;
            String depDllPath = projectBaseDir + "\\wcs\\WEB-INF\\classes\\lib\\" + depDllName;
            String projectBaseDir = "D:\\apache-tomcat-8.5.57";
            String mainDllPath = projectBaseDir + "\\webapps\\" + mainDllName;
            String depDllPath = projectBaseDir + "\\webapps\\" + depDllName;
            
            File mainDllFile = new File(mainDllPath);
            File depDllFile = new File(depDllPath);
src/main/java/com/zy/controller/CrnController.java
@@ -35,11 +35,13 @@
import com.zy.core.properties.SlaveProperties;
import com.zy.core.properties.SystemProperties;
import com.zy.core.thread.RgvThread;
import com.zy.task.CrnTiltRecordTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
@@ -68,6 +70,8 @@
    private MainServiceImpl mainService;
    @Autowired
    private LocMastService locMastService;
    @Autowired
    private CrnTiltRecordTask crnTiltRecordTask;
    @ManagerAuth(memo = "进行中的命令")
@@ -529,5 +533,20 @@
        return false;
    }
    /**
     * 手动触发堆垛机倾斜度记录
     * @param crnNo 堆垛机编号,如果为空则记录所有堆垛机
     * @return
     */
    @PostMapping("/tiltRecord/manual")
    @ManagerAuth
    public R manualTiltRecord(@RequestParam(required = false) Integer crnNo) {
        try {
            crnTiltRecordTask.manualRecord(crnNo);
            return R.ok("手动触发倾斜度记录成功");
        } catch (Exception e) {
            log.error("手动触发倾斜度记录失败", e);
            return R.error("手动触发失败:" + e.getMessage());
        }
    }
}
src/main/java/com/zy/core/properties/ScheduleProperties.java
New file
@@ -0,0 +1,23 @@
package com.zy.core.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
 * 定时任务配置
 * Created on 2024
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "schedule")
public class ScheduleProperties {
    /**
     * 定时任务总开关
     * true: 启用所有定时任务(默认)
     * false: 禁用所有定时任务
     */
    private boolean enabled = true;
}
src/main/java/com/zy/entity/CrnTiltRecord.java
New file
@@ -0,0 +1,200 @@
package com.zy.entity;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * 堆垛机倾斜度记录实体类
 * 用于存储每周记录的堆垛机倾斜度数据
 */
@TableName("asr_crn_tilt_record")
public class CrnTiltRecord implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 主键ID
     */
    @ApiModelProperty(value = "主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 堆垛机编号
     */
    @ApiModelProperty(value = "堆垛机编号")
    @TableField("crn_no")
    private Integer crnNo;
    /**
     * 倾斜度值(度或弧度,根据实际传感器单位)
     */
    @ApiModelProperty(value = "倾斜度值")
    @TableField("tilt_value")
    private Float tiltValue;
    /**
     * X方向倾斜度
     */
    @ApiModelProperty(value = "X方向倾斜度")
    @TableField("tilt_x")
    private Float tiltX;
    /**
     * Y方向倾斜度
     */
    @ApiModelProperty(value = "Y方向倾斜度")
    @TableField("tilt_y")
    private Float tiltY;
    /**
     * Z方向倾斜度
     */
    @ApiModelProperty(value = "Z方向倾斜度")
    @TableField("tilt_z")
    private Float tiltZ;
    /**
     * 记录日期(每周记录一次)
     */
    @ApiModelProperty(value = "记录日期")
    @TableField("record_date")
    private Date recordDate;
    /**
     * 记录时间
     */
    @ApiModelProperty(value = "记录时间")
    @TableField("record_time")
    private Date recordTime;
    /**
     * 上次记录的倾斜度值(用于对比)
     */
    @ApiModelProperty(value = "上次记录的倾斜度值")
    @TableField("prev_tilt_value")
    private Float prevTiltValue;
    /**
     * 倾斜度变化量(当前值-上次值)
     */
    @ApiModelProperty(value = "倾斜度变化量")
    @TableField("tilt_change")
    private Float tiltChange;
    /**
     * 记录类型:AUTO-自动记录,MANUAL-手动触发
     */
    @ApiModelProperty(value = "记录类型")
    @TableField("record_type")
    private String recordType;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public Integer getCrnNo() {
        return crnNo;
    }
    public void setCrnNo(Integer crnNo) {
        this.crnNo = crnNo;
    }
    public Float getTiltValue() {
        return tiltValue;
    }
    public void setTiltValue(Float tiltValue) {
        this.tiltValue = tiltValue;
    }
    public Float getTiltX() {
        return tiltX;
    }
    public void setTiltX(Float tiltX) {
        this.tiltX = tiltX;
    }
    public Float getTiltY() {
        return tiltY;
    }
    public void setTiltY(Float tiltY) {
        this.tiltY = tiltY;
    }
    public Float getTiltZ() {
        return tiltZ;
    }
    public void setTiltZ(Float tiltZ) {
        this.tiltZ = tiltZ;
    }
    public Date getRecordDate() {
        return recordDate;
    }
    public void setRecordDate(Date recordDate) {
        this.recordDate = recordDate;
    }
    public String getRecordDate$() {
        if (recordDate == null) {
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd").format(recordDate);
    }
    public Date getRecordTime() {
        return recordTime;
    }
    public void setRecordTime(Date recordTime) {
        this.recordTime = recordTime;
    }
    public String getRecordTime$() {
        if (recordTime == null) {
            return "";
        }
        return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(recordTime);
    }
    public Float getPrevTiltValue() {
        return prevTiltValue;
    }
    public void setPrevTiltValue(Float prevTiltValue) {
        this.prevTiltValue = prevTiltValue;
    }
    public Float getTiltChange() {
        return tiltChange;
    }
    public void setTiltChange(Float tiltChange) {
        this.tiltChange = tiltChange;
    }
    public String getRecordType() {
        return recordType;
    }
    public void setRecordType(String recordType) {
        this.recordType = recordType;
    }
}
src/main/java/com/zy/mapper/CrnTiltRecordMapper.java
New file
@@ -0,0 +1,15 @@
package com.zy.mapper;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.zy.entity.CrnTiltRecord;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
/**
 * 堆垛机倾斜度记录 Mapper
 */
@Mapper
@Repository
public interface CrnTiltRecordMapper extends BaseMapper<CrnTiltRecord> {
}
src/main/java/com/zy/service/CrnTiltRecordService.java
New file
@@ -0,0 +1,11 @@
package com.zy.service;
import com.baomidou.mybatisplus.service.IService;
import com.zy.entity.CrnTiltRecord;
/**
 * 堆垛机倾斜度记录 Service 接口
 */
public interface CrnTiltRecordService extends IService<CrnTiltRecord> {
}
src/main/java/com/zy/service/impl/CrnTiltRecordServiceImpl.java
New file
@@ -0,0 +1,15 @@
package com.zy.service.impl;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.zy.entity.CrnTiltRecord;
import com.zy.mapper.CrnTiltRecordMapper;
import com.zy.service.CrnTiltRecordService;
import org.springframework.stereotype.Service;
/**
 * 堆垛机倾斜度记录 Service 实现类
 */
@Service
public class CrnTiltRecordServiceImpl extends ServiceImpl<CrnTiltRecordMapper, CrnTiltRecord> implements CrnTiltRecordService {
}
src/main/java/com/zy/task/CrnTiltRecordTask.java
New file
@@ -0,0 +1,416 @@
package com.zy.task;
import HslCommunication.Core.Types.OperateResult;
import HslCommunication.Core.Types.OperateResultExOne;
import HslCommunication.Profinet.Siemens.SiemensPLCS;
import HslCommunication.Profinet.Siemens.SiemensS7Net;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.zy.core.model.CrnSlave;
import com.zy.core.properties.SlaveProperties;
import com.zy.entity.CrnTiltRecord;
import com.zy.service.CrnTiltRecordService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
 * 堆垛机倾斜度记录定时任务
 * 每周记录一次堆垛机的倾斜度数据
 *
 * 说明:
 * 1. 根据"货架倾斜度.txt"和"堆垛机_DB101发送wcs信息.pdf"文档
 * 2. 倾斜度数据从 PLC 的 DB101 数据块读取(具体偏移量需要根据实际传感器配置确定)
 * 3. 每周执行一次(默认每周一凌晨2点执行)
 */
@Slf4j
@Component
public class CrnTiltRecordTask implements ApplicationListener<ContextRefreshedEvent> {
    @Autowired
    private SlaveProperties slaveProperties;
    @Autowired
    private CrnTiltRecordService crnTiltRecordService;
    private boolean startupRecordExecuted = false;
    /**
     * 系统启动后自动记录一次倾斜度数据
     * 使用 ApplicationListener 监听 ContextRefreshedEvent,确保在 Spring 上下文完全初始化后执行
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 避免重复执行(Spring 可能会触发多次事件)
        if (startupRecordExecuted) {
            return;
        }
        startupRecordExecuted = true;
        log.info("========== 系统启动,开始执行堆垛机倾斜度启动记录 ==========");
        // 延迟10秒执行,确保系统完全启动,数据库连接等都已就绪
        new Thread(() -> {
            try {
                log.info("倾斜度启动记录任务将在10秒后执行...");
                Thread.sleep(10000);
                log.info("开始执行倾斜度启动记录任务...");
                recordTiltDataOnStartup();
            } catch (InterruptedException e) {
                log.error("启动记录任务被中断", e);
            } catch (Exception e) {
                log.error("启动记录任务执行异常", e);
            }
        }, "CrnTiltRecord-Startup").start();
    }
    /**
     * 启动时记录倾斜度数据(不检查当天是否已记录)
     */
    private void recordTiltDataOnStartup() {
        log.info("========== 开始执行倾斜度启动记录 ==========");
        try {
            if (slaveProperties == null) {
                log.error("SlaveProperties 未注入,跳过启动倾斜度记录");
                return;
            }
            if (crnTiltRecordService == null) {
                log.error("CrnTiltRecordService 未注入,跳过启动倾斜度记录");
                return;
            }
            List<CrnSlave> crnSlaves = slaveProperties.getCrn();
            if (crnSlaves == null || crnSlaves.isEmpty()) {
                log.warn("没有配置堆垛机,跳过启动倾斜度记录");
                return;
            }
            log.info("找到 {} 台堆垛机,开始记录倾斜度数据", crnSlaves.size());
            Date recordDate = new Date();
            Calendar cal = Calendar.getInstance();
            cal.setTime(recordDate);
            cal.set(Calendar.HOUR_OF_DAY, 0);
            cal.set(Calendar.MINUTE, 0);
            cal.set(Calendar.SECOND, 0);
            cal.set(Calendar.MILLISECOND, 0);
            Date recordDateOnly = cal.getTime();
            int successCount = 0;
            int failCount = 0;
            for (CrnSlave crnSlave : crnSlaves) {
                try {
                    // 读取倾斜度数据
                    CrnTiltRecord record = readTiltDataFromPLC(crnSlave);
                    if (record != null) {
                        record.setCrnNo(crnSlave.getId());
                        record.setRecordDate(recordDateOnly);
                        record.setRecordTime(recordDate);
                        record.setRecordType("STARTUP"); // 启动时记录
                        // 获取上次记录用于对比
                        EntityWrapper<CrnTiltRecord> lastWrapper = new EntityWrapper<>();
                        lastWrapper.eq("crn_no", crnSlave.getId());
                        lastWrapper.orderBy("record_date", false);
                        lastWrapper.orderBy("record_time", false);
                        lastWrapper.last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY");
                        CrnTiltRecord lastRecord = crnTiltRecordService.selectOne(lastWrapper);
                        if (lastRecord != null && lastRecord.getTiltValue() != null) {
                            // 对比倾斜度
                            record.setPrevTiltValue(lastRecord.getTiltValue());
                            if (record.getTiltValue() != null) {
                                float change = record.getTiltValue() - lastRecord.getTiltValue();
                                record.setTiltChange(change);
                                log.info("堆垛机{}启动倾斜度对比:当前值={}, 上次值={}, 变化量={}",
                                    crnSlave.getId(), record.getTiltValue(),
                                    lastRecord.getTiltValue(), change);
                            }
                        }
                        // 保存到数据库
                        if (crnTiltRecordService.insert(record)) {
                            successCount++;
                            log.info("堆垛机{}启动倾斜度记录成功:倾斜度={}, X={}, Y={}, Z={}, 变化量={}",
                                crnSlave.getId(), record.getTiltValue(),
                                record.getTiltX(), record.getTiltY(), record.getTiltZ(),
                                record.getTiltChange() != null ? record.getTiltChange() : 0);
                        } else {
                            failCount++;
                            log.error("堆垛机{}启动倾斜度记录保存失败", crnSlave.getId());
                        }
                    } else {
                        failCount++;
                        log.error("堆垛机{}启动倾斜度数据读取失败", crnSlave.getId());
                    }
                } catch (Exception e) {
                    failCount++;
                    log.error("堆垛机{}启动倾斜度记录异常", crnSlave.getId(), e);
                }
            }
            log.info("堆垛机倾斜度启动记录完成:成功{}条,失败{}条", successCount, failCount);
        } catch (Exception e) {
            log.error("堆垛机倾斜度启动记录任务执行异常", e);
        }
    }
    /**
     * 每周记录一次堆垛机倾斜度数据
     * cron表达式:0 0 2 ? * MON 表示每周一凌晨2点执行
     * 如果需要修改执行时间,可以调整cron表达式
     */
    @Scheduled(cron = "0 0 2 ? * MON")
    public void recordCrnTiltData() {
        log.info("开始执行堆垛机倾斜度记录任务...");
        try {
            List<CrnSlave> crnSlaves = slaveProperties.getCrn();
            if (crnSlaves == null || crnSlaves.isEmpty()) {
                log.warn("没有配置堆垛机,跳过倾斜度记录");
                return;
            }
            Date recordDate = new Date();
            Calendar cal = Calendar.getInstance();
            cal.setTime(recordDate);
            // 设置为当天的开始时间(00:00:00)
            cal.set(Calendar.HOUR_OF_DAY, 0);
            cal.set(Calendar.MINUTE, 0);
            cal.set(Calendar.SECOND, 0);
            cal.set(Calendar.MILLISECOND, 0);
            Date recordDateOnly = cal.getTime();
            int successCount = 0;
            int failCount = 0;
            for (CrnSlave crnSlave : crnSlaves) {
                try {
                    // 检查本周是否已经记录过
                    EntityWrapper<CrnTiltRecord> wrapper = new EntityWrapper<>();
                    wrapper.eq("crn_no", crnSlave.getId());
                    wrapper.ge("record_date", recordDateOnly);
                    wrapper.le("record_date", new Date(recordDateOnly.getTime() + 24 * 60 * 60 * 1000 - 1));
                    int count = crnTiltRecordService.selectCount(wrapper);
                    if (count > 0) {
                        log.info("堆垛机{}本周已记录过倾斜度数据,跳过", crnSlave.getId());
                        continue;
                    }
                    // 读取倾斜度数据
                    CrnTiltRecord record = readTiltDataFromPLC(crnSlave);
                    if (record != null) {
                        record.setCrnNo(crnSlave.getId());
                        record.setRecordDate(recordDateOnly);
                        record.setRecordTime(recordDate);
                        record.setRecordType("AUTO"); // 自动记录
                        // 获取上次记录用于对比
                        EntityWrapper<CrnTiltRecord> lastWrapper = new EntityWrapper<>();
                        lastWrapper.eq("crn_no", crnSlave.getId());
                        lastWrapper.orderBy("record_date", false);
                        lastWrapper.orderBy("record_time", false);
                        lastWrapper.last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY");
                        CrnTiltRecord lastRecord = crnTiltRecordService.selectOne(lastWrapper);
                        if (lastRecord != null && lastRecord.getTiltValue() != null) {
                            // 对比倾斜度
                            record.setPrevTiltValue(lastRecord.getTiltValue());
                            if (record.getTiltValue() != null) {
                                float change = record.getTiltValue() - lastRecord.getTiltValue();
                                record.setTiltChange(change);
                                log.info("堆垛机{}倾斜度对比:当前值={}, 上次值={}, 变化量={}",
                                    crnSlave.getId(), record.getTiltValue(),
                                    lastRecord.getTiltValue(), change);
                            }
                        }
                        // 保存到数据库
                        if (crnTiltRecordService.insert(record)) {
                            successCount++;
                            log.info("堆垛机{}倾斜度记录成功:倾斜度={}, X={}, Y={}, Z={}, 变化量={}",
                                crnSlave.getId(), record.getTiltValue(),
                                record.getTiltX(), record.getTiltY(), record.getTiltZ(),
                                record.getTiltChange() != null ? record.getTiltChange() : 0);
                        } else {
                            failCount++;
                            log.error("堆垛机{}倾斜度记录保存失败", crnSlave.getId());
                        }
                    } else {
                        failCount++;
                        log.error("堆垛机{}倾斜度数据读取失败", crnSlave.getId());
                    }
                } catch (Exception e) {
                    failCount++;
                    log.error("堆垛机{}倾斜度记录异常", crnSlave.getId(), e);
                }
            }
            log.info("堆垛机倾斜度记录任务完成:成功{}条,失败{}条", successCount, failCount);
        } catch (Exception e) {
            log.error("堆垛机倾斜度记录任务执行异常", e);
        }
    }
    /**
     * 从 PLC 读取倾斜度数据
     *
     * 注意:根据实际传感器配置,倾斜度数据可能存储在:
     * 1. DB101 的某个偏移量位置(需要根据"货架倾斜度.txt"文档确定具体位置)
     * 2. 或者其他数据块
     *
     * 当前实现假设倾斜度数据存储在 DB101 的偏移量位置(需要根据实际配置调整)
     *
     * @param crnSlave 堆垛机配置
     * @return 倾斜度记录对象,如果读取失败返回null
     */
    private CrnTiltRecord readTiltDataFromPLC(CrnSlave crnSlave) {
        SiemensS7Net siemensNet = null;
        try {
            // 创建 PLC 连接
            siemensNet = new SiemensS7Net(SiemensPLCS.S1200, crnSlave.getIp());
            siemensNet.setRack(crnSlave.getRack().byteValue());
            siemensNet.setSlot(crnSlave.getSlot().byteValue());
            OperateResult connect = siemensNet.ConnectServer();
            if (!connect.IsSuccess) {
                log.error("堆垛机{} PLC连接失败:{}", crnSlave.getId(), connect.Message);
                return null;
            }
            // 读取倾斜度数据
            // 注意:根据"货架倾斜度.txt"文档,需要确定倾斜度数据在 DB101 中的具体偏移量
            // 假设倾斜度数据存储在 DB101.56 开始的位置(需要根据实际配置调整)
            // 读取4个Real类型数据(X、Y、Z倾斜度和总倾斜度),每个Real占4字节,共16字节
            OperateResultExOne<byte[]> result = siemensNet.Read("DB101.56", (short) 16);
            if (!result.IsSuccess) {
                log.error("堆垛机{}读取倾斜度数据失败:{}", crnSlave.getId(), result.Message);
                return null;
            }
            CrnTiltRecord record = new CrnTiltRecord();
            // 解析倾斜度数据(根据实际传感器数据格式调整)
            // 假设:偏移量0-3: X方向倾斜度(Real),偏移量4-7: Y方向倾斜度(Real),偏移量8-11: Z方向倾斜度(Real),偏移量12-15: 总倾斜度(Real)
            record.setTiltX(siemensNet.getByteTransform().TransSingle(result.Content, 0));
            record.setTiltY(siemensNet.getByteTransform().TransSingle(result.Content, 4));
            record.setTiltZ(siemensNet.getByteTransform().TransSingle(result.Content, 8));
            record.setTiltValue(siemensNet.getByteTransform().TransSingle(result.Content, 12));
            log.debug("堆垛机{}倾斜度数据读取成功:X={}, Y={}, Z={}, 总倾斜度={}",
                crnSlave.getId(), record.getTiltX(), record.getTiltY(),
                record.getTiltZ(), record.getTiltValue());
            return record;
        } catch (Exception e) {
            log.error("堆垛机{}读取倾斜度数据异常", crnSlave.getId(), e);
            return null;
        } finally {
            if (siemensNet != null) {
                try {
                    siemensNet.ConnectClose();
                } catch (Exception e) {
                    log.warn("关闭PLC连接异常", e);
                }
            }
        }
    }
    /**
     * 手动触发记录(用于测试或手动记录)
     * @param crnNo 堆垛机编号,如果为null则记录所有堆垛机
     */
    public void manualRecord(Integer crnNo) {
        log.info("手动触发堆垛机倾斜度记录任务,堆垛机编号:{}", crnNo);
        try {
            List<CrnSlave> crnSlaves = slaveProperties.getCrn();
            if (crnSlaves == null || crnSlaves.isEmpty()) {
                log.warn("没有配置堆垛机,跳过倾斜度记录");
                return;
            }
            Date recordDate = new Date();
            Calendar cal = Calendar.getInstance();
            cal.setTime(recordDate);
            cal.set(Calendar.HOUR_OF_DAY, 0);
            cal.set(Calendar.MINUTE, 0);
            cal.set(Calendar.SECOND, 0);
            cal.set(Calendar.MILLISECOND, 0);
            Date recordDateOnly = cal.getTime();
            int successCount = 0;
            int failCount = 0;
            for (CrnSlave crnSlave : crnSlaves) {
                // 如果指定了堆垛机编号,只记录指定的堆垛机
                if (crnNo != null && !crnNo.equals(crnSlave.getId())) {
                    continue;
                }
                try {
                    // 读取倾斜度数据
                    CrnTiltRecord record = readTiltDataFromPLC(crnSlave);
                    if (record != null) {
                        record.setCrnNo(crnSlave.getId());
                        record.setRecordDate(recordDateOnly);
                        record.setRecordTime(recordDate);
                        record.setRecordType("MANUAL"); // 手动触发
                        // 获取上次记录用于对比
                        EntityWrapper<CrnTiltRecord> lastWrapper = new EntityWrapper<>();
                        lastWrapper.eq("crn_no", crnSlave.getId());
                        lastWrapper.orderBy("record_date", false);
                        lastWrapper.orderBy("record_time", false);
                        lastWrapper.last("OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY");
                        CrnTiltRecord lastRecord = crnTiltRecordService.selectOne(lastWrapper);
                        if (lastRecord != null && lastRecord.getTiltValue() != null) {
                            // 对比倾斜度
                            record.setPrevTiltValue(lastRecord.getTiltValue());
                            if (record.getTiltValue() != null) {
                                float change = record.getTiltValue() - lastRecord.getTiltValue();
                                record.setTiltChange(change);
                                log.info("堆垛机{}倾斜度对比:当前值={}, 上次值={}, 变化量={}",
                                    crnSlave.getId(), record.getTiltValue(),
                                    lastRecord.getTiltValue(), change);
                            }
                        }
                        // 保存到数据库
                        if (crnTiltRecordService.insert(record)) {
                            successCount++;
                            log.info("堆垛机{}手动倾斜度记录成功:倾斜度={}, X={}, Y={}, Z={}, 变化量={}",
                                crnSlave.getId(), record.getTiltValue(),
                                record.getTiltX(), record.getTiltY(), record.getTiltZ(),
                                record.getTiltChange() != null ? record.getTiltChange() : 0);
                        } else {
                            failCount++;
                            log.error("堆垛机{}倾斜度记录保存失败", crnSlave.getId());
                        }
                    } else {
                        failCount++;
                        log.error("堆垛机{}倾斜度数据读取失败", crnSlave.getId());
                    }
                } catch (Exception e) {
                    failCount++;
                    log.error("堆垛机{}倾斜度记录异常", crnSlave.getId(), e);
                }
            }
            log.info("手动堆垛机倾斜度记录任务完成:成功{}条,失败{}条", successCount, failCount);
        } catch (Exception e) {
            log.error("手动堆垛机倾斜度记录任务执行异常", e);
        }
    }
}