| | |
| | | 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; |
| | |
| | | } |
| | | |
| | | // 从配置中获取速度(如果有) |
| | | Double speed = getLogicParam(logicParams, "vehicleSpeed", null); |
| | | 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(); |
| | |
| | | |
| | | // 从逻辑参数中获取配置(从 extraParams.deviceLogic 读取) |
| | | Integer vehicleCapacity = getLogicParam(logicParams, "vehicleCapacity", 6000); |
| | | Integer glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", 1000); |
| | | // 优先使用运行时参数中的glassIntervalMs(从任务参数传入),如果没有则使用设备配置的 |
| | | Integer glassIntervalMs = null; |
| | | if (params.containsKey("glassIntervalMs") && params.get("glassIntervalMs") != null) { |
| | | Object intervalObj = params.get("glassIntervalMs"); |
| | | if (intervalObj instanceof Number) { |
| | | glassIntervalMs = ((Number) intervalObj).intValue(); |
| | | } else if (intervalObj instanceof String) { |
| | | try { |
| | | glassIntervalMs = Integer.parseInt((String) intervalObj); |
| | | } catch (NumberFormatException e) { |
| | | // 忽略 |
| | | } |
| | | } |
| | | } |
| | | if (glassIntervalMs == null) { |
| | | glassIntervalMs = getLogicParam(logicParams, "glassIntervalMs", 1000); |
| | | } |
| | | Boolean autoFeed = getLogicParam(logicParams, "autoFeed", true); |
| | | Integer maxRetryCount = getLogicParam(logicParams, "maxRetryCount", 5); |
| | | |
| | |
| | | Boolean triggerRequest = (Boolean) params.getOrDefault("triggerRequest", autoFeed); |
| | | |
| | | List<GlassInfo> plannedGlasses = planGlassLoading(glassInfos, vehicleCapacity, |
| | | getLogicParam(logicParams, "defaultGlassLength", 2000)); |
| | | if (plannedGlasses.isEmpty()) { |
| | | deviceConfig.getDeviceId()); |
| | | if (plannedGlasses == null) { |
| | | // 玻璃没有长度时返回null表示错误 |
| | | return DevicePlcVO.OperationResult.builder() |
| | | .success(false) |
| | | .message("当前玻璃尺寸超出车辆容量,无法装载") |
| | | .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<String, Object> payload = new HashMap<>(); |
| | |
| | | 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(); |
| | | } |
| | | } |
| | | |
| | | // 写入PLC,让大车开始装玻璃 |
| | | DevicePlcVO.OperationResult result = devicePlcOperationService.writeFields( |
| | | deviceConfig.getId(), payload, operationName); |
| | | |
| | | // 注意:glassIntervalMs 的等待应该在批次之间(在TaskExecutionEngine中处理), |
| | | // 而不是在这里等待,因为这里等待会阻塞大车的正常装玻璃流程 |
| | | // 如果需要在写入后等待,应该在批次之间等待,让大车有时间处理当前批次的玻璃 |
| | | |
| | | // 如果执行成功,更新位置信息到状态,并启动状态监控 |
| | | if (Boolean.TRUE.equals(result.getSuccess())) { |
| | |
| | | defaultParams.put("taskMonitorIntervalMs", 1000); // 任务监控间隔(毫秒) |
| | | defaultParams.put("mesConfirmTimeoutMs", 30000); // MES确认超时(毫秒) |
| | | |
| | | // 出片任务相关配置 |
| | | // outboundSlotRanges: 出片任务的startSlot范围,例如[1, 101]表示格子1~101都是出片任务 |
| | | // 如果不配置,则通过判断startSlot是否在positionMapping中来区分进片/出片 |
| | | List<Integer> outboundSlotRanges = new ArrayList<>(); |
| | | outboundSlotRanges.add(1); // 最小格子编号 |
| | | outboundSlotRanges.add(101); // 最大格子编号 |
| | | defaultParams.put("outboundSlotRanges", outboundSlotRanges); |
| | | |
| | | // gridPositionMapping: 格子编号到位置的映射表(可选) |
| | | // 如果不配置,则格子编号直接作为位置值 |
| | |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 规划玻璃装载 |
| | | * @param source 源玻璃列表 |
| | | * @param vehicleCapacity 车辆容量 |
| | | * @param deviceId 设备ID(用于日志) |
| | | * @return 规划后的玻璃列表,如果玻璃没有长度则返回null(用于测试MES程序) |
| | | */ |
| | | private List<GlassInfo> planGlassLoading(List<GlassInfo> source, |
| | | int vehicleCapacity, |
| | | Integer defaultGlassLength) { |
| | | String deviceId) { |
| | | List<GlassInfo> 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; |
| | | 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; |
| | |
| | | // 这里简化处理:如果startSlot不在positionMapping中,且是数字,可能是格子编号 |
| | | // 可以通过配置指定格子编号范围,或者通过查找同组设备判断 |
| | | |
| | | // 方法3:通过配置指定出片任务的startSlot范围 |
| | | // 方法3:通过配置指定车辆运动格子范围(兼容旧配置outboundSlotRanges) |
| | | @SuppressWarnings("unchecked") |
| | | List<Integer> 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); |
| | | List<Integer> 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; |
| | | } |
| | |
| | | */ |
| | | private TimeCalculation calculateTime(Integer currentPos, Integer startPos, |
| | | Integer targetPos, Map<String, Object> logicParams) { |
| | | // 验证车辆运动格子范围 |
| | | @SuppressWarnings("unchecked") |
| | | List<Integer> 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) { |
| | |
| | | Thread.currentThread().interrupt(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 通知卧转立扫码设备暂停或继续 |
| | | * @param params 参数,包含_taskContext引用 |
| | | * @param pause true=暂停,false=继续 |
| | | */ |
| | | private void notifyScannerPause(Map<String, Object> 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,无法通知卧转立扫码设备暂停"); |
| | | } |
| | | } |
| | | } |
| | | |