feat: add executable wcs dispatch simulation
This commit is contained in:
48
javaTest/README.md
Normal file
48
javaTest/README.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# FT546 WCS 中心调度与交通管制 Demo
|
||||
|
||||
这是一个独立的 Java 8 Maven Demo,用于模拟 FT546 WCS 的 LEGACY 中心调度和交通管制行为。
|
||||
|
||||
## 安全边界
|
||||
|
||||
- 仅使用内存车辆、任务、队列和地图。
|
||||
- 不启动 Spring,不连接 SQL Server、PLC 或 WMS。
|
||||
- 不发送真实设备命令。
|
||||
- 不修改生产 `ruoyi-*` 模块。
|
||||
- 不启用 SHADOW 或 ACTIVE 控制模式。
|
||||
|
||||
## 已覆盖行为
|
||||
|
||||
- 每个调度周期只执行一次业务任务准入。
|
||||
- 在线、自动、空闲、允许下发且电量大于 30 的车辆才可参与分配。
|
||||
- 同层、楼层差、Y 距离、X 距离、车辆编号的稳定选车规则。
|
||||
- 其他车辆剩余路径和离线车辆当前位置锁定。
|
||||
- 同通道阻挡、高优先级避让任务和避让去重。
|
||||
- 模拟协议失败、下一周期重试。
|
||||
- 队列 `0/1 → 1/3 → 1/4` 状态流转、完成归档和占用释放。
|
||||
- 无路可达时返回 HOLD,而不是抛出空指针。
|
||||
|
||||
## 运行测试
|
||||
|
||||
```bash
|
||||
mvn test
|
||||
```
|
||||
|
||||
测试必须由 Maven Surefire 实际发现并执行;验收标准为测试数大于 0、Failures 为 0、Errors 为 0。
|
||||
|
||||
## 运行控制台模拟
|
||||
|
||||
```bash
|
||||
mvn exec:java
|
||||
```
|
||||
|
||||
输出事件包括:
|
||||
|
||||
- `CYCLE`:调度周期开始
|
||||
- `ASSIGN`:任务选车和裁决理由
|
||||
- `PATH`:规划成功的路径
|
||||
- `AVOID`:创建避让队列
|
||||
- `HOLD`:交通或协议原因导致等待
|
||||
- `SEND`:模拟协议下发成功
|
||||
- `MOVE`:模拟车辆推进
|
||||
- `COMPLETE`:队列完成并归档
|
||||
- `SUMMARY`:最终周期、活动队列、历史和协议尝试次数
|
||||
109
javaTest/src/main/java/com/ft546/wcs/demo/DemoApplication.java
Normal file
109
javaTest/src/main/java/com/ft546/wcs/demo/DemoApplication.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package com.ft546.wcs.demo;
|
||||
|
||||
import com.ft546.wcs.demo.center.CycleResult;
|
||||
import com.ft546.wcs.demo.center.LegacyDispatchCenter;
|
||||
import com.ft546.wcs.demo.center.QueueAdmissionService;
|
||||
import com.ft546.wcs.demo.center.VehicleAssignmentService;
|
||||
import com.ft546.wcs.demo.model.DispatchTask;
|
||||
import com.ft546.wcs.demo.model.Site;
|
||||
import com.ft546.wcs.demo.model.VehicleSnapshot;
|
||||
import com.ft546.wcs.demo.repository.InMemoryWcsState;
|
||||
import com.ft546.wcs.demo.simulation.SimulatedProtocolAdapter;
|
||||
import com.ft546.wcs.demo.traffic.GridMap;
|
||||
import com.ft546.wcs.demo.traffic.GridPathPlanner;
|
||||
import com.ft546.wcs.demo.traffic.TrafficController;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
public final class DemoApplication {
|
||||
|
||||
private DemoApplication() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
runScenario(System.out);
|
||||
}
|
||||
|
||||
public static void runScenario(PrintStream out) {
|
||||
if (out == null) {
|
||||
throw new IllegalArgumentException("output must not be null");
|
||||
}
|
||||
|
||||
GridMap map = createMap();
|
||||
InMemoryWcsState state = new InMemoryWcsState();
|
||||
state.addVehicle(vehicle("3", "V3"));
|
||||
state.addVehicle(vehicle("2", "B"));
|
||||
state.addVehicle(vehicle("1", "A"));
|
||||
state.addTask(new DispatchTask(
|
||||
"TASK-OUT-1",
|
||||
DispatchTask.TaskType.OUTBOUND,
|
||||
"A",
|
||||
"TARGET",
|
||||
1,
|
||||
2
|
||||
));
|
||||
|
||||
QueueAdmissionService admission = new QueueAdmissionService(
|
||||
new VehicleAssignmentService(),
|
||||
map
|
||||
);
|
||||
SimulatedProtocolAdapter protocol = new SimulatedProtocolAdapter();
|
||||
protocol.failNextSend("simulated first PLC rejection");
|
||||
LegacyDispatchCenter center = new LegacyDispatchCenter(
|
||||
state,
|
||||
admission,
|
||||
new TrafficController(map, new GridPathPlanner()),
|
||||
protocol
|
||||
);
|
||||
|
||||
out.println("FT546 WCS LEGACY DISPATCH AND TRAFFIC DEMO");
|
||||
out.println("MODE LEGACY / IO DISABLED / IN-MEMORY ONLY");
|
||||
|
||||
for (int cycle = 0; cycle < 6; cycle++) {
|
||||
printCycle(out, center.runCycle());
|
||||
}
|
||||
|
||||
state.addTask(new DispatchTask(
|
||||
"TASK-NO-PATH",
|
||||
DispatchTask.TaskType.RELOCATION,
|
||||
"TARGET",
|
||||
"ISOLATED",
|
||||
1,
|
||||
1
|
||||
));
|
||||
printCycle(out, center.runCycle());
|
||||
|
||||
out.println(
|
||||
"SUMMARY cycles=" + state.getCycleNumber()
|
||||
+ " activeQueues=" + state.getActiveQueues().size()
|
||||
+ " history=" + state.getHistory().size()
|
||||
+ " protocolAttempts=" + protocol.getAttemptedQueueIds().size()
|
||||
);
|
||||
}
|
||||
|
||||
private static void printCycle(PrintStream out, CycleResult result) {
|
||||
for (String event : result.getEvents()) {
|
||||
out.println(event);
|
||||
}
|
||||
}
|
||||
|
||||
private static GridMap createMap() {
|
||||
GridMap map = new GridMap();
|
||||
map.addSite(new Site("A", 1, 0, 0, "START"));
|
||||
map.addSite(new Site("B", 1, 1, 0, "AISLE-1"));
|
||||
map.addSite(new Site("TARGET", 1, 2, 0, "AISLE-1"));
|
||||
map.addSite(new Site("CACHE", 1, 1, 1, "CACHE"));
|
||||
map.addSite(new Site("V3", 1, 0, 1, "V3-CHANNEL"));
|
||||
map.addSite(new Site("ISOLATED", 1, 9, 9, "ISLAND"));
|
||||
map.connect("A", "B");
|
||||
map.connect("B", "TARGET");
|
||||
map.connect("B", "CACHE");
|
||||
map.connect("A", "V3");
|
||||
map.setCacheSite("AISLE-1", "CACHE");
|
||||
return map;
|
||||
}
|
||||
|
||||
private static VehicleSnapshot vehicle(String id, String site) {
|
||||
return new VehicleSnapshot(id, 1, site, 5, 1, 80, true, true, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.ft546.wcs.demo;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class DemoScenarioTest {
|
||||
|
||||
@Test
|
||||
void executableScenarioShowsAssignmentAvoidanceRetryCompletionAndNoPathHold()
|
||||
throws Exception {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
|
||||
DemoApplication.runScenario(new PrintStream(output, true, "UTF-8"));
|
||||
|
||||
String text = new String(output.toByteArray(), StandardCharsets.UTF_8);
|
||||
assertTrue(text.contains("CYCLE"));
|
||||
assertTrue(text.contains("ASSIGN"));
|
||||
assertTrue(text.contains("PATH"));
|
||||
assertTrue(text.contains("AVOID"));
|
||||
assertTrue(text.contains("HOLD HOLD_PROTOCOL_FAILURE"));
|
||||
assertTrue(text.contains("HOLD HOLD_CHANNEL_BLOCKED"));
|
||||
assertTrue(text.contains("HOLD HOLD_NO_PATH"));
|
||||
assertTrue(text.contains("SEND"));
|
||||
assertTrue(text.contains("COMPLETE"));
|
||||
assertTrue(text.contains("SUMMARY cycles=7 activeQueues=1 history=2"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user