mes-processes/mes-plcSend/src/main/java/com/mes/task/service/TaskExecutionEngine.java
@@ -148,7 +148,7 @@
            
            for (DeviceConfig device : devices) {
                String deviceType = device.getDeviceType();
                log.info("处理设备: deviceId={}, deviceType={}, deviceName={}, WORKSTATION_SCANNER常量={}, equals={}",
                log.debug("处理设备: deviceId={}, deviceType={}, deviceName={}, WORKSTATION_SCANNER常量={}, equals={}",
                        device.getId(), deviceType, device.getDeviceName(), 
                        DeviceConfig.DeviceType.WORKSTATION_SCANNER,
                        DeviceConfig.DeviceType.WORKSTATION_SCANNER.equals(deviceType));
@@ -157,25 +157,20 @@
                        || (deviceType != null && (deviceType.contains("扫码") || deviceType.contains("SCANNER")));
                boolean isLargeGlass = DeviceConfig.DeviceType.LARGE_GLASS.equals(deviceType);
                boolean isTransfer = DeviceConfig.DeviceType.WORKSTATION_TRANSFER.equals(deviceType);
                log.info("设备类型判断: deviceId={}, isLoadVehicle={}, isScanner={}, isLargeGlass={}, isTransfer={}",
                log.debug("设备类型判断: deviceId={}, isLoadVehicle={}, isScanner={}, isLargeGlass={}, isTransfer={}",
                        device.getId(), isLoadVehicle, isScanner, isLargeGlass, isTransfer);
                // 1. 卧转立扫码设备:启动定时器扫描(每10秒处理一个玻璃ID)
                if (isScanner) {
                    log.info("检测到扫码设备,准备启动定时器: deviceId={}, deviceType={}, deviceName={}",
                    log.debug("检测到扫码设备,准备启动定时器: deviceId={}, deviceType={}, deviceName={}",
                            device.getId(), device.getDeviceType(), device.getDeviceName());
                    TaskStepDetail step = createStepRecord(task, device, currentOrder);
                    // 设置步骤为运行状态,并设置开始时间
                    step.setStatus(TaskStepDetail.Status.RUNNING.name());
                    step.setStartTime(new Date());
                    taskStepDetailMapper.updateById(step);
                    notificationService.notifyStepUpdate(task.getTaskId(), step);
                    
                    ScheduledFuture<?> scannerTask = startScannerTimer(task, step, device, context);
                    if (scannerTask != null) {
                        registerScheduledTask(task.getTaskId(), scannerTask);
                        stepSummaries.add(createStepSummary(device.getDeviceName(), true, "定时器已启动,每10秒扫描一次"));
                        log.info("扫码设备定时器启动成功: deviceId={}, taskId={}", device.getId(), task.getTaskId());
                        log.debug("扫码设备定时器启动成功: deviceId={}, taskId={}", device.getId(), task.getTaskId());
                    } else {
                        log.warn("扫码设备定时器启动失败,glassIds可能为空: deviceId={}, taskId={}, contextParams={}", 
                                device.getId(), task.getTaskId(), context.getParameters());
@@ -190,20 +185,15 @@
                // 2. 卧转立设备:启动定时器定期检查并处理(中转设备)
                if (isTransfer) {
                    log.info("检测到卧转立设备,准备启动定时器: deviceId={}, deviceType={}, deviceName={}",
                    log.debug("检测到卧转立设备,准备启动定时器: deviceId={}, deviceType={}, deviceName={}",
                            device.getId(), device.getDeviceType(), device.getDeviceName());
                    TaskStepDetail step = createStepRecord(task, device, currentOrder);
                    // 设置步骤为运行状态,并设置开始时间
                    step.setStatus(TaskStepDetail.Status.RUNNING.name());
                    step.setStartTime(new Date());
                    taskStepDetailMapper.updateById(step);
                    notificationService.notifyStepUpdate(task.getTaskId(), step);
                    
                    ScheduledFuture<?> transferTask = startTransferTimer(task, step, device, context);
                    if (transferTask != null) {
                        registerScheduledTask(task.getTaskId(), transferTask);
                        stepSummaries.add(createStepSummary(device.getDeviceName(), true, "定时器已启动,定期检查并处理玻璃批次"));
                        log.info("卧转立设备定时器启动成功: deviceId={}, taskId={}", device.getId(), task.getTaskId());
                        log.debug("卧转立设备定时器启动成功: deviceId={}, taskId={}", device.getId(), task.getTaskId());
                    } else {
                        log.warn("卧转立设备定时器启动失败: deviceId={}, taskId={}", device.getId(), task.getTaskId());
                        stepSummaries.add(createStepSummary(device.getDeviceName(), false, "启动定时器失败"));
@@ -221,11 +211,6 @@
                    boolean isInboundVehicle = currentLoadVehicleIndex == 1; // 第一个大车是进片大车
                    
                    TaskStepDetail step = createStepRecord(task, device, currentOrder);
                    // 设置步骤为运行状态,并设置开始时间
                    step.setStatus(TaskStepDetail.Status.RUNNING.name());
                    step.setStartTime(new Date());
                    taskStepDetailMapper.updateById(step);
                    notificationService.notifyStepUpdate(task.getTaskId(), step);
                    
                    ScheduledFuture<?> vehicleTask;
                    if (isInboundVehicle) {
@@ -260,11 +245,6 @@
                // 4. 大理片笼设备:启动定时器逻辑处理(不涉及PLC交互,只负责逻辑处理)
                if (isLargeGlass) {
                    TaskStepDetail step = createStepRecord(task, device, currentOrder);
                    // 设置步骤为运行状态,并设置开始时间
                    step.setStatus(TaskStepDetail.Status.RUNNING.name());
                    step.setStartTime(new Date());
                    taskStepDetailMapper.updateById(step);
                    notificationService.notifyStepUpdate(task.getTaskId(), step);
                    
                    ScheduledFuture<?> largeGlassTask = startLargeGlassTimer(task, step, device, context);
                    if (largeGlassTask != null) {
@@ -296,7 +276,7 @@
            // 定时器会在后台持续运行,直到手动停止或超时
            boolean hasScheduledTasks = !CollectionUtils.isEmpty(taskScheduledTasks.get(task.getTaskId()));
            if (hasScheduledTasks) {
                log.info("任务已启动所有定时器,保持运行状态: taskId={}, scheduledTasksCount={}",
                log.debug("任务已启动所有定时器,保持运行状态: taskId={}, scheduledTasksCount={}",
                        task.getTaskId(), taskScheduledTasks.get(task.getTaskId()).size());
                // 任务保持 RUNNING 状态,定时器在后台运行
                // 不更新任务状态为 COMPLETED,让任务持续运行
@@ -376,7 +356,7 @@
        try {
            TaskParameters params = context.getParameters();
            List<String> glassIds = params.getGlassIds();
            log.info("卧转立扫码定时器初始化: taskId={}, deviceId={}, glassIds={}, glassIdsSize={}, isEmpty={}",
            log.debug("卧转立扫码定时器初始化: taskId={}, deviceId={}, glassIds={}, glassIdsSize={}, isEmpty={}",
                    task.getTaskId(), device.getId(), glassIds, 
                    glassIds != null ? glassIds.size() : 0, 
                    CollectionUtils.isEmpty(glassIds));
@@ -391,19 +371,22 @@
            AtomicInteger successCount = new AtomicInteger(0);
            AtomicInteger failCount = new AtomicInteger(0);
            
            final long CYCLE_INTERVAL_MS = 10_000; // 10秒间隔
            // 从设备配置中获取扫码间隔,默认10秒
            Map<String, Object> logicParams = parseLogicParams(device);
            Integer scanIntervalMs = getLogicParam(logicParams, "scanIntervalMs", 10_000);
            
            log.info("启动卧转立扫码定时器: taskId={}, deviceId={}, glassCount={}, interval={}s, glassIds={}",
                    task.getTaskId(), device.getId(), glassIds.size(), CYCLE_INTERVAL_MS / 1000, glassIds);
            log.debug("启动卧转立扫码定时器: taskId={}, deviceId={}, glassCount={}, interval={}ms, glassIds={}",
                    task.getTaskId(), device.getId(), glassIds.size(), scanIntervalMs, glassIds);
            
            // 启动定时任务
            ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
                try {
                    if (isTaskCancelled(context)) {
                        log.info("任务已取消,停止卧转立扫码定时器: taskId={}, deviceId={}",
                        log.debug("任务已取消,停止卧转立扫码定时器: taskId={}, deviceId={}",
                                task.getTaskId(), device.getId());
                        return;
                    }
                    ensureStepRunning(step, task.getTaskId());
                    // 检查是否需要暂停
                    if (shouldPauseScanner(context)) {
                        log.debug("卧转立扫码定时器暂停: taskId={}, deviceId={}", task.getTaskId(), device.getId());
@@ -413,9 +396,25 @@
                    // 检查是否还有待处理的玻璃ID
                    String glassId = glassIdQueue.poll();
                    if (glassId == null) {
                        log.info("卧转立扫码定时器完成: taskId={}, deviceId={}, processed={}/{}, success={}, fail={}",
                        log.debug("卧转立扫码定时器完成: taskId={}, deviceId={}, processed={}/{}, success={}, fail={}",
                                task.getTaskId(), device.getId(), processedCount.get(), glassIds.size(),
                                successCount.get(), failCount.get());
                        // 清空plcRequest和plcGlassId(确保PLC状态清理)
                        try {
                            DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
                            if (handler != null) {
                                Map<String, Object> clearParams = new HashMap<>();
                                clearParams.put("_taskContext", context);
                                handler.execute(device, "clearPlc", clearParams);
                                log.debug("卧转立扫码定时器完成,已清空PLC请求字段: taskId={}, deviceId={}",
                                        task.getTaskId(), device.getId());
                            }
                        } catch (Exception e) {
                            log.warn("卧转立扫码定时器完成时清空PLC失败: taskId={}, deviceId={}, error={}",
                                    task.getTaskId(), device.getId(), e.getMessage());
                        }
                        // 若之前未出现失败,再将状态置为完成
                        boolean alreadyFailed = TaskStepDetail.Status.FAILED.name().equals(step.getStatus());
                        if (!alreadyFailed) {
@@ -426,6 +425,8 @@
                            }
                            taskStepDetailMapper.updateById(step);
                            notificationService.notifyStepUpdate(task.getTaskId(), step);
                            // 扫码设备完成后尝试自动收尾整个任务
                            checkAndCompleteTaskIfDone(step.getTaskId());
                        }
                        deviceCoordinationService.syncDeviceStatus(device,
                                DeviceCoordinationService.DeviceStatus.COMPLETED, context);
@@ -433,32 +434,31 @@
                    }
                    
                    int currentIndex = processedCount.incrementAndGet();
                    log.info("卧转立扫码定时器处理第{}/{}个玻璃: taskId={}, deviceId={}, glassId={}",
                    log.debug("卧转立扫码定时器处理第{}/{}个玻璃: taskId={}, deviceId={}, glassId={}",
                            currentIndex, glassIds.size(), task.getTaskId(), device.getId(), glassId);
                    
                    // 执行单次扫描
                    Map<String, Object> scanParams = new HashMap<>();
                    scanParams.put("glassId", glassId);
                    scanParams.put("_taskContext", context);
                    log.info("卧转立扫码定时器准备执行: taskId={}, deviceId={}, glassId={}, scanParams={}",
                    log.debug("卧转立扫码定时器准备执行: taskId={}, deviceId={}, glassId={}, scanParams={}",
                            task.getTaskId(), device.getId(), glassId, scanParams);
                    
                    DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType());
                    if (handler != null) {
                        // 将logicParams合并到scanParams中
                        Map<String, Object> logicParams = parseLogicParams(device);
                        // 将logicParams合并到scanParams中(使用已定义的logicParams变量)
                        if (logicParams != null && !logicParams.isEmpty()) {
                            scanParams.put("_logicParams", logicParams);
                        }
                        log.info("卧转立扫码定时器调用handler.execute: taskId={}, deviceId={}, glassId={}, operation=scanOnce, scanParamsKeys={}, scanParams={}",
                        log.debug("卧转立扫码定时器调用handler.execute: taskId={}, deviceId={}, glassId={}, operation=scanOnce, scanParamsKeys={}, scanParams={}",
                                task.getTaskId(), device.getId(), glassId, scanParams.keySet(), scanParams);
                        DevicePlcVO.OperationResult result = handler.execute(device, "scanOnce", scanParams);
                        log.info("卧转立扫码定时器handler.execute返回: taskId={}, deviceId={}, glassId={}, success={}",
                        log.debug("卧转立扫码定时器handler.execute返回: taskId={}, deviceId={}, glassId={}, success={}",
                                task.getTaskId(), device.getId(), glassId, result.getSuccess());
                        
                        if (Boolean.TRUE.equals(result.getSuccess())) {
                            successCount.incrementAndGet();
                            log.info("卧转立扫码定时器处理成功: taskId={}, deviceId={}, glassId={}",
                            log.debug("卧转立扫码定时器处理成功: taskId={}, deviceId={}, glassId={}",
                                    task.getTaskId(), device.getId(), glassId);
                        } else {
                            failCount.incrementAndGet();
@@ -466,9 +466,10 @@
                                    task.getTaskId(), device.getId(), glassId, result.getMessage());
                        }
                        
                        // 更新步骤状态
                        updateStepStatus(step, result);
                        // 通知步骤更新(让前端实时看到步骤状态)
                        // 更新步骤状态(显示进度,保持RUNNING状态直到所有玻璃处理完成)
                        updateStepStatusForScanner(step, result, currentIndex, glassIds.size(),
                                successCount.get(), failCount.get());
                        // 通知步骤更新(让前端实时看到步骤状态和进度)
                        notificationService.notifyStepUpdate(task.getTaskId(), step);
                        boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
                        updateTaskProgress(task, step.getStepOrder(), opSuccess);
@@ -481,7 +482,7 @@
                    log.error("卧转立扫码定时器执行异常: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
                    failCount.incrementAndGet();
                }
            }, 0, CYCLE_INTERVAL_MS, TimeUnit.MILLISECONDS);
            }, 0, scanIntervalMs, TimeUnit.MILLISECONDS);
            
            deviceCoordinationService.syncDeviceStatus(device,
                    DeviceCoordinationService.DeviceStatus.RUNNING, context);
@@ -504,17 +505,18 @@
            Map<String, Object> logicParams = parseLogicParams(device);
            Integer monitorIntervalMs = getLogicParam(logicParams, "monitorIntervalMs", 5_000);
            
            log.info("启动卧转立设备定时器: taskId={}, deviceId={}, interval={}ms",
            log.debug("启动卧转立设备定时器: taskId={}, deviceId={}, interval={}ms",
                    task.getTaskId(), device.getId(), monitorIntervalMs);
            
            // 启动定时任务
            ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
                try {
                    if (isTaskCancelled(context)) {
                        log.info("任务已取消,停止卧转立设备定时器: taskId={}, deviceId={}",
                        log.debug("任务已取消,停止卧转立设备定时器: taskId={}, deviceId={}",
                                task.getTaskId(), device.getId());
                        return;
                    }
                    ensureStepRunning(step, task.getTaskId());
                    // 构建参数
                    Map<String, Object> params = new HashMap<>();
                    params.put("_taskContext", context);
@@ -536,10 +538,10 @@
                        if (opSuccess) {
                            String message = result.getMessage();
                            if (message != null && message.contains("批次已写入PLC")) {
                                log.info("卧转立设备定时器执行成功(已写入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 {
@@ -574,17 +576,18 @@
            final long MONITOR_INTERVAL_MS = 2_000; // 2秒监控一次
            final AtomicInteger lastProcessedCount = new AtomicInteger(0);
            
            log.info("启动进片大车设备定时器: taskId={}, deviceId={}, interval={}s",
            log.debug("启动进片大车设备定时器: taskId={}, deviceId={}, interval={}s",
                    task.getTaskId(), device.getId(), MONITOR_INTERVAL_MS / 1000);
            
            // 启动定时任务
            ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
                try {
                    if (isTaskCancelled(context)) {
                        log.info("任务已取消,停止进片大车定时器: taskId={}, deviceId={}",
                        log.debug("任务已取消,停止进片大车定时器: taskId={}, deviceId={}",
                                task.getTaskId(), device.getId());
                        return;
                    }
                    ensureStepRunning(step, task.getTaskId());
                    // 检查是否有卧转立主体已输出、准备上大车的玻璃信息
                    List<String> readyGlassIds = getTransferReadyGlassIds(context);
                    if (CollectionUtils.isEmpty(readyGlassIds)) {
@@ -600,7 +603,7 @@
                        return;
                    }
                    
                    log.info("进片大车设备定时器检测到卧转立输出的玻璃信息: taskId={}, deviceId={}, glassCount={}",
                        log.debug("进片大车设备定时器检测到卧转立输出的玻璃信息: taskId={}, deviceId={}, glassCount={}",
                            task.getTaskId(), device.getId(), currentCount);
                    
                    // 检查容量
@@ -615,10 +618,11 @@
                        if (logicParams != null && !logicParams.isEmpty()) {
                            checkParams.put("_logicParams", logicParams);
                        }
                        DevicePlcVO.OperationResult result = handler.execute(device, "feedGlass", checkParams);
                        // 第一步:写入大车上料请求
                        DevicePlcVO.OperationResult feedResult = handler.execute(device, "feedGlass", checkParams);
                        
                        if (Boolean.TRUE.equals(result.getSuccess())) {
                            log.info("进片大车设备定时器执行成功: taskId={}, deviceId={}, glassCount={}",
                        if (Boolean.TRUE.equals(feedResult.getSuccess())) {
                            log.debug("进片大车设备定时器执行成功: taskId={}, deviceId={}, glassCount={}",
                                    task.getTaskId(), device.getId(), readyGlassIds.size());
                            // 将已装载的玻璃ID保存到共享数据中(供大理片笼使用)
                            setLoadedGlassIds(context, new ArrayList<>(readyGlassIds));
@@ -630,17 +634,36 @@
                        } else {
                            // 装不下,记录容量不足(是否需要影响扫码由工艺再决定)
                            log.warn("进片大车设备定时器容量不足: taskId={}, deviceId={}, message={}",
                                    task.getTaskId(), device.getId(), result.getMessage());
                                    task.getTaskId(), device.getId(), feedResult.getMessage());
                            lastProcessedCount.set(currentCount); // 记录当前数量,避免重复检查
                        }
                        
                        // 更新步骤状态
                        updateStepStatus(step, result);
                        boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
                        updateTaskProgress(task, step.getStepOrder(), opSuccess);
                        if (!opSuccess) {
                            deviceCoordinationService.syncDeviceStatus(device,
                                    DeviceCoordinationService.DeviceStatus.FAILED, context);
                        // 第二步:检查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);
                            }
                        } else {
                            updateStepStatusForVehicle(step, feedResult);
                            boolean opSuccess = Boolean.TRUE.equals(feedResult.getSuccess());
                            updateTaskProgress(task, step.getStepOrder(), opSuccess);
                            if (!opSuccess) {
                                deviceCoordinationService.syncDeviceStatus(device,
                                        DeviceCoordinationService.DeviceStatus.FAILED, context);
                            }
                        }
                    }
                } catch (Exception e) {
@@ -667,17 +690,18 @@
        try {
            final long MONITOR_INTERVAL_MS = 2_000; // 2秒监控一次
            
            log.info("启动出片大车设备定时器: taskId={}, deviceId={}, interval={}s",
            log.debug("启动出片大车设备定时器: taskId={}, deviceId={}, interval={}s",
                    task.getTaskId(), device.getId(), MONITOR_INTERVAL_MS / 1000);
            
            // 启动定时任务
            ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
                try {
                    if (isTaskCancelled(context)) {
                        log.info("任务已取消,停止出片大车定时器: taskId={}, deviceId={}",
                        log.debug("任务已取消,停止出片大车定时器: taskId={}, deviceId={}",
                                task.getTaskId(), device.getId());
                        return;
                    }
                    ensureStepRunning(step, task.getTaskId());
                    // 检查是否有已处理的玻璃信息(从大理片笼来的)
                    List<String> processedGlassIds = getProcessedGlassIds(context);
                    if (CollectionUtils.isEmpty(processedGlassIds)) {
@@ -686,7 +710,7 @@
                        return;
                    }
                    
                    log.info("出片大车设备定时器检测到已处理的玻璃信息: taskId={}, deviceId={}, glassCount={}",
                    log.debug("出片大车设备定时器检测到已处理的玻璃信息: taskId={}, deviceId={}, glassCount={}",
                            task.getTaskId(), device.getId(), processedGlassIds.size());
                    
                    // 执行出片操作
@@ -701,25 +725,45 @@
                        if (logicParams != null && !logicParams.isEmpty()) {
                            checkParams.put("_logicParams", logicParams);
                        }
                        DevicePlcVO.OperationResult result = handler.execute(device, "feedGlass", checkParams);
                        // 第一步:写入大车出片请求
                        DevicePlcVO.OperationResult feedResult = handler.execute(device, "feedGlass", checkParams);
                        
                        if (Boolean.TRUE.equals(result.getSuccess())) {
                            log.info("出片大车设备定时器执行成功: taskId={}, deviceId={}, glassCount={}",
                        if (Boolean.TRUE.equals(feedResult.getSuccess())) {
                            log.debug("出片大车设备定时器执行成功: taskId={}, deviceId={}, glassCount={}",
                                    task.getTaskId(), device.getId(), processedGlassIds.size());
                            // 清空已处理的玻璃ID列表(已处理)
                            clearProcessedGlassIds(context);
                        } else {
                            log.debug("出片大车设备定时器执行失败: taskId={}, deviceId={}, message={}",
                                    task.getTaskId(), device.getId(), result.getMessage());
                                    task.getTaskId(), device.getId(), feedResult.getMessage());
                        }
                        
                        // 更新步骤状态
                        updateStepStatus(step, result);
                        boolean opSuccess = Boolean.TRUE.equals(result.getSuccess());
                        updateTaskProgress(task, step.getStepOrder(), opSuccess);
                        if (!opSuccess) {
                            deviceCoordinationService.syncDeviceStatus(device,
                                    DeviceCoordinationService.DeviceStatus.FAILED, context);
                        // 第二步:检查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);
                            }
                        } else {
                            updateStepStatusForVehicle(step, feedResult);
                            boolean opSuccess = Boolean.TRUE.equals(feedResult.getSuccess());
                            updateTaskProgress(task, step.getStepOrder(), opSuccess);
                            if (!opSuccess) {
                                deviceCoordinationService.syncDeviceStatus(device,
                                        DeviceCoordinationService.DeviceStatus.FAILED, context);
                            }
                        }
                    }
                } catch (Exception e) {
@@ -749,17 +793,18 @@
            Integer processTimeSeconds = getLogicParam(logicParams, "processTimeSeconds", 30);
            final long PROCESS_TIME_MS = processTimeSeconds * 1000;
            
            log.info("启动大理片笼设备定时器: taskId={}, deviceId={}, processTime={}s",
            log.debug("启动大理片笼设备定时器: taskId={}, deviceId={}, processTime={}s",
                    task.getTaskId(), device.getId(), processTimeSeconds);
            
            // 启动定时任务
            ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> {
                try {
                    if (isTaskCancelled(context)) {
                        log.info("任务已取消,停止大理片笼定时器: taskId={}, deviceId={}",
                        log.debug("任务已取消,停止大理片笼定时器: taskId={}, deviceId={}",
                                task.getTaskId(), device.getId());
                        return;
                    }
                    ensureStepRunning(step, task.getTaskId());
                    // 检查是否有已装载的玻璃信息(从进片大车来的)
                    List<String> loadedGlassIds = getLoadedGlassIds(context);
                    if (CollectionUtils.isEmpty(loadedGlassIds)) {
@@ -773,7 +818,7 @@
                    if (processStartTime == null) {
                        // 第一次检测到玻璃,记录开始处理时间
                        setProcessStartTime(context, System.currentTimeMillis());
                        log.info("大理片笼设备开始处理: taskId={}, deviceId={}, glassCount={}, processTime={}s",
                        log.debug("大理片笼设备开始处理: taskId={}, deviceId={}, glassCount={}, processTime={}s",
                                task.getTaskId(), device.getId(), loadedGlassIds.size(), processTimeSeconds);
                        return;
                    }
@@ -787,7 +832,7 @@
                    }
                    
                    // 处理时间已到,完成任务汇报
                    log.info("大理片笼设备处理完成: taskId={}, deviceId={}, glassCount={}, processTime={}s",
                    log.debug("大理片笼设备处理完成: taskId={}, deviceId={}, glassCount={}, processTime={}s",
                            task.getTaskId(), device.getId(), loadedGlassIds.size(), processTimeSeconds);
                    
                    // 将已处理的玻璃ID转移到已处理列表(供出片大车使用)
@@ -800,6 +845,8 @@
                    step.setErrorMessage(null);
                    step.setOutputData(toJson(Collections.singletonMap("glassIds", loadedGlassIds)));
                    taskStepDetailMapper.updateById(step);
                    // 大理片笼完成后尝试自动收尾整个任务
                    checkAndCompleteTaskIfDone(step.getTaskId());
                    
                } catch (Exception e) {
                    log.error("大理片笼设备定时器执行异常: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e);
@@ -1024,7 +1071,7 @@
                    future.cancel(false);
                }
            }
            log.info("已停止任务的所有定时器: taskId={}, count={}", taskId, futures.size());
            log.debug("已停止任务的所有定时器: taskId={}, count={}", taskId, futures.size());
        }
        runningTaskContexts.remove(taskId);
    }
@@ -1040,7 +1087,7 @@
        long timeoutMs = timeoutMinutes * 60 * 1000;
        long deadline = System.currentTimeMillis() + timeoutMs;
        
        log.info("等待定时器任务完成: taskId={}, timeout={}分钟", taskId, timeoutMinutes);
        log.debug("等待定时器任务完成: taskId={}, timeout={}分钟", taskId, timeoutMinutes);
        
        while (System.currentTimeMillis() < deadline) {
            List<ScheduledFuture<?>> futures = taskScheduledTasks.get(taskId);
@@ -1069,7 +1116,54 @@
            }
        }
        
        log.info("定时器任务等待完成: taskId={}", taskId);
        log.debug("定时器任务等待完成: taskId={}", taskId);
    }
    /**
     * 当某个步骤可能完成时,检查任务是否所有步骤都已完成,如果是则自动将任务标记为已完成
     */
    private void checkAndCompleteTaskIfDone(String taskId) {
        if (taskId == null) {
            return;
        }
        try {
            MultiDeviceTask task = multiDeviceTaskMapper.selectOne(
                    Wrappers.<MultiDeviceTask>lambdaQuery()
                            .eq(MultiDeviceTask::getTaskId, taskId)
            );
            if (task == null) {
                return;
            }
            // 仅在任务仍为RUNNING时才尝试自动收尾
            if (!MultiDeviceTask.Status.RUNNING.name().equals(task.getStatus())) {
                return;
            }
            int totalSteps = task.getTotalSteps() != null ? task.getTotalSteps() : 0;
            if (totalSteps <= 0) {
                return;
            }
            int completedSteps = countCompletedSteps(taskId);
            if (completedSteps < totalSteps) {
                return;
            }
            // 所有步骤都已完成,收尾任务
            task.setStatus(MultiDeviceTask.Status.COMPLETED.name());
            task.setEndTime(new Date());
            multiDeviceTaskMapper.updateById(task);
            // 停止所有定时器
            stopScheduledTasks(taskId);
            // 通知任务完成
            notificationService.notifyTaskStatus(task);
            log.info("所有步骤已完成,自动将任务标记为已完成: taskId={}, totalSteps={}", taskId, totalSteps);
        } catch (Exception e) {
            log.warn("检查并自动完成任务失败: taskId={}", taskId, e);
        }
    }
    
    /**
@@ -1105,6 +1199,135 @@
    }
    
    /**
     * 确保步骤进入RUNNING状态(仅在第一次真正执行前调用)
     */
    private void ensureStepRunning(TaskStepDetail step, String taskId) {
        if (step == null) {
            return;
        }
        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(taskId, step);
        }
    }
    /**
     * 更新扫码设备步骤状态(显示进度,保持RUNNING状态直到所有玻璃处理完成)
     */
    private void updateStepStatusForScanner(TaskStepDetail step, DevicePlcVO.OperationResult result,
                                            int currentIndex, int totalCount,
                                            int successCount, int failCount) {
        if (step == null || result == null) {
            return;
        }
        boolean success = Boolean.TRUE.equals(result.getSuccess());
        // 保持RUNNING状态,直到所有玻璃处理完成(在定时器完成时再设置为COMPLETED)
        step.setStatus(TaskStepDetail.Status.RUNNING.name());
        // 更新时间和耗时,前端可以实时看到执行耗时
        Date now = new Date();
        if (step.getStartTime() == null) {
            step.setStartTime(now);
        }
        if (step.getStartTime() != null) {
            step.setDurationMs(now.getTime() - step.getStartTime().getTime());
        }
        // 更新进度信息
        String progressMessage = String.format("正在处理 %d/%d (成功:%d, 失败:%d)",
                currentIndex, totalCount, successCount, failCount);
        if (success) {
            // 成功时显示进度和成功消息
            String resultMessage = result.getMessage();
            if (StringUtils.hasText(resultMessage)) {
                step.setSuccessMessage(progressMessage + " - " + resultMessage);
            } else {
                step.setSuccessMessage(progressMessage);
            }
            step.setErrorMessage(null);
        } else {
            // 失败时显示进度和错误消息
            String errorMessage = result.getMessage();
            step.setErrorMessage(progressMessage + " - " + (StringUtils.hasText(errorMessage) ? errorMessage : "处理失败"));
            step.setSuccessMessage(null);
        }
        step.setOutputData(toJson(result));
        taskStepDetailMapper.updateById(step);
    }
    /**
     * 更新大车设备步骤状态(保持RUNNING,直到手动停止或任务取消;失败时标记为FAILED)
     */
    private void updateStepStatusForVehicle(TaskStepDetail step, DevicePlcVO.OperationResult result) {
        if (step == null || result == null) {
            return;
        }
        boolean success = Boolean.TRUE.equals(result.getSuccess());
        boolean completed = false;
        if (result.getData() != null && result.getData().get("completed") != null) {
            Object flag = result.getData().get("completed");
            if (flag instanceof Boolean) {
                completed = (Boolean) flag;
            } else {
                completed = "true".equalsIgnoreCase(String.valueOf(flag));
            }
        }
        Date now = new Date();
        // 初始化开始时间
        if (step.getStartTime() == null) {
            step.setStartTime(now);
        }
        if (success && !completed) {
            // 成功但未完成:保持RUNNING状态,仅更新提示信息和耗时
            step.setStatus(TaskStepDetail.Status.RUNNING.name());
            String message = result.getMessage();
            step.setSuccessMessage(StringUtils.hasText(message) ? message : "大车设备运行中");
            step.setErrorMessage(null);
            if (step.getStartTime() != null) {
                step.setDurationMs(now.getTime() - step.getStartTime().getTime());
            }
        } else if (success && completed) {
            // 成功且MES已确认完成:标记为COMPLETED并记录结束时间
            step.setStatus(TaskStepDetail.Status.COMPLETED.name());
            String message = result.getMessage();
            step.setSuccessMessage(StringUtils.hasText(message) ? message : "大车设备任务已完成");
            step.setErrorMessage(null);
            if (step.getEndTime() == null) {
                step.setEndTime(now);
            }
            if (step.getStartTime() != null && step.getEndTime() != null) {
                step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
            }
            // 尝试自动收尾整个任务
            checkAndCompleteTaskIfDone(step.getTaskId());
        } else {
            // 失败:标记为FAILED并记录结束时间
            step.setStatus(TaskStepDetail.Status.FAILED.name());
            String message = result.getMessage();
            step.setErrorMessage(message);
            if (step.getEndTime() == null) {
                step.setEndTime(now);
            }
            if (step.getStartTime() != null && step.getEndTime() != null) {
                step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
            }
        }
        step.setOutputData(toJson(result));
        taskStepDetailMapper.updateById(step);
    }
    /**
     * 更新卧转立设备步骤状态(区分等待中和真正完成)
     */
    private void updateStepStatusForTransfer(TaskStepDetail step, DevicePlcVO.OperationResult result) {
@@ -1114,8 +1337,12 @@
        boolean success = Boolean.TRUE.equals(result.getSuccess());
        String message = result.getMessage();
        
        // 判断是否真正完成(只有写入PLC才算完成)
        boolean isRealCompleted = success && message != null && message.contains("批次已写入PLC");
        // 判断是否真正完成:
        // 1. 写入PLC成功
        // 2. 且缓冲已清空(表示所有玻璃已处理完,无新玻璃)
        boolean isRealCompleted = success && message != null
                && message.contains("批次已写入PLC")
                && message.contains("缓冲已清空,任务完成");
        
        if (isRealCompleted) {
            // 真正完成:设置为完成状态,并设置结束时间
@@ -1123,6 +1350,23 @@
            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.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())) {
                step.setStatus(TaskStepDetail.Status.RUNNING.name());
            }
            step.setSuccessMessage(message);
            // 确保开始时间已设置
            if (step.getStartTime() == null) {
                step.setStartTime(new Date());
            }
        } else if (success) {
            // 等待中:保持运行状态,只更新消息
@@ -1140,6 +1384,10 @@
            step.setErrorMessage(message);
            if (step.getEndTime() == null) {
                step.setEndTime(new Date());
            }
            // 计算耗时
            if (step.getStartTime() != null && step.getEndTime() != null) {
                step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime());
            }
        }
        
@@ -1368,7 +1616,7 @@
    }
    /**
     * 分批执行大车设备玻璃上料(当玻璃ID数量超过6个且设置了单片间隔时)
     * 分批执行大车设备玻璃上料(当玻璃ID数量超过6个时)
     */
    private StepResult executeLoadVehicleWithBatches(MultiDeviceTask task,
                                                      DeviceConfig device,
@@ -1376,13 +1624,12 @@
                                                      TaskExecutionContext context,
                                                      List<Map<String, Object>> stepSummaries) {
        List<String> allGlassIds = context.getParameters().getGlassIds();
        Integer glassIntervalMs = context.getParameters().getGlassIntervalMs();
        int batchSize = 6; // 每批最多6个玻璃ID
        
        // 分批处理
        int totalBatches = (allGlassIds.size() + batchSize - 1) / batchSize;
        log.info("大车设备分批上料: deviceId={}, totalGlassIds={}, batchSize={}, totalBatches={}, glassIntervalMs={}",
                device.getId(), allGlassIds.size(), batchSize, totalBatches, glassIntervalMs);
        log.debug("大车设备分批上料: deviceId={}, totalGlassIds={}, batchSize={}, totalBatches={}",
                device.getId(), allGlassIds.size(), batchSize, totalBatches);
        
        for (int batchIndex = 0; batchIndex < totalBatches; batchIndex++) {
            int startIndex = batchIndex * batchSize;
@@ -1392,7 +1639,6 @@
            // 创建临时参数,只包含当前批次的玻璃ID
            TaskParameters batchParams = new TaskParameters();
            batchParams.setGlassIds(new ArrayList<>(batchGlassIds));
            batchParams.setGlassIntervalMs(glassIntervalMs);
            batchParams.setPositionCode(context.getParameters().getPositionCode());
            batchParams.setPositionValue(context.getParameters().getPositionValue());
            
@@ -1413,20 +1659,8 @@
                return stepResult;
            }
            
            log.info("大车设备分批上料成功: deviceId={}, batchIndex={}/{}, glassIds={}",
            log.debug("大车设备分批上料成功: deviceId={}, batchIndex={}/{}, glassIds={}",
                    device.getId(), batchIndex + 1, totalBatches, batchGlassIds);
            // 如果不是最后一批,等待间隔(模拟玻璃每片运动的时间)
            // 这个等待让大车有时间处理当前批次的玻璃,然后再传递下一批
            if (batchIndex < totalBatches - 1 && glassIntervalMs != null && glassIntervalMs > 0) {
                try {
                    log.info("等待单片间隔(模拟玻璃运动时间): glassIntervalMs={}ms, 大车可在此期间继续装玻璃", glassIntervalMs);
                    Thread.sleep(glassIntervalMs);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    return StepResult.failure(device.getDeviceName(), "等待单片间隔时被中断");
                }
            }
        }
        
        // 更新上下文中的已加载玻璃ID
@@ -1487,7 +1721,7 @@
        Map<String, Object> params = buildOperationParams(device, context);
        // 将context引用放入params,供设备处理器使用(用于设备协调)
        params.put("_taskContext", context);
        log.info("executeStepWithRetry构建参数: deviceId={}, deviceType={}, operation={}, paramsKeys={}, params={}",
        log.debug("executeStepWithRetry构建参数: deviceId={}, deviceType={}, operation={}, paramsKeys={}, params={}",
                device.getId(), device.getDeviceType(), determineOperation(device, params), params.keySet(), params);
        step.setInputData(toJson(params));
        taskStepDetailMapper.updateById(step);
@@ -1503,7 +1737,7 @@
                if (retryAttempt > 0) {
                    // 重试前等待
                    long waitTime = retryPolicy.calculateRetryInterval(retryAttempt);
                    log.info("步骤执行重试: deviceId={}, operation={}, retryAttempt={}/{}, waitTime={}ms",
                    log.debug("步骤执行重试: deviceId={}, operation={}, retryAttempt={}/{}, waitTime={}ms",
                        device.getId(), operation, retryAttempt, retryPolicy.getMaxRetryCount(), waitTime);
                    Thread.sleep(waitTime);
                    
@@ -1697,7 +1931,7 @@
            try {
                if (retryAttempt > 0) {
                    long waitTime = retryPolicy.calculateRetryInterval(retryAttempt);
                    log.info("交互步骤执行重试: deviceId={}, retryAttempt={}/{}, waitTime={}ms",
                    log.debug("交互步骤执行重试: deviceId={}, retryAttempt={}/{}, waitTime={}ms",
                        device.getId(), retryAttempt, retryPolicy.getMaxRetryCount(), waitTime);
                    Thread.sleep(waitTime);
                    
@@ -1914,10 +2148,6 @@
                if (taskParams.getPositionValue() != null) {
                    params.put("positionValue", taskParams.getPositionValue());
                }
                // 传递单片间隔配置,如果任务参数中有设置,优先使用任务参数的,否则使用设备配置的
                if (taskParams.getGlassIntervalMs() != null) {
                    params.put("glassIntervalMs", taskParams.getGlassIntervalMs());
                }
                params.put("triggerRequest", true);
                break;
            case DeviceConfig.DeviceType.LARGE_GLASS:
@@ -1935,13 +2165,13 @@
            case DeviceConfig.DeviceType.WORKSTATION_SCANNER:
                // 卧转立扫码设备:从任务参数中获取玻璃ID列表,取第一个作为当前要测试的玻璃ID
                // 注意:扫码设备通常通过定时器执行,但如果通过executeStep执行,也需要传递glassId
                log.info("buildOperationParams处理扫码设备: deviceId={}, taskParams.glassIds={}, isEmpty={}",
                log.debug("buildOperationParams处理扫码设备: deviceId={}, taskParams.glassIds={}, isEmpty={}",
                        device.getId(), taskParams.getGlassIds(), 
                        CollectionUtils.isEmpty(taskParams.getGlassIds()));
                if (!CollectionUtils.isEmpty(taskParams.getGlassIds())) {
                    params.put("glassId", taskParams.getGlassIds().get(0));
                    params.put("glassIds", new ArrayList<>(taskParams.getGlassIds()));
                    log.info("buildOperationParams为扫码设备添加glassId: deviceId={}, glassId={}, glassIdsSize={}",
                    log.debug("buildOperationParams为扫码设备添加glassId: deviceId={}, glassId={}, glassIdsSize={}",
                            device.getId(), taskParams.getGlassIds().get(0), taskParams.getGlassIds().size());
                } else {
                    log.warn("buildOperationParams扫码设备glassIds为空: deviceId={}, taskParams.glassIds={}, taskParams={}", 
@@ -2017,7 +2247,7 @@
        if (!CollectionUtils.isEmpty(scannerGlassIds)) {
            context.getParameters().setGlassIds(new ArrayList<>(scannerGlassIds));
            context.setLoadedGlassIds(new ArrayList<>(scannerGlassIds));
            log.info("卧转立扫码获取到玻璃ID: {}", scannerGlassIds);
            log.debug("卧转立扫码获取到玻璃ID: {}", scannerGlassIds);
        } else {
            log.warn("卧转立扫码未获取到玻璃ID,后续设备可能无法执行");
        }