package com.mes.interaction.vehicle.handler; import com.mes.device.entity.DeviceConfig; 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.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 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 PlcDynamicDataService plcDynamicDataService; @Autowired(required = false) private S7SerializerProvider s7SerializerProvider; // MES字段列表(进片和出片共用同一套协议) private static final List MES_FIELDS = Arrays.asList( "mesSend", "mesGlassId", "mesWidth", "mesHeight", "startSlot", "targetSlot", "workLine" ); // 监控线程池:用于定期检查大车状态并协调卧转立设备 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<>(); @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()) { 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; 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) { // 所有操作都需要检查状态,除了查询类操作 return !"query".equals(operation) && !"status".equals(operation); } /** * 判断操作是否需要状态管理 */ 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); } // 从配置中获取速度(如果有) Double speed = getLogicParam(logicParams, "vehicleSpeed", null); 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 glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", 1000); Boolean autoFeed = getLogicParam(logicParams, "autoFeed", true); Integer maxRetryCount = getLogicParam(logicParams, "maxRetryCount", 5); // 从运行时参数中获取数据(从接口调用时传入) 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, getLogicParam(logicParams, "defaultGlassLength", 2000)); if (plannedGlasses.isEmpty()) { return DevicePlcVO.OperationResult.builder() .success(false) .message("当前玻璃尺寸超出车辆容量,无法装载") .build(); } // 构建写入数据 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); // 写入位置信息 if (positionValue != null) { payload.put("inPosition", positionValue); } else if (positionCode != null) { // 从位置映射中获取位置值 @SuppressWarnings("unchecked") Map positionMapping = getLogicParam(logicParams, "positionMapping", new HashMap<>()); Integer mappedValue = positionMapping.get(positionCode); if (mappedValue != null) { payload.put("inPosition", mappedValue); } } // 自动触发请求字 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); if (glassIntervalMs != null && glassIntervalMs > 0) { try { Thread.sleep(glassIntervalMs); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } 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); } // 启动自动状态监控,当 state=1 时自动协调卧转立设备 startStateMonitoring(deviceConfig, logicParams); } 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); 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()); } 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("plcGlassCount", 0); payload.put("plcRequest", 0); payload.put("plcReport", 0); 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, "大车设备-清空玻璃数据" ); // 清空后,恢复为空闲状态,停止监控 if (Boolean.TRUE.equals(result.getSuccess())) { statusManager.clearVehicleTask(deviceConfig.getDeviceId()); statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE); stopStateMonitoring(deviceConfig.getDeviceId()); } return result; } 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 glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", null); if (glassIntervalMs != null && glassIntervalMs < 0) { return "玻璃间隔时间(glassIntervalMs)不能为负数"; } return null; // 验证通过 } @Override public String getDefaultLogicParams() { Map defaultParams = new HashMap<>(); defaultParams.put("vehicleCapacity", 6000); defaultParams.put("glassIntervalMs", 1000); defaultParams.put("autoFeed", true); defaultParams.put("maxRetryCount", 5); defaultParams.put("defaultGlassLength", 2000); // 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确认超时(毫秒) // 出片任务相关配置 // outboundSlotRanges: 出片任务的startSlot范围,例如[1, 101]表示格子1~101都是出片任务 // 如果不配置,则通过判断startSlot是否在positionMapping中来区分进片/出片 List outboundSlotRanges = new ArrayList<>(); outboundSlotRanges.add(1); // 最小格子编号 outboundSlotRanges.add(101); // 最大格子编号 defaultParams.put("outboundSlotRanges", outboundSlotRanges); // 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; } private List planGlassLoading(List source, int vehicleCapacity, Integer defaultGlassLength) { List planned = new ArrayList<>(); int usedLength = 0; int capacity = Math.max(vehicleCapacity, 1); int fallbackLength = defaultGlassLength != null && defaultGlassLength > 0 ? defaultGlassLength : 2000; for (GlassInfo info : source) { int length = info.getLength() != null && info.getLength() > 0 ? info.getLength() : fallbackLength; if (planned.isEmpty()) { planned.add(info.withLength(length)); usedLength = length; continue; } if (usedLength + length <= capacity) { planned.add(info.withLength(length)); usedLength += length; } else { break; } } 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 { // 读取MES字段(进片和出片共用) Map mesData = plcDynamicDataService.readPlcData( deviceConfig, MES_FIELDS, serializer); if (mesData == null || mesData.isEmpty()) { return DevicePlcVO.OperationResult.builder() .success(false) .message("读取MES字段失败") .build(); } Integer mesSend = parseInteger(mesData.get("mesSend")); if (mesSend == null || mesSend == 0) { return DevicePlcVO.OperationResult.builder() .success(true) .message("暂无MES任务(mesSend=0)") .build(); } // mesSend=1,读取任务参数 String glassId = parseString(mesData.get("mesGlassId")); Integer startSlot = parseInteger(mesData.get("startSlot")); // 起始位置编号 Integer targetSlot = parseInteger(mesData.get("targetSlot")); // 目标位置编号 Integer workLine = parseInteger(mesData.get("workLine")); if (glassId == null || glassId.isEmpty()) { return DevicePlcVO.OperationResult.builder() .success(false) .message("MES未提供玻璃ID") .build(); } // 判断是进片还是出片任务 // 方法:通过startSlot判断 // - 如果startSlot是卧转立编号(如900/901),则是进片任务 // - 如果startSlot是格子编号(在大理片笼范围内),则是出片任务 boolean 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) { return DevicePlcVO.OperationResult.builder() .success(false) .message(String.format("位置映射失败: startSlot=%s, targetSlot=%s, isOutbound=%s", startSlot, targetSlot, isOutbound)) .build(); } // 读取当前位置 Integer currentPosition = getCurrentPosition(deviceConfig, logicParams); // 计算时间 TimeCalculation timeCalc = calculateTime( currentPosition, startPosition, targetPosition, logicParams); // 创建任务信息 MesTaskInfo taskInfo = new MesTaskInfo(); taskInfo.glassId = glassId; taskInfo.startSlot = startSlot; taskInfo.targetSlot = targetSlot; taskInfo.startPosition = startPosition; taskInfo.targetPosition = targetPosition; taskInfo.currentPosition = currentPosition; taskInfo.gotime = timeCalc.gotime; taskInfo.cartime = timeCalc.cartime; taskInfo.workLine = workLine; taskInfo.createdTime = System.currentTimeMillis(); taskInfo.isOutbound = isOutbound; currentTasks.put(deviceId, taskInfo); // 清空plcRequest(表示已接收任务) Map payload = new HashMap<>(); payload.put("plcRequest", 0); plcDynamicDataService.writePlcData(deviceConfig, payload, serializer); // 更新车辆状态为执行中 statusManager.updateVehicleStatus(deviceId, deviceConfig.getDeviceName(), VehicleState.EXECUTING); // 启动任务监控 handleStartTaskMonitor(deviceConfig, params, logicParams); String taskType = isOutbound ? "出片" : "进片"; log.info("MES{}任务已创建: deviceId={}, glassId={}, startSlot={}(位置{}格), targetSlot={}(位置{}格), 距离{}格->{}格, gotime={}ms({}秒), cartime={}ms({}秒)", taskType, deviceId, glassId, startSlot, startPosition, targetSlot, targetPosition, Math.abs(startPosition - currentPosition), Math.abs(targetPosition - startPosition), timeCalc.gotime, timeCalc.gotime / 1000.0, timeCalc.cartime, timeCalc.cartime / 1000.0); return DevicePlcVO.OperationResult.builder() .success(true) .message(String.format("MES%s任务已创建: glassId=%s, start=%d, target=%d", taskType, glassId, startPosition, targetPosition)) .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:通过配置指定出片任务的startSlot范围 @SuppressWarnings("unchecked") List outboundSlotRanges = getLogicParam(logicParams, "outboundSlotRanges", null); if (outboundSlotRanges != null && !outboundSlotRanges.isEmpty()) { // 如果配置了出片slot范围,检查startSlot是否在范围内 // 例如:[1, 101] 表示格子1~101都是出片任务 if (outboundSlotRanges.size() >= 2) { int minSlot = outboundSlotRanges.get(0); int maxSlot = outboundSlotRanges.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) { // 获取速度(格/秒,grid/s) Double speed = getLogicParam(logicParams, "vehicleSpeed", 1.0); 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; // 到达目标位置,运输完成 // 更新state状态 if (elapsed >= state1Time && elapsed < state2Time) { // state应该为1 if (taskInfo.isOutbound) { // 出片任务:到达源位置(大理片笼),取片完成 updateStateIfNeeded(deviceConfig, serializer, stateValues, 1, taskInfo); } else { // 进片任务:到达起始位置(卧转立),上车完成 updateStateIfNeeded(deviceConfig, serializer, stateValues, 1, taskInfo); } } else if (elapsed >= state2Time) { // state应该为2(运输完成) updateStateIfNeeded(deviceConfig, serializer, stateValues, 2, taskInfo); // 检查是否所有state都>=2,如果是则给MES汇报 if (allStatesCompleted(stateValues)) { reportToMes(deviceConfig, serializer, taskInfo, logicParams); } } } catch (Exception e) { log.error("监控任务执行异常: deviceId={}", deviceId, e); } } /** * 更新state状态(如果需要) */ private void updateStateIfNeeded(DeviceConfig deviceConfig, EnhancedS7Serializer serializer, Map currentStates, int targetState, MesTaskInfo taskInfo) { // 这里可以根据实际需求更新state字段 // 暂时只记录日志,实际更新可能需要根据具体PLC字段配置 log.debug("任务状态更新: deviceId={}, targetState={}", deviceConfig.getDeviceId(), targetState); } /** * 检查是否所有state都已完成(>=2) */ private boolean allStatesCompleted(Map stateValues) { for (Object value : stateValues.values()) { Integer state = parseInteger(value); if (state == null || state < 2) { return false; } } return !stateValues.isEmpty(); } /** * 给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 ? "出片" : "进片"; log.info("已给MES汇报({}任务): deviceId={}, glassId={}", taskType, deviceConfig.getDeviceId(), taskInfo.glassId); // 等待MES确认 waitForMesConfirm(deviceConfig, serializer, taskInfo, logicParams); } catch (Exception e) { log.error("给MES汇报异常: deviceId={}", deviceConfig.getDeviceId(), e); } } /** * 等待MES确认 */ private void waitForMesConfirm(DeviceConfig deviceConfig, EnhancedS7Serializer serializer, MesTaskInfo taskInfo, Map logicParams) { try { // 读取确认字(假设字段名为mesConfirm) Integer maxWaitTime = getLogicParam(logicParams, "mesConfirmTimeoutMs", 30000); // 默认30秒 long startTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startTime < maxWaitTime) { Object confirmValue = plcDynamicDataService.readPlcField( deviceConfig, "mesConfirm", serializer); Integer confirm = parseInteger(confirmValue); if (confirm != null && confirm == 1) { // MES已确认,清空state和汇报字 clearTaskStates(deviceConfig, serializer); // 任务完成,恢复为空闲状态 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={}, glassId={}", deviceConfig.getDeviceId(), taskInfo.glassId); return; } Thread.sleep(500); // 等待500ms后重试 } log.warn("等待MES确认超时: deviceId={}, glassId={}", deviceConfig.getDeviceId(), taskInfo.glassId); } catch (Exception e) { log.error("等待MES确认异常: deviceId={}", deviceConfig.getDeviceId(), e); } } /** * 清空任务状态 */ 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; } } /** * MES任务信息 */ private static class MesTaskInfo { String glassId; Integer startSlot; Integer targetSlot; Integer startPosition; Integer targetPosition; Integer currentPosition; long gotime; long cartime; Integer workLine; long createdTime; boolean isOutbound = false; // 是否为出片任务(false=进片,true=出片) } /** * 应用关闭时清理资源 */ @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(); } } }