huang
9 天以前 9a9479a5e34324822b223747b7c88ff060466db0
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());
            }