| 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.R; | 
| 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.Task; | 
| 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.AgvDetailService; | 
| import com.zy.acs.manager.manager.service.AgvService; | 
| import com.zy.acs.manager.manager.service.CodeService; | 
| import com.zy.acs.manager.manager.service.TaskService; | 
| 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.ArrayList; | 
| import java.util.Collections; | 
| import java.util.List; | 
| import java.util.Map; | 
| import java.util.concurrent.*; | 
|   | 
| /** | 
|  * Created by vincent on 11/9/2024 | 
|  */ | 
| @Slf4j | 
| @Service | 
| public class PatrolService { | 
|   | 
|     private static final int SCHEDULE_TIME_INTERVAL = 5; | 
|   | 
|     private static final Map<String, ScheduledFuture<?>> AGV_PATROL_MAP = new ConcurrentHashMap<>(); | 
|   | 
|     private final RedisSupport redis = RedisSupport.defaultRedisSupport; | 
|   | 
|     private ScheduledExecutorService scheduler = null; | 
|   | 
|     @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 LaneService laneService; | 
|     @Autowired | 
|     private AllocateService allocateService; | 
|     @Autowired | 
|     private ConfigService configService; | 
|   | 
|     private void executePatrolLogic(String agvNo) { | 
|         this.patrolOfMove(agvNo); | 
|     } | 
|   | 
|     private void patrolOfMove(String agvNo) { | 
|         Agv agv = agvService.selectByUuid(agvNo); | 
|         AgvDetail agvDetail = agvDetailService.selectByAgvId(agv.getId()); | 
|         if (taskService.count(new LambdaQueryWrapper<Task>() | 
|                 .eq(Task::getAgvId, agv.getId()) | 
|                 .and(i -> { | 
|                     i.eq(Task::getTaskSts, TaskStsType.WAITING.val()) | 
|                             .or().eq(Task::getTaskSts, TaskStsType.ASSIGN.val()) | 
|                             .or().eq(Task::getTaskSts, TaskStsType.PROGRESS.val()); | 
|                 })) > 0) { | 
|             return; | 
|         } | 
|         if (!agvService.judgeEnable(agv.getId())) { | 
|             return; | 
|         } | 
|         Code destinationCode = this.getDestinationCode(agv, agvDetail); | 
|         if (null == destinationCode) { | 
|             return; | 
|         } | 
|         if (mainLockWrapService.buildMinorTask(agv, TaskTypeType.MOVE, destinationCode.getData(), null)) { | 
|             log.info(agv.getUuid() + "开始走行演示..."); | 
|         } | 
|     } | 
|   | 
|     /** | 
|      * 4个地方调用了buildMinorTask,在什么时候、哪里设置task的lane | 
|      * ( | 
|          * HandlerController, 手动  (手动是否需要判断lane) | 
|          * MaintainScheduler, 自动  (一般不需要考虑 lane) | 
|          * PatrolService,     自动  (需要预处理 lane) ✔ | 
|          * TrafficService,    自动  (寻址时已经处理过 lane) ✔ | 
|      * ) | 
|      * 评估HandlerController没有调用buildMajorTask,手动创建task的可行性 | 
|      * agv地图图标变化 | 
|      */ | 
|     public Code getDestinationCode(Agv agv, AgvDetail agvDetail) { | 
|         Integer maxAgvCountInLane = configService.getVal("maxAgvCountInLane", Integer.class); | 
|   | 
|         Code startCode = codeService.getById(agvDetail.getRecentCode()); | 
|   | 
|         List<String> notInCodeList = new ArrayList<>(); | 
|         notInCodeList.add("00000301"); | 
|         notInCodeList.add("00000302"); | 
|         notInCodeList.add("00000303"); | 
|         notInCodeList.add("00000351"); | 
|         notInCodeList.add("00000353"); | 
|         notInCodeList.add("00000401"); | 
|         notInCodeList.add("00000402"); | 
|   | 
|         notInCodeList.add("00000311"); | 
|         notInCodeList.add("00000312"); | 
|         notInCodeList.add("00000313"); | 
|         notInCodeList.add("00000361"); | 
|         notInCodeList.add("00000363"); | 
|         notInCodeList.add("00000411"); | 
|         notInCodeList.add("00000412"); | 
|   | 
|         notInCodeList.add("00000046"); | 
|         notInCodeList.add("00000047"); | 
|         List<Code> list = codeService.list(new LambdaQueryWrapper<Code>().notIn(Code::getData, notInCodeList)); | 
|   | 
|         Collections.shuffle(list); | 
|   | 
|         for (Code endCode : list) { | 
|             // valid lane | 
|             if (!allocateService.validCapacityOfLane(agv, endCode)) { | 
|                 continue; | 
|             } | 
|   | 
|             // valid path length | 
|             List<String> pathList = mapService.validFeasibility(startCode, endCode); | 
|             if (pathList.size() >= 5) { | 
|                 return endCode; | 
|             } | 
|         } | 
|   | 
|         return list.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); | 
|             } catch (Exception e) { | 
|                 log.error("执行AGV " + agvNo + " 跑库任务时发生异常: " + e.getMessage()); | 
|                 e.printStackTrace(); | 
|             } | 
|         }; | 
|   | 
|         ScheduledFuture<?> scheduledFuture = scheduler.scheduleAtFixedRate(patrolTask, 0, SCHEDULE_TIME_INTERVAL, TimeUnit.SECONDS); | 
|   | 
|         AGV_PATROL_MAP.put(agvNo, scheduledFuture); | 
|         log.info("已启动AGV " + agvNo + " 的跑库任务。"); | 
|         return R.ok(); | 
|     } | 
|   | 
|   | 
|     public R shutdownPatrol(String 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); | 
|             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); | 
|         } | 
|     } | 
|   | 
|     @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(); | 
|         } | 
|     } | 
|   | 
| } |