package com.zy.asrs.wcs.core.utils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.zy.asrs.framework.common.Cools; import com.zy.asrs.framework.common.SnowflakeIdWorker; import com.zy.asrs.framework.exception.CoolException; import com.zy.asrs.wcs.core.entity.*; import com.zy.asrs.wcs.core.kernel.AnalyzeService; import com.zy.asrs.wcs.core.model.NavigateNode; import com.zy.asrs.wcs.core.model.enums.*; import com.zy.asrs.wcs.core.service.*; import com.zy.asrs.wcs.rcs.News; import com.zy.asrs.wcs.rcs.cache.SlaveConnection; import com.zy.asrs.wcs.rcs.entity.Device; import com.zy.asrs.wcs.rcs.model.enums.ShuttleProtocolStatusType; import com.zy.asrs.wcs.rcs.model.enums.SlaveType; import com.zy.asrs.wcs.rcs.model.protocol.ShuttleProtocol; import com.zy.asrs.wcs.rcs.service.DeviceService; import com.zy.asrs.wcs.rcs.thread.LiftThread; import com.zy.asrs.wcs.rcs.thread.ShuttleThread; import com.zy.asrs.wcs.system.entity.Dict; import com.zy.asrs.wcs.system.service.DictService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.*; /** * Created by vincent on 2023/10/12 */ @Service public class ShuttleDispatcher { private static final Integer INF = 9999999; private static final Integer WEIGHT = 1000000; @Autowired private TaskService taskService; @Autowired private LiftDispatcher liftDispatcher; @Autowired private SnowflakeIdWorker snowflakeIdWorker; @Autowired private AnalyzeService analyzeService; @Autowired private MotionService motionService; @Autowired private DeviceService deviceService; @Autowired private BasShuttleService basShuttleService; @Autowired private TaskCtgService taskCtgService; @Autowired private ShuttleStandbyService shuttleStandbyService; @Autowired private DictService dictService; @Autowired private NavigateUtils navigateUtils; public synchronized ShuttleThread searchIdleShuttle(Task task) { String locNo = taskService.judgeInbound(task) ? task.getDestLoc() : task.getOriginLoc(); ShuttleThread resThread = null; int lev = Utils.getLev(locNo); List list = deviceService.list(new LambdaQueryWrapper() .eq(Device::getDeviceType, DeviceCtgType.SHUTTLE.val()) .eq(Device::getHostId, task.getHostId()) .eq(Device::getStatus, 1)); //获取同层小车 List currentLevDevices = new ArrayList<>(); //获取跨层小车 HashMap> diffLevDeviceMap = new HashMap<>(); for (Device device : list) { //获取四向穿梭车线程 ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, device.getId().intValue()); ShuttleProtocol shuttleProtocol = shuttleThread.getStatus(); if (shuttleProtocol == null || shuttleProtocol.getShuttleNo() == null) { continue; } int shuttleLev = Utils.getLev(shuttleProtocol.getCurrentLocNo()); if (shuttleLev == lev) { currentLevDevices.add(device); }else { List devices = null; if(diffLevDeviceMap.containsKey(shuttleLev)) { devices = diffLevDeviceMap.get(shuttleLev); }else { devices = new ArrayList<>(); } devices.add(device); diffLevDeviceMap.put(shuttleLev, devices); } } //搜索同层 resThread = this.searchCurrentLevShuttle(currentLevDevices, locNo); //同层没有搜索到合适小车,跨楼层搜索 if(resThread == null) { resThread = this.searchDiffLevShuttle(diffLevDeviceMap, locNo, task); } return resThread; } private synchronized ShuttleThread searchCurrentLevShuttle(List devices, String locNo) { ShuttleThread resThread = null; Integer finalDistance = ShuttleDispatcher.INF; for (Device device : devices) { if (taskService.hasBusyOutboundByShuttle(Integer.parseInt(device.getDeviceNo()))) { continue; } //获取四向穿梭车线程 ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, device.getId().intValue()); ShuttleProtocol shuttleProtocol = shuttleThread.getStatus(); if (shuttleProtocol == null || shuttleProtocol.getShuttleNo() == null) { continue; } if (!shuttleThread.isIdle()) { continue; } BasShuttle basShuttle = basShuttleService.getOne(new LambdaQueryWrapper() .eq(BasShuttle::getShuttleNo, device.getDeviceNo()) .eq(BasShuttle::getHostId, device.getHostId())); if (basShuttle == null) { continue;//小车基础数据不存在 } if (!Cools.isEmpty(basShuttle.getDisableLev())) { List disableLev = JSON.parseArray(basShuttle.getDisableLev(), Integer.class); //检查小车是否禁用该楼层 if (disableLev.contains(Utils.getLev(locNo))) { continue;//小车禁用该楼层跳过该车 } } //检测是否存在充电任务 Task taskCharge = taskService.selectChargeWorking(Integer.valueOf(device.getDeviceNo())); if (taskCharge != null) { continue; } // 有没有被其他任务调度 int currentLev = Utils.getLev(shuttleProtocol.getCurrentLocNo());//小车当前层高 String currentLocNo = shuttleProtocol.getCurrentLocNo();//小车当前库位号 if (currentLocNo.equals(locNo)) { resThread = shuttleThread; break; } // 同楼层直接计算到目标库位 //当前穿梭车线程到当前车子所在楼层的目标库位距离 List currentShuttlePath = navigateUtils.calc( currentLocNo , locNo , NavigationMapType.NORMAL.id , Utils.getShuttlePoints(Integer.parseInt(shuttleThread.getDevice().getDeviceNo()), currentLev) );//搜索空闲穿梭车,使用正常通道地图 if (currentShuttlePath == null) { continue; } Integer currDistance = navigateUtils.getOriginPathAllDistance(currentShuttlePath);//计算当前路径行走总距离 // 挂载任务权重 List tasks = taskService.selectWorkingByShuttle(Integer.valueOf(device.getDeviceNo()), null); if (!Cools.isEmpty(tasks)) { currDistance += tasks.size() * WEIGHT; } if (currDistance < finalDistance) { finalDistance = currDistance; resThread = shuttleThread; } } return resThread; } private synchronized ShuttleThread searchDiffLevShuttle(HashMap> devicesMap, String locNo, Task task) { ShuttleThread resThread = null; Integer finalDistance = ShuttleDispatcher.INF; //检测目标楼层车数量是否小于允许的最大数量 boolean checkDispatchMaxNum = checkDispatchMaxNum(Utils.getLev(locNo), task.getHostId()); for (Map.Entry> entry : devicesMap.entrySet()) { Integer lev = entry.getKey(); List devices = entry.getValue(); for (Device device : devices) { if (taskService.hasBusyOutboundByShuttle(Integer.parseInt(device.getDeviceNo()))) { continue; } //获取四向穿梭车线程 ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, device.getId().intValue()); ShuttleProtocol shuttleProtocol = shuttleThread.getStatus(); if (shuttleProtocol == null || shuttleProtocol.getShuttleNo() == null) { continue; } if (!shuttleThread.isIdle()) { continue; } BasShuttle basShuttle = basShuttleService.getOne(new LambdaQueryWrapper() .eq(BasShuttle::getShuttleNo, device.getDeviceNo()) .eq(BasShuttle::getHostId, device.getHostId())); if (basShuttle == null) { continue;//小车基础数据不存在 } if (!Cools.isEmpty(basShuttle.getDisableLev())) { List disableLev = JSON.parseArray(basShuttle.getDisableLev(), Integer.class); //检查小车是否禁用该楼层 if (disableLev.contains(Utils.getLev(locNo))) { continue;//小车禁用该楼层跳过该车 } } //检测是否存在充电任务 Task taskCharge = taskService.selectChargeWorking(Integer.valueOf(device.getDeviceNo())); if (taskCharge != null) { continue; } // 有没有被其他任务调度 int currentLev = Utils.getLev(shuttleProtocol.getCurrentLocNo());//小车当前层高 String currentLocNo = shuttleProtocol.getCurrentLocNo();//小车当前库位号 if (!checkDispatchMaxNum) { News.info("{}任务,{}层,已经达到当前楼层调度车辆最大值", task.getTaskNo(), Utils.getLev(locNo)); continue; } //获取距离小车位置最近的空闲可换层提升机 LiftThread liftThread = liftDispatcher.searchIdleLift(currentLocNo, task.getHostId(), true); if (liftThread == null) { continue; } Device recentTransferLift = liftThread.getDevice(); //获取小车楼层提升机待机位 ShuttleStandby shuttleStandby = shuttleStandbyService.getOne(new LambdaQueryWrapper() .eq(ShuttleStandby::getDeviceId, recentTransferLift.getId()) .eq(ShuttleStandby::getDeviceLev, currentLev) .eq(ShuttleStandby::getStatus, 1)); String targetLocNo = shuttleStandby.getDeviceLoc(); //当前穿梭车线程到当前车子所在楼层的提升机待机位距离 List currentShuttlePath = navigateUtils.calc( currentLocNo , targetLocNo , NavigationMapType.NORMAL.id , Utils.getShuttlePoints(Integer.parseInt(shuttleThread.getDevice().getDeviceNo()), currentLev) );//搜索空闲穿梭车,使用正常通道地图 if (currentShuttlePath == null) { continue; } Integer currDistance = navigateUtils.getOriginPathAllDistance(currentShuttlePath);//计算当前路径行走总距离 // 不同楼层权重 if (currentLev != Utils.getLev(locNo)) { currDistance += WEIGHT; } // 挂载任务权重 List tasks = taskService.selectWorkingByShuttle(Integer.valueOf(device.getDeviceNo()), null); if (!Cools.isEmpty(tasks)) { currDistance += tasks.size() * WEIGHT; } if (currDistance < finalDistance) { finalDistance = currDistance; resThread = shuttleThread; } } if (resThread != null) { break; } } return resThread; } //生成迁移任务 public synchronized Task generateMoveTask(Device device, String locNo) { // 已有迁移任务 if (taskService.selectMoveWorking(Integer.valueOf(device.getDeviceNo())) != null) { return null; } //获取迁移任务类型 TaskCtg taskCtg = taskCtgService.getOne(new LambdaQueryWrapper() .eq(TaskCtg::getFlag, String.valueOf(TaskCtgType.MOVE)) .eq(TaskCtg::getStatus, 1)); if (taskCtg == null) { return null; } ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, device.getId().intValue()); if (shuttleThread == null) { return null; } Task task = new Task(); task.setUuid(String.valueOf(snowflakeIdWorker.nextId())); task.setTaskNo(String.valueOf(Utils.getTaskNo("MOVE"))); task.setTaskSts(TaskStsType.NEW_MOVE.sts); task.setTaskCtg(taskCtg.getId()); task.setPriority(10); task.setOriginSite(null); task.setOriginLoc(null); task.setDestSite(null); task.setDestLoc(locNo); // 迁移位置 task.setIoTime(new Date()); task.setStartTime(new Date()); task.setHostId(device.getHostId()); task.setStatus(1); task.setShuttleNo(Integer.valueOf(device.getDeviceNo())); // generate motion list List motionList = analyzeService.generateShuttleMoveMotion(task); if (Cools.isEmpty(motionList)) { News.error("保存{}号四向穿梭车迁移任务失败!!!", device.getDeviceNo()); return null; } motionService.batchInsert(motionList, task.getUuid(), Integer.valueOf(task.getTaskNo()), device.getHostId()); task.setTaskSts(TaskStsType.ANALYZE_MOVE.sts); if (!taskService.save(task)) { News.error("保存{}号四向穿梭车迁移任务失败!!!", device.getDeviceNo()); return null; } return task; } //生成手动取放货任务 public synchronized Task generateManuaTakeMoveTask(Device device, String sourceLocNo, String locNo) { // 已有手动任务 if (taskService.selectManualWorking(Integer.valueOf(device.getDeviceNo())) != null) { return null; } //获取手动任务类型 TaskCtg taskCtg = taskCtgService.getOne(new LambdaQueryWrapper() .eq(TaskCtg::getFlag, String.valueOf(TaskCtgType.MANUAL)) .eq(TaskCtg::getStatus, 1)); if (taskCtg == null) { return null; } ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, device.getId().intValue()); if (shuttleThread == null) { return null; } Task task = new Task(); task.setUuid(String.valueOf(snowflakeIdWorker.nextId())); task.setTaskNo(String.valueOf(Utils.getTaskNo("MANUAL"))); task.setTaskSts(TaskStsType.NEW_MANUAL.sts); task.setTaskCtg(taskCtg.getId()); task.setPriority(10); task.setOriginSite(null); task.setOriginLoc(sourceLocNo); task.setDestSite("takeMove"); task.setDestLoc(locNo); task.setIoTime(new Date()); task.setStartTime(new Date()); task.setStatus(1); task.setMemo("manual"); task.setShuttleNo(Integer.valueOf(device.getDeviceNo())); task.setRecordLoc("Y");//记录库存信息 task.setHostId(device.getHostId()); // generate motion list List motionList = analyzeService.generateShuttleManualMotion(task); if (Cools.isEmpty(motionList)) { News.error("保存{}号四向穿梭车手动任务失败!!!", device.getDeviceNo()); return null; } motionService.batchInsert(motionList, task.getUuid(), Integer.valueOf(task.getTaskNo()), device.getHostId()); task.setTaskSts(TaskStsType.ANALYZE_MANUAL.sts); if (!taskService.save(task)) { News.error("保存{}号四向穿梭车手动任务失败!!!", device.getDeviceNo()); return null; } return task; } /** * 搜索可用库位,通过小车号和目标库位 */ public String searchAvailableLocNo(Integer shuttleNo, Long hostId, String currentLocNo, List locNos) { BasShuttle basShuttle = basShuttleService.getOne(new LambdaQueryWrapper() .eq(BasShuttle::getShuttleNo, shuttleNo) .eq(BasShuttle::getHostId, hostId)); if (basShuttle == null) { throw new CoolException("小车基础数据不存在"); } if (locNos.isEmpty()) { throw new CoolException("当前层无避让位置"); } int lev = Utils.getLev(currentLocNo); Integer finalDistance = ShuttleDispatcher.INF; String recentLoc = null; for (String loc : locNos) { //当前穿梭车到避让位计算 List currentShuttlePath = navigateUtils.calc( currentLocNo , loc , NavigationMapType.NORMAL.id , Utils.getShuttlePoints(shuttleNo, lev) );//使用正常通道地图 if (currentShuttlePath == null) { continue; } Integer currDistance = navigateUtils.getOriginPathAllDistance(currentShuttlePath);//计算当前路径行走总距离 if (currDistance < finalDistance) { finalDistance = currDistance; recentLoc = loc; } } if (recentLoc == null) { throw new CoolException("搜索避让位置失败"); } return recentLoc; } /** * 检测目标楼层车数量是否小于允许的最大数量 * true: 小于最大数量 false: 大于或等于最大数量 */ public boolean checkDispatchMaxNum(Integer lev, Long hostId) { Dict dict = dictService.getOne(new LambdaQueryWrapper().eq(Dict::getFlag, "dispatchShuttleMaxNum")); if (dict == null) { return false; } ArrayList shuttleNos = new ArrayList<>(); List list = deviceService.list(new LambdaQueryWrapper() .eq(Device::getDeviceType, DeviceCtgType.SHUTTLE.val()) .eq(Device::getHostId, hostId) .eq(Device::getStatus, 1)); for (Device device : list) { //获取四向穿梭车线程 ShuttleThread shuttleThread = (ShuttleThread) SlaveConnection.get(SlaveType.Shuttle, device.getId().intValue()); if (shuttleThread == null) { continue; } ShuttleProtocol shuttleProtocol = shuttleThread.getStatus(); if (shuttleProtocol == null) { continue; } if (shuttleProtocol.getProtocolStatusType().equals(ShuttleProtocolStatusType.OFFLINE)) { continue; } if(!shuttleThread.isDeviceIdle()) { continue; } if(shuttleProtocol.getCurrentLocNo() == null) { continue; } if (Utils.getLev(shuttleProtocol.getCurrentLocNo()) == lev) { if (shuttleProtocol.getHasCharge()) { continue;//充电中 } shuttleNos.add(shuttleProtocol.getShuttleNo());//目标楼层有车,添加进list } } //搜索是否存在前往目标楼层的小车移动工作档 for (Task task : taskService.list(new LambdaQueryWrapper() .in(Task::getTaskSts, TaskStsType.NEW_MOVE.sts, TaskStsType.ANALYZE_MOVE.sts, TaskStsType.EXECUTE_MOVE.sts, TaskStsType.COMPLETE_MOVE.sts))) { if (task.getOriginLoc() == null || task.getDestLoc() == null) { continue; } int sourceLev = Utils.getLev(task.getOriginLoc());//工作档源楼层 int targetLev = Utils.getLev(task.getDestLoc());//工作档目标楼层 if (sourceLev == targetLev) { continue;//工作档楼层和目标楼层相同,跳过 } if (targetLev == lev) { //工作档目标楼层和实际楼层相同,数量增加 if (!shuttleNos.contains(task.getShuttleNo())) { shuttleNos.add(task.getShuttleNo()); } continue; } } //搜索是否存在前往目标楼层的小车工作档 for (Task task : taskService.list(new LambdaQueryWrapper() .in(Task::getTaskSts, TaskStsType.NEW_INBOUND.sts, TaskStsType.ANALYZE_INBOUND.sts, TaskStsType.EXECUTE_INBOUND.sts, TaskStsType.COMPLETE_INBOUND.sts , TaskStsType.NEW_OUTBOUND.sts, TaskStsType.ANALYZE_OUTBOUND.sts, TaskStsType.EXECUTE_OUTBOUND.sts, TaskStsType.COMPLETE_OUTBOUND.sts))) { String locNo = taskService.judgeInbound(task) ? task.getDestLoc() : task.getOriginLoc(); if (Utils.getLev(locNo) != lev) { continue; } if (task.getShuttleNo() == null) { continue; } if (!shuttleNos.contains(task.getShuttleNo())) { shuttleNos.add(task.getShuttleNo()); } } return shuttleNos.size() < Integer.parseInt(dict.getValue()); } //分析出库路径待机库位 public String analyzeOutPathWaitLoc(String startLoc, String targetLoc, Device shuttleDevice) { //计算路径并分解成两段动作 List nodeList = navigateUtils.calc(startLoc, targetLoc, NavigationMapType.DFX.id, Utils.getShuttlePoints(Integer.parseInt(shuttleDevice.getDeviceNo()), Utils.getLev(startLoc))); if (nodeList == null) { News.error("{} dash {} can't find navigate path!", startLoc, targetLoc); return null; } //获取分段路径 ArrayList> data = navigateUtils.getSectionPath(nodeList); if (data.size() <= 1) { return startLoc;//两点之间只有一段路径,在起点位置等待 } //取出倒数第二段路径 ArrayList navigateNodes = data.get(data.size() - 2); NavigateNode startNode = navigateNodes.get(0); String lastPathStartLoc = Utils.getLocNo(startNode.getX(), startNode.getY(), startNode.getZ()); return lastPathStartLoc; } }