Browse Source

Merge remote-tracking branch 'origin/master'

master
yuliang 6 months ago
parent
commit
474f2fae0d
  1. 1
      .gitignore
  2. 1145
      examples/example1.json
  3. 80
      servo/src/main/java/com/galaxis/rcs/RCS.java
  4. 2
      servo/src/main/java/com/galaxis/rcs/common/enums/LCCDirection.java
  5. 1
      servo/src/main/java/com/galaxis/rcs/common/enums/PlanTaskType.java
  6. 51
      servo/src/main/java/com/galaxis/rcs/plan/PlanTaskSequence.java
  7. 166
      servo/src/main/java/com/galaxis/rcs/plan/path/GraphInitializer.java
  8. 8
      servo/src/main/java/com/galaxis/rcs/plan/path2/AGVPathPlanner.java
  9. 220
      servo/src/main/java/com/galaxis/rcs/plan/path2/AStarPathPlanner.java
  10. 174
      servo/src/main/java/com/galaxis/rcs/plan/path2/NavigationGraph.java
  11. 168
      servo/src/main/java/com/galaxis/rcs/plan/path2/PathUtils.java
  12. 142
      servo/src/main/java/com/galaxis/rcs/plan/path2/PtrPathPlanner.java
  13. 19
      servo/src/main/java/com/galaxis/rcs/plan/path2/State.java
  14. 9
      servo/src/main/java/com/galaxis/rcs/plan/path2/StoreLink.java
  15. 34
      servo/src/main/java/com/galaxis/rcs/plan/task/CarryTask.java
  16. 2
      servo/src/main/java/com/yvan/logisticsEnv/EnvStartParam.java
  17. 14
      servo/src/main/java/com/yvan/logisticsEnv/LogisticsEnv.java
  18. 35
      servo/src/main/java/com/yvan/logisticsModel/LogisticsRuntime.java
  19. 5
      servo/src/main/java/com/yvan/logisticsModel/PtrAgvItem.java
  20. 8
      servo/src/main/java/com/yvan/workbench/controller/EnvController.java
  21. 2
      servo/src/main/resources/application-dev.yml

1
.gitignore

@ -1,5 +1,6 @@
# Compiled class file
*.class
.lck
.gradle
.idea

1145
examples/example1.json

File diff suppressed because it is too large

80
servo/src/main/java/com/galaxis/rcs/RCS.java

