package com.zy.acs.manager.core.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.zy.acs.common.utils.RedisSupport;
|
import com.zy.acs.framework.common.Cools;
|
import com.zy.acs.framework.common.R;
|
import com.zy.acs.manager.core.cache.CoreCache;
|
import com.zy.acs.manager.core.domain.CodeStepDto;
|
import com.zy.acs.manager.core.domain.type.JobType;
|
import com.zy.acs.manager.manager.entity.Agv;
|
import com.zy.acs.manager.manager.entity.AgvDetail;
|
import com.zy.acs.manager.manager.entity.Code;
|
import com.zy.acs.manager.manager.entity.Travel;
|
import com.zy.acs.manager.manager.enums.StatusType;
|
import com.zy.acs.manager.manager.enums.TaskTypeType;
|
import com.zy.acs.manager.manager.enums.TravelStateType;
|
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 javax.annotation.PostConstruct;
|
import javax.annotation.PreDestroy;
|
import java.util.*;
|
import java.util.concurrent.*;
|
import java.util.stream.Collectors;
|
|
/**
|
* Created by vincent on 11/9/2024
|
*/
|
@Slf4j
|
@Service
|
public class PatrolService {
|
|
private static final int SCHEDULE_TIME_INTERVAL = 100;
|
|
public static final Map<String, ScheduledFuture<?>> AGV_PATROL_MAP = new ConcurrentHashMap<>();
|
|
private final RedisSupport redis = RedisSupport.defaultRedisSupport;
|
|
private ScheduledExecutorService scheduler = null;
|
|
private List<String> CODE_DATA_CACHE = new ArrayList<>();
|
|
private List<String> LOC_CODE_DATA_CACHE = new ArrayList<>();
|
|
private List<String> CONVEYOR_DROP_CODE_DATA_CACHE = new ArrayList<String>(){{
|
add("00000048");
|
}};
|
|
private List<String> CONVEYOR_PICK_CODE_DATA_CACHE = new ArrayList<String>(){{
|
add("00000146");
|
}};
|
|
@Autowired
|
private AgvService agvService;
|
@Autowired
|
private AgvDetailService agvDetailService;
|
@Autowired
|
private TaskService taskService;
|
@Autowired
|
private MainLockWrapService mainLockWrapService;
|
@Autowired
|
private CodeService codeService;
|
@Autowired
|
private MapService mapService;
|
@Autowired
|
private TravelService travelService;
|
@Autowired
|
private AllocateService allocateService;
|
@Autowired
|
private ConfigService configService;
|
@Autowired
|
private AgvModelService agvModelService;
|
|
// pick & drop logic --------------------------------------------------
|
|
private void executeUShapeConveyor(String agvNo) {
|
Long agvId = agvService.getAgvId(agvNo);
|
if (0 < travelService.count(new LambdaQueryWrapper<Travel>()
|
.eq(Travel::getAgvId, agvId)
|
.eq(Travel::getState, TravelStateType.RUNNING.toString()))) {
|
return;
|
}
|
if (!agvService.judgeEnable(agvId)) {
|
return;
|
}
|
|
JobType jobType = CoreCache.AGV_MOCK_JOB_CACHE.get(agvNo);
|
if (null == jobType) {
|
CoreCache.AGV_MOCK_JOB_CACHE.put(agvNo, JobType.LOC_PICK);
|
CoreCache.AGV_BACKPACK_USED_CACHE.put(agvId, 0);
|
jobType = JobType.LOC_PICK;
|
}
|
|
String destinationCodeData = null;
|
switch (jobType) {
|
case LOC_PICK:
|
// 5, 4, 3, 2, 1
|
int locPickRemaining = agvService.getBackpackRemainingCapacity(agvId);
|
if (0 < locPickRemaining) {
|
destinationCodeData = this.getLocCode(agvNo, null);
|
}
|
break;
|
case CONVEYOR_DROP:
|
destinationCodeData = this.getConveyorDropCode(agvNo);
|
break;
|
case CONVEYOR_PICK:
|
destinationCodeData = this.getConveyorPickCode(agvNo);
|
break;
|
case LOCK_DROP:
|
// 0, 1, 2, 3, 4
|
int locDropRemaining = agvService.getBackpackRemainingCapacity(agvId);
|
if (locDropRemaining < agvModelService.getByAgvId(agvId).getBackpack()) {
|
destinationCodeData = this.getLocCode(agvNo, null);
|
}
|
break;
|
default:
|
log.warn("AGV {}: 未知阶段: {}", agvNo, jobType);
|
break;
|
}
|
if (Cools.isEmpty(destinationCodeData)) {
|
return;
|
}
|
|
if (mainLockWrapService.buildMinorTask(agvId, TaskTypeType.MOVE, destinationCodeData, null)) {
|
log.info( "{}开始作业演示...", agvNo);
|
|
CoreCache.AGV_MOCK_STEP_CACHE.put(agvId, CodeStepDto.build(destinationCodeData, jobType));
|
}
|
}
|
|
public String getLocCode(String agvNo, AgvDetail agvDetail) {
|
if (LOC_CODE_DATA_CACHE.isEmpty()) {
|
return null;
|
}
|
Collections.shuffle(LOC_CODE_DATA_CACHE);
|
for (String locCodeData : LOC_CODE_DATA_CACHE) {
|
Code locCode = codeService.getCacheByData(locCodeData);
|
|
// valid lane
|
if (!allocateService.validCapacityOfLane(agvNo, locCode)) {
|
continue;
|
}
|
|
return locCodeData;
|
}
|
return LOC_CODE_DATA_CACHE.stream().findFirst().orElse(null);
|
}
|
|
public String getConveyorDropCode(String agvNo) {
|
return CONVEYOR_DROP_CODE_DATA_CACHE.stream().findFirst().orElse(null);
|
}
|
|
public String getConveyorPickCode(String agvNo) {
|
return CONVEYOR_PICK_CODE_DATA_CACHE.stream().findFirst().orElse(null);
|
}
|
|
|
// pure move logic --------------------------------------------------
|
|
private void executePatrolLogic(String agvNo) {
|
this.patrolOfMove(agvNo);
|
}
|
|
private void patrolOfMove(String agvNo) {
|
Long agvId = agvService.getAgvId(agvNo);
|
if (0 < travelService.count(new LambdaQueryWrapper<Travel>()
|
.eq(Travel::getAgvId, agvId)
|
.eq(Travel::getState, TravelStateType.RUNNING.toString()))) {
|
return;
|
}
|
if (!agvService.judgeEnable(agvId)) {
|
return;
|
}
|
AgvDetail agvDetail = agvDetailService.selectMajorByAgvId(agvId);
|
String destinationCodeData = this.getDestinationCode(agvNo, agvDetail);
|
if (Cools.isEmpty(destinationCodeData)) {
|
return;
|
}
|
if (mainLockWrapService.buildMinorTask(agvId, TaskTypeType.MOVE, destinationCodeData, null)) {
|
log.info( "{}开始走行演示...", agvNo);
|
}
|
}
|
|
/**
|
* 4个地方调用了buildMinorTask,在什么时候、哪里设置task的lane
|
* (
|
* HandlerController, 手动 (手动是否需要判断lane)
|
* MaintainScheduler, 自动 (一般不需要考虑 lane)
|
* PatrolService, 自动 (需要预处理 lane) ✔
|
* TrafficService, 自动 (寻址时已经处理过 lane) ✔
|
* )
|
* 评估HandlerController没有调用buildMajorTask,手动创建task的可行性
|
* agv地图图标变化
|
*/
|
public String getDestinationCode(String agvNo, AgvDetail agvDetail) {
|
|
Code startCode = codeService.getCacheById(agvDetail.getRecentCode());
|
|
Set<String> notInCodeSet = new HashSet<>();
|
notInCodeSet.add("00000301");
|
notInCodeSet.add("00000302");
|
notInCodeSet.add("00000303");
|
notInCodeSet.add("00000351");
|
notInCodeSet.add("00000353");
|
notInCodeSet.add("00000401");
|
notInCodeSet.add("00000402");
|
|
notInCodeSet.add("00000311");
|
notInCodeSet.add("00000312");
|
notInCodeSet.add("00000313");
|
notInCodeSet.add("00000361");
|
notInCodeSet.add("00000363");
|
notInCodeSet.add("00000411");
|
notInCodeSet.add("00000412");
|
|
notInCodeSet.add("00000046");
|
notInCodeSet.add("00000047");
|
|
Collections.shuffle(CODE_DATA_CACHE);
|
|
for (String endCodeData : CODE_DATA_CACHE) {
|
if (notInCodeSet.contains(endCodeData)) { continue; }
|
Code endCode = codeService.getCacheByData(endCodeData);
|
|
// valid lane
|
if (!allocateService.validCapacityOfLane(agvNo, endCode)) {
|
continue;
|
}
|
|
// valid path length
|
List<String> pathList = mapService.validFeasibility(startCode, endCode);
|
if (pathList.size() >= 5) {
|
return endCodeData;
|
}
|
}
|
|
return CODE_DATA_CACHE.stream().findFirst().orElse(null);
|
}
|
|
// ---------------------------------------------------------------------------
|
|
public boolean isPatrolling(String agvNo) {
|
ScheduledFuture<?> scheduledFuture = AGV_PATROL_MAP.get(agvNo);
|
if (scheduledFuture == null) {
|
return false;
|
}
|
return !scheduledFuture.isCancelled() && !scheduledFuture.isDone();
|
}
|
|
public R startupPatrol(String agvNo) {
|
if (AGV_PATROL_MAP.containsKey(agvNo)) {
|
return R.error("AGV " + agvNo + " 的跑库任务已经在运行中。");
|
}
|
|
Runnable patrolTask = () -> {
|
try {
|
executePatrolLogic(agvNo);
|
// executeUShapeConveyor(agvNo);
|
} catch (Exception e) {
|
log.error("执行AGV{}跑库任务时发生异常: {}", agvNo, e.getMessage());
|
}
|
};
|
|
ScheduledFuture<?> scheduledFuture = scheduler.scheduleAtFixedRate(patrolTask, 0, SCHEDULE_TIME_INTERVAL, TimeUnit.MILLISECONDS);
|
|
AGV_PATROL_MAP.put(agvNo, scheduledFuture);
|
log.info("已启动AGV{}的跑库任务。", agvNo);
|
return R.ok();
|
}
|
|
|
public R shutdownPatrol(String agvNo) {
|
Long agvId = agvService.getAgvId(agvNo);
|
ScheduledFuture<?> scheduledFuture = AGV_PATROL_MAP.get(agvNo);
|
if (scheduledFuture == null) {
|
return R.error("AGV " + agvNo + " 没有正在运行的跑库任务。");
|
}
|
|
boolean cancelled = scheduledFuture.cancel(true);
|
if (cancelled) {
|
AGV_PATROL_MAP.remove(agvNo);
|
CoreCache.AGV_BACKPACK_USED_CACHE.remove(agvId);
|
CoreCache.AGV_MOCK_STEP_CACHE.remove(agvId);
|
CoreCache.AGV_MOCK_JOB_CACHE.remove(agvNo);
|
log.info("已停止AGV {} 的跑库任务。", agvNo);
|
return R.ok("已停止AGV " + agvNo + " 的跑库任务。");
|
} else {
|
log.error("未能成功停止AGV " + agvNo + " 的跑库任务。");
|
return R.error("未能成功停止AGV " + agvNo + " 的跑库任务。");
|
}
|
}
|
|
@PostConstruct
|
public void init() {
|
int count = agvService.count(new LambdaQueryWrapper<Agv>().eq(Agv::getStatus, StatusType.ENABLE.val));
|
if (count > 0) {
|
this.scheduler = Executors.newScheduledThreadPool(count);
|
}
|
// init all code data
|
List<Code> codeList = codeService.list(new LambdaQueryWrapper<Code>().eq(Code::getStatus, StatusType.ENABLE.val));
|
this.CODE_DATA_CACHE = codeList.stream().map(Code::getData).distinct().collect(Collectors.toList());
|
|
// int all loc code data
|
List<Code> locCodeList = codeService.getAllLocCode();
|
this.LOC_CODE_DATA_CACHE = locCodeList.stream().map(Code::getData).distinct().collect(Collectors.toList());
|
|
}
|
|
@PreDestroy
|
public void destroy() throws InterruptedException {
|
for (Map.Entry<String, ScheduledFuture<?>> entry : AGV_PATROL_MAP.entrySet()) {
|
entry.getValue().cancel(true);
|
}
|
scheduler.shutdown();
|
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
|
scheduler.shutdownNow();
|
}
|
}
|
|
}
|