feat(ft546): adapt lift conveyor and charger protocols

This commit is contained in:
zgw
2026-08-11 13:23:53 +08:00
parent ff384cc019
commit 14a5a6174e
15 changed files with 907 additions and 36 deletions

View File

@@ -0,0 +1,309 @@
package com.ruoyi.project.ft546.device;
import com.ruoyi.common.domain.PlcConnectInfo;
import com.ruoyi.device.cdz.domain.DeviceCdz;
import com.ruoyi.device.srgv.domain.DeviceSrgv;
import com.ruoyi.device.ssj.domain.DeviceSsj;
import com.ruoyi.device.tsj.domain.DeviceTsj;
import com.ruoyi.project.ft546.config.Ft546DeviceProfile;
import com.ruoyi.project.ft546.device.charger.Ft546ChargerAdapter;
import com.ruoyi.project.ft546.device.conveyor.Ft546ConveyorAdapter;
import com.ruoyi.project.ft546.device.lift.Ft546LiftAdapter;
import com.ruoyi.schedule.plcRead.cdz.PlcCdzRead;
import com.ruoyi.schedule.plcRead.ssj.PlcSsjRead;
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
import com.ruoyi.schedule.plcRead.tsj.PlcTsjRead;
import com.ruoyi.wcs.application.device.DeviceCommandEnvelope;
import com.ruoyi.wcs.application.device.SendReceipt;
import com.ruoyi.wcs.domain.execution.DeviceCommandType;
import com.ruoyi.wcs.domain.execution.DeviceSnapshot;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
class Ft546EquipmentAdaptersTest {
private static final Instant NOW = Instant.parse("2026-08-11T05:00:00Z");
@Test
void liftAdapterReportsFactsWithoutReleasingCoreResources() {
DeviceTsj device = liftDevice();
device.setDqcs(3);
device.setJdbz(2);
device.setHczt(1);
device.setYxxf(1);
device.setSbzt(2);
device.setCzfs(2);
device.setRwh(30001L);
FakeTsjRead runtime = new FakeTsjRead("1", device);
Ft546LiftAdapter adapter = new Ft546LiftAdapter(runtime,
profile("1", "LIFT", "LIFT_TRANSFER", "DB602.0"),
(deviceId, commandId) -> false, contextProvider());
DeviceSnapshot snapshot = adapter.readSnapshot("1");
assertEquals("3", snapshot.getFacts().get("floor"));
assertEquals("OPEN", snapshot.getFacts().get("doorState"));
assertEquals("READY", snapshot.getFacts().get("portState"));
assertEquals("1", snapshot.getFacts().get("load"));
assertEquals("2", snapshot.getFacts().get("status"));
assertEquals("2", snapshot.getFacts().get("mode"));
assertFalse(snapshot.getFacts().containsKey("releaseLiftResource"));
assertFalse(snapshot.hasOrderedDeviceSequence());
assertFalse(snapshot.hasDeviceBootSession());
assertFalse(snapshot.isCommandCompleted());
}
@Test
void liftCommandKeepsTheLegacyByteLayout() {
FakeTsjRead runtime = new FakeTsjRead("1", liftDevice());
Ft546LiftAdapter adapter = new Ft546LiftAdapter(runtime,
profile("1", "LIFT", "LIFT_TRANSFER", "DB602.0"),
(deviceId, commandId) -> false, contextProvider());
SendReceipt receipt = adapter.send(envelope("LIFT-1", "1",
DeviceCommandType.LIFT_TRANSFER,
"taskNo=30001|taskType=1|sourceFloor=3|targetFloor=7"));
assertEquals(SendReceipt.Status.ACCEPTED, receipt.getStatus());
assertEquals("DB602.0", runtime.addresses.get(0));
assertArrayEquals(new byte[] {0, 0, 117, 49, 0, 1, 0, 3,
0, 7, 0, 1, 0, (byte) 178}, runtime.payloads.get(0));
}
@Test
void conveyorAdapterMapsGoodsModeAndReadyFactsAndLegacyCommand() {
DeviceSsj device = conveyorDevice();
device.setTaskNo(30002);
device.setDw(1);
device.setKx(1);
device.setModel(1);
device.setTrayCode("TRAY-001");
device.setErrorCode(0);
FakeSsjRead runtime = new FakeSsjRead("GS1002", device);
Ft546ConveyorAdapter adapter = new Ft546ConveyorAdapter(runtime,
profile("GS1002", "CONVEYOR", "CONVEYOR_TRANSFER",
"DB41.0"),
(deviceId, commandId) -> false, contextProvider());
DeviceSnapshot snapshot = adapter.readSnapshot("GS1002");
SendReceipt receipt = adapter.send(envelope("CONV-1", "GS1002",
DeviceCommandType.CONVEYOR_TRANSFER,
"taskNo=30001|destination=2|completed=1"));
assertEquals("PRESENT", snapshot.getFacts().get("goodsState"));
assertEquals("1", snapshot.getFacts().get("mode"));
assertEquals("READY", snapshot.getFacts().get("readyState"));
assertEquals("TRAY-001", snapshot.getFacts().get("trayCode"));
assertEquals("0", snapshot.getFacts().get("faultCode"));
assertEquals(SendReceipt.Status.ACCEPTED, receipt.getStatus());
assertEquals("DB41.0", runtime.addresses.get(0));
assertArrayEquals(new byte[] {0, 0, 117, 49, 0, 2, 0, 1,
0, 0, 0, (byte) 169}, runtime.payloads.get(0));
}
@Test
void chargerAdapterMapsOccupancyChargingAndFaultFactsAndLegacyCommand() {
DeviceCdz device = chargerDevice();
device.setRwh(30003);
device.setStatus(1);
device.setModel(1);
device.setSpare1("1");
FakeCdzRead runtime = new FakeCdzRead("1", device);
Ft546ChargerAdapter adapter = new Ft546ChargerAdapter(runtime,
profile("1", "CHARGER", "CHARGE", "DB701.0"),
(deviceId, commandId) -> false, contextProvider());
DeviceSnapshot snapshot = adapter.readSnapshot("1");
SendReceipt receipt = adapter.send(envelope("CHARGE-1", "1",
DeviceCommandType.CHARGE, "enabled=1"));
assertEquals("OCCUPIED", snapshot.getFacts().get("occupancy"));
assertEquals("ON", snapshot.getFacts().get("charging"));
assertEquals("UNAVAILABLE", snapshot.getFacts().get("fault"));
assertEquals("070501", snapshot.getFacts().get("site"));
assertEquals(SendReceipt.Status.ACCEPTED, receipt.getStatus());
assertEquals("DB701.0", runtime.addresses.get(0));
assertArrayEquals(new byte[] {0, 1}, runtime.payloads.get(0));
}
@Test
void attemptedEquipmentWriteFailureIsUnknownAndBlocksRetry() {
FakeCdzRead runtime = new FakeCdzRead("1", chargerDevice());
runtime.writeResult = 0;
Ft546ChargerAdapter adapter = new Ft546ChargerAdapter(runtime,
profile("1", "CHARGER", "CHARGE", "DB701.0"),
(deviceId, commandId) -> false, contextProvider());
SendReceipt first = adapter.send(envelope("CHARGE-1", "1",
DeviceCommandType.CHARGE, "enabled=1"));
SendReceipt retry = adapter.send(envelope("CHARGE-2", "1",
DeviceCommandType.CHARGE, "enabled=1"));
assertEquals(SendReceipt.Status.ACCEPTED_UNKNOWN,
first.getStatus());
assertEquals(SendReceipt.Status.REJECTED, retry.getStatus());
assertEquals(1, runtime.payloads.size());
}
@Test
void factReadersPublishAConnectionDropAfterTheyWereConnected() {
FakeTsjRead lift = new FakeTsjRead("1", liftDevice());
FakeSsjRead conveyor = new FakeSsjRead("GS1002", conveyorDevice());
FakeCdzRead charger = new FakeCdzRead("1", chargerDevice());
DeviceSrgv shuttleDevice = new DeviceSrgv();
shuttleDevice.setDeviceId(1);
PlcSrgvRead shuttle = new PlcSrgvRead(shuttleDevice,
new PlcConnectInfo("127.0.0.1", 502));
shuttle.setIsConnect(true);
lift.setIsConnect(false);
conveyor.setIsConnect(false);
charger.setIsConnect(false);
shuttle.setIsConnect(false);
assertFalse(lift.IsConnect());
assertFalse(conveyor.IsConnect());
assertFalse(charger.IsConnect());
assertFalse(shuttle.IsConnect());
}
private static Ft546DeviceProfile profile(String deviceId,
String deviceType,
String capability,
String commandAddress) {
Ft546DeviceProfile profile = new Ft546DeviceProfile();
profile.setDeviceId(deviceId);
profile.setDeviceType(deviceType);
profile.setAdapterType("FT546_LEGACY_" + deviceType);
profile.setProfileVersion(1L);
profile.getCapabilities().add(capability);
profile.getRegisters().put("command", commandAddress);
return profile;
}
private static Ft546SnapshotContextProvider contextProvider() {
return deviceId -> new Ft546SnapshotContext("SNAP-" + deviceId,
"KEY-" + deviceId, 9L, 17L, NOW);
}
private static DeviceCommandEnvelope envelope(String commandId,
String deviceId,
DeviceCommandType type,
String payload) {
return new DeviceCommandEnvelope(commandId, "KEY-" + commandId,
deviceId, type, payload, 1, 9L, 17L);
}
private static DeviceTsj liftDevice() {
DeviceTsj device = new DeviceTsj();
device.setId(1L);
return device;
}
private static DeviceSsj conveyorDevice() {
DeviceSsj device = new DeviceSsj();
device.setDeviceCode("GS1002");
return device;
}
private static DeviceCdz chargerDevice() {
DeviceCdz device = new DeviceCdz();
device.setId(1);
device.setSiteId("070501");
return device;
}
private abstract static class RecordingRuntime {
private final List<String> addresses = new ArrayList<String>();
private final List<byte[]> payloads = new ArrayList<byte[]>();
private int writeResult = 1;
final int record(String address, byte[] payload) {
addresses.add(address);
payloads.add(payload.clone());
return writeResult;
}
}
private static final class FakeTsjRead extends PlcTsjRead {
private final RecordingRuntime recording = new RecordingRuntime() { };
private final List<String> addresses = recording.addresses;
private final List<byte[]> payloads = recording.payloads;
private FakeTsjRead(String id, DeviceTsj device) {
super(map(id, device), new PlcConnectInfo("127.0.0.1", 102));
setIsConnect(true);
}
@Override
public int write(String address, byte[] payload) {
return recording.record(address, payload);
}
}
private static final class FakeSsjRead extends PlcSsjRead {
private final RecordingRuntime recording = new RecordingRuntime() { };
private final List<String> addresses = recording.addresses;
private final List<byte[]> payloads = recording.payloads;
private FakeSsjRead(String id, DeviceSsj device) {
super(map(id, device), new PlcConnectInfo("127.0.0.1", 102));
setIsConnect(true);
}
@Override
public int write(String address, byte[] payload) {
return recording.record(address, payload);
}
}
private static final class FakeCdzRead extends PlcCdzRead {
private final List<String> addresses = new ArrayList<String>();
private final List<byte[]> payloads = new ArrayList<byte[]>();
private int writeResult = 1;
private FakeCdzRead(String id, DeviceCdz device) {
super(map(id, device), new PlcConnectInfo("127.0.0.1", 102));
setIsConnect(true);
}
@Override
public int write(String address, byte[] payload) {
addresses.add(address);
payloads.add(payload.clone());
return writeResult;
}
}
private static ConcurrentHashMap<String, DeviceTsj> map(
String id, DeviceTsj device) {
ConcurrentHashMap<String, DeviceTsj> map =
new ConcurrentHashMap<String, DeviceTsj>();
map.put(id, device);
return map;
}
private static ConcurrentHashMap<String, DeviceSsj> map(
String id, DeviceSsj device) {
ConcurrentHashMap<String, DeviceSsj> map =
new ConcurrentHashMap<String, DeviceSsj>();
map.put(id, device);
return map;
}
private static ConcurrentHashMap<String, DeviceCdz> map(
String id, DeviceCdz device) {
ConcurrentHashMap<String, DeviceCdz> map =
new ConcurrentHashMap<String, DeviceCdz>();
map.put(id, device);
return map;
}
}

View File

@@ -8,6 +8,7 @@ import com.ruoyi.project.ft546.config.Ft546CapabilityEvidence;
import com.ruoyi.project.ft546.config.Ft546CapabilityGate;
import com.ruoyi.project.ft546.config.Ft546DeviceProfile;
import com.ruoyi.project.ft546.device.Ft546DeviceRuntimeRegistry;
import com.ruoyi.project.ft546.device.Ft546MotionCommandGuard;
import com.ruoyi.schedule.plcRead.srgv.PlcSrgvRead;
import com.ruoyi.wcs.application.device.DeviceCommandEnvelope;
import com.ruoyi.wcs.application.device.SendReceipt;
@@ -241,7 +242,7 @@ class Ft546LegacySrgvAdapterTest {
private static Ft546LegacySrgvAdapter adapter(
FakePlcSrgvRead runtime, Ft546DeviceProfile profile,
Ft546CapabilityEvidence evidence,
Ft546LegacySrgvAdapter.MotionCommandGuard motionGuard) {
Ft546MotionCommandGuard motionGuard) {
Ft546DeviceRuntimeRegistry registry =
new Ft546DeviceRuntimeRegistry();
registry.register("1", runtime);