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.Lane;
|
import com.zy.acs.manager.core.domain.TaskPosDto;
|
import com.zy.acs.manager.manager.entity.*;
|
import com.zy.acs.manager.manager.enums.StatusType;
|
import com.zy.acs.manager.manager.enums.TaskStsType;
|
import com.zy.acs.manager.manager.enums.TaskTypeType;
|
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 java.util.*;
|
import java.util.stream.Collectors;
|
|
/**
|
* Created by vincent on 8/12/2024
|
*/
|
@Slf4j
|
@Service
|
public class AllocateService {
|
|
@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 LaneService laneService;
|
|
/**
|
* get available agv list which is idle
|
*/
|
private List<Agv> getAvailableAgv() {
|
List<Agv> result = new ArrayList<>();
|
List<Agv> agvList = agvService.list(new LambdaQueryWrapper<Agv>().eq(Agv::getStatus, StatusType.ENABLE.val));
|
Collections.shuffle(agvList);
|
for (Agv agv : agvList) {
|
|
// 1. without running tasks
|
if (0 < taskService.count(new LambdaQueryWrapper<Task>()
|
.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);
|
}
|
|
return result;
|
}
|
|
/**
|
* 1. 判断task的起始点和目的点所在的巷道承载任务数量,
|
* 如果数量已经达到负载,则判断负载任务的AGV是否还有空背篓,如果有则优先派发给它,
|
* 如果没有了,那么则阻塞任务,直到该巷道释放
|
* 2. 轮询空闲小车,目标是让每台小车都动起来
|
* 判断逻辑:背篓数量最少的小车轮询的时候,优先级最高
|
*
|
* it can break the limit of the number of agv backpack
|
*/
|
public synchronized Agv execute(Task task) {
|
List<Agv> availableAgvList = getAvailableAgv();
|
if (Cools.isEmpty(availableAgvList)) {
|
// log.warn("No available agv to assign the task[{}]", task.getSeqNum());
|
return null;
|
}
|
List<String> availableAgvNos = availableAgvList.stream().map(Agv::getUuid).distinct().collect(Collectors.toList());
|
|
Integer maxAgvCountInLane = configService.getVal("maxAgvCountInLane", Integer.class);
|
|
// checkout lane
|
Lane originLane = taskService.checkoutOriginLane(task);
|
Lane destinationLane = taskService.checkoutDestinationLane(task);
|
|
|
// allocate about origin
|
List<String> availableAgvNosByOriLane = new ArrayList<>(availableAgvNos);
|
if (null != originLane) {
|
List<String> agvNosByOriLane = findAgvNosByLane(originLane); // 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<String> availableAgvNosByDestLane = new ArrayList<>(availableAgvNos);
|
if (null != destinationLane) {
|
List<String> agvNosByDestLane = findAgvNosByLane(destinationLane);
|
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<String> actualAvailableAgvNos = Cools.getIntersection(availableAgvNosByOriLane, availableAgvNosByDestLane);
|
if (Cools.isEmpty(actualAvailableAgvNos)) {
|
log.warn("No available agv to assign the task[{}]", task.getSeqNum());
|
return null;
|
}
|
|
// choose min number of running task
|
actualAvailableAgvNos.sort(new Comparator<String>() {
|
@Override
|
public int compare(String agvNo1, String agvNo2) {
|
return calcAllocateWeight(agvNo1, task) - calcAllocateWeight(agvNo2, task);
|
}
|
});
|
|
|
if (null != originLane) {
|
task.setOriLaneHash(originLane.getHashCode());
|
}
|
if (null != destinationLane) {
|
task.setDestLaneHash(destinationLane.getHashCode());
|
}
|
|
return agvService.selectByUuid(actualAvailableAgvNos.stream().findFirst().orElse(null));
|
}
|
|
public List<String> findAgvNosByLane(Lane lane) {
|
if (null == lane) {
|
return new ArrayList<>();
|
}
|
List<Task> taskList = taskService.findRunningTasksByLaneHash(lane.getHashCode());
|
if (Cools.isEmpty(taskList)) {
|
return new ArrayList<>();
|
}
|
return taskList.stream()
|
.map(task -> agvService.getById(task.getAgvId()).getUuid())
|
.distinct()
|
.collect(Collectors.toList());
|
}
|
|
private List<String> validBackpackLimit(List<String> agvNoList) {
|
if (Cools.isEmpty(agvNoList)) {
|
return new ArrayList<>();
|
}
|
return agvNoList.stream().filter(agvNo -> {
|
Agv agv = agvService.selectByUuid(agvNo);
|
AgvModel agvModel = agvModelService.getById(agv.getAgvModel());
|
List<Task> transportTasks = taskService.findTransportTasksByAgv(agv.getId());
|
return transportTasks.size() < 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
|
List<Task> transportTasks = taskService.findTransportTasksByAgv(agvId);
|
if (!Cools.isEmpty(transportTasks)) {
|
weight = weight + transportTasks.size() * 100000;
|
}
|
|
// distance
|
// from
|
AgvDetail agvDetail = agvDetailService.selectByAgvId(agvId);
|
Code agvCurrCode = codeService.getById(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.getById(oriLoc.getCode());
|
break;
|
case STA_TO_LOC:
|
case STA_TO_STA:
|
Sta oriSta = staService.getById(task.getOriSta());
|
firstCode = codeService.getById(oriSta.getCode());
|
break;
|
case TO_CHARGE:
|
case TO_STANDBY:
|
case MOVE:
|
firstCode = codeService.getById(task.getDestCode());
|
break;
|
default:
|
firstCode = codeService.getById(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(Agv agv, Code code) {
|
Lane lane = laneService.search(code.getData());
|
if (null != lane) {
|
Integer maxAgvCountInLane = configService.getVal("maxAgvCountInLane", Integer.class);
|
|
List<String> agvNosByLane = this.findAgvNosByLane(lane);
|
agvNosByLane.remove(agv.getUuid());
|
if (agvNosByLane.size() >= maxAgvCountInLane) {
|
return false;
|
}
|
}
|
|
return true;
|
}
|
|
|
// The Permutations and combinations for task
|
|
public Double[] pac(Double[] currPosition, List<List<TaskPosDto>> list) {
|
List<TaskPosDto> theFirstOne = list.get(0);
|
List<TaskPosDto> 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();
|
}
|
|
}
|