279 lines
11 KiB
Java
279 lines
11 KiB
Java
package com.ruoyi.ft546.protocol;
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
|
|
import static com.ruoyi.ft546.protocol.Ft546RegisterWrite.DataType.INT16;
|
|
import static com.ruoyi.ft546.protocol.Ft546RegisterWrite.DataType.INT32;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
class Ft546SrgvCommandEncoderTest {
|
|
private final Ft546SrgvCommandEncoder encoder =
|
|
new Ft546SrgvCommandEncoder();
|
|
|
|
@Test
|
|
void ordinaryIntentEncodesTheExactLegacySequence() {
|
|
Ft546EncodedCommand encoded = encoder.encode(
|
|
intent((short) 1, points(70501, 70502), null, null),
|
|
fullProfile(), capabilities("POINT_TO_POINT_COMMAND"));
|
|
|
|
assertWrites(encoded,
|
|
write(1, "4004", INT32, false,
|
|
70501, 70502, 0, 0, 0, 0, 0, 0),
|
|
write(2, "4000", INT32, false, 30001),
|
|
write(3, "4002", INT16, false, 1),
|
|
write(4, "4021", INT16, true, 1));
|
|
assertEquals("1", encoded.getDeviceId());
|
|
assertEquals("PARENT-1", encoded.getParentTaskId());
|
|
assertEquals("LOADED_TO_PLACE", encoded.getPhase());
|
|
assertEquals("KEY-1", encoded.getCommandKey());
|
|
assertEquals(3L, encoded.getRouteVersion());
|
|
assertEquals(30001, encoded.getTaskNo());
|
|
assertEquals((short) 1, encoded.getTaskType());
|
|
assertEquals(points(70501, 70502), encoded.getPathPoints());
|
|
assertEquals(1, encoded.getTriggerValue());
|
|
}
|
|
|
|
@Test
|
|
void crossFloorIntentEncodesTheExactLegacyPostTriggerSequence() {
|
|
Ft546EncodedCommand encoded = encoder.encode(
|
|
intent((short) 8, points(70501, 70502),
|
|
Short.valueOf((short) 8), null),
|
|
fullProfile(), capabilities("POINT_TO_POINT_COMMAND"));
|
|
|
|
assertWrites(encoded,
|
|
write(1, "4004", INT32, false,
|
|
70501, 70502, 0, 0, 0, 0, 0, 0),
|
|
write(2, "4000", INT32, false, 30001),
|
|
write(3, "4002", INT16, false, 8),
|
|
write(4, "4021", INT16, true, 1),
|
|
write(5, "4020", INT16, false, 1),
|
|
write(6, "4022", INT16, false, 8));
|
|
}
|
|
|
|
@Test
|
|
void twoStageOneEncodesTheExactLegacySequence() {
|
|
Ft546EncodedCommand encoded = encoder.encode(
|
|
intent((short) 1, points(70501, 70503), null, 1),
|
|
fullProfile(), capabilities("TWO_STAGE_ATOMIC_TASK"));
|
|
|
|
assertWrites(encoded,
|
|
write(1, "4404", INT32, false,
|
|
70501, 70503, 0, 0, 0, 0, 0, 0),
|
|
write(2, "4422", INT16, true, 1),
|
|
write(3, "4402", INT16, false, 1),
|
|
write(4, "4400", INT32, false, 30001));
|
|
assertEquals(1, encoded.getTriggerValue());
|
|
}
|
|
|
|
@Test
|
|
void twoStageTwoEncodesTheExactLegacySequence() {
|
|
Ft546EncodedCommand encoded = encoder.encode(
|
|
intent((short) 9, points(70503, 70504), null, 2),
|
|
fullProfile(), capabilities("TWO_STAGE_ATOMIC_TASK"));
|
|
|
|
assertWrites(encoded,
|
|
write(1, "4404", INT32, false,
|
|
70503, 70504, 0, 0, 0, 0, 0, 0),
|
|
write(2, "4422", INT16, true, 2),
|
|
write(3, "4423", INT16, false, 99),
|
|
write(4, "4402", INT16, false, 9),
|
|
write(5, "4400", INT32, false, 30001));
|
|
assertEquals(2, encoded.getTriggerValue());
|
|
}
|
|
|
|
@Test
|
|
void missingRegisterFailsClosedBeforeEncoding() {
|
|
Ft546DeviceProfile profile = fullProfile();
|
|
Map<String, String> registers = new LinkedHashMap<String, String>(
|
|
profile.getRegisters());
|
|
registers.remove("trigger");
|
|
profile.setRegisters(registers);
|
|
|
|
IllegalArgumentException failure = assertThrows(
|
|
IllegalArgumentException.class,
|
|
() -> encoder.encode(intent((short) 1,
|
|
points(70501, 70502), null, null),
|
|
profile, capabilities("POINT_TO_POINT_COMMAND")));
|
|
|
|
assertEquals("profile register is missing: trigger",
|
|
failure.getMessage());
|
|
}
|
|
|
|
@Test
|
|
void routeLengthTwoAndEightAreAcceptedAndPaddedExactly() {
|
|
Ft546EncodedCommand two = encodeRoute(points(1, 2));
|
|
Ft546EncodedCommand eight = encodeRoute(
|
|
points(1, 2, 3, 4, 5, 6, 7, 8));
|
|
|
|
assertEquals(points(1, 2, 0, 0, 0, 0, 0, 0),
|
|
two.getWrites().get(0).getValues());
|
|
assertEquals(points(1, 2, 3, 4, 5, 6, 7, 8),
|
|
eight.getWrites().get(0).getValues());
|
|
}
|
|
|
|
@Test
|
|
void routeLengthOneAndNineAreRejected() {
|
|
assertRouteRejected(points(1));
|
|
assertRouteRejected(points(1, 2, 3, 4, 5, 6, 7, 8, 9));
|
|
}
|
|
|
|
@Test
|
|
void routePointBeyondSixDigitsIsRejected() {
|
|
IllegalArgumentException failure = assertThrows(
|
|
IllegalArgumentException.class,
|
|
() -> encodeRoute(points(1, 1000000)));
|
|
|
|
assertEquals("route node exceeds six digits", failure.getMessage());
|
|
}
|
|
|
|
@Test
|
|
void capabilityGatesMainAndTwoStageIndependently() {
|
|
IllegalStateException main = assertThrows(IllegalStateException.class,
|
|
() -> encoder.encode(intent((short) 1,
|
|
points(1, 2), null, null),
|
|
fullProfile(), Collections.<String>emptySet()));
|
|
IllegalStateException stage = assertThrows(IllegalStateException.class,
|
|
() -> encoder.encode(intent((short) 1,
|
|
points(1, 2), null, 1),
|
|
fullProfile(), capabilities(
|
|
"POINT_TO_POINT_COMMAND")));
|
|
|
|
assertEquals("POINT_TO_POINT_COMMAND is not enabled",
|
|
main.getMessage());
|
|
assertEquals("TWO_STAGE_ATOMIC_TASK is not enabled",
|
|
stage.getMessage());
|
|
}
|
|
|
|
@Test
|
|
void profileIntentAndEncodedCollectionsAreImmutableSnapshots() {
|
|
Ft546DeviceProfile profile = fullProfile();
|
|
List<Integer> mutablePoints = new ArrayList<Integer>(
|
|
points(70501, 70502));
|
|
Ft546SrgvCommandIntent intent = intent((short) 1,
|
|
mutablePoints, null, null);
|
|
mutablePoints.set(0, 99999);
|
|
Ft546EncodedCommand encoded = encoder.encode(intent, profile,
|
|
capabilities("POINT_TO_POINT_COMMAND"));
|
|
|
|
assertThrows(UnsupportedOperationException.class,
|
|
() -> profile.getRegisters().put("path", "9999"));
|
|
assertThrows(UnsupportedOperationException.class,
|
|
() -> profile.getCapabilities().add("UNSAFE"));
|
|
assertThrows(UnsupportedOperationException.class,
|
|
() -> intent.getPathPoints().add(3));
|
|
assertThrows(UnsupportedOperationException.class,
|
|
() -> encoded.getWrites().clear());
|
|
assertThrows(UnsupportedOperationException.class,
|
|
() -> encoded.getWrites().get(0).getValues().set(0, 99999));
|
|
assertEquals(Integer.valueOf(70501),
|
|
encoded.getPathPoints().get(0));
|
|
}
|
|
|
|
private Ft546EncodedCommand encodeRoute(List<Integer> route) {
|
|
return encoder.encode(intent((short) 1, route, null, null),
|
|
fullProfile(), capabilities("POINT_TO_POINT_COMMAND"));
|
|
}
|
|
|
|
private void assertRouteRejected(List<Integer> route) {
|
|
IllegalArgumentException failure = assertThrows(
|
|
IllegalArgumentException.class, () -> encodeRoute(route));
|
|
assertEquals(
|
|
"legacy route must contain between 2 and 8 command points",
|
|
failure.getMessage());
|
|
}
|
|
|
|
private static Ft546SrgvCommandIntent intent(short taskType,
|
|
List<Integer> route,
|
|
Short crossFloorTarget,
|
|
Integer stage) {
|
|
return new Ft546SrgvCommandIntent("1", "PARENT-1",
|
|
"LOADED_TO_PLACE", "KEY-1", 3L, 30001, taskType,
|
|
route, crossFloorTarget, stage);
|
|
}
|
|
|
|
private static Ft546DeviceProfile fullProfile() {
|
|
Ft546DeviceProfile profile = new Ft546DeviceProfile();
|
|
profile.setDeviceId("1");
|
|
profile.setDeviceType("SHUTTLE");
|
|
profile.setAdapterType("FT546_LEGACY_SRGV");
|
|
profile.setProfileVersion(1L);
|
|
profile.setCapabilities(capabilities("POINT_TO_POINT_COMMAND",
|
|
"TWO_STAGE_ATOMIC_TASK"));
|
|
Map<String, String> registers =
|
|
new LinkedHashMap<String, String>();
|
|
registers.put("taskNo", "4000");
|
|
registers.put("taskType", "4002");
|
|
registers.put("path", "4004");
|
|
registers.put("trigger", "4021");
|
|
registers.put("crossFloorFlag", "4020");
|
|
registers.put("crossFloorTarget", "4022");
|
|
registers.put("secondTaskNo", "4400");
|
|
registers.put("secondTaskType", "4402");
|
|
registers.put("secondPath", "4404");
|
|
registers.put("secondStageFlag", "4422");
|
|
registers.put("secondComplete", "4423");
|
|
profile.setRegisters(registers);
|
|
return profile;
|
|
}
|
|
|
|
private static Set<String> capabilities(String... values) {
|
|
return new LinkedHashSet<String>(Arrays.asList(values));
|
|
}
|
|
|
|
private static List<Integer> points(Integer... values) {
|
|
return Arrays.asList(values);
|
|
}
|
|
|
|
private static ExpectedWrite write(int sequence, String address,
|
|
Ft546RegisterWrite.DataType dataType,
|
|
boolean trigger,
|
|
Integer... values) {
|
|
return new ExpectedWrite(sequence, address, dataType, trigger,
|
|
Arrays.asList(values));
|
|
}
|
|
|
|
private static void assertWrites(Ft546EncodedCommand command,
|
|
ExpectedWrite... expected) {
|
|
assertEquals(expected.length, command.getWrites().size());
|
|
for (int index = 0; index < expected.length; index++) {
|
|
ExpectedWrite wanted = expected[index];
|
|
Ft546RegisterWrite actual = command.getWrites().get(index);
|
|
assertEquals(wanted.sequence, actual.getWriteSequence());
|
|
assertEquals(wanted.address, actual.getAddress());
|
|
assertEquals(wanted.dataType, actual.getDataType());
|
|
assertEquals(wanted.values, actual.getValues());
|
|
assertEquals(wanted.trigger, actual.isTrigger());
|
|
}
|
|
}
|
|
|
|
private static final class ExpectedWrite {
|
|
private final int sequence;
|
|
private final String address;
|
|
private final Ft546RegisterWrite.DataType dataType;
|
|
private final boolean trigger;
|
|
private final List<Integer> values;
|
|
|
|
private ExpectedWrite(int sequence, String address,
|
|
Ft546RegisterWrite.DataType dataType,
|
|
boolean trigger, List<Integer> values) {
|
|
this.sequence = sequence;
|
|
this.address = address;
|
|
this.dataType = dataType;
|
|
this.trigger = trigger;
|
|
this.values = values;
|
|
}
|
|
}
|
|
}
|