huang
2025-11-26 792236ef78c2cdd3a989fb40a7f2e2487c4e17b6
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java
@@ -302,7 +302,14 @@
        Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoFeed);
        List<GlassInfo> plannedGlasses = planGlassLoading(glassInfos, vehicleCapacity,
                getLogicParam(logicParams, "defaultGlassLength", 2000));
                deviceConfig.getDeviceId());
        if (plannedGlasses == null) {
            // 玻璃没有长度时返回null表示错误
            return DevicePlcVO.OperationResult.builder()
                    .success(false)
                    .message("玻璃信息缺少长度数据,无法进行容量计算。请检查MES程序是否正确提供玻璃长度。")
                    .build();
        }
        if (plannedGlasses.isEmpty()) {
            return DevicePlcVO.OperationResult.builder()
                    .success(false)
@@ -551,13 +558,6 @@
        defaultParams.put("taskMonitorIntervalMs", 1000); // 任务监控间隔(毫秒)
        defaultParams.put("mesConfirmTimeoutMs", 30000); // MES确认超时(毫秒)
        
        // 出片任务相关配置
        // outboundSlotRanges: 出片任务的startSlot范围,例如[1, 101]表示格子1~101都是出片任务
        // 如果不配置,则通过判断startSlot是否在positionMapping中来区分进片/出片
        List<Integer> outboundSlotRanges = new ArrayList<>();
        outboundSlotRanges.add(1);   // 最小格子编号
        outboundSlotRanges.add(101); // 最大格子编号
        defaultParams.put("outboundSlotRanges", outboundSlotRanges);
        
        // gridPositionMapping: 格子编号到位置的映射表(可选)
        // 如果不配置,则格子编号直接作为位置值
@@ -644,16 +644,32 @@
        return null;
    }
    /**
     * 规划玻璃装载
     * @param source 源玻璃列表
     * @param vehicleCapacity 车辆容量
     * @param deviceId 设备ID(用于日志)
     * @return 规划后的玻璃列表,如果玻璃没有长度则返回null(用于测试MES程序)
     */
    private List<GlassInfo> planGlassLoading(List<GlassInfo> source,
                                             int vehicleCapacity,
                                             Integer defaultGlassLength) {
                                             String deviceId) {
        List<GlassInfo> planned = new ArrayList<>();
        int usedLength = 0;
        int capacity = Math.max(vehicleCapacity, 1);
        int fallbackLength = defaultGlassLength != null && defaultGlassLength > 0 ? defaultGlassLength : 2000;
        for (GlassInfo info : source) {
            int length = info.getLength() != null && info.getLength() > 0 ? info.getLength() : fallbackLength;
            Integer glassLength = info.getLength();
            if (glassLength == null || glassLength <= 0) {
                // 玻璃没有长度,直接报错(用于测试MES程序)
                log.error("玻璃[{}]缺少长度数据,无法进行容量计算。deviceId={},请检查MES程序是否正确提供玻璃长度。",
                        info.getGlassId(), deviceId);
                return null;
            }
            int length = glassLength;
            if (planned.isEmpty()) {
                planned.add(info.withLength(length));
                usedLength = length;
@@ -1269,15 +1285,19 @@
        // 这里简化处理:如果startSlot不在positionMapping中,且是数字,可能是格子编号
        // 可以通过配置指定格子编号范围,或者通过查找同组设备判断
        
        // 方法3:通过配置指定出片任务的startSlot范围
        // 方法3:通过配置指定车辆运动格子范围(兼容旧配置outboundSlotRanges)
        @SuppressWarnings("unchecked")
        List<Integer> outboundSlotRanges = getLogicParam(logicParams, "outboundSlotRanges", null);
        if (outboundSlotRanges != null && !outboundSlotRanges.isEmpty()) {
            // 如果配置了出片slot范围,检查startSlot是否在范围内
            // 例如:[1, 101] 表示格子1~101都是出片任务
            if (outboundSlotRanges.size() >= 2) {
                int minSlot = outboundSlotRanges.get(0);
                int maxSlot = outboundSlotRanges.get(1);
        List<Integer> vehicleSlotRange = getLogicParam(logicParams, "vehicleSlotRange", null);
        if (vehicleSlotRange == null || vehicleSlotRange.isEmpty()) {
            // 兼容旧配置
            vehicleSlotRange = getLogicParam(logicParams, "outboundSlotRanges", null);
        }
        if (vehicleSlotRange != null && !vehicleSlotRange.isEmpty()) {
            // 如果配置了车辆运动格子范围,检查startSlot是否在范围内
            // 例如:[1, 101] 表示车辆只能在格子1~101之间运动
            if (vehicleSlotRange.size() >= 2) {
                int minSlot = vehicleSlotRange.get(0);
                int maxSlot = vehicleSlotRange.get(1);
                if (startSlot >= minSlot && startSlot <= maxSlot) {
                    return true;
                }
@@ -1361,6 +1381,25 @@
     */
    private TimeCalculation calculateTime(Integer currentPos, Integer startPos, 
                                          Integer targetPos, Map<String, Object> logicParams) {
        // 验证车辆运动格子范围
        @SuppressWarnings("unchecked")
        List<Integer> vehicleSlotRange = getLogicParam(logicParams, "vehicleSlotRange", null);
        if (vehicleSlotRange == null || vehicleSlotRange.isEmpty()) {
            // 兼容旧配置
            vehicleSlotRange = getLogicParam(logicParams, "outboundSlotRanges", null);
        }
        if (vehicleSlotRange != null && vehicleSlotRange.size() >= 2) {
            int minSlot = vehicleSlotRange.get(0);
            int maxSlot = vehicleSlotRange.get(1);
            // 验证startPos和targetPos是否在允许的范围内
            if (startPos != null && (startPos < minSlot || startPos > maxSlot)) {
                log.warn("起始位置 {} 超出车辆运动格子范围 [{}, {}]", startPos, minSlot, maxSlot);
            }
            if (targetPos != null && (targetPos < minSlot || targetPos > maxSlot)) {
                log.warn("目标位置 {} 超出车辆运动格子范围 [{}, {}]", targetPos, minSlot, maxSlot);
            }
        }
        // 获取速度(格/秒,grid/s)
        Double speed = getLogicParam(logicParams, "vehicleSpeed", 1.0);
        if (speed == null || speed <= 0) {