mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java
@@ -1,10 +1,12 @@
package com.mes.interaction.vehicle.handler;
import com.mes.device.entity.DeviceConfig;
import com.mes.device.entity.DeviceStatus;
import com.mes.device.service.DeviceConfigService;
import com.mes.device.service.DeviceGroupRelationService;
import com.mes.device.service.DevicePlcOperationService;
import com.mes.device.service.GlassInfoService;
import com.mes.device.service.DeviceStatusService;
import com.mes.device.vo.DeviceGroupVO;
import com.mes.device.vo.DevicePlcVO;
import com.mes.interaction.BaseDeviceLogicHandler;
@@ -49,6 +51,9 @@
    
    @Autowired(required = false)
    private DeviceGroupRelationService deviceGroupRelationService;
    @Autowired(required = false)
    private DeviceStatusService deviceStatusService;
    
    @Autowired(required = false)
    private PlcDynamicDataService plcDynamicDataService;
@@ -189,6 +194,9 @@
                case "stopTaskMonitor":
                    result = handleStopTaskMonitor(deviceConfig);
                    break;
                case "setOnlineState":
                    result = handleSetOnlineState(deviceConfig, params, logicParams);
                    break;
                default:
                    log.warn("不支持的操作类型: {}", operation);
                    result = DevicePlcVO.OperationResult.builder()
