huang
9 天以前 9a9479a5e34324822b223747b7c88ff060466db0
修改任务定时触发大车逻辑,循环检查mes值是否符合
5个文件已修改
1118 ■■■■ 已修改文件
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java 672 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java 408 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-web/src/views/device/components/DeviceLogicConfig/LoadVehicleConfig.vue 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java
@@ -62,9 +62,19 @@
    private S7SerializerProvider s7SerializerProvider;
    // MES字段列表(进片和出片共用同一套协议)
    // 根据协议,使用带数字后缀的字段名(1-6对应6个玻璃位置)
    // 支持读取所有6个玻璃的信息
    private static final List<String> MES_FIELDS = Arrays.asList(
            "mesSend", "mesGlassId", "mesWidth", "mesHeight",
            "startSlot", "targetSlot", "workLine"
            "mesSend", "mesConfirm",
            // 玻璃1-6的ID
            "mesGlassId1", "mesGlassId2", "mesGlassId3", "mesGlassId4", "mesGlassId5", "mesGlassId6",
            // 玻璃1-6的尺寸
            "mesWidth1", "mesWidth2", "mesWidth3", "mesWidth4", "mesWidth5", "mesWidth6",
            "mesHeight1", "mesHeight2", "mesHeight3", "mesHeight4", "mesHeight5", "mesHeight6",
            "mesThickness1", "mesThickness2", "mesThickness3", "mesThickness4", "mesThickness5", "mesThickness6",
            // 玻璃1-6的起始位置和目标位置
            "start1", "start2", "start3", "start4", "start5", "start6",
            "target1", "target2", "target3", "target4", "target5", "target6"
    );
    // 监控线程池:用于定期检查大车状态并协调卧转立设备
@@ -131,14 +141,21 @@
        if (needsStateCheck(operation)) {
            VehicleStatus status = statusManager.getVehicleStatus(deviceId);
            if (status != null && !status.isAvailable()) {
                return DevicePlcVO.OperationResult.builder()
                    .success(false)
                    .message(String.format("车辆 %s (%s) 当前状态为 %s,无法执行操作 %s",
                        deviceConfig.getDeviceName(),
                        deviceId,
                        status.getState(),
                        operation))
                    .build();
                // 对于 feedGlass 操作,如果状态是 EXECUTING,允许继续执行
                // 因为定时器可能会重复调用,或者需要等待任务完成
                if ("feedGlass".equals(operation) && status.isExecuting()) {
                    log.debug("车辆 {} 当前状态为 EXECUTING,但允许继续执行 feedGlass(定时器重复调用)", deviceId);
                    // 允许继续执行,不返回错误
                } else {
                    return DevicePlcVO.OperationResult.builder()
                        .success(false)
                        .message(String.format("车辆 %s (%s) 当前状态为 %s,无法执行操作 %s",
                            deviceConfig.getDeviceName(),
                            deviceId,
                            status.getState(),
                            operation))
                        .build();
                }
            }
        }
@@ -196,6 +213,12 @@
                    break;
                case "setOnlineState":
                    result = handleSetOnlineState(deviceConfig, params, logicParams);
                    break;
                case "checkMesConfirm":
                    result = checkMesConfirm(deviceConfig, logicParams);
                    break;
                case "markBroken":
                    result = handleMarkBroken(deviceConfig, params, logicParams);
                    break;
                default:
                    log.warn("不支持的操作类型: {}", operation);
@@ -317,8 +340,7 @@
        // 从逻辑参数中获取配置(从 extraParams.deviceLogic 读取)
        Integer vehicleCapacity = getLogicParam(logicParams, "vehicleCapacity", 6000);
        Integer glassGap = getLogicParam(logicParams, "glassGap", 200); // 玻璃之间的物理间隔(mm),默认200mm
        Boolean autoFeed = getLogicParam(logicParams, "autoFeed", true);
        Integer maxRetryCount = getLogicParam(logicParams, "maxRetryCount", 5);
        Boolean autoFeed = getLogicParam(logicParams, "autoFeed", true); // 自动上料:是否自动触发上料请求(写入plcRequest=1)
        // 从运行时参数中获取数据(从接口调用时传入)
        List<GlassInfo> glassInfos = extractGlassInfos(params);
@@ -475,6 +497,10 @@
        Map<String, Object> payload = new HashMap<>();
        payload.put("plcRequest", 0);
        payload.put("plcReport", 0);
        // 清空state1~6,避免遗留状态导致误判
        for (int i = 1; i <= 6; i++) {
            payload.put("state" + i, 0);
        }
        payload.put("onlineState", Boolean.TRUE);
        
        log.info("大车设备重置: deviceId={}", deviceConfig.getId());
@@ -490,7 +516,14 @@
            statusManager.clearVehicleTask(deviceConfig.getDeviceId());
            statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE);
            stopStateMonitoring(deviceConfig.getDeviceId());
            handleStopTaskMonitor(deviceConfig);
            handleStopIdleMonitor(deviceConfig);
            updateDeviceOnlineStatus(deviceConfig, true);
        } else {
            // 即便重置失败,也尝试停止内部监控,避免任务取消后仍然反复写PLC
            stopStateMonitoring(deviceConfig.getDeviceId());
            handleStopTaskMonitor(deviceConfig);
            handleStopIdleMonitor(deviceConfig);
        }
        
        return result;
@@ -572,7 +605,6 @@
            payload.put(field, "");
        }
        payload.put("plcGlassCount", 0);
        payload.put("plcRequest", 0);
        payload.put("plcReport", 0);
        payload.put("onlineState", Boolean.TRUE);
@@ -590,13 +622,23 @@
                payload,
                "大车设备-清空玻璃数据"
        );
        // 同步清空state1~6等动态字段(在动态数据区中)
        clearDynamicTaskStates(deviceConfig);
        
        // 清空后,恢复为空闲状态,停止监控
        if (Boolean.TRUE.equals(result.getSuccess())) {
            statusManager.clearVehicleTask(deviceConfig.getDeviceId());
            statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE);
            stopStateMonitoring(deviceConfig.getDeviceId());
            handleStopTaskMonitor(deviceConfig);
            handleStopIdleMonitor(deviceConfig);
            updateDeviceOnlineStatus(deviceConfig, true);
        } else {
            // 写入失败也尝试停止监控,避免任务取消后仍旧运行
            stopStateMonitoring(deviceConfig.getDeviceId());
            handleStopTaskMonitor(deviceConfig);
            handleStopIdleMonitor(deviceConfig);
        }
        
        return result;
@@ -663,9 +705,8 @@
        Map<String, Object> defaultParams = new HashMap<>();
        defaultParams.put("vehicleCapacity", 6000);
        defaultParams.put("glassGap", 200); // 玻璃之间的物理间隔(mm),默认200mm
        defaultParams.put("autoFeed", true);
        defaultParams.put("autoFeed", true); // 自动上料:是否自动触发上料请求(写入plcRequest=1),默认true
        defaultParams.put("maxRetryCount", 5);
        defaultParams.put("defaultGlassLength", 2000);
        
        // MES任务相关配置
        defaultParams.put("vehicleSpeed", 1.0); // 车辆速度(格/秒,grid/s),默认1格/秒
