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.constant.AgvAreaDispatcher; 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> AGV_PATROL_MAP = new ConcurrentHashMap<>(); private final RedisSupport redis = RedisSupport.defaultRedisSupport; private ScheduledExecutorService scheduler = null; private List CODE_DATA_CACHE = new ArrayList<>(); private List LOC_CODE_DATA_CACHE = new ArrayList<>(); private List CONVEYOR_DROP_CODE_DATA_CACHE = new ArrayList(){{ add("00000048"); }}; private List CONVEYOR_PICK_CODE_DATA_CACHE = new ArrayList(){{ 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() .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() .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()); List codeList = AgvAreaDispatcher.AGV_AREA.get(agvNo); List disableCodeList = AgvAreaDispatcher.AGV_DISABLE_AREA.get(agvNo); if (!Cools.isEmpty(codeList)) { Collections.shuffle(codeList); } for (String endCodeData : codeList) { if (disableCodeList.contains(endCodeData)) { continue; } Code endCode = codeService.getCacheByData(endCodeData); // valid lane if (!allocateService.validCapacityOfLane(agvNo, endCode)) { continue; } // valid path length List pathList = mapService.validFeasibility(startCode, endCode); if (pathList.size() >= 5) { return endCodeData; } } return codeList.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); } }; 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().eq(Agv::getStatus, StatusType.ENABLE.val)); if (count > 0) { this.scheduler = Executors.newScheduledThreadPool(count); } // init all code data List codeList = codeService.list(new LambdaQueryWrapper().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 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> entry : AGV_PATROL_MAP.entrySet()) { entry.getValue().cancel(true); } scheduler.shutdown(); if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) { scheduler.shutdownNow(); } } }