| | |
| | | |
| | | 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()); |
| | |
| | | // 4. 大理片笼设备:启动定时器逻辑处理(不涉及PLC交互,只负责逻辑处理) |
| | | if (isLargeGlass) { |
| | | TaskStepDetail step = createStepRecord(task, device, currentOrder); |
| | | |
| | | ScheduledFuture<?> largeGlassTask = startLargeGlassTimer(task, step, device, context); |
| | | if (largeGlassTask != null) { |
| | | registerScheduledTask(task.getTaskId(), largeGlassTask); |
| | | stepSummaries.add(createStepSummary(device.getDeviceName(), true, "大理片笼定时器已启动,逻辑处理中")); |
| | | } else { |
| | | stepSummaries.add(createStepSummary(device.getDeviceName(), false, "启动定时器失败")); |
| | | success = false; |
| | | failureMessage = "大理片笼设备启动定时器失败"; |
| | | break; |
| | | } |
| | | |
| | | // 等进片大车步骤真正完成后再启动大理片笼定时器,保证执行顺序为:进片大车 -> 大理片笼 |
| | | context.getSharedData().put("largeGlassStepId", step.getId()); |
| | | context.getSharedData().put("largeGlassDeviceId", device.getId()); |
| | | stepSummaries.add(createStepSummary(device.getDeviceName(), true, "已创建大理片笼步骤,等待进片大车完成后启动定时器")); |
| | | currentOrder++; |
| | | continue; |
| | | } |
| | |
| | | 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); |
| | | } |
| | |
| | | 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)) { |
| | |
| | | } |
| | | }, 0, scanIntervalMs, TimeUnit.MILLISECONDS); |
| | | |
| | | // 在串行执行模式下,扫码设备是第一个,应该立即设置为 RUNNING |
| | | // 其他设备保持 WAITING,直到它们真正开始工作 |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.RUNNING, context); |
| | | return future; |
| | |
| | | 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)) { |
| | |
| | | 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); |
| | |
| | | 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 { |
| | |
| | | } |
| | | }, 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); |
| | |
| | | task.getTaskId(), device.getId()); |
| | | return; |
| | | } |
| | | ensureStepRunning(step, task.getTaskId()); |
| | | // 检查是否有卧转立主体已输出、准备上大车的玻璃信息 |
| | | List<String> readyGlassIds = getTransferReadyGlassIds(context); |
| | | if (CollectionUtils.isEmpty(readyGlassIds)) { |
| | | // 没有卧转立输出的玻璃,继续等待 |
| | | // 已完成/失败的步骤不再回退状态 |
| | | if (TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus()) |
| | | || TaskStepDetail.Status.FAILED.name().equals(step.getStatus())) { |
| | | return; |
| | | } |
| | | // 进片大车设备:只有在真正开始处理时才设置为RUNNING |
| | | // 先检查卧转立设备是否已完成,如果还在执行中,不应该开始大车的工作 |
| | | boolean transferCompleted = isTransferDeviceCompleted(task.getTaskId(), context); |
| | | if (!transferCompleted) { |
| | | // 卧转立还在执行中,等待卧转立完成 |
| | | if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) { |
| | | step.setStatus(TaskStepDetail.Status.PENDING.name()); |
| | | step.setSuccessMessage("等待卧转立完成"); |
| | | if (step.getStartTime() == null) { |
| | | step.setStartTime(new Date()); |
| | | } |
| | | taskStepDetailMapper.updateById(step); |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | } |
| | | log.debug("卧转立还在执行中,进片大车等待: taskId={}, deviceId={}", |
| | | task.getTaskId(), device.getId()); |
| | | return; |
| | | } |
| | | |
| | | // 如果玻璃ID数量没有变化,说明没有新的玻璃,继续等待 |
| | | // 检查是否有卧转立主体已输出、准备上大车的玻璃信息 |
| | | List<String> readyGlassIds = getTransferReadyGlassIds(context); |
| | | |
| | | // 如果当前没有新的玻璃,但卧转立已完成,可以轮询MES任务/确认状态 |
| | | if (CollectionUtils.isEmpty(readyGlassIds)) { |
| | | // 检查是否所有初始玻璃都已装载 |
| | | @SuppressWarnings("unchecked") |
| | | List<String> initialGlassIds = (List<String>) context.getSharedData().get("initialGlassIds"); |
| | | List<String> loadedGlassIds = getLoadedGlassIds(context); |
| | | |
| | | if (initialGlassIds != null && !initialGlassIds.isEmpty()) { |
| | | // 如果所有初始玻璃都已装载,说明已经处理完,不应该再变回等待 |
| | | if (loadedGlassIds != null && !loadedGlassIds.isEmpty() |
| | | && loadedGlassIds.containsAll(initialGlassIds)) { |
| | | // 所有玻璃都已装载,保持RUNNING状态,继续轮询MES任务/确认 |
| | | 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); |
| | | } |
| | | // 继续轮询MES任务/确认状态,若MES确认完成会在内部更新步骤为COMPLETED |
| | | pollMesForVehicle(task, step, device, context); |
| | | // 如果进片大车步骤在轮询后已完成,则尝试启动大理片笼定时器 |
| | | if (TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) { |
| | | startLargeGlassTimerIfNeeded(task, context); |
| | | } |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 如果大车已经装载过玻璃(RUNNING状态),轮询MES任务/确认状态 |
| | | if (TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())) { |
| | | // 轮询MES任务/确认状态,若MES确认完成会在内部更新步骤为COMPLETED |
| | | pollMesForVehicle(task, step, device, context); |
| | | // 如果进片大车步骤在轮询后已完成,则尝试启动大理片笼定时器 |
| | | if (TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) { |
| | | startLargeGlassTimerIfNeeded(task, context); |
| | | } |
| | | } else { |
| | | // 如果还没有装载过玻璃,等待卧转立输出 |
| | | if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus())) { |
| | | step.setStatus(TaskStepDetail.Status.PENDING.name()); |
| | | step.setSuccessMessage("等待卧转立输出玻璃"); |
| | | if (step.getStartTime() == null) { |
| | | step.setStartTime(new Date()); |
| | | } |
| | | taskStepDetailMapper.updateById(step); |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | } |
| | | } |
| | | return; |
| | | } |
| | | |
| | | // 如果玻璃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; |
| | |
| | | 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保存到共享数据中(供大理片笼使用) |
| | |
| | | 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()); |
| | |
| | | } |
| | | |
| | | // 第二步:检查MES确认状态(如果大车处理器支持的话) |
| | | // 只有在任务已开始执行(有任务记录)时才检查MES确认 |
| | | DevicePlcVO.OperationResult mesResult = null; |
| | | try { |
| | | mesResult = handler.execute(device, "checkMesConfirm", Collections.emptyMap()); |
| | | Map<String, Object> confirmParams = new HashMap<>(); |
| | | confirmParams.put("_taskContext", context); |
| | | mesResult = handler.execute(device, "checkMesConfirm", confirmParams); |
| | | } catch (Exception e) { |
| | | log.warn("进片大车设备检查MES确认状态异常: taskId={}, deviceId={}, error={}", |
| | | task.getTaskId(), device.getId(), e.getMessage()); |
| | | } |
| | | |
| | | // 如果MES已确认完成(mesConfirm=1),检查卧转立设备状态和玻璃信息 |
| | | // 如果卧转立已完成且所有玻璃都已装载,可以标记为完成 |
| | | if (mesResult != null && mesResult.getData() != null) { |
| | | Object completedFlag = mesResult.getData().get("completed"); |
| | | boolean mesConfirmed = false; |
| | | if (completedFlag instanceof Boolean) { |
| | | mesConfirmed = (Boolean) completedFlag; |
| | | } else if (completedFlag != null) { |
| | | mesConfirmed = "true".equalsIgnoreCase(String.valueOf(completedFlag)); |
| | | } |
| | | |
| | | if (mesConfirmed) { |
| | | // MES已确认完成,检查卧转立设备是否已完成 |
| | | boolean transferCompletedForMes = isTransferDeviceCompleted(task.getTaskId(), context); |
| | | if (transferCompletedForMes) { |
| | | // 检查任务上下文中的初始玻璃ID和已装载的玻璃ID |
| | | @SuppressWarnings("unchecked") |
| | | List<String> initialGlassIds = (List<String>) context.getSharedData().get("initialGlassIds"); |
| | | List<String> loadedGlassIds = getLoadedGlassIds(context); |
| | | |
| | | if (initialGlassIds != null && !initialGlassIds.isEmpty() |
| | | && loadedGlassIds != null && !loadedGlassIds.isEmpty()) { |
| | | // 检查是否所有初始玻璃都已装载 |
| | | boolean allGlassesLoaded = loadedGlassIds.containsAll(initialGlassIds); |
| | | if (allGlassesLoaded) { |
| | | // 卧转立已完成且所有玻璃都已装载,标记为完成 |
| | | log.info("MES已确认且卧转立已完成且所有玻璃已装载,任务自动完成: taskId={}, deviceId={}, initialCount={}, loadedCount={}", |
| | | task.getTaskId(), device.getId(), initialGlassIds.size(), loadedGlassIds.size()); |
| | | // mesResult已经包含completed=true,不需要修改 |
| | | } |
| | | } |
| | | } else { |
| | | // 卧转立还未完成,不应该标记为完成 |
| | | log.debug("MES已确认但卧转立未完成,等待卧转立完成: taskId={}, deviceId={}", |
| | | task.getTaskId(), device.getId()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 更新步骤状态(大车设备保持RUNNING,直到MES确认完成或任务取消) |
| | | if (mesResult != null) { |
| | | updateStepStatusForVehicle(step, mesResult); |
| | | updateStepStatusForVehicle(task.getTaskId(), step, mesResult); |
| | | boolean opSuccess = Boolean.TRUE.equals(mesResult.getSuccess()); |
| | | updateTaskProgress(task, step.getStepOrder(), opSuccess); |
| | | if (!opSuccess) { |
| | |
| | | DeviceCoordinationService.DeviceStatus.FAILED, context); |
| | | } |
| | | } else { |
| | | updateStepStatusForVehicle(step, feedResult); |
| | | updateStepStatusForVehicle(task.getTaskId(), step, feedResult); |
| | | boolean opSuccess = Boolean.TRUE.equals(feedResult.getSuccess()); |
| | | updateTaskProgress(task, step.getStepOrder(), opSuccess); |
| | | if (!opSuccess) { |
| | |
| | | DeviceCoordinationService.DeviceStatus.FAILED, context); |
| | | } |
| | | } |
| | | |
| | | // 当进片大车步骤真正完成后,再启动大理片笼定时器,保证执行顺序 |
| | | if (TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) { |
| | | startLargeGlassTimerIfNeeded(task, context); |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("进片大车设备定时器执行异常: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e); |
| | | } |
| | | }, 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); |
| | |
| | | TaskExecutionContext context) { |
| | | try { |
| | | final long MONITOR_INTERVAL_MS = 2_000; // 2秒监控一次 |
| | | |
| | | |
| | | log.debug("启动出片大车设备定时器: taskId={}, deviceId={}, interval={}s", |
| | | task.getTaskId(), device.getId(), MONITOR_INTERVAL_MS / 1000); |
| | | |
| | | // 启动定时任务 |
| | | |
| | | ScheduledFuture<?> future = scheduledExecutor.scheduleWithFixedDelay(() -> { |
| | | try { |
| | | if (isTaskCancelled(context)) { |
| | |
| | | task.getTaskId(), device.getId()); |
| | | return; |
| | | } |
| | | ensureStepRunning(step, task.getTaskId()); |
| | | // 检查是否有已处理的玻璃信息(从大理片笼来的) |
| | | List<String> processedGlassIds = getProcessedGlassIds(context); |
| | | if (CollectionUtils.isEmpty(processedGlassIds)) { |
| | | log.debug("出片大车设备定时器:暂无已处理的玻璃信息: taskId={}, deviceId={}", |
| | | task.getTaskId(), device.getId()); |
| | | |
| | | final String lastMsgKey = "outboundVehicleLastMessage:" + device.getId(); |
| | | |
| | | // 1) 总目标:大理片笼产出的“应出片玻璃列表” |
| | | List<String> processedGlassIdsRaw = getProcessedGlassIds(context); |
| | | if (CollectionUtils.isEmpty(processedGlassIdsRaw)) { |
| | | // 尚未有大理片笼输出,保持等待 |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.WAITING, context); |
| | | if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus()) |
| | | && !TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) { |
| | | step.setStatus(TaskStepDetail.Status.PENDING.name()); |
| | | step.setSuccessMessage("等待大理片笼处理完成"); |
| | | if (step.getStartTime() == null) { |
| | | step.setStartTime(new Date()); |
| | | } |
| | | taskStepDetailMapper.updateById(step); |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | log.debug("出片大车设备定时器检测到已处理的玻璃信息: taskId={}, deviceId={}, glassCount={}", |
| | | task.getTaskId(), device.getId(), processedGlassIds.size()); |
| | | |
| | | // 执行出片操作 |
| | | Map<String, Object> checkParams = new HashMap<>(); |
| | | checkParams.put("glassIds", new ArrayList<>(processedGlassIds)); |
| | | checkParams.put("_taskContext", context); |
| | | |
| | | |
| | | // 2) 已完成累积:outboundGlassIds(任务侧维护,按mesConfirm=1累加) |
| | | Set<String> processedSet = new LinkedHashSet<>(); |
| | | for (String id : processedGlassIdsRaw) { |
| | | if (StringUtils.hasText(id)) { |
| | | processedSet.add(id); |
| | | } |
| | | } |
| | | Set<String> outboundSet = new HashSet<>(); |
| | | for (String id : getOutboundGlassIds(context)) { |
| | | if (StringUtils.hasText(id)) { |
| | | outboundSet.add(id); |
| | | } |
| | | } |
| | | |
| | | List<String> remaining = new ArrayList<>(); |
| | | for (String id : processedSet) { |
| | | if (!outboundSet.contains(id)) { |
| | | remaining.add(id); |
| | | } |
| | | } |
| | | int total = processedSet.size(); |
| | | int done = total - remaining.size(); |
| | | |
| | | // 3) 若已全部完成,直接收尾步骤 |
| | | if (total > 0 && remaining.isEmpty()) { |
| | | if (!TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) { |
| | | step.setStatus(TaskStepDetail.Status.COMPLETED.name()); |
| | | String lastMsg = null; |
| | | Object lastObj = context.getSharedData().get(lastMsgKey); |
| | | if (lastObj != null && StringUtils.hasText(String.valueOf(lastObj))) { |
| | | lastMsg = String.valueOf(lastObj); |
| | | } |
| | | step.setSuccessMessage(StringUtils.hasText(lastMsg) |
| | | ? String.format("出片完成:%d/%d;%s", done, total, lastMsg) |
| | | : String.format("出片完成:%d/%d", done, total)); |
| | | step.setErrorMessage(null); |
| | | Date now = new Date(); |
| | | if (step.getStartTime() == null) { |
| | | step.setStartTime(now); |
| | | } |
| | | if (step.getEndTime() == null) { |
| | | step.setEndTime(now); |
| | | } |
| | | if (step.getStartTime() != null && step.getEndTime() != null) { |
| | | step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime()); |
| | | } |
| | | taskStepDetailMapper.updateById(step); |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | checkAndCompleteTaskIfDone(step.getTaskId()); |
| | | } |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.COMPLETED, context); |
| | | return; |
| | | } |
| | | |
| | | // 4) 进入运行态(只在真正开始出片时) |
| | | if (!TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())) { |
| | | step.setStatus(TaskStepDetail.Status.RUNNING.name()); |
| | | if (step.getStartTime() == null) { |
| | | step.setStartTime(new Date()); |
| | | } |
| | | } |
| | | // 更新进度信息(便于前端实时展示) |
| | | Date now = new Date(); |
| | | if (step.getStartTime() != null) { |
| | | step.setDurationMs(now.getTime() - step.getStartTime().getTime()); |
| | | } |
| | | taskStepDetailMapper.updateById(step); |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | |
| | | DeviceLogicHandler handler = handlerFactory.getHandler(device.getDeviceType()); |
| | | if (handler != null) { |
| | | // 将logicParams合并到checkParams中 |
| | | Map<String, Object> logicParams = parseLogicParams(device); |
| | | if (logicParams != null && !logicParams.isEmpty()) { |
| | | checkParams.put("_logicParams", logicParams); |
| | | if (handler == null) { |
| | | log.warn("未找到出片大车handler: deviceId={}, deviceType={}", device.getId(), device.getDeviceType()); |
| | | return; |
| | | } |
| | | |
| | | Map<String, Object> logicParams = parseLogicParams(device); |
| | | Map<String, Object> baseParams = new HashMap<>(); |
| | | baseParams.put("_taskContext", context); |
| | | if (logicParams != null && !logicParams.isEmpty()) { |
| | | baseParams.put("_logicParams", logicParams); |
| | | } |
| | | |
| | | String latestInteractionMsg = null; |
| | | |
| | | // 5) 先检查MES任务(mesSend=1时会把本批次玻璃ID写入currentMesBatchGlassIds) |
| | | DevicePlcVO.OperationResult mesTaskResult = null; |
| | | try { |
| | | mesTaskResult = handler.execute(device, "checkMesTask", new HashMap<>(baseParams)); |
| | | } catch (Exception e) { |
| | | log.warn("出片大车检查MES任务异常: taskId={}, deviceId={}, error={}", |
| | | task.getTaskId(), device.getId(), e.getMessage()); |
| | | } |
| | | if (mesTaskResult != null && StringUtils.hasText(mesTaskResult.getMessage())) { |
| | | latestInteractionMsg = mesTaskResult.getMessage(); |
| | | } |
| | | |
| | | // 从checkMesTask返回值中读取本批次玻璃ID(不依赖设备侧写_taskContext) |
| | | try { |
| | | if (mesTaskResult != null && mesTaskResult.getData() != null) { |
| | | Object batchObj = mesTaskResult.getData().get("batchGlassIds"); |
| | | if (batchObj instanceof List) { |
| | | @SuppressWarnings("unchecked") |
| | | List<Object> raw = (List<Object>) batchObj; |
| | | List<String> batchIds = new ArrayList<>(); |
| | | for (Object o : raw) { |
| | | if (o != null && StringUtils.hasText(String.valueOf(o))) { |
| | | batchIds.add(String.valueOf(o)); |
| | | } |
| | | } |
| | | if (!batchIds.isEmpty()) { |
| | | context.getSharedData().put("currentMesBatchGlassIds", batchIds); |
| | | } |
| | | } |
| | | } |
| | | // 第一步:写入大车出片请求 |
| | | DevicePlcVO.OperationResult feedResult = handler.execute(device, "feedGlass", checkParams); |
| | | |
| | | 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(), feedResult.getMessage()); |
| | | } catch (Exception ignore) { |
| | | // 不影响主流程 |
| | | } |
| | | |
| | | boolean mesSendIsZero = false; |
| | | if (mesTaskResult != null && mesTaskResult.getData() != null) { |
| | | Object reason = mesTaskResult.getData().get("waitingReason"); |
| | | if ("mesSend=0".equals(String.valueOf(reason))) { |
| | | mesSendIsZero = true; |
| | | } |
| | | |
| | | // 第二步:检查MES确认状态(如果大车处理器支持的话) |
| | | DevicePlcVO.OperationResult mesResult = null; |
| | | } else if (mesTaskResult != null && StringUtils.hasText(mesTaskResult.getMessage()) |
| | | && mesTaskResult.getMessage().contains("mesSend=0")) { |
| | | mesSendIsZero = true; |
| | | } |
| | | |
| | | // 6) 若MES尚未下发任务(mesSend=0),触发一次出片请求(feedGlass) |
| | | if (mesSendIsZero && !remaining.isEmpty()) { |
| | | Map<String, Object> feedParams = new HashMap<>(baseParams); |
| | | feedParams.put("glassIds", new ArrayList<>(remaining)); |
| | | try { |
| | | mesResult = handler.execute(device, "checkMesConfirm", Collections.emptyMap()); |
| | | DevicePlcVO.OperationResult feedResult = handler.execute(device, "feedGlass", feedParams); |
| | | if (feedResult != null && StringUtils.hasText(feedResult.getMessage())) { |
| | | latestInteractionMsg = feedResult.getMessage(); |
| | | } |
| | | if (Boolean.TRUE.equals(feedResult.getSuccess())) { |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.RUNNING, context); |
| | | } else { |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.WAITING, context); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("出片大车设备检查MES确认状态异常: taskId={}, deviceId={}, error={}", |
| | | log.warn("出片大车触发feedGlass异常: 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); |
| | | } |
| | | } |
| | | |
| | | // 7) 再检查MES确认(mesConfirm=1表示本次交互完成) |
| | | DevicePlcVO.OperationResult mesConfirmResult = null; |
| | | try { |
| | | mesConfirmResult = handler.execute(device, "checkMesConfirm", new HashMap<>(baseParams)); |
| | | } catch (Exception e) { |
| | | log.warn("出片大车检查MES确认异常: taskId={}, deviceId={}, error={}", |
| | | task.getTaskId(), device.getId(), e.getMessage()); |
| | | } |
| | | if (mesConfirmResult != null && StringUtils.hasText(mesConfirmResult.getMessage())) { |
| | | // 确认提示优先级最高 |
| | | latestInteractionMsg = mesConfirmResult.getMessage(); |
| | | } |
| | | |
| | | boolean interactionCompleted = false; |
| | | if (mesConfirmResult != null && mesConfirmResult.getData() != null) { |
| | | Object completedFlag = mesConfirmResult.getData().get("completed"); |
| | | if (completedFlag instanceof Boolean) { |
| | | interactionCompleted = (Boolean) completedFlag; |
| | | } else if (completedFlag != null) { |
| | | interactionCompleted = "true".equalsIgnoreCase(String.valueOf(completedFlag)); |
| | | } |
| | | } |
| | | |
| | | // 8) 更新“最近一次交互提示”与步骤提示文案(避免被纯进度覆盖) |
| | | if (StringUtils.hasText(latestInteractionMsg)) { |
| | | context.getSharedData().put(lastMsgKey, latestInteractionMsg); |
| | | } |
| | | String lastMsg = null; |
| | | Object lastObj = context.getSharedData().get(lastMsgKey); |
| | | if (lastObj != null && StringUtils.hasText(String.valueOf(lastObj))) { |
| | | lastMsg = String.valueOf(lastObj); |
| | | } |
| | | step.setSuccessMessage(StringUtils.hasText(lastMsg) |
| | | ? String.format("出片进行中:%d/%d;%s", done, total, lastMsg) |
| | | : String.format("出片进行中:%d/%d", done, total)); |
| | | taskStepDetailMapper.updateById(step); |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | |
| | | if (interactionCompleted) { |
| | | // 本次交互完成:将本批次(currentMesBatchGlassIds)累加到已出片(outboundGlassIds) |
| | | Object batchObj = context.getSharedData().get("currentMesBatchGlassIds"); |
| | | if (batchObj instanceof List) { |
| | | @SuppressWarnings("unchecked") |
| | | List<String> batchIds = new ArrayList<>((List<String>) batchObj); |
| | | // 仅累加“目标集合”内的玻璃,避免脏数据 |
| | | List<String> filtered = new ArrayList<>(); |
| | | for (String id : batchIds) { |
| | | if (StringUtils.hasText(id) && processedSet.contains(id)) { |
| | | filtered.add(id); |
| | | } |
| | | } |
| | | addOutboundGlassIds(context, filtered); |
| | | } |
| | | // 清空本批次记录,避免下一次轮询重复使用旧批次 |
| | | context.getSharedData().put("currentMesBatchGlassIds", new ArrayList<>()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("出片大车设备定时器执行异常: taskId={}, deviceId={}", task.getTaskId(), device.getId(), e); |
| | | } |
| | | }, 0, MONITOR_INTERVAL_MS, TimeUnit.MILLISECONDS); |
| | | |
| | | |
| | | // 启动时保持WAITING,后续由定时器逻辑切换为RUNNING/COMPLETED |
| | | 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); |
| | |
| | | task.getTaskId(), device.getId()); |
| | | return; |
| | | } |
| | | ensureStepRunning(step, task.getTaskId()); |
| | | // 如果步骤已经完成,不再处理 |
| | | if (TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) { |
| | | // 检查是否所有初始玻璃都已处理完 |
| | | @SuppressWarnings("unchecked") |
| | | List<String> initialGlassIds = (List<String>) context.getSharedData().get("initialGlassIds"); |
| | | List<String> processedGlassIds = getProcessedGlassIds(context); |
| | | |
| | | if (initialGlassIds != null && !initialGlassIds.isEmpty() |
| | | && processedGlassIds != null && !processedGlassIds.isEmpty() |
| | | && processedGlassIds.containsAll(initialGlassIds)) { |
| | | // 所有玻璃都已处理完,保持完成状态 |
| | | log.debug("大理片笼设备已完成且所有玻璃已处理: taskId={}, deviceId={}, initialCount={}, processedCount={}", |
| | | task.getTaskId(), device.getId(), initialGlassIds.size(), processedGlassIds.size()); |
| | | return; |
| | | } |
| | | } |
| | | |
| | | // 大理片笼设备:只有在真正开始处理时才设置为RUNNING |
| | | // 检查是否有已装载的玻璃信息(从进片大车来的) |
| | | List<String> loadedGlassIds = getLoadedGlassIds(context); |
| | | if (CollectionUtils.isEmpty(loadedGlassIds)) { |
| | | // 没有数据,检查是否所有玻璃都已处理完 |
| | | @SuppressWarnings("unchecked") |
| | | List<String> initialGlassIds = (List<String>) context.getSharedData().get("initialGlassIds"); |
| | | List<String> processedGlassIds = getProcessedGlassIds(context); |
| | | |
| | | if (initialGlassIds != null && !initialGlassIds.isEmpty() |
| | | && processedGlassIds != null && !processedGlassIds.isEmpty() |
| | | && processedGlassIds.containsAll(initialGlassIds)) { |
| | | // 所有玻璃都已处理完,标记为完成 |
| | | if (!TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus())) { |
| | | step.setStatus(TaskStepDetail.Status.COMPLETED.name()); |
| | | step.setSuccessMessage("所有玻璃已处理完成"); |
| | | if (step.getEndTime() == null) { |
| | | step.setEndTime(new Date()); |
| | | } |
| | | if (step.getStartTime() != null && step.getEndTime() != null) { |
| | | step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime()); |
| | | } |
| | | taskStepDetailMapper.updateById(step); |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | checkAndCompleteTaskIfDone(step.getTaskId()); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | // 没有数据且未完成,保持WAITING状态和PENDING步骤状态 |
| | | deviceCoordinationService.syncDeviceStatus(device, |
| | | DeviceCoordinationService.DeviceStatus.WAITING, context); |
| | | if (!TaskStepDetail.Status.PENDING.name().equals(step.getStatus()) |
| | | && !TaskStepDetail.Status.COMPLETED.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); |
| | | if (processStartTime == null) { |
| | | // 第一次检测到玻璃,记录开始处理时间 |
| | | setProcessStartTime(context, System.currentTimeMillis()); |
| | | long now = System.currentTimeMillis(); |
| | | setProcessStartTime(context, now); |
| | | // 补齐步骤的开始时间与状态,确保前端耗时正常显示 |
| | | if (!TaskStepDetail.Status.RUNNING.name().equals(step.getStatus())) { |
| | | step.setStatus(TaskStepDetail.Status.RUNNING.name()); |
| | | } |
| | | if (step.getStartTime() == null) { |
| | | step.setStartTime(new Date(now)); |
| | | } |
| | | taskStepDetailMapper.updateById(step); |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | log.debug("大理片笼设备开始处理: taskId={}, deviceId={}, glassCount={}, processTime={}s", |
| | | task.getTaskId(), device.getId(), loadedGlassIds.size(), processTimeSeconds); |
| | | return; |
| | |
| | | // 更新步骤状态 |
| | | step.setStatus(TaskStepDetail.Status.COMPLETED.name()); |
| | | step.setErrorMessage(null); |
| | | // 记录结束时间与耗时 |
| | | if (step.getEndTime() == null) { |
| | | step.setEndTime(new Date()); |
| | | } |
| | | if (step.getStartTime() == null) { |
| | | // 如果开始时间缺失,用处理开始时间或结束时间兜底 |
| | | step.setStartTime(new Date(processStartTime)); |
| | | } |
| | | if (step.getStartTime() != null && step.getEndTime() != null) { |
| | | step.setDurationMs(step.getEndTime().getTime() - step.getStartTime().getTime()); |
| | | } |
| | | step.setOutputData(toJson(Collections.singletonMap("glassIds", loadedGlassIds))); |
| | | taskStepDetailMapper.updateById(step); |
| | | // 通知前端状态更新 |
| | | notificationService.notifyStepUpdate(task.getTaskId(), step); |
| | | // 大理片笼完成后尝试自动收尾整个任务 |
| | | checkAndCompleteTaskIfDone(step.getTaskId()); |
| | | |
| | |
| | | } |
| | | }, 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); |
| | |
| | | |
| | | /** |
| | | * 设置已装载的玻璃ID列表 |
| | | * 对于分批出片场景,每次设置应替换为当前批次的玻璃ID |
| | | */ |
| | | private void setLoadedGlassIds(TaskExecutionContext context, List<String> glassIds) { |
| | | if (context != null) { |
| | | context.getSharedData().put("loadedGlassIds", new ArrayList<>(glassIds)); |
| | | // 替换为当前批次的玻璃ID |
| | | List<String> currentBatch = new ArrayList<>(); |
| | | if (glassIds != null) { |
| | | currentBatch.addAll(glassIds); |
| | | } |
| | | context.getSharedData().put("loadedGlassIds", currentBatch); |
| | | } |
| | | } |
| | | |
| | |
| | | private void clearProcessedGlassIds(TaskExecutionContext context) { |
| | | if (context != null) { |
| | | context.getSharedData().put("processedGlassIds", new ArrayList<>()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 获取已出片的玻璃ID列表 |
| | | */ |
| | | @SuppressWarnings("unchecked") |
| | | private List<String> getOutboundGlassIds(TaskExecutionContext context) { |
| | | if (context == null) { |
| | | return Collections.emptyList(); |
| | | } |
| | | Object glassIds = context.getSharedData().get("outboundGlassIds"); |
| | | if (glassIds instanceof List) { |
| | | return new ArrayList<>((List<String>) glassIds); |
| | | } |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | /** |
| | | * 添加已出片的玻璃ID列表 |
| | | */ |
| | | private void addOutboundGlassIds(TaskExecutionContext context, List<String> glassIds) { |
| | | if (context != null && glassIds != null && !glassIds.isEmpty()) { |
| | | List<String> existing = getOutboundGlassIds(context); |
| | | Set<String> allOutbound = new HashSet<>(existing); |
| | | allOutbound.addAll(glassIds); |
| | | context.getSharedData().put("outboundGlassIds", new ArrayList<>(allOutbound)); |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | // 所有步骤都已完成,收尾任务 |
| | | task.setStatus(MultiDeviceTask.Status.COMPLETED.name()); |
| | | // 关键:同步进度到最终值,避免前端仍显示“3/5”这类旧进度 |
| | | task.setCurrentStep(totalSteps); |
| | | task.setEndTime(new Date()); |
| | | multiDeviceTaskMapper.updateById(task); |
| | | |
| | |
| | | } |
| | | step.setOutputData(toJson(result)); |
| | | taskStepDetailMapper.updateById(step); |
| | | if (StringUtils.hasText(step.getTaskId())) { |
| | | notificationService.notifyStepUpdate(step.getTaskId(), step); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | /** |
| | | * 更新大车设备步骤状态(保持RUNNING,直到手动停止或任务取消;失败时标记为FAILED) |
| | | */ |
| | | private void updateStepStatusForVehicle(TaskStepDetail step, DevicePlcVO.OperationResult result) { |
| | | private void updateStepStatusForVehicle(String taskId, TaskStepDetail step, DevicePlcVO.OperationResult result) { |
| | | if (step == null || result == null) { |
| | | return; |
| | | } |
| | | // 如果步骤已经处于完成或失败状态,则不再被重复更新(防止状态反复切换) |
| | | if (TaskStepDetail.Status.COMPLETED.name().equals(step.getStatus()) |
| | | || TaskStepDetail.Status.FAILED.name().equals(step.getStatus())) { |
| | | log.debug("步骤已完成或失败,不再更新状态: stepId={}, status={}", step.getId(), step.getStatus()); |
| | | return; |
| | | } |
| | | boolean success = Boolean.TRUE.equals(result.getSuccess()); |
| | |
| | | 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状态决定显示为等待还是执行中 |
| | | // 注意:如果步骤已经是RUNNING状态(说明已经装载过玻璃),不应该改回PENDING |
| | | boolean isAlreadyRunning = TaskStepDetail.Status.RUNNING.name().equals(step.getStatus()); |
| | | if (waiting && !isAlreadyRunning) { |
| | | // 只有在还没有开始运行时,才设置为PENDING |
| | | step.setStatus(TaskStepDetail.Status.PENDING.name()); |
| | | } else { |
| | | // 如果已经运行过,或者不是等待状态,保持或设置为RUNNING |
| | | 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 && !isAlreadyRunning ? "大车设备等待中" : "大车设备运行中")); |
| | | step.setErrorMessage(null); |
| | | if (step.getStartTime() != null) { |
| | | step.setDurationMs(now.getTime() - step.getStartTime().getTime()); |
| | |
| | | |
| | | step.setOutputData(toJson(result)); |
| | | taskStepDetailMapper.updateById(step); |
| | | // 通知前端步骤状态已更新 |
| | | notificationService.notifyStepUpdate(taskId, 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 { |
| | | Map<String, Object> taskParams = new HashMap<>(); |
| | | taskParams.put("_taskContext", context); |
| | | mesTaskResult = handler.execute(device, "checkMesTask", taskParams); |
| | | 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 { |
| | | Map<String, Object> checkParams = new HashMap<>(); |
| | | checkParams.put("_taskContext", context); |
| | | mesResult = handler.execute(device, "checkMesConfirm", checkParams); |
| | | } catch (Exception e) { |
| | | log.warn("大车设备检查MES确认状态异常: taskId={}, deviceId={}, error={}", |
| | | task.getTaskId(), device.getId(), e.getMessage()); |
| | | } |
| | | |
| | | // 如果MES已确认完成(mesConfirm=1),检查卧转立设备状态和玻璃信息 |
| | | // 如果卧转立已完成且所有玻璃都已装载,可以标记为完成 |
| | | if (mesResult != null && mesResult.getData() != null) { |
| | | Object completedFlag = mesResult.getData().get("completed"); |
| | | boolean mesConfirmed = false; |
| | | if (completedFlag instanceof Boolean) { |
| | | mesConfirmed = (Boolean) completedFlag; |
| | | } else if (completedFlag != null) { |
| | | mesConfirmed = "true".equalsIgnoreCase(String.valueOf(completedFlag)); |
| | | } |
| | | |
| | | if (mesConfirmed) { |
| | | // MES已确认完成,检查卧转立设备是否已完成 |
| | | boolean transferCompleted = isTransferDeviceCompleted(task.getTaskId(), context); |
| | | if (transferCompleted) { |
| | | // 检查任务上下文中的初始玻璃ID和已装载的玻璃ID |
| | | @SuppressWarnings("unchecked") |
| | | List<String> initialGlassIds = (List<String>) context.getSharedData().get("initialGlassIds"); |
| | | List<String> loadedGlassIds = getLoadedGlassIds(context); |
| | | |
| | | if (initialGlassIds != null && !initialGlassIds.isEmpty() |
| | | && loadedGlassIds != null && !loadedGlassIds.isEmpty()) { |
| | | // 检查是否所有初始玻璃都已装载 |
| | | boolean allGlassesLoaded = loadedGlassIds.containsAll(initialGlassIds); |
| | | if (allGlassesLoaded) { |
| | | // 卧转立已完成且所有玻璃都已装载,标记为完成 |
| | | log.info("MES已确认且卧转立已完成且所有玻璃已装载,任务自动完成: taskId={}, deviceId={}, initialCount={}, loadedCount={}", |
| | | task.getTaskId(), device.getId(), initialGlassIds.size(), loadedGlassIds.size()); |
| | | // mesResult已经包含completed=true,不需要修改 |
| | | } |
| | | } |
| | | } else { |
| | | // 卧转立还未完成,不应该标记为完成 |
| | | log.debug("MES已确认但卧转立未完成,等待卧转立完成: taskId={}, deviceId={}", |
| | | task.getTaskId(), device.getId()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 更新步骤状态 |
| | | if (mesResult != null) { |
| | | updateStepStatusForVehicle(task.getTaskId(), 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; |
| | | } |
| | |
| | | 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())) { |
| | |
| | | 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()); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 当进片大车步骤完成后,如果存在大理片笼设备且尚未启动其定时器,则启动大理片笼定时器 |
| | | * 保证执行顺序为:进片大车 -> 大理片笼 |
| | | */ |
| | | private void startLargeGlassTimerIfNeeded(MultiDeviceTask task, TaskExecutionContext context) { |
| | | if (task == null || context == null) { |
| | | return; |
| | | } |
| | | // 防止重复启动 |
| | | Object startedFlag = context.getSharedData().get("largeGlassTimerStarted"); |
| | | if (startedFlag instanceof Boolean && (Boolean) startedFlag) { |
| | | return; |
| | | } |
| | | |
| | | try { |
| | | // 从上下文中获取设备列表 |
| | | @SuppressWarnings("unchecked") |
| | | List<DeviceConfig> devices = (List<DeviceConfig>) context.getSharedData().get("devices"); |
| | | if (devices == null || devices.isEmpty()) { |
| | | return; |
| | | } |
| | | |
| | | DeviceConfig largeGlassDevice = null; |
| | | for (DeviceConfig device : devices) { |
| | | if (DeviceConfig.DeviceType.LARGE_GLASS.equals(device.getDeviceType())) { |
| | | largeGlassDevice = device; |
| | | break; |
| | | } |
| | | } |
| | | if (largeGlassDevice == null) { |
| | | log.warn("未在设备列表中找到大理片笼设备: taskId={}", task.getTaskId()); |
| | | return; |
| | | } |
| | | |
| | | // 重新加载大理片笼步骤,确保拿到最新状态(取该设备最新的一条步骤记录) |
| | | List<TaskStepDetail> largeGlassSteps = taskStepDetailMapper.selectList( |
| | | Wrappers.<TaskStepDetail>lambdaQuery() |
| | | .eq(TaskStepDetail::getTaskId, task.getTaskId()) |
| | | .eq(TaskStepDetail::getDeviceId, largeGlassDevice.getId()) |
| | | .orderByDesc(TaskStepDetail::getStepOrder) |
| | | .last("LIMIT 1") |
| | | ); |
| | | TaskStepDetail largeGlassStep = (largeGlassSteps != null && !largeGlassSteps.isEmpty()) |
| | | ? largeGlassSteps.get(0) |
| | | : null; |
| | | if (largeGlassStep == null) { |
| | | log.warn("未找到大理片笼步骤记录: taskId={}, deviceId={}", task.getTaskId(), largeGlassDevice.getId()); |
| | | return; |
| | | } |
| | | |
| | | // 如果步骤已经完成或失败,则不再启动定时器 |
| | | String status = largeGlassStep.getStatus(); |
| | | if (TaskStepDetail.Status.COMPLETED.name().equals(status) |
| | | || TaskStepDetail.Status.FAILED.name().equals(status)) { |
| | | log.debug("大理片笼步骤已结束,不再启动定时器: taskId={}, stepId={}, status={}", task.getTaskId(), largeGlassStep.getId(), status); |
| | | context.getSharedData().put("largeGlassTimerStarted", true); |
| | | return; |
| | | } |
| | | |
| | | ScheduledFuture<?> largeGlassTask = startLargeGlassTimer(task, largeGlassStep, largeGlassDevice, context); |
| | | if (largeGlassTask != null) { |
| | | registerScheduledTask(task.getTaskId(), largeGlassTask); |
| | | context.getSharedData().put("largeGlassTimerStarted", true); |
| | | log.info("已在进片大车完成后启动大理片笼定时器: taskId={}, largeGlassDeviceId={}, largeGlassStepId={}", |
| | | task.getTaskId(), largeGlassDevice.getId(), largeGlassStep.getId()); |
| | | } else { |
| | | log.warn("在进片大车完成后启动大理片笼定时器失败: taskId={}, largeGlassDeviceId={}, largeGlassStepId={}", |
| | | task.getTaskId(), largeGlassDevice.getId(), largeGlassStep.getId()); |
| | | } |
| | | } catch (Exception e) { |
| | | log.warn("在进片大车完成后启动大理片笼定时器异常: taskId={}", task.getTaskId(), e); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 检查卧转立设备是否已完成 |
| | | * 返回true表示卧转立已完成(COMPLETED),可以判断大车是否完成 |
| | | * 返回false表示卧转立还在运行中(RUNNING)或等待中(PENDING),不应该标记大车为完成 |
| | | */ |
| | | private boolean isTransferDeviceCompleted(String taskId, TaskExecutionContext context) { |
| | | if (taskId == null || context == null) { |
| | | return false; |
| | | } |
| | | try { |
| | | // 从上下文中获取设备列表 |
| | | @SuppressWarnings("unchecked") |
| | | List<DeviceConfig> devices = (List<DeviceConfig>) context.getSharedData().get("devices"); |
| | | if (devices == null || devices.isEmpty()) { |
| | | return false; |
| | | } |
| | | |
| | | // 查找卧转立设备 |
| | | DeviceConfig transferDevice = null; |
| | | for (DeviceConfig device : devices) { |
| | | if (DeviceConfig.DeviceType.WORKSTATION_TRANSFER.equals(device.getDeviceType())) { |
| | | transferDevice = device; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (transferDevice == null) { |
| | | // 没有卧转立设备,返回true(不影响判断) |
| | | return true; |
| | | } |
| | | |
| | | // 查找卧转立设备的步骤(应该只有一个步骤) |
| | | List<TaskStepDetail> transferSteps = taskStepDetailMapper.selectList( |
| | | Wrappers.<TaskStepDetail>lambdaQuery() |
| | | .eq(TaskStepDetail::getTaskId, taskId) |
| | | .eq(TaskStepDetail::getDeviceId, transferDevice.getId()) |
| | | .orderByDesc(TaskStepDetail::getStepOrder) |
| | | .last("LIMIT 1") |
| | | ); |
| | | |
| | | if (transferSteps == null || transferSteps.isEmpty()) { |
| | | // 没有找到步骤,返回false(卧转立可能还没开始) |
| | | return false; |
| | | } |
| | | |
| | | // 检查步骤状态:只有COMPLETED才算完成,RUNNING或PENDING都不算完成 |
| | | TaskStepDetail transferStep = transferSteps.get(0); |
| | | String status = transferStep.getStatus(); |
| | | boolean isCompleted = TaskStepDetail.Status.COMPLETED.name().equals(status); |
| | | |
| | | log.debug("检查卧转立设备状态: taskId={}, deviceId={}, status={}, isCompleted={}", |
| | | taskId, transferDevice.getId(), status, isCompleted); |
| | | |
| | | return isCompleted; |
| | | } catch (Exception e) { |
| | | log.warn("检查卧转立设备状态失败: taskId={}", taskId, e); |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private String determineOperation(DeviceConfig device, Map<String, Object> params) { |
| | | if (params != null && params.containsKey("operation")) { |
| | | Object op = params.get("operation"); |