@ -4,16 +4,17 @@ import com.galaxis.rcs.common.entity.AddTaskRequest;
import com.galaxis.rcs.common.entity.AddTaskResult;
import com.galaxis.rcs.common.entity.RcsTaskBiz;
import com.galaxis.rcs.common.entity.StoreLocation;
import com.galaxis.rcs.common.enums.BizTaskStatus;
import com.galaxis.rcs.common.enums.BizTaskType;
import com.galaxis.rcs.common.enums.LCCDirection;
import com.galaxis.rcs.plan.planner.Planner;
import com.galaxis.rcs.plan.TaskPlannerFactory;
import com.galaxis.rcs.plan.PlanTaskSequence;
import com.galaxis.rcs.plan.task.CarryTask;
import com.google.common.base.Joiner;
import com.yvan.logisticsEnv.LogisticsEnv;
import com.yvan.logisticsEnv.EnvStartParam;
import com.yvan.logisticsModel.LogisticsRuntime;
import com.yvan.logisticsModel.LogisticsRuntimeService;
import com.yvan.logisticsMonitor.task.BizTask;
import com.yvan.workbench.model.entity.Model;
import lombok.SneakyThrows;
import org.apache.commons.io.FileUtils;
import org.clever.core.json.JsonWrapper;
@ -32,14 +33,17 @@ public class RCS {
@SneakyThrows
public static void init() {
if (LogisticsRuntimeService.INSTANCE.findByEnvCode(1L) == null) {
String fs = Joiner.on("\n").join(FileUtils.readLines(new File("./yvan-rcs-web/src/example/example1.json"), StandardCharsets.UTF_8));
String fs = Joiner.on("\n").join(FileUtils.readLines(new File("./examples/example1.json"), StandardCharsets.UTF_8));
JsonWrapper jw = new JsonWrapper(fs);
LogisticsRuntimeService.INSTANCE.createEnv(1L);
LogisticsRuntime runtime = LogisticsRuntimeService.INSTANCE.findByEnvCode(1L);
runtime.loadMap(jw);
runtime.start();
EnvStartParam param = new EnvStartParam();
param.setTimeRate(1);
param.setVirtual(false);
runtime.start(param);
}
}
@ -75,6 +79,72 @@ public class RCS {
return result;
}
public static Object runPath() {
String executorId = "10"; // 执行器ID
String lpn = "pallet1124";
long envId = 1;
LogisticsRuntime logisticsRuntime = LogisticsRuntimeService.INSTANCE.findByEnvCode(envId);
RcsTaskBiz bizTask = new RcsTaskBiz();
bizTask.setBizTaskId(100L);
bizTask.setEnvId(envId);
bizTask.setBizType(BizTaskType.CARRY.toString());
bizTask.setLpn(lpn);
bizTask.setPriority(1);
bizTask.setTaskFrom("rack1_0_1_0");
bizTask.setTaskTo("54_0_0_0");
bizTask.setAllocatedExecutorId(executorId);
bizTask.setBizTaskPayload("N/A");
bizTask.setBizTaskErrorInfo("N/A");
bizTask.setBizTaskDescription("N/A");
bizTask.setBizTaskStatus(BizTaskStatus.WAITING_FOR_DISPATCH.toString());
PlanTaskSequence planSequence = new PlanTaskSequence(executorId, logisticsRuntime, bizTask, "demo");
CarryTask carryTask = new CarryTask(
executorId, lpn, 1,
new StoreLocation("rack1", 0, 1, 0),
new StoreLocation("54", 0, 0, 0)
);
logisticsRuntime.pathPlannerMap.get("cl2")
.planCarryTask(planSequence, "17", LCCDirection.DOWN, carryTask);
return planSequence.toPrettyMap();
}
public static Object runPath2() {
String executorId = "10"; // 执行器ID
String lpn = "pallet1124";
long envId = 1;
LogisticsRuntime logisticsRuntime = LogisticsRuntimeService.INSTANCE.findByEnvCode(envId);
RcsTaskBiz bizTask = new RcsTaskBiz();
bizTask.setBizTaskId(100L);
bizTask.setEnvId(envId);
bizTask.setBizType(BizTaskType.CARRY.toString());
bizTask.setLpn(lpn);
bizTask.setPriority(1);
bizTask.setTaskFrom("rack1_0_1_0");
bizTask.setTaskTo("54_0_0_0");
bizTask.setAllocatedExecutorId(executorId);
bizTask.setBizTaskPayload("N/A");
bizTask.setBizTaskErrorInfo("N/A");
bizTask.setBizTaskDescription("N/A");
bizTask.setBizTaskStatus(BizTaskStatus.WAITING_FOR_DISPATCH.toString());
PlanTaskSequence planSequence = new PlanTaskSequence(executorId, logisticsRuntime, bizTask, "demo");
CarryTask carryTask = new CarryTask(
executorId, lpn, 1,
new StoreLocation("rack1", 0, 1, 0),
new StoreLocation("54", 0, 0, 0)
);
logisticsRuntime.pathPlannerMap.get("cl2")
.planCarryTask(planSequence, "27", LCCDirection.UP, carryTask);
return planSequence.toPrettyMap();
}
public static void runDemo() {
String executorId = "10"; // 执行器ID
String lpn = "pallet1124";

2
servo/src/main/java/com/galaxis/rcs/common/enums/LCCDirection.java

@ -1,7 +1,7 @@
package com.galaxis.rcs.common.enums;
/**
* 路径点与货位 / 充电器关联记录
* 方向枚举
*/
public enum LCCDirection {
/**

1
servo/src/main/java/com/galaxis/rcs/common/enums/PlanTaskType.java

@ -5,6 +5,7 @@ package com.galaxis.rcs.common.enums;
*/
public enum PlanTaskType {
MOVE, // 移动任务
MOVE_BACKWARD, // 移动任务
ROTATION, // 旋转任务
LOAD, // 取货任务
UNLOAD, // 装载任务

51
servo/src/main/java/com/galaxis/rcs/plan/PlanTaskSequence.java

@ -1,5 +1,6 @@
package com.galaxis.rcs.plan;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.galaxis.rcs.common.entity.RcsTaskBiz;
import com.galaxis.rcs.common.entity.RcsTaskPlan;
import com.galaxis.rcs.common.enums.PlanTaskStatus;
@ -7,14 +8,18 @@ import com.galaxis.rcs.common.enums.PlanTaskType;
import com.google.common.collect.Lists;
import com.yvan.logisticsModel.LogisticsRuntime;
import org.clever.core.id.SnowFlake;
import org.clever.core.json.JsonWrapper;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class PlanTaskSequence {
@JsonIgnore
public static final SnowFlake snowFlake = new SnowFlake();
public final String executorId;
@JsonIgnore
public final LogisticsRuntime logisticsRuntime;
public final List<RcsTaskPlan> taskList = Lists.newArrayList();
public final RcsTaskBiz bizTask;
@ -58,6 +63,13 @@ public class PlanTaskSequence {
return task;
}
public RcsTaskPlan addMoveBackward(String wayPointId) {
RcsTaskPlan task = this.createTaskPlanEntity(PlanTaskType.MOVE_BACKWARD.toString());
task.setTargetId(wayPointId);
this.lastWayPointId = wayPointId;
return task;
}
// 添加旋转动作
public RcsTaskPlan addRotationTo(float rotationAngle) {
RcsTaskPlan task = this.createTaskPlanEntity(PlanTaskType.ROTATION.toString());
@ -94,4 +106,43 @@ public class PlanTaskSequence {
this.isFinished = true;
return task;
}
/**
* 输出方便阅读的 Json 格式
*
* @return
*/
public Map<String, ?> toPrettyMap() {
JsonWrapper jw = new JsonWrapper();
jw.set("executorId", executorId);
jw.set("bizTask", bizTask);
List<String> list = Lists.newArrayList();
for (RcsTaskPlan task : taskList) {
String taskStr = "UNKNOWN:" + task.getPlanType();
switch (PlanTaskType.valueOf(task.getPlanType())) {
case MOVE:
taskStr = "MOVE " + task.getTargetId();
break;
case MOVE_BACKWARD:
taskStr = "MOVE_BACKWARD " + task.getTargetId();
break;
case LOAD:
taskStr = "LOAD " + task.getTargetId() + "_" + task.getTargetBay() + "_" + task.getTargetLevel() + "_" + task.getTargetCell();
break;
case UNLOAD:
taskStr = "UNLOAD " + task.getTargetId() + "_" + task.getTargetBay() + "_" + task.getTargetLevel() + "_" + task.getTargetCell();
break;
case ROTATION:
taskStr = "Rotation " + task.getTargetRotation();
break;
case FINISH:
taskStr = "FINISH";
break;
}
list.add(taskStr);
}
jw.set("items", list);
return jw.getInnerMap();
}
}

166
servo/src/main/java/com/galaxis/rcs/plan/path/GraphInitializer.java

@ -9,87 +9,87 @@ import java.util.Map;
public class GraphInitializer {
public NavigationGraph initializeGraph(LogisticsRuntime runtime, String agvType, List<Map<String, Object>> jsonData) {
if (runtime.executorGraphMap.containsKey(agvType)) {
return runtime.executorGraphMap.get(agvType);
}
NavigationGraph graph = new NavigationGraph();
runtime.executorGraphMap.put(agvType, graph);
// 第一步:创建所有节点
for (Map<String, Object> nodeData : jsonData) {
String id = (String) nodeData.get("id");
// 判断是否是 way 类型才创建 NavigationNode
if (!"way".equals(nodeData.get("t"))) {
continue;
}
List<List<Float>> tf = (List<List<Float>>) nodeData.get("tf");
float x = tf.get(0).get(0);
float z = tf.get(0).get(2);
// 检查是否为可旋转点
Map<String, Object> dt = (Map<String, Object>) nodeData.get("dt");
boolean rotatable = false;
if (dt.containsKey("agvRotation")) {
rotatable = true;
}
// 添加节点
graph.addNode(new NavigationNode(id, x, z, rotatable));
}
// 第二步:添加路径连接
for (Map<String, Object> nodeData : jsonData) {
if (!"way".equals(nodeData.get("t"))) continue;
String id = (String) nodeData.get("id");
Map<String, Object> dt = (Map<String, Object>) nodeData.get("dt");
List<String> outEdges = (List<String>) dt.get("out");
if (outEdges != null) {
for (String neighborId : outEdges) {
if (graph.nodes.containsKey(id) && graph.nodes.containsKey(neighborId)) {
NavigationNode from = graph.nodes.get(id);
NavigationNode to = graph.nodes.get(neighborId);
graph.addBidirectionalPath(from, to);
}
}
}
}
// 第三步:添加操作点 OperationPoint
for (Map<String, Object> nodeData : jsonData) {
if (!"way".equals(nodeData.get("t"))) continue;
String nodeId = (String) nodeData.get("id");
Map<String, Object> dt = (Map<String, Object>) nodeData.get("dt");
if (dt.containsKey("linkStore")) {
List<Map<String, Object>> linkStores = (List<Map<String, Object>>) dt.get("linkStore");
for (Map<String, Object> store : linkStores) {
String targetId = (String) store.get("item");
Integer bay = Conv.asInteger(store.get("bay"));
Integer level = Conv.asInteger(store.get("level"));
Integer cell = Conv.asInteger(store.get("cell"));
// 根据位置确定方位(这里假设固定为 TOP,可根据 tf 中的方向判断更精确)
OperationSide side = OperationSide.TOP;
OperationPoint point = new OperationPoint(
graph.nodes.get(nodeId),
targetId,
side,
bay,
level,
cell
);
graph.addOperationPoint(point);
}
}
}
return graph;
}
// public NavigationGraph initializeGraph(LogisticsRuntime runtime, String agvType, List<Map<String, Object>> jsonData) {
// if (runtime.pathPlannerMap.containsKey(agvType)) {
// return runtime.pathPlannerMap.get(agvType);
// }
// NavigationGraph graph = new NavigationGraph();
// runtime.pathPlannerMap.put(agvType, graph);
//
// // 第一步:创建所有节点
// for (Map<String, Object> nodeData : jsonData) {
// String id = (String) nodeData.get("id");
//
// // 判断是否是 way 类型才创建 NavigationNode
// if (!"way".equals(nodeData.get("t"))) {
// continue;
// }
//
// List<List<Float>> tf = (List<List<Float>>) nodeData.get("tf");
// float x = tf.get(0).get(0);
// float z = tf.get(0).get(2);
//
// // 检查是否为可旋转点
// Map<String, Object> dt = (Map<String, Object>) nodeData.get("dt");
// boolean rotatable = false;
// if (dt.containsKey("agvRotation")) {
// rotatable = true;
// }
//
// // 添加节点
// graph.addNode(new NavigationNode(id, x, z, rotatable));
// }
//
// // 第二步:添加路径连接
// for (Map<String, Object> nodeData : jsonData) {
// if (!"way".equals(nodeData.get("t"))) continue;
//
// String id = (String) nodeData.get("id");
// Map<String, Object> dt = (Map<String, Object>) nodeData.get("dt");
//
// List<String> outEdges = (List<String>) dt.get("out");
// if (outEdges != null) {
// for (String neighborId : outEdges) {
// if (graph.nodes.containsKey(id) && graph.nodes.containsKey(neighborId)) {
// NavigationNode from = graph.nodes.get(id);
// NavigationNode to = graph.nodes.get(neighborId);
// graph.addBidirectionalPath(from, to);
// }
// }
// }
// }
//
// // 第三步:添加操作点 OperationPoint
// for (Map<String, Object> nodeData : jsonData) {
// if (!"way".equals(nodeData.get("t"))) continue;
//
// String nodeId = (String) nodeData.get("id");
// Map<String, Object> dt = (Map<String, Object>) nodeData.get("dt");
//
// if (dt.containsKey("linkStore")) {
// List<Map<String, Object>> linkStores = (List<Map<String, Object>>) dt.get("linkStore");
// for (Map<String, Object> store : linkStores) {
// String targetId = (String) store.get("item");
// Integer bay = Conv.asInteger(store.get("bay"));
// Integer level = Conv.asInteger(store.get("level"));
// Integer cell = Conv.asInteger(store.get("cell"));
//
// // 根据位置确定方位(这里假设固定为 TOP,可根据 tf 中的方向判断更精确)
// OperationSide side = OperationSide.TOP;
//
// OperationPoint point = new OperationPoint(
// graph.nodes.get(nodeId),
// targetId,
// side,
// bay,
// level,
// cell
// );
// graph.addOperationPoint(point);
// }
// }
// }
//
// return graph;
// }
}

8
servo/src/main/java/com/galaxis/rcs/plan/path2/AGVPathPlanner.java

@ -1,8 +0,0 @@
package com.galaxis.rcs.plan.path2;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class AGVPathPlanner {
}

220
servo/src/main/java/com/galaxis/rcs/plan/path2/AStarPathPlanner.java

@ -0,0 +1,220 @@
package com.galaxis.rcs.plan.path2;
import com.galaxis.rcs.common.enums.LCCDirection;
import com.google.common.collect.Maps;
import java.util.*;
import static com.galaxis.rcs.plan.path2.PathUtils.*;
public class AStarPathPlanner {
private static final float ROTATION_COST_PER_DEGREE = 0.01f;
private static final float BLOCKED_COST = 10000f; // 阻塞系数成本
private static final float WEIGHT_FACTOR = 1.5f; // 权重因子
private final NavigationGraph graph;
private final Map<String, Float> nodeWeights = Maps.newConcurrentMap();
private final Map<String, Float> blockedNodes = Maps.newConcurrentMap();
public AStarPathPlanner(NavigationGraph graph) {
this.graph = graph;
}
// 路径规划状态
public List<State> findPath(String startId, LCCDirection startDirection, String endId, LCCDirection endDirection) {
Node start = graph.getNode(startId);
Node end = graph.getNode(endId);
if (start == null) {
throw new RuntimeException("Start node not found: " + startId);
}
if (end == null) {
throw new RuntimeException("End node not found: " + endId);
}
// 使用复合键避免状态重复
Map<String, State> visited = new HashMap<>();
PriorityQueue<State> open = new PriorityQueue<>();
// 初始状态
State initialState = new State(start, startDirection, 0, heuristic(start, end), null);
open.add(initialState);
visited.put(stateKey(start.id(), startDirection), initialState);
while (!open.isEmpty()) {
State current = open.poll();
// 到达目标节点且方向匹配
if (current.node().id().equals(endId) && current.direction() == endDirection) {
return buildPath(current);
}
// 处理邻边移动
for (Node neighbor : graph.getNeighbors(current.node())) {
// 检查节点是否被阻塞
if (isBlocked(neighbor.id())) continue;
// 计算移动方向
LCCDirection moveDirection = calculateMoveDirection(current.node(), neighbor);
// 尝试前进
if (canMoveForward(current.direction(), moveDirection)) {
float moveCost = calculateMoveCost(current.node(), neighbor, false);
considerState(current, neighbor, current.direction(),
moveCost, open, visited, end);
}
// 尝试后退
else if (canMoveBackward(current.direction(), moveDirection)) {
float moveCost = calculateMoveCost(current.node(), neighbor, true);
considerState(current, neighbor, current.direction(),
moveCost, open, visited, end);
}
// 需要旋转
else if (current.node().rotatable()) {
// 计算需要旋转到的方向
LCCDirection requiredDirection = calculateRequiredDirection(moveDirection);
// 考虑旋转后移动
float rotationCost = calculateRotationCost(
current.direction(), requiredDirection, ROTATION_COST_PER_DEGREE
);
float moveCost = calculateMoveCost(current.node(), neighbor, false);
float totalCost = rotationCost + moveCost;
// 创建旋转状态
State rotatedState = new State(
current.node(), requiredDirection,
current.g() + rotationCost,
heuristic(current.node(), end),
current
);
// 考虑旋转后的移动
considerState(rotatedState, neighbor, requiredDirection,
moveCost, open, visited, end);
}
}
// 处理原地旋转 - 只考虑目标方向
if (current.node().rotatable()) {
// 只考虑旋转到目标方向(如果可能)
if (current.direction() != endDirection) {
float rotationCost = calculateRotationCost(
current.direction(), endDirection, ROTATION_COST_PER_DEGREE
);
considerState(current, current.node(), endDirection,
rotationCost, open, visited, end);
}
// 另外考虑旋转到移动所需的方向
for (Node neighbor : graph.getNeighbors(current.node())) {
LCCDirection moveDirection = calculateMoveDirection(current.node(), neighbor);
LCCDirection requiredDirection = calculateRequiredDirection(moveDirection);
if (requiredDirection != current.direction()) {
float rotationCost = calculateRotationCost(
current.direction(), requiredDirection, ROTATION_COST_PER_DEGREE
);
considerState(current, current.node(), requiredDirection,
rotationCost, open, visited, end);
}
}
// for (LCCDirection rotation : LCCDirection.values()) {
// if (rotation == current.direction()) continue;
//
// float rotationCost = calculateRotationCost(
// current.direction(), rotation, ROTATION_COST_PER_DEGREE
// );
// considerState(current, current.node(), rotation,
// rotationCost, open, visited, end);
// }
}
}
return Collections.emptyList();
}
/**
* 考虑新的状态并更新开放列表和访问记录
*
* @param current 当前状态
* @param nextNode 下一个节点
* @param nextDirection 下一个方向
* @param cost 移动成本
* @param open 开放列表
* @param visited 访问记录
* @param end 目标节点
*/
private void considerState(State current, Node nextNode, LCCDirection nextDirection,
float cost, PriorityQueue<State> open,
Map<String, State> visited, Node end) {
String key = stateKey(nextNode.id(), nextDirection);
float newG = current.g() + cost;
if (!visited.containsKey(key) || visited.get(key).g() > newG) {
float h = heuristic(nextNode, end);
State newState = new State(nextNode, nextDirection, newG, h, current);
open.add(newState);
visited.put(key, newState);
}
}
private List<State> buildPath(State state) {
LinkedList<State> path = new LinkedList<>();
while (state != null) {
path.addFirst(state);
state = state.parent();
}
return path;
}
/**
* 启发式函数计算两个节点之间的距离
*/
private float heuristic(Node a, Node b) {
return graph.distance(a, b);
// 使用曼哈顿距离??
// return Math.abs(a.x() - b.x()) + Math.abs(a.z() - b.z());
}
/**
* 生成状态的唯一键
*/
private String stateKey(String nodeId, LCCDirection direction) {
return nodeId + "|" + direction;
}
public void setNodeWeight(String nodeId, float weight) {
nodeWeights.put(nodeId, weight);
}
public void setBlocked(String nodeId, float blockedFactor) {
blockedNodes.put(nodeId, blockedFactor);
}
private float calculateMoveCost(Node from, Node to, boolean isBackward) {
float baseCost = graph.distance(from, to);
float weight = nodeWeights.getOrDefault(to.id(), 1.0f);
float blockedFactor = blockedNodes.getOrDefault(to.id(), 0f);
// 后退移动增加额外成本??
// return baseCost * weight * (1 + blockedFactor) * (isBackward ? 1.2f : 1.0f);
return baseCost * weight * (1 + blockedFactor);
}
private boolean isBlocked(String nodeId) {
return blockedNodes.containsKey(nodeId) && blockedNodes.get(nodeId) > 0.8f;
}
private boolean canMoveForward(LCCDirection currentDir, LCCDirection moveDir) {
return currentDir == moveDir;
}
private boolean canMoveBackward(LCCDirection currentDir, LCCDirection moveDir) {
return currentDir == getOppositeDirection(moveDir);
}
private LCCDirection calculateRequiredDirection(LCCDirection moveDirection) {
// 侧插式AGV只能前进或后退,不能横移
return moveDirection;
}
}

174
servo/src/main/java/com/galaxis/rcs/plan/path2/NavigationGraph.java

@ -1,83 +1,67 @@
package com.galaxis.rcs.plan.path2;
import com.galaxis.rcs.common.enums.LCCDirection;
import com.google.common.collect.Maps;
import com.yvan.logisticsModel.LogisticsRuntime;
import com.yvan.logisticsModel.StaticItem;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
/**
* A* 导航图
*/
@Slf4j
public class NavigationGraph {
private final LogisticsRuntime runtime;
/**
* 缓存距离计算结果避免重复计算
*/
private final Map<String, Float> distanceCache = Maps.newConcurrentMap();
/**
* 缓存邻居节点列表避免重复查询
*/
private final Map<String, List<Node>> neighborCache = Maps.newConcurrentMap();
/**
* 添加路径缓存
*/
private final Map<String, List<State>> pathCache = Maps.newConcurrentMap();
private final Map<String, Node> nodeMap = new HashMap<>();
private final Map<String, List<String>> storeToNodes = new HashMap<>();
public NavigationGraph() {
}
// private void initialize(LogisticsRuntime runtime) {
// var items = runtime.getStaticItems();
// for (Map<String, Object> item : items) {
// String type = (String) item.get("t");
// if ("way".equals(type)) {
// float[][] tf = (float[][]) item.get("tf");
// String id = (String) item.get("id");
// Map<String, Object> dt = (Map<String, Object>) item.get("dt");
//
// // 提取坐标
// float x = tf[0][0];
// float z = tf[0][2]; // Z向下增长
//
// // 检查可旋转性
// boolean rotatable = dt.containsKey("agvRotation");
//
// // 提取邻居节点
// List<String> in = (List<String>) dt.get("in");
// List<String> out = (List<String>) dt.get("out");
// Set<String> neighbors = new HashSet<>();
// if (in != null) neighbors.addAll(in);
// if (out != null) neighbors.addAll(out);
//
// // 提取货位链接
// List<StoreLink> storeLinks = new ArrayList<>();
// List<Map<String, Object>> links = (List<Map<String, Object>>) dt.get("linkStore");
// if (links != null) {
// for (Map<String, Object> link : links) {
// String storeId = (String) link.get("item");
// int bay = (Integer) link.get("bay");
// int level = (Integer) link.get("level");
// int cell = (Integer) link.get("cell");
// String direction = (String) link.get("direction");
// storeLinks.add(new StoreLink(storeId, bay, level, cell, LCCDirection.fromString(direction)));
//
// // 建立货位到节点的反向映射
// storeToNodes.computeIfAbsent(storeId, k -> new ArrayList<>()).add(id);
// }
// }
//
// nodeMap.put(id, new Node(id, x, z, rotatable,
// new ArrayList<>(neighbors), storeLinks));
// }
// }
//
// // 2. 验证邻居双向连接
// for (Node node : nodeMap.values()) {
// Iterator<String> it = node.neighbors().iterator();
// while (it.hasNext()) {
// String neighborId = it.next();
// if (!nodeMap.containsKey(neighborId)) {
// it.remove(); // 移除无效邻居
// }
// }
// }
// }
public NavigationGraph(LogisticsRuntime runtime) {
this.runtime = runtime;
}
public Node getNode(String id) {
return nodeMap.get(id);
public List<State> getCachedPath(String startId, LCCDirection startDirection,
String endId, LCCDirection endDirection) {
String cacheKey = startId + "|" + startDirection + "->" + endId + "|" + endDirection;
return pathCache.get(cacheKey);
}
public void cachePath(String startId, LCCDirection startDirection,
String endId, LCCDirection endDirection,
List<State> path) {
String cacheKey = startId + "|" + startDirection + "->" + endId + "|" + endDirection;
pathCache.put(cacheKey, path);
}
public List<String> getNodesForStore(String storeId) {
return storeToNodes.getOrDefault(storeId, Collections.emptyList());
public List<Node> getNodesForStore(String storeId) {
List<Node> nodes = new ArrayList<>();
List<String> nodeIds = storeToNodes.get(storeId);
if (nodeIds != null) {
for (String id : nodeIds) {
Node node = nodeMap.get(id);
if (node != null) {
nodes.add(node);
}
}
}
return nodes;
}
public List<Node> getNeighbors(Node node) {
@ -92,4 +76,68 @@ public class NavigationGraph {
float dz = a.z() - b.z();
return (float) Math.sqrt(dx * dx + dz * dz);
}
public void init() {
var items = runtime.getStaticItems();
for (StaticItem item : items) {
if ("way".equals(item.getT())) {
float[][] tf = item.getTf();
String id = item.getId();
Map<String, Object> dt = item.getDt();
// 提取坐标
float x = tf[0][0];
float z = tf[0][2]; // Z向下增长
// 检查可旋转性
boolean rotatable = dt.containsKey("agvRotation");
if (rotatable) {
log.info("Node {} is rotatable", id);
}
// 提取邻居节点
List<String> in = (List<String>) dt.get("in");
List<String> out = (List<String>) dt.get("out");
Set<String> neighbors = new HashSet<>();
if (in != null) neighbors.addAll(in);
if (out != null) neighbors.addAll(out);
// 提取货位链接
List<StoreLink> storeLinks = new ArrayList<>();
List<Map<String, Object>> links = (List<Map<String, Object>>) dt.get("linkStore");
if (links != null) {
for (Map<String, Object> link : links) {
String storeId = (String) link.get("item");
int bay = (Integer) link.get("bay");
int level = (Integer) link.get("level");
int cell = (Integer) link.get("cell");
String direction = (String) link.get("direction");
storeLinks.add(new StoreLink(storeId, bay, level, cell, LCCDirection.fromString(direction)));
// 建立货位到节点的反向映射
storeToNodes.computeIfAbsent(storeId, k -> new ArrayList<>()).add(id);
}
}
nodeMap.put(id, new Node(id, x, z, rotatable,
new ArrayList<>(neighbors), storeLinks));
}
}
// 2. 验证邻居双向连接
for (Node node : nodeMap.values()) {
Iterator<String> it = node.neighbors().iterator();
while (it.hasNext()) {
String neighborId = it.next();
if (!nodeMap.containsKey(neighborId)) {
it.remove(); // 移除无效邻居
}
}
}
}
public Node getNode(String id) {
return nodeMap.get(id);
}
}

168
servo/src/main/java/com/galaxis/rcs/plan/path2/PathUtils.java

@ -0,0 +1,168 @@
package com.galaxis.rcs.plan.path2;
import com.galaxis.rcs.common.enums.LCCDirection;
import java.util.*;
public class PathUtils {
/**
* 计算移动方向
*/
public static LCCDirection calculateMoveDirection(Node from, Node to) {
float dx = to.x() - from.x();
float dz = to.z() - from.z(); // 注意:Z轴向下增长
// 考虑左手坐标系:X向右,Z向下
if (Math.abs(dx) > Math.abs(dz)) {
return dx > 0.5 ? LCCDirection.RIGHT : LCCDirection.LEFT;
} else {
// 注意:Z向下增长,所以Z值增加表示向下移动
return dz > 0.5 ? LCCDirection.DOWN : LCCDirection.UP;
}
}
/**
* 检查方向是否有效侧插式专用
*/
public static boolean isValidForSideLoader(Node node, int bay, LCCDirection direction) {
Optional<StoreLink> link = node.storeLinks().stream()
.filter(l -> l.bay() == bay)
.findFirst();
if (link.isEmpty()) return false;
LCCDirection requiredDirection = convertForSideLoader(link.get().direction());
return direction == requiredDirection;
}
/**
* 转换货位方向到AGV所需方向侧插式专用
*/
public static LCCDirection convertForSideLoader(LCCDirection storeDirection) {
/*
* 侧插式AGV方向规则
* - 货位在上方 车头向右货叉朝左
* - 货位在下方 车头向左货叉朝左
* - 货位在左方 车头向上货叉朝左
* - 货位在右方 车头向下货叉朝左
*/
return switch (storeDirection) {
case UP -> LCCDirection.RIGHT;
case DOWN -> LCCDirection.LEFT;
case LEFT -> LCCDirection.UP;
case RIGHT -> LCCDirection.DOWN;
};
}
/**
* 获取最近的旋转点
*/
public static Node findNearestRotationNode(NavigationGraph graph, Node from, LCCDirection currentDir,
LCCDirection requiredDir) {
// 如果当前方向已经匹配,不需要旋转
if (currentDir == requiredDir) return from;
// 使用Dijkstra算法查找最近的旋转点
Map<Node, Float> distances = new HashMap<>();
Map<Node, Node> predecessors = new HashMap<>();
PriorityQueue<Node> queue = new PriorityQueue<>(Comparator.comparingDouble(distances::get));
distances.put(from, 0f);
queue.add(from);
Node result = null;
while (!queue.isEmpty()) {
Node current = queue.poll();
// 找到可旋转点
if (current.rotatable()) {
result = current;
break;
}
// 探索邻居
for (Node neighbor : graph.getNeighbors(current)) {
float newDist = distances.get(current) + graph.distance(current, neighbor);
if (newDist < distances.getOrDefault(neighbor, Float.MAX_VALUE)) {
distances.put(neighbor, newDist);
predecessors.put(neighbor, current);
queue.add(neighbor);
}
}
}
return result;
}
/**
* 获取相反方向
*/
public static LCCDirection getOppositeDirection(LCCDirection dir) {
return switch (dir) {
case UP -> LCCDirection.DOWN;
case DOWN -> LCCDirection.UP;
case LEFT -> LCCDirection.RIGHT;
case RIGHT -> LCCDirection.LEFT;
};
}
/**
* 计算旋转代价
*/
public static float calculateRotationCost(LCCDirection from, LCCDirection to, float ROTATION_COST_PER_DEGREE) {
float angle1 = getRequiredDirection(from);
float angle2 = getRequiredDirection(to);
float diff = Math.abs(angle1 - angle2);
diff = Math.min(diff, 360 - diff);
// return diff * ROTATION_COST_PER_DEGREE;
return 0.1f;
}
/**
* 获取所需方向的角度
*/
public static float getRequiredDirection(LCCDirection direction) {
return switch (direction) {
case UP -> 90f;
case DOWN -> 270f;
case LEFT -> 180f;
case RIGHT -> 0f;
};
}
/**
* 转换货位方向到AGV所需方向
*/
public static LCCDirection convertStoreDirection(LCCDirection storeDirection) {
// 转换规则: 货位在路径点的方位 -> AGV所需方向
return switch (storeDirection) {
case UP -> LCCDirection.RIGHT; // 货位在上方 → 车头向右
case DOWN -> LCCDirection.LEFT; // 货位在下方 → 车头向左
case LEFT -> LCCDirection.UP; // 货位在左方 → 车头向上
case RIGHT -> LCCDirection.DOWN; // 货位在右方 → 车头向下
};
}
/**
* 将角度转换为 LCCDirection
*/
public static LCCDirection convertAngleToDirection(float angle) {
// 标准化角度
angle = ((angle % 360f) + 360f) % 360f;
if (angle >= 315f || angle < 45f) return LCCDirection.RIGHT;
if (angle >= 45f && angle < 135f) return LCCDirection.UP;
if (angle >= 135f && angle < 225f) return LCCDirection.LEFT;
return LCCDirection.DOWN;
}
/**
* 计算方向角度差
*/
public static float angleDifference(float angle1, float angle2) {
float diff = Math.abs(angle1 - angle2) % 360;
return diff > 180 ? 360 - diff : diff;
}
}

142
servo/src/main/java/com/galaxis/rcs/plan/path2/PtrPathPlanner.java

@ -0,0 +1,142 @@
package com.galaxis.rcs.plan.path2;
import com.galaxis.rcs.common.enums.LCCDirection;
import com.galaxis.rcs.plan.PlanTaskSequence;
import com.galaxis.rcs.plan.task.CarryTask;
import java.util.List;
import static com.galaxis.rcs.plan.path2.PathUtils.convertStoreDirection;
import static com.galaxis.rcs.plan.path2.PathUtils.getRequiredDirection;
public class PtrPathPlanner {
private final NavigationGraph graph;
private final AStarPathPlanner astar;
public PtrPathPlanner(NavigationGraph graph) {
this.graph = graph;
this.astar = new AStarPathPlanner(graph);
}
public void planCarryTask(PlanTaskSequence plan, String startNodeId, LCCDirection startDirection, CarryTask task) {
// 取货点
String loadRackId = task.from().rackId();
int pickupBay = task.from().bay();
NodeDirection loadNodeDirection = findNodeForStore(loadRackId, pickupBay);
if (loadNodeDirection == null) {
throw new RuntimeException("Pickup node not found for rackId=" + loadRackId + ", bay=" + pickupBay);
}
// 放货点
String unloadRackId = task.to().rackId();
int unloadBay = task.to().bay();
NodeDirection unloadNodeDirection = findNodeForStore(unloadRackId, unloadBay);
if (unloadNodeDirection == null) {
throw new RuntimeException("Drop node not found for rackId=" + unloadRackId + ", bay=" + unloadBay);
}
// 规划到取货点路径
List<State> toLoadPath = astar.findPath(startNodeId, startDirection, loadNodeDirection.node().id(), loadNodeDirection.direction());
// 检查方向是否匹配,如果不匹配则插入旋转点
if (!toLoadPath.isEmpty()) {
State lastState = toLoadPath.get(toLoadPath.size() - 1);
if (lastState.direction() != loadNodeDirection.direction()) {
Node rotationNode = PathUtils.findNearestRotationNode(
graph, lastState.node(), lastState.direction(), loadNodeDirection.direction()
);
if (rotationNode != null) {
// 插入旋转路径
List<State> toRotation = astar.findPath(
lastState.node().id(), lastState.direction(),
rotationNode.id(), loadNodeDirection.direction()
);
toLoadPath.addAll(toRotation);
// 从旋转点到目标点
List<State> fromRotation = astar.findPath(
rotationNode.id(), loadNodeDirection.direction(),
loadNodeDirection.node().id(), loadNodeDirection.direction()
);
toLoadPath.addAll(fromRotation);
}
}
}
// 规划到放货点路径
List<State> toUnloadPath = astar.findPath(loadNodeDirection.node().id(), loadNodeDirection.direction(), unloadNodeDirection.node().id(), unloadNodeDirection.direction());
// 生成指令序列
generateMoves(plan, toLoadPath);
plan.addLoad(task.lpn(), loadRackId, pickupBay, task.from().level(), task.from().cell());
generateMoves(plan, toUnloadPath);
plan.addUnload(unloadRackId, task.to().level(), task.to().bay(), task.to().cell());
plan.addFinish();
}
private NodeDirection findNodeForStore(String storeId, int bay) {
List<Node> nodes = this.graph.getNodesForStore(storeId);
for (Node node : nodes) {
for (StoreLink link : node.storeLinks()) {
if (link.storeId().equals(storeId) && link.bay() == bay) {
LCCDirection agvDirection = convertStoreDirection(link.direction());
return new NodeDirection(node, agvDirection);
}
}
}
return null;
}
/**
* 获取指定节点和货架的所需朝向
*/
private LCCDirection getRequiredHeading(Node node, int bay) {
return node.storeLinks().stream()
.filter(link -> link.bay() == bay)
.findFirst()
.map(link -> link.direction())
.orElseThrow(() -> new RuntimeException("Not found storeLink in id=" + node.id() + ", bay=" + bay));
}
/**
* 根据A*状态生成移动指令序列
*/
private void generateMoves(PlanTaskSequence sequence, List<State> path) {
if (path.isEmpty()) return;
// 第一个状态是起点,跳过
State prevState = path.get(0);
for (int i = 1; i < path.size(); i++) {
State current = path.get(i);
// 如果是旋转动作
if (current.node().equals(prevState.node())) {
float angle = PathUtils.getRequiredDirection(current.direction());
sequence.addRotationTo(angle);
}
// 移动动作 - 检查是否需要后退
else {
// 检查移动方向
LCCDirection moveDir = PathUtils.calculateMoveDirection(prevState.node(), current.node());
// 如果移动方向与车头方向相反,需要后退
boolean isBackward = (current.direction() == PathUtils.getOppositeDirection(moveDir));
if (isBackward) {
sequence.addMoveBackward(current.node().id());
} else {
sequence.addMoveTo(current.node().id());
}
}
prevState = current;
}
}
// 辅助记录类
private record NodeDirection(Node node, LCCDirection direction) {
}
}

19
servo/src/main/java/com/galaxis/rcs/plan/path2/State.java

@ -0,0 +1,19 @@
package com.galaxis.rcs.plan.path2;
import com.galaxis.rcs.common.enums.LCCDirection;
/**
* A* 路径规划状态
*/
public record State(Node node,
LCCDirection direction,
float g,
float h,
State parent)
implements Comparable<State> {
@Override
public int compareTo(State other) {
return Float.compare(g + h, other.g + other.h);
}
}

9
servo/src/main/java/com/galaxis/rcs/plan/path2/StoreLink.java

@ -2,6 +2,15 @@ package com.galaxis.rcs.plan.path2;
import com.galaxis.rcs.common.enums.LCCDirection;
/**
* A* 路径与库位的连接定义
*
* @param storeId 库位ID
* @param bay
* @param level
* @param cell
* @param direction 方位路径相对于库位的方位比如路标的"左边"是库位
*/
public record StoreLink(
String storeId, int bay, int level, int cell, LCCDirection direction
) {

34
servo/src/main/java/com/galaxis/rcs/plan/task/CarryTask.java

@ -0,0 +1,34 @@
package com.galaxis.rcs.plan.task;
import com.galaxis.rcs.common.entity.StoreLocation;
/**
* 搬运任务
* <pre>
* {
* type: 'carry', // 任务类型
* agv: 'cl2', // 车号
* lpn: 'pallet1124', // 托盘ID,用于校验,规划器不用管
* priority: 1, // 优先级,用于排车,规划器不用管
* // 搬运源货位
* from: {
* item: 'rack1',// 货架编号
* bay: 0, // 货架列
* level: 1, // 货架层(用于机械臂,规划器不用管)
* cell: 0 // 货架格(用于机械臂,规划器不用管)
* },
* // 目标货位
* to: {
* item: '54' // 地堆货位号
* }
* }
* </pre>
*/
public record CarryTask(
String agv,
String lpn,
int priority,
StoreLocation from,
StoreLocation to
) {
}

2
servo/src/main/java/com/yvan/logisticsEnv/EnvStartParam.java

@ -18,6 +18,6 @@ public class EnvStartParam {
/**
* 时间倍速(仿真环境专用)
*/
private Integer timespeed;
private Integer timeRate;
}

14
servo/src/main/java/com/yvan/logisticsEnv/LogisticsEnv.java

@ -48,11 +48,15 @@ public class LogisticsEnv {
*/
private int timeRate;
/**
* 获取AGV车列表
*/
List<ExecutorItem> getExecutorList() {
return Lists.newArrayList();
public void start(EnvStartParam param) {
if (this.isRunning) {
throw new IllegalStateException("环境已经在运行中");
}
this.startParam = param;
this.isRunning = true;
this.startTime = System.currentTimeMillis();
this.stopTime = 0L;
this.timeRate = this.startParam.getTimeRate();
}
public long currentTimeMillis() {

35
servo/src/main/java/com/yvan/logisticsModel/LogisticsRuntime.java

@ -1,21 +1,22 @@
package com.yvan.logisticsModel;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.galaxis.rcs.common.entity.RcsTaskBiz;
import com.galaxis.rcs.plan.path.NavigationGraph;
import com.galaxis.rcs.plan.path2.NavigationGraph;
import com.galaxis.rcs.plan.path2.PtrPathPlanner;
import com.galaxis.rcs.task.TaskDispatchFactory;
import com.galaxis.rcs.task.TaskService;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.yvan.logisticsEnv.EnvStartParam;
import com.yvan.logisticsEnv.LogisticsEnv;
import com.yvan.workbench.model.entity.Vector2;
import lombok.extern.slf4j.Slf4j;
import org.clever.core.Conv;
import org.clever.core.json.JsonWrapper;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 物流上下文运行时
@ -54,7 +55,7 @@ public class LogisticsRuntime {
* AGV导航地图 车类型 t -> NavigationGraph
*/
@JsonIgnore
public final Map<String, NavigationGraph> executorGraphMap = Maps.newHashMap();
public final Map<String, PtrPathPlanner> pathPlannerMap = Maps.newHashMap();
/**
* 楼层目录 catalogCode -> Floor
@ -156,21 +157,33 @@ public class LogisticsRuntime {
}
}
public void start() {
public void start(EnvStartParam param) {
// 开启所有机器人的任务调度
Set<String> executorTypes = Sets.newHashSet();
for (ExecutorItem executorItem : executorItemMap.values()) {
executorItem.mapReady();
executorTypes.add(executorItem.getT());
log.info("Executor {} started", executorItem.getId());
}
// 初始化地图
NavigationGraph graph = new NavigationGraph(this);
graph.init();
for (String type : executorTypes) {
this.pathPlannerMap.put(type, new PtrPathPlanner(graph));
}
this.logisticsEnv.start(param);
this.taskDispatchFactory.startPolling();
}
public Iterable<StaticItem> getStaticItems() {
for (Floor floor : this.floorMap.values()) {
// return floor.itemMap.values();
return floor.itemMap.values();
public boolean isRunning() {
return this.logisticsEnv.isRunning();
}
return null;
public Iterable<StaticItem> getStaticItems() {
return () -> this.floorMap.values().stream()
.flatMap(floor -> floor.itemMap.values().stream())
.iterator();
}
}

5
servo/src/main/java/com/yvan/logisticsModel/PtrAgvItem.java

@ -12,6 +12,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
//0.4m/ss // a max 1.2m/s
//90 = 3.5s cl2
//90 = 5s // cLX
@ -77,6 +78,7 @@ public class PtrAgvItem extends ExecutorItem {
this.startConnector();
}
/**
* 启动连接器线程
*/
@ -211,7 +213,7 @@ public class PtrAgvItem extends ExecutorItem {
}
public boolean isFree() {
return (this.deviceTaskQueue.isEmpty() && this.connectorThread.isRunning());
return (this.logisticsRuntime.isRunning() && this.deviceTaskQueue.isEmpty() && this.connectorThread.isRunning());
}
public PtrAgvItem(LogisticsRuntime logisticsRuntime, Map<String, Object> raw) {
@ -228,7 +230,6 @@ public class PtrAgvItem extends ExecutorItem {
}
private static class COperationType {
public static final short move = 0;
public static final short load = 1;

8
servo/src/main/java/com/yvan/workbench/controller/EnvController.java

@ -32,6 +32,14 @@ public class EnvController {
return Model.newSuccess(true);
}
public static Model<?> runPath() {
return Model.newSuccess(RCS.runPath());
}
public static Model<?> runPath2() {
return Model.newSuccess(RCS.runPath2());
}
public static Model<?> getDemo() {
return Model.newSuccess(RCS.getDemo());
}

2
servo/src/main/resources/application-dev.yml

@ -70,7 +70,7 @@ web:
timeout: -1
read-only: false
hot-reload:
enable: true
enable: false
# watchFile: './build/.hotReload'
interval: 1s
exclude-packages: [ ]

Loading…
Cancel
Save