diff --git a/examples/f1.json b/examples/f1.json new file mode 100644 index 0000000..fcf20fd --- /dev/null +++ b/examples/f1.json @@ -0,0 +1,422 @@ +[ + { + "id": "rack1", + "t": "rack", + "v": true, + "tf": [ + [ + 1.55, + 0, + -1.5 + ], + [ + 0, + 0, + 0 + ], + [ + 2.2, + 2.8, + 1 + ] + ], + "dt": { + "rackDepth": 1, + "bottomBarHeight": 0.2, + "bottomLinkHeight": 0.2, + "topLinkDistance": 0.2, + "levelCount": 2, + "bayCount": 2, + "hideFloor": 0, + "extendColumns": 1, + "columnSpacing": 1, + "bays": [ + { + "bayWidth": 1.1, + "levelHeight": [ + 1.4, + 1.4 + ] + }, + { + "bayWidth": 1.1, + "levelHeight": [ + 1.4, + 1.4 + ] + } + ], + "center": [], + "in": [], + "out": [], + "rackWidth": 2.2, + "rackHeight": 2.8 + }, + "_rid": "_2" + }, + { + "id": "rack2", + "t": "rack", + "v": true, + "tf": [ + [ + 3.9, + 0, + -1.5 + ], + [ + 0, + 0, + 0 + ], + [ + 2.2, + 2.8, + 1 + ] + ], + "dt": { + "rackDepth": 1, + "bottomBarHeight": 0.2, + "bottomLinkHeight": 0.2, + "topLinkDistance": 0.2, + "levelCount": 2, + "bayCount": 2, + "hideFloor": 0, + "extendColumns": 1, + "columnSpacing": 1, + "bays": [ + { + "bayWidth": 1.1, + "levelHeight": [ + 1.4, + 1.4 + ] + }, + { + "bayWidth": 1.1, + "levelHeight": [ + 1.4, + 1.4 + ] + } + ], + "center": [], + "in": [], + "out": [], + "rackWidth": 2.2, + "rackHeight": 2.8 + }, + "_rid": "_3" + }, + { + "id": "1_2", + "t": "way", + "v": true, + "logicX": 1, + "logicY": 2, + "tf": [ + [ + 1, + 0.01, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0.25, + 0.1, + 0.25 + ] + ], + "dt": { + "in": [ + "2_2" + ], + "out": [ + "2_2" + ], + "center": [], + "linkStore": [ + { + "item": "rack1", + "bay": 0, + "level": 0, + "cell": 0, + "direction": "up" + }, + { + "item": "rack1", + "bay": 0, + "level": 1, + "cell": 0, + "direction": "up" + } + ] + }, + "_rid": "_4" + }, + { + "id": "2_2", + "t": "way", + "v": true, + "logicX": 2, + "logicY": 2, + "tf": [ + [ + 2.1, + 0.01, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0.25, + 0.1, + 0.25 + ] + ], + "dt": { + "in": [ + "1_2", + "3_2" + ], + "out": [ + "1_2", + "3_2" + ], + "center": [], + "linkStore": [ + { + "item": "rack1", + "bay": 1, + "level": 0, + "cell": 0, + "direction": "up" + }, + { + "item": "rack1", + "bay": 1, + "level": 1, + "cell": 0, + "direction": "up" + } + ] + }, + "_rid": "_5" + }, + { + "id": "3_2", + "t": "way", + "v": true, + "logicX": 3, + "logicY": 2, + "tf": [ + [ + 3.39, + 0.01, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0.25, + 0.1, + 0.25 + ] + ], + "dt": { + "in": [ + "2_2", + "4_2" + ], + "out": [ + "2_2", + "4_2" + ], + "center": [], + "linkStore": [ + { + "item": "rack2", + "bay": 0, + "level": 0, + "cell": 0, + "direction": "up" + }, + { + "item": "rack2", + "bay": 0, + "level": 1, + "cell": 0, + "direction": "up" + } + ] + }, + "_rid": "_6" + }, + { + "id": "4_2", + "t": "way", + "v": true, + "logicX": 4, + "logicY": 2, + "tf": [ + [ + 4.44, + 0.01, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0.25, + 0.1, + 0.25 + ] + ], + "dt": { + "in": [ + "3_2", + "5_2" + ], + "out": [ + "3_2", + "5_2" + ], + "center": [], + "linkStore": [ + { + "item": "rack2", + "bay": 1, + "level": 0, + "cell": 0, + "direction": "up" + }, + { + "item": "rack2", + "bay": 1, + "level": 1, + "cell": 0, + "direction": "up" + } + ] + }, + "_rid": "_7" + }, + { + "id": "5_2", + "t": "way", + "v": true, + "logicX": 5, + "logicY": 2, + "tf": [ + [ + 5.44, + 0.01, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0.25, + 0.1, + 0.25 + ] + ], + "dt": { + "in": [ + "4_2", + "6_2" + ], + "out": [ + "4_2", + "6_2" + ], + "center": [] + }, + "_rid": "_8" + }, + { + "id": "6_2", + "t": "way", + "v": true, + "logicX": 6, + "logicY": 2, + "tf": [ + [ + 6.44, + 0.01, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 0.25, + 0.1, + 0.25 + ] + ], + "dt": { + "in": [ + "5_2" + ], + "out": [ + "5_2" + ], + "center": [], + "agvRotation": [ + "cl2", + "clx" + ] + }, + "_rid": "_9" + }, + { + "id": "3", + "t": "cl2", + "v": true, + "tf": [ + [ + 6.440, + 0, + 0 + ], + [ + 0, + 0, + 0 + ], + [ + 1.5, + 1.98, + 1.5 + ] + ], + "dt": { + "in": [], + "out": [], + "center": [], + "ptrWidth": 1.5, + "ptrDepth": 1.5, + "ptrHeight": 1.98 + } + } +] diff --git a/servo/src/main/java/com/galaxis/rcs/RCS.java b/servo/src/main/java/com/galaxis/rcs/RCS.java index 81c9f75..ee3c150 100644 --- a/servo/src/main/java/com/galaxis/rcs/RCS.java +++ b/servo/src/main/java/com/galaxis/rcs/RCS.java @@ -11,41 +11,21 @@ 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.google.common.base.Strings; import com.yvan.logisticsEnv.EnvStartParam; import com.yvan.logisticsModel.LogisticsRuntime; import com.yvan.logisticsModel.LogisticsRuntimeService; +import com.yvan.workbench.model.query.QLccModelFloor; import lombok.SneakyThrows; -import org.apache.commons.io.FileUtils; import org.clever.core.json.JsonWrapper; - -import java.io.File; -import java.nio.charset.StandardCharsets; +import org.clever.data.jdbc.DaoFactory; +import org.clever.data.jdbc.QueryDSL; /** * RCS 对外API调用类 */ public class RCS { static { - init(); - } - - @SneakyThrows - public static void init() { - if (LogisticsRuntimeService.INSTANCE.findByEnvCode(1L) == null) { - 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); - - EnvStartParam param = new EnvStartParam(); - param.setTimeRate(1); - param.setVirtual(false); - runtime.start(param); - } - } public static void ok() { @@ -79,6 +59,40 @@ public class RCS { return result; } + /** + * 加载项目的楼层数据 (只加载物理生产环境) + */ + @SneakyThrows + public static boolean loadFloor(String projectUUID, String catalogCode, long envId) { + LogisticsRuntime runtime = LogisticsRuntimeService.INSTANCE.findByProjectFloor(projectUUID, catalogCode); + if (runtime == null) { + QueryDSL queryDsl = DaoFactory.getQueryDSL(); + String floorPayload = queryDsl.select(QLccModelFloor.lccModelFloor.items) + .from(QLccModelFloor.lccModelFloor) + .where(QLccModelFloor.lccModelFloor.projectUuid.eq(projectUUID)) + .where(QLccModelFloor.lccModelFloor.catalogCode.eq(catalogCode)) + .fetchFirst(); + + if (Strings.isNullOrEmpty(floorPayload)) { + throw new RuntimeException("not found floor data for projectUUID: " + projectUUID + ", catalogCode: " + catalogCode); + } + + JsonWrapper jw = new JsonWrapper("{ \"catalogCode\": \"" + catalogCode + "\", \"t\": \"floor\", \"items\": " + floorPayload + "}"); + + LogisticsRuntimeService.INSTANCE.createEnv(envId); + runtime = LogisticsRuntimeService.INSTANCE.findByEnvCode(envId); + runtime.projectUUID = projectUUID; + runtime.catalogCode = catalogCode; + runtime.loadMap(jw); + + EnvStartParam param = new EnvStartParam(); + param.setTimeRate(1); + param.setVirtual(false); + runtime.start(param); + } + return true; + } + public static Object runPath() { String executorId = "10"; // 执行器ID String lpn = "pallet1124"; diff --git a/servo/src/main/java/com/galaxis/rcs/common/enums/BizTaskType.java b/servo/src/main/java/com/galaxis/rcs/common/enums/BizTaskType.java index d95355b..f4568d1 100644 --- a/servo/src/main/java/com/galaxis/rcs/common/enums/BizTaskType.java +++ b/servo/src/main/java/com/galaxis/rcs/common/enums/BizTaskType.java @@ -1,6 +1,8 @@ package com.galaxis.rcs.common.enums; public enum BizTaskType { + MOVE, // 移动任务 + CHARGE, // 充电任务 CARRY; // 搬运任务 public static BizTaskType fromString(String value) { diff --git a/servo/src/main/java/com/galaxis/rcs/plan/path2/NavigationGraph.java b/servo/src/main/java/com/galaxis/rcs/plan/path2/NavigationGraph.java index 837b1b4..bb1722d 100644 --- a/servo/src/main/java/com/galaxis/rcs/plan/path2/NavigationGraph.java +++ b/servo/src/main/java/com/galaxis/rcs/plan/path2/NavigationGraph.java @@ -50,6 +50,10 @@ public class NavigationGraph { pathCache.put(cacheKey, path); } + public Node getNodeById(String nodeId){ + return nodeMap.get(nodeId); + } + public List getNodesForStore(String storeId) { List nodes = new ArrayList<>(); List nodeIds = storeToNodes.get(storeId); diff --git a/servo/src/main/java/com/galaxis/rcs/plan/path2/PtrPathPlanner.java b/servo/src/main/java/com/galaxis/rcs/plan/path2/PtrPathPlanner.java index e11e2de..309dfba 100644 --- a/servo/src/main/java/com/galaxis/rcs/plan/path2/PtrPathPlanner.java +++ b/servo/src/main/java/com/galaxis/rcs/plan/path2/PtrPathPlanner.java @@ -3,6 +3,7 @@ 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 com.galaxis.rcs.plan.task.MoveTask; import java.util.List; @@ -18,6 +19,16 @@ public class PtrPathPlanner { this.astar = new AStarPathPlanner(graph); } + + public void planMoveTask(PlanTaskSequence plan, String startNodeId, LCCDirection startDirection, MoveTask moveTask) { + + Node node = this.graph.getNodeById(moveTask.targetWayPointId()); + + List toPath = astar.findPath(startNodeId, startDirection, node.id(), startDirection); + generateMoves(plan, toPath); + plan.addFinish(); + } + public void planCarryTask(PlanTaskSequence plan, String startNodeId, LCCDirection startDirection, CarryTask task) { // 取货点 String loadRackId = task.from().rackId(); @@ -91,17 +102,6 @@ public class PtrPathPlanner { } /** - * 获取指定节点和货架的所需朝向 - */ - 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 path) { @@ -136,6 +136,7 @@ public class PtrPathPlanner { } } + // 辅助记录类 private record NodeDirection(Node node, LCCDirection direction) { } diff --git a/servo/src/main/java/com/galaxis/rcs/plan/task/CarryTask.java b/servo/src/main/java/com/galaxis/rcs/plan/task/CarryTask.java index 89d1a24..8a49546 100644 --- a/servo/src/main/java/com/galaxis/rcs/plan/task/CarryTask.java +++ b/servo/src/main/java/com/galaxis/rcs/plan/task/CarryTask.java @@ -32,3 +32,5 @@ public record CarryTask( StoreLocation to ) { } + + diff --git a/servo/src/main/java/com/galaxis/rcs/plan/task/MoveTask.java b/servo/src/main/java/com/galaxis/rcs/plan/task/MoveTask.java new file mode 100644 index 0000000..43fecf0 --- /dev/null +++ b/servo/src/main/java/com/galaxis/rcs/plan/task/MoveTask.java @@ -0,0 +1,8 @@ +package com.galaxis.rcs.plan.task; + +public record MoveTask( + String agv, + String targetWayPointId, + int priority +) { +} diff --git a/servo/src/main/java/com/yvan/logisticsModel/ExecutorItem.java b/servo/src/main/java/com/yvan/logisticsModel/ExecutorItem.java index 0eec10d..12039bd 100644 --- a/servo/src/main/java/com/yvan/logisticsModel/ExecutorItem.java +++ b/servo/src/main/java/com/yvan/logisticsModel/ExecutorItem.java @@ -1,5 +1,6 @@ package com.yvan.logisticsModel; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.galaxis.rcs.common.entity.RcsTaskPlan; import com.galaxis.rcs.plan.PlanTaskSequence; import com.google.common.collect.Queues; @@ -14,8 +15,9 @@ import java.util.concurrent.BlockingQueue; * 每个物流任务执行器都会有一个 Connector 线程,专门用来消费 currentPlanList 队列,变成实际的设备指令 */ public abstract class ExecutorItem extends BaseItem { - + @JsonIgnore public final LogisticsRuntime logisticsRuntime; + @JsonIgnore public boolean isMapReady = false; public void mapReady() { diff --git a/servo/src/main/java/com/yvan/logisticsModel/LogisticsRuntime.java b/servo/src/main/java/com/yvan/logisticsModel/LogisticsRuntime.java index 4c97685..098f167 100644 --- a/servo/src/main/java/com/yvan/logisticsModel/LogisticsRuntime.java +++ b/servo/src/main/java/com/yvan/logisticsModel/LogisticsRuntime.java @@ -66,6 +66,9 @@ public class LogisticsRuntime { this.logisticsEnv = logisticsEnv; } + public String projectUUID; + public String catalogCode; + /** * 获取当前空闲的执行器列表 */ diff --git a/servo/src/main/java/com/yvan/logisticsModel/LogisticsRuntimeService.java b/servo/src/main/java/com/yvan/logisticsModel/LogisticsRuntimeService.java index c57cb2e..82a525d 100644 --- a/servo/src/main/java/com/yvan/logisticsModel/LogisticsRuntimeService.java +++ b/servo/src/main/java/com/yvan/logisticsModel/LogisticsRuntimeService.java @@ -22,6 +22,18 @@ public class LogisticsRuntimeService { } } + /** + * 根据项目UUID和目录代码查找物流运行时实例 + */ + public LogisticsRuntime findByProjectFloor(String projectUUID, String catalogCode) { + for (LogisticsRuntime runtime : runtimeMap.values()) { + if (projectUUID.equals(runtime.projectUUID) && catalogCode.equals(runtime.catalogCode)) { + return runtime; + } + } + return null; + } + public void createEnv(long envId) { synchronized (LOCK) { if (runtimeMap.containsKey(envId)) { diff --git a/servo/src/main/java/com/yvan/logisticsModel/PtrAgvItem.java b/servo/src/main/java/com/yvan/logisticsModel/PtrAgvItem.java index 65a8de0..0c433ac 100644 --- a/servo/src/main/java/com/yvan/logisticsModel/PtrAgvItem.java +++ b/servo/src/main/java/com/yvan/logisticsModel/PtrAgvItem.java @@ -1,6 +1,8 @@ package com.yvan.logisticsModel; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.galaxis.rcs.common.entity.RcsTaskPlan; +import com.galaxis.rcs.common.enums.LCCDirection; import com.galaxis.rcs.common.enums.PlanTaskType; import com.galaxis.rcs.connector.cl2.Cl2DeviceConnector; import com.galaxis.rcs.plan.PlanTaskSequence; @@ -61,15 +63,27 @@ public class PtrAgvItem extends ExecutorItem { // agv当前转动角度值 public double orientation; + public LCCDirection getLCCDirection() { + return switch (direction) { + case 0 -> LCCDirection.RIGHT; + case 1 -> LCCDirection.DOWN; + case 2 -> LCCDirection.LEFT; + case 3 -> LCCDirection.UP; + default -> null; + }; + } // 执行中的任务 + @JsonIgnore public List runningDeviceTaskList = new ArrayList<>(); /** * 当前执行的任务规划列表 */ + @JsonIgnore final BlockingQueue planQueue = Queues.newArrayBlockingQueue(BLOCKING_QUEUE_CAPACITY); + @JsonIgnore final BlockingQueue deviceTaskQueue = Queues.newArrayBlockingQueue(BLOCKING_QUEUE_CAPACITY); @@ -80,6 +94,7 @@ public class PtrAgvItem extends ExecutorItem { /** * 更新设备任务状态 暂时没有处理任务取消相关的状态 + * * @param seqNo * @param x * @param y @@ -231,7 +246,7 @@ public class PtrAgvItem extends ExecutorItem { List> bays = (List>) storeItemRaw.get("bays"); Map bay = bays.get(plan.getTargetBay()); List levelHeight = (List) bay.get("levelHeight"); - deviceTask.goodsSlotHeight = (int)Math.round(levelHeight.get(plan.getTargetLevel()) * 1000); + deviceTask.goodsSlotHeight = (int) Math.round(levelHeight.get(plan.getTargetLevel()) * 1000); } else { deviceTask.goodsSlotHeight = 1; } @@ -269,7 +284,7 @@ public class PtrAgvItem extends ExecutorItem { List> bays = (List>) storeItemRaw.get("bays"); Map bay = bays.get(plan.getTargetBay()); List levelHeight = (List) bay.get("levels"); - deviceTask.goodsSlotHeight = (int)Math.round(levelHeight.get(plan.getTargetLevel()) * 1000); + deviceTask.goodsSlotHeight = (int) Math.round(levelHeight.get(plan.getTargetLevel()) * 1000); } else { deviceTask.goodsSlotHeight = 1; } diff --git a/servo/src/main/java/com/yvan/workbench/controller/LccController.java b/servo/src/main/java/com/yvan/workbench/controller/LccController.java new file mode 100644 index 0000000..9bbbb56 --- /dev/null +++ b/servo/src/main/java/com/yvan/workbench/controller/LccController.java @@ -0,0 +1,23 @@ +package com.yvan.workbench.controller; + +import com.galaxis.rcs.RCS; +import com.yvan.workbench.model.entity.Model; +import org.clever.core.Conv; +import org.clever.web.mvc.annotation.RequestBody; + +import java.util.Map; + +public class LccController { + + /** + * 支持前端 RCSScript.loadFloor 方法加载楼层数据 + */ + Model loadFloor(@RequestBody Map params) { + String projectUUID = Conv.asString(params.get("projectUUID")); + String catalogCode = Conv.asString(params.get("catalogCode")); + Long envId = Conv.asLong(params.get("envId")); + boolean result = RCS.loadFloor(projectUUID, catalogCode, envId); + + return Model.newSuccess(result); + } +} diff --git a/servo/src/main/java/com/yvan/workbench/controller/RcsController.java b/servo/src/main/java/com/yvan/workbench/controller/RcsController.java new file mode 100644 index 0000000..5d5436a --- /dev/null +++ b/servo/src/main/java/com/yvan/workbench/controller/RcsController.java @@ -0,0 +1,244 @@ +package com.yvan.workbench.controller; + +import com.galaxis.rcs.RCS; +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.PlanTaskSequence; +import com.galaxis.rcs.plan.task.CarryTask; +import com.galaxis.rcs.plan.task.MoveTask; +import com.google.common.base.Strings; +import com.yvan.logisticsModel.*; +import com.yvan.workbench.model.entity.Model; +import org.apache.commons.lang3.NotImplementedException; +import org.clever.core.Conv; +import org.clever.core.id.SnowFlake; +import org.clever.web.mvc.annotation.RequestBody; + +import java.util.Map; + +public class RcsController { +// /** +// * 后台机器人移动 +// * @param agvId 机器人ID +// * @param targetWayPointId 目标路径点ID +// * @param option 选项 +// */ +// agvMove(agvId: string, targetWayPointId: string, option: any = {}): Promise> +// +// /** +// * 后台机器人搬运(库存点 -> 库存点) +// * @param agvId 机器人ID +// * @param fromStoreLoc 库存点ID +// * @param targetStoreLoc 目标库存点ID +// * @param option 其他选项 +// */ +// agvCarry(agvId: string, fromStoreLoc: string, targetStoreLoc: string, option: any = {}): Promise> +// +// /** +// * 机器人充电 +// * @param agvId 机器人ID +// * @param chargerId 充电桩ID +// * @param option 其他选项 +// */ +// agvToCharger(agvId: string, chargerId: string, option: any = {}): Promise> + + static final SnowFlake snowFlake = new SnowFlake(); + + public static Model agvMove(@RequestBody Map params) { + String projectUUID = Conv.asString(params.get("projectUUID")); + String catalogCode = Conv.asString(params.get("catalogCode")); + Long envId = Conv.asLong(params.get("envId")); + String agvId = Conv.asString(params.get("agvId")); + String targetWayPointId = Conv.asString(params.get("targetWayPointId")); + Map option = (Map) params.get("option"); + + if (Strings.isNullOrEmpty(agvId)) { + return Model.newFail("agvId Must not be empty"); + } + if (Strings.isNullOrEmpty(targetWayPointId)) { + return Model.newFail("targetWayPointId Must not be empty"); + } + + RCS.loadFloor(projectUUID, catalogCode, envId); + + LogisticsRuntime logisticsRuntime = LogisticsRuntimeService.INSTANCE.findByProjectFloor(projectUUID, catalogCode); + StaticItem toItem = logisticsRuntime.getStaticItemById(targetWayPointId); + if (toItem == null) { + return Model.newFail("target wayPoint not found!"); + } + + ExecutorItem executorItem = logisticsRuntime.executorItemMap.get(agvId); + if (executorItem == null) { + return Model.newFail("executor not found: " + agvId); + } + if (!(executorItem instanceof PtrAgvItem)) { + return Model.newFail("executor is not a PtrAgvItem id=" + agvId); + } + + // 获取机器人当前所在位置, 也可以前端强制指定 + // forceStartWayPointId: '6_2', forceStartDirectior: 'right' + StaticItem fromItem = null; + LCCDirection fromDirection = null; + if (option.get("forceStartWayPointId") != null) { + fromItem = logisticsRuntime.getStaticItemById(Conv.asString(option.get("forceStartWayPointId"))); + } else { + fromItem = logisticsRuntime.getStaticItemByLogicXY(((PtrAgvItem) executorItem).logicX, ((PtrAgvItem) executorItem).logicY); + } + if (option.get("forceStartDirection") != null) { + fromDirection = LCCDirection.fromString(Conv.asString(option.get("forceStartDirection"))); + } else { + fromDirection = ((PtrAgvItem) executorItem).getLCCDirection(); + } + + if (fromItem == null) { + return Model.newFail("PtrAgvItem not found at current location: " + ((PtrAgvItem) executorItem).logicX + "_" + ((PtrAgvItem) executorItem).logicY + ", id=" + agvId); + } + if (fromDirection == null) { + return Model.newFail("PtrAgvItem unkown direction id=" + agvId); + } + + RcsTaskBiz bizTask = new RcsTaskBiz(); + bizTask.setBizTaskId(snowFlake.nextId()); + bizTask.setEnvId(envId); + bizTask.setBizType(BizTaskType.MOVE.toString()); + bizTask.setLpn("N/A"); + bizTask.setPriority(Conv.asInteger(option.get("priority"), 1)); + bizTask.setTaskFrom(fromItem.getId()); + bizTask.setTaskTo(targetWayPointId); + bizTask.setAllocatedExecutorId(agvId); + bizTask.setBizTaskPayload("N/A"); + bizTask.setBizTaskErrorInfo("N/A"); + bizTask.setBizTaskDescription("N/A"); + bizTask.setBizTaskStatus(BizTaskStatus.WAITING_FOR_DISPATCH.toString()); + + PlanTaskSequence planSequence = new PlanTaskSequence(agvId, logisticsRuntime, bizTask, "demo"); + + MoveTask moveTask = new MoveTask( + agvId, bizTask.getTaskTo(), bizTask.getPriority() + ); + + logisticsRuntime.pathPlannerMap.get(executorItem.getT()) + .planMoveTask(planSequence, fromItem.getId(), fromDirection, moveTask); + return Model.newSuccess(planSequence.toPrettyMap()); + } + + public static Model agvInfo(@RequestBody Map params) { + String projectUUID = Conv.asString(params.get("projectUUID")); + String catalogCode = Conv.asString(params.get("catalogCode")); + Long envId = Conv.asLong(params.get("envId")); + String agvId = Conv.asString(params.get("agvId")); + + if (Strings.isNullOrEmpty(agvId)) { + return Model.newFail("agvId Must not be empty"); + } + + RCS.loadFloor(projectUUID, catalogCode, envId); + + LogisticsRuntime logisticsRuntime = LogisticsRuntimeService.INSTANCE.findByProjectFloor(projectUUID, catalogCode); + + ExecutorItem executorItem = logisticsRuntime.executorItemMap.get(agvId); + if (executorItem == null) { + return Model.newFail("executor not found: " + agvId); + } + if (!(executorItem instanceof PtrAgvItem)) { + return Model.newFail("executor is not a PtrAgvItem id=" + agvId); + } + + return Model.newSuccess(executorItem); + } + + public static Model agvCarry(@RequestBody Map params) { + String projectUUID = Conv.asString(params.get("projectUUID")); + String catalogCode = Conv.asString(params.get("catalogCode")); + Long envId = Conv.asLong(params.get("envId")); + String agvId = Conv.asString(params.get("agvId")); + String fromStoreLoc = Conv.asString(params.get("fromStoreLoc")); + String targetStoreLoc = Conv.asString(params.get("targetStoreLoc")); + Map option = (Map) params.get("option"); + + if (Strings.isNullOrEmpty(agvId)) { + return Model.newFail("agvId Must not be empty"); + } + if (Strings.isNullOrEmpty(fromStoreLoc)) { + return Model.newFail("fromStoreLoc Must not be empty"); + } + if (Strings.isNullOrEmpty(targetStoreLoc)) { + return Model.newFail("targetStoreLoc Must not be empty"); + } + + RCS.loadFloor(projectUUID, catalogCode, envId); + + LogisticsRuntime logisticsRuntime = LogisticsRuntimeService.INSTANCE.findByProjectFloor(projectUUID, catalogCode); + StaticItem sourceItem = logisticsRuntime.getStaticItemById(fromStoreLoc); + if (sourceItem == null) { + return Model.newFail("fromStoreLoc storePoint not found!"); + } + StaticItem targetItem = logisticsRuntime.getStaticItemById(targetStoreLoc); + if (targetItem == null) { + return Model.newFail("targetStoreLoc storePoint not found!"); + } + + ExecutorItem executorItem = logisticsRuntime.executorItemMap.get(agvId); + if (executorItem == null) { + return Model.newFail("executor not found: " + agvId); + } + if (!(executorItem instanceof PtrAgvItem)) { + return Model.newFail("executor is not a PtrAgvItem id=" + agvId); + } + + // 获取机器人当前所在位置, 也可以前端强制指定 + // forceStartWayPointId: '6_2', forceStartDirectior: 'right' + StaticItem fromItem = null; + LCCDirection fromDirection = null; + if (option.get("forceStartWayPointId") != null) { + fromItem = logisticsRuntime.getStaticItemById(Conv.asString(option.get("forceStartWayPointId"))); + } else { + fromItem = logisticsRuntime.getStaticItemByLogicXY(((PtrAgvItem) executorItem).logicX, ((PtrAgvItem) executorItem).logicY); + } + if (option.get("forceStartDirection") != null) { + fromDirection = LCCDirection.fromString(Conv.asString(option.get("forceStartDirection"))); + } else { + fromDirection = ((PtrAgvItem) executorItem).getLCCDirection(); + } + + if (fromItem == null) { + return Model.newFail("PtrAgvItem not found at current location: " + ((PtrAgvItem) executorItem).logicX + "_" + ((PtrAgvItem) executorItem).logicY + ", id=" + agvId); + } + if (fromDirection == null) { + return Model.newFail("PtrAgvItem unkown direction id=" + agvId); + } + + RcsTaskBiz bizTask = new RcsTaskBiz(); + bizTask.setBizTaskId(snowFlake.nextId()); + bizTask.setEnvId(1L); + bizTask.setBizType(BizTaskType.MOVE.toString()); + bizTask.setLpn("N/A"); + bizTask.setPriority(Conv.asInteger(option.get("priority"), 1)); + bizTask.setTaskFrom(sourceItem.getId()); + bizTask.setTaskTo(targetItem.getId()); + bizTask.setAllocatedExecutorId(agvId); + bizTask.setBizTaskPayload("N/A"); + bizTask.setBizTaskErrorInfo("N/A"); + bizTask.setBizTaskDescription("N/A"); + bizTask.setBizTaskStatus(BizTaskStatus.WAITING_FOR_DISPATCH.toString()); + + PlanTaskSequence planSequence = new PlanTaskSequence(agvId, logisticsRuntime, bizTask, "demo"); + + CarryTask carryTask = new CarryTask( + agvId, "dummy", 1, + new StoreLocation("rack1", 0, 1, 0), + new StoreLocation("54", 0, 0, 0) + ); + + logisticsRuntime.pathPlannerMap.get(executorItem.getT()) + .planCarryTask(planSequence, fromItem.getId(), fromDirection, carryTask); + return Model.newSuccess(planSequence.toPrettyMap()); + } + + public static Model agvToCharger(@RequestBody Map params) { + throw new NotImplementedException("agvToCharger not implemented yet"); + } +} diff --git a/servo/src/main/java/com/yvan/workbench/model/entity/Model.java b/servo/src/main/java/com/yvan/workbench/model/entity/Model.java index 0238e43..d8ec861 100644 --- a/servo/src/main/java/com/yvan/workbench/model/entity/Model.java +++ b/servo/src/main/java/com/yvan/workbench/model/entity/Model.java @@ -9,6 +9,14 @@ public class Model implements java.io.Serializable { return new Model().setData(data).setSuccess(true).setMsg("操作成功"); } + public static Model newFail(Exception e, T data) { + return new Model().setMsg(e.toString()).setSuccess(false).setData(data); + } + + public static Model newFail(String msg) { + return new Model().setMsg(msg).setSuccess(false); + } + public T getData() { return data; }