| | |
| | | |
| | | // 记录当前任务:deviceId -> 任务信息 |
| | | private final Map<String, MesTaskInfo> currentTasks = new ConcurrentHashMap<>(); |
| | | /** |
| | | * 记录最近一次已完成但MES未复位的任务签名,避免重复拉起 |
| | | */ |
| | | private final Map<String, CompletedMesRecord> lastCompletedMesRecords = new ConcurrentHashMap<>(); |
| | | |
| | | @Autowired |
| | | public LoadVehicleLogicHandler( |
| | |
| | | // 因为定时器可能会重复调用,或者需要等待任务完成 |
| | | if ("feedGlass".equals(operation) && status.isExecuting()) { |
| | | log.debug("车辆 {} 当前状态为 EXECUTING,但允许继续执行 feedGlass(定时器重复调用)", deviceId); |
| | | // 允许继续执行,不返回错误 |
| | | } else if ("clearGlass".equals(operation) || "clearPlc".equals(operation) || "clear".equals(operation)) { |
| | | // 清除操作应该允许在任何状态下执行,因为其目的就是重置设备状态 |
| | | log.debug("车辆 {} 当前状态为 {},但允许执行清除操作 {}", deviceId, status.getState(), operation); |
| | | // 允许继续执行,不返回错误 |
| | | } else { |
| | | return DevicePlcVO.OperationResult.builder() |
| | |
| | | result = handleSetOnlineState(deviceConfig, params, logicParams); |
| | | break; |
| | | case "checkMesConfirm": |
| | | result = checkMesConfirm(deviceConfig, logicParams); |
| | | result = checkMesConfirm(deviceConfig, params, logicParams); |
| | | break; |
| | | case "markBroken": |
| | | result = handleMarkBroken(deviceConfig, params, logicParams); |
| | |
| | | .build(); |
| | | } |
| | | |
| | | // 构建当前MES数据签名,用于判断是否为已确认但未复位的旧任务 |
| | | String mesSignature = buildMesSignature(mesData); |
| | | CompletedMesRecord completedRecord = lastCompletedMesRecords.get(deviceId); |
| | | if (completedRecord != null |
| | | && mesSignature.equals(completedRecord.signature)) { |
| | | Integer mesConfirm = parseInteger(mesData.get("mesConfirm")); |
| | | if (mesConfirm != null && mesConfirm == 1) { |
| | | Map<String, Object> waitData = new HashMap<>(); |
| | | waitData.put("completed", true); |
| | | waitData.put("waiting", true); |
| | | waitData.put("waitingReason", "mesNotReset"); |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(true) |
| | | .message("MES已确认完成但未复位(mesSend/mesConfirm),等待复位后再接收新任务") |
| | | .data(waitData) |
| | | .build(); |
| | | } |
| | | } |
| | | |
| | | // mesSend=1,记录日志 |
| | | log.info("检测到mesSend=1,开始读取MES任务信息: deviceId={}", deviceId); |
| | | |
| | |
| | | taskInfo.cartime = timeCalc.cartime; |
| | | taskInfo.createdTime = System.currentTimeMillis(); |
| | | taskInfo.isOutbound = isOutbound; |
| | | taskInfo.mesSignature = mesSignature; |
| | | |
| | | // 从配置中读取破损玻璃索引(用于测试场景) |
| | | // 配置格式:brokenGlassIndices: [0, 2] 表示第1个和第3个玻璃应该破损 |
| | |
| | | * 返回OperationResult.data中的 completed 标志表示是否已确认完成 |
| | | */ |
| | | public DevicePlcVO.OperationResult checkMesConfirm(DeviceConfig deviceConfig, |
| | | Map<String, Object> params, |
| | | Map<String, Object> logicParams) { |
| | | if (plcDynamicDataService == null || s7SerializerProvider == null) { |
| | | return DevicePlcVO.OperationResult.builder() |
| | |
| | | data.put("completed", false); |
| | | data.put("waiting", true); |
| | | data.put("waitingReason", "waitingReport"); |
| | | String detail = taskInfo.currentStepDesc; |
| | | String message = "大车任务执行中,尚未汇报,无需检查确认"; |
| | | if (detail != null && !detail.isEmpty()) { |
| | | message = detail + ";" + message; |
| | | } |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(true) |
| | | .message("大车任务执行中,尚未汇报,无需检查确认") |
| | | .message(message) |
| | | .data(data) |
| | | .build(); |
| | | } |
| | |
| | | data.put("completed", completed); |
| | | |
| | | if (completed) { |
| | | // MES已确认,检查是否还有未出片的玻璃(仅对出片任务) |
| | | boolean hasMoreGlass = false; |
| | | int completedCount = 0; |
| | | int totalCount = 0; |
| | | |
| | | if (taskInfo.isOutbound && params != null) { |
| | | // 从TaskExecutionContext中获取已出片的玻璃ID列表和初始玻璃ID列表 |
| | | Object contextObj = params.get("_taskContext"); |
| | | if (contextObj instanceof com.mes.task.model.TaskExecutionContext) { |
| | | com.mes.task.model.TaskExecutionContext context = |
| | | (com.mes.task.model.TaskExecutionContext) contextObj; |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | List<String> initialGlassIds = (List<String>) context.getSharedData().get("initialGlassIds"); |
| | | @SuppressWarnings("unchecked") |
| | | List<String> outboundGlassIds = (List<String>) context.getSharedData().get("outboundGlassIds"); |
| | | |
| | | if (initialGlassIds != null && !initialGlassIds.isEmpty()) { |
| | | totalCount = initialGlassIds.size(); |
| | | completedCount = (outboundGlassIds != null) ? outboundGlassIds.size() : 0; |
| | | |
| | | // 检查是否所有玻璃都已出片 |
| | | if (outboundGlassIds == null || !outboundGlassIds.containsAll(initialGlassIds)) { |
| | | hasMoreGlass = true; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 如果还有未出片的玻璃,保持plcRequest=1,清理本次任务状态,等待下次交互 |
| | | // 这样第二次交互时,checkMesTask可以检测到mesSend=1,创建新任务,完整地走一遍逻辑 |
| | | if (hasMoreGlass) { |
| | | // 清空state和汇报字(本次交互已完成) |
| | | clearTaskStates(deviceConfig, serializer); |
| | | |
| | | // 注意:不记录lastCompletedMesRecords,因为还有未出片的玻璃,任务未真正完成 |
| | | // 这样第二次交互时,即使MES发送新任务(新的玻璃ID),也不会被误判为旧任务 |
| | | |
| | | // 任务完成,恢复为空闲状态(本次交互已完成) |
| | | statusManager.updateVehicleStatus( |
| | | deviceConfig.getDeviceId(), VehicleState.IDLE); |
| | | statusManager.clearVehicleTask(deviceConfig.getDeviceId()); |
| | | |
| | | // 移除任务记录(本次交互已完成,等待下次交互时创建新任务) |
| | | currentTasks.remove(deviceConfig.getDeviceId()); |
| | | |
| | | // 停止任务监控(本次交互已完成) |
| | | handleStopTaskMonitor(deviceConfig); |
| | | |
| | | // 保持plcRequest=1(可以接收下次任务) |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put("plcRequest", 1); |
| | | plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); |
| | | |
| | | log.info("出片任务本次交互完成,还有未出片的玻璃,等待下次交互: deviceId={}, completedCount={}, totalCount={}", |
| | | deviceConfig.getDeviceId(), completedCount, totalCount); |
| | | |
| | | String progressMessage = String.format("目前完成出片玻璃数量%d/%d,等待下次交互任务", completedCount, totalCount); |
| | | data.put("completed", false); // 标记为未完成,因为还有未出片的玻璃 |
| | | data.put("waiting", true); |
| | | data.put("waitingReason", "moreGlassToOutbound"); |
| | | data.put("completedCount", completedCount); |
| | | data.put("totalCount", totalCount); |
| | | |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(true) |
| | | .message(String.format("出片任务本次交互完成:MES已确认(mesConfirm=1),已清空state和汇报字。%s。大车空闲(plcRequest=1),等待MES发送下次任务", progressMessage)) |
| | | .data(data) |
| | | .build(); |
| | | } |
| | | |
| | | // 所有玻璃都已出片,正常完成流程 |
| | | // MES已确认,清空state和汇报字 |
| | | clearTaskStates(deviceConfig, serializer); |
| | | |
| | | // 记录已完成的任务签名,避免MES未复位时被重复拉起 |
| | | lastCompletedMesRecords.put(deviceId, |
| | | new CompletedMesRecord(taskInfo.mesSignature, System.currentTimeMillis())); |
| | | |
| | | // 任务完成,恢复为空闲状态 |
| | | statusManager.updateVehicleStatus( |
| | |
| | | List<Integer> brokenGlassIndices = null; // 标记为破损的玻璃索引列表(0-based,用于测试场景) |
| | | Long mesConfirmStartTime = null; // MES确认开始等待的时间(用于超时检测) |
| | | String currentStepDesc = null; // 当前步骤描述(用于显示详细的执行状态) |
| | | String mesSignature = null; // MES数据签名,用于避免未复位时重复拉起 |
| | | } |
| | | |
| | | /** |
| | | * 记录已完成但MES未复位的任务信息 |
| | | */ |
| | | private static class CompletedMesRecord { |
| | | final String signature; |
| | | final long completedAt; |
| | | |
| | | CompletedMesRecord(String signature, long completedAt) { |
| | | this.signature = signature; |
| | | this.completedAt = completedAt; |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | log.warn("清空大车state字段失败: deviceId={}, error={}", deviceConfig != null ? deviceConfig.getId() : "null", e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 将MES数据构造成签名字符串,用于识别是否为同一批次任务 |
| | | */ |
| | | private String buildMesSignature(Map<String, Object> mesData) { |
| | | if (mesData == null || mesData.isEmpty()) { |
| | | return "empty"; |
| | | } |
| | | StringBuilder sb = new StringBuilder(); |
| | | sb.append("mesSend=").append(mesData.getOrDefault("mesSend", "")) |
| | | .append(";mesConfirm=").append(mesData.getOrDefault("mesConfirm", "")); |
| | | for (int i = 1; i <= 6; i++) { |
| | | sb.append(";g").append(i).append("=") |
| | | .append(mesData.getOrDefault("mesGlassId" + i, "")) |
| | | .append(",s").append(mesData.getOrDefault("start" + i, "")) |
| | | .append(",t").append(mesData.getOrDefault("target" + i, "")) |
| | | .append(",w").append(mesData.getOrDefault("mesWidth" + i, "")) |
| | | .append(",h").append(mesData.getOrDefault("mesHeight" + i, "")) |
| | | .append(",th").append(mesData.getOrDefault("mesThickness" + i, "")); |
| | | } |
| | | return sb.toString(); |
| | | } |
| | | } |
| | | |