| 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<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()); | 
|   | 
|         List<String> codeList = AgvAreaDispatcher.AGV_AREA.get(agvNo); | 
|         List<String> disableCodeList = AgvAreaDispatcher.AGV_DISABLE_AREA.get(agvNo); | 
|         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<String> 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.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(); | 
|         } | 
|     } | 
|   | 
| } |