@@ -768,7 +809,7 @@
     * @param vehicleCapacity 车辆容量(mm)
     * @param glassGap 玻璃之间的物理间隔(mm),默认200mm
     * @param deviceId 设备ID(用于日志)
     * @return 规划后的玻璃列表,如果玻璃没有长度则返回null(用于测试MES程序)
     * @return 规划后的玻璃列表,如果玻璃没有长度则返回null(MES未提供长度数据)
     */
    private List<GlassInfo> planGlassLoading(List<GlassInfo> source,
                                             int vehicleCapacity,
@@ -782,8 +823,8 @@
        for (GlassInfo info : source) {
            Integer glassLength = info.getLength();
            
            // 如果玻璃没有长度,说明MES未提供,直接报错
            if (glassLength == null || glassLength <= 0) {
                // 玻璃没有长度,直接报错(用于测试MES程序)
                log.error("玻璃[{}]缺少长度数据,无法进行容量计算。deviceId={},请检查MES程序是否正确提供玻璃长度。", 
                        info.getGlassId(), deviceId);
                return null;
@@ -1278,84 +1319,157 @@
        }
        
        try {
            // 检查是否已有任务在执行中
            MesTaskInfo existingTask = currentTasks.get(deviceId);
            if (existingTask != null) {
                log.debug("设备已有任务在执行中,跳过检查MES任务: deviceId={}", deviceId);
                return DevicePlcVO.OperationResult.builder()
                        .success(true)
                        .message("任务执行中,无需重复检查MES任务")
                        .data(Collections.singletonMap("waiting", false))
                        .build();
            }
            // 读取MES字段(进片和出片共用)
            Map<String, Object> mesData = plcDynamicDataService.readPlcData(
                    deviceConfig, MES_FIELDS, serializer);
            if (mesData == null || mesData.isEmpty()) {
                log.warn("读取MES字段失败: deviceId={}, mesData为空或null", deviceId);
                return DevicePlcVO.OperationResult.builder()
                        .success(false)
                        .message("读取MES字段失败")
                        .build();
            }
            // 解析mesSend
            Integer mesSend = parseInteger(mesData.get("mesSend"));
            if (mesSend == null || mesSend == 0) {
                Map<String, Object> waitData = new HashMap<>();
                waitData.put("completed", false);
                waitData.put("waiting", true);
                waitData.put("waitingReason", "mesSend=0");
                return DevicePlcVO.OperationResult.builder()
                        .success(true)
                        .message("暂无MES任务(mesSend=0)")
                        .message("等待MES发送请求(mesSend=0)")
                        .data(waitData)
                        .build();
            }
            
            // mesSend=1,读取任务参数
            String glassId = parseString(mesData.get("mesGlassId"));
            Integer startSlot = parseInteger(mesData.get("startSlot")); // 起始位置编号
            Integer targetSlot = parseInteger(mesData.get("targetSlot")); // 目标位置编号
            Integer workLine = parseInteger(mesData.get("workLine"));
            // mesSend=1,记录日志
            log.info("检测到mesSend=1,开始读取MES任务信息: deviceId={}", deviceId);
            
            if (glassId == null || glassId.isEmpty()) {
                return DevicePlcVO.OperationResult.builder()
                        .success(false)
                        .message("MES未提供玻璃ID")
                        .build();
            // mesSend=1,读取所有玻璃的任务参数(支持1-6个玻璃)
            List<GlassTaskInfo> glasses = new ArrayList<>();
            boolean isOutbound = false; // 将在处理第一个有效玻璃时确定
            // 读取所有6个玻璃的信息
            for (int i = 1; i <= 6; i++) {
                String glassId = parseString(mesData.get("mesGlassId" + i));
                if (glassId == null || glassId.isEmpty()) {
                    continue; // 跳过空的玻璃ID
                }
                Integer startSlot = parseInteger(mesData.get("start" + i));
                Integer targetSlot = parseInteger(mesData.get("target" + i));
                Integer width = parseInteger(mesData.get("mesWidth" + i));
                Integer height = parseInteger(mesData.get("mesHeight" + i));
                Integer thickness = parseInteger(mesData.get("mesThickness" + i));
                log.debug("读取玻璃{}信息: deviceId={}, glassId={}, startSlot={}, targetSlot={}, width={}, height={}, thickness={}",
                        i, deviceId, glassId, startSlot, targetSlot, width, height, thickness);
                if (startSlot == null || targetSlot == null) {
                    log.warn("玻璃{}信息不完整,跳过: glassId={}, startSlot={}, targetSlot={}",
                            i, glassId, startSlot, targetSlot);
                    continue;
                }
                // 对于第一个有效玻璃,判断是进片还是出片任务
                if (glasses.isEmpty()) {
                    isOutbound = isOutboundTask(startSlot, logicParams);
                }
                // 位置映射
                Integer startPosition;
                if (isOutbound) {
                    // 出片任务:startSlot是格子编号,需要映射到实际位置
                    startPosition = mapOutboundPosition(startSlot, logicParams);
                } else {
                    // 进片任务:startSlot是卧转立编号,通过positionMapping映射
                    startPosition = mapPosition(startSlot, logicParams);
                }
                // targetSlot统一通过positionMapping映射
                Integer targetPosition = mapPosition(targetSlot, logicParams);
                if (startPosition == null || targetPosition == null) {
                    log.warn("玻璃{}位置映射失败,跳过: glassId={}, startSlot={}, targetSlot={}",
                            i, glassId, startSlot, targetSlot);
                    continue;
                }
                // 创建玻璃任务信息
                GlassTaskInfo glassInfo = new GlassTaskInfo();
                glassInfo.glassId = glassId;
                glassInfo.startSlot = startSlot;
                glassInfo.targetSlot = targetSlot;
                glassInfo.startPosition = startPosition;
                glassInfo.targetPosition = targetPosition;
                glassInfo.width = width;
                glassInfo.height = height;
                glassInfo.thickness = thickness;
                glasses.add(glassInfo);
            }
            
            // 判断是进片还是出片任务
            // 方法:通过startSlot判断
            // - 如果startSlot是卧转立编号(如900/901),则是进片任务
            // - 如果startSlot是格子编号(在大理片笼范围内),则是出片任务
            boolean isOutbound = isOutboundTask(startSlot, logicParams);
            // 位置映射
            Integer startPosition;
            if (isOutbound) {
                // 出片任务:startSlot是格子编号,需要映射到实际位置
                startPosition = mapOutboundPosition(startSlot, logicParams);
            } else {
                // 进片任务:startSlot是卧转立编号,通过positionMapping映射
                startPosition = mapPosition(startSlot, logicParams);
            }
            // targetSlot统一通过positionMapping映射
            Integer targetPosition = mapPosition(targetSlot, logicParams);
            if (startPosition == null || targetPosition == null) {
            if (glasses.isEmpty()) {
                // 记录详细的MES数据,帮助调试
                log.warn("MES未提供有效的玻璃信息: deviceId={}, mesSend={}, mesData={}",
                        deviceId, mesSend, mesData);
                Map<String, Object> waitData = new HashMap<>();
                waitData.put("completed", false);
                waitData.put("waiting", true);
                waitData.put("waitingReason", "glassInfoMissing");
                return DevicePlcVO.OperationResult.builder()
                        .success(false)
                        .message(String.format("位置映射失败: startSlot=%s, targetSlot=%s, isOutbound=%s",
                                startSlot, targetSlot, isOutbound))
                        .message("MES未提供有效的玻璃信息(mesSend=1但未找到有效的glassId、startSlot、targetSlot)")
                        .data(waitData)
                        .build();
            }
            
            // 读取当前位置
            Integer currentPosition = getCurrentPosition(deviceConfig, logicParams);
            
            // 计算时间
            // 使用第一个玻璃的位置计算时间(所有玻璃使用相同的路径)
            GlassTaskInfo firstGlass = glasses.get(0);
            TimeCalculation timeCalc = calculateTime(
                    currentPosition, startPosition, targetPosition, logicParams);
                    currentPosition, firstGlass.startPosition, firstGlass.targetPosition, logicParams);
            
            // 创建任务信息
            MesTaskInfo taskInfo = new MesTaskInfo();
            taskInfo.glassId = glassId;
            taskInfo.startSlot = startSlot;
            taskInfo.targetSlot = targetSlot;
            taskInfo.startPosition = startPosition;
            taskInfo.targetPosition = targetPosition;
            taskInfo.glasses = glasses;
            taskInfo.currentPosition = currentPosition;
            taskInfo.gotime = timeCalc.gotime;
            taskInfo.cartime = timeCalc.cartime;
            taskInfo.workLine = workLine;
            taskInfo.createdTime = System.currentTimeMillis();
            taskInfo.isOutbound = isOutbound;
            // 从配置中读取破损玻璃索引(用于测试场景)
            // 配置格式:brokenGlassIndices: [0, 2] 表示第1个和第3个玻璃应该破损
            @SuppressWarnings("unchecked")
            List<Integer> brokenIndices = getLogicParam(logicParams, "brokenGlassIndices", null);
            if (brokenIndices != null && !brokenIndices.isEmpty()) {
                taskInfo.brokenGlassIndices = new ArrayList<>();
                for (Integer index : brokenIndices) {
                    if (index != null && index >= 0 && index < glasses.size()) {
                        taskInfo.brokenGlassIndices.add(index);
                    }
                }
                if (!taskInfo.brokenGlassIndices.isEmpty()) {
                    log.info("任务创建时标记破损玻璃: deviceId={}, brokenIndices={}",
                            deviceId, taskInfo.brokenGlassIndices);
                }
            }
            
            currentTasks.put(deviceId, taskInfo);
            
@@ -1363,23 +1477,43 @@
            Map<String, Object> payload = new HashMap<>();
            payload.put("plcRequest", 0);
            plcDynamicDataService.writePlcData(deviceConfig, payload, serializer);
            log.info("已清空plcRequest=0: deviceId={}", deviceId);
            
            // 更新车辆状态为执行中
            statusManager.updateVehicleStatus(deviceId, deviceConfig.getDeviceName(), VehicleState.EXECUTING);
            
            // 启动任务监控
            handleStartTaskMonitor(deviceConfig, params, logicParams);
            log.info("已启动任务监控: deviceId={}", deviceId);
            
            String taskType = isOutbound ? "出片" : "进片";
            log.info("MES{}任务已创建: deviceId={}, glassId={}, startSlot={}(位置{}格), targetSlot={}(位置{}格), 距离{}格->{}格, gotime={}ms({}秒), cartime={}ms({}秒)",
                    taskType, deviceId, glassId, startSlot, startPosition, targetSlot, targetPosition,
                    Math.abs(startPosition - currentPosition), Math.abs(targetPosition - startPosition),
            String glassIds = glasses.stream()
                    .map(g -> g.glassId)
                    .collect(java.util.stream.Collectors.joining(","));
            log.info("MES{}任务已创建: deviceId={}, glassCount={}, glassIds=[{}], 起始位置={}格, 目标位置={}格, 距离{}格->{}格, gotime={}ms({}秒), cartime={}ms({}秒)",
                    taskType, deviceId, glasses.size(), glassIds,
                    firstGlass.startPosition, firstGlass.targetPosition,
                    Math.abs(firstGlass.startPosition - currentPosition),
                    Math.abs(firstGlass.targetPosition - firstGlass.startPosition),
                    timeCalc.gotime, timeCalc.gotime / 1000.0, timeCalc.cartime, timeCalc.cartime / 1000.0);
            // 构建详细的步骤提示信息
            StringBuilder stepMessage = new StringBuilder();
            stepMessage.append("检测到MES任务(mesSend=1),已清空请求字(plcRequest=0)");
            stepMessage.append(";开始计算时间,预计到达起始位置耗时").append(timeCalc.gotime / 1000.0).append("秒");
            stepMessage.append(",运输到目标位置耗时").append(timeCalc.cartime / 1000.0).append("秒");
            if (!isOutbound) {
                stepMessage.append(";进片任务:等待玻璃上车后,将告知卧转立设备");
            }
            Map<String, Object> successData = new HashMap<>();
            successData.put("waiting", false);
            successData.put("taskStarted", true);
            
            return DevicePlcVO.OperationResult.builder()
                    .success(true)
                    .message(String.format("MES%s任务已创建: glassId=%s, start=%d, target=%d",
                            taskType, glassId, startPosition, targetPosition))
                    .message(stepMessage.toString())
                    .data(successData)
                    .build();
                    
        } catch (Exception e) {
@@ -1531,7 +1665,22 @@
        }
        
        // 获取速度(格/秒,grid/s)
        Double speed = getLogicParam(logicParams, "vehicleSpeed", 1.0);
        // 这里不能直接用 Double 泛型,否则当配置里是 Integer 时会出现
        // java.lang.Integer cannot be cast to java.lang.Double 的异常
        Object speedObj = getLogicParam(logicParams, "vehicleSpeed", 1.0);
        Double speed = null;
        if (speedObj instanceof Double) {
            speed = (Double) speedObj;
        } else if (speedObj instanceof Integer) {
            speed = ((Integer) speedObj).doubleValue();
        } else if (speedObj instanceof Number) {
            speed = ((Number) speedObj).doubleValue();
        } else {
            try {
                speed = Double.parseDouble(String.valueOf(speedObj));
            } catch (Exception ignore) {
            }
        }
        if (speed == null || speed <= 0) {
            speed = 1.0; // 默认1格/秒
        }
@@ -1645,24 +1794,90 @@
            long state1Time = taskInfo.gotime; // 到达起始位置,上车完成
            long state2Time = taskInfo.gotime + taskInfo.cartime; // 到达目标位置,运输完成
            
            // 更新state状态
            if (elapsed >= state1Time && elapsed < state2Time) {
                // state应该为1
                if (taskInfo.isOutbound) {
                    // 出片任务:到达源位置(大理片笼),取片完成
                    updateStateIfNeeded(deviceConfig, serializer, stateValues, 1, taskInfo);
                } else {
                    // 进片任务:到达起始位置(卧转立),上车完成
                    updateStateIfNeeded(deviceConfig, serializer, stateValues, 1, taskInfo);
                }
            } else if (elapsed >= state2Time) {
                // state应该为2(运输完成)
                updateStateIfNeeded(deviceConfig, serializer, stateValues, 2, taskInfo);
            // 获取超时时间配置(默认:超过预期完成时间的200%视为超时未完成)
            Double timeoutRatio = getLogicParam(logicParams, "state3TimeoutRatio", 2.0);
            long state3TimeoutTime = (long) (state2Time * timeoutRatio); // 超时时间点
            // 更新state状态(每个玻璃对应一个state)
            int glassCount = taskInfo.glasses.size();
            boolean hasStateOne = false; // 标记是否有state变为1
            boolean hasStateTwo = false; // 标记是否有state变为2
            String currentStepDesc = ""; // 当前步骤描述
            for (int i = 0; i < glassCount && i < 6; i++) {
                String stateField = "state" + (i + 1);
                Object currentValue = stateValues.get(stateField);
                Integer currentState = parseInteger(currentValue);
                
                // 检查是否所有state都>=2,如果是则给MES汇报
                if (allStatesCompleted(stateValues)) {
                    reportToMes(deviceConfig, serializer, taskInfo, logicParams);
                // 如果当前state已经是3(未完成)或8(破损),跳过
                if (currentState != null && (currentState == 3 || currentState == 8)) {
                    continue;
                }
                // 优先检查是否标记为破损(state=8)
                // 检查任务信息中是否标记了该玻璃为破损
                if (taskInfo.brokenGlassIndices != null && taskInfo.brokenGlassIndices.contains(i)) {
                    updateStateIfNeeded(deviceConfig, serializer, stateValues, stateField, 8, taskInfo);
                    log.info("玻璃标记为破损: deviceId={}, stateField={}, glassIndex={}",
                            deviceConfig.getDeviceId(), stateField, i);
                    continue;
                }
                // 检查超时未完成(state=3)
                if (elapsed >= state3TimeoutTime && (currentState == null || currentState < 2)) {
                    updateStateIfNeeded(deviceConfig, serializer, stateValues, stateField, 3, taskInfo);
                    log.warn("任务超时未完成: deviceId={}, stateField={}, elapsed={}ms, expectedTime={}ms",
                            deviceConfig.getDeviceId(), stateField, elapsed, state2Time);
                    continue;
                }
                // 正常状态更新
                if (elapsed >= state1Time && elapsed < state2Time) {
                    // state应该为1(上车完成)
                    boolean stateChanged = updateStateIfNeeded(deviceConfig, serializer, stateValues, stateField, 1, taskInfo);
                    if (stateChanged) {
                        hasStateOne = true;
                        currentStepDesc = "玻璃已上车(state=1),正在运输到目标位置";
                    } else if (currentState != null && currentState == 1) {
                        currentStepDesc = "玻璃已上车(state=1),正在运输到目标位置";
                    }
                } else if (elapsed >= state2Time) {
                    // state应该为2(运输完成)
                    boolean stateChanged = updateStateIfNeeded(deviceConfig, serializer, stateValues, stateField, 2, taskInfo);
                    if (stateChanged) {
                        hasStateTwo = true;
                        currentStepDesc = "玻璃已到达目标位置(state=2),等待MES确认";
                    } else if (currentState != null && currentState == 2) {
                        currentStepDesc = "玻璃已到达目标位置(state=2),等待MES确认";
                    }
                } else {
                    // 还在前往起始位置
                    currentStepDesc = "正在前往起始位置,预计耗时" + (state1Time / 1000.0) + "秒";
                }
            }
            // 当state变为1时(玻璃上车完成),清空卧转立设备的plcRequest(只执行一次)
            if (hasStateOne && !taskInfo.transferPlcRequestCleared) {
                clearTransferPlcRequest(deviceConfig, logicParams);
                taskInfo.transferPlcRequestCleared = true; // 标记已清空,避免重复执行
                if (!taskInfo.isOutbound) {
                    currentStepDesc = "玻璃已上车(state=1),已告知卧转立设备(plcRequest=0),正在运输到目标位置";
                }
            }
            // 检查是否所有state都>=2,如果是则给MES汇报
            if (elapsed >= state2Time && allStatesCompleted(stateValues, glassCount)) {
                reportToMes(deviceConfig, serializer, taskInfo, logicParams);
                // 记录MES确认开始等待的时间(只记录一次)
                if (taskInfo.mesConfirmStartTime == null) {
                    taskInfo.mesConfirmStartTime = System.currentTimeMillis();
                    currentStepDesc = "玻璃已到达目标位置(state=2),已发送汇报(plcReport=1),等待MES确认";
                }
            }
            // 保存当前步骤描述到任务信息中,供checkMesConfirm使用
            if (!currentStepDesc.isEmpty()) {
                taskInfo.currentStepDesc = currentStepDesc;
            }
            
        } catch (Exception e) {
@@ -1672,30 +1887,97 @@
    /**
     * 更新state状态(如果需要)
     * @return 是否发生了状态变化(从非目标状态变为目标状态)
     */
    private void updateStateIfNeeded(DeviceConfig deviceConfig,
    private boolean updateStateIfNeeded(DeviceConfig deviceConfig,
                                     EnhancedS7Serializer serializer,
                                     Map<String, Object> currentStates,
                                     String stateField,
                                     int targetState,
                                     MesTaskInfo taskInfo) {
        
        // 这里可以根据实际需求更新state字段
        // 暂时只记录日志,实际更新可能需要根据具体PLC字段配置
        log.debug("任务状态更新: deviceId={}, targetState={}",
                deviceConfig.getDeviceId(), targetState);
        // 检查当前state值
        Object currentValue = currentStates.get(stateField);
        Integer currentState = parseInteger(currentValue);
        // 如果当前state小于目标state,则更新
        // 注意:如果当前state已经是3(未完成)或8(破损),不再更新
        if (currentState != null && (currentState == 3 || currentState == 8)) {
            log.debug("任务状态已为异常状态,不再更新: deviceId={}, stateField={}, currentState={}, targetState={}",
                    deviceConfig.getDeviceId(), stateField, currentState, targetState);
            return false;
        }
        if (currentState == null || currentState < targetState) {
            // 实际写入PLC的state字段
            try {
                Map<String, Object> payload = new HashMap<>();
                payload.put(stateField, targetState);
                plcDynamicDataService.writePlcData(deviceConfig, payload, serializer);
                log.info("任务状态已更新到PLC: deviceId={}, stateField={}, currentState={}, targetState={}",
                        deviceConfig.getDeviceId(), stateField, currentState, targetState);
                // 返回true表示状态发生了变化
                return true;
            } catch (Exception e) {
                log.error("写入PLC state字段失败: deviceId={}, stateField={}, targetState={}, error={}",
                        deviceConfig.getDeviceId(), stateField, targetState, e.getMessage());
                return false;
            }
        }
        return false;
    }
    /**
     * 清空卧转立设备的plcRequest(当大车state=1时,表示玻璃已上车)
     */
    private void clearTransferPlcRequest(DeviceConfig deviceConfig, Map<String, Object> logicParams) {
        try {
            // 查找同组的卧转立设备
            List<DeviceConfig> transferDevices = findTransferDevicesInSameGroup(deviceConfig);
            if (transferDevices.isEmpty()) {
                log.debug("未找到同组的卧转立设备,跳过清空plcRequest: deviceId={}", deviceConfig.getId());
                return;
            }
            // 将每个卧转立设备的 plcRequest 置 0
            for (DeviceConfig transferDevice : transferDevices) {
                Map<String, Object> payload = new HashMap<>();
                payload.put("plcRequest", 0);
                DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields(
                        transferDevice.getId(),
                        payload,
                        "大车state=1自动清空卧转立请求"
                );
                if (Boolean.TRUE.equals(result.getSuccess())) {
                    log.info("已自动清空卧转立设备 plcRequest: vehicleDeviceId={}, transferDeviceId={}, transferDeviceName={}",
                            deviceConfig.getId(), transferDevice.getId(), transferDevice.getDeviceName());
                } else {
                    log.warn("自动清空卧转立设备 plcRequest 失败: vehicleDeviceId={}, transferDeviceId={}, message={}",
                            deviceConfig.getId(), transferDevice.getId(), result.getMessage());
                }
            }
        } catch (Exception e) {
            log.error("清空卧转立设备plcRequest异常: deviceId={}", deviceConfig.getId(), e);
        }
    }
    /**
     * 检查是否所有state都已完成(>=2)
     */
    private boolean allStatesCompleted(Map<String, Object> stateValues) {
        for (Object value : stateValues.values()) {
    private boolean allStatesCompleted(Map<String, Object> stateValues, int glassCount) {
        // 只检查实际有玻璃的state字段
        for (int i = 1; i <= glassCount && i <= 6; i++) {
            String stateField = "state" + i;
            Object value = stateValues.get(stateField);
            Integer state = parseInteger(value);
            if (state == null || state < 2) {
                return false;
            }
        }
        return !stateValues.isEmpty();
        return glassCount > 0;
    }
    /**
@@ -1713,8 +1995,11 @@
            plcDynamicDataService.writePlcData(deviceConfig, payload, serializer);
            
            String taskType = taskInfo.isOutbound ? "出片" : "进片";
            log.info("已给MES汇报({}任务): deviceId={}, glassId={}",
                    taskType, deviceConfig.getDeviceId(), taskInfo.glassId);
            String glassIds = taskInfo.glasses.stream()
                    .map(g -> g.glassId)
                    .collect(java.util.stream.Collectors.joining(","));
            log.info("已给MES汇报({}任务): deviceId={}, glassCount={}, glassIds=[{}]",
                    taskType, deviceConfig.getDeviceId(), taskInfo.glasses.size(), glassIds);
            
            // 多设备任务场景下,不在这里阻塞等待MES确认,由任务引擎定时调用checkMesConfirm
        } catch (Exception e) {
@@ -1742,8 +2027,88 @@
                    .build();
        }
        String deviceId = deviceConfig.getDeviceId();
        MesTaskInfo taskInfo = currentTasks.get(deviceId);
        // 如果没有任务记录,优先尝试补偿性地检查一次MES任务(避免因时序问题一直noTask)
        if (taskInfo == null) {
            log.info("检查MES确认时未找到任务记录,尝试补偿检查MES任务: deviceId={}", deviceId);
            try {
                DevicePlcVO.OperationResult checkResult =
                        handleCheckMesTask(deviceConfig, Collections.emptyMap(), logicParams);
                if (Boolean.TRUE.equals(checkResult.getSuccess())) {
                    taskInfo = currentTasks.get(deviceId);
                    if (taskInfo != null) {
                        log.info("补偿检查MES任务成功,已创建任务记录: deviceId={}", deviceId);
                    } else {
                        log.info("补偿检查MES任务执行成功但未创建任务记录: deviceId={}, message={}",
                                deviceId, checkResult.getMessage());
                    }
                } else {
                    log.warn("补偿检查MES任务失败: deviceId={}, message={}", deviceId, checkResult.getMessage());
                }
            } catch (Exception e) {
                log.warn("补偿检查MES任务异常: deviceId={}, error={}", deviceId, e.getMessage());
            }
        }
        // 补偿后仍然没有任务记录,说明MES尚未提供可执行任务
        if (taskInfo == null) {
            Map<String, Object> waitData = new HashMap<>();
            waitData.put("completed", false);
            waitData.put("waiting", true);
            waitData.put("waitingReason", "noTask");
            return DevicePlcVO.OperationResult.builder()
                    .success(true)
                    .message("等待MES发送请求(mesSend=1)")
                    .data(waitData)
                    .build();
        }
        // 获取MES确认超时配置(默认30秒)
        Integer mesConfirmTimeoutMs = getLogicParam(logicParams, "mesConfirmTimeoutMs", 30000);
        Map<String, Object> data = new HashMap<>();
        try {
            // 如果尚未向MES汇报(plcReport=1),无需检查确认
            if (taskInfo.mesConfirmStartTime == null) {
                data.put("completed", false);
                data.put("waiting", true);
                data.put("waitingReason", "waitingReport");
                return DevicePlcVO.OperationResult.builder()
                        .success(true)
                        .message("大车任务执行中,尚未汇报,无需检查确认")
                        .data(data)
                        .build();
            }
            // 检查超时
            long waitTime = System.currentTimeMillis() - taskInfo.mesConfirmStartTime;
            if (waitTime > mesConfirmTimeoutMs) {
                log.warn("MES确认超时: deviceId={}, waitTime={}ms, timeout={}ms",
                        deviceId, waitTime, mesConfirmTimeoutMs);
                data.put("completed", false);
                data.put("timeout", true);
                data.put("waitTime", waitTime);
                // 超时视为任务失败:清理任务状态并停止监控,避免继续累加等待时间
                try {
                    clearTaskStates(deviceConfig, serializer);
                } catch (Exception e) {
                    log.warn("MES确认超时时清空任务状态失败: deviceId={}, error={}", deviceId, e.getMessage());
                }
                statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.ERROR);
                statusManager.clearVehicleTask(deviceConfig.getDeviceId());
                currentTasks.remove(deviceConfig.getDeviceId());
                handleStopTaskMonitor(deviceConfig);
                return DevicePlcVO.OperationResult.builder()
                        .success(false)
                        .message(String.format("MES确认超时: 等待时间%dms,超时时间%dms", waitTime, mesConfirmTimeoutMs))
                        .data(data)
                        .build();
            }
            Object confirmValue = plcDynamicDataService.readPlcField(
                    deviceConfig, "mesConfirm", serializer);
            Integer confirm = parseInteger(confirmValue);
@@ -1771,16 +2136,26 @@
                plcDynamicDataService.writePlcData(deviceConfig, payload, serializer);
                log.info("MES任务已确认完成: deviceId={}", deviceConfig.getDeviceId());
                String taskType = taskInfo.isOutbound ? "出片" : "进片";
                return DevicePlcVO.OperationResult.builder()
                        .success(true)
                        .message("MES任务已确认完成")
                        .message(String.format("%s任务完成:MES已确认(mesConfirm=1),已清空state和汇报字,大车空闲(plcRequest=1),可以等待下次任务", taskType))
                        .data(data)
                        .build();
            }
            // 构建详细的等待提示信息
            String waitMessage = "等待MES确认中(mesConfirm=0)";
            if (taskInfo.currentStepDesc != null && !taskInfo.currentStepDesc.isEmpty()) {
                waitMessage = taskInfo.currentStepDesc + ";" + waitMessage;
            } else if (taskInfo.mesConfirmStartTime != null) {
                long waitMillis = System.currentTimeMillis() - taskInfo.mesConfirmStartTime;
                waitMessage = String.format("等待MES确认中(mesConfirm=0),已等待%.1f秒", waitMillis / 1000.0);
            }
            return DevicePlcVO.OperationResult.builder()
                    .success(true)
                    .message("等待MES确认中")
                    .message(waitMessage)
                    .data(data)
                    .build();
        } catch (Exception e) {
@@ -1793,6 +2168,84 @@
        }
    }
    /**
     * 设置玻璃为破损状态(state=8)
     * 用于测试场景,模拟玻璃破损
     */
    private DevicePlcVO.OperationResult handleMarkBroken(DeviceConfig deviceConfig,
                                                         Map<String, Object> params,
                                                         Map<String, Object> logicParams) {
        String deviceId = deviceConfig.getDeviceId();
        MesTaskInfo taskInfo = currentTasks.get(deviceId);
        if (taskInfo == null) {
            return DevicePlcVO.OperationResult.builder()
                    .success(false)
                    .message("没有正在执行的任务")
                    .build();
        }
        // 从参数中获取要标记为破损的玻璃索引(0-based)
        // 支持单个索引或索引列表
        List<Integer> brokenIndices = new ArrayList<>();
        Object brokenIndexObj = params.get("glassIndex");
        if (brokenIndexObj != null) {
            if (brokenIndexObj instanceof List) {
                @SuppressWarnings("unchecked")
                List<Object> indexList = (List<Object>) brokenIndexObj;
                for (Object idx : indexList) {
                    Integer index = parseInteger(idx);
                    if (index != null && index >= 0 && index < taskInfo.glasses.size()) {
                        brokenIndices.add(index);
                    }
                }
            } else {
                Integer index = parseInteger(brokenIndexObj);
                if (index != null && index >= 0 && index < taskInfo.glasses.size()) {
                    brokenIndices.add(index);
                }
            }
        }
        if (brokenIndices.isEmpty()) {
            return DevicePlcVO.OperationResult.builder()
                    .success(false)
                    .message("未指定有效的玻璃索引")
                    .build();
        }
        // 标记为破损
        if (taskInfo.brokenGlassIndices == null) {
            taskInfo.brokenGlassIndices = new ArrayList<>();
        }
        for (Integer index : brokenIndices) {
            if (!taskInfo.brokenGlassIndices.contains(index)) {
                taskInfo.brokenGlassIndices.add(index);
            }
        }
        // 立即写入PLC的state字段
        EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig);
        if (serializer != null) {
            try {
                Map<String, Object> payload = new HashMap<>();
                for (Integer index : brokenIndices) {
                    String stateField = "state" + (index + 1);
                    payload.put(stateField, 8);
                }
                plcDynamicDataService.writePlcData(deviceConfig, payload, serializer);
                log.info("已标记玻璃为破损并写入PLC: deviceId={}, brokenIndices={}",
                        deviceId, brokenIndices);
            } catch (Exception e) {
                log.error("写入破损状态到PLC失败: deviceId={}, error={}", deviceId, e.getMessage());
            }
        }
        return DevicePlcVO.OperationResult.builder()
                .success(true)
                .message(String.format("已标记玻璃为破损: indices=%s", brokenIndices))
                .build();
    }
    /**
     * 清空任务状态
     */
@@ -1841,20 +2294,33 @@
    }
    /**
     * MES任务信息
     * 单个玻璃的任务信息
     */
    private static class MesTaskInfo {
    private static class GlassTaskInfo {
        String glassId;
        Integer startSlot;
        Integer targetSlot;
        Integer startPosition;
        Integer targetPosition;
        Integer width;
        Integer height;
        Integer thickness;
    }
    /**
     * MES任务信息(可能包含多个玻璃)
     */
    private static class MesTaskInfo {
        List<GlassTaskInfo> glasses = new ArrayList<>(); // 多个玻璃信息
        Integer currentPosition;
        long gotime;
        long cartime;
        Integer workLine;
        long createdTime;
        boolean isOutbound = false; // 是否为出片任务(false=进片,true=出片)
        boolean transferPlcRequestCleared = false; // 是否已清空卧转立plcRequest(避免重复清空)
        List<Integer> brokenGlassIndices = null; // 标记为破损的玻璃索引列表(0-based,用于测试场景)
        Long mesConfirmStartTime = null; // MES确认开始等待的时间(用于超时检测)
        String currentStepDesc = null; // 当前步骤描述(用于显示详细的执行状态)
    }
    /**
@@ -1919,5 +2385,19 @@
            log.debug("未找到TaskExecutionContext,无法通知卧转立扫码设备暂停");
        }
    }
    private void clearDynamicTaskStates(DeviceConfig deviceConfig) {
        if (plcDynamicDataService == null || s7SerializerProvider == null) {
            return;
        }
        try {
            EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig);
            if (serializer != null) {
                clearTaskStates(deviceConfig, serializer);
            }
        } catch (Exception e) {
            log.warn("清空大车state字段失败: deviceId={}, error={}", deviceConfig != null ? deviceConfig.getId() : "null", e.getMessage());
        }
    }
}
mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
@@ -102,6 +102,8 @@
        TaskExecutionContext context = new TaskExecutionContext(parameters);
        runningTaskContexts.put(task.getTaskId(), context);
        // 将本次任务涉及的设备列表存入上下文,便于取消任务时做设备级收尾(如停止大车内部监控定时器)
        context.getSharedData().put("devices", devices);
        
        task.setTotalSteps(devices.size());
        task.setStatus(MultiDeviceTask.Status.RUNNING.name());
@@ -340,6 +342,35 @@
        if (context != null) {
            context.getSharedData().put("taskCancelled", true);
            log.warn("已标记任务取消: taskId={}", taskId);
            // 同时通知相关设备逻辑处理器执行取消收尾逻辑(例如停止大车内部的监控定时器)
            try {
                Map<String, Object> cancelParams = new HashMap<>();
                cancelParams.put("_taskContext", context);
                Object devicesObj = context.getSharedData().get("devices");
                if (devicesObj instanceof List) {
                    @SuppressWarnings("unchecked")
                    List<DeviceConfig> devices = (List<DeviceConfig>) devicesObj;
                    for (DeviceConfig device : devices) {
                        if (device == null) {
                            continue;
                        }
                        try {
                            DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
                            if (handler != null) {
                                // 目前大车逻辑处理器会在reset/clear等操作中停止内部监控定时器
                                // 这里统一调用一次“reset”作为任务取消时的收尾动作
                                handler.execute(device, "reset", cancelParams);
                            }
                        } catch (Exception e) {
                            log.warn("任务取消时执行设备收尾(reset)失败: taskId={}, deviceId={}, error={}",
                                    taskId, device.getId(), e.getMessage());
                        }
                    }
                }
            } catch (Exception e) {
                log.warn("任务取消时执行设备收尾逻辑异常: taskId={}, error={}", taskId, e.getMessage());
            }
        } else {
            log.warn("请求取消任务但未找到上下文: taskId={}", taskId);
        }
@@ -382,10 +413,13 @@
            ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
                try {
                    if (isTaskCancelled(context)) {
                        log.debug("任务已取消,停止卧转立扫码定时器: taskId={}, deviceId={}",
                        log.debug("任务已取消,停止卧转立扫码定时器: taskId={}, deviceId={}",
                                task.getTaskId(), device.getId());
                        return;
                    }
                    // 定时器第一次执行时,将设备状态从 WAITING 设置为 RUNNING
                    deviceCoordinationService.syncDeviceStatus(device,
                            DeviceCoordinationService.DeviceStatus.RUNNING, context);
                    ensureStepRunning(step, task.getTaskId());
                    // 检查是否需要暂停
                    if (shouldPauseScanner(context)) {
@@ -484,6 +518,8 @@
                }
            }, 0, scanIntervalMs, TimeUnit.MILLISECONDS);
            
            // 在串行执行模式下,扫码设备是第一个,应该立即设置为 RUNNING
            // 其他设备保持 WAITING,直到它们真正开始工作
            deviceCoordinationService.syncDeviceStatus(device,
                    DeviceCoordinationService.DeviceStatus.RUNNING, context);
            return future;
@@ -509,6 +545,9 @@
                    task.getTaskId(), device.getId(), monitorIntervalMs);
            
            // 启动定时任务
            // 使用AtomicBoolean标记是否第一次执行
            final java.util.concurrent.atomic.AtomicBoolean firstExecution = new java.util.concurrent.atomic.AtomicBoolean(true);
            ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
                try {
                    if (isTaskCancelled(context)) {
@@ -516,7 +555,14 @@
                                task.getTaskId(), device.getId());
                        return;
                    }
                    ensureStepRunning(step, task.getTaskId());
                    // 如果步骤已经完成,不再执行后续逻辑(避免状态被重置)
                    if (TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) {
                        log.debug("卧转立设备步骤已完成,停止定时器执行: taskId={}, deviceId={}",
                                task.getTaskId(), device.getId());
                        return;
                    }
                    // 构建参数
                    Map<String, Object> params = new HashMap<>();
                    params.put("_taskContext", context);
@@ -529,19 +575,61 @@
                    if (handler != null) {
                        DevicePlcVO.OperationResult result = handler.execute(device, "checkAndProcess", params);
                        
                        // 检查是否有数据:如果有数据或正在处理,设置为RUNNING;如果缓冲队列为空且无待处理玻璃,保持PENDING
                        String message = result.getMessage();
                        boolean hasData = result.getSuccess() != null && result.getSuccess()
                                && message != null && !message.contains("缓冲队列为空,无待处理玻璃");
                        // 如果当前是PENDING状态,且检测到有数据,则设置为RUNNING(设备开始工作)
                        boolean isPending = TaskStepDetail.Status.PENDING.name().equals(step.getStatus());
                        if (hasData && isPending) {
                            // 检测到数据且当前是等待状态,设备开始工作,设置为RUNNING
                            deviceCoordinationService.syncDeviceStatus(device,
                                    DeviceCoordinationService.DeviceStatus.RUNNING, context);
                            step.setStatus(TaskStepDetail.Status.RUNNING.name());
                            if (step.getStartTime() == null) {
                                step.setStartTime(new Date());
                            }
                            taskStepDetailMapper.updateById(step);
                            notificationService.notifyStepUpdate(task.getTaskId(), step);
                            log.debug("卧转立设备定时器检测到数据,从PENDING转为RUNNING: taskId={}, deviceId={}, message={}",
                                    task.getTaskId(), device.getId(), message);
                        } else if (!hasData) {
                            // 没有数据,保持PENDING状态,等待扫码设备输出
                            if (firstExecution.compareAndSet(true, false)) {
                                // 第一次执行,确保状态是PENDING
                                if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) {
                                    step.setStatus(TaskStepDetail.Status.PENDING.name());
                                    step.setSuccessMessage("等待扫码设备输出数据");
                                    taskStepDetailMapper.updateById(step);
                                    notificationService.notifyStepUpdate(task.getTaskId(), step);
                                }
                                log.debug("卧转立设备定时器第一次执行,无数据,保持PENDING: taskId={}, deviceId={}, message={}",
                                        task.getTaskId(), device.getId(), message);
                            }
                            return; // 不执行后续逻辑,等待下一次定时器触发
                        }
                        // 更新步骤状态(区分等待中和真正完成)
                        updateStepStatusForTransfer(step, result);
                        updateStepStatusForTransfer(step, result, device, context);
                        // 通知步骤更新(让前端实时看到步骤状态)
                        notificationService.notifyStepUpdate(task.getTaskId(), step);
                        boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
                        updateTaskProgress(task, step.getStepOrder(), opSuccess);
                        // 根据执行结果更新设备状态
                        // 注意:设备已经开始工作(定时器已执行),所以应该保持RUNNING状态
                        // 只有在真正完成时才设置为COMPLETED
                        if (opSuccess) {
                            String message = result.getMessage();
                            // 设备正在工作(等待缓冲、处理中等),保持RUNNING状态
                            deviceCoordinationService.syncDeviceStatus(device,
                                    DeviceCoordinationService.DeviceStatus.RUNNING, context);
                            if (message != null && message.contains("批次已写入PLC")) {
                        log.debug("卧转立设备定时器执行成功(已写入PLC): taskId={}, deviceId={}, message={}",
                                log.debug("卧转立设备定时器执行成功(已写入PLC): taskId={}, deviceId={}, message={}",
                                        task.getTaskId(), device.getId(), message);
                            } else {
                        log.debug("卧转立设备定时器等待中: taskId={}, deviceId={}, message={}",
                                log.debug("卧转立设备定时器工作中(等待缓冲): taskId={}, deviceId={}, message={}",
                                        task.getTaskId(), device.getId(), message);
                            }
                        } else {
@@ -556,8 +644,9 @@
                }
            }, 0, monitorIntervalMs, TimeUnit.MILLISECONDS);
            
            // 在串行执行模式下,设备启动定时器时先设置为 WAITING,定时器第一次执行时再设置为 RUNNING
            deviceCoordinationService.syncDeviceStatus(device,
                    DeviceCoordinationService.DeviceStatus.RUNNING, context);
                    DeviceCoordinationService.DeviceStatus.WAITING, context);
            return future;
        } catch (Exception e) {
            log.error("启动卧转立设备定时器失败: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
@@ -587,17 +676,30 @@
                                task.getTaskId(), device.getId());
                        return;
                    }
                    ensureStepRunning(step, task.getTaskId());
                    // 进片大车设备:只有在真正开始处理时才设置为RUNNING
                    // 检查是否有卧转立主体已输出、准备上大车的玻璃信息
                    List<String> readyGlassIds = getTransferReadyGlassIds(context);
                    // 如果当前没有新的玻璃,无论步骤是否已进入RUNNING,都应该轮询MES任务/确认状态
                    if (CollectionUtils.isEmpty(readyGlassIds)) {
                        // 没有卧转立输出的玻璃,继续等待
                        // 轮询MES任务/确认,避免错过MES侧后写入的任务
                        pollMesForVehicle(task, step, device, context);
                        // 如果仍然没有卧转立输出的玻璃,保持/更新为PENDING提示
                        if (!TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())
                                && !TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) {
                            step.setStatus(TaskStepDetail.Status.PENDING.name());
                            step.setSuccessMessage("等待卧转立输出玻璃");
                            taskStepDetailMapper.updateById(step);
                            notificationService.notifyStepUpdate(task.getTaskId(), step);
                        }
                        return;
                    }
                    
                    // 如果玻璃ID数量没有变化,说明没有新的玻璃,继续等待
                    // 如果玻璃ID数量没有变化,说明没有新的玻璃
                    int currentCount = readyGlassIds.size();
                    if (currentCount == lastProcessedCount.get()) {
                        // 玻璃数量没有变化:没有新玻璃,但仍需轮询MES任务/确认,避免错过MES侧的变化
                        pollMesForVehicle(task, step, device, context);
                        log.debug("大车设备定时器:玻璃ID数量未变化,继续等待: taskId={}, deviceId={}, count={}",
                                task.getTaskId(), device.getId(), currentCount);
                        return;
@@ -622,6 +724,18 @@
                        DevicePlcVO.OperationResult feedResult = handler.execute(device, "feedGlass", checkParams);
                        
                        if (Boolean.TRUE.equals(feedResult.getSuccess())) {
                            // 真正开始处理,设置为RUNNING
                            deviceCoordinationService.syncDeviceStatus(device,
                                    DeviceCoordinationService.DeviceStatus.RUNNING, context);
                            // 步骤状态也设置为RUNNING
                            if (!TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())) {
                                step.setStatus(TaskStepDetail.Status.RUNNING.name());
                                if (step.getStartTime() == null) {
                                    step.setStartTime(new Date());
                                }
                                taskStepDetailMapper.updateById(step);
                                notificationService.notifyStepUpdate(task.getTaskId(), step);
                            }
                            log.debug("进片大车设备定时器执行成功: taskId={}, deviceId={}, glassCount={}",
                                    task.getTaskId(), device.getId(), readyGlassIds.size());
                            // 将已装载的玻璃ID保存到共享数据中(供大理片笼使用)
@@ -631,7 +745,29 @@
                            lastProcessedCount.set(0);
                            // 确保卧转立扫码继续运行
                            setScannerPause(context, false);
                            // feedGlass成功后,先检查MES任务(checkMesTask)来开始执行任务
                            DevicePlcVO.OperationResult mesTaskResult = null;
                            try {
                                mesTaskResult = handler.execute(device, "checkMesTask", Collections.emptyMap());
                                if (mesTaskResult != null && Boolean.TRUE.equals(mesTaskResult.getSuccess())) {
                                    log.info("进片大车设备已检查MES任务并开始执行: taskId={}, deviceId={}, message={}",
                                            task.getTaskId(), device.getId(), mesTaskResult.getMessage());
                                }
                            } catch (Exception e) {
                                log.warn("进片大车设备检查MES任务异常: taskId={}, deviceId={}, error={}",
                                        task.getTaskId(), device.getId(), e.getMessage());
                            }
                        } else {
                            // 装不下,保持WAITING状态和PENDING步骤状态
                            deviceCoordinationService.syncDeviceStatus(device,
                                    DeviceCoordinationService.DeviceStatus.WAITING, context);
                            if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) {
                                step.setStatus(TaskStepDetail.Status.PENDING.name());
                                step.setSuccessMessage("容量不足,等待中");
                                taskStepDetailMapper.updateById(step);
                                notificationService.notifyStepUpdate(task.getTaskId(), step);
                            }
                            // 装不下,记录容量不足(是否需要影响扫码由工艺再决定)
                            log.warn("进片大车设备定时器容量不足: taskId={}, deviceId={}, message={}",
                                    task.getTaskId(), device.getId(), feedResult.getMessage());
@@ -639,6 +775,7 @@
                        }
                        
                        // 第二步:检查MES确认状态(如果大车处理器支持的话)
                        // 只有在任务已开始执行(有任务记录)时才检查MES确认
                        DevicePlcVO.OperationResult mesResult = null;
                        try {
                            mesResult = handler.execute(device, "checkMesConfirm", Collections.emptyMap());
@@ -671,8 +808,9 @@
                }
            }, 0, MONITOR_INTERVAL_MS, TimeUnit.MILLISECONDS);
            
            // 在串行执行模式下,设备启动定时器时先设置为 WAITING,定时器第一次执行时再设置为 RUNNING
            deviceCoordinationService.syncDeviceStatus(device,
                    DeviceCoordinationService.DeviceStatus.RUNNING, context);
                    DeviceCoordinationService.DeviceStatus.WAITING, context);
            return future;
        } catch (Exception e) {
            log.error("启动进片大车设备定时器失败: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
@@ -701,13 +839,65 @@
                                task.getTaskId(), device.getId());
                        return;
                    }
                    ensureStepRunning(step, task.getTaskId());
                    // 出片大车设备:只有在真正开始处理时才设置为RUNNING
                    // 检查是否有已处理的玻璃信息(从大理片笼来的)
                    List<String> processedGlassIds = getProcessedGlassIds(context);
                    boolean isRunning = TaskStepDetail.Status.RUNNING.name().equals(step.getStatus());
                    // 如果设备已经在运行中,即使没有新玻璃,也要继续监控MES任务和确认状态
                    if (CollectionUtils.isEmpty(processedGlassIds)) {
                        log.debug("出片大车设备定时器:暂无已处理的玻璃信息: taskId={}, deviceId={}",
                                task.getTaskId(), device.getId());
                        return;
                        if (isRunning) {
                            // 设备正在运行中,先检查MES任务,然后监控MES确认状态
                            DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
                            if (handler != null) {
                                Map<String, Object> logicParams = parseLogicParams(device);
                                // 先检查MES任务(如果mesSend=1,会创建任务并开始执行)
                                DevicePlcVO.OperationResult mesTaskResult = null;
                                try {
                                    mesTaskResult = handler.execute(device, "checkMesTask", Collections.emptyMap());
                                    if (mesTaskResult != null && Boolean.TRUE.equals(mesTaskResult.getSuccess())) {
                                        log.info("出片大车设备已检查MES任务并开始执行: taskId={}, deviceId={}, message={}",
                                                task.getTaskId(), device.getId(), mesTaskResult.getMessage());
                                    }
                                } catch (Exception e) {
                                    log.warn("出片大车设备检查MES任务异常: taskId={}, deviceId={}, error={}",
                                            task.getTaskId(), device.getId(), e.getMessage());
                                }
                                // 然后检查MES确认状态(只有在任务已开始执行时才检查)
                                DevicePlcVO.OperationResult mesResult = null;
                                try {
                                    mesResult = handler.execute(device, "checkMesConfirm", Collections.emptyMap());
                                } catch (Exception e) {
                                    log.warn("出片大车设备检查MES确认状态异常: taskId={}, deviceId={}, error={}",
                                            task.getTaskId(), device.getId(), e.getMessage());
                                }
                                // 更新步骤状态(大车设备保持RUNNING,直到MES确认完成或任务取消)
                                if (mesResult != null) {
                                    updateStepStatusForVehicle(step, mesResult);
                                    boolean opSuccess = Boolean.TRUE.equals(mesResult.getSuccess());
                                    updateTaskProgress(task, step.getStepOrder(), opSuccess);
                                    if (!opSuccess) {
                                        deviceCoordinationService.syncDeviceStatus(device,
                                                DeviceCoordinationService.DeviceStatus.FAILED, context);
                                    }
                                }
                            }
                            return;
                        } else {
                            // 没有数据,且设备未运行,保持PENDING状态
                            if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) {
                                step.setStatus(TaskStepDetail.Status.PENDING.name());
                                step.setSuccessMessage("等待大理片笼处理完成");
                                taskStepDetailMapper.updateById(step);
                                notificationService.notifyStepUpdate(task.getTaskId(), step);
                            }
                            log.debug("出片大车设备定时器:暂无已处理的玻璃信息: taskId={}, deviceId={}",
                                    task.getTaskId(), device.getId());
                            return;
                        }
                    }
                    
                    log.debug("出片大车设备定时器检测到已处理的玻璃信息: taskId={}, deviceId={}, glassCount={}",
@@ -729,16 +919,51 @@
                        DevicePlcVO.OperationResult feedResult = handler.execute(device, "feedGlass", checkParams);
                        
                        if (Boolean.TRUE.equals(feedResult.getSuccess())) {
                            // 真正开始处理,设置为RUNNING
                            deviceCoordinationService.syncDeviceStatus(device,
                                    DeviceCoordinationService.DeviceStatus.RUNNING, context);
                            // 步骤状态也设置为RUNNING
                            if (!TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())) {
                                step.setStatus(TaskStepDetail.Status.RUNNING.name());
                                if (step.getStartTime() == null) {
                                    step.setStartTime(new Date());
                                }
                                taskStepDetailMapper.updateById(step);
                                notificationService.notifyStepUpdate(task.getTaskId(), step);
                            }
                            log.debug("出片大车设备定时器执行成功: taskId={}, deviceId={}, glassCount={}",
                                    task.getTaskId(), device.getId(), processedGlassIds.size());
                            // 清空已处理的玻璃ID列表(已处理)
                            clearProcessedGlassIds(context);
                            // feedGlass成功后,先检查MES任务(checkMesTask)来开始执行任务
                            DevicePlcVO.OperationResult mesTaskResult = null;
                            try {
                                mesTaskResult = handler.execute(device, "checkMesTask", Collections.emptyMap());
                                if (mesTaskResult != null && Boolean.TRUE.equals(mesTaskResult.getSuccess())) {
                                    log.info("出片大车设备已检查MES任务并开始执行: taskId={}, deviceId={}, message={}",
                                            task.getTaskId(), device.getId(), mesTaskResult.getMessage());
                                }
                            } catch (Exception e) {
                                log.warn("出片大车设备检查MES任务异常: taskId={}, deviceId={}, error={}",
                                        task.getTaskId(), device.getId(), e.getMessage());
                            }
                        } else {
                            // 没有数据,保持WAITING状态和PENDING步骤状态
                            deviceCoordinationService.syncDeviceStatus(device,
                                    DeviceCoordinationService.DeviceStatus.WAITING, context);
                            if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) {
                                step.setStatus(TaskStepDetail.Status.PENDING.name());
                                step.setSuccessMessage("等待中");
                                taskStepDetailMapper.updateById(step);
                                notificationService.notifyStepUpdate(task.getTaskId(), step);
                            }
                            log.debug("出片大车设备定时器执行失败: taskId={}, deviceId={}, message={}",
                                    task.getTaskId(), device.getId(), feedResult.getMessage());
                        }
                        
                        // 第二步:检查MES确认状态(如果大车处理器支持的话)
                        // 只有在任务已开始执行(有任务记录)时才检查MES确认
                        DevicePlcVO.OperationResult mesResult = null;
                        try {
                            mesResult = handler.execute(device, "checkMesConfirm", Collections.emptyMap());
@@ -771,8 +996,9 @@
                }
            }, 0, MONITOR_INTERVAL_MS, TimeUnit.MILLISECONDS);
            
            // 在串行执行模式下,设备启动定时器时先设置为 WAITING,定时器第一次执行时再设置为 RUNNING
            deviceCoordinationService.syncDeviceStatus(device,
                    DeviceCoordinationService.DeviceStatus.RUNNING, context);
                    DeviceCoordinationService.DeviceStatus.WAITING, context);
            return future;
        } catch (Exception e) {
            log.error("启动出片大车设备定时器失败: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
@@ -804,14 +1030,27 @@
                                task.getTaskId(), device.getId());
                        return;
                    }
                    ensureStepRunning(step, task.getTaskId());
                    // 大理片笼设备:只有在真正开始处理时才设置为RUNNING
                    // 检查是否有已装载的玻璃信息(从进片大车来的)
                    List<String> loadedGlassIds = getLoadedGlassIds(context);
                    if (CollectionUtils.isEmpty(loadedGlassIds)) {
                        // 没有数据,保持WAITING状态和PENDING步骤状态
                        deviceCoordinationService.syncDeviceStatus(device,
                                DeviceCoordinationService.DeviceStatus.WAITING, context);
                        if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) {
                            step.setStatus(TaskStepDetail.Status.PENDING.name());
                            step.setSuccessMessage("等待进片大车装载玻璃");
                            taskStepDetailMapper.updateById(step);
                            notificationService.notifyStepUpdate(task.getTaskId(), step);
                        }
                        log.debug("大理片笼设备定时器:暂无已装载的玻璃信息: taskId={}, deviceId={}",
                                task.getTaskId(), device.getId());
                        return;
                    }
                    // 有数据,设置为RUNNING
                    deviceCoordinationService.syncDeviceStatus(device,
                            DeviceCoordinationService.DeviceStatus.RUNNING, context);
                    
                    // 检查玻璃是否已经处理完成(通过处理时间判断)
                    Long processStartTime = getProcessStartTime(context);
@@ -853,6 +1092,9 @@
                }
            }, 0, 1_000, TimeUnit.MILLISECONDS); // 每秒检查一次
            
            // 在串行执行模式下,设备启动定时器时先设置为 WAITING,定时器第一次执行时再设置为 RUNNING
            deviceCoordinationService.syncDeviceStatus(device,
                    DeviceCoordinationService.DeviceStatus.WAITING, context);
            return future;
        } catch (Exception e) {
            log.error("启动大理片笼设备定时器失败: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
@@ -1287,11 +1529,33 @@
            step.setStartTime(now);
        }
        boolean waiting = false;
        String waitingReason = null;
        if (result.getData() != null) {
            Object waitingFlag = result.getData().get("waiting");
            if (waitingFlag instanceof Boolean) {
                waiting = (Boolean) waitingFlag;
            } else if (waitingFlag != null) {
                waiting = "true".equalsIgnoreCase(String.valueOf(waitingFlag));
            }
            Object reason = result.getData().get("waitingReason");
            if (reason != null) {
                waitingReason = String.valueOf(reason);
            }
        }
        if (success && !completed) {
            // 成功但未完成:保持RUNNING状态,仅更新提示信息和耗时
            step.setStatus(TaskStepDetail.Status.RUNNING.name());
            // 成功但未完成:根据waiting状态决定显示为等待还是执行中
            if (waiting) {
                step.setStatus(TaskStepDetail.Status.PENDING.name());
            } else {
                step.setStatus(TaskStepDetail.Status.RUNNING.name());
            }
            String message = result.getMessage();
            step.setSuccessMessage(StringUtils.hasText(message) ? message : "大车设备运行中");
            if (!StringUtils.hasText(message) && waiting) {
                message = "大车设备等待中" + (StringUtils.hasText(waitingReason) ? "(" + waitingReason + ")" : "");
            }
            step.setSuccessMessage(StringUtils.hasText(message) ? message : (waiting ? "大车设备等待中" : "大车设备运行中"));
            step.setErrorMessage(null);
            if (step.getStartTime() != null) {
                step.setDurationMs(now.getTime() - step.getStartTime().getTime());
@@ -1326,11 +1590,66 @@
        step.setOutputData(toJson(result));
        taskStepDetailMapper.updateById(step);
    }
    /**
     * 轮询大车设备的MES任务和确认状态
     * 无论步骤当前是否为RUNNING/PENDING,都可以调用,用于避免错过MES端后写入的任务
     */
    private void pollMesForVehicle(MultiDeviceTask task,
                                   TaskStepDetail step,
                                   DeviceConfig device,
                                   TaskExecutionContext context) {
        try {
            DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
            if (handler == null) {
                return;
            }
            Map<String, Object> logicParams = parseLogicParams(device);
            // 先检查MES任务(如果mesSend=1,会创建任务并开始执行)
            DevicePlcVO.OperationResult mesTaskResult = null;
            try {
                mesTaskResult = handler.execute(device, "checkMesTask", Collections.emptyMap());
                if (mesTaskResult != null && Boolean.TRUE.equals(mesTaskResult.getSuccess())) {
                    log.info("大车设备已检查MES任务并开始执行: taskId={}, deviceId={}, message={}",
                            task.getTaskId(), device.getId(), mesTaskResult.getMessage());
                }
            } catch (Exception e) {
                log.warn("大车设备检查MES任务异常: taskId={}, deviceId={}, error={}",
                        task.getTaskId(), device.getId(), e.getMessage());
            }
            // 然后检查MES确认状态
            DevicePlcVO.OperationResult mesResult = null;
            try {
                mesResult = handler.execute(device, "checkMesConfirm", Collections.emptyMap());
            } catch (Exception e) {
                log.warn("大车设备检查MES确认状态异常: taskId={}, deviceId={}, error={}",
                        task.getTaskId(), device.getId(), e.getMessage());
            }
            // 更新步骤状态
            if (mesResult != null) {
                updateStepStatusForVehicle(step, mesResult);
                boolean opSuccess = Boolean.TRUE.equals(mesResult.getSuccess());
                updateTaskProgress(task, step.getStepOrder(), opSuccess);
                if (!opSuccess) {
                    deviceCoordinationService.syncDeviceStatus(device,
                            DeviceCoordinationService.DeviceStatus.FAILED, context);
                }
            }
        } catch (Exception e) {
            log.warn("轮询大车设备MES任务/确认状态异常: taskId={}, deviceId={}, error={}",
                    task.getTaskId(), device.getId(), e.getMessage());
        }
    }
    
    /**
     * 更新卧转立设备步骤状态(区分等待中和真正完成)
     */
    private void updateStepStatusForTransfer(TaskStepDetail step, DevicePlcVO.OperationResult result) {
    private void updateStepStatusForTransfer(TaskStepDetail step, DevicePlcVO.OperationResult result,
                                            DeviceConfig device, TaskExecutionContext context) {
        if (step == null || result == null) {
            return;
        }
@@ -1338,26 +1657,36 @@
        String message = result.getMessage();
        
        // 判断是否真正完成:
        // 1. 写入PLC成功
        // 2. 且缓冲已清空(表示所有玻璃已处理完,无新玻璃)
        boolean isRealCompleted = success && message != null
                && message.contains("批次已写入PLC")
                && message.contains("缓冲已清空,任务完成");
        // 1. 写入PLC成功且缓冲已清空(表示所有玻璃已处理完,无新玻璃)
        // 注意:缓冲队列为空且无待处理玻璃,在任务刚开始时也可能出现,不应该立即标记为完成
        // 只有当任务已经运行一段时间,且确实没有玻璃需要处理时,才标记为完成
        boolean isRealCompleted = success && message != null && (
                (message.contains("批次已写入PLC") && message.contains("缓冲已清空,任务完成"))
        );
        
        if (isRealCompleted) {
            // 真正完成:设置为完成状态,并设置结束时间
            step.setStatus(TaskStepDetail.Status.COMPLETED.name());
            step.setSuccessMessage(message);
            if (step.getEndTime() == null) {
                step.setEndTime(new Date());
            // 注意:一旦标记为完成,状态不应该再被改变
            if (!TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) {
                step.setStatus(TaskStepDetail.Status.COMPLETED.name());
                step.setSuccessMessage(message);
                if (step.getEndTime() == null) {
                    step.setEndTime(new Date());
                }
                // 计算耗时
                if (step.getStartTime() != null && step.getEndTime() != null) {
                    step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
                }
                log.info("卧转立设备步骤已完成: stepId={}, durationMs={}, message={}",
                        step.getId(), step.getDurationMs(), message);
                // 卧转立主体完成后尝试自动收尾整个任务
                checkAndCompleteTaskIfDone(step.getTaskId());
                // 更新设备状态为已完成
                if (device != null) {
                    deviceCoordinationService.syncDeviceStatus(device,
                            DeviceCoordinationService.DeviceStatus.COMPLETED, context);
                }
            }
            // 计算耗时
            if (step.getStartTime() != null && step.getEndTime() != null) {
                step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
            }
            log.debug("卧转立设备步骤已完成: stepId={}, durationMs={}", step.getId(), step.getDurationMs());
            // 卧转立主体完成后尝试自动收尾整个任务
            checkAndCompleteTaskIfDone(step.getTaskId());
        } else if (success && message != null && message.contains("批次已写入PLC")) {
            // 写入PLC成功但缓冲还有玻璃(车满情况),继续运行
            if (!TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())) {
@@ -1369,12 +1698,13 @@
                step.setStartTime(new Date());
            }
        } else if (success) {
            // 等待中:保持运行状态,只更新消息
            // 设备正在工作(等待缓冲、等待更多玻璃等),保持RUNNING状态
            // 因为定时器已经执行,说明设备已经开始工作了
            if (!TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())) {
                step.setStatus(TaskStepDetail.Status.RUNNING.name());
            }
            step.setSuccessMessage(message);
            // 确保开始时间已设置
            // 确保开始时间已设置(设备已经开始工作)
            if (step.getStartTime() == null) {
                step.setStartTime(new Date());
            }
mes-web/src/views/device/components/DeviceLogicConfig/LoadVehicleConfig.vue
@@ -44,19 +44,6 @@
          <span class="form-tip">多块玻璃之间的物理间隔空隙,默认200mm</span>
        </el-form-item>
      </el-col>
      <el-col :span="12">
        <el-form-item label="默认玻璃长度(mm)">
          <el-input-number
            v-model="config.defaultGlassLength"
            :min="100"
            :max="10000"
            :step="100"
            style="width: 100%;"
            @change="emitConfigUpdate"
          />
          <span class="form-tip">当玻璃未提供长度时使用的默认值</span>
        </el-form-item>
      </el-col>
    </el-row>
    <el-row :gutter="20">
@@ -149,21 +136,6 @@
      </el-col>
    </el-row>
    <el-row :gutter="20">
      <el-col :span="12">
        <el-form-item label="最大重试次数">
          <el-input-number
            v-model="config.maxRetryCount"
            :min="0"
            :max="10"
            :step="1"
            style="width: 100%;"
            @change="emitConfigUpdate"
          />
        </el-form-item>
      </el-col>
    </el-row>
    <el-form-item label="位置映射">
      <div class="position-mapping">
        <div
@@ -222,7 +194,6 @@
  vehicleCapacity: 6000,
  vehicleSpeed: 1.0,
  glassGap: 200,
  defaultGlassLength: 2000,
  homePosition: 0,
  minRange: 1,
  maxRange: 100,
@@ -230,7 +201,6 @@
  taskMonitorIntervalMs: 1000,
  mesConfirmTimeoutMs: 30000,
  autoFeed: true,
  maxRetryCount: 5,
  positionMapping: {}
})
@@ -282,7 +252,6 @@
      vehicleCapacity: newVal.vehicleCapacity ?? 6000,
      vehicleSpeed: newVal.vehicleSpeed ?? 1.0,
      glassGap: newVal.glassGap ?? 200,
      defaultGlassLength: newVal.defaultGlassLength ?? 2000,
      homePosition: newVal.homePosition ?? 0,
      minRange: newVal.minRange ?? 1,
      maxRange: newVal.maxRange ?? 100,
@@ -290,7 +259,6 @@
      taskMonitorIntervalMs: newVal.taskMonitorIntervalMs ?? 1000,
      mesConfirmTimeoutMs: newVal.mesConfirmTimeoutMs ?? 30000,
      autoFeed: newVal.autoFeed ?? true,
      maxRetryCount: newVal.maxRetryCount ?? 5,
      positionMapping: newVal.positionMapping || {}
    }
    // 将毫秒转换为秒用于显示
mes-web/src/views/plcTest/components/DeviceGroup/GroupTopology.vue
@@ -51,7 +51,7 @@
                <div class="node-actions">
                  <el-button
                    size="small"
                    text
                    type="danger"
                    @click.stop="clearPlc(device)"
                    :loading="clearingDeviceId === (device.deviceId || device.id)"
                  >
mes-web/src/views/plcTest/components/MultiDeviceTest/TaskOrchestration.vue
@@ -8,7 +8,7 @@
        <p v-if="group && loadDeviceName" class="sub-info">当前设备:{{ loadDeviceName }}</p>
      </div>
      <div class="action-buttons">
        <el-button
        <!-- <el-button
          type="danger"
          plain
          :disabled="!group || !loadDeviceId || loadDeviceLoading"
@@ -17,7 +17,7 @@
        >
          <el-icon><Delete /></el-icon>
          清空PLC
        </el-button>
        </el-button> -->
        <el-button type="primary" :disabled="!group" :loading="loading" @click="handleSubmit">
          <el-icon><Promotion /></el-icon>
          启动测试