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字段列表(进片和出片共用同一套协议) 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; case "setOnlineState": result = handleSetOnlineState(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); 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, 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); 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()); updateDeviceOnlineStatus(deviceConfig, true); } 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("plcGlassCount", 0); 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, "大车设备-清空玻璃数据" ); // 清空后,恢复为空闲状态,停止监控 if (Boolean.TRUE.equals(result.getSuccess())) { statusManager.clearVehicleTask(deviceConfig.getDeviceId()); statusManager.updateVehicleStatus(deviceConfig.getDeviceId(), VehicleState.IDLE); stopStateMonitoring(deviceConfig.getDeviceId()); updateDeviceOnlineStatus(deviceConfig, true); } 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); 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确认超时(毫秒) // 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(); if (glassLength == null || glassLength <= 0) { // 玻璃没有长度,直接报错(用于测试MES程序) 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 { // 读取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:通过配置指定车辆运动格子范围(兼容旧配置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 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确认,由任务引擎定时调用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(); } Map data = new HashMap<>(); try { 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); // 任务完成,恢复为空闲状态 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()); return DevicePlcVO.OperationResult.builder() .success(true) .message("MES任务已确认完成") .data(data) .build(); } return DevicePlcVO.OperationResult.builder() .success(true) .message("等待MES确认中") .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(); } } /** * 清空任务状态 */ 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(); } } /** * 通知卧转立扫码设备暂停或继续 * @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,无法通知卧转立扫码设备暂停"); } } }