package com.zy.acs.manager.core.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.zy.acs.framework.common.Cools; import com.zy.acs.manager.common.utils.CommonUtil; import com.zy.acs.manager.core.domain.AgvCntDto; import com.zy.acs.manager.core.domain.FilterLaneDto; import com.zy.acs.manager.core.domain.LaneDto; import com.zy.acs.manager.core.domain.TaskPosDto; import com.zy.acs.manager.manager.entity.*; import com.zy.acs.manager.manager.enums.*; import com.zy.acs.manager.manager.service.*; import com.zy.acs.manager.system.service.ConfigService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.stream.Collectors; /** * Created by vincent on 8/12/2024 */ @Slf4j @Service public class AllocateService { public static final Integer OUTBOUND_TASKS_ALLOCATE_LIMIT = 5; @Autowired private AgvService agvService; @Autowired private AgvDetailService agvDetailService; @Autowired private AgvModelService agvModelService; @Autowired private ConfigService configService; @Autowired private TaskService taskService; @Autowired private CodeService codeService; @Autowired private StaService staService; @Autowired private LocService locService; @Autowired private LaneBuilder laneBuilder; @Autowired private AgvAreaDispatcher agvAreaDispatcher; @Autowired private SegmentService segmentService; /** * get available agv list which is idle */ private List getAvailableAgvNos(List agvIds, boolean hasRunning) { List agvList = Cools.isEmpty(agvIds) ? agvService.list(new LambdaQueryWrapper().eq(Agv::getStatus, StatusType.ENABLE.val)) : agvIds.stream().map(agvService::getById).filter(Agv::getStatusBool).collect(Collectors.toList()); List result = new ArrayList<>(); for (Agv agv : agvList) { if (!hasRunning) { // 1. without running tasks if (0 < taskService.count(new LambdaQueryWrapper() .eq(Task::getAgvId, agv.getId()) .and(i -> i .eq(Task::getTaskSts, TaskStsType.ASSIGN.val()) .or() .eq(Task::getTaskSts, TaskStsType.PROGRESS.val()) ) )) { continue; } } // 2. in idle status if (!agvService.judgeEnable(agv.getId(), true)) { continue; } result.add(agv.getUuid()); } if (!Cools.isEmpty(result)) { Collections.shuffle(result); } return result; } @Transactional(rollbackFor = Exception.class) public synchronized String execute(Task task, AllocateSupport inbound, AllocateSupport normal) { // inbound roller station Sta rollerOriSta = getInboundRollerSta(task); String inboundAgv = tryAllocateForRoller(task, rollerOriSta, true); if (!Cools.isEmpty(inboundAgv)) { inbound.success(task, inboundAgv, rollerOriSta); return inboundAgv; } // outbound roller station Sta rollerDestSta = getOutboundRollerSta(task); String outboundAgv = tryAllocateForRoller(task, rollerDestSta, false); if (!Cools.isEmpty(outboundAgv)) { normal.success(task, outboundAgv, rollerDestSta); return outboundAgv; } String normalAgv = this.normalExecute(task); if (!Cools.isEmpty(normalAgv)) { normal.success(task, normalAgv, null); return normalAgv; } return null; } private String tryAllocateForRoller(Task task, Sta rollerSta, boolean inbound) { if (rollerSta == null) { return null; } List availableAgvNos = this.getAvailableAgvNos(agvAreaDispatcher.getAgvNosByTask(task), true); FilterLaneDto filterLaneDto = this.filterThroughLane(task, availableAgvNos); if (filterLaneDto == null) { return null; } String agvNo = inbound ? this.checkoutAgvForInboundRoller(task, rollerSta, filterLaneDto.getActualAvailableAgvNos()) : this.checkoutAgvForOutboundRoller(task, rollerSta, filterLaneDto.getActualAvailableAgvNos()); if (Cools.isEmpty(agvNo)) { return null; } // record lane hash for later dispatch/traffic-control logic if (filterLaneDto.getOriginLaneDto() != null) { task.setOriLaneHash(filterLaneDto.getOriginLaneDto().getHashCode()); } if (filterLaneDto.getDestinationLaneDto() != null) { task.setDestLaneHash(filterLaneDto.getDestinationLaneDto().getHashCode()); } return agvNo; } /** * 1. 判断task的起始点和目的点所在的巷道承载任务数量, * 如果数量已经达到负载,则判断负载任务的AGV是否还有空背篓,如果有则优先派发给它, * 如果没有了,那么则阻塞任务,直到该巷道释放 * 2. 轮询空闲小车,目标是让每台小车都动起来 * 判断逻辑:背篓数量最少的小车轮询的时候,优先级最高 * * it can break the limit of the number of agv backpack */ public synchronized String normalExecute(Task task) { List availableAgvNos = this.getAvailableAgvNos(agvAreaDispatcher.getAgvNosByTask(task), false); // List availableAgvNos = this.getAvailableAgvNos(null); if (Cools.isEmpty(availableAgvNos)) { // log.warn("No available agv to assign the task[{}]", task.getSeqNum()); return null; } // calc lane FilterLaneDto filterLaneDto = this.filterThroughLane(task, availableAgvNos); if (null == filterLaneDto) { return null; } LaneDto originLaneDto = filterLaneDto.getOriginLaneDto(); LaneDto destinationLaneDto = filterLaneDto.getDestinationLaneDto(); List actualAvailableAgvNos = filterLaneDto.getActualAvailableAgvNos(); if (Cools.isEmpty(actualAvailableAgvNos)) { return null; } // choose min number of running task actualAvailableAgvNos.sort((o1, o2) -> Integer.compare( calcAllocateWeight(o2, task), calcAllocateWeight(o1, task) )); if (null != originLaneDto) { task.setOriLaneHash(originLaneDto.getHashCode()); } if (null != destinationLaneDto) { task.setDestLaneHash(destinationLaneDto.getHashCode()); } return actualAvailableAgvNos.get(0); } private String checkoutAgvForInboundRoller(Task task, Sta sta, List availableAgvNos) { if (Cools.isEmpty(availableAgvNos, task, sta)) { return null; } for (String agvNo : availableAgvNos) { Long agvId = agvService.getAgvId(agvNo); Code currentCode = agvDetailService.getCurrentCode(agvId); if (null == currentCode) { continue; } // only checkout the agv which at sta code position if (!sta.getCode().equals(currentCode.getId())) { continue; } // has running task and within oriSta // int taskCnt = taskService.count(new LambdaQueryWrapper() // .eq(Task::getAgvId, agvId) // .eq(Task::getOriSta, sta.getId()) // .and(wrapper -> wrapper // .eq(Task::getTaskSts, TaskStsType.ASSIGN.val()) // .or() // .eq(Task::getTaskSts, TaskStsType.PROGRESS.val()) // ) // ); // if (taskCnt == 0) { // continue; // } // in TransferStationHandler.hasDelayAtSta Segment currSeg = segmentService.getRollerWaiting(agvId, sta.getCode(), TaskPosDto.PosType.ORI_STA); if (null == currSeg) { continue; } // has enough backpack space to load Integer backpack = agvService.getBackpack(agvId); List usedBackpacks = segmentService.selectUsedBackpacks(null, agvId); if (usedBackpacks.size() >= backpack) { continue; } return agvNo; } return null; } private String checkoutAgvForOutboundRoller(Task task, Sta sta, List availableAgvNos) { if (Cools.isEmpty(availableAgvNos, task, sta)) { return null; } List taskList = taskService.list(new LambdaQueryWrapper() .eq(Task::getDestSta, sta.getId()) .eq(Task::getTaskSts, TaskStsType.WAITING.val()) .isNotNull(Task::getAgvId) ); if (Cools.isEmpty(taskList)) { return null; } List cntDtoList = new ArrayList<>(); for (Task t : taskList) { AgvCntDto cntDto = new AgvCntDto(t.getAgvId()); if (AgvCntDto.has(cntDtoList, cntDto)) { AgvCntDto dto = AgvCntDto.find(cntDtoList, cntDto); assert null != dto; dto.setCount(dto.getCount() + 1); } else { cntDtoList.add(cntDto); } } cntDtoList.sort(new Comparator() { @Override public int compare(AgvCntDto o1, AgvCntDto o2) { return o1.getCount() - o2.getCount(); } }); for (AgvCntDto cntDto : cntDtoList) { if (cntDto.getAgvId() >= OUTBOUND_TASKS_ALLOCATE_LIMIT) { continue; } return agvService.getAgvNo(cntDto.getAgvId()); } return null; } public FilterLaneDto filterThroughLane(Task task, List availableAgvNos) { if (Cools.isEmpty(availableAgvNos, task)) { return null; } Integer maxAgvCountInLane = configService.getVal("maxAgvCountInLane", Integer.class); // checkout lane LaneDto originLaneDto = taskService.checkoutOriginLane(task); LaneDto destinationLaneDto = taskService.checkoutDestinationLane(task); // allocate about origin List availableAgvNosByOriLane = new ArrayList<>(availableAgvNos); if (null != originLaneDto) { List agvNosByOriLane = findAgvNosByLane(originLaneDto); // the agv list that had tasks in this lane // if full lane if (agvNosByOriLane.size() >= maxAgvCountInLane) { availableAgvNosByOriLane = Cools.getIntersection(agvNosByOriLane, availableAgvNos); } } // valid backpack limit availableAgvNosByOriLane = this.validBackpackLimit(availableAgvNosByOriLane); // allocate about destination List availableAgvNosByDestLane = new ArrayList<>(availableAgvNos); if (null != destinationLaneDto) { List agvNosByDestLane = findAgvNosByLane(destinationLaneDto); if (agvNosByDestLane.size() >= maxAgvCountInLane) { availableAgvNosByDestLane = Cools.getIntersection(agvNosByDestLane, availableAgvNos); } } availableAgvNosByDestLane = this.validBackpackLimit(availableAgvNosByDestLane); // valid if (Cools.isEmpty(availableAgvNosByOriLane)) { log.warn("No available agv to assign the task origin[{}]", task.getSeqNum()); return null; } if (Cools.isEmpty(availableAgvNosByDestLane)) { log.warn("No available agv to assign the task destination[{}]", task.getSeqNum()); return null; } List actualAvailableAgvNos = Cools.getIntersection(availableAgvNosByOriLane, availableAgvNosByDestLane); if (Cools.isEmpty(actualAvailableAgvNos)) { log.warn("No available agv to assign the task[{}]", task.getSeqNum()); return null; } return new FilterLaneDto(originLaneDto, destinationLaneDto, actualAvailableAgvNos); } public List findAgvNosByLane(LaneDto laneDto) { if (null == laneDto) { return new ArrayList<>(); } List taskList = taskService.findRunningTasksByLaneHash(laneDto.getHashCode()); if (Cools.isEmpty(taskList)) { return new ArrayList<>(); } return taskList.stream() .map(task -> agvService.getById(task.getAgvId()).getUuid()) .distinct() .collect(Collectors.toList()); } private List validBackpackLimit(List agvNoList) { if (Cools.isEmpty(agvNoList)) { return new ArrayList<>(); } return agvNoList.stream().filter(agvNo -> { Long agvId = agvService.getAgvId(agvNo); int transportTasksCount = taskService.findTransportTasksCountByAgv(agvId); AgvModel agvModel = agvModelService.getByAgvNo(agvNo); return transportTasksCount < agvModel.getBackpack(); }).collect(Collectors.toList()); } // calculate wight = backpack + distance private int calcAllocateWeight(String agvNo, Task task) { int weight = 0; Long agvId = agvService.getAgvId(agvNo); // backpack Integer transportTasksCount = taskService.findTransportTasksCountByAgv(agvId); if (!Cools.isEmpty(transportTasksCount)) { weight = weight + transportTasksCount * 100000; } // distance // from AgvDetail agvDetail = agvDetailService.selectByAgvId(agvId); Code agvCurrCode = codeService.getCacheById(agvDetail.getRecentCode()); Double[] fromPosition = new Double[]{agvCurrCode.getX(), agvCurrCode.getY()}; // to Code firstCode = null; TaskTypeType typeType = TaskTypeType.get(task.getTaskTypeEl()); switch (Objects.requireNonNull(typeType)) { case LOC_TO_LOC: case LOC_TO_STA: Loc oriLoc = locService.getById(task.getOriLoc()); firstCode = codeService.getCacheById(oriLoc.getCode()); break; case STA_TO_LOC: case STA_TO_STA: Sta oriSta = staService.getById(task.getOriSta()); firstCode = codeService.getCacheById(oriSta.getCode()); break; case TO_CHARGE: case TO_STANDBY: case MOVE: firstCode = codeService.getCacheById(task.getDestCode()); break; default: firstCode = codeService.getCacheById(task.getDestCode()); break; } assert null != firstCode; Double[] toPosition = new Double[]{firstCode.getX(), firstCode.getY()}; // calculate distance weight = weight + CommonUtil.calcDistance(fromPosition, toPosition); // return opposite return -weight; } public Boolean validCapacityOfLane(String agvNo, Code code) { LaneDto laneDto = laneBuilder.search(code.getData()); if (null != laneDto) { Integer maxAgvCountInLane = configService.getVal("maxAgvCountInLane", Integer.class); List agvNosByLane = this.findAgvNosByLane(laneDto); agvNosByLane.remove(agvNo); if (agvNosByLane.size() >= maxAgvCountInLane) { return false; } } return true; } // The Permutations and combinations for task public Double[] pac(Double[] currPosition, List> list) { List theFirstOne = list.get(0); List theLastOne = list.get(list.size() - 1); if (list.size() == 1) { TaskPosDto head = theFirstOne.get(0); TaskPosDto tail = theFirstOne.get(theFirstOne.size() - 1); int distanceByHead = CommonUtil.calcDistance(currPosition, head.getXy()); int distanceByTail = CommonUtil.calcDistance(currPosition, tail.getXy()); if (distanceByTail < distanceByHead) { Collections.reverse(theFirstOne); } } else { TaskPosDto headOfFirst = theFirstOne.get(0); TaskPosDto tailOfFirst = theFirstOne.get(theFirstOne.size() - 1); TaskPosDto headOfLast = theLastOne.get(0); TaskPosDto tailOfLast = theLastOne.get(theLastOne.size() - 1); int distanceByHeadOfFirst = CommonUtil.calcDistance(currPosition, headOfFirst.getXy()); int distanceByTailOfFirst = CommonUtil.calcDistance(currPosition, tailOfFirst.getXy()); int distanceByHeadOfLast = CommonUtil.calcDistance(currPosition, headOfLast.getXy()); int distanceByTailOfLast = CommonUtil.calcDistance(currPosition, tailOfLast.getXy()); if (Math.min(distanceByHeadOfLast, distanceByTailOfLast) < Math.min(distanceByHeadOfFirst, distanceByTailOfFirst)) { Collections.reverse(list); if (distanceByTailOfLast < distanceByHeadOfLast) { Collections.reverse(theLastOne); } } else { if (distanceByTailOfFirst < distanceByHeadOfFirst) { Collections.reverse(theFirstOne); } } } theLastOne = list.get(list.size() - 1); return theLastOne.get(theLastOne.size() - 1).getXy(); } // about roller -------------------------------------------- private Sta getInboundRollerSta(Task task) { TaskTypeType type = TaskTypeType.get(task.getTaskTypeEl()); switch (Objects.requireNonNull(type)) { case STA_TO_LOC: case STA_TO_STA: Long oriStaId = task.getOriSta(); if (null == oriStaId) { return null; } Sta oriSta = staService.getById(oriStaId); if (oriSta == null || Cools.isEmpty(oriSta.getStaType())) { return null; } if (StaTypeType.ROLLER.val() != oriSta.getStaType()) { return null; } return oriSta; default: return null; } } private Sta getOutboundRollerSta(Task task) { TaskTypeType type = TaskTypeType.get(task.getTaskTypeEl()); switch (Objects.requireNonNull(type)) { case LOC_TO_STA: case STA_TO_STA: Long destStaId = task.getDestSta(); if (null == destStaId) { return null; } Sta destSta = staService.getById(destStaId); if (destSta == null || Cools.isEmpty(destSta.getStaType())) { return null; } if (StaTypeType.ROLLER.val() != destSta.getStaType()) { return null; } return destSta; default: return null; } } }