package com.mes.interaction.vehicle.handler; import com.mes.device.entity.DeviceConfig; import com.mes.device.entity.DeviceStatus; import com.mes.device.service.DeviceConfigService; import com.mes.device.service.DeviceGroupRelationService; import com.mes.device.service.DevicePlcOperationService; import com.mes.device.service.GlassInfoService; import com.mes.device.service.DeviceStatusService; import com.mes.device.vo.DeviceGroupVO; import com.mes.device.vo.DevicePlcVO; import com.mes.interaction.BaseDeviceLogicHandler; import com.mes.interaction.vehicle.coordination.VehicleStatusManager; import com.mes.interaction.vehicle.model.VehiclePosition; 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.task.model.TaskExecutionContext; import com.mes.interaction.workstation.scanner.handler.HorizontalScannerLogicHandler; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import javax.annotation.PreDestroy; import java.util.*; import java.util.concurrent.*; /** * 大车设备逻辑处理器 * 所有大车设备实例共享这个处理器 * 集成多实例状态管理和协调功能 * * @author huang * @since 2025-11-21 */ @Slf4j @Component public class LoadVehicleLogicHandler extends BaseDeviceLogicHandler { private final GlassInfoService glassInfoService; @Autowired private VehicleStatusManager statusManager; @Autowired(required = false) private DeviceConfigService deviceConfigService; @Autowired(required = false) private DeviceGroupRelationService deviceGroupRelationService; @Autowired(required = false) private DeviceStatusService deviceStatusService; @Autowired(required = false) private PlcDynamicDataService plcDynamicDataService; @Autowired(required = false) private S7SerializerProvider s7SerializerProvider; // MES字段列表(进片和出片共用同一套协议) // 根据协议,使用带数字后缀的字段名(1-6对应6个玻璃位置) // 支持读取所有6个玻璃的信息 private static final List MES_FIELDS = Arrays.asList( "mesSend", "mesConfirm", // 玻璃1-6的ID "mesGlassId1", "mesGlassId2", "mesGlassId3", "mesGlassId4", "mesGlassId5", "mesGlassId6", // 玻璃1-6的尺寸 "mesWidth1", "mesWidth2", "mesWidth3", "mesWidth4", "mesWidth5", "mesWidth6", "mesHeight1", "mesHeight2", "mesHeight3", "mesHeight4", "mesHeight5", "mesHeight6", "mesThickness1", "mesThickness2", "mesThickness3", "mesThickness4", "mesThickness5", "mesThickness6", // 玻璃1-6的起始位置和目标位置 "start1", "start2", "start3", "start4", "start5", "start6", "target1", "target2", "target3", "target4", "target5", "target6" ); // 监控线程池:用于定期检查大车状态并协调卧转立设备 private final ScheduledExecutorService stateMonitorExecutor = Executors.newScheduledThreadPool(5, r -> { Thread t = new Thread(r, "VehicleStateMonitor"); t.setDaemon(true); return t; }); // 空闲监控线程池:用于保持plcRequest=1 private final ScheduledExecutorService idleMonitorExecutor = Executors.newScheduledThreadPool(3, r -> { Thread t = new Thread(r, "VehicleIdleMonitor"); t.setDaemon(true); return t; }); // 任务监控线程池:用于监控任务执行和状态切换 private final ScheduledExecutorService taskMonitorExecutor = Executors.newScheduledThreadPool(5, r -> { Thread t = new Thread(r, "VehicleTaskMonitor"); t.setDaemon(true); return t; }); // 记录正在监控的设备:deviceId -> 监控任务 private final Map> monitoringTasks = new ConcurrentHashMap<>(); // 记录空闲监控任务:deviceId -> 空闲监控任务 private final Map> idleMonitoringTasks = new ConcurrentHashMap<>(); // 记录任务监控任务:deviceId -> 任务监控任务 private final Map> taskMonitoringTasks = new ConcurrentHashMap<>(); // 记录已协调的设备:deviceId -> 已协调的state字段集合(避免重复协调) private final Map> coordinatedStates = new ConcurrentHashMap<>(); // 记录当前任务:deviceId -> 任务信息 private final Map currentTasks = new ConcurrentHashMap<>(); /** * 记录最近一次已完成但MES未复位的任务签名,避免重复拉起 */ private final Map lastCompletedMesRecords = new ConcurrentHashMap<>(); @Autowired public LoadVehicleLogicHandler( DevicePlcOperationService devicePlcOperationService, @Qualifier("deviceGlassInfoService") GlassInfoService glassInfoService) { super(devicePlcOperationService); this.glassInfoService = glassInfoService; } @Override public String getDeviceType() { return DeviceConfig.DeviceType.LOAD_VEHICLE; } @Override protected DevicePlcVO.OperationResult doExecute( DeviceConfig deviceConfig, String operation, Map params, Map logicParams) { String deviceId = deviceConfig.getDeviceId(); log.info("执行大车设备操作: deviceId={}, deviceName={}, operation={}", deviceId, deviceConfig.getDeviceName(), operation); // 1. 检查这个设备实例的状态(对于需要状态检查的操作) if (needsStateCheck(operation)) { VehicleStatus status = statusManager.getVehicleStatus(deviceId); if (status != null && !status.isAvailable()) { // 对于 feedGlass 操作,如果状态是 EXECUTING,允许继续执行 // 因为定时器可能会重复调用,或者需要等待任务完成 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() .success(false) .message(String.format("车辆 %s (%s) 当前状态为 %s,无法执行操作 %s", deviceConfig.getDeviceName(), deviceId, status.getState(), operation)) .build(); } } } // 2. 标记为执行中(对于需要状态管理的操作) if (needsStateManagement(operation)) { statusManager.updateVehicleStatus(deviceId, deviceConfig.getDeviceName(), VehicleState.EXECUTING); // 创建任务信息 VehicleTask task = createVehicleTask(deviceConfig, operation, params, logicParams); statusManager.setVehicleTask(deviceId, task); } try { // 3. 执行原有逻辑 DevicePlcVO.OperationResult result; switch (operation) { case "feedGlass": result = handleFeedGlass(deviceConfig, params, logicParams); break; case "triggerRequest": result = handleTriggerRequest(deviceConfig, params, logicParams); break; case "triggerReport": result = handleTriggerReport(deviceConfig, params, logicParams); break; case "reset": result = handleReset(deviceConfig, params, logicParams); break; case "clearGlass": case "clearPlc": case "clear": result = handleClearGlass(deviceConfig, params, logicParams); break; case "checkStateAndCoordinate": result = handleCheckStateAndCoordinate(deviceConfig, params, logicParams); break; case "startIdleMonitor": result = handleStartIdleMonitor(deviceConfig, params, logicParams); break; case "stopIdleMonitor": result = handleStopIdleMonitor(deviceConfig); break; case "checkMesTask": result = handleCheckMesTask(deviceConfig, params, logicParams); break; case "checkMesOutboundTask": // 出片任务也使用同一套协议,通过handleCheckMesTask处理 result = handleCheckMesTask(deviceConfig, params, logicParams); break; case "startTaskMonitor": result = handleStartTaskMonitor(deviceConfig, params, logicParams); break; case "stopTaskMonitor": result = handleStopTaskMonitor(deviceConfig); break; case "setOnlineState": result = handleSetOnlineState(deviceConfig, params, logicParams); break; case "checkMesConfirm": result = checkMesConfirm(deviceConfig, logicParams); break; case "markBroken": result = handleMarkBroken(deviceConfig, params, logicParams); break; default: log.warn("不支持的操作类型: {}", operation); result = DevicePlcVO.OperationResult.builder() .success(false) .message("不支持的操作: " + operation) .build(); } return result; } catch (Exception e) { log.error("执行大车设备操作异常: deviceId={}, operation={}", deviceId, operation, e); // 发生异常时,将状态设置为错误 if (needsStateManagement(operation)) { statusManager.updateVehicleStatus(deviceId, VehicleState.ERROR); } throw e; } finally { // 4. 执行完成后恢复为空闲状态(对于需要状态管理的操作) if (needsStateManagement(operation)) { // 注意:这里不立即设置为IDLE,因为实际执行可能需要时间 // 真正的状态更新应该在任务完成后通过回调或状态查询来更新 // 这里先保持EXECUTING状态,等待外部确认完成后再更新 log.debug("操作执行完成,保持执行中状态,等待外部确认: deviceId={}", deviceId); } } } /** * 判断操作是否需要状态检查 */ private boolean needsStateCheck(String operation) { // 所有操作都需要检查状态,除了查询类操作和特定内部检查 if ("query".equals(operation) || "status".equals(operation)) { return false; } if ("checkMesConfirm".equals(operation)) { return false; } return true; } /** * 判断操作是否需要状态管理 */ private boolean needsStateManagement(String operation) { // feedGlass 需要状态管理,其他操作根据实际情况 return "feedGlass".equals(operation); } /** * 创建车辆任务信息 */ private VehicleTask createVehicleTask( DeviceConfig deviceConfig, String operation, Map params, Map logicParams) { VehicleTask task = new VehicleTask(); task.setTaskId(generateTaskId(deviceConfig.getDeviceId())); task.setTaskName("大车设备-" + operation); task.setOperation(operation); // 从参数中提取位置信息 String positionCode = (String) params.get("positionCode"); Integer positionValue = (Integer) params.get("positionValue"); if (positionCode != null || positionValue != null) { VehiclePosition position = new VehiclePosition(positionCode, positionValue); task.getPlannedPath().setStartPosition(position); task.getPlannedPath().setEndPosition(position); } // 从配置中获取速度(如果有) Object speedObj = logicParams != null ? logicParams.get("vehicleSpeed") : null; Double speed = null; if (speedObj != null) { if (speedObj instanceof Double) { speed = (Double) speedObj; } else if (speedObj instanceof Integer) { speed = ((Integer) speedObj).doubleValue(); } else if (speedObj instanceof Number) { speed = ((Number) speedObj).doubleValue(); } else { try { speed = Double.parseDouble(String.valueOf(speedObj)); } catch (NumberFormatException e) { log.warn("无法解析vehicleSpeed: {}", speedObj); } } } if (speed != null) { task.setSpeed(speed); task.calculateEstimatedEndTime(); } task.setParameters(new HashMap<>(params)); return task; } /** * 生成任务ID */ private String generateTaskId(String deviceId) { return "TASK_" + deviceId + "_" + System.currentTimeMillis(); } /** * 处理玻璃上料操作 */ private DevicePlcVO.OperationResult handleFeedGlass( DeviceConfig deviceConfig, Map params, Map logicParams) { // 从逻辑参数中获取配置(从 extraParams.deviceLogic 读取) Integer vehicleCapacity = getLogicParam(logicParams, "vehicleCapacity", 6000); Integer glassGap = getLogicParam(logicParams, "glassGap", 200); // 玻璃之间的物理间隔(mm),默认200mm Boolean autoFeed = getLogicParam(logicParams, "autoFeed", true); // 自动上料:是否自动触发上料请求(写入plcRequest=1) // 从运行时参数中获取数据(从接口调用时传入) List glassInfos = extractGlassInfos(params); if (glassInfos.isEmpty()) { return DevicePlcVO.OperationResult.builder() .success(false) .message("未提供有效的玻璃信息") .build(); } String positionCode = (String) params.get("positionCode"); Integer positionValue = (Integer) params.get("positionValue"); Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoFeed); List plannedGlasses = planGlassLoading(glassInfos, vehicleCapacity, glassGap, deviceConfig.getDeviceId()); if (plannedGlasses == null) { // 玻璃没有长度时返回null表示错误 return DevicePlcVO.OperationResult.builder() .success(false) .message("玻璃信息缺少长度数据,无法进行容量计算。请检查MES程序是否正确提供玻璃长度。") .build(); } if (plannedGlasses.isEmpty()) { // 装不下,通知卧转立扫码设备暂停 notifyScannerPause(params, true); log.warn("大车设备装不下,已通知卧转立扫码暂停: deviceId={}, glassCount={}, vehicleCapacity={}", deviceConfig.getId(), glassInfos.size(), vehicleCapacity); return DevicePlcVO.OperationResult.builder() .success(false) .message("当前玻璃尺寸超出车辆容量,无法装载,已通知卧转立扫码暂停") .build(); } // 装得下,确保卧转立扫码继续运行 notifyScannerPause(params, false); // 继续执行原有逻辑 // 构建写入数据 Map payload = new HashMap<>(); // 写入玻璃ID int plcSlots = Math.min(plannedGlasses.size(), 6); for (int i = 0; i < plcSlots; i++) { String fieldName = "plcGlassId" + (i + 1); payload.put(fieldName, plannedGlasses.get(i).getGlassId()); } payload.put("plcGlassCount", plcSlots); // 写入位置信息:PLC侧期望的是 MES 编号(如1001/1002),而不是位置映射后的格子值 Integer plcPosition = null; if (positionValue != null) { // 如果调用方直接传了数值,则认为这是MES编号,直接写入 plcPosition = positionValue; } else if (positionCode != null) { // 尝试将位置代码解析为数字(例如 "900" -> 900) try { plcPosition = Integer.parseInt(positionCode.trim()); } catch (NumberFormatException ignore) { // 非数字编码时,不写入inPosition,由PLC或后续逻辑自行处理 } } if (plcPosition != null) { payload.put("inPosition", plcPosition); } // 自动触发请求字 if (triggerRequest != null && triggerRequest) { payload.put("plcRequest", 1); } String operationName = "大车设备-玻璃上料"; if (positionCode != null) { operationName += "(" + positionCode + ")"; } log.info("大车设备玻璃上料: deviceId={}, glassCount={}, position={}, plannedGlassIds={}", deviceConfig.getId(), plcSlots, positionCode, plannedGlasses); // 写入PLC,让大车开始装玻璃 DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields( deviceConfig.getId(), payload, operationName); // 如果执行成功,更新位置信息到状态 if (Boolean.TRUE.equals(result.getSuccess())) { VehicleStatus status = statusManager.getOrCreateVehicleStatus( deviceConfig.getDeviceId(), deviceConfig.getDeviceName()); if (positionCode != null || positionValue != null) { VehiclePosition position = new VehiclePosition(positionCode, positionValue); status.setCurrentPosition(position); } // 仅在“非多设备任务”场景下,才启动大车自身的自动状态监控和 MES 任务监控 boolean inMultiDeviceTask = params != null && params.containsKey("_taskContext"); if (!inMultiDeviceTask) { // 启动自动状态监控,当 state=1 时自动协调卧转立设备 startStateMonitoring(deviceConfig, logicParams); // 从 PLC/MES 创建正式任务并启动监控的逻辑,保留给独立 MES 场景使用 // 多设备任务场景下,这部分交由 TaskExecutionEngine 统一编排 } } return result; } /** * 处理触发请求操作 */ private DevicePlcVO.OperationResult handleTriggerRequest( DeviceConfig deviceConfig, Map params, Map logicParams) { Map payload = new HashMap<>(); payload.put("plcRequest", 1); log.info("大车设备触发请求: deviceId={}", deviceConfig.getId()); return devicePlcOperationService.writeFields( deviceConfig.getId(), payload, "大车设备-触发请求" ); } /** * 处理触发汇报操作 */ private DevicePlcVO.OperationResult handleTriggerReport( DeviceConfig deviceConfig, Map params, Map logicParams) { Map payload = new HashMap<>(); payload.put("plcReport", 1); log.info("大车设备触发汇报: deviceId={}", deviceConfig.getId()); return devicePlcOperationService.writeFields( deviceConfig.getId(), payload, "大车设备-触发汇报" ); } /** * 处理重置操作 */ private DevicePlcVO.OperationResult handleReset( DeviceConfig deviceConfig, Map params, Map logicParams) { Map payload = new HashMap<>(); payload.put("plcRequest", 0); payload.put("plcReport", 0); // 清空state1~6,避免遗留状态导致误判 for (int i = 1; i <= 6; i++) { payload.put("state" + i, 0); } payload.put("onlineState", Boolean.TRUE); log.info("大车设备重置: deviceId={}", deviceConfig.getId()); DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields( deviceConfig.getId(), payload, "大车设备-重置" ); // 重置时,清除任务并恢复为空闲状态,停止监控 if (Boolean.TRUE.equals(result.getSuccess())) { statusManager.clearVehicleTask(deviceConfig.getDeviceId()); statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE); stopStateMonitoring(deviceConfig.getDeviceId()); handleStopTaskMonitor(deviceConfig); handleStopIdleMonitor(deviceConfig); updateDeviceOnlineStatus(deviceConfig, true); } else { // 即便重置失败,也尝试停止内部监控,避免任务取消后仍然反复写PLC stopStateMonitoring(deviceConfig.getDeviceId()); handleStopTaskMonitor(deviceConfig); handleStopIdleMonitor(deviceConfig); } return result; } /** * 设置联机状态 * @param deviceConfig 设备配置 * @param params 参数,可包含 onlineState(1=联机,0=脱机) * @param logicParams 逻辑参数 * @return 操作结果 */ private DevicePlcVO.OperationResult handleSetOnlineState( DeviceConfig deviceConfig, Map params, Map logicParams) { // 从参数中获取联机状态值,默认为true(联机) boolean onlineState = true; if (params != null && params.containsKey("onlineState")) { Object stateObj = params.get("onlineState"); if (stateObj instanceof Boolean) { onlineState = (Boolean) stateObj; } else if (stateObj instanceof Number) { onlineState = ((Number) stateObj).intValue() != 0; } else if (stateObj instanceof String) { try { String str = ((String) stateObj).trim(); if ("true".equalsIgnoreCase(str)) { onlineState = true; } else if ("false".equalsIgnoreCase(str)) { onlineState = false; } else { onlineState = Integer.parseInt(str) != 0; } } catch (NumberFormatException e) { log.warn("解析onlineState失败,使用默认值true: deviceId={}, value={}", deviceConfig.getId(), stateObj); } } } Map payload = new HashMap<>(); payload.put("onlineState", onlineState); String stateText = onlineState ? "联机" : "脱机"; log.info("大车设备设置联机状态: deviceId={}, onlineState={} ({})", deviceConfig.getId(), onlineState, stateText); DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields( deviceConfig.getId(), payload, "大车设备-设置联机状态(" + stateText + ")" ); if (Boolean.TRUE.equals(result.getSuccess())) { updateDeviceOnlineStatus(deviceConfig, onlineState); } return result; } /** * 清空PLC中的玻璃数据 */ private DevicePlcVO.OperationResult handleClearGlass( DeviceConfig deviceConfig, Map params, Map logicParams) { Map payload = new HashMap<>(); int slotCount = getLogicParam(logicParams, "glassSlotCount", 6); if (slotCount <= 0) { slotCount = 6; } List slotFields = resolveGlassSlotFields(logicParams, slotCount); for (String field : slotFields) { payload.put(field, ""); } payload.put("plcRequest", 0); payload.put("plcReport", 0); payload.put("onlineState", Boolean.TRUE); if (params != null && params.containsKey("positionValue")) { payload.put("inPosition", params.get("positionValue")); } else if (params != null && Boolean.TRUE.equals(params.get("clearPosition"))) { payload.put("inPosition", 0); } log.info("清空大车设备PLC玻璃数据: deviceId={}, clearedSlots={}", deviceConfig.getId(), slotFields.size()); DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields( deviceConfig.getId(), payload, "大车设备-清空玻璃数据" ); // 同步清空state1~6等动态字段(在动态数据区中) clearDynamicTaskStates(deviceConfig); // 清空后,恢复为空闲状态,停止监控 if (Boolean.TRUE.equals(result.getSuccess())) { statusManager.clearVehicleTask(deviceConfig.getDeviceId()); statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE); stopStateMonitoring(deviceConfig.getDeviceId()); handleStopTaskMonitor(deviceConfig); handleStopIdleMonitor(deviceConfig); updateDeviceOnlineStatus(deviceConfig, true); } else { // 写入失败也尝试停止监控,避免任务取消后仍旧运行 stopStateMonitoring(deviceConfig.getDeviceId()); handleStopTaskMonitor(deviceConfig); handleStopIdleMonitor(deviceConfig); } return result; } private void updateDeviceOnlineStatus(DeviceConfig deviceConfig, boolean online) { if (deviceStatusService == null || deviceConfig == null || deviceConfig.getId() == null) { return; } try { String status = online ? DeviceStatus.Status.ONLINE : DeviceStatus.Status.OFFLINE; deviceStatusService.updateDeviceOnlineStatus(deviceConfig.getId(), status); } catch (Exception e) { log.warn("同步设备在线状态到数据库失败: deviceId={}, online={}, error={}", deviceConfig.getDeviceId(), online, e.getMessage()); } } private List resolveGlassSlotFields(Map logicParams, int fallbackCount) { List fields = new ArrayList<>(); if (logicParams != null) { Object slotFieldConfig = logicParams.get("glassSlotFields"); if (slotFieldConfig instanceof List) { List configured = (List) slotFieldConfig; for (Object item : configured) { if (item != null) { String fieldName = String.valueOf(item).trim(); if (!fieldName.isEmpty()) { fields.add(fieldName); } } } } } if (fields.isEmpty()) { for (int i = 1; i <= fallbackCount; i++) { fields.add("plcGlassId" + i); } } return fields; } @Override public String validateLogicParams(DeviceConfig deviceConfig) { Map logicParams = parseLogicParams(deviceConfig); // 验证必填参数 Integer vehicleCapacity = getLogicParam(logicParams, "vehicleCapacity", null); if (vehicleCapacity == null || vehicleCapacity <= 0) { return "车辆容量(vehicleCapacity)必须大于0"; } Integer glassGap = getLogicParam(logicParams, "glassGap", null); if (glassGap != null && glassGap < 0) { return "玻璃间隔(glassGap)不能为负数"; } return null; // 验证通过 } @Override public String getDefaultLogicParams() { Map defaultParams = new HashMap<>(); defaultParams.put("vehicleCapacity", 6000); defaultParams.put("glassGap", 200); // 玻璃之间的物理间隔(mm),默认200mm defaultParams.put("autoFeed", true); // 自动上料:是否自动触发上料请求(写入plcRequest=1),默认true defaultParams.put("maxRetryCount", 5); // MES任务相关配置 defaultParams.put("vehicleSpeed", 1.0); // 车辆速度(格/秒,grid/s),默认1格/秒 defaultParams.put("minRange", 1); // 最小运动距离(格子) defaultParams.put("maxRange", 100); // 最大运动距离(格子),例如100格 defaultParams.put("homePosition", 0); // 初始位置(格子) defaultParams.put("idleMonitorIntervalMs", 2000); // 空闲监控间隔(毫秒) defaultParams.put("taskMonitorIntervalMs", 1000); // 任务监控间隔(毫秒) defaultParams.put("mesConfirmTimeoutMs", 30000); // MES确认超时(毫秒) // gridPositionMapping: 格子编号到位置的映射表(可选) // 如果不配置,则格子编号直接作为位置值 Map gridPositionMapping = new HashMap<>(); defaultParams.put("gridPositionMapping", gridPositionMapping); Map positionMapping = new HashMap<>(); positionMapping.put("POS1", 1); positionMapping.put("POS2", 2); defaultParams.put("positionMapping", positionMapping); try { return objectMapper.writeValueAsString(defaultParams); } catch (Exception e) { log.error("生成默认逻辑参数失败", e); return "{}"; } } @SuppressWarnings("unchecked") private List extractGlassInfos(Map params) { List result = new ArrayList<>(); Object rawGlassInfos = params.get("glassInfos"); if (rawGlassInfos instanceof List) { List list = (List) rawGlassInfos; for (Object item : list) { GlassInfo info = convertToGlassInfo(item); if (info != null) { result.add(info); } } } if (result.isEmpty()) { List glassIds = (List) params.get("glassIds"); if (glassIds != null && !glassIds.isEmpty()) { // 从数据库查询玻璃尺寸 Map lengthMap = glassInfoService.getGlassLengthMap(glassIds); for (String glassId : glassIds) { Integer length = lengthMap.get(glassId); result.add(new GlassInfo(glassId, length)); } log.debug("从数据库查询玻璃尺寸: glassIds={}, lengthMap={}", glassIds, lengthMap); } } return result; } private GlassInfo convertToGlassInfo(Object source) { if (source instanceof GlassInfo) { return (GlassInfo) source; } if (source instanceof Map) { Map map = (Map) source; Object id = map.get("glassId"); if (id == null) { id = map.get("id"); } if (id == null) { return null; } Integer length = parseLength(map.get("length")); if (length == null) { length = parseLength(map.get("size")); } return new GlassInfo(String.valueOf(id), length); } if (source instanceof String) { return new GlassInfo((String) source, null); } return null; } private Integer parseLength(Object value) { if (value instanceof Number) { return ((Number) value).intValue(); } if (value instanceof String) { try { return Integer.parseInt((String) value); } catch (NumberFormatException ignored) { } } return null; } /** * 规划玻璃装载 * @param source 源玻璃列表 * @param vehicleCapacity 车辆容量(mm) * @param glassGap 玻璃之间的物理间隔(mm),默认200mm * @param deviceId 设备ID(用于日志) * @return 规划后的玻璃列表,如果玻璃没有长度则返回null(MES未提供长度数据) */ private List planGlassLoading(List source, int vehicleCapacity, int glassGap, String deviceId) { List planned = new ArrayList<>(); int usedLength = 0; int capacity = Math.max(vehicleCapacity, 1); int gap = Math.max(glassGap, 0); // 确保间隔不为负数 for (GlassInfo info : source) { Integer glassLength = info.getLength(); // 如果玻璃没有长度,说明MES未提供,直接报错 if (glassLength == null || glassLength <= 0) { log.error("玻璃[{}]缺少长度数据,无法进行容量计算。deviceId={},请检查MES程序是否正确提供玻璃长度。", info.getGlassId(), deviceId); return null; } int length = glassLength; if (planned.isEmpty()) { // 第一块玻璃,不需要间隙 planned.add(info.withLength(length)); usedLength = length; continue; } // 后续玻璃需要考虑间隙:玻璃长度 + 间隙 int requiredLength = length + gap; if (usedLength + requiredLength <= capacity) { planned.add(info.withLength(length)); usedLength += requiredLength; // 包含间隙 } else { // 装不下了,停止添加 break; } } log.debug("玻璃装载规划: deviceId={}, total={}, planned={}, usedLength={}, capacity={}, glassGap={}", deviceId, source.size(), planned.size(), usedLength, capacity, gap); return planned; } /** * 启动状态监控 * 定期检查大车的 state1~6,当检测到 state=1 时自动协调卧转立设备 */ private void startStateMonitoring(DeviceConfig deviceConfig, Map logicParams) { String deviceId = deviceConfig.getDeviceId(); // 如果已经在监控,先停止旧的监控任务 stopStateMonitoring(deviceId); // 获取监控配置 Integer monitorIntervalMs = getLogicParam(logicParams, "stateMonitorIntervalMs", 1000); Integer monitorTimeoutMs = getLogicParam(logicParams, "stateMonitorTimeoutMs", 300000); if (monitorIntervalMs == null || monitorIntervalMs <= 0) { monitorIntervalMs = 1000; } if (monitorTimeoutMs == null || monitorTimeoutMs <= 0) { monitorTimeoutMs = 300000; } final int finalMonitorIntervalMs = monitorIntervalMs; final int finalMonitorTimeoutMs = monitorTimeoutMs; // 初始化已协调状态记录 coordinatedStates.put(deviceId, new CopyOnWriteArrayList<>()); // 记录监控开始时间 final long startTime = System.currentTimeMillis(); // 启动监控任务 ScheduledFuture future = stateMonitorExecutor.scheduleWithFixedDelay(() -> { try { // 检查超时 if (System.currentTimeMillis() - startTime > finalMonitorTimeoutMs) { log.info("大车状态监控超时,停止监控: deviceId={}, timeout={}ms", deviceId, finalMonitorTimeoutMs); stopStateMonitoring(deviceId); return; } // 检查车辆是否还在执行任务 VehicleStatus status = statusManager.getVehicleStatus(deviceId); if (status == null || status.getState() != VehicleState.EXECUTING) { log.debug("大车状态已改变,停止监控: deviceId={}, state={}", deviceId, status != null ? status.getState() : "null"); stopStateMonitoring(deviceId); return; } // 执行状态检查和协调 checkAndCoordinateState(deviceConfig); } catch (Exception e) { log.error("大车状态监控异常: deviceId={}", deviceId, e); } }, finalMonitorIntervalMs, finalMonitorIntervalMs, TimeUnit.MILLISECONDS); monitoringTasks.put(deviceId, future); log.info("已启动大车状态监控: deviceId={}, interval={}ms, timeout={}ms", deviceId, finalMonitorIntervalMs, finalMonitorTimeoutMs); } /** * 停止状态监控 */ private void stopStateMonitoring(String deviceId) { ScheduledFuture future = monitoringTasks.remove(deviceId); if (future != null && !future.isCancelled()) { future.cancel(false); log.debug("已停止大车状态监控: deviceId={}", deviceId); } coordinatedStates.remove(deviceId); } /** * 检查大车状态并协调卧转立设备(内部方法,由监控线程调用) */ private void checkAndCoordinateState(DeviceConfig deviceConfig) { String deviceId = deviceConfig.getDeviceId(); List alreadyCoordinated = coordinatedStates.get(deviceId); if (alreadyCoordinated == null) { alreadyCoordinated = new CopyOnWriteArrayList<>(); coordinatedStates.put(deviceId, alreadyCoordinated); } try { // 读取 state1~6 字段 List stateFields = Arrays.asList("state1", "state2", "state3", "state4", "state5", "state6"); Map stateValues = new HashMap<>(); // 从 PLC 读取状态字段 DevicePlcVO.StatusInfo statusInfo = devicePlcOperationService.readStatus(deviceConfig.getId()); if (statusInfo == null || statusInfo.getFieldValues() == null) { return; } for (String field : stateFields) { Object value = statusInfo.getFieldValues().get(field); if (value != null) { stateValues.put(field, value); } } // 检查是否有任何一个 state 为 1,且尚未协调过 List newStateOneFields = new ArrayList<>(); for (String field : stateFields) { Integer stateValue = parseInteger(stateValues.get(field)); if (stateValue != null && stateValue == 1 && !alreadyCoordinated.contains(field)) { newStateOneFields.add(field); } } if (newStateOneFields.isEmpty()) { return; // 没有新的 state=1,无需协调 } log.info("检测到大车新的 state=1: deviceId={}, stateFields={}", deviceId, newStateOneFields); // 查找同组的卧转立设备 List transferDevices = findTransferDevicesInSameGroup(deviceConfig); if (transferDevices.isEmpty()) { log.warn("未找到同组的卧转立设备: deviceId={}", deviceId); // 即使找不到设备,也标记为已协调,避免重复检查 alreadyCoordinated.addAll(newStateOneFields); return; } // 将每个卧转立设备的 plcRequest 置 0 boolean allSuccess = true; for (DeviceConfig transferDevice : transferDevices) { Map payload = new HashMap<>(); payload.put("plcRequest", 0); DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields( transferDevice.getId(), payload, "大车自动协调-清空卧转立请求" ); if (Boolean.TRUE.equals(result.getSuccess())) { log.info("已自动清空卧转立设备 plcRequest: vehicleDeviceId={}, transferDeviceId={}, transferDeviceName={}, stateFields={}", deviceId, transferDevice.getId(), transferDevice.getDeviceName(), newStateOneFields); } else { log.warn("自动清空卧转立设备 plcRequest 失败: vehicleDeviceId={}, transferDeviceId={}, message={}", deviceId, transferDevice.getId(), result.getMessage()); allSuccess = false; } } // 标记为已协调(无论成功与否,避免重复协调) if (allSuccess) { alreadyCoordinated.addAll(newStateOneFields); log.info("大车状态协调完成: deviceId={}, coordinatedStateFields={}", deviceId, newStateOneFields); } } catch (Exception e) { log.error("检查大车状态并协调卧转立设备异常: deviceId={}", deviceId, e); } } /** * 检查大车状态并协调卧转立设备(手动调用接口) * 当 state1~6 中任何一个变为 1(上车完成)时,将同组卧转立设备的 plcRequest 置 0 */ private DevicePlcVO.OperationResult handleCheckStateAndCoordinate( DeviceConfig deviceConfig, Map params, Map logicParams) { try { // 读取 state1~6 字段 List stateFields = Arrays.asList("state1", "state2", "state3", "state4", "state5", "state6"); Map stateValues = new HashMap<>(); // 从 PLC 读取状态字段 DevicePlcVO.StatusInfo statusInfo = devicePlcOperationService.readStatus(deviceConfig.getId()); if (statusInfo != null && statusInfo.getFieldValues() != null) { for (String field : stateFields) { Object value = statusInfo.getFieldValues().get(field); if (value != null) { stateValues.put(field, value); } } } // 检查是否有任何一个 state 为 1 boolean hasStateOne = false; List stateOneFields = new ArrayList<>(); for (String field : stateFields) { Integer stateValue = parseInteger(stateValues.get(field)); if (stateValue != null && stateValue == 1) { hasStateOne = true; stateOneFields.add(field); } } if (!hasStateOne) { return DevicePlcVO.OperationResult.builder() .success(true) .message("当前无 state=1 的状态,无需协调") .build(); } log.info("检测到大车 state=1: deviceId={}, stateFields={}", deviceConfig.getId(), stateOneFields); // 查找同组的卧转立设备 List transferDevices = findTransferDevicesInSameGroup(deviceConfig); if (transferDevices.isEmpty()) { log.warn("未找到同组的卧转立设备: deviceId={}", deviceConfig.getId()); return DevicePlcVO.OperationResult.builder() .success(true) .message("未找到同组的卧转立设备,跳过协调") .build(); } // 将每个卧转立设备的 plcRequest 置 0 List results = new ArrayList<>(); for (DeviceConfig transferDevice : transferDevices) { Map payload = new HashMap<>(); payload.put("plcRequest", 0); DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields( transferDevice.getId(), payload, "大车协调-清空卧转立请求" ); results.add(result); if (Boolean.TRUE.equals(result.getSuccess())) { log.info("已清空卧转立设备 plcRequest: transferDeviceId={}, transferDeviceName={}", transferDevice.getId(), transferDevice.getDeviceName()); } else { log.warn("清空卧转立设备 plcRequest 失败: transferDeviceId={}, message={}", transferDevice.getId(), result.getMessage()); } } boolean allSuccess = results.stream() .allMatch(r -> Boolean.TRUE.equals(r.getSuccess())); return DevicePlcVO.OperationResult.builder() .success(allSuccess) .message(String.format("已协调 %d 个卧转立设备,成功: %d", transferDevices.size(), results.stream().mapToInt(r -> Boolean.TRUE.equals(r.getSuccess()) ? 1 : 0).sum())) .build(); } catch (Exception e) { log.error("检查大车状态并协调卧转立设备异常: deviceId={}", deviceConfig.getId(), e); return DevicePlcVO.OperationResult.builder() .success(false) .message("协调异常: " + e.getMessage()) .build(); } } /** * 查找同组内的卧转立设备 */ private List findTransferDevicesInSameGroup(DeviceConfig vehicleDevice) { List transferDevices = new ArrayList<>(); if (deviceGroupRelationService == null || deviceConfigService == null) { log.warn("DeviceGroupRelationService 或 DeviceConfigService 未注入,无法查找同组设备"); return transferDevices; } try { // 获取大车所属的设备组 List groups = deviceGroupRelationService.getDeviceGroups(vehicleDevice.getId()); if (groups.isEmpty()) { log.debug("大车设备未加入任何设备组: deviceId={}", vehicleDevice.getId()); return transferDevices; } // 遍历所有设备组,查找卧转立设备 for (DeviceGroupVO.GroupInfo group : groups) { List groupDevices = deviceGroupRelationService.getGroupDevices(group.getId()); for (DeviceGroupVO.DeviceInfo deviceInfo : groupDevices) { // 检查是否为卧转立设备 if (DeviceConfig.DeviceType.WORKSTATION_TRANSFER.equals(deviceInfo.getDeviceType())) { DeviceConfig transferDevice = deviceConfigService.getDeviceById(deviceInfo.getId()); if (transferDevice != null) { transferDevices.add(transferDevice); } } } } log.debug("找到同组卧转立设备: vehicleDeviceId={}, transferDeviceCount={}", vehicleDevice.getId(), transferDevices.size()); } catch (Exception e) { log.error("查找同组卧转立设备异常: vehicleDeviceId={}", vehicleDevice.getId(), e); } return transferDevices; } private Integer parseInteger(Object value) { if (value instanceof Number) { return ((Number) value).intValue(); } if (value == null) { return null; } try { return Integer.parseInt(String.valueOf(value)); } catch (NumberFormatException e) { return null; } } private String parseString(Object value) { return value == null ? null : String.valueOf(value).trim(); } private static class GlassInfo { private final String glassId; private final Integer length; GlassInfo(String glassId, Integer length) { this.glassId = glassId; this.length = length; } public String getGlassId() { return glassId; } public Integer getLength() { return length; } public GlassInfo withLength(Integer newLength) { return new GlassInfo(this.glassId, newLength); } @Override public String toString() { return glassId; } @Override public int hashCode() { return Objects.hash(glassId, length); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; GlassInfo other = (GlassInfo) obj; return Objects.equals(glassId, other.glassId) && Objects.equals(length, other.length); } } /** * 启动空闲监控(没有任务时,plcRequest一直保持为1) */ private DevicePlcVO.OperationResult handleStartIdleMonitor( DeviceConfig deviceConfig, Map params, Map logicParams) { String deviceId = deviceConfig.getDeviceId(); // 停止旧的监控任务 handleStopIdleMonitor(deviceConfig); // 获取监控间隔 Integer monitorIntervalMs = getLogicParam(logicParams, "idleMonitorIntervalMs", 2000); // 默认2秒 // 启动监控任务 ScheduledFuture future = idleMonitorExecutor.scheduleWithFixedDelay(() -> { try { // 检查是否有任务在执行 VehicleStatus status = statusManager.getVehicleStatus(deviceId); if (status != null && status.getState() == VehicleState.EXECUTING) { // 有任务在执行,不设置plcRequest return; } // 检查是否有待处理的进片或出片任务 if (plcDynamicDataService != null && s7SerializerProvider != null) { EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig); if (serializer != null) { // 检查进片任务 Map 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; } } } // 没有任务,保持plcRequest=1 Map payload = new HashMap<>(); payload.put("plcRequest", 1); devicePlcOperationService.writeFields(deviceConfig.getId(), payload, "大车空闲监控-保持请求"); log.debug("大车空闲监控: deviceId={}, 保持plcRequest=1", deviceId); } catch (Exception e) { log.error("大车空闲监控异常: deviceId={}", deviceId, e); } }, monitorIntervalMs, monitorIntervalMs, TimeUnit.MILLISECONDS); idleMonitoringTasks.put(deviceId, future); log.info("已启动大车空闲监控: deviceId={}, interval={}ms", deviceId, monitorIntervalMs); return DevicePlcVO.OperationResult.builder() .success(true) .message("空闲监控已启动") .build(); } /** * 停止空闲监控 */ private DevicePlcVO.OperationResult handleStopIdleMonitor(DeviceConfig deviceConfig) { String deviceId = deviceConfig.getDeviceId(); ScheduledFuture future = idleMonitoringTasks.remove(deviceId); if (future != null && !future.isCancelled()) { future.cancel(false); log.info("已停止大车空闲监控: deviceId={}", deviceId); } return DevicePlcVO.OperationResult.builder() .success(true) .message("空闲监控已停止") .build(); } /** * 检查MES任务(当mesSend=1时,读取MES参数并创建任务) * 进片和出片共用同一套协议字段,通过位置信息判断任务类型 */ private DevicePlcVO.OperationResult handleCheckMesTask( DeviceConfig deviceConfig, Map params, Map logicParams) { if (plcDynamicDataService == null || s7SerializerProvider == 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序列化器失败") .build(); } try { // 检查是否已有任务在执行中 MesTaskInfo existingTask = currentTasks.get(deviceId); if (existingTask != null) { log.debug("设备已有任务在执行中,跳过检查MES任务: deviceId={}", deviceId); return DevicePlcVO.OperationResult.builder() .success(true) .message("任务执行中,无需重复检查MES任务") .data(Collections.singletonMap("waiting", false)) .build(); } // 读取MES字段(进片和出片共用) Map mesData = plcDynamicDataService.readPlcData( deviceConfig, MES_FIELDS, serializer); if (mesData == null || mesData.isEmpty()) { log.warn("读取MES字段失败: deviceId={}, mesData为空或null", deviceId); return DevicePlcVO.OperationResult.builder() .success(false) .message("读取MES字段失败") .build(); } // 解析mesSend Integer mesSend = parseInteger(mesData.get("mesSend")); if (mesSend == null || mesSend == 0) { Map waitData = new HashMap<>(); waitData.put("completed", false); waitData.put("waiting", true); waitData.put("waitingReason", "mesSend=0"); 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 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); // mesSend=1,读取所有玻璃的任务参数(支持1-6个玻璃) List glasses = new ArrayList<>(); boolean isOutbound = false; // 将在处理第一个有效玻璃时确定 // 读取所有6个玻璃的信息 for (int i = 1; i <= 6; i++) { String glassId = parseString(mesData.get("mesGlassId" + i)); if (glassId == null || glassId.isEmpty()) { continue; // 跳过空的玻璃ID } Integer startSlot = parseInteger(mesData.get("start" + i)); Integer targetSlot = parseInteger(mesData.get("target" + i)); Integer width = parseInteger(mesData.get("mesWidth" + i)); Integer height = parseInteger(mesData.get("mesHeight" + i)); Integer thickness = parseInteger(mesData.get("mesThickness" + i)); log.debug("读取玻璃{}信息: deviceId={}, glassId={}, startSlot={}, targetSlot={}, width={}, height={}, thickness={}", i, deviceId, glassId, startSlot, targetSlot, width, height, thickness); if (startSlot == null || targetSlot == null) { log.warn("玻璃{}信息不完整,跳过: glassId={}, startSlot={}, targetSlot={}", i, glassId, startSlot, targetSlot); continue; } // 对于第一个有效玻璃,判断是进片还是出片任务 if (glasses.isEmpty()) { isOutbound = isOutboundTask(startSlot, logicParams); } // 位置映射 Integer startPosition; if (isOutbound) { // 出片任务:startSlot是格子编号,需要映射到实际位置 startPosition = mapOutboundPosition(startSlot, logicParams); } else { // 进片任务:startSlot是卧转立编号,通过positionMapping映射 startPosition = mapPosition(startSlot, logicParams); } // targetSlot统一通过positionMapping映射 Integer targetPosition = mapPosition(targetSlot, logicParams); if (startPosition == null || targetPosition == null) { log.warn("玻璃{}位置映射失败,跳过: glassId={}, startSlot={}, targetSlot={}", i, glassId, startSlot, targetSlot); continue; } // 创建玻璃任务信息 GlassTaskInfo glassInfo = new GlassTaskInfo(); glassInfo.glassId = glassId; glassInfo.startSlot = startSlot; glassInfo.targetSlot = targetSlot; glassInfo.startPosition = startPosition; glassInfo.targetPosition = targetPosition; glassInfo.width = width; glassInfo.height = height; glassInfo.thickness = thickness; glasses.add(glassInfo); } if (glasses.isEmpty()) { // 记录详细的MES数据,帮助调试 log.warn("MES未提供有效的玻璃信息: deviceId={}, mesSend={}, mesData={}", deviceId, mesSend, mesData); Map waitData = new HashMap<>(); waitData.put("completed", false); waitData.put("waiting", true); waitData.put("waitingReason", "glassInfoMissing"); return DevicePlcVO.OperationResult.builder() .success(false) .message("MES未提供有效的玻璃信息(mesSend=1但未找到有效的glassId、startSlot、targetSlot)") .data(waitData) .build(); } // 读取当前位置 Integer currentPosition = getCurrentPosition(deviceConfig, logicParams); // 使用第一个玻璃的位置计算时间(所有玻璃使用相同的路径) GlassTaskInfo firstGlass = glasses.get(0); TimeCalculation timeCalc = calculateTime( currentPosition, firstGlass.startPosition, firstGlass.targetPosition, logicParams); // 创建任务信息 MesTaskInfo taskInfo = new MesTaskInfo(); taskInfo.glasses = glasses; taskInfo.currentPosition = currentPosition; taskInfo.gotime = timeCalc.gotime; taskInfo.cartime = timeCalc.cartime; taskInfo.createdTime = System.currentTimeMillis(); taskInfo.isOutbound = isOutbound; taskInfo.mesSignature = mesSignature; // 从配置中读取破损玻璃索引(用于测试场景) // 配置格式:brokenGlassIndices: [0, 2] 表示第1个和第3个玻璃应该破损 @SuppressWarnings("unchecked") List brokenIndices = getLogicParam(logicParams, "brokenGlassIndices", null); if (brokenIndices != null && !brokenIndices.isEmpty()) { taskInfo.brokenGlassIndices = new ArrayList<>(); for (Integer index : brokenIndices) { if (index != null && index >= 0 && index < glasses.size()) { taskInfo.brokenGlassIndices.add(index); } } if (!taskInfo.brokenGlassIndices.isEmpty()) { log.info("任务创建时标记破损玻璃: deviceId={}, brokenIndices={}", deviceId, taskInfo.brokenGlassIndices); } } currentTasks.put(deviceId, taskInfo); // 清空plcRequest(表示已接收任务) Map payload = new HashMap<>(); payload.put("plcRequest", 0); plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); log.info("已清空plcRequest=0: deviceId={}", deviceId); // 更新车辆状态为执行中 statusManager.updateVehicleStatus(deviceId, deviceConfig.getDeviceName(), VehicleState.EXECUTING); // 启动任务监控 handleStartTaskMonitor(deviceConfig, params, logicParams); log.info("已启动任务监控: deviceId={}", deviceId); String taskType = isOutbound ? "出片" : "进片"; String glassIds = glasses.stream() .map(g -> g.glassId) .collect(java.util.stream.Collectors.joining(",")); log.info("MES{}任务已创建: deviceId={}, glassCount={}, glassIds=[{}], 起始位置={}格, 目标位置={}格, 距离{}格->{}格, gotime={}ms({}秒), cartime={}ms({}秒)", taskType, deviceId, glasses.size(), glassIds, firstGlass.startPosition, firstGlass.targetPosition, Math.abs(firstGlass.startPosition - currentPosition), Math.abs(firstGlass.targetPosition - firstGlass.startPosition), timeCalc.gotime, timeCalc.gotime / 1000.0, timeCalc.cartime, timeCalc.cartime / 1000.0); // 构建详细的步骤提示信息 StringBuilder stepMessage = new StringBuilder(); stepMessage.append("检测到MES任务(mesSend=1),已清空请求字(plcRequest=0)"); stepMessage.append(";开始计算时间,预计到达起始位置耗时").append(timeCalc.gotime / 1000.0).append("秒"); stepMessage.append(",运输到目标位置耗时").append(timeCalc.cartime / 1000.0).append("秒"); if (!isOutbound) { stepMessage.append(";进片任务:等待玻璃上车后,将告知卧转立设备"); } Map successData = new HashMap<>(); successData.put("waiting", false); successData.put("taskStarted", true); return DevicePlcVO.OperationResult.builder() .success(true) .message(stepMessage.toString()) .data(successData) .build(); } catch (Exception e) { log.error("检查MES任务异常: deviceId={}", deviceId, e); return DevicePlcVO.OperationResult.builder() .success(false) .message("处理异常: " + e.getMessage()) .build(); } } /** * 判断是否为出片任务 * 通过startSlot判断: * - 如果startSlot在positionMapping中,且不在大理片笼格子范围内,则是进片任务 * - 如果startSlot不在positionMapping中,或在大理片笼格子范围内,则是出片任务 */ private boolean isOutboundTask(Integer startSlot, Map logicParams) { if (startSlot == null) { return false; } // 方法1:检查startSlot是否在positionMapping中(卧转立编号) @SuppressWarnings("unchecked") Map positionMapping = getLogicParam(logicParams, "positionMapping", new HashMap<>()); if (positionMapping.containsKey(String.valueOf(startSlot))) { // startSlot在positionMapping中,说明是卧转立编号,是进片任务 return false; } // 方法2:检查startSlot是否在大理片笼格子范围内 // 通过查找同组的大理片笼设备,检查格子范围 // 这里简化处理:如果startSlot不在positionMapping中,且是数字,可能是格子编号 // 可以通过配置指定格子编号范围,或者通过查找同组设备判断 // 方法3:通过配置指定车辆运动格子范围(兼容旧配置outboundSlotRanges) @SuppressWarnings("unchecked") List vehicleSlotRange = getLogicParam(logicParams, "vehicleSlotRange", null); if (vehicleSlotRange == null || vehicleSlotRange.isEmpty()) { // 兼容旧配置 vehicleSlotRange = getLogicParam(logicParams, "outboundSlotRanges", null); } if (vehicleSlotRange != null && !vehicleSlotRange.isEmpty()) { // 如果配置了车辆运动格子范围,检查startSlot是否在范围内 // 例如:[1, 101] 表示车辆只能在格子1~101之间运动 if (vehicleSlotRange.size() >= 2) { int minSlot = vehicleSlotRange.get(0); int maxSlot = vehicleSlotRange.get(1); if (startSlot >= minSlot && startSlot <= maxSlot) { return true; } } } // 默认:如果startSlot不在positionMapping中,且是较小的数字(可能是格子编号),判断为出片 // 这里可以根据实际需求调整判断逻辑 // 暂时:如果startSlot不在positionMapping中,判断为出片任务 return true; } /** * 映射出片源位置(格子编号转换为实际位置) * 通过查找同组的大理片笼设备配置,将格子编号转换为位置 */ private Integer mapOutboundPosition(Integer gridNumber, Map logicParams) { if (gridNumber == null) { return null; } // 方法1:如果配置了格子到位置的映射表 @SuppressWarnings("unchecked") Map gridPositionMapping = getLogicParam(logicParams, "gridPositionMapping", new HashMap<>()); Integer position = gridPositionMapping.get(String.valueOf(gridNumber)); if (position != null) { return position; } // 方法2:格子编号直接作为位置(如果格子编号就是位置值) // 例如:格子1对应位置1格,格子52对应位置52格 // 这里可以根据实际需求调整映射逻辑 // 暂时直接使用格子编号作为位置 log.debug("使用格子编号作为位置: gridNumber={}", gridNumber); return gridNumber; } /** * 位置映射:将MES给的编号(如900/901)转换为实际位置值(如100/500) */ private Integer mapPosition(Integer slotNumber, Map logicParams) { if (slotNumber == null) { return null; } // 从配置中获取位置映射表 @SuppressWarnings("unchecked") Map positionMapping = getLogicParam(logicParams, "positionMapping", new HashMap<>()); // 查找映射 Integer position = positionMapping.get(String.valueOf(slotNumber)); if (position != null) { return position; } // 如果没有配置映射,尝试直接使用编号 log.warn("位置映射未找到: slotNumber={}, 使用编号作为位置值", slotNumber); return slotNumber; } /** * 获取当前位置 */ private Integer getCurrentPosition(DeviceConfig deviceConfig, Map logicParams) { // 从状态管理器获取 VehicleStatus status = statusManager.getVehicleStatus(deviceConfig.getDeviceId()); if (status != null && status.getCurrentPosition() != null) { return status.getCurrentPosition().getPositionValue(); } // 从配置中获取默认位置 return getLogicParam(logicParams, "homePosition", 0); } /** * 时间计算:根据速度、当前位置、目标位置计算gotime和cartime * 速度单位:格/秒(grid/s) * 位置和距离单位:格子(grid) */ private TimeCalculation calculateTime(Integer currentPos, Integer startPos, Integer targetPos, Map logicParams) { // 验证车辆运动格子范围 @SuppressWarnings("unchecked") List vehicleSlotRange = getLogicParam(logicParams, "vehicleSlotRange", null); if (vehicleSlotRange == null || vehicleSlotRange.isEmpty()) { // 兼容旧配置 vehicleSlotRange = getLogicParam(logicParams, "outboundSlotRanges", null); } if (vehicleSlotRange != null && vehicleSlotRange.size() >= 2) { int minSlot = vehicleSlotRange.get(0); int maxSlot = vehicleSlotRange.get(1); // 验证startPos和targetPos是否在允许的范围内 if (startPos != null && (startPos < minSlot || startPos > maxSlot)) { log.warn("起始位置 {} 超出车辆运动格子范围 [{}, {}]", startPos, minSlot, maxSlot); } if (targetPos != null && (targetPos < minSlot || targetPos > maxSlot)) { log.warn("目标位置 {} 超出车辆运动格子范围 [{}, {}]", targetPos, minSlot, maxSlot); } } // 获取速度(格/秒,grid/s) // 这里不能直接用 Double 泛型,否则当配置里是 Integer 时会出现 // java.lang.Integer cannot be cast to java.lang.Double 的异常 Object speedObj = getLogicParam(logicParams, "vehicleSpeed", 1.0); Double speed = null; if (speedObj instanceof Double) { speed = (Double) speedObj; } else if (speedObj instanceof Integer) { speed = ((Integer) speedObj).doubleValue(); } else if (speedObj instanceof Number) { speed = ((Number) speedObj).doubleValue(); } else { try { speed = Double.parseDouble(String.valueOf(speedObj)); } catch (Exception ignore) { } } if (speed == null || speed <= 0) { speed = 1.0; // 默认1格/秒 } // 获取运动距离范围(格子) Integer minRange = getLogicParam(logicParams, "minRange", 1); Integer maxRange = getLogicParam(logicParams, "maxRange", 100); // 计算gotime:从当前位置到起始位置的时间(毫秒) // 公式:时间(ms) = 距离(格) / 速度(格/秒) * 1000 long gotime = 0; if (currentPos != null && startPos != null) { int distance = Math.abs(startPos - currentPos); // 距离(格子) // 限制在范围内 distance = Math.max(minRange, Math.min(maxRange, distance)); gotime = (long) (distance / speed * 1000); // 转换为毫秒 } // 计算cartime:从起始位置到目标位置的时间(毫秒) // 公式:时间(ms) = 距离(格) / 速度(格/秒) * 1000 long cartime = 0; if (startPos != null && targetPos != null) { int distance = Math.abs(targetPos - startPos); // 距离(格子) // 限制在范围内 distance = Math.max(minRange, Math.min(maxRange, distance)); cartime = (long) (distance / speed * 1000); // 转换为毫秒 } return new TimeCalculation(gotime, cartime); } /** * 启动任务监控(监控state状态切换和任务完成) */ private DevicePlcVO.OperationResult handleStartTaskMonitor( DeviceConfig deviceConfig, Map params, Map logicParams) { String deviceId = deviceConfig.getDeviceId(); // 停止旧的监控任务 handleStopTaskMonitor(deviceConfig); MesTaskInfo taskInfo = currentTasks.get(deviceId); if (taskInfo == null) { return DevicePlcVO.OperationResult.builder() .success(false) .message("没有正在执行的任务") .build(); } // 获取监控间隔 Integer monitorIntervalMs = getLogicParam(logicParams, "taskMonitorIntervalMs", 1000); // 默认1秒 // 启动监控任务 ScheduledFuture future = taskMonitorExecutor.scheduleWithFixedDelay(() -> { try { monitorTaskExecution(deviceConfig, taskInfo, logicParams); } catch (Exception e) { log.error("任务监控异常: deviceId={}", deviceId, e); } }, monitorIntervalMs, monitorIntervalMs, TimeUnit.MILLISECONDS); taskMonitoringTasks.put(deviceId, future); log.info("已启动任务监控: deviceId={}, interval={}ms", deviceId, monitorIntervalMs); return DevicePlcVO.OperationResult.builder() .success(true) .message("任务监控已启动") .build(); } /** * 监控任务执行 */ private void monitorTaskExecution(DeviceConfig deviceConfig, MesTaskInfo taskInfo, Map logicParams) { if (plcDynamicDataService == null || s7SerializerProvider == null) { return; } String deviceId = deviceConfig.getDeviceId(); EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig); if (serializer == null) { return; } try { // 读取state1~6 List stateFields = Arrays.asList("state1", "state2", "state3", "state4", "state5", "state6"); Map stateValues = new HashMap<>(); DevicePlcVO.StatusInfo statusInfo = devicePlcOperationService.readStatus(deviceConfig.getId()); if (statusInfo != null && statusInfo.getFieldValues() != null) { for (String field : stateFields) { Object value = statusInfo.getFieldValues().get(field); if (value != null) { stateValues.put(field, value); } } } // 根据时间计算更新state状态 long currentTime = System.currentTimeMillis(); long elapsed = currentTime - taskInfo.createdTime; // 计算状态切换时间点 long state1Time = taskInfo.gotime; // 到达起始位置,上车完成 long state2Time = taskInfo.gotime + taskInfo.cartime; // 到达目标位置,运输完成 // 获取超时时间配置(默认:超过预期完成时间的200%视为超时未完成) Double timeoutRatio = getLogicParam(logicParams, "state3TimeoutRatio", 2.0); long state3TimeoutTime = (long) (state2Time * timeoutRatio); // 超时时间点 // 更新state状态(每个玻璃对应一个state) int glassCount = taskInfo.glasses.size(); boolean hasStateOne = false; // 标记是否有state变为1 boolean hasStateTwo = false; // 标记是否有state变为2 String currentStepDesc = ""; // 当前步骤描述 for (int i = 0; i < glassCount && i < 6; i++) { String stateField = "state" + (i + 1); Object currentValue = stateValues.get(stateField); Integer currentState = parseInteger(currentValue); // 如果当前state已经是3(未完成)或8(破损),跳过 if (currentState != null && (currentState == 3 || currentState == 8)) { continue; } // 优先检查是否标记为破损(state=8) // 检查任务信息中是否标记了该玻璃为破损 if (taskInfo.brokenGlassIndices != null && taskInfo.brokenGlassIndices.contains(i)) { updateStateIfNeeded(deviceConfig, serializer, stateValues, stateField, 8, taskInfo); log.info("玻璃标记为破损: deviceId={}, stateField={}, glassIndex={}", deviceConfig.getDeviceId(), stateField, i); continue; } // 检查超时未完成(state=3) if (elapsed >= state3TimeoutTime && (currentState == null || currentState < 2)) { updateStateIfNeeded(deviceConfig, serializer, stateValues, stateField, 3, taskInfo); log.warn("任务超时未完成: deviceId={}, stateField={}, elapsed={}ms, expectedTime={}ms", deviceConfig.getDeviceId(), stateField, elapsed, state2Time); continue; } // 正常状态更新 if (elapsed >= state1Time && elapsed < state2Time) { // state应该为1(上车完成) boolean stateChanged = updateStateIfNeeded(deviceConfig, serializer, stateValues, stateField, 1, taskInfo); if (stateChanged) { hasStateOne = true; currentStepDesc = "玻璃已上车(state=1),正在运输到目标位置"; } else if (currentState != null && currentState == 1) { currentStepDesc = "玻璃已上车(state=1),正在运输到目标位置"; } } else if (elapsed >= state2Time) { // state应该为2(运输完成) boolean stateChanged = updateStateIfNeeded(deviceConfig, serializer, stateValues, stateField, 2, taskInfo); if (stateChanged) { hasStateTwo = true; currentStepDesc = "玻璃已到达目标位置(state=2),等待MES确认"; } else if (currentState != null && currentState == 2) { currentStepDesc = "玻璃已到达目标位置(state=2),等待MES确认"; } } else { // 还在前往起始位置 currentStepDesc = "正在前往起始位置,预计耗时" + (state1Time / 1000.0) + "秒"; } } // 当state变为1时(玻璃上车完成),清空卧转立设备的plcRequest(只执行一次) if (hasStateOne && !taskInfo.transferPlcRequestCleared) { clearTransferPlcRequest(deviceConfig, logicParams); taskInfo.transferPlcRequestCleared = true; // 标记已清空,避免重复执行 if (!taskInfo.isOutbound) { currentStepDesc = "玻璃已上车(state=1),已告知卧转立设备(plcRequest=0),正在运输到目标位置"; } } // 检查是否所有state都>=2,如果是则给MES汇报 if (elapsed >= state2Time && allStatesCompleted(stateValues, glassCount)) { reportToMes(deviceConfig, serializer, taskInfo, logicParams); // 记录MES确认开始等待的时间(只记录一次) if (taskInfo.mesConfirmStartTime == null) { taskInfo.mesConfirmStartTime = System.currentTimeMillis(); currentStepDesc = "玻璃已到达目标位置(state=2),已发送汇报(plcReport=1),等待MES确认"; } } // 保存当前步骤描述到任务信息中,供checkMesConfirm使用 if (!currentStepDesc.isEmpty()) { taskInfo.currentStepDesc = currentStepDesc; } } catch (Exception e) { log.error("监控任务执行异常: deviceId={}", deviceId, e); } } /** * 更新state状态(如果需要) * @return 是否发生了状态变化(从非目标状态变为目标状态) */ private boolean updateStateIfNeeded(DeviceConfig deviceConfig, EnhancedS7Serializer serializer, Map currentStates, String stateField, int targetState, MesTaskInfo taskInfo) { // 检查当前state值 Object currentValue = currentStates.get(stateField); Integer currentState = parseInteger(currentValue); // 如果当前state小于目标state,则更新 // 注意:如果当前state已经是3(未完成)或8(破损),不再更新 if (currentState != null && (currentState == 3 || currentState == 8)) { log.debug("任务状态已为异常状态,不再更新: deviceId={}, stateField={}, currentState={}, targetState={}", deviceConfig.getDeviceId(), stateField, currentState, targetState); return false; } if (currentState == null || currentState < targetState) { // 实际写入PLC的state字段 try { Map payload = new HashMap<>(); payload.put(stateField, targetState); plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); log.info("任务状态已更新到PLC: deviceId={}, stateField={}, currentState={}, targetState={}", deviceConfig.getDeviceId(), stateField, currentState, targetState); // 返回true表示状态发生了变化 return true; } catch (Exception e) { log.error("写入PLC state字段失败: deviceId={}, stateField={}, targetState={}, error={}", deviceConfig.getDeviceId(), stateField, targetState, e.getMessage()); return false; } } return false; } /** * 清空卧转立设备的plcRequest(当大车state=1时,表示玻璃已上车) */ private void clearTransferPlcRequest(DeviceConfig deviceConfig, Map logicParams) { try { // 查找同组的卧转立设备 List transferDevices = findTransferDevicesInSameGroup(deviceConfig); if (transferDevices.isEmpty()) { log.debug("未找到同组的卧转立设备,跳过清空plcRequest: deviceId={}", deviceConfig.getId()); return; } // 将每个卧转立设备的 plcRequest 置 0 for (DeviceConfig transferDevice : transferDevices) { Map payload = new HashMap<>(); payload.put("plcRequest", 0); DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields( transferDevice.getId(), payload, "大车state=1自动清空卧转立请求" ); if (Boolean.TRUE.equals(result.getSuccess())) { log.info("已自动清空卧转立设备 plcRequest: vehicleDeviceId={}, transferDeviceId={}, transferDeviceName={}", deviceConfig.getId(), transferDevice.getId(), transferDevice.getDeviceName()); } else { log.warn("自动清空卧转立设备 plcRequest 失败: vehicleDeviceId={}, transferDeviceId={}, message={}", deviceConfig.getId(), transferDevice.getId(), result.getMessage()); } } } catch (Exception e) { log.error("清空卧转立设备plcRequest异常: deviceId={}", deviceConfig.getId(), e); } } /** * 检查是否所有state都已完成(>=2) */ private boolean allStatesCompleted(Map stateValues, int glassCount) { // 只检查实际有玻璃的state字段 for (int i = 1; i <= glassCount && i <= 6; i++) { String stateField = "state" + i; Object value = stateValues.get(stateField); Integer state = parseInteger(value); if (state == null || state < 2) { return false; } } return glassCount > 0; } /** * 给MES汇报 */ private void reportToMes(DeviceConfig deviceConfig, EnhancedS7Serializer serializer, MesTaskInfo taskInfo, Map logicParams) { try { // 设置汇报字 Map payload = new HashMap<>(); payload.put("plcReport", 1); plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); 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); // 多设备任务场景下,不在这里阻塞等待MES确认,由任务引擎定时调用checkMesConfirm } catch (Exception e) { log.error("给MES汇报异常: deviceId={}", deviceConfig.getDeviceId(), e); } } /** * 检查MES确认状态(供任务引擎周期性调用) * 返回OperationResult.data中的 completed 标志表示是否已确认完成 */ public DevicePlcVO.OperationResult checkMesConfirm(DeviceConfig deviceConfig, Map logicParams) { if (plcDynamicDataService == null || s7SerializerProvider == null) { return DevicePlcVO.OperationResult.builder() .success(false) .message("PlcDynamicDataService或S7SerializerProvider未注入") .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 { DevicePlcVO.OperationResult checkResult = handleCheckMesTask(deviceConfig, Collections.emptyMap(), logicParams); if (Boolean.TRUE.equals(checkResult.getSuccess())) { taskInfo = currentTasks.get(deviceId); if (taskInfo != null) { log.info("补偿检查MES任务成功,已创建任务记录: deviceId={}", deviceId); } else { log.info("补偿检查MES任务执行成功但未创建任务记录: deviceId={}, message={}", deviceId, checkResult.getMessage()); } } else { log.warn("补偿检查MES任务失败: deviceId={}, message={}", deviceId, checkResult.getMessage()); } } catch (Exception e) { log.warn("补偿检查MES任务异常: deviceId={}, error={}", deviceId, e.getMessage()); } } // 补偿后仍然没有任务记录,说明MES尚未提供可执行任务 if (taskInfo == null) { Map waitData = new HashMap<>(); waitData.put("completed", false); waitData.put("waiting", true); waitData.put("waitingReason", "noTask"); return DevicePlcVO.OperationResult.builder() .success(true) .message("等待MES发送请求(mesSend=1)") .data(waitData) .build(); } // 获取MES确认超时配置(默认30秒) Integer mesConfirmTimeoutMs = getLogicParam(logicParams, "mesConfirmTimeoutMs", 30000); Map data = new HashMap<>(); try { // 如果尚未向MES汇报(plcReport=1),无需检查确认 if (taskInfo.mesConfirmStartTime == 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) .data(data) .build(); } // 检查超时 long waitTime = System.currentTimeMillis() - taskInfo.mesConfirmStartTime; if (waitTime > mesConfirmTimeoutMs) { log.warn("MES确认超时: deviceId={}, waitTime={}ms, timeout={}ms", deviceId, waitTime, mesConfirmTimeoutMs); data.put("completed", false); data.put("timeout", true); data.put("waitTime", waitTime); // 超时视为任务失败:清理任务状态并停止监控,避免继续累加等待时间 try { clearTaskStates(deviceConfig, serializer); } 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()); handleStopTaskMonitor(deviceConfig); return DevicePlcVO.OperationResult.builder() .success(false) .message(String.format("MES确认超时: 等待时间%dms,超时时间%dms", waitTime, mesConfirmTimeoutMs)) .data(data) .build(); } Object confirmValue = plcDynamicDataService.readPlcField( deviceConfig, "mesConfirm", serializer); Integer confirm = parseInteger(confirmValue); boolean completed = confirm != null && confirm == 1; data.put("completed", completed); if (completed) { // MES已确认,清空state和汇报字 clearTaskStates(deviceConfig, serializer); // 记录已完成的任务签名,避免MES未复位时被重复拉起 lastCompletedMesRecords.put(deviceId, new CompletedMesRecord(taskInfo.mesSignature, System.currentTimeMillis())); // 任务完成,恢复为空闲状态 statusManager.updateVehicleStatus( deviceConfig.getDeviceId(), VehicleState.IDLE); statusManager.clearVehicleTask(deviceConfig.getDeviceId()); // 移除任务记录(如果有) currentTasks.remove(deviceConfig.getDeviceId()); // 停止任务监控 handleStopTaskMonitor(deviceConfig); // 恢复plcRequest=1(可以接收新任务) Map payload = new HashMap<>(); payload.put("plcRequest", 1); plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); log.info("MES任务已确认完成: deviceId={}", deviceConfig.getDeviceId()); String taskType = taskInfo.isOutbound ? "出片" : "进片"; return DevicePlcVO.OperationResult.builder() .success(true) .message(String.format("%s任务完成:MES已确认(mesConfirm=1),已清空state和汇报字,大车空闲(plcRequest=1),可以等待下次任务", taskType)) .data(data) .build(); } // 构建详细的等待提示信息 String waitMessage = "等待MES确认中(mesConfirm=0)"; if (taskInfo.currentStepDesc != null && !taskInfo.currentStepDesc.isEmpty()) { waitMessage = taskInfo.currentStepDesc + ";" + waitMessage; } else if (taskInfo.mesConfirmStartTime != null) { long waitMillis = System.currentTimeMillis() - taskInfo.mesConfirmStartTime; waitMessage = String.format("等待MES确认中(mesConfirm=0),已等待%.1f秒", waitMillis / 1000.0); } return DevicePlcVO.OperationResult.builder() .success(true) .message(waitMessage) .data(data) .build(); } catch (Exception e) { log.error("检查MES确认状态异常: deviceId={}", deviceConfig.getDeviceId(), e); return DevicePlcVO.OperationResult.builder() .success(false) .message("检查MES确认状态异常: " + e.getMessage()) .data(data) .build(); } } /** * 设置玻璃为破损状态(state=8) * 用于测试场景,模拟玻璃破损 */ private DevicePlcVO.OperationResult handleMarkBroken(DeviceConfig deviceConfig, Map params, Map logicParams) { String deviceId = deviceConfig.getDeviceId(); MesTaskInfo taskInfo = currentTasks.get(deviceId); if (taskInfo == null) { return DevicePlcVO.OperationResult.builder() .success(false) .message("没有正在执行的任务") .build(); } // 从参数中获取要标记为破损的玻璃索引(0-based) // 支持单个索引或索引列表 List brokenIndices = new ArrayList<>(); Object brokenIndexObj = params.get("glassIndex"); if (brokenIndexObj != null) { if (brokenIndexObj instanceof List) { @SuppressWarnings("unchecked") List indexList = (List) brokenIndexObj; for (Object idx : indexList) { Integer index = parseInteger(idx); if (index != null && index >= 0 && index < taskInfo.glasses.size()) { brokenIndices.add(index); } } } else { Integer index = parseInteger(brokenIndexObj); if (index != null && index >= 0 && index < taskInfo.glasses.size()) { brokenIndices.add(index); } } } if (brokenIndices.isEmpty()) { return DevicePlcVO.OperationResult.builder() .success(false) .message("未指定有效的玻璃索引") .build(); } // 标记为破损 if (taskInfo.brokenGlassIndices == null) { taskInfo.brokenGlassIndices = new ArrayList<>(); } for (Integer index : brokenIndices) { if (!taskInfo.brokenGlassIndices.contains(index)) { taskInfo.brokenGlassIndices.add(index); } } // 立即写入PLC的state字段 EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig); if (serializer != null) { try { Map 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); } catch (Exception e) { log.error("写入破损状态到PLC失败: deviceId={}, error={}", deviceId, e.getMessage()); } } return DevicePlcVO.OperationResult.builder() .success(true) .message(String.format("已标记玻璃为破损: indices=%s", brokenIndices)) .build(); } /** * 清空任务状态 */ private void clearTaskStates(DeviceConfig deviceConfig, EnhancedS7Serializer serializer) { try { Map payload = new HashMap<>(); // 清空state1~6 for (int i = 1; i <= 6; i++) { payload.put("state" + i, 0); } // 清空汇报字 payload.put("plcReport", 0); plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); } catch (Exception e) { log.error("清空任务状态异常: deviceId={}", deviceConfig.getDeviceId(), e); } } /** * 停止任务监控 */ private DevicePlcVO.OperationResult handleStopTaskMonitor(DeviceConfig deviceConfig) { String deviceId = deviceConfig.getDeviceId(); ScheduledFuture future = taskMonitoringTasks.remove(deviceId); if (future != null && !future.isCancelled()) { future.cancel(false); log.info("已停止任务监控: deviceId={}", deviceId); } return DevicePlcVO.OperationResult.builder() .success(true) .message("任务监控已停止") .build(); } /** * 时间计算结果 */ private static class TimeCalculation { final long gotime; // 到起始位置的时间(毫秒) final long cartime; // 从起始到目标位置的时间(毫秒) TimeCalculation(long gotime, long cartime) { this.gotime = gotime; this.cartime = cartime; } } /** * 单个玻璃的任务信息 */ private static class GlassTaskInfo { String glassId; Integer startSlot; Integer targetSlot; Integer startPosition; Integer targetPosition; Integer width; Integer height; Integer thickness; } /** * MES任务信息(可能包含多个玻璃) */ private static class MesTaskInfo { List glasses = new ArrayList<>(); // 多个玻璃信息 Integer currentPosition; long gotime; long cartime; long createdTime; boolean isOutbound = false; // 是否为出片任务(false=进片,true=出片) boolean transferPlcRequestCleared = false; // 是否已清空卧转立plcRequest(避免重复清空) List 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; } } /** * 应用关闭时清理资源 */ @PreDestroy public void destroy() { log.info("正在关闭大车监控线程池..."); // 停止所有监控任务 for (String deviceId : new ArrayList<>(monitoringTasks.keySet())) { stopStateMonitoring(deviceId); } for (String deviceId : new ArrayList<>(idleMonitoringTasks.keySet())) { ScheduledFuture future = idleMonitoringTasks.remove(deviceId); if (future != null) future.cancel(false); } for (String deviceId : new ArrayList<>(taskMonitoringTasks.keySet())) { ScheduledFuture future = taskMonitoringTasks.remove(deviceId); if (future != null) future.cancel(false); } // 关闭线程池 shutdownExecutor(stateMonitorExecutor, "状态监控"); shutdownExecutor(idleMonitorExecutor, "空闲监控"); shutdownExecutor(taskMonitorExecutor, "任务监控"); log.info("大车监控线程池已关闭"); } private void shutdownExecutor(ScheduledExecutorService executor, String name) { executor.shutdown(); try { if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { executor.shutdownNow(); if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { log.warn("{}线程池未能正常关闭", name); } } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); } } /** * 通知卧转立扫码设备暂停或继续 * @param params 参数,包含_taskContext引用 * @param pause true=暂停,false=继续 */ private void notifyScannerPause(Map params, boolean pause) { if (params == null) { return; } Object contextObj = params.get("_taskContext"); if (contextObj instanceof TaskExecutionContext) { TaskExecutionContext context = (TaskExecutionContext) contextObj; HorizontalScannerLogicHandler.setPauseFlag(context, pause); log.info("已通知卧转立扫码设备{}: pause={}", pause ? "暂停" : "继续", pause); } else { log.debug("未找到TaskExecutionContext,无法通知卧转立扫码设备暂停"); } } private void clearDynamicTaskStates(DeviceConfig deviceConfig) { if (plcDynamicDataService == null || s7SerializerProvider == null) { return; } try { EnhancedS7Serializer serializer = s7SerializerProvider.getSerializer(deviceConfig); if (serializer != null) { clearTaskStates(deviceConfig, serializer); } } catch (Exception e) { log.warn("清空大车state字段失败: deviceId={}, error={}", deviceConfig != null ? deviceConfig.getId() : "null", e.getMessage()); } } /** * 将MES数据构造成签名字符串,用于识别是否为同一批次任务 */ private String buildMesSignature(Map 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(); } }