huang
3 天以前 ea51b55feb73883040ed8a87b5a4aeb0bf94bb5e
mes-processes/mes-plcSend/src/main/java/com/mes/interaction/vehicle/handler/LoadVehicleLogicHandler.java
@@ -112,6 +112,10 @@
    
    // 记录当前任务:deviceId -> 任务信息
    private final Map<String, MesTaskInfo> currentTasks = new ConcurrentHashMap<>();
    /**
     * 记录最近一次已完成但MES未复位的任务签名,避免重复拉起
     */
    private final Map<String, CompletedMesRecord> lastCompletedMesRecords = new ConcurrentHashMap<>();
    @Autowired
    public LoadVehicleLogicHandler(
@@ -145,6 +149,10 @@
                // 因为定时器可能会重复调用,或者需要等待任务完成
                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()
@@ -215,7 +223,7 @@
                    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);
@@ -1356,6 +1364,25 @@
                        .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);
            
@@ -1453,6 +1480,7 @@
            taskInfo.cartime = timeCalc.cartime;
            taskInfo.createdTime = System.currentTimeMillis();
            taskInfo.isOutbound = isOutbound;
            taskInfo.mesSignature = mesSignature;
            
            // 从配置中读取破损玻璃索引(用于测试场景)
            // 配置格式:brokenGlassIndices: [0, 2] 表示第1个和第3个玻璃应该破损
@@ -2012,6 +2040,7 @@
     * 返回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()
@@ -2075,9 +2104,14 @@
                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();
            }
@@ -2116,8 +2150,84 @@
            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(
@@ -2321,6 +2431,20 @@
        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;
        }
    }
    /**
@@ -2399,5 +2523,27 @@
            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();
    }
}