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<WrkMast> 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<Integer> getAllOutOrderList() {
|
List<Integer> list = new ArrayList<>();
|
List<BasDevp> basDevps = basDevpService.list(new QueryWrapper<BasDevp>());
|
for (BasDevp basDevp : basDevps) {
|
List<Integer> orderList = basDevp.getOutOrderIntList();
|
list.addAll(orderList);
|
}
|
return list;
|
}
|
|
public OutOrderDispatchDecision resolveOutboundDispatchDecision(Integer currentStationId,
|
WrkMast wrkMast,
|
List<Integer> 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<Integer> 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<NavigateNode> 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<WrkMast> loadActiveBatchTaskList(String batch) {
|
if (Cools.isEmpty(batch)) {
|
return Collections.emptyList();
|
}
|
return wrkMastService.list(new QueryWrapper<WrkMast>()
|
.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<Integer> 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<Integer> 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<Integer> outOrderStationIds) {
|
return currentStationId != null
|
&& outOrderStationIds != null
|
&& outOrderStationIds.contains(currentStationId);
|
}
|
|
private OutOrderDispatchDecision resolveCurrentOutOrderDispatchDecision(Integer currentStationId,
|
WrkMast wrkMast,
|
List<Integer> outOrderStationIds,
|
Double pathLenFactor) {
|
if (!isCurrentOutOrderDispatchStation(currentStationId, wrkMast, outOrderStationIds, pathLenFactor)) {
|
return null;
|
}
|
|
List<WrkMast> batchWrkList = wrkMastService.list(new QueryWrapper<WrkMast>()
|
.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<NavigateNode> 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<Integer> 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<Integer> outOrderList,
|
Double pathLenFactor) {
|
if (finalTargetStationId == null) {
|
return null;
|
}
|
if (sourceStationId == null || outOrderList == null || outOrderList.isEmpty()) {
|
return finalTargetStationId;
|
}
|
|
try {
|
List<NavigateNode> 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<NavigateNode> 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<BasStation>().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<Integer> 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<CircleTargetCandidate> 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<NavigateNode> 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<CircleTargetCandidate>() {
|
@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<CircleTargetCandidate> candidateList,
|
Double pathLenFactor) {
|
if (candidateList == null || candidateList.isEmpty()) {
|
return null;
|
}
|
|
List<CircleTargetCandidate> 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<NavigateNode> pathList, Integer searchStationId, String searchBatch) {
|
if (pathList == null || pathList.isEmpty() || searchStationId == null || Cools.isEmpty(searchBatch)) {
|
return null;
|
}
|
|
List<Integer> 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<String, Integer> batchMap = new HashMap<>();
|
for (Integer station : checkList) {
|
BasStation basStation = basStationService.getOne(new QueryWrapper<BasStation>().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();
|
}
|
}
|