@@ -221,8 +229,14 @@
     * 判断操作是否需要状态检查
     */
    private boolean needsStateCheck(String operation) {
        // 所有操作都需要检查状态,除了查询类操作
        return !"query".equals(operation) && !"status".equals(operation);
        // 所有操作都需要检查状态,除了查询类操作和特定内部检查
        if ("query".equals(operation) || "status".equals(operation)) {
            return false;
        }
        if ("checkMesConfirm".equals(operation)) {
            return false;
        }
        return true;
    }
    /**
@@ -302,23 +316,7 @@
        // 从逻辑参数中获取配置(从 extraParams.deviceLogic 读取)
        Integer vehicleCapacity = getLogicParam(logicParams, "vehicleCapacity", 6000);
        // 优先使用运行时参数中的glassIntervalMs(从任务参数传入),如果没有则使用设备配置的
        Integer glassIntervalMs = null;
        if (params.containsKey("glassIntervalMs") && params.get("glassIntervalMs") != null) {
            Object intervalObj = params.get("glassIntervalMs");
            if (intervalObj instanceof Number) {
                glassIntervalMs = ((Number) intervalObj).intValue();
            } else if (intervalObj instanceof String) {
                try {
                    glassIntervalMs = Integer.parseInt((String) intervalObj);
                } catch (NumberFormatException e) {
                    // 忽略
                }
            }
        }
        if (glassIntervalMs == null) {
            glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", 1000);
        }
        Integer glassGap = getLogicParam(logicParams, "glassGap", 200); // 玻璃之间的物理间隔(mm),默认200mm
        Boolean autoFeed = getLogicParam(logicParams, "autoFeed", true);
        Integer maxRetryCount = getLogicParam(logicParams, "maxRetryCount", 5);
@@ -335,7 +333,7 @@
        Integer positionValue = (Integer) params.get("positionValue");
        Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoFeed);
        List<GlassInfo> plannedGlasses = planGlassLoading(glassInfos, vehicleCapacity,
        List<GlassInfo> plannedGlasses = planGlassLoading(glassInfos, vehicleCapacity, glassGap,
                deviceConfig.getDeviceId());
        if (plannedGlasses == null) {
            // 玻璃没有长度时返回null表示错误
@@ -371,17 +369,21 @@
        }
        payload.put("plcGlassCount", plcSlots);
        // 写入位置信息
        // 写入位置信息:PLC侧期望的是 MES 编号(如1001/1002),而不是位置映射后的格子值
        Integer plcPosition = null;
        if (positionValue != null) {
            payload.put("inPosition", positionValue);
            // 如果调用方直接传了数值,则认为这是MES编号,直接写入
            plcPosition = positionValue;
        } else if (positionCode != null) {
            // 从位置映射中获取位置值
            @SuppressWarnings("unchecked")
            Map<String, Integer> positionMapping = getLogicParam(logicParams, "positionMapping", new HashMap<>());
            Integer mappedValue = positionMapping.get(positionCode);
            if (mappedValue != null) {
                payload.put("inPosition", mappedValue);
            // 尝试将位置代码解析为数字(例如 "900" -> 900)
            try {
                plcPosition = Integer.parseInt(positionCode.trim());
            } catch (NumberFormatException ignore) {
                // 非数字编码时,不写入inPosition,由PLC或后续逻辑自行处理
            }
        }
        if (plcPosition != null) {
            payload.put("inPosition", plcPosition);
        }
        // 自动触发请求字
@@ -401,11 +403,7 @@
        DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields(
            deviceConfig.getId(), payload, operationName);
        
        // 注意:glassIntervalMs 的等待应该在批次之间(在TaskExecutionEngine中处理),
        // 而不是在这里等待,因为这里等待会阻塞大车的正常装玻璃流程
        // 如果需要在写入后等待,应该在批次之间等待,让大车有时间处理当前批次的玻璃
        // 如果执行成功,更新位置信息到状态,并启动状态监控
        // 如果执行成功,更新位置信息到状态
        if (Boolean.TRUE.equals(result.getSuccess())) {
            VehicleStatus status = statusManager.getOrCreateVehicleStatus(
                deviceConfig.getDeviceId(), deviceConfig.getDeviceName());
@@ -413,9 +411,16 @@
                VehiclePosition position = new VehiclePosition(positionCode, positionValue);
                status.setCurrentPosition(position);
            }
            // 启动自动状态监控,当 state=1 时自动协调卧转立设备
            startStateMonitoring(deviceConfig, logicParams);
            // 仅在“非多设备任务”场景下,才启动大车自身的自动状态监控和 MES 任务监控
            boolean inMultiDeviceTask = params != null && params.containsKey("_taskContext");
            if (!inMultiDeviceTask) {
                // 启动自动状态监控,当 state=1 时自动协调卧转立设备
                startStateMonitoring(deviceConfig, logicParams);
                // 从 PLC/MES 创建正式任务并启动监控的逻辑,保留给独立 MES 场景使用
                // 多设备任务场景下,这部分交由 TaskExecutionEngine 统一编排
            }
        }
        
        return result;
@@ -470,6 +475,7 @@
        Map<String, Object> payload = new HashMap<>();
        payload.put("plcRequest", 0);
        payload.put("plcReport", 0);
        payload.put("onlineState", Boolean.TRUE);
        
        log.info("大车设备重置: deviceId={}", deviceConfig.getId());
        
@@ -484,8 +490,65 @@
            statusManager.clearVehicleTask(deviceConfig.getDeviceId());
            statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE);
            stopStateMonitoring(deviceConfig.getDeviceId());
            updateDeviceOnlineStatus(deviceConfig, true);
        }
        
        return result;
    }
    /**
     * 设置联机状态
     * @param deviceConfig 设备配置
     * @param params 参数,可包含 onlineState(1=联机,0=脱机)
     * @param logicParams 逻辑参数
     * @return 操作结果
     */
    private DevicePlcVO.OperationResult handleSetOnlineState(
            DeviceConfig deviceConfig,
            Map<String, Object> params,
            Map<String, Object> logicParams) {
        // 从参数中获取联机状态值,默认为true(联机)
        boolean onlineState = true;
        if (params != null && params.containsKey("onlineState")) {
            Object stateObj = params.get("onlineState");
            if (stateObj instanceof Boolean) {
                onlineState = (Boolean) stateObj;
            } else if (stateObj instanceof Number) {
                onlineState = ((Number) stateObj).intValue() != 0;
            } else if (stateObj instanceof String) {
                try {
                    String str = ((String) stateObj).trim();
                    if ("true".equalsIgnoreCase(str)) {
                        onlineState = true;
                    } else if ("false".equalsIgnoreCase(str)) {
                        onlineState = false;
                    } else {
                        onlineState = Integer.parseInt(str) != 0;
                    }
                } catch (NumberFormatException e) {
                    log.warn("解析onlineState失败,使用默认值true: deviceId={}, value={}",
                            deviceConfig.getId(), stateObj);
                }
            }
        }
        Map<String, Object> payload = new HashMap<>();
        payload.put("onlineState", onlineState);
        String stateText = onlineState ? "联机" : "脱机";
        log.info("大车设备设置联机状态: deviceId={}, onlineState={} ({})",
                deviceConfig.getId(), onlineState, stateText);
        DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields(
                deviceConfig.getId(),
                payload,
                "大车设备-设置联机状态(" + stateText + ")"
        );
        if (Boolean.TRUE.equals(result.getSuccess())) {
            updateDeviceOnlineStatus(deviceConfig, onlineState);
        }
        return result;
    }
