| | |
| | | import com.mes.interaction.vehicle.model.VehicleState; |
| | | import com.mes.interaction.vehicle.model.VehicleStatus; |
| | | import com.mes.interaction.vehicle.model.VehicleTask; |
| | | import com.mes.s7.enhanced.EnhancedS7Serializer; |
| | | import com.mes.s7.provider.S7SerializerProvider; |
| | | import com.mes.service.PlcDynamicDataService; |
| | | import com.mes.plc.client.PlcClient; |
| | | import com.mes.plc.factory.PlcClientFactory; |
| | | import com.mes.task.model.TaskExecutionContext; |
| | | import com.mes.interaction.workstation.scanner.handler.HorizontalScannerLogicHandler; |
| | | import lombok.extern.slf4j.Slf4j; |
| | |
| | | import javax.annotation.PreDestroy; |
| | | import java.util.*; |
| | | import java.util.concurrent.*; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * 大车设备逻辑处理器 |
| | |
| | | private DeviceStatusService deviceStatusService; |
| | | |
| | | @Autowired(required = false) |
| | | private PlcDynamicDataService plcDynamicDataService; |
| | | |
| | | @Autowired(required = false) |
| | | private S7SerializerProvider s7SerializerProvider; |
| | | private PlcClientFactory plcClientFactory; |
| | | |
| | | // MES字段列表(进片和出片共用同一套协议) |
| | | // 根据协议,使用带数字后缀的字段名(1-6对应6个玻璃位置) |
| | |
| | | |
| | | // 记录当前任务:deviceId -> 任务信息 |
| | | private final Map<String, MesTaskInfo> currentTasks = new ConcurrentHashMap<>(); |
| | | /** |
| | | * 记录最近一次已完成但MES未复位的任务签名,避免重复拉起 |
| | | */ |
| | | private final Map<String, CompletedMesRecord> lastCompletedMesRecords = new ConcurrentHashMap<>(); |
| | | |
| | | @Autowired |
| | | public LoadVehicleLogicHandler( |
| | | DevicePlcOperationService devicePlcOperationService, |
| | | @Qualifier("deviceGlassInfoService") GlassInfoService glassInfoService) { |
| | | @Qualifier("deviceGlassInfoService") GlassInfoService glassInfoService, |
| | | PlcClientFactory plcClientFactory) { |
| | | super(devicePlcOperationService); |
| | | this.glassInfoService = glassInfoService; |
| | | this.plcClientFactory = plcClientFactory; |
| | | // 设置 PlcClientFactory 到基类 |
| | | this.setPlcClientFactory(plcClientFactory); |
| | | } |
| | | |
| | | @Override |
| | |
| | | Map<String, Object> params, |
| | | Map<String, Object> logicParams) { |
| | | |
| | | String deviceId = deviceConfig.getDeviceId(); |
| | | String deviceId = String.valueOf(deviceConfig.getId()); |
| | | log.info("执行大车设备操作: deviceId={}, deviceName={}, operation={}", |
| | | deviceId, deviceConfig.getDeviceName(), operation); |
| | | |
| | |
| | | // 因为定时器可能会重复调用,或者需要等待任务完成 |
| | | 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); |
| | |
| | | Map<String, Object> logicParams) { |
| | | |
| | | VehicleTask task = new VehicleTask(); |
| | | task.setTaskId(generateTaskId(deviceConfig.getDeviceId())); |
| | | task.setTaskId(generateTaskId(String.valueOf(deviceConfig.getId()))); |
| | | task.setTaskName("大车设备-" + operation); |
| | | task.setOperation(operation); |
| | | |
| | |
| | | Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoFeed); |
| | | |
| | | List<GlassInfo> plannedGlasses = planGlassLoading(glassInfos, vehicleCapacity, glassGap, |
| | | deviceConfig.getDeviceId()); |
| | | String.valueOf(deviceConfig.getId())); |
| | | if (plannedGlasses == null) { |
| | | // 玻璃没有长度时返回null表示错误 |
| | | return DevicePlcVO.OperationResult.builder() |
| | |
| | | // 如果执行成功,更新位置信息到状态 |
| | | if (Boolean.TRUE.equals(result.getSuccess())) { |
| | | VehicleStatus status = statusManager.getOrCreateVehicleStatus( |
| | | deviceConfig.getDeviceId(), deviceConfig.getDeviceName()); |
| | | String.valueOf(deviceConfig.getId()), deviceConfig.getDeviceName()); |
| | | if (positionCode != null || positionValue != null) { |
| | | VehiclePosition position = new VehiclePosition(positionCode, positionValue); |
| | | status.setCurrentPosition(position); |
| | |
| | | |
| | | // 重置时,清除任务并恢复为空闲状态,停止监控 |
| | | if (Boolean.TRUE.equals(result.getSuccess())) { |
| | | statusManager.clearVehicleTask(deviceConfig.getDeviceId()); |
| | | statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE); |
| | | stopStateMonitoring(deviceConfig.getDeviceId()); |
| | | statusManager.clearVehicleTask(String.valueOf(deviceConfig.getId())); |
| | | statusManager.updateVehicleStatus(String.valueOf(deviceConfig.getId()), VehicleState.IDLE); |
| | | stopStateMonitoring(String.valueOf(deviceConfig.getId())); |
| | | handleStopTaskMonitor(deviceConfig); |
| | | handleStopIdleMonitor(deviceConfig); |
| | | updateDeviceOnlineStatus(deviceConfig, true); |
| | | } else { |
| | | // 即便重置失败,也尝试停止内部监控,避免任务取消后仍然反复写PLC |
| | | stopStateMonitoring(deviceConfig.getDeviceId()); |
| | | stopStateMonitoring(String.valueOf(deviceConfig.getId())); |
| | | handleStopTaskMonitor(deviceConfig); |
| | | handleStopIdleMonitor(deviceConfig); |
| | | } |
| | |
| | | |
| | | // 清空后,恢复为空闲状态,停止监控 |
| | | if (Boolean.TRUE.equals(result.getSuccess())) { |
| | | statusManager.clearVehicleTask(deviceConfig.getDeviceId()); |
| | | statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE); |
| | | stopStateMonitoring(deviceConfig.getDeviceId()); |
| | | statusManager.clearVehicleTask(String.valueOf(deviceConfig.getId())); |
| | | statusManager.updateVehicleStatus(String.valueOf(deviceConfig.getId()), VehicleState.IDLE); |
| | | stopStateMonitoring(String.valueOf(deviceConfig.getId())); |
| | | handleStopTaskMonitor(deviceConfig); |
| | | handleStopIdleMonitor(deviceConfig); |
| | | updateDeviceOnlineStatus(deviceConfig, true); |
| | | } else { |
| | | // 写入失败也尝试停止监控,避免任务取消后仍旧运行 |
| | | stopStateMonitoring(deviceConfig.getDeviceId()); |
| | | stopStateMonitoring(String.valueOf(deviceConfig.getId())); |
| | | handleStopTaskMonitor(deviceConfig); |
| | | handleStopIdleMonitor(deviceConfig); |
| | | } |
| | |
| | | deviceStatusService.updateDeviceOnlineStatus(deviceConfig.getId(), status); |
| | | } catch (Exception e) { |
| | | log.warn("同步设备在线状态到数据库失败: deviceId={}, online={}, error={}", |
| | | deviceConfig.getDeviceId(), online, e.getMessage()); |
| | | String.valueOf(deviceConfig.getId()), online, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | |
| | | * 定期检查大车的 state1~6,当检测到 state=1 时自动协调卧转立设备 |
| | | */ |
| | | private void startStateMonitoring(DeviceConfig deviceConfig, Map<String, Object> logicParams) { |
| | | String deviceId = deviceConfig.getDeviceId(); |
| | | String deviceId = String.valueOf(deviceConfig.getId()); |
| | | |
| | | // 如果已经在监控,先停止旧的监控任务 |
| | | stopStateMonitoring(deviceId); |
| | |
| | | * 检查大车状态并协调卧转立设备(内部方法,由监控线程调用) |
| | | */ |
| | | private void checkAndCoordinateState(DeviceConfig deviceConfig) { |
| | | String deviceId = deviceConfig.getDeviceId(); |
| | | String deviceId = String.valueOf(deviceConfig.getId()); |
| | | List<String> alreadyCoordinated = coordinatedStates.get(deviceId); |
| | | if (alreadyCoordinated == null) { |
| | | alreadyCoordinated = new CopyOnWriteArrayList<>(); |
| | |
| | | Map<String, Object> params, |
| | | Map<String, Object> logicParams) { |
| | | |
| | | String deviceId = deviceConfig.getDeviceId(); |
| | | String deviceId = String.valueOf(deviceConfig.getId()); |
| | | |
| | | // 停止旧的监控任务 |
| | | handleStopIdleMonitor(deviceConfig); |
| | |
| | | } |
| | | |
| | | // 检查是否有待处理的进片或出片任务 |
| | | if (plcDynamicDataService != null && s7SerializerProvider != null) { |
| | | EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig); |
| | | if (serializer != null) { |
| | | // 检查进片任务 |
| | | Map<String, Object> mesData = plcDynamicDataService.readPlcData( |
| | | deviceConfig, MES_FIELDS, serializer); |
| | | Integer mesSend = parseInteger(mesData != null ? mesData.get("mesSend") : null); |
| | | |
| | | // 进片和出片共用mesSend字段,只需检查一次 |
| | | // 如果有待处理的任务,不设置plcRequest(等待任务处理) |
| | | if (mesSend != null && mesSend == 1) { |
| | | log.debug("大车空闲监控: deviceId={}, 检测到待处理任务(mesSend=1),不设置plcRequest", deviceId); |
| | | return; |
| | | } |
| | | PlcClient plcClient = getPlcClient(deviceConfig); |
| | | if (plcClient != null) { |
| | | // 检查进片任务 |
| | | Map<String, Object> mesData = plcClient.readData(MES_FIELDS.toArray(new String[0])); |
| | | Integer mesSend = parseInteger(mesData != null ? mesData.get("mesSend") : null); |
| | | |
| | | // 进片和出片共用mesSend字段,只需检查一次 |
| | | // 如果有待处理的任务,不设置plcRequest(等待任务处理) |
| | | if (mesSend != null && mesSend == 1) { |
| | | log.debug("大车空闲监控: deviceId={}, 检测到待处理任务(mesSend=1),不设置plcRequest", deviceId); |
| | | return; |
| | | } |
| | | } |
| | | |
| | |
| | | * 停止空闲监控 |
| | | */ |
| | | private DevicePlcVO.OperationResult handleStopIdleMonitor(DeviceConfig deviceConfig) { |
| | | String deviceId = deviceConfig.getDeviceId(); |
| | | String deviceId = String.valueOf(deviceConfig.getId()); |
| | | ScheduledFuture<?> future = idleMonitoringTasks.remove(deviceId); |
| | | if (future != null && !future.isCancelled()) { |
| | | future.cancel(false); |
| | |
| | | Map<String, Object> params, |
| | | Map<String, Object> logicParams) { |
| | | |
| | | if (plcDynamicDataService == null || s7SerializerProvider == null) { |
| | | String deviceId = String.valueOf(deviceConfig.getId()); |
| | | PlcClient plcClient = getPlcClient(deviceConfig); |
| | | if (plcClient == null) { |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(false) |
| | | .message("PlcDynamicDataService或S7SerializerProvider未注入") |
| | | .build(); |
| | | } |
| | | |
| | | String deviceId = deviceConfig.getDeviceId(); |
| | | EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig); |
| | | if (serializer == null) { |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(false) |
| | | .message("获取PLC序列化器失败") |
| | | .message("获取PLC客户端失败") |
| | | .build(); |
| | | } |
| | | |
| | |
| | | MesTaskInfo existingTask = currentTasks.get(deviceId); |
| | | if (existingTask != null) { |
| | | log.debug("设备已有任务在执行中,跳过检查MES任务: deviceId={}", deviceId); |
| | | // 仍然返回当前任务的玻璃列表,供任务引擎记录/对账本批次 |
| | | List<String> batchIds = new ArrayList<>(); |
| | | if (existingTask.glasses != null) { |
| | | for (GlassTaskInfo g : existingTask.glasses) { |
| | | if (g != null && g.glassId != null && !g.glassId.isEmpty()) { |
| | | batchIds.add(g.glassId); |
| | | } |
| | | } |
| | | } |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(true) |
| | | .message("任务执行中,无需重复检查MES任务") |
| | | .data(Collections.singletonMap("waiting", false)) |
| | | .data(new HashMap<String, Object>() {{ |
| | | put("waiting", false); |
| | | put("batchGlassIds", batchIds); |
| | | }}) |
| | | .build(); |
| | | } |
| | | |
| | | // 读取MES字段(进片和出片共用) |
| | | Map<String, Object> mesData = plcDynamicDataService.readPlcData( |
| | | deviceConfig, MES_FIELDS, serializer); |
| | | Map<String, Object> mesData = plcClient.readData(MES_FIELDS.toArray(new String[0])); |
| | | if (mesData == null || mesData.isEmpty()) { |
| | | log.warn("读取MES字段失败: deviceId={}, mesData为空或null", deviceId); |
| | | return DevicePlcVO.OperationResult.builder() |
| | |
| | | waitData.put("completed", false); |
| | | waitData.put("waiting", true); |
| | | waitData.put("waitingReason", "mesSend=0"); |
| | | waitData.put("batchGlassIds", new ArrayList<>()); |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(true) |
| | | .message("等待MES发送请求(mesSend=0)") |
| | | .data(waitData) |
| | | .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,记录日志 |
| | |
| | | taskInfo.cartime = timeCalc.cartime; |
| | | taskInfo.createdTime = System.currentTimeMillis(); |
| | | taskInfo.isOutbound = isOutbound; |
| | | taskInfo.mesSignature = mesSignature; |
| | | |
| | | // 从配置中读取破损玻璃索引(用于测试场景) |
| | | // 配置格式:brokenGlassIndices: [0, 2] 表示第1个和第3个玻璃应该破损 |
| | |
| | | } |
| | | |
| | | currentTasks.put(deviceId, taskInfo); |
| | | |
| | | // 如果有多设备任务上下文,则记录本次MES下发的玻璃ID列表到上下文,供分批校验使用 |
| | | if (params != null) { |
| | | Object ctxObj = params.get("_taskContext"); |
| | | if (ctxObj instanceof TaskExecutionContext) { |
| | | TaskExecutionContext ctx = (TaskExecutionContext) ctxObj; |
| | | List<String> batchIds = new ArrayList<>(); |
| | | for (GlassTaskInfo g : glasses) { |
| | | if (g != null && g.glassId != null && !g.glassId.isEmpty()) { |
| | | batchIds.add(g.glassId); |
| | | } |
| | | } |
| | | // 1. 记录当前批次的玻璃ID |
| | | ctx.getSharedData().put("currentMesBatchGlassIds", batchIds); |
| | | log.info("记录本次MES批次玻璃列表: deviceId={}, batchIds={}", deviceId, batchIds); |
| | | |
| | | // 2. 初始化总待出片玻璃列表(仅第一次初始化,从任务参数获取) |
| | | if (!ctx.getSharedData().containsKey("initialGlassIds")) { |
| | | // 从任务参数中获取总待出片玻璃ID(核心:总列表来自任务参数,而非MES批次) |
| | | List<String> taskGlassIds = ctx.getParameters().getGlassIds(); |
| | | if (taskGlassIds != null && !taskGlassIds.isEmpty()) { |
| | | ctx.getSharedData().put("initialGlassIds", new ArrayList<>(taskGlassIds)); |
| | | // 初始化已出片列表为空 |
| | | if (!ctx.getSharedData().containsKey("outboundGlassIds")) { |
| | | ctx.getSharedData().put("outboundGlassIds", new ArrayList<>()); |
| | | } |
| | | log.info("初始化总待出片玻璃列表: deviceId={}, taskGlassIds={}", deviceId, taskGlassIds); |
| | | } else { |
| | | log.warn("任务参数中未找到总待出片玻璃ID列表: deviceId={}", deviceId); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 清空plcRequest(表示已接收任务) |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put("plcRequest", 0); |
| | | plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); |
| | | plcClient.writeData(payload); |
| | | log.info("已清空plcRequest=0: deviceId={}", deviceId); |
| | | |
| | | // 更新车辆状态为执行中 |
| | |
| | | String taskType = isOutbound ? "出片" : "进片"; |
| | | String glassIds = glasses.stream() |
| | | .map(g -> g.glassId) |
| | | .collect(java.util.stream.Collectors.joining(",")); |
| | | .collect(Collectors.joining(",")); |
| | | log.info("MES{}任务已创建: deviceId={}, glassCount={}, glassIds=[{}], 起始位置={}格, 目标位置={}格, 距离{}格->{}格, gotime={}ms({}秒), cartime={}ms({}秒)", |
| | | taskType, deviceId, glasses.size(), glassIds, |
| | | firstGlass.startPosition, firstGlass.targetPosition, |
| | |
| | | Map<String, Object> successData = new HashMap<>(); |
| | | successData.put("waiting", false); |
| | | successData.put("taskStarted", true); |
| | | // 将本次MES下发的玻璃ID列表通过返回值带回(任务引擎不再依赖_taskContext写入) |
| | | List<String> batchIdsForReturn = new ArrayList<>(); |
| | | for (GlassTaskInfo g : glasses) { |
| | | if (g != null && g.glassId != null && !g.glassId.isEmpty()) { |
| | | batchIdsForReturn.add(g.glassId); |
| | | } |
| | | } |
| | | successData.put("batchGlassIds", batchIdsForReturn); |
| | | |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(true) |
| | |
| | | */ |
| | | private Integer getCurrentPosition(DeviceConfig deviceConfig, Map<String, Object> logicParams) { |
| | | // 从状态管理器获取 |
| | | VehicleStatus status = statusManager.getVehicleStatus(deviceConfig.getDeviceId()); |
| | | VehicleStatus status = statusManager.getVehicleStatus(String.valueOf(deviceConfig.getId())); |
| | | if (status != null && status.getCurrentPosition() != null) { |
| | | return status.getCurrentPosition().getPositionValue(); |
| | | } |
| | |
| | | Map<String, Object> params, |
| | | Map<String, Object> logicParams) { |
| | | |
| | | String deviceId = deviceConfig.getDeviceId(); |
| | | String deviceId = String.valueOf(deviceConfig.getId()); |
| | | |
| | | // 停止旧的监控任务 |
| | | handleStopTaskMonitor(deviceConfig); |
| | |
| | | MesTaskInfo taskInfo, |
| | | Map<String, Object> logicParams) { |
| | | |
| | | if (plcDynamicDataService == null || s7SerializerProvider == null) { |
| | | return; |
| | | } |
| | | |
| | | String deviceId = deviceConfig.getDeviceId(); |
| | | EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig); |
| | | if (serializer == null) { |
| | | String deviceId = String.valueOf(deviceConfig.getId()); |
| | | PlcClient plcClient = getPlcClient(deviceConfig); |
| | | if (plcClient == null) { |
| | | return; |
| | | } |
| | | |
| | |
| | | // 优先检查是否标记为破损(state=8) |
| | | // 检查任务信息中是否标记了该玻璃为破损 |
| | | if (taskInfo.brokenGlassIndices != null && taskInfo.brokenGlassIndices.contains(i)) { |
| | | updateStateIfNeeded(deviceConfig, serializer, stateValues, stateField, 8, taskInfo); |
| | | updateStateIfNeeded(deviceConfig, plcClient, stateValues, stateField, 8, taskInfo); |
| | | log.info("玻璃标记为破损: deviceId={}, stateField={}, glassIndex={}", |
| | | deviceConfig.getDeviceId(), stateField, i); |
| | | String.valueOf(deviceConfig.getId()), stateField, i); |
| | | continue; |
| | | } |
| | | |
| | | // 检查超时未完成(state=3) |
| | | if (elapsed >= state3TimeoutTime && (currentState == null || currentState < 2)) { |
| | | updateStateIfNeeded(deviceConfig, serializer, stateValues, stateField, 3, taskInfo); |
| | | updateStateIfNeeded(deviceConfig, plcClient, stateValues, stateField, 3, taskInfo); |
| | | log.warn("任务超时未完成: deviceId={}, stateField={}, elapsed={}ms, expectedTime={}ms", |
| | | deviceConfig.getDeviceId(), stateField, elapsed, state2Time); |
| | | String.valueOf(deviceConfig.getId()), stateField, elapsed, state2Time); |
| | | continue; |
| | | } |
| | | |
| | | // 正常状态更新 |
| | | if (elapsed >= state1Time && elapsed < state2Time) { |
| | | // state应该为1(上车完成) |
| | | boolean stateChanged = updateStateIfNeeded(deviceConfig, serializer, stateValues, stateField, 1, taskInfo); |
| | | boolean stateChanged = updateStateIfNeeded(deviceConfig, plcClient, stateValues, stateField, 1, taskInfo); |
| | | if (stateChanged) { |
| | | hasStateOne = true; |
| | | currentStepDesc = "玻璃已上车(state=1),正在运输到目标位置"; |
| | |
| | | } |
| | | } else if (elapsed >= state2Time) { |
| | | // state应该为2(运输完成) |
| | | boolean stateChanged = updateStateIfNeeded(deviceConfig, serializer, stateValues, stateField, 2, taskInfo); |
| | | boolean stateChanged = updateStateIfNeeded(deviceConfig, plcClient, stateValues, stateField, 2, taskInfo); |
| | | if (stateChanged) { |
| | | hasStateTwo = true; |
| | | currentStepDesc = "玻璃已到达目标位置(state=2),等待MES确认"; |
| | |
| | | |
| | | // 检查是否所有state都>=2,如果是则给MES汇报 |
| | | if (elapsed >= state2Time && allStatesCompleted(stateValues, glassCount)) { |
| | | reportToMes(deviceConfig, serializer, taskInfo, logicParams); |
| | | reportToMes(deviceConfig, plcClient, taskInfo, logicParams); |
| | | // 记录MES确认开始等待的时间(只记录一次) |
| | | if (taskInfo.mesConfirmStartTime == null) { |
| | | taskInfo.mesConfirmStartTime = System.currentTimeMillis(); |
| | |
| | | * @return 是否发生了状态变化(从非目标状态变为目标状态) |
| | | */ |
| | | private boolean updateStateIfNeeded(DeviceConfig deviceConfig, |
| | | EnhancedS7Serializer serializer, |
| | | PlcClient plcClient, |
| | | Map<String, Object> currentStates, |
| | | String stateField, |
| | | int targetState, |
| | |
| | | // 注意:如果当前state已经是3(未完成)或8(破损),不再更新 |
| | | if (currentState != null && (currentState == 3 || currentState == 8)) { |
| | | log.debug("任务状态已为异常状态,不再更新: deviceId={}, stateField={}, currentState={}, targetState={}", |
| | | deviceConfig.getDeviceId(), stateField, currentState, targetState); |
| | | String.valueOf(deviceConfig.getId()), stateField, currentState, targetState); |
| | | return false; |
| | | } |
| | | |
| | |
| | | try { |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put(stateField, targetState); |
| | | plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); |
| | | plcClient.writeData(payload); |
| | | |
| | | log.info("任务状态已更新到PLC: deviceId={}, stateField={}, currentState={}, targetState={}", |
| | | deviceConfig.getDeviceId(), stateField, currentState, targetState); |
| | | String.valueOf(deviceConfig.getId()), stateField, currentState, targetState); |
| | | // 返回true表示状态发生了变化 |
| | | return true; |
| | | } catch (Exception e) { |
| | | log.error("写入PLC state字段失败: deviceId={}, stateField={}, targetState={}, error={}", |
| | | deviceConfig.getDeviceId(), stateField, targetState, e.getMessage()); |
| | | String.valueOf(deviceConfig.getId()), stateField, targetState, e.getMessage()); |
| | | return false; |
| | | } |
| | | } |
| | |
| | | * 给MES汇报 |
| | | */ |
| | | private void reportToMes(DeviceConfig deviceConfig, |
| | | EnhancedS7Serializer serializer, |
| | | PlcClient plcClient, |
| | | MesTaskInfo taskInfo, |
| | | Map<String, Object> logicParams) { |
| | | |
| | |
| | | // 设置汇报字 |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put("plcReport", 1); |
| | | plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); |
| | | plcClient.writeData(payload); |
| | | |
| | | String taskType = taskInfo.isOutbound ? "出片" : "进片"; |
| | | String glassIds = taskInfo.glasses.stream() |
| | | .map(g -> g.glassId) |
| | | .collect(java.util.stream.Collectors.joining(",")); |
| | | log.info("已给MES汇报({}任务): deviceId={}, glassCount={}, glassIds=[{}]", |
| | | taskType, deviceConfig.getDeviceId(), taskInfo.glasses.size(), glassIds); |
| | | taskType, String.valueOf(deviceConfig.getId()), taskInfo.glasses.size(), glassIds); |
| | | |
| | | // 多设备任务场景下,不在这里阻塞等待MES确认,由任务引擎定时调用checkMesConfirm |
| | | } catch (Exception e) { |
| | | log.error("给MES汇报异常: deviceId={}", deviceConfig.getDeviceId(), e); |
| | | log.error("给MES汇报异常: deviceId={}", String.valueOf(deviceConfig.getId()), e); |
| | | } |
| | | } |
| | | |
| | |
| | | * 返回OperationResult.data中的 completed 标志表示是否已确认完成 |
| | | */ |
| | | public DevicePlcVO.OperationResult checkMesConfirm(DeviceConfig deviceConfig, |
| | | Map<String, Object> params, |
| | | Map<String, Object> logicParams) { |
| | | if (plcDynamicDataService == null || s7SerializerProvider == null) { |
| | | String deviceId = String.valueOf(deviceConfig.getId()); |
| | | PlcClient plcClient = getPlcClient(deviceConfig); |
| | | if (plcClient == null) { |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(false) |
| | | .message("PlcDynamicDataService或S7SerializerProvider未注入") |
| | | .message("获取PLC客户端失败") |
| | | .build(); |
| | | } |
| | | EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig); |
| | | if (serializer == null) { |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(false) |
| | | .message("获取PLC序列化器失败") |
| | | .build(); |
| | | } |
| | | |
| | | String deviceId = deviceConfig.getDeviceId(); |
| | | MesTaskInfo taskInfo = currentTasks.get(deviceId); |
| | | |
| | | // 如果没有任务记录,优先尝试补偿性地检查一次MES任务(避免因时序问题一直noTask) |
| | | if (taskInfo == null) { |
| | | log.info("检查MES确认时未找到任务记录,尝试补偿检查MES任务: deviceId={}", deviceId); |
| | | try { |
| | | // 关键:补偿检查时也要透传params(包含_taskContext), |
| | | // 否则handleCheckMesTask无法把本批次玻璃ID写入currentMesBatchGlassIds,任务引擎无法累加完成进度 |
| | | Map<String, Object> checkParams = params != null ? params : Collections.emptyMap(); |
| | | DevicePlcVO.OperationResult checkResult = |
| | | handleCheckMesTask(deviceConfig, Collections.emptyMap(), logicParams); |
| | | handleCheckMesTask(deviceConfig, checkParams, logicParams); |
| | | if (Boolean.TRUE.equals(checkResult.getSuccess())) { |
| | | taskInfo = currentTasks.get(deviceId); |
| | | if (taskInfo != null) { |
| | |
| | | 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(); |
| | | } |
| | |
| | | |
| | | // 超时视为任务失败:清理任务状态并停止监控,避免继续累加等待时间 |
| | | try { |
| | | clearTaskStates(deviceConfig, serializer); |
| | | clearTaskStates(deviceConfig, plcClient); |
| | | } catch (Exception e) { |
| | | log.warn("MES确认超时时清空任务状态失败: deviceId={}, error={}", deviceId, e.getMessage()); |
| | | } |
| | | statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.ERROR); |
| | | statusManager.clearVehicleTask(deviceConfig.getDeviceId()); |
| | | currentTasks.remove(deviceConfig.getDeviceId()); |
| | | statusManager.updateVehicleStatus(String.valueOf(deviceConfig.getId()), VehicleState.ERROR); |
| | | statusManager.clearVehicleTask(String.valueOf(deviceConfig.getId())); |
| | | currentTasks.remove(String.valueOf(deviceConfig.getId())); |
| | | handleStopTaskMonitor(deviceConfig); |
| | | |
| | | return DevicePlcVO.OperationResult.builder() |
| | |
| | | .build(); |
| | | } |
| | | |
| | | Object confirmValue = plcDynamicDataService.readPlcField( |
| | | deviceConfig, "mesConfirm", serializer); |
| | | Integer confirm = parseInteger(confirmValue); |
| | | Map<String, Object> confirmData = plcClient.readData("mesConfirm"); |
| | | Integer confirm = parseInteger(confirmData != null ? confirmData.get("mesConfirm") : null); |
| | | boolean completed = confirm != null && confirm == 1; |
| | | data.put("completed", completed); |
| | | |
| | | if (completed) { |
| | | // MES已确认,清空state和汇报字 |
| | | clearTaskStates(deviceConfig, serializer); |
| | | // MES已确认:本次交互完成(不在设备侧判断"是否还有更多玻璃",由任务引擎统一编排) |
| | | clearTaskStates(deviceConfig, plcClient); |
| | | |
| | | // 记录已完成的任务签名,避免MES未复位时被重复拉起 |
| | | if (taskInfo != null && taskInfo.mesSignature != null) { |
| | | lastCompletedMesRecords.put(deviceId, |
| | | new CompletedMesRecord(taskInfo.mesSignature, System.currentTimeMillis())); |
| | | } |
| | | |
| | | // 任务完成,恢复为空闲状态 |
| | | statusManager.updateVehicleStatus( |
| | | deviceConfig.getDeviceId(), VehicleState.IDLE); |
| | | statusManager.clearVehicleTask(deviceConfig.getDeviceId()); |
| | | String.valueOf(deviceConfig.getId()), VehicleState.IDLE); |
| | | statusManager.clearVehicleTask(String.valueOf(deviceConfig.getId())); |
| | | |
| | | // 移除任务记录(如果有) |
| | | currentTasks.remove(deviceConfig.getDeviceId()); |
| | | currentTasks.remove(String.valueOf(deviceConfig.getId())); |
| | | |
| | | // 停止任务监控 |
| | | handleStopTaskMonitor(deviceConfig); |
| | |
| | | // 恢复plcRequest=1(可以接收新任务) |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | payload.put("plcRequest", 1); |
| | | plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); |
| | | plcClient.writeData(payload); |
| | | |
| | | log.info("MES任务已确认完成: deviceId={}", deviceConfig.getDeviceId()); |
| | | String taskType = taskInfo.isOutbound ? "出片" : "进片"; |
| | | log.info("MES任务已确认完成: deviceId={}", String.valueOf(deviceConfig.getId())); |
| | | String taskType = (taskInfo != null && taskInfo.isOutbound) ? "出片" : "进片"; |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(true) |
| | | .message(String.format("%s任务完成:MES已确认(mesConfirm=1),已清空state和汇报字,大车空闲(plcRequest=1),可以等待下次任务", taskType)) |
| | | .message(String.format("%s任务交互完成:MES已确认(mesConfirm=1),已清空state和汇报字,大车空闲(plcRequest=1)", taskType)) |
| | | .data(data) |
| | | .build(); |
| | | } |
| | |
| | | .data(data) |
| | | .build(); |
| | | } catch (Exception e) { |
| | | log.error("检查MES确认状态异常: deviceId={}", deviceConfig.getDeviceId(), e); |
| | | log.error("检查MES确认状态异常: deviceId={}", String.valueOf(deviceConfig.getId()), e); |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(false) |
| | | .message("检查MES确认状态异常: " + e.getMessage()) |
| | |
| | | private DevicePlcVO.OperationResult handleMarkBroken(DeviceConfig deviceConfig, |
| | | Map<String, Object> params, |
| | | Map<String, Object> logicParams) { |
| | | String deviceId = deviceConfig.getDeviceId(); |
| | | String deviceId = String.valueOf(deviceConfig.getId()); |
| | | MesTaskInfo taskInfo = currentTasks.get(deviceId); |
| | | if (taskInfo == null) { |
| | | return DevicePlcVO.OperationResult.builder() |
| | |
| | | } |
| | | |
| | | // 立即写入PLC的state字段 |
| | | EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig); |
| | | if (serializer != null) { |
| | | PlcClient plcClient = getPlcClient(deviceConfig); |
| | | if (plcClient != null) { |
| | | try { |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | for (Integer index : brokenIndices) { |
| | | String stateField = "state" + (index + 1); |
| | | payload.put(stateField, 8); |
| | | } |
| | | plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); |
| | | log.info("已标记玻璃为破损并写入PLC: deviceId={}, brokenIndices={}", |
| | | deviceId, brokenIndices); |
| | | boolean success = plcClient.writeData(payload); |
| | | if (success) { |
| | | log.info("已标记玻璃为破损并写入PLC: deviceId={}, brokenIndices={}", |
| | | deviceId, brokenIndices); |
| | | } else { |
| | | log.error("写入破损状态到PLC失败: deviceId={}, brokenIndices={}", |
| | | deviceId, brokenIndices); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("写入破损状态到PLC失败: deviceId={}, error={}", deviceId, e.getMessage()); |
| | | } |
| | | } else { |
| | | log.error("获取PLC客户端失败,无法写入破损状态: deviceId={}", deviceId); |
| | | } |
| | | |
| | | return DevicePlcVO.OperationResult.builder() |
| | |
| | | /** |
| | | * 清空任务状态 |
| | | */ |
| | | private void clearTaskStates(DeviceConfig deviceConfig, EnhancedS7Serializer serializer) { |
| | | private void clearTaskStates(DeviceConfig deviceConfig, PlcClient plcClient) { |
| | | try { |
| | | Map<String, Object> payload = new HashMap<>(); |
| | | // 清空state1~6 |
| | |
| | | } |
| | | // 清空汇报字 |
| | | payload.put("plcReport", 0); |
| | | plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); |
| | | plcClient.writeData(payload); |
| | | } catch (Exception e) { |
| | | log.error("清空任务状态异常: deviceId={}", deviceConfig.getDeviceId(), e); |
| | | log.error("清空任务状态异常: deviceId={}", String.valueOf(deviceConfig.getId()), e); |
| | | } |
| | | } |
| | | |
| | |
| | | * 停止任务监控 |
| | | */ |
| | | private DevicePlcVO.OperationResult handleStopTaskMonitor(DeviceConfig deviceConfig) { |
| | | String deviceId = deviceConfig.getDeviceId(); |
| | | String deviceId = String.valueOf(deviceConfig.getId()); |
| | | ScheduledFuture<?> future = taskMonitoringTasks.remove(deviceId); |
| | | if (future != null && !future.isCancelled()) { |
| | | future.cancel(false); |
| | |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | private void clearDynamicTaskStates(DeviceConfig deviceConfig) { |
| | | if (plcDynamicDataService == null || s7SerializerProvider == null) { |
| | | return; |
| | | } |
| | | try { |
| | | EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig); |
| | | if (serializer != null) { |
| | | clearTaskStates(deviceConfig, serializer); |
| | | PlcClient plcClient = getPlcClient(deviceConfig); |
| | | if (plcClient != null) { |
| | | clearTaskStates(deviceConfig, plcClient); |
| | | } |
| | | } catch (Exception e) { |
| | | 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(); |
| | | } |
| | | } |
| | | |