package com.zy.core.utils.station; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.core.common.Cools; import com.zy.asrs.entity.BasDevp; import com.zy.asrs.entity.BasStation; import com.zy.asrs.entity.WrkMast; import com.zy.asrs.service.BasDevpService; import com.zy.asrs.service.BasStationService; import com.zy.asrs.service.WrkMastService; import com.zy.common.model.NavigateNode; import com.zy.common.utils.NavigateUtils; import com.zy.core.News; import com.zy.core.cache.SlaveConnection; import com.zy.core.enums.SlaveType; import com.zy.core.enums.StationCommandType; import com.zy.core.enums.WrkIoType; import com.zy.core.enums.WrkStsType; import com.zy.core.model.command.StationCommand; import com.zy.core.model.protocol.StationProtocol; import com.zy.core.move.StationMoveCoordinator; import com.zy.core.move.StationMoveDispatchMode; import com.zy.core.move.StationMoveSession; import com.zy.core.service.StationTaskLoopService; import com.zy.core.thread.StationThread; import com.zy.core.utils.station.model.CircleTargetCandidate; import com.zy.core.utils.station.model.OutOrderDispatchDecision; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @Component public class StationOutboundDecisionSupport { @Autowired private WrkMastService wrkMastService; @Autowired private BasDevpService basDevpService; @Autowired private BasStationService basStationService; @Autowired private NavigateUtils navigateUtils; @Autowired private StationTaskLoopService stationTaskLoopService; @Autowired private StationMoveCoordinator stationMoveCoordinator; @Autowired private StationDispatchRuntimeStateSupport stationDispatchRuntimeStateSupport; public StationCommand buildOutboundMoveCommand(StationThread stationThread, WrkMast wrkMast, Integer stationId, Integer targetStationId, Double pathLenFactor) { if (stationThread == null || wrkMast == null) { return null; } return stationThread.getCommand( StationCommandType.MOVE, wrkMast.getWrkNo(), stationId, targetStationId, 0, normalizePathLenFactor(pathLenFactor) ); } public Double resolveOutboundPathLenFactor(WrkMast wrkMast) { if (!isBatchOutboundTaskWithSeq(wrkMast)) { return 0.0d; } List activeBatchTaskList = loadActiveBatchTaskList(wrkMast.getBatch()); if (activeBatchTaskList.size() <= 1) { return 0.0d; } int activeTaskCount = 0; int predecessorCount = 0; for (WrkMast item : activeBatchTaskList) { if (!isFactorCandidateTask(item)) { continue; } activeTaskCount++; if (item.getBatchSeq() < wrkMast.getBatchSeq()) { predecessorCount++; } } if (activeTaskCount <= 1 || predecessorCount <= 0) { return 0.0d; } return normalizePathLenFactor((double) predecessorCount / (double) (activeTaskCount - 1)); } public List getAllOutOrderList() { List list = new ArrayList<>(); List basDevps = basDevpService.list(new QueryWrapper()); for (BasDevp basDevp : basDevps) { List orderList = basDevp.getOutOrderIntList(); list.addAll(orderList); } return list; } public OutOrderDispatchDecision resolveOutboundDispatchDecision(Integer currentStationId, WrkMast wrkMast, List outOrderStationIds, Double pathLenFactor) { if (wrkMast == null || wrkMast.getStaNo() == null) { return null; } if (!shouldApplyOutOrder(wrkMast, outOrderStationIds)) { return OutOrderDispatchDecision.direct(wrkMast.getStaNo()); } Integer dispatchStationId = resolveDispatchOutOrderTarget( wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), outOrderStationIds, pathLenFactor ); if (dispatchStationId == null) { return null; } if (isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) { return resolveCurrentOutOrderDispatchDecision(currentStationId, wrkMast, outOrderStationIds, pathLenFactor); } if (!Objects.equals(dispatchStationId, wrkMast.getStaNo()) && isCurrentOutOrderStation(currentStationId, outOrderStationIds) && isWatchingCircleArrival(wrkMast.getWrkNo(), currentStationId)) { return OutOrderDispatchDecision.circle(dispatchStationId, null, false); } return OutOrderDispatchDecision.direct(dispatchStationId); } public void syncOutOrderWatchState(WrkMast wrkMast, Integer currentStationId, List outOrderStationIds, OutOrderDispatchDecision dispatchDecision, StationCommand command) { if (dispatchDecision == null || command == null || !shouldApplyOutOrder(wrkMast, outOrderStationIds)) { return; } if (dispatchDecision.isCircle()) { stationDispatchRuntimeStateSupport.saveWatchCircleCommand(wrkMast.getWrkNo(), command); if (dispatchDecision.shouldCountLoopIssue() && stationTaskLoopService != null && dispatchDecision.getLoopEvaluation() != null) { stationTaskLoopService.recordLoopIssue(dispatchDecision.getLoopEvaluation(), "OUT_ORDER_CIRCLE"); } } else { stationDispatchRuntimeStateSupport.clearWatchCircleCommand(wrkMast.getWrkNo()); } } public boolean shouldSkipOutOrderDispatchForExistingRoute(Integer wrkNo, Integer stationId) { if (stationMoveCoordinator == null || wrkNo == null || wrkNo <= 0 || stationId == null) { return false; } StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo); if (session == null) { return false; } if (!session.isActive() || !session.containsStation(stationId)) { return false; } if (StationMoveDispatchMode.CIRCLE == session.getDispatchMode()) { return true; } return !Objects.equals(stationId, session.getCurrentRouteTargetStationId()); } public boolean isWatchingCircleArrival(Integer wrkNo, Integer stationId) { if (stationMoveCoordinator != null) { StationMoveSession session = stationMoveCoordinator.loadSession(wrkNo); if (session != null && session.isActive() && stationId != null) { if (stationId.equals(session.getNextDecisionStationId())) { return true; } if (session.containsStation(stationId)) { return false; } } } StationCommand command = stationDispatchRuntimeStateSupport.loadWatchCircleCommand(wrkNo); return command != null && stationId != null && stationId.equals(command.getTargetStaNo()); } private List calcOutboundNavigatePath(WrkMast wrkMast, Integer sourceStationId, Integer targetStationId, Double pathLenFactor) { Double normalizedFactor = normalizePathLenFactor(pathLenFactor); Integer currentTaskNo = wrkMast == null ? null : wrkMast.getWrkNo(); if (currentTaskNo == null) { return navigateUtils.calcByStationId(sourceStationId, targetStationId, normalizedFactor); } return navigateUtils.calcByStationId(sourceStationId, targetStationId, currentTaskNo, normalizedFactor); } private boolean isBatchOutboundTaskWithSeq(WrkMast wrkMast) { return wrkMast != null && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) && !Cools.isEmpty(wrkMast.getBatch()) && wrkMast.getBatchSeq() != null && wrkMast.getWrkNo() != null; } private List loadActiveBatchTaskList(String batch) { if (Cools.isEmpty(batch)) { return Collections.emptyList(); } return wrkMastService.list(new QueryWrapper() .eq("io_type", WrkIoType.OUT.id) .eq("batch", batch) .notIn("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts, WrkStsType.COMPLETE_OUTBOUND.sts, WrkStsType.SETTLE_OUTBOUND.sts)); } private boolean isFactorCandidateTask(WrkMast wrkMast) { return wrkMast != null && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) && wrkMast.getBatchSeq() != null && !"taskCancel".equals(wrkMast.getMk()); } private boolean shouldApplyOutOrder(WrkMast wrkMast, List outOrderStationIds) { return wrkMast != null && wrkMast.getStaNo() != null && Objects.equals(wrkMast.getIoType(), WrkIoType.OUT.id) && !Cools.isEmpty(wrkMast.getBatch()) && wrkMast.getBatchSeq() != null && outOrderStationIds != null && !outOrderStationIds.isEmpty(); } private boolean isCurrentOutOrderDispatchStation(Integer currentStationId, WrkMast wrkMast, List outOrderStationIds, Double pathLenFactor) { if (!shouldApplyOutOrder(wrkMast, outOrderStationIds) || currentStationId == null) { return false; } Integer dispatchStationId = resolveDispatchOutOrderTarget( wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), outOrderStationIds, pathLenFactor ); return dispatchStationId != null && !Objects.equals(dispatchStationId, wrkMast.getStaNo()) && Objects.equals(currentStationId, dispatchStationId); } private boolean isCurrentOutOrderStation(Integer currentStationId, List outOrderStationIds) { return currentStationId != null && outOrderStationIds != null && outOrderStationIds.contains(currentStationId); } private OutOrderDispatchDecision resolveCurrentOutOrderDispatchDecision(Integer currentStationId, WrkMast wrkMast, List outOrderStationIds, Double pathLenFactor) { if (!isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) { return null; } List batchWrkList = wrkMastService.list(new QueryWrapper() .eq("io_type", WrkIoType.OUT.id) .notIn("wrk_sts", WrkStsType.STATION_RUN_COMPLETE.sts, WrkStsType.COMPLETE_OUTBOUND.sts, WrkStsType.SETTLE_OUTBOUND.sts) .eq("batch", wrkMast.getBatch()) .orderByAsc("batch_seq") .orderByAsc("wrk_no")); if (batchWrkList.isEmpty()) { return OutOrderDispatchDecision.direct(wrkMast.getStaNo()); } WrkMast firstWrkMast = batchWrkList.get(0); Integer currentBatchSeq = firstWrkMast.getBatchSeq(); if (currentBatchSeq == null) { News.taskInfo(wrkMast.getWrkNo(), "批次:{} 首个未完成任务缺少批次序号,当前任务暂不放行", wrkMast.getBatch()); return null; } List initPath; try { initPath = calcOutboundNavigatePath(wrkMast, wrkMast.getSourceStaNo(), wrkMast.getStaNo(), pathLenFactor); } catch (Exception e) { News.taskInfo(wrkMast.getWrkNo(), "批次:{} 计算排序路径失败,当前站点={}", wrkMast.getBatch(), currentStationId); return null; } Integer seq = getOutStationBatchSeq(initPath, currentStationId, wrkMast.getBatch()); boolean toTarget = seq == null ? currentBatchSeq.equals(wrkMast.getBatchSeq()) : Integer.valueOf(seq + 1).equals(wrkMast.getBatchSeq()); if (toTarget) { if (hasReachableOutReleaseSlot(wrkMast, currentStationId, wrkMast.getStaNo(), pathLenFactor)) { return OutOrderDispatchDecision.direct(wrkMast.getStaNo()); } StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop( wrkMast.getWrkNo(), currentStationId, outOrderStationIds ); Integer circleTarget = resolveNextCircleOrderTarget( wrkMast, currentStationId, outOrderStationIds, loopEvaluation.getExpectedLoopIssueCount(), pathLenFactor ); if (circleTarget == null) { News.taskInfo(wrkMast.getWrkNo(), "目标站当前不可进,且未找到可执行的下一排序检测点,当前站点={}", currentStationId); return null; } return OutOrderDispatchDecision.circle(circleTarget, loopEvaluation, true); } StationTaskLoopService.LoopEvaluation loopEvaluation = evaluateOutOrderLoop( wrkMast.getWrkNo(), currentStationId, outOrderStationIds ); Integer circleTarget = resolveNextCircleOrderTarget( wrkMast, currentStationId, outOrderStationIds, loopEvaluation.getExpectedLoopIssueCount(), pathLenFactor ); if (circleTarget == null) { News.taskInfo(wrkMast.getWrkNo(), "未找到可执行的下一排序检测点,当前站点={}", currentStationId); return null; } return OutOrderDispatchDecision.circle(circleTarget, loopEvaluation, true); } private StationTaskLoopService.LoopEvaluation evaluateOutOrderLoop(Integer taskNo, Integer currentStationId, List outOrderStationIds) { if (stationTaskLoopService == null) { return new StationTaskLoopService.LoopEvaluation( taskNo, currentStationId, StationTaskLoopService.LoopIdentitySnapshot.empty(), 0, 0, false ); } return stationTaskLoopService.evaluateLoop( taskNo, currentStationId, true, outOrderStationIds, "outOrderCircle" ); } private Integer resolveDispatchOutOrderTarget(WrkMast wrkMast, Integer sourceStationId, Integer finalTargetStationId, List outOrderList, Double pathLenFactor) { if (finalTargetStationId == null) { return null; } if (sourceStationId == null || outOrderList == null || outOrderList.isEmpty()) { return finalTargetStationId; } try { List nodes = calcOutboundNavigatePath(wrkMast, sourceStationId, finalTargetStationId, pathLenFactor); for (int i = nodes.size() - 1; i >= 0; i--) { Integer stationId = getStationIdFromNode(nodes.get(i)); if (stationId == null) { continue; } if (Objects.equals(stationId, finalTargetStationId)) { continue; } if (outOrderList.contains(stationId)) { return stationId; } } } catch (Exception ignore) { } return finalTargetStationId; } private boolean hasReachableOutReleaseSlot(WrkMast wrkMast, Integer currentStationId, Integer finalTargetStationId, Double pathLenFactor) { if (currentStationId == null || finalTargetStationId == null) { return true; } try { List nodes = calcOutboundNavigatePath(wrkMast, currentStationId, finalTargetStationId, pathLenFactor); if (nodes == null || nodes.isEmpty()) { return true; } for (NavigateNode node : nodes) { Integer stationId = getStationIdFromNode(node); if (stationId == null || Objects.equals(stationId, currentStationId)) { continue; } if (!isPathStationBlocked(stationId)) { return true; } } return false; } catch (Exception ignore) { return true; } } private boolean isPathStationBlocked(Integer stationId) { if (stationId == null) { return true; } BasStation basStation = basStationService.getOne(new QueryWrapper().eq("station_id", stationId)); if (basStation == null) { return true; } StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); if (stationThread == null) { return true; } StationProtocol stationProtocol = stationThread.getStatusMap().get(stationId); if (stationProtocol == null) { return true; } return !stationProtocol.isAutoing() || stationProtocol.isLoading() || (stationProtocol.getTaskNo() != null && stationProtocol.getTaskNo() > 0); } private Integer resolveNextCircleOrderTarget(WrkMast wrkMast, Integer currentStationId, List orderedOutStationList, Integer expectedLoopIssueCount, Double pathLenFactor) { if (currentStationId == null || orderedOutStationList == null || orderedOutStationList.size() <= 1) { return null; } int startIndex = orderedOutStationList.indexOf(currentStationId); int total = orderedOutStationList.size(); List candidateList = new ArrayList<>(); for (int offset = 1; offset < total; offset++) { int candidateIndex = (startIndex + offset + total) % total; Integer candidateStationId = orderedOutStationList.get(candidateIndex); if (candidateStationId == null || currentStationId.equals(candidateStationId)) { continue; } try { List path = calcOutboundNavigatePath(wrkMast, currentStationId, candidateStationId, pathLenFactor); if (path != null && !path.isEmpty()) { candidateList.add(new CircleTargetCandidate(candidateStationId, path.size(), offset)); } } catch (Exception ignore) { } } if (candidateList.isEmpty()) { return null; } candidateList.sort(new Comparator() { @Override public int compare(CircleTargetCandidate left, CircleTargetCandidate right) { if (left == right) { return 0; } if (left == null) { return 1; } if (right == null) { return -1; } int pathCompare = Integer.compare(left.getPathLength(), right.getPathLength()); if (pathCompare != 0) { return pathCompare; } return Integer.compare(left.getOffset(), right.getOffset()); } }); return resolveGradualCircleTargetByPathLength(expectedLoopIssueCount, candidateList, pathLenFactor); } private Integer resolveGradualCircleTargetByPathLength(Integer expectedLoopIssueCount, List candidateList, Double pathLenFactor) { if (candidateList == null || candidateList.isEmpty()) { return null; } List tierList = new ArrayList<>(); Integer lastPathLength = null; for (CircleTargetCandidate candidate : candidateList) { if (candidate == null) { continue; } if (lastPathLength == null || !Objects.equals(lastPathLength, candidate.getPathLength())) { tierList.add(candidate); lastPathLength = candidate.getPathLength(); } } if (tierList.isEmpty()) { return candidateList.get(0).getStationId(); } int defaultTierIndex = expectedLoopIssueCount == null || expectedLoopIssueCount <= 2 ? 0 : Math.min(expectedLoopIssueCount - 2, tierList.size() - 1); int factorTierIndex = (int) Math.round(normalizePathLenFactor(pathLenFactor) * (tierList.size() - 1)); int tierIndex = Math.max(defaultTierIndex, factorTierIndex); return tierList.get(tierIndex).getStationId(); } private Integer getOutStationBatchSeq(List pathList, Integer searchStationId, String searchBatch) { if (pathList == null || pathList.isEmpty() || searchStationId == null || Cools.isEmpty(searchBatch)) { return null; } List checkList = new ArrayList<>(); for (int i = pathList.size() - 1; i >= 0; i--) { NavigateNode node = pathList.get(i); JSONObject value = JSONObject.parseObject(node.getNodeValue()); if (value == null) { continue; } Integer stationId = value.getInteger("stationId"); if (searchStationId.equals(stationId)) { break; } checkList.add(stationId); } HashMap batchMap = new HashMap<>(); for (Integer station : checkList) { BasStation basStation = basStationService.getOne(new QueryWrapper().eq("station_id", station)); if (basStation == null) { continue; } StationThread stationThread = (StationThread) SlaveConnection.get(SlaveType.Devp, basStation.getDeviceNo()); if (stationThread == null) { continue; } StationProtocol checkStationProtocol = stationThread.getStatusMap().get(station); if (checkStationProtocol == null) { continue; } if (checkStationProtocol.getTaskNo() > 0) { WrkMast checkWrkMast = wrkMastService.selectByWorkNo(checkStationProtocol.getTaskNo()); if (checkWrkMast == null) { continue; } if (!Cools.isEmpty(checkWrkMast.getBatch())) { batchMap.put(checkWrkMast.getBatch(), checkWrkMast.getBatchSeq()); } } } return batchMap.get(searchBatch); } private Integer getStationIdFromNode(NavigateNode node) { if (node == null || isBlank(node.getNodeValue())) { return null; } try { JSONObject value = JSONObject.parseObject(node.getNodeValue()); return value == null ? null : value.getInteger("stationId"); } catch (Exception ignore) { return null; } } private Double normalizePathLenFactor(Double pathLenFactor) { if (pathLenFactor == null || pathLenFactor < 0.0d) { return 0.0d; } if (pathLenFactor > 1.0d) { return 1.0d; } return pathLenFactor; } private boolean isBlank(String value) { return value == null || value.trim().isEmpty(); } }