@@ -512,6 +575,7 @@
        payload.put("plcGlassCount", 0);
        payload.put("plcRequest", 0);
        payload.put("plcReport", 0);
        payload.put("onlineState", Boolean.TRUE);
        if (params != null && params.containsKey("positionValue")) {
            payload.put("inPosition", params.get("positionValue"));
@@ -532,9 +596,23 @@
            statusManager.clearVehicleTask(deviceConfig.getDeviceId());
            statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE);
            stopStateMonitoring(deviceConfig.getDeviceId());
            updateDeviceOnlineStatus(deviceConfig, true);
        }
        
        return result;
    }
    private void updateDeviceOnlineStatus(DeviceConfig deviceConfig, boolean online) {
        if (deviceStatusService == null || deviceConfig == null || deviceConfig.getId() == null) {
            return;
        }
        try {
            String status = online ? DeviceStatus.Status.ONLINE : DeviceStatus.Status.OFFLINE;
            deviceStatusService.updateDeviceOnlineStatus(deviceConfig.getId(), status);
        } catch (Exception e) {
            log.warn("同步设备在线状态到数据库失败: deviceId={}, online={}, error={}",
                    deviceConfig.getDeviceId(), online, e.getMessage());
        }
    }
    private List<String> resolveGlassSlotFields(Map<String, Object> logicParams, int fallbackCount) {
@@ -572,9 +650,9 @@
            return "车辆容量(vehicleCapacity)必须大于0";
        }
        Integer glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", null);
        if (glassIntervalMs != null && glassIntervalMs < 0) {
            return "玻璃间隔时间(glassIntervalMs)不能为负数";
        Integer glassGap = getLogicParam(logicParams, "glassGap", null);
        if (glassGap != null && glassGap < 0) {
            return "玻璃间隔(glassGap)不能为负数";
        }
        return null; // 验证通过
@@ -584,7 +662,7 @@
    public String getDefaultLogicParams() {
        Map<String, Object> defaultParams = new HashMap<>();
        defaultParams.put("vehicleCapacity", 6000);
        defaultParams.put("glassIntervalMs", 1000);
        defaultParams.put("glassGap", 200); // 玻璃之间的物理间隔(mm),默认200mm
        defaultParams.put("autoFeed", true);
        defaultParams.put("maxRetryCount", 5);
        defaultParams.put("defaultGlassLength", 2000);
@@ -687,16 +765,19 @@
    /**
     * 规划玻璃装载
     * @param source 源玻璃列表
     * @param vehicleCapacity 车辆容量
     * @param vehicleCapacity 车辆容量(mm)
     * @param glassGap 玻璃之间的物理间隔(mm),默认200mm
     * @param deviceId 设备ID(用于日志)
     * @return 规划后的玻璃列表,如果玻璃没有长度则返回null(用于测试MES程序)
     */
    private List<GlassInfo> planGlassLoading(List<GlassInfo> source,
                                             int vehicleCapacity,
                                             int glassGap,
                                             String deviceId) {
        List<GlassInfo> planned = new ArrayList<>();
        int usedLength = 0;
        int capacity = Math.max(vehicleCapacity, 1);
        int gap = Math.max(glassGap, 0); // 确保间隔不为负数
        for (GlassInfo info : source) {
            Integer glassLength = info.getLength();
@@ -711,17 +792,26 @@
            int length = glassLength;
            
            if (planned.isEmpty()) {
                // 第一块玻璃,不需要间隙
                planned.add(info.withLength(length));
                usedLength = length;
                continue;
            }
            if (usedLength + length <= capacity) {
            // 后续玻璃需要考虑间隙:玻璃长度 + 间隙
            int requiredLength = length + gap;
            if (usedLength + requiredLength <= capacity) {
                planned.add(info.withLength(length));
                usedLength += length;
                usedLength += requiredLength; // 包含间隙
            } else {
                // 装不下了,停止添加
                break;
            }
        }
        log.debug("玻璃装载规划: deviceId={}, total={}, planned={}, usedLength={}, capacity={}, glassGap={}",
                deviceId, source.size(), planned.size(), usedLength, capacity, gap);
        return planned;
    }
@@ -1626,65 +1716,80 @@
            log.info("已给MES汇报({}任务): deviceId={}, glassId={}", 
                    taskType, deviceConfig.getDeviceId(), taskInfo.glassId);
            
            // 等待MES确认
            waitForMesConfirm(deviceConfig, serializer, taskInfo, logicParams);
            // 多设备任务场景下,不在这里阻塞等待MES确认,由任务引擎定时调用checkMesConfirm
        } catch (Exception e) {
            log.error("给MES汇报异常: deviceId={}", deviceConfig.getDeviceId(), e);
        }
    }
    /**
     * 等待MES确认
     * 检查MES确认状态(供任务引擎周期性调用)
     * 返回OperationResult.data中的 completed 标志表示是否已确认完成
     */
    private void waitForMesConfirm(DeviceConfig deviceConfig,
                                  EnhancedS7Serializer serializer,
                                  MesTaskInfo taskInfo,
                                  Map<String, Object> logicParams) {
    public DevicePlcVO.OperationResult checkMesConfirm(DeviceConfig deviceConfig,
                                                       Map<String, Object> logicParams) {
        if (plcDynamicDataService == null || s7SerializerProvider == null) {
            return DevicePlcVO.OperationResult.builder()
                    .success(false)
                    .message("PlcDynamicDataService或S7SerializerProvider未注入")
                    .build();
        }
        EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig);
        if (serializer == null) {
            return DevicePlcVO.OperationResult.builder()
                    .success(false)
                    .message("获取PLC序列化器失败")
                    .build();
        }
        Map<String, Object> data = new HashMap<>();
        try {
            // 读取确认字(假设字段名为mesConfirm)
            Integer maxWaitTime = getLogicParam(logicParams, "mesConfirmTimeoutMs", 30000); // 默认30秒
            long startTime = System.currentTimeMillis();
            while (System.currentTimeMillis() - startTime < maxWaitTime) {
                Object confirmValue = plcDynamicDataService.readPlcField(
                        deviceConfig, "mesConfirm", serializer);
                Integer confirm = parseInteger(confirmValue);
                if (confirm != null && confirm == 1) {
                    // MES已确认,清空state和汇报字
                    clearTaskStates(deviceConfig, serializer);
                    // 任务完成,恢复为空闲状态
                    statusManager.updateVehicleStatus(
                            deviceConfig.getDeviceId(), VehicleState.IDLE);
                    statusManager.clearVehicleTask(deviceConfig.getDeviceId());
                    // 移除任务
                    currentTasks.remove(deviceConfig.getDeviceId());
                    // 停止任务监控
                    handleStopTaskMonitor(deviceConfig);
                    // 恢复plcRequest=1(可以接收新任务)
                    Map<String, Object> payload = new HashMap<>();
                    payload.put("plcRequest", 1);
                    plcDynamicDataService.writePlcData(deviceConfig, payload, serializer);
                    log.info("MES任务已完成: deviceId={}, glassId={}",
                            deviceConfig.getDeviceId(), taskInfo.glassId);
                    return;
                }
                Thread.sleep(500); // 等待500ms后重试
            Object confirmValue = plcDynamicDataService.readPlcField(
                    deviceConfig, "mesConfirm", serializer);
            Integer confirm = parseInteger(confirmValue);
            boolean completed = confirm != null && confirm == 1;
            data.put("completed", completed);
            if (completed) {
                // MES已确认,清空state和汇报字
                clearTaskStates(deviceConfig, serializer);
                // 任务完成,恢复为空闲状态
                statusManager.updateVehicleStatus(
                        deviceConfig.getDeviceId(), VehicleState.IDLE);
                statusManager.clearVehicleTask(deviceConfig.getDeviceId());
                // 移除任务记录(如果有)
                currentTasks.remove(deviceConfig.getDeviceId());
                // 停止任务监控
                handleStopTaskMonitor(deviceConfig);
                // 恢复plcRequest=1(可以接收新任务)
                Map<String, Object> payload = new HashMap<>();
                payload.put("plcRequest", 1);
                plcDynamicDataService.writePlcData(deviceConfig, payload, serializer);
                log.info("MES任务已确认完成: deviceId={}", deviceConfig.getDeviceId());
                return DevicePlcVO.OperationResult.builder()
                        .success(true)
                        .message("MES任务已确认完成")
                        .data(data)
                        .build();
            }
            log.warn("等待MES确认超时: deviceId={}, glassId={}",
                    deviceConfig.getDeviceId(), taskInfo.glassId);
            return DevicePlcVO.OperationResult.builder()
                    .success(true)
                    .message("等待MES确认中")
                    .data(data)
                    .build();
        } catch (Exception e) {
            log.error("等待MES确认异常: deviceId={}", deviceConfig.getDeviceId(), e);
            log.error("检查MES确认状态异常: deviceId={}", deviceConfig.getDeviceId(), e);
            return DevicePlcVO.OperationResult.builder()
                    .success(false)
                    .message("检查MES确认状态异常: " + e.getMessage())
                    .data(data)
                    .build();
        }
    }