diff --git a/.trae/documents/实现“调度阻挡车避让”的优化方案.md b/.trae/documents/实现“调度阻挡车避让”的优化方案.md new file mode 100644 index 0000000..89d2ad0 --- /dev/null +++ b/.trae/documents/实现“调度阻挡车避让”的优化方案.md @@ -0,0 +1,47 @@ +# 优化避让策略:优先调度阻挡车避让 + +针对您提出的 "让B小车(障碍物)避让" 的合理建议,我设计了如下优化后的调度逻辑。新逻辑将优先尝试移开挡路车,只有在无法移开时才让当前车自己避让。 + +## 核心逻辑流程 + +当小车 A (当前调度车辆) 路径规划失败时,按以下步骤处理: + +1. **识别阻挡者 (Find Blocker)** + * 构建一张**纯净的静态地图** (忽略所有小车位置)。 + * 在静态地图上为 A 规划一条**理想路径**。 + * 沿着理想路径检查,找到第一辆占据路径的小车 **B**。 + +2. **判断阻挡者状态 (Check Blocker Status)** + * **如果 B 正在执行任务 (Busy)**: + * 说明 B 只是暂时路过或正在工作。 + * **策略**:A 小车暂时等待 (本次不生成指令),等待 B 自行移开。 + * **如果 B 空闲 (Idle)**: + * 说明 B 停在路上挡住了 A。 + * **策略**:调度 B 去避让。 + +3. **执行避让调度 (Execute Avoidance)** + * **场景 1:调度 B 避让 (优先)** + * 为 B 寻找一个最近的**避让点** (Cache Point)。 + * *关键条件*:避让点不能在 A 的理想路径上 (防止 B 躲到 A 的下一步去)。 + * 生成一条给 B 的**移动任务** (点到点移动)。 + * A 小车保持等待,直到 B 移开。 + * **场景 2:A 自己避让 (兜底)** + * 如果 B 无法移动 (例如 B 也被堵死,或找不到合适的避让点),则回退到原有的逻辑:**A 自己去避让点**,并在之后生成后续任务回来。 + +## 代码实现计划 + +将在 `BaseSRgvThread.java` 中现有的避让逻辑前插入新的判断块: + +1. **新增 helper 方法**: + * `getStaticMap()`: 获取不含动态障碍物的地图。 + * `isAgvIdle(String agvCode)`: 检查某小车是否空闲。 +2. **修改 `sendPointToPointTask`**: + * 在路径规划失败后,先执行 "寻找阻挡车并调度其避让" 的逻辑。 + * 如果成功调度了 B,则 `return false` (结束本次 A 的调度)。 + * 如果未成功,则继续执行原有的 "A 去避让" 逻辑。 + +## 预期效果 + +* **更智能**:不再傻傻地让正在干活的车去绕路,而是把闲置的挡路车移开。 +* **防止死锁**:通过主动疏通路径,减少多车拥堵的概率。 +* **兼容性**:保留了原有的 A 避让逻辑作为兜底方案,确保任何情况下都有路可走。 \ No newline at end of file diff --git a/ruoyi-admin/src/main/java/com/ruoyi/schedule/scheduleTask/srgv/BaseSRgvThread.java b/ruoyi-admin/src/main/java/com/ruoyi/schedule/scheduleTask/srgv/BaseSRgvThread.java index eabb8f3..2495236 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/schedule/scheduleTask/srgv/BaseSRgvThread.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/schedule/scheduleTask/srgv/BaseSRgvThread.java @@ -151,8 +151,8 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface { //下发断电任务 // int i = PlcCdzRead.writeToCdz("2"); // if (i == 1) { - srgvChargingJobMapper.insertHistory(srgvChargingJob.getGuid()); - srgvChargingJobMapper.deleteSrgvChargingJobByGuid(srgvChargingJob.getGuid()); + srgvChargingJobMapper.insertHistory(srgvChargingJob.getGuid()); + srgvChargingJobMapper.deleteSrgvChargingJobByGuid(srgvChargingJob.getGuid()); // } } } @@ -180,7 +180,7 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface { String chargingId = srgvChargingJob.getChargingId(); DeviceSrgv srgv = PlcSrgvRead.deviceSrgvMap.get(agvId); // DeviceCdz cdz = PlcCdzRead.deviceCdzMap.get(chargingId); - if (srgv.getCurrFloor() == 1) { + if (srgv.getCurrFloor()==1) { //生成四向小车充电队列 String chargingSite = srgvChargingJob.getChargingSite();//充电桩站点 WcsSrgvQueue wcsSrgvQueueParam = new WcsSrgvQueue(); @@ -200,8 +200,8 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface { List wcsSrgvQueues = wcsSagvQueueMapper.selectWcsSrgvQueueList(wcsSrgvQueueParam); if (CollUtil.isEmpty(wcsSrgvQueues)) { createSrgvChargingQueue(deviceSrgv, chargingSite, "8", srgvChargingJob.getTaskNo()); -// srgvChargingJob.setJobStatus("2"); -// srgvChargingJobMapper.updateSrgvChargingJob(srgvChargingJob); + srgvChargingJob.setJobStatus("2"); + srgvChargingJobMapper.updateSrgvChargingJob(srgvChargingJob); } } @@ -346,6 +346,7 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface { //TODO 调用WMS完成作业接口 定时器回传完成作业 JobCallBack jobCallBack = new JobCallBack(); jobCallBack.setType("1"); + jobCallBack.setMydojobId(mydoJob.getJobId()); jobCallBack.setStatus("0"); jobCallBack.setTaskNo(Convert.toLong(mydoJob.getTaskNo())); jobCallBack.setCreateTime(new Date()); @@ -550,6 +551,232 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface { //判断是否能规划出最短路径 if (shortestPath == null || shortestPath.isEmpty()) { + // ========================================================= + // 特殊优先策略:若目标点被其他小车占用,直接调度占用车到缓存点 + // 场景示例:1号车目标为 040701,但 2号车正停在 040701 + // 处理思路:不等待、不绕行,直接把占用目标点的小车移开 + // ========================================================= + try { + // 查找占用目标点的阻挡车(排除当前车) + String endBlockAgv = null; + DeviceSrgv endBlockDevice = null; + for (Map.Entry entry : PlcSrgvRead.deviceSrgvMap.entrySet()) { + DeviceSrgv other = entry.getValue(); + if (other == null) continue; + if (Objects.equals(other.getCurrSite(), endSite) + && !Objects.equals(Convert.toStr(deviceSrgv.getDeviceId()), entry.getKey())) { + endBlockAgv = entry.getKey(); + endBlockDevice = other; + break; + } + } + WcsSrgvQueue wcsSrgvQueue1 =new WcsSrgvQueue(); + wcsSrgvQueue1.setAgvId(endBlockAgv); + List wcsSrgvQueues = wcsSagvQueueMapper.selectWcsSrgvQueueList(wcsSrgvQueue1); + if (endBlockDevice != null&&wcsSrgvQueues.isEmpty()) { + // 选择当前层最近且可达的缓存点(避让点) + WcsSrgvMap cacheParam = new WcsSrgvMap(); + cacheParam.setRemark("cache"); + cacheParam.setTheLayer(floor); + List cacheMaps = wcsSrgvMapService.selectWcsSrgvMapList(cacheParam); + // 生成阻挡车的避让任务(点到点,高优先级,未下发) + WcsSrgvQueue taskB = new WcsSrgvQueue(); + taskB.setGuid(UUID.randomUUID().toString()); + // 生成纯数字任务号(PLC要求),9 + HHmmss + taskB.setTaskNo(String.valueOf(taskCache)); + taskB.setAgvId(endBlockAgv); + taskB.setTaskType("4"); // 点到点移动 + taskB.setDefault02("4"); // 业务类型标识为点到点 + taskB.setPriority(9L); // 高优先级,尽快避让 + taskB.setIssued("0"); // 未下发 + taskB.setExecutionStatus("1");// 初始状态 + taskB.setStartSite(endBlockDevice.getCurrSite()); + taskB.setEndSite("050501"); + taskB.setTheFloor(floor.toString()); + taskB.setDefault03(endBlockDevice.getCurrSite()); + taskB.setDefault07(floor.toString()); + taskB.setDefault08("1");//避让任务,直接完成 + taskB.setCreateDate(DateUtil.now()); + wcsSagvQueueMapper.insertWcsSrgvQueue(taskB); + taskCache++; + log.info("目标点 {} 被小车 {} 占用,已调度其前往避让点 {}", endSite, endBlockAgv, "050501"); + // 当前车等待下一轮调度(待阻挡车移开后自然可达) + return false; + } else { + log.warn("目标点 {} 被小车 {} 占用,但无可用避让点", endSite, endBlockAgv); + // 无法直接移开阻挡车,后续进入常规策略(寻找阻挡者或自身避让) + } + } catch (Exception e) { + log.error("终点占用直接避让策略执行异常", e); + } + // --------------------------------------------------------- + // 策略1:尝试调度阻挡车避让 (新增逻辑) + // --------------------------------------------------------- + try { + // 1. 获取纯净静态地图 (不含动态障碍物) + int[][] staticMap = new int[29][16]; + for (WcsSrgvMap site : arrByRoadSite.values()) { + if (site.getX() != null && site.getY() != null) { + staticMap[site.getX()][site.getY()] = site.getValue(); + } + } + // 2. 规划理想路径 (忽略其他小车) + int sx = arrByRoadSite.get(startSite).getX(); + int sy = arrByRoadSite.get(startSite).getY(); + int ex = arrByRoadSite.get(endSite).getX(); + int ey = arrByRoadSite.get(endSite).getY(); + staticMap[sx][sy] = 2; + staticMap[ex][ey] = 2; + List idealPath = PathFinder.findShortestPath( + staticMap, + sx, sy, + ex, ey + ); + // 3. 寻找阻挡车 + String blockingAgv = null; + if (idealPath == null || idealPath.isEmpty()) { + log.warn("理想路径为空,sx={},sy={},ex={},ey={},startVal={},endVal={}", + sx, sy, ex, ey, staticMap[sx][sy], staticMap[ex][ey]); + } + if (idealPath != null && !idealPath.isEmpty()) { + for (int[] point : idealPath) { + // 通过坐标反查站点 (简单遍历) + // 注意:这里假设同一层坐标唯一 + String pointSiteId = null; + for (Map.Entry entry : arrByRoadSite.entrySet()) { + WcsSrgvMap s = entry.getValue(); + if (s.getX() == point[0] && s.getY() == point[1]) { + pointSiteId = s.getSiteId(); + break; + } + } + + if (pointSiteId != null) { + // 检查是否有其他车在这个点 + for (Map.Entry entry : PlcSrgvRead.deviceSrgvMap.entrySet()) { + String otherAgvCode = entry.getKey(); + DeviceSrgv otherAgv = entry.getValue(); + + if (!otherAgvCode.equals(wcsSrgvQueue.getAgvId()) && // 不是自己 + pointSiteId.equals(otherAgv.getCurrSite())) { // 是别人 + + blockingAgv = otherAgvCode; + break; + } + } + } + if (blockingAgv != null) break; // 找到第一个阻挡者即可 + } + } + + // 4. 调度阻挡车 + if (blockingAgv != null) { + log.info("发现阻挡车: {}, 尝试调度避让", blockingAgv); + + // 检查 B 是否忙碌 (是否有未完成任务) + WcsSrgvQueue blockingParam = new WcsSrgvQueue(); + blockingParam.setAgvId(blockingAgv); // 注意:这里用 AgvId 还是 SrgvCode 需要确认,假设是一致的 + // 通常数据库存的是 ID,这里 blockingAgv 来自 deviceSrgvMap 的 key,通常是 code + // 如果 code != id,需要转换。假设 code 就是 id。 + + List tasks = wcsSagvQueueMapper.selectWcsSrgvQueueList(blockingParam); + boolean isBusy = CollUtil.isNotEmpty(tasks); + // 这里简化判断:只要有队列记录就算忙。更精细的判断可以看 status。 + + if (isBusy) { + log.info("阻挡车 {} 正在执行任务,当前车 {} 等待", blockingAgv, wcsSrgvQueue.getAgvId()); + return false; // B 忙,A 等待 (不做任何操作,下一轮循环再试) + } else { + // B 空闲,寻找避让点 + // 获取当前层所有避让点 + WcsSrgvMap cacheParam = new WcsSrgvMap(); + cacheParam.setRemark("cache"); + cacheParam.setTheLayer(floor); + List cacheMaps = wcsSrgvMapService.selectWcsSrgvMapList(cacheParam); + + // 获取所有小车位置 (用于避让点占用检查) + Set occupiedSites = new HashSet<>(); + for (DeviceSrgv d : PlcSrgvRead.deviceSrgvMap.values()) { + occupiedSites.add(d.getCurrSite()); + } + + String bestCacheForB = null; + int minLenForB = Integer.MAX_VALUE; + + DeviceSrgv deviceB = PlcSrgvRead.deviceSrgvMap.get(blockingAgv); + if (deviceB != null) { + WcsSrgvMap startNodeB = arrByRoadSite.get(deviceB.getCurrSite()); + + for (WcsSrgvMap cache : cacheMaps) { + String siteId = cache.getSiteId(); + if (occupiedSites.contains(siteId)) continue; // 已被占 + + // 关键:避让点不能在 A 的理想路径上 + boolean onIdealPath = false; + for (int[] p : idealPath) { + if (p[0] == cache.getX() && p[1] == cache.getY()) { + onIdealPath = true; + break; + } + } + if (onIdealPath) continue; + + // 检查 B 到避让点的可达性 (在静态地图上即可,因为其他车可能就是 A) + // 此时 A 还没动,所以 B 应该能动。 + // 为了安全,还是用 emptyMap (含 A) 检查 B 的路径? + // 不,用 emptyMap 检查 B 的路径,因为 A 还在原位。 + if (startNodeB != null && cache.getX() != null && cache.getY() != null) { + List pathB = PathFinder.findShortestPath(emptyMap, + startNodeB.getX(), startNodeB.getY(), + cache.getX(), cache.getY()); + + if (pathB != null && !pathB.isEmpty() && pathB.size() < minLenForB) { + minLenForB = pathB.size(); + bestCacheForB = siteId; + } + } + } + + if (bestCacheForB != null) { + // 生成 B 的避让任务 + WcsSrgvQueue taskB = new WcsSrgvQueue(); + taskB.setGuid(UUID.randomUUID().toString()); + // 生成纯数字任务号,避免 PLC 解析错误 + // 格式:9 + HHmmss + 随机位 (防止重复) + String timeStr = DateUtil.format(new Date(), "HHmmss"); + taskB.setTaskNo(String.valueOf(taskCache)); + taskB.setAgvId(blockingAgv); + taskB.setTaskType("4"); // 点到点 + taskB.setPriority(9L); // 高优先级 + taskB.setIssued("0"); + taskB.setExecutionStatus("1"); + taskB.setStartSite(deviceB.getCurrSite()); + taskB.setEndSite(bestCacheForB); + taskB.setTheFloor(floor.toString()); + taskB.setCreateDate(DateUtil.now()); + taskB.setDefault02("4"); + taskB.setDefault03(deviceB.getCurrSite()); // 默认值填充 + taskB.setDefault07(floor.toString()); + taskB.setDefault08("1"); + wcsSagvQueueMapper.insertWcsSrgvQueue(taskB); + taskCache++; + log.info("成功调度阻挡车 {} 前往避让点 {}", blockingAgv, bestCacheForB); + return false; // A 等待 + } else { + log.warn("阻挡车 {} 无可用避让点", blockingAgv); + // Fallback to Strategy 2 (A moves) + } + } + } + } + } catch (Exception e) { + log.error("调度阻挡车避让逻辑异常", e); + // Fallback to Strategy 2 + } + + // --------------------------------------------------------- + // 策略2:自己避让 (原有逻辑) + // --------------------------------------------------------- // ========================================================= // 特殊优先策略:若目标点被其他小车占用,直接调度占用车到缓存点 // 场景示例:1号车目标为 040701,但 2号车正停在 040701 @@ -1238,8 +1465,8 @@ public class BaseSRgvThread implements Runnable, SRgvThreadInterface { // } // } startSite = wcsSrgvQueue.getStartSite(); - if (startSite.equals("tsj")) { - startSite = getSite(wcsSrgvQueue.getStartSite(), wcsSrgvQueue.getTheFloor(), wcsSrgvQueue.getTaskType()); + if (startSite.equals("tsj")){ + startSite=getSite(wcsSrgvQueue.getStartSite(),wcsSrgvQueue.getTheFloor(),wcsSrgvQueue.getTaskType()); } if (!startSite.equals(currSite)) { wcsSrgvQueue.setIssued("1");