diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..b90b9d73797f9f1e8360876dc6a9e62f3f541ae3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +build/ +.idea/ +.gradle/ +.git/ +ros3rag.placeA/build/ +ros3rag.placeB/build/ +ros3rag.common/build/ +Dockerfile diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 15774ae91cb09f92b308577d34b1f9058bb708f5..f3c7e98935ba157aecac5bf3d4343db4b0a2f411 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,4 +8,4 @@ build: image: openjdk:11 stage: build script: - - ./gradlew assemble + - ./gradlew build diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..b87f50be67c42d8f326c6f5d13117f55f73af32e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +FROM openjdk:11 + +ENV DEBIAN_FRONTEND noninteractive +ENV HOME /home/user +RUN useradd --create-home --home-dir $HOME user \ + && chmod -R u+rwx $HOME \ + && chown -R user:user $HOME + +USER user + +ENV GRADLE_USER_HOME /home/user/.gradle +COPY --chown=user:user gradle /temp/gradle +COPY --chown=user:user gradlew /temp/ + +WORKDIR /temp +RUN ./gradlew --no-daemon --version + +COPY --chown=user:user . /ros3rag/ + +WORKDIR /ros3rag + +RUN ./gradlew --no-daemon installDist + +# target is either: ros3rag.placeA oder ros3rag.placeB +ENV TARGET unspecified +# ENV CONFIG_FILE ./ros3rag.common/config.yaml +ENTRYPOINT ["/bin/bash", "-c", "./${TARGET}/build/install/${TARGET}/bin/${TARGET}"] diff --git a/buildSrc/src/main/groovy/ros3rag.java-application-conventions.gradle b/buildSrc/src/main/groovy/ros3rag.java-application-conventions.gradle index ca778a499ace2bc60db88434e4f64745d05b4717..2242c13850ef41b8c202298aeab8ee06fded6cde 100644 --- a/buildSrc/src/main/groovy/ros3rag.java-application-conventions.gradle +++ b/buildSrc/src/main/groovy/ros3rag.java-application-conventions.gradle @@ -2,7 +2,3 @@ plugins { id 'ros3rag.java-common-conventions' id 'application' } - -run { - standardInput = System.in -} diff --git a/buildSrc/src/main/groovy/ros3rag.java-jastadd-conventions.gradle b/buildSrc/src/main/groovy/ros3rag.java-jastadd-conventions.gradle index a2eada848ea2ee77f105a56295102f64f5b65430..9549924c8d8e4928d9989ae5d61929d3dd2285ab 100644 --- a/buildSrc/src/main/groovy/ros3rag.java-jastadd-conventions.gradle +++ b/buildSrc/src/main/groovy/ros3rag.java-jastadd-conventions.gradle @@ -2,6 +2,7 @@ plugins { id 'ros3rag.java-common-conventions' id 'java-library' id 'idea' + id 'org.jastadd' } dependencies { diff --git a/buildSrc/src/main/groovy/ros3rag.java-ragconnect-conventions.gradle b/buildSrc/src/main/groovy/ros3rag.java-ragconnect-conventions.gradle index 2281b3436d3e0b1ba6c88335e6f366642143ab07..ad1b6a77d40560af9619a15e0791845b4a506306 100644 --- a/buildSrc/src/main/groovy/ros3rag.java-ragconnect-conventions.gradle +++ b/buildSrc/src/main/groovy/ros3rag.java-ragconnect-conventions.gradle @@ -3,18 +3,19 @@ plugins { } configurations { - baseRuntimeClasspath - ragconnectClasspath - grammar2umlClasspath + ragconnect + grammar2uml + relast } dependencies { api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11' - grammar2umlClasspath group: 'de.tudresden.inf.st', name: 'grammar2uml', version: '0.1.1' + grammar2uml group: 'de.tudresden.inf.st', name: 'grammar2uml', version: '0.2.3' - ragconnectClasspath group: 'de.tudresden.inf.st', name: 'ragconnect', version: '0.3.0' -// ragconnectClasspath fileTree(include: ['ragconnect-0.2.5.jar'], dir: '../libs') + relast group: 'org.jastadd', name: 'relast', version: "0.4.0-143" + ragconnect group: 'de.tudresden.inf.st', name: 'ragconnect', version: '1.0.0-alpha-198' // implementation group: 'de.tudresden.inf.st', name: 'dumpAstWithPlantuml', version: '0.3.5' // implementation fileTree(include: ['dumpAstWithPlantuml-0.3.5.jar'], dir: '../libs') } + diff --git a/cloc/run-cloc.sh b/cloc/run-cloc.sh index 15422cde3c5d2fa2a1f84cf4487f72ce89380531..56f3d936b2104e98beb324036d90630975fcae53 100755 --- a/cloc/run-cloc.sh +++ b/cloc/run-cloc.sh @@ -6,6 +6,9 @@ then : else +# remove previous results +rm *-result.txt + # --force-lang=Java,jrag --force-lang=Java,jadd DEF_FILE=my_definitions.txt echo "Export language definitions" @@ -17,11 +20,14 @@ done echo "Running cloc with new definitions" # --ignored=bad-files.txt CLOC_CMD="cloc --exclude-lang=JSON --read-lang-def=my_definitions.txt --exclude-list-file=.clocignore --quiet" -$CLOC_CMD --report-file=place-result.txt ../ros3rag.place?/src/main/ja* 2>>cloc-errors.log -$CLOC_CMD --report-file=common-result.txt ../ros3rag.common/src/main 2>>cloc-errors.log - -$CLOC_CMD --report-file=place-gen-result.txt ../ros3rag.place?/src/gen 2>>cloc-errors.log -$CLOC_CMD --report-file=common-gen-result.txt ../ros3rag.common/build/generated/source/proto/main 2>>cloc-errors.log +$CLOC_CMD --report-file=common-result.txt ../ros3rag.common/src/main/resources/jastadd ../ros3rag.common/src/main/java 2>>cloc-errors.log +$CLOC_CMD --report-file=placeA-result.txt ../ros3rag.placeA/src/main/ja* 2>>cloc-errors.log +$CLOC_CMD --report-file=placeB-result.txt ../ros3rag.placeB/src/main/ja* 2>>cloc-errors.log + +# $CLOC_CMD --report-file=base-gen-result.txt ../ros3rag.base/src/gen 2>>cloc-errors.log +$CLOC_CMD --report-file=placeA-gen-result.txt ../ros3rag.placeA/src/gen 2>>cloc-errors.log +$CLOC_CMD --report-file=placeB-gen-result.txt ../ros3rag.placeB/src/gen 2>>cloc-errors.log +$CLOC_CMD --report-file=common-gen-result.txt ../ros3rag.common/build/generated/source/proto/main/java/de 2>>cloc-errors.log # CFC_CMD='grep -o 'if'\|'for'\|'return'' diff --git a/gradle.properties b/gradle.properties index 054aadd59669403c2259cbc5cef9748baef94bce..45378cc37ed4a3ac3643f8748530f9082394c660 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,8 @@ -jackson_version = 2.12.3 -jupiter_version = 5.8.0-M1 -assertj_version = 3.19.0 -apache_httpcomponents_version = 4.5.8 -log4j_version = 2.14.1 +jackson_version = 2.13.2.2 +jackson_yaml_version = 2.13.2 +jupiter_version = 5.8.2 +assertj_version = 3.22.0 +log4j_version = 2.17.2 protobuf_version = 4.0.0-rc-2 +protobuf_plugin_version = 0.8.18 +jastadd_gradle_version = 1.14.5 diff --git a/libs/jastadd2.jar b/libs/jastadd2.jar new file mode 100644 index 0000000000000000000000000000000000000000..d615b895453d660f0e7397fffad58a05029169fd Binary files /dev/null and b/libs/jastadd2.jar differ diff --git a/ros3rag.common/build.gradle b/ros3rag.common/build.gradle index 40c0c7ff928270f9147e88caec466b904cf64815..d3efb0c5ed12f6d9609eb725358a3f3ebca5c707 100644 --- a/ros3rag.common/build.gradle +++ b/ros3rag.common/build.gradle @@ -1,19 +1,18 @@ buildscript { - repositories.jcenter() + repositories.mavenCentral() dependencies { - classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.16' + classpath "com.google.protobuf:protobuf-gradle-plugin:${protobuf_plugin_version}" } } plugins { id 'ros3rag.java-application-conventions' id 'java-library' + id "com.google.protobuf" version "${protobuf_plugin_version}" } -apply plugin: 'com.google.protobuf' - dependencies { - implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: "${jackson_version}" + implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: "${jackson_yaml_version}" api group: 'com.google.protobuf', name: 'protobuf-java', version: "${protobuf_version}" api group: 'com.google.protobuf', name: 'protobuf-java-util', version: "${protobuf_version}" api group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1' diff --git a/ros3rag.common/src/main/java/de/tudresden/inf/st/ros3rag/common/Configuration.java b/ros3rag.common/src/main/java/de/tudresden/inf/st/ros3rag/common/Configuration.java index 4323482606e8a6850c3304bb4f90fb3ff2919fb7..962e31ac5a8a74840a9985b5cd71c9b9026166af 100644 --- a/ros3rag.common/src/main/java/de/tudresden/inf/st/ros3rag/common/Configuration.java +++ b/ros3rag.common/src/main/java/de/tudresden/inf/st/ros3rag/common/Configuration.java @@ -14,10 +14,15 @@ public class Configuration { public String mqttHost; public String filenameInitialScene; public boolean useReachability; + public String coordinatorMqttTopicPrefix; public List<ReachabilityConfig> reachability; public static class ReachabilityConfig { public String idRobot; public String filename; } + + public boolean useCoordinator() { + return coordinatorMqttTopicPrefix != null; + } } diff --git a/ros3rag.common/src/main/java/de/tudresden/inf/st/ros3rag/common/SharedMainParts.java b/ros3rag.common/src/main/java/de/tudresden/inf/st/ros3rag/common/SharedMainParts.java new file mode 100644 index 0000000000000000000000000000000000000000..dfb03f3f94b8c06192e181b52f23713d597b8406 --- /dev/null +++ b/ros3rag.common/src/main/java/de/tudresden/inf/st/ros3rag/common/SharedMainParts.java @@ -0,0 +1,206 @@ +package de.tudresden.inf.st.ros3rag.common; + +import de.tudresden.inf.st.ceti.Scene; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Shared parts of both main classes. + * + * @author rschoene - Initial contribution + */ +public abstract class SharedMainParts<MqttHandler extends SharedMainParts.MqttHandlerWrapper<MqttHandler>, + WorldModel extends SharedMainParts.WorldModelWrapper> { + protected final Logger logger = LogManager.getLogger(this.getClass()); + + private final String TOPIC_MODEL; + private final String TOPIC_STATUS; + private final String TOPIC_REWIND; + private final String TOPIC_EXIT; + private final String TOPIC_SCENE_INIT; + + private static final String TOPIC_SUFFIX_COORDINATOR_STATUS = "status"; + private static final String TOPIC_SUFFIX_COORDINATOR_COMMAND = "command"; + + protected MqttHandler mainHandler; + protected WorldModel model; + protected Configuration config; + protected final String cellName; + protected final Path pathToConfig; + private CountDownLatch startCondition; + + public SharedMainParts(String cellName, Path pathToConfig) { + this.cellName = cellName; + this.pathToConfig = pathToConfig; + + this.TOPIC_MODEL = cellName + "/model"; + this.TOPIC_STATUS = cellName + "/status"; + this.TOPIC_REWIND = cellName + "/rewind"; + this.TOPIC_EXIT = cellName + "/exit"; + + this.TOPIC_SCENE_INIT = cellName + "/scene/init"; + } + + protected abstract MqttHandler createMqttHandler(); + + private Map<String, String> channelDescriptions() { + return new LinkedHashMap<>() {{ + put(TOPIC_MODEL, "Print current model (detailed if message starts with 'detail"); + put(TOPIC_REWIND, "Rewind app to start"); + put(TOPIC_EXIT, "Exit app"); + }}; + } + + public void run() throws Exception { + config = Util.parseConfig(pathToConfig.toFile()); + + /// Prepare main handler + mainHandler = createMqttHandler().dontSendWelcomeMessage(); + mainHandler.setHost(config.mqttHost); + mainHandler.waitUntilReady(2, TimeUnit.SECONDS); + CountDownLatch exitCondition = new CountDownLatch(1); + mainHandler.newConnection(TOPIC_EXIT, bytes -> exitCondition.countDown()); + mainHandler.newConnection(TOPIC_MODEL, bytes -> logStatus(new String(bytes))); + mainHandler.newConnection(TOPIC_REWIND, bytes -> + { + try { + rewind("rewind"); + } catch (Exception e) { + logger.catching(e); + } + } + ); + if (config.useCoordinator()) { + logger.info("Using coordinator logic"); + mainHandler.newConnection(joinTopics(config.coordinatorMqttTopicPrefix, TOPIC_SUFFIX_COORDINATOR_COMMAND), + bytes -> reactToCoordinatorCommand(new String(bytes))); + } + + createSpecificMainHandlerConnections(); + + logger.info("Supported commands: {}", channelDescriptions()); + + rewind("Start"); + + Runtime.getRuntime().addShutdownHook(new Thread(this::close)); + + exitCondition.await(); + } + + private String joinTopics(String... topics) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < topics.length; i++) { + String topic = topics[i]; + if (i > 0 && topic.startsWith("/")) { + topic = topic.substring(1); + } + if (i < topics.length - 1 && topic.endsWith("/")) { + topic = topic.substring(0, topic.length() - 1); + } + if (i > 0) { + sb.append("/"); + } + sb.append(topic); + } + return sb.toString(); + } + + private void reactToCoordinatorCommand(String command) { + switch (command) { + case "rewind": + try { + rewind(command); + } catch (Exception e) { + e.printStackTrace(); + } + break; + case "start": + startCondition.countDown(); + break; + default: + System.err.println("Unknown coordinator command: " + command); + } + } + + protected abstract void createSpecificMainHandlerConnections(); + + private void rewind(String statusMessage) throws Exception { + if (model != null) { + model.ragconnectCloseConnections(); + } + model = createWorldModel(); + + Scene scene = readSceneAndRobots(); + + /// Setup model connection + model.ragconnectCheckIncremental(); + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + if (config.useCoordinator()) { + startCondition = new CountDownLatch(1); + mainHandler.publish(joinTopics(config.coordinatorMqttTopicPrefix, TOPIC_SUFFIX_COORDINATOR_STATUS), + "up".getBytes(StandardCharsets.UTF_8)); + startCondition.await(); + } + + connectEndpoints(); + + logStatus(statusMessage); + + mainHandler.publish(TOPIC_SCENE_INIT, scene.toByteArray()); + if (config.useCoordinator()) { + mainHandler.publish(joinTopics(config.coordinatorMqttTopicPrefix, TOPIC_SUFFIX_COORDINATOR_STATUS), + "ready".getBytes(StandardCharsets.UTF_8)); + } + } + + protected abstract Scene readSceneAndRobots() throws Exception; + + protected abstract void connectEndpoints() throws IOException; + + protected abstract WorldModel createWorldModel(); + + protected abstract String getModelInfos(WorldModel model, boolean detailed); + + private void logStatus(String message) { + logger.info(message); + String content = getModelInfos(model, message.equals("Start") || message.startsWith("detail")); + logger.info("WorldModelB\n{}", content); + if (mainHandler != null) { + mainHandler.publish(TOPIC_STATUS, content.getBytes(StandardCharsets.UTF_8)); + } + } + + private void close() { + logger.info("Exiting ..."); + mainHandler.close(); + model.ragconnectCloseConnections(); + } + + public interface MqttHandlerWrapper<SELF> { + SELF dontSendWelcomeMessage(); + boolean newConnection(String topic, Consumer<byte[]> callback); + void publish(String topic, byte[] payload); + void close(); + + SELF setHost(String host) throws java.io.IOException; + + @SuppressWarnings("UnusedReturnValue") + boolean waitUntilReady(long value, TimeUnit unit); + } + + public interface WorldModelWrapper { + void ragconnectCheckIncremental(); + void ragconnectSetupMqttWaitUntilReady(long value, TimeUnit unit); + void ragconnectCloseConnections(); + } +} diff --git a/ros3rag.common/src/main/java/de/tudresden/inf/st/ros3rag/common/Util.java b/ros3rag.common/src/main/java/de/tudresden/inf/st/ros3rag/common/Util.java index 29c0d850fd2c370f5776469701314c9ac7c5c0d5..1b8b116cf7dd075207d3b3177f619b543841a814 100644 --- a/ros3rag.common/src/main/java/de/tudresden/inf/st/ros3rag/common/Util.java +++ b/ros3rag.common/src/main/java/de/tudresden/inf/st/ros3rag/common/Util.java @@ -14,7 +14,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Helper method dealing with config. @@ -58,11 +60,18 @@ public class Util { return pathToModuleDirectory("ros3rag.common"); } + private final static Set<String> modules = new HashSet<>() {{ + add("ros3rag.placeA"); + add("ros3rag.placeB"); + add("ros3rag.common"); + add("ros3rag.coordinator"); + }}; + public static Path pathToModuleDirectory(String moduleName) { Path current = Paths.get("").toAbsolutePath(); String currentFileName = current.getFileName().toString(); Path repoRoot; - if (currentFileName.equals("ros3rag.placeA") || currentFileName.equals("ros3rag.placeB") || currentFileName.equals("ros3rag.common")) { + if (modules.contains(currentFileName)) { // we are in some module, use parent repoRoot = current.getParent(); } else if (current.resolve(moduleName).toFile().exists()) { @@ -72,14 +81,4 @@ public class Util { } return repoRoot.resolve(moduleName); } - - private static class HostAndPort { - String host; - int port; - } - - @FunctionalInterface - public interface HandleLink { - void handle(boolean isEndEffector, String topic, String name) throws IOException; - } } diff --git a/ros3rag.common/src/main/proto/cgv_connector.proto b/ros3rag.common/src/main/proto/cgv_connector.proto index be68b973f8c82c5b99b10e7fefcbbf4c05bb4c5d..ca2f8c48e61b1d433d5322894dac851cfb6491af 100644 --- a/ros3rag.common/src/main/proto/cgv_connector.proto +++ b/ros3rag.common/src/main/proto/cgv_connector.proto @@ -37,6 +37,7 @@ message Object { BOX = 1; BIN = 2; ARM = 3; + DROP_OFF_LOCATION = 4; } string id = 1; @@ -72,4 +73,4 @@ message Reachability { } string idRobot = 1; // the id of the robot arm repeated ObjectReachability objects = 2; // all objects reachable -} +} \ No newline at end of file diff --git a/ros3rag.common/src/main/resources/jastadd/types.connect b/ros3rag.common/src/main/resources/jastadd/types.connect index 9d2f860d5e7e2c3e2457a00d54fa763756137ecc..ed12a0066808358bff82d26c5a8630e3b35436e1 100644 --- a/ros3rag.common/src/main/resources/jastadd/types.connect +++ b/ros3rag.common/src/main/resources/jastadd/types.connect @@ -13,8 +13,8 @@ ConvertScene maps de.tudresden.inf.st.ceti.Scene pbScene to Scene {: result.addMovableObject(movableObject); obj = movableObject; break; - case BIN: - // BIN == DropOffLocation + case DROP_OFF_LOCATION: + // DROP_OFF_LOCATION == DropOffLocation var dropOffLocation = new DropOffLocation(); result.addDropOffLocation(dropOffLocation); obj = dropOffLocation; diff --git a/ros3rag.common/src/main/resources/jastadd/types.jadd b/ros3rag.common/src/main/resources/jastadd/types.jadd index edaf5b36248eb2c642e81ff299a83a54dfaf28f6..0d9fe88eb118fec13f1aadec7f72440fbefab760 100644 --- a/ros3rag.common/src/main/resources/jastadd/types.jadd +++ b/ros3rag.common/src/main/resources/jastadd/types.jadd @@ -2,33 +2,8 @@ import org.apache.commons.math3.geometry.euclidean.threed.Rotation; import org.apache.commons.math3.geometry.euclidean.threed.Vector3D; import java.util.*; -aspect GrammarTypes { - public static Position Position.of(int x, int y, int z) { - return new Position(x, y, z); - } - - @Override - public boolean Position.equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Position that = (Position) o; - return getX() == that.getX() && - getY() == that.getY() && - getZ() == that.getZ(); - } - - @Override - public int Position.hashCode() { - return java.util.Objects.hash(getX(), getY(), getZ()); - } - - @Override - public String Position.toString() { - return "(" + getX() + ", " + getY() + ", " + getZ() + ")"; - } -} - aspect Resolving { + //--- resolveObjectOfInterest --- syn ObjectOfInterest Scene.resolveObjectOfInterest(String name) { for (DropOffLocation location : getDropOffLocationList()) { if (location.getName().equals(name)) { @@ -42,6 +17,8 @@ aspect Resolving { } return null; } + + //--- resolveLogicalObjectOfInterest --- syn LogicalObjectOfInterest LogicalScene.resolveLogicalObjectOfInterest(String name) { for (LogicalDropOffLocation location : getLogicalDropOffLocationList()) { if (location.getName().equals(name)) { @@ -58,6 +35,7 @@ aspect Resolving { } aspect Computation { + //--- isLocatedAt --- syn boolean MovableObject.isLocatedAt(DropOffLocation location) { Orientation orient = location.getOrientation(); Position locationPosition = location.getPosition(); @@ -84,34 +62,35 @@ aspect Computation { double rotatedY = rotatedObjectVector.getY(); double rotatedZ = rotatedObjectVector.getZ(); - System.out.printf("min: (%f , %f , %f). rotated: (%f , %f , %f), max: (%f , %f , %f)%n", - -halfLocationLength, -halfLocationWidth, -halfLocationHeight, - rotatedX, rotatedY, rotatedZ, - halfLocationLength, - halfLocationWidth, - halfLocationHeight); +// System.out.printf("min: (%f , %f , %f). rotated: (%f , %f , %f), max: (%f , %f , %f)%n", +// -halfLocationLength, -halfLocationWidth, -halfLocationHeight, +// rotatedX, rotatedY, rotatedZ, +// halfLocationLength, +// halfLocationWidth, +// halfLocationHeight); return LT(-halfLocationLength, rotatedX) && LT(rotatedX, halfLocationLength) && LT(-halfLocationWidth, rotatedY) && LT(rotatedY, halfLocationWidth) && LT(-halfLocationHeight, rotatedZ) && LT(rotatedZ, halfLocationHeight); } + //--- getLogicalScene --- syn LogicalScene Scene.getLogicalScene() { var result = new LogicalScene(); Map<MovableObject, LogicalMovableObject> objects = new HashMap<>(); + for (MovableObject movableObject : getMovableObjectList()) { + var newLogicalMovableObject = new LogicalMovableObject(); + newLogicalMovableObject.setName(movableObject.getName()); + result.addLogicalMovableObject(newLogicalMovableObject); + objects.put(movableObject, newLogicalMovableObject); + } for (DropOffLocation location : getDropOffLocationList()) { var logicalLocation = new LogicalDropOffLocation(); logicalLocation.setName(location.getName()); result.addLogicalDropOffLocation(logicalLocation); for (MovableObject movableObject : getMovableObjectList()) { if (movableObject.isLocatedAt(location)) { - LogicalMovableObject logicalMovableObject = objects.computeIfAbsent(movableObject, k -> { - var newLogicalMovableObject = new LogicalMovableObject(); - newLogicalMovableObject.setName(k.getName()); - result.addLogicalMovableObject(newLogicalMovableObject); - return newLogicalMovableObject; - }); - logicalLocation.addContainedObject(logicalMovableObject); + logicalLocation.addContainedObject(objects.get(movableObject)); } } } @@ -120,6 +99,7 @@ aspect Computation { private static final double MovableObject.DELTA = 0.0001; + //--- LT --- /** * @return d1 <= d2 (within a DELTA) */ @@ -147,36 +127,90 @@ aspect Navigation { } aspect Printing { - syn String LogicalScene.prettyPrint() { + //--- prettyPrint --- + syn String JastAddList.prettyPrint() { + return prettyPrint(Object::toString); + } + syn String JastAddList.prettyPrint(java.util.function.Function<T, String> toString) { + return java.util.stream.StreamSupport.stream(Spliterators.spliteratorUnknownSize(this.iterator(), 16), false).map(toString::apply).collect(java.util.stream.Collectors.joining(", ", "[", "]")); + } + + syn String Scene.prettyPrint() { StringBuilder sb = new StringBuilder(); - for (var location : getLogicalDropOffLocationList()) { - sb.append("location ").append(location); - if (!location.getContainedObjectList().isEmpty()) { - sb.append(" (objects: ").append(location.getContainedObjectList()).append(")"); + if (getNumDropOffLocation() == 0) { + sb.append(" no locations\n"); + } else { + for (var location : getDropOffLocationList()) { + sb.append(" location ").append(location.prettyPrint()).append("\n"); } - sb.append("\n"); } - for (var obj : getLogicalMovableObjectList()) { - sb.append("obj ").append(obj); - if (obj.hasLocatedAt()) { - sb.append(" (locatedAt: ").append(obj.getLocatedAt()).append(")"); + if (getNumMovableObject() == 0) { + sb.append(" no objects\n"); + } else { + for (var obj : getMovableObjectList()) { + sb.append(" obj ").append(obj.prettyPrint()).append("\n"); } - sb.append("\n"); } return sb.toString(); } - syn String JastAddList.prettyPrint() { - return prettyPrint(Object::toString); + + syn String MovableObject.prettyPrint() { + return "<obj " + getName() + getPosition().prettyPrint() + getOrientation().prettyPrint() + getSize().prettyPrint() + "@" + Integer.toHexString(hashCode()) + ">"; } - syn String JastAddList.prettyPrint(java.util.function.Function<T, String> toString) { - return java.util.stream.StreamSupport.stream(Spliterators.spliteratorUnknownSize(this.iterator(), 16), false).map(toString::apply).collect(java.util.stream.Collectors.joining(", ", "[", "]")); + + syn String DropOffLocation.prettyPrint() { + return "{loc " + getName() + getPosition().prettyPrint() + getOrientation().prettyPrint() + getSize().prettyPrint() + "@" + Integer.toHexString(hashCode()) + "}"; + } + + syn String Position.prettyPrint() { + return " pos=(" + getX() + "," + getY() + "," + getZ() + ")"; + } + + syn String Orientation.prettyPrint() { + return " orient=(" + getX() + "," + getY() + "," + getZ() + "," + getW() + ")"; + } + + syn String Size.prettyPrint() { + return " size=(" + getLength() + "," + getWidth() + "," + getHeight() + ")"; + } + + syn String LogicalScene.prettyPrint() { + StringBuilder sb = new StringBuilder(); + if (getNumLogicalDropOffLocation() == 0) { + sb.append(" no locations\n"); + } else { + for (var location : getLogicalDropOffLocationList()) { + sb.append(" location ").append(location.prettyPrint()); + if (!location.getContainedObjectList().isEmpty()) { + sb.append(" (objects: ").append(location.getContainedObjectList()).append(")"); + } + sb.append("\n"); + } + } + if (getNumLogicalMovableObject() == 0) { + sb.append(" no objects\n"); + } else { + for (var obj : getLogicalMovableObjectList()) { + sb.append(" obj ").append(obj.prettyPrint()); + if (obj.hasLocatedAt()) { + sb.append(" (locatedAt: ").append(obj.getLocatedAt()).append(")"); + } + sb.append("\n"); + } + } + return sb.toString(); } - syn String LogicalMovableObject.toString() = "<obj " + getName() + "@" + hashCode() + ">"; - syn String LogicalDropOffLocation.toString() = "{loc " + getName() + "@" + hashCode() + "}"; + syn String LogicalMovableObject.prettyPrint() { + return "<obj " + getName() + "@" + Integer.toHexString(hashCode()) + ">"; + } + syn String LogicalDropOffLocation.prettyPrint() { + return "{loc " + getName() + "@" + Integer.toHexString(hashCode()) + "}"; + } } aspect ConvenienceMethods { + // --- of --- public static DropOffLocation DropOffLocation.of(String name, Position position, Orientation orientation, Size size) { var location = new DropOffLocation(); @@ -211,3 +245,7 @@ aspect ConvenienceMethods { return new Size(length, width, height); } } + +aspect Glue { + class MqttHandler implements de.tudresden.inf.st.ros3rag.common.SharedMainParts.MqttHandlerWrapper<MqttHandler> {} +} diff --git a/ros3rag.common/src/main/resources/tasks.gradle b/ros3rag.common/src/main/resources/tasks.gradle index 7b7c51f49605ed3cfb0eaa059031c06ca15fb4f4..8016213bdb698da3a2458d150e84f6574ec4f03e 100644 --- a/ros3rag.common/src/main/resources/tasks.gradle +++ b/ros3rag.common/src/main/resources/tasks.gradle @@ -1,10 +1,8 @@ -apply plugin: 'jastadd' - dependencies { - jastadd2 "org.jastadd:jastadd:2.3.5" + jastadd2 "org.jastadd:jastadd2:2.3.5-dresden-5" +// jastadd2 fileTree(include: ['jastadd2.jar'], dir: '../libs') api group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15' - } sourceCompatibility = 11 @@ -15,7 +13,7 @@ targetCompatibility = 11 task ragConnect(type: JavaExec) { group = 'Build' main = 'org.jastadd.ragconnect.compiler.Compiler' - classpath = configurations.ragconnectClasspath + classpath = configurations.ragconnect args([ '--o=src/gen/jastadd', @@ -27,7 +25,9 @@ task ragConnect(type: JavaExec) { '--logWrites', // '--verbose', '--rootNode=' + project.ext.ragConnectRootNode, - '--incremental=param,debug', + '--List=JastAddList', + '--experimental-jastadd-329', + '--incremental=param', "--tracing=cache,flush" ]) @@ -38,32 +38,35 @@ task ragConnect(type: JavaExec) { task grammar2uml(type: JavaExec) { main = 'de.tudresden.inf.st.jastadd.grammar2uml.compiler.Compiler' - classpath = configurations.grammar2umlClasspath + classpath = configurations.grammar2uml args([ '--verbose', 'src/gen/jastadd/types.relast' - ] - + - project.ext.relastFiles) + ] + project.ext.relastFiles) } // phase: RelAst task relastToJastAdd(type: JavaExec) { group = 'Build' - main = "-jar" + classpath = configurations.relast + //noinspection GroovyAssignabilityCheck, GroovyAccessibility + mainClass = 'org.jastadd.relast.compiler.Compiler' + + doFirst { + mkdir "src/gen/jastadd/" + } - args(["../libs/relast.jar", - "--grammarName=./src/gen/jastadd/model", + args = [ + "--grammarName=./src/gen/jastadd/model", "--useJastAddNames", "--listClass=java.util.ArrayList", "--jastAddList=JastAddList", "--serializer=jackson", "--resolverHelper", "--file", - "src/gen/jastadd/types.relast"] - + - project.ext.relastFiles) + "src/gen/jastadd/types.relast" + ] + project.ext.relastFiles inputs.files project.ext.relastFiles + ['src/gen/jastadd/types.relast'] outputs.files file("./src/gen/jastadd/model.ast"), file("./src/gen/jastadd/model.jadd") @@ -119,7 +122,7 @@ jastadd { '--lineColumnNumbers', '--List=JastAddList', '--cache=all', - "--flush=full", + "--flush=api", "--incremental=param,debug", "--tracing=cache,flush", ] diff --git a/ros3rag.placeA/build.gradle b/ros3rag.placeA/build.gradle index 69f31e94c823d394dc6929e1b7145ba5c94bbe59..5e90f00c02c0ffe44d5e15740f9d6c6702702c4e 100644 --- a/ros3rag.placeA/build.gradle +++ b/ros3rag.placeA/build.gradle @@ -1,7 +1,7 @@ buildscript { repositories.mavenCentral() dependencies { - classpath group: 'org.jastadd', name: 'jastaddgradle', version: '1.13.3' + classpath group: 'org.jastadd', name: 'jastaddgradle', version: "${jastadd_gradle_version}" } } diff --git a/ros3rag.placeA/src/main/jastadd/WorldModelA.connect b/ros3rag.placeA/src/main/jastadd/WorldModelA.connect index aa4b1c84fbd0bc8f63fc5e259d73d9e6760e968f..c3e16e8da32f55d35ca7eb6ce2bcfdf7e8f2b57f 100644 --- a/ros3rag.placeA/src/main/jastadd/WorldModelA.connect +++ b/ros3rag.placeA/src/main/jastadd/WorldModelA.connect @@ -1,13 +1,13 @@ // --- receiving --- -receive tree WorldModelA.Scene using ParseScene, ConvertScene ; +receive WorldModelA.Scene using ParseScene, ConvertScene ; // rs: not sure whether we want to receive a complete scene. could be hard to update/merge // if necessary, need a mapping. the multi-point-connection is an alternative. // we need to decide for one way // rs: let's try to use a "multi-point-connection" (same input meesage, multiple recipients) -receive tree ObjectOfInterest.Position using DeserializeObject, ExtractPositionFromObject ; -receive tree ObjectOfInterest.Size using DeserializeObject, ExtractSizeFromObject ; -receive tree ObjectOfInterest.Orientation using DeserializeObject,ExtractOrientationFromObject ; +receive ObjectOfInterest.Position using DeserializeObject, ExtractPositionFromObject ; +receive ObjectOfInterest.Size using DeserializeObject, ExtractSizeFromObject ; +receive ObjectOfInterest.Orientation using DeserializeObject,ExtractOrientationFromObject ; DeserializeObject maps byte[] bytes to de.tudresden.inf.st.ceti.Object {: return de.tudresden.inf.st.ceti.Object.parseFrom(bytes); @@ -38,4 +38,4 @@ ExtractOrientationFromObject maps de.tudresden.inf.st.ceti.Object o to Orientati :} // --- sending --- -send tree Scene.LogicalScene ; +send WorldModelA.LogicalScene ; diff --git a/ros3rag.placeA/src/main/jastadd/WorldModelA.jadd b/ros3rag.placeA/src/main/jastadd/WorldModelA.jadd index 3ad5caed414eab31b1b1c2223ed552184c54a022..b9dd1eebab6bf09ba350bce028e8f6bac4349695 100644 --- a/ros3rag.placeA/src/main/jastadd/WorldModelA.jadd +++ b/ros3rag.placeA/src/main/jastadd/WorldModelA.jadd @@ -1,33 +1,14 @@ -import java.util.List; -import java.util.ArrayList; -import java.util.Map; -import java.util.HashMap; - aspect Resolving { + //--- resolveObjectOfInterest --- inh ObjectOfInterest Robot.resolveObjectOfInterest(String name); eq WorldModelA.getRobot().resolveObjectOfInterest(String name) { return getScene().resolveObjectOfInterest(name); } } -aspect Computation { -// syn LogicalScene WorldModelA.getLogicalScene() { -// LogicalScene result = new LogicalScene(); -// Map<MovableObject, LogicalMovableObject> objects = new HashMap<>(); -// for (DropOffLocation location : getScene().getDropOffLocationList()) { -// var logicalLocation = new LogicalDropOffLocation(); -// logicalLocation.setName(location.getName()); -// for (MovableObject movableObject : getScene().getMovableObjectList()) { -// if (movableObject.isLocatedAt(location)) { -// LogicalMovableObject logicalMovableObject = objects.computeIfAbsent(movableObject, k -> { -// var newLogicalMovableObject = new LogicalMovableObject(); -// newLogicalMovableObject.setName(k.getName()); -// return newLogicalMovableObject; -// }); -// logicalLocation.addContainedObjects(logicalMovableObject); -// } -// } -// } -// return result; -// } +aspect Glue { + class WorldModelA implements de.tudresden.inf.st.ros3rag.common.SharedMainParts.WorldModelWrapper {} + + //--- getLogicalScene --- + syn LogicalScene WorldModelA.getLogicalScene() = hasScene() ? getScene().getLogicalScene() : new LogicalScene(); } diff --git a/ros3rag.placeA/src/main/jastadd/WorldModelA.relast b/ros3rag.placeA/src/main/jastadd/WorldModelA.relast index 67c9b50130f87fa614a10a7381fb0ca6f72404dc..4089553a078bf8fb2d1cbdda036562f9ce309b68 100644 --- a/ros3rag.placeA/src/main/jastadd/WorldModelA.relast +++ b/ros3rag.placeA/src/main/jastadd/WorldModelA.relast @@ -1,5 +1,3 @@ -WorldModelA ::= Robot [Scene] ; //Task* ; +WorldModelA ::= Robot [Scene] /LogicalScene/ ; Robot ::= <Name:String> ; - -//Task ::= ObjectToMove:MovableObject TargetLocation:DropOffLocation ; diff --git a/ros3rag.placeA/src/main/java/de/tudresden/inf/st/placeA/MainA.java b/ros3rag.placeA/src/main/java/de/tudresden/inf/st/placeA/MainA.java index 83ca62dd2629f663b2684111fd594b4c91d4e689..157de87d3bbaed53de0fb47e66df021cc2ef3d02 100644 --- a/ros3rag.placeA/src/main/java/de/tudresden/inf/st/placeA/MainA.java +++ b/ros3rag.placeA/src/main/java/de/tudresden/inf/st/placeA/MainA.java @@ -4,14 +4,10 @@ import de.tudresden.inf.st.placeA.ast.MqttHandler; import de.tudresden.inf.st.placeA.ast.Robot; import de.tudresden.inf.st.placeA.ast.Scene; import de.tudresden.inf.st.placeA.ast.WorldModelA; +import de.tudresden.inf.st.ros3rag.common.SharedMainParts; import de.tudresden.inf.st.ros3rag.common.Util; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import java.io.File; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; +import java.io.IOException; import static de.tudresden.inf.st.ros3rag.common.Util.mqttUri; import static de.tudresden.inf.st.ros3rag.common.Util.readScene; @@ -21,41 +17,47 @@ import static de.tudresden.inf.st.ros3rag.common.Util.readScene; * * @author rschoene - Initial contribution */ -public class MainA { - private static final Logger logger = LogManager.getLogger(MainA.class); - - private final static String TOPIC_MODEL = "place-a/model"; - private final static String TOPIC_STATUS = "place-a/status"; - private final static String TOPIC_EXIT = "place-a/exit"; - - private final static String TOPIC_SCENE_INIT = "place-a/init"; - private final static String TOPIC_SCENE_UPDATE_FROM_ROS = "place-a/scene/update"; - private final static String TOPIC_SCENE_UPDATE_TO_PLACE_B = "place-a/logical/update"; - - private MqttHandler mainHandler; - private WorldModelA model; - - public static void main(String[] args) throws Exception { - new MainA().run(args); +public class MainA extends SharedMainParts<MqttHandler, WorldModelA> { + private final String TOPIC_SCENE_UPDATE_FROM_ROS; + private final String TOPIC_SCENE_UPDATE_TO_PLACE_B; + + private final String TOPIC_DEMO_MOVE_objectRed1_BLUE; + private final String TOPIC_DEMO_MOVE_objectRed1_RED; + private final String TOPIC_EVAL_MOVE; + + MainA(String configFile) { + super("place-a", UtilA.pathToDirectoryOfPlaceA().resolve(configFile)); + this.TOPIC_SCENE_UPDATE_FROM_ROS = cellName + "/scene/update"; + this.TOPIC_SCENE_UPDATE_TO_PLACE_B = cellName + "/logical/update"; + + this.TOPIC_DEMO_MOVE_objectRed1_BLUE = cellName + "/demo/move/objectRed1/blue"; + this.TOPIC_DEMO_MOVE_objectRed1_RED = cellName + "/demo/move/objectRed1/red"; + this.TOPIC_EVAL_MOVE = cellName + "/eval/move"; } - private void run(String[] args) throws Exception { + public static void main(String[] args) throws Exception { String configFile = args.length == 0 ? "src/main/resources/config-a.yaml" : args[0]; + new MainA(configFile).run(); + } - // --- No configuration below this line --- - - final var config = Util.parseConfig(UtilA.pathToDirectoryOfPlaceA().resolve(configFile).toFile()); - - model = new WorldModelA(); - - /// Prepare main handler - mainHandler = new MqttHandler("mainHandler").dontSendWelcomeMessage(); - mainHandler.setHost(config.mqttHost); - CountDownLatch exitCondition = new CountDownLatch(1); - mainHandler.waitUntilReady(2, TimeUnit.SECONDS); - mainHandler.newConnection(TOPIC_EXIT, bytes -> exitCondition.countDown()); - mainHandler.newConnection(TOPIC_MODEL, bytes -> logStatus(new String(bytes))); + @Override + protected void createSpecificMainHandlerConnections() { + mainHandler.newConnection(TOPIC_DEMO_MOVE_objectRed1_BLUE, bytes -> + UtilA.updatePositionOfObjectToLocation(model.getScene(), "objectRed1", "binBlue") + ); + mainHandler.newConnection(TOPIC_DEMO_MOVE_objectRed1_RED, bytes -> + UtilA.updatePositionOfObjectToLocation(model.getScene(), "objectRed1", "binRed") + ); + mainHandler.newConnection(TOPIC_EVAL_MOVE, bytes -> { + String[] tokens = new String(bytes).split(" to "); + String objectName = tokens[0]; + String locationName = tokens[1]; + UtilA.updatePositionOfObjectToLocation(model.getScene(), objectName, locationName); + }); + } + @Override + protected de.tudresden.inf.st.ceti.Scene readSceneAndRobots() throws Exception { /// Reading scene and robot de.tudresden.inf.st.ceti.Scene scene = readScene( UtilA.pathToDirectoryOfPlaceA().resolve(config.filenameInitialScene) @@ -64,48 +66,34 @@ public class MainA { model.setScene(myScene); // if multiple robots are specified, then error message will be displayed - Util.extractRobotNames(scene).forEach(this::setRobot); - - /// Setup model connection - model.ragconnectCheckIncremental(); - model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + Util.extractRobotNames(scene).forEach(name -> { + if (model.getRobot() != null) { + logger.error("Robot already set. Overriding with newly found name."); + } + model.setRobot(new Robot().setName(name)); + }); + + return scene; + } - /// Connect endpoints + @Override + protected void connectEndpoints() throws IOException { model.connectScene(mqttUri(TOPIC_SCENE_UPDATE_FROM_ROS, config)); - model.getScene().connectLogicalScene(mqttUri(TOPIC_SCENE_UPDATE_TO_PLACE_B, config), true); - - logStatus("Start"); - - logger.info("To print the current model states, send a message to the topic '{}'.", TOPIC_MODEL); - logger.info("To exit the system cleanly, send a message to the topic '{}', or use Ctrl+C.", TOPIC_EXIT); - -// mainHandler.publish(mqttUri(TOPIC_SCENE_INIT, config), scene.toByteArray()); - logger.fatal("Skipping publishing to {} for now", TOPIC_SCENE_INIT); - - Runtime.getRuntime().addShutdownHook(new Thread(this::close)); - - exitCondition.await(); + model.connectLogicalScene(mqttUri(TOPIC_SCENE_UPDATE_TO_PLACE_B, config), true); } - private void setRobot(String name) { - if (model.getRobot() != null) { - logger.error("Robot already set. Overriding with newly found name."); - } - model.setRobot(new Robot().setName(name)); + @Override + protected MqttHandler createMqttHandler() { + return new MqttHandler("mainHandlerA"); } - private void logStatus(String prefix) { - logger.info(prefix); - String content = UtilA.getModelInfos(model); - logger.info("WorldModelA\n{}", content); - if (mainHandler != null) { - mainHandler.publish(TOPIC_STATUS, content.getBytes(StandardCharsets.UTF_8)); - } + @Override + protected WorldModelA createWorldModel() { + return new WorldModelA(); } - private void close() { - logger.info("Exiting ..."); - mainHandler.close(); - model.ragconnectCloseConnections(); + @Override + protected String getModelInfos(WorldModelA worldModelA, boolean detailed) { + return UtilA.getModelInfos(model, detailed); } } diff --git a/ros3rag.placeA/src/main/java/de/tudresden/inf/st/placeA/SimpleMainA.java b/ros3rag.placeA/src/main/java/de/tudresden/inf/st/placeA/SimpleMainA.java index 327b5b5ec5cab962a7f36108fc4125cb3dc0ac0f..de2074c5219b619765ce87ca40446bcac35f267a 100644 --- a/ros3rag.placeA/src/main/java/de/tudresden/inf/st/placeA/SimpleMainA.java +++ b/ros3rag.placeA/src/main/java/de/tudresden/inf/st/placeA/SimpleMainA.java @@ -59,7 +59,7 @@ public class SimpleMainA { model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); model.connectScene("mqtt://localhost/scene/update"); - model.getScene().connectLogicalScene("mqtt://localhost/logical/scene/update", true); + model.connectLogicalScene("mqtt://localhost/logical/scene/update", true); } @SuppressWarnings("unused") diff --git a/ros3rag.placeA/src/main/java/de/tudresden/inf/st/placeA/UtilA.java b/ros3rag.placeA/src/main/java/de/tudresden/inf/st/placeA/UtilA.java index c7910770b58214cc10b25f6b393c2f45e6e3ea6a..fafa021fed70482ede4922a531724e386b8e1adc 100644 --- a/ros3rag.placeA/src/main/java/de/tudresden/inf/st/placeA/UtilA.java +++ b/ros3rag.placeA/src/main/java/de/tudresden/inf/st/placeA/UtilA.java @@ -1,8 +1,6 @@ package de.tudresden.inf.st.placeA; -import de.tudresden.inf.st.placeA.ast.ASTNode; -import de.tudresden.inf.st.placeA.ast.Scene; -import de.tudresden.inf.st.placeA.ast.WorldModelA; +import de.tudresden.inf.st.placeA.ast.*; import de.tudresden.inf.st.ros3rag.common.Util; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -25,11 +23,39 @@ public class UtilA { return new ExposingASTNode().exposed_apply_ConvertScene(scene); } - public static String getModelInfos(WorldModelA model) { + static void updatePositionOfObjectToLocation(Scene scene, String objName, String locationName) { + ObjectOfInterest obj = scene.resolveObjectOfInterest(objName); + ObjectOfInterest location = scene.resolveObjectOfInterest(locationName); + if (obj != null && location != null) { + // move objectRed1 to binBlue + logger.info("Got " + obj + " and location " + location); + logger.debug("before to {} at {}\n{}", locationName, location.getPosition(), + scene.prettyPrint()); + + obj.setPosition(Position.of(location.getPosition().getX(), + location.getPosition().getY(), + location.getPosition().getZ())); + + logger.debug("after\n{}", scene.prettyPrint()); + } else { + logger.error("Obj (" + obj + ") or location (" + location + ") are null"); + } + } + + public static String getModelInfos(WorldModelA model, boolean detailed) { StringBuilder sb = new StringBuilder(); sb.append("myRobot: ") .append(model.getRobot().getName()) .append("\n"); + if (detailed) { + // also include "normal" scene + sb.append("myScene:"); + if (model.hasScene()) { + sb.append("\n").append(model.getScene().prettyPrint()); + } else { + sb.append(" (unset)\n"); + } + } sb.append("myLogicalScene:"); if (model.hasScene()) { sb.append("\n").append(model.getScene().getLogicalScene().prettyPrint()); @@ -42,7 +68,7 @@ public class UtilA { @SuppressWarnings("rawtypes") static class ExposingASTNode extends ASTNode { public Scene exposed_apply_ConvertScene(de.tudresden.inf.st.ceti.Scene pbScene) throws Exception { - return ASTNode._apply_ConvertScene(pbScene); + return ASTNode._ragconnect__apply_ConvertScene(pbScene); } } } diff --git a/ros3rag.placeA/src/main/resources/config-a.yaml b/ros3rag.placeA/src/main/resources/config-a.yaml index a3901c56e167bfdf68e837111068528b5ca991f2..ab5d9af69a148c11b82c1f3301c8b0e2727bad18 100644 --- a/ros3rag.placeA/src/main/resources/config-a.yaml +++ b/ros3rag.placeA/src/main/resources/config-a.yaml @@ -1,3 +1,4 @@ mqttHost: "localhost" filenameInitialScene: "src/main/resources/config-scene-a.json" useReachability: false +coordinatorMqttTopicPrefix: "coordinating/rag-a" diff --git a/ros3rag.placeA/src/main/resources/config-scene-a.json b/ros3rag.placeA/src/main/resources/config-scene-a.json index 9f09d081b7d6caa749305865649366b180d27286..92b9ce4a0dde73a3379c4bb3cab0f3abfe892978 100644 --- a/ros3rag.placeA/src/main/resources/config-scene-a.json +++ b/ros3rag.placeA/src/main/resources/config-scene-a.json @@ -4,17 +4,21 @@ { "id": "tablePillar3","pos": { "x": -0.77,"y": 0.77,"z": 0.325 },"size": { "length": 0.06,"width": 0.06,"height": 0.65 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } }, { "id": "tablePillar4","pos": { "x": 0.77,"y": -0.77,"z": 0.325 },"size": { "length": 0.06,"width": 0.06,"height": 0.65 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } }, { "id": "table","pos": { "z": 0.7 },"size": { "length": 1.6,"width": 1.6,"height": 0.1 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } }, - { "id": "binBlue","type": "BIN","pos": { "x": -0.34,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "b": 1 } }, - { "id": "binRed","type": "BIN","pos": { "x": 0.06,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "r": 1 } }, - { "id": "binGreen","type": "BIN","pos": { "x": 0.46,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "g": 1 } }, + { "id": "binBlue","type": "DROP_OFF_LOCATION","pos": { "x": -0.34,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "b": 1 } }, + { "id": "binRed","type": "DROP_OFF_LOCATION","pos": { "x": 0.06,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "r": 1 } }, + { "id": "binGreen","type": "DROP_OFF_LOCATION","pos": { "x": 0.46,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "g": 1 } }, + { "id": "binYellow","type": "DROP_OFF_LOCATION","pos": { "x": 1.46,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "r": 1, "g": 1 } }, + { "id": "binPurple","type": "DROP_OFF_LOCATION","pos": { "x": 2.46,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "r": 1, "b": 1 } }, { "id": "objectRed1","type": "BOX","pos": { "x": 0.5,"y": -0.1,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "r": 1 } }, { "id": "objectRed2","type": "BOX","pos": { "x": 0.25,"y": -0.2,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "w": 1 },"color": { "r": 1 } }, { "id": "objectRed3","type": "BOX","pos": { "x": -0.4,"z": 0.819 },"size": { "length": 0.031,"width": 0.031,"height": 0.138 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "r": 1 } }, { "id": "objectGreen1","type": "BOX","pos": { "x": 0.45,"y": -0.3,"z": 0.8105 },"size": {"length":0.031,"width":0.062,"height":0.121},"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "g": 1 } }, - { "id": "objectGreen2","type": "BOX","pos": { "x": -0.45,"y": -0.2,"z": 0.8105 },"size": {"length":0.031,"width":0.031,"height":0.138},"orientation": { "z": 1,"w": 0.92388 },"color": { "g": 1 } }, + { "id": "objectGreen2","type": "BOX","pos": { "x": -0.45,"y": -0.2,"z": 0.8105 },"size": {"length":0.031,"width":0.031,"height":0.138},"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "g": 1 } }, { "id": "objectGreen3","type": "BOX","pos": { "x": 0.1,"y": -0.3,"z": 0.819 },"size": {"length":0.031,"width":0.062,"height":0.121},"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "g": 1 } }, { "id": "objectBlue1","type": "BOX","pos": { "x": 0.25,"y": -0.4,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "b": 1 } }, { "id": "objectBlue2","type": "BOX","pos": { "x": -0.3,"y": -0.3,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "b": 1 } }, { "id": "objectBlue3","type": "BOX","pos": { "x": 0.4,"z": 0.819 },"size": { "length": 0.031,"width": 0.031,"height": 0.138 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "b": 1 } }, + { "id": "objectYellow1","type": "BOX","pos": { "x": 0.4,"z": 0.819 },"size": { "length": 0.031,"width": 0.031,"height": 0.138 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "r": 1, "g": 1 } }, + { "id": "objectPurple1","type": "BOX","pos": { "x": 0.3,"z": 0.819 },"size": { "length": 0.031,"width": 0.031,"height": 0.138 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "r": 1, "b": 1 } }, { "id": "arm","type": "ARM","pos": { "z": 0.75 },"size": { },"orientation": { "w": 1 },"color": { "r": 1.00,"g": 1.00,"b": 1.00 } } ] } diff --git a/ros3rag.placeB/build.gradle b/ros3rag.placeB/build.gradle index 95d2d16ba73432095be516bff69b187c4fcd047f..0df9901804825aed51c43f9ed284a383c4b03e38 100644 --- a/ros3rag.placeB/build.gradle +++ b/ros3rag.placeB/build.gradle @@ -2,7 +2,7 @@ buildscript { repositories.mavenLocal() repositories.mavenCentral() dependencies { - classpath group: 'org.jastadd', name: 'jastaddgradle', version: '1.13.3' + classpath group: 'org.jastadd', name: 'jastaddgradle', version: "${jastadd_gradle_version}" } } @@ -17,18 +17,18 @@ task simpleRun(type: JavaExec) { group 'application' classpath sourceSets.main.runtimeClasspath main = "de.tudresden.inf.st.placeB.SimpleMainB" - } dependencies { implementation project(':ros3rag.common') + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: "${jupiter_version}" } ext.sharedJastAddDir = 'src/main/jastadd/shared' ext.ragConnectInputGrammar = 'src/main/jastadd/WorldModelB.relast' ext.ragConnectInputConnect = 'src/main/jastadd/WorldModelB.connect' ext.ragConnectRootNode = 'WorldModelB' -ext.relastFiles = ["src/gen/jastadd/WorldModelB.relast", "src/gen/jastadd/RagConnect.relast"] +ext.relastFiles = ["src/gen/jastadd/WorldModelB.relast", "src/main/jastadd/BFS/BFS.relast", "src/main/jastadd/RobotReachabilityToBFS.relast", "src/gen/jastadd/RagConnect.relast"] ext.jastaddAstPackage = 'de.tudresden.inf.st.placeB.ast' apply from: '../ros3rag.common/src/main/resources/tasks.gradle' diff --git a/ros3rag.placeB/src/main/jastadd/BFS/BFS.jrag b/ros3rag.placeB/src/main/jastadd/BFS/BFS.jrag new file mode 100644 index 0000000000000000000000000000000000000000..d59de4fa0aa754a46085e640eff698831da30728 --- /dev/null +++ b/ros3rag.placeB/src/main/jastadd/BFS/BFS.jrag @@ -0,0 +1,67 @@ +import java.util.*; + +aspect BFS { + //--- BFS --- + syn List<Edge> Vertex.BFS(Vertex goal) { + Map<Vertex, Edge> pred = new HashMap<>(); + Queue<Vertex> Q = new LinkedList<>(); + Set<Vertex> seen = new HashSet<>(); + seen.add(this); + Q.add(this); + while (!Q.isEmpty()) { + Vertex v = Q.remove(); + if (v.equals(goal)) { + return reconstructFrom(goal, pred); + } + for (Edge e : v.getOutgoingList()) { + Vertex w = e.getTo(); + if (seen.contains(w)) { + continue; + } + seen.add(w); + pred.put(w, e); + Q.add(w); + } + for (Edge e : v.getIncomingList()) { + if (!e.getBidirectional()) { continue; } + Vertex w = e.getFrom(); + if (seen.contains(w)) { + continue; + } + seen.add(w); + pred.put(w, e); + Q.add(w); + } + } + // failure + return null; + } + + //--- reconstructFrom --- + private List<Edge> Vertex.reconstructFrom(Vertex goal, Map<Vertex, Edge> pred) { + // used at the end of BFS to back-track path from goal to start + List<Edge> result = new ArrayList<>(); + Vertex current = goal; + while (!current.equals(this)) { + Edge e = pred.get(current); + result.add(e); + current = e.getFrom().equals(current) ? e.getTo() : e.getFrom(); + } +// result.add(this); + Collections.reverse(result); + return result; + } + + //--- prettyPrint --- + public String Graph.prettyPrint() { + StringJoiner sj = new StringJoiner("\n", "Graph {\n", "\n}"); + if (!hasVertex()) { sj.add("<no vertices>"); } + for (Vertex v : getVertexList()) { sj.add(v.toString()); } + if (!hasEdge()) { sj.add("<no edges>"); } + for (Edge e : getEdgeList()) { sj.add(e.prettyPrint()); } + return sj.toString(); + } + public String Edge.prettyPrint() { + return this.toString() + " : " + getFrom() + (getBidirectional() ? " <-> " : " -> ") + getTo(); + } +} diff --git a/ros3rag.placeB/src/main/jastadd/BFS/BFS.relast b/ros3rag.placeB/src/main/jastadd/BFS/BFS.relast new file mode 100644 index 0000000000000000000000000000000000000000..c3931ad536b40be9d04dfe733a542e8f56360c37 --- /dev/null +++ b/ros3rag.placeB/src/main/jastadd/BFS/BFS.relast @@ -0,0 +1,5 @@ +Graph ::= Vertex* Edge* ; +Vertex ; +Edge ::= <Bidirectional:boolean> ; +rel Edge.From <-> Vertex.Outgoing* ; +rel Edge.To <-> Vertex.Incoming* ; diff --git a/ros3rag.placeB/src/main/jastadd/RobotReachabilityToBFS.jrag b/ros3rag.placeB/src/main/jastadd/RobotReachabilityToBFS.jrag new file mode 100644 index 0000000000000000000000000000000000000000..cced6d479cb6ec7f922f67a059c895923ea9d2e3 --- /dev/null +++ b/ros3rag.placeB/src/main/jastadd/RobotReachabilityToBFS.jrag @@ -0,0 +1,62 @@ +aspect RobotReachabilityToBFS { + //--- toReachabilityGraph --- + syn nta Graph WorldModelB.toReachabilityGraph() { + Graph result = new Graph(); + if (!hasMyScene()) { + return result; + } + Map<LogicalObjectOfInterest, Vertex> mapping = new HashMap<>(); + for (LogicalDropOffLocation loc : getMyScene().getLogicalScene().getLogicalDropOffLocationList()) { + Vertex vertex = new Vertex(); + vertex.setObjectOfInterest(loc); + result.addVertex(vertex); + mapping.put(loc, vertex); + } + for (LogicalMovableObject obj : getMyScene().getLogicalScene().getLogicalMovableObjectList()) { + Vertex vertex = new Vertex(); + vertex.setObjectOfInterest(obj); + result.addVertex(vertex); + mapping.put(obj, vertex); + } + for (Robot robot : getRobotList()) { + List<LogicalObjectOfInterest> reachableLocations = robot.reachableObjects().stream() + .filter(LogicalObjectOfInterest::isLogicalDropOffLocation) + .collect(java.util.stream.Collectors.toList()); + // bidirectional edges between locations, unidirectional edges from all reachable movableobjects to locations + for (LogicalObjectOfInterest obj : robot.reachableObjects()) { + for (LogicalObjectOfInterest other : reachableLocations) { + if (obj == other) { continue; } + Edge edge = new Edge(); + edge.setRobot(robot); + edge.setBidirectional(obj.isLogicalDropOffLocation()); + edge.setFrom(mapping.get(obj)); + edge.setTo(mapping.get(other)); + result.addEdge(edge); + } + } + } + return result; + } + + //--- correspondingVertex --- + syn Optional<Vertex> LogicalObjectOfInterest.correspondingVertex() { + if (!worldModelB().hasMyScene()) { + return Optional.empty(); + } + for (Vertex v : worldModelB().toReachabilityGraph().getVertexList()) { + if (v.getObjectOfInterest().getName().equals(this.getName())) { + return Optional.of(v); + } + } + return Optional.empty(); + } +} + +aspect Printint { + public String Vertex.toString() { + return "V(" + getObjectOfInterest().getName() + ")"; + } + public String Edge.toString() { + return "E[" + getRobot().getName() + "]"; + } +} diff --git a/ros3rag.placeB/src/main/jastadd/RobotReachabilityToBFS.relast b/ros3rag.placeB/src/main/jastadd/RobotReachabilityToBFS.relast new file mode 100644 index 0000000000000000000000000000000000000000..62d0b4071475bea2960b6aed0becbad54aa9eaaf --- /dev/null +++ b/ros3rag.placeB/src/main/jastadd/RobotReachabilityToBFS.relast @@ -0,0 +1,2 @@ +rel Vertex.ObjectOfInterest -> LogicalObjectOfInterest ; +rel Edge.Robot -> Robot ; diff --git a/ros3rag.placeB/src/main/jastadd/WorldModelB.connect b/ros3rag.placeB/src/main/jastadd/WorldModelB.connect index f77374792f0d58550ceb3515913b5a5a6f802752..d2a9fab1ffc9a1e03bed700abce9002545137088 100644 --- a/ros3rag.placeB/src/main/jastadd/WorldModelB.connect +++ b/ros3rag.placeB/src/main/jastadd/WorldModelB.connect @@ -1,11 +1,17 @@ // --- receiving --- -receive tree WorldModelB.MyScene using ParseScene, ConvertScene ; -receive tree WorldModelB.OtherScene ; -receive tree Robot.CanReachObjectOfInterestWrapper using ParseReachability, ConvertReachability ; +receive WorldModelB.MyScene using ParseScene, ConvertScene ; +receive WorldModelB.OtherScene1 ; +receive WorldModelB.OtherScene2 ; +receive WorldModelB.TestingOtherScene ; +receive Robot.CanReachObjectOfInterestWrapper using ParseReachability, ConvertReachability ; // --- sending --- -send tree WorldModelB.NextOperation using PrintOperation ; +send WorldModelB.NextOperation using PrintOperation ; PrintOperation maps Operation op to byte[] {: - return op.toMergedSelection().toByteArray(); + var result = op.toMergedSelection(); + if (result == null) { + reject(); + } + return result.toByteArray(); :} diff --git a/ros3rag.placeB/src/main/jastadd/WorldModelB.jadd b/ros3rag.placeB/src/main/jastadd/WorldModelB.jadd index 047ade07f567ab5e163fb7f04bace5daad17057b..119cccc15f630c7968c7370f628de6db60e0a847 100644 --- a/ros3rag.placeB/src/main/jastadd/WorldModelB.jadd +++ b/ros3rag.placeB/src/main/jastadd/WorldModelB.jadd @@ -1,36 +1,120 @@ aspect Computation { + //--- unspecifiedLocation --- + syn nta LogicalDropOffLocation WorldModelB.unspecifiedLocation() = new LogicalDropOffLocation().setName("<unspecified>"); + + //--- getOtherSceneList --- + syn JastAddList<LogicalScene> WorldModelB.getOtherSceneList() { + JastAddList<LogicalScene> result = new JastAddList<>(); + if (hasOtherScene1()) { + result.add(getOtherScene1()); + } + if (hasOtherScene2()) { + result.add(getOtherScene2()); + } + return result; + } + + //--- mergedOtherScene --- + syn nta LogicalScene WorldModelB.mergedOtherScene() { + // return empty scene, if there are no other scenes (yet) + if (!hasOtherScene()) { + return new LogicalScene(); + } + // return scene, if there is exactly one. (TODO: check if copy is needed here) + if (getNumOtherScene() == 1) { + return getOtherScene(0); + } + var result = new LogicalScene(); + // use getOtherSceneList() and merge by name all locations, objects and relations into one new, unified scene + Map<String, LogicalDropOffLocation> mergedLocations = new HashMap<>(); + Map<String, LogicalMovableObject> mergedObjects = new HashMap<>(); + // first create all locations + for (LogicalScene scene : getOtherSceneList()) { + for (LogicalDropOffLocation loc : scene.getLogicalDropOffLocationList()) { + if (!mergedLocations.containsKey(loc.getName())) { + LogicalDropOffLocation copyOfLoc = new LogicalDropOffLocation(); + copyOfLoc.setName(loc.getName()); + mergedLocations.put(loc.getName(), copyOfLoc); + result.addLogicalDropOffLocation(copyOfLoc); + } + } + } + // then create all objects, and set LocatedAt relation (checking for possible conflicts) + for (LogicalScene scene : getOtherSceneList()) { + for (LogicalMovableObject obj : scene.getLogicalMovableObjectList()) { + if (mergedObjects.containsKey(obj.getName())) { + // object already known + LogicalMovableObject copyOfObj = mergedObjects.get(obj.getName()); + if (obj.hasLocatedAt()) { + String locationName = obj.getLocatedAt().getName(); + LogicalDropOffLocation copyOfLoc = mergedLocations.get(locationName); + if (copyOfObj.hasLocatedAt() && !obj.getLocatedAt().getName().equals(copyOfObj.getLocatedAt().getName())) { + System.err.println("Overriding located-at of " + obj.getName() + " while merging (" + locationName + "," + copyOfObj.getLocatedAt().getName() + ")"); + } + copyOfObj.setLocatedAt(copyOfLoc); + } + } else { + LogicalMovableObject copyOfObj = new LogicalMovableObject(); + copyOfObj.setName(obj.getName()); + if (obj.hasLocatedAt()) { + copyOfObj.setLocatedAt(mergedLocations.get(obj.getLocatedAt().getName())); + } + mergedObjects.put(obj.getName(), copyOfObj); + result.addLogicalMovableObject(copyOfObj); + } + } + } + return result; + } + + //--- diffScenes --- syn nta JastAddList<Difference> WorldModelB.diffScenes() { var result = new JastAddList<Difference>(); if (!hasMyScene() || !hasOtherScene()) { - // without both scenes, there are no differences + // without my and at least one other scene, there are no differences return result; } var myLogicalScene = getMyScene().getLogicalScene(); - for (LogicalMovableObject otherObject : getOtherScene().getLogicalMovableObjectList()) { + for (LogicalMovableObject otherObject : mergedOtherScene().getLogicalMovableObjectList()) { LogicalObjectOfInterest myGenericObject = myLogicalScene.resolveLogicalObjectOfInterest(otherObject.getName()); + if (myGenericObject == null) { + // new object, currently not handled + var diff = new DifferenceNewObject(); + diff.setObject(otherObject); + result.add(diff); + continue; + } if (!myGenericObject.isLogicalMovableObject()) { System.err.println("MovableObject " + otherObject.getName() + " is a location in myScene. Skipping!"); continue; } LogicalMovableObject myObject = myGenericObject.asLogicalMovableObject(); - if (myObject == null) { - // new object, currently not handled - result.add(new DifferenceNewObject()); + if (myObject.hasLocatedAt() && !otherObject.hasLocatedAt()) { + var diff = new DifferenceObjectMisplaced(); + diff.setObject(myObject); + diff.setPreviousLocation(myObject.getLocatedAt()); + result.add(diff); + continue; + } + if (!myObject.hasLocatedAt() && !otherObject.hasLocatedAt() || + (myObject.hasLocatedAt() && otherObject.hasLocatedAt() && + myObject.getLocatedAt().getName().equals(otherObject.getLocatedAt().getName()))) { + // no diff if otherObject has no location information, and if both objects are located at same location + // otherObject is always non-null, so this case does not need to be treated continue; } - if (!myObject.getLocatedAt().getName().equals(otherObject.getLocatedAt().getName())) { - // object at different locations - var difference = new DifferenceObjectAtWrongPlace(); - difference.setObject(myObject); + var difference = new DifferenceObjectAtWrongPlace(); + difference.setObject(myObject); + if (myObject.hasLocatedAt()) { difference.setPreviousLocation(myObject.getLocatedAt()); - difference.setNewLocation(otherObject.getLocatedAt()); - result.add(difference); } - // other difference not handled yet, e.g. object deleted + difference.setNewLocation(otherObject.getLocatedAt()); + result.add(difference); } return result; } + //--- diffToOperations --- syn nta JastAddList<Operation> WorldModelB.diffToOperations() { var result = new JastAddList<Operation>(); for (Difference difference : diffScenes()) { @@ -41,59 +125,41 @@ aspect Computation { return result; } + //--- computeOperations --- syn List<Operation> Difference.computeOperations(); eq DifferenceObjectAtWrongPlace.computeOperations() { - // first, find robots that can reach the locations - // there does not necessarily has to be one robot that can reach both - Set<Robot> robotsFitForPreviousLocation = new HashSet<>(); - Set<Robot> robotsFitForNewLocation = new HashSet<>(); - for (var robot : worldModelB().getRobotList()) { - if (robot.canReach(getPreviousLocation().getName())) { - robotsFitForPreviousLocation.add(robot); - } - if (robot.canReach(getNewLocation().getName())) { - robotsFitForNewLocation.add(robot); - } - } - if (robotsFitForPreviousLocation.isEmpty()) { - return Collections.singletonList(new ErrorOperation("No robot can reach previous location " + getPreviousLocation().getName())); - } - if (robotsFitForNewLocation.isEmpty()) { - return Collections.singletonList(new ErrorOperation("No robot can reach new location " + getNewLocation().getName())); - } - var result = new ArrayList<Operation>(); - var robotFitForBothLocations = robotsFitForPreviousLocation.stream() - .filter(robotsFitForNewLocation::contains) - .findFirst(); - robotFitForBothLocations.ifPresentOrElse(robot -> { - // there is at least one robot that can reach both locations - result.add(createPickAndPlace(robot, this.getObject(), this.getNewLocation())); - }, - () -> { - // no single robot can reach both locations - // find a location, that can be used as exchange point reached by two robots - for (var robotForPrevious : robotsFitForPreviousLocation) { - for (var robotForNew : robotsFitForNewLocation) { - for (var canReachOfRobotForPrevious : robotForPrevious.getCanReachObjectOfInterestWrapper().getCanReachObjectOfInterestList()) { - String objectName = canReachOfRobotForPrevious.getObjectName(); - if (resolveLogicalObjectOfInterest(objectName).isLogicalDropOffLocation() && robotForNew.canReach(objectName)) { - // add two operations. - // obj -> transitLoc by robotForPrevious - // obj -> targetLoc by robotForNew - result.add(createPickAndPlace(robotForPrevious, this.getObject(), resolveLogicalObjectOfInterest(objectName).asLogicalDropOffLocation())); - result.add(createPickAndPlace(robotForNew, this.getObject(), this.getNewLocation())); - } + return (hasPreviousLocation() ? getPreviousLocation() : getObject()).correspondingVertex().map(previousVertex -> { + return getNewLocation().correspondingVertex().map(newVertex -> { + List<Edge> shortestPath = previousVertex.BFS(newVertex); + if (shortestPath == null || shortestPath.isEmpty()) { + return error("No sequence of operations to move " + getObject().getName() + (hasPreviousLocation() ? " from " + getPreviousLocation().getName() : "") + " to " + getNewLocation().getName()); } - } - } - }); - return result; + List<Operation> result = new ArrayList<>(); + Vertex transit = previousVertex; + for (Edge edge : shortestPath) { + Vertex target = edge.getFrom().equals(transit) ? edge.getTo() : edge.getFrom(); + result.add(createPickAndPlace(edge.getRobot(), getObject(), target.getObjectOfInterest().asLogicalDropOffLocation())); + transit = target; + } + return result; + }).orElseGet(() -> error("Could not resolve graph vertex of new location " + getNewLocation().getName())); + }).orElseGet(() -> error("Could not resolve graph vertex of previous location " + getPreviousLocation().getName())); } eq DifferenceNewObject.computeOperations() { // FIXME. stub, may be implemented later return Collections.emptyList(); } + eq DifferenceObjectMisplaced.computeOperations() { + // FIXME. stub, may be implemented later + return Collections.emptyList(); + } + //--- error --- + protected static List<Operation> Difference.error(String message) { + return Collections.singletonList(new ErrorOperation(message)); + } + + //--- createPickAndPlace --- private static Operation DifferenceObjectAtWrongPlace.createPickAndPlace(Robot robot, LogicalMovableObject obj, LogicalDropOffLocation target) { var result = new PickAndPlace(); result.setRobotToExecute(robot); @@ -102,12 +168,20 @@ aspect Computation { return result; } + //--- getNextOperation --- syn Operation WorldModelB.getNextOperation() { - return diffToOperations().getNumChild() > 0 ? diffToOperations().getChild(0) : new ErrorOperation("No operation computed!"); + if (diffToOperations().getNumChild() == 0) { + return new ErrorOperation("No operation computed!"); + } + for (Operation op : diffToOperations()) { + if (!op.isErrorOperation()) { + return op; + } + } + return new ErrorOperation("No executable operation found!"); } -// syn List<String> locationIds - + //--- canReach --- syn boolean Robot.canReach(String objectName) { for (var canReachObj : getCanReachObjectOfInterestWrapper().getCanReachObjectOfInterestList()) { if (canReachObj.getObjectName().equals(objectName)) { @@ -116,6 +190,18 @@ aspect Computation { } return false; } + + //--- reachableObjects --- + syn List<LogicalObjectOfInterest> Robot.reachableObjects() { + if (!worldModelB().hasMyScene()) { + return Collections.emptyList(); + } + List<LogicalObjectOfInterest> result = new ArrayList<>(); + for (var canReachObj : getCanReachObjectOfInterestWrapper().getCanReachObjectOfInterestList()) { + result.add(worldModelB().getMyScene().getLogicalScene().resolveLogicalObjectOfInterest(canReachObj.getObjectName())); + } + return result; + } } //aspect RelationsByReference { @@ -129,9 +215,11 @@ aspect Computation { //} aspect AttributeMappings { + //--- toMergedSelection --- syn de.tudresden.inf.st.ceti.MergedSelection Operation.toMergedSelection(); eq ErrorOperation.toMergedSelection() { - throw new RuntimeException(getErrorMessage()); + System.err.println(getErrorMessage()); + return null; } eq Pick.toMergedSelection() { throw new RuntimeException("Separate Pick operation not supported yet!"); @@ -140,37 +228,57 @@ aspect AttributeMappings { throw new RuntimeException("Separate Place operation not supported yet!"); } eq PickAndPlace.toMergedSelection() = de.tudresden.inf.st.ceti.MergedSelection.newBuilder() + .setIdRobot(getRobotToExecute().getName()) .setIdPick(getObjectToPick().getName()) .setIdPlace(getTargetLocation().getName()) .build(); } aspect Navigation { + //--- worldModelB --- inh WorldModelB ASTNode.worldModelB(); eq WorldModelB.getChild().worldModelB() = this; + syn boolean Operation.isErrorOperation() = false; + eq ErrorOperation.isErrorOperation() = true; } aspect GlueForShared { + //--- resolveLogicalObjectOfInterest --- // uncache Difference.resolveLogicalObjectOfInterest inh LogicalObjectOfInterest Difference.resolveLogicalObjectOfInterest(String name); eq WorldModelB.diffScenes().resolveLogicalObjectOfInterest(String name) = getMyScene().getLogicalScene().resolveLogicalObjectOfInterest(name); + + class WorldModelB implements de.tudresden.inf.st.ros3rag.common.SharedMainParts.WorldModelWrapper {} } aspect Printing { - syn String Robot.toString() = "~Robot " + getName() + "~"; + //--- prettyPrint --- + syn String Robot.prettyPrint() = "~Robot " + getName() + "~"; - syn String DifferenceObjectAtWrongPlace.toString() { - return "+DifferenceObjectAtWrongPlace of " + getObject() + ": " + getPreviousLocation() + " -> " + getNewLocation() + "+"; + syn String Difference.prettyPrint(); + eq DifferenceObjectAtWrongPlace.prettyPrint() { + return "-DifferenceObjectAtWrongPlace of " + getObject().prettyPrint() + ": " + (hasPreviousLocation() ? getPreviousLocation().prettyPrint() : "_") + " -> " + getNewLocation().prettyPrint() + "-"; } - syn String PickAndPlace.toString() { - return "+PickAndPlace by " + getRobotToExecute() + " of " + getObjectToPick() + " -> " + getTargetLocation() + "+"; + eq DifferenceNewObject.prettyPrint() { + return "-DifferenceNewObject of " + getObject().prettyPrint() + "-"; + } + eq DifferenceObjectMisplaced.prettyPrint() { + return "-DifferenceObjectMisplaced of " + getObject().prettyPrint() + ": " + getPreviousLocation().prettyPrint() + "-"; + } + + syn String Operation.prettyPrint(); + eq PickAndPlace.prettyPrint() { + return "+PickAndPlace by " + getRobotToExecute().prettyPrint() + " of " + getObjectToPick().prettyPrint() + " -> " + getTargetLocation().prettyPrint() + "+"; } - syn String ErrorOperation.toString() { + eq Pick.prettyPrint() = "+Pick not implemented+"; + eq Place.prettyPrint() = "+Place not implemented+"; + eq ErrorOperation.prettyPrint() { return "+Error: " + getErrorMessage() + "+"; } } aspect Resolving { + //--- findRobot --- public Optional<Robot> WorldModelB.findRobot(String name) { for (Robot robot : getRobotList()) { if (robot.getName().equals(name)) { diff --git a/ros3rag.placeB/src/main/jastadd/WorldModelB.relast b/ros3rag.placeB/src/main/jastadd/WorldModelB.relast index 6955828c67b6111c63c6d310571b6360a18d3ba2..f2bc3600e6bc36d75cadc98b8f6100cc8cdaa220 100644 --- a/ros3rag.placeB/src/main/jastadd/WorldModelB.relast +++ b/ros3rag.placeB/src/main/jastadd/WorldModelB.relast @@ -1,13 +1,16 @@ -WorldModelB ::= Robot* [MyScene:Scene] [OtherScene:LogicalScene] /NextOperation:Operation/ ; +WorldModelB ::= Robot* [MyScene:Scene] [OtherScene1:LogicalScene] [OtherScene2:LogicalScene] /OtherScene:LogicalScene*/ /NextOperation:Operation/ TestingOtherScene:LogicalScene* ; +// workaround with NTA OtherSceneList as receiving lists is not possible yet Robot ::= <Name:String> CanReachObjectOfInterestWrapper ; abstract Difference ; +rel Difference.Object -> LogicalMovableObject ; DifferenceObjectAtWrongPlace : Difference ; -rel DifferenceObjectAtWrongPlace.Object -> LogicalMovableObject ; -rel DifferenceObjectAtWrongPlace.PreviousLocation -> LogicalDropOffLocation ; +rel DifferenceObjectAtWrongPlace.PreviousLocation? -> LogicalDropOffLocation ; rel DifferenceObjectAtWrongPlace.NewLocation -> LogicalDropOffLocation ; DifferenceNewObject : Difference ; +DifferenceObjectMisplaced : Difference ; +rel DifferenceObjectMisplaced.PreviousLocation -> LogicalDropOffLocation ; abstract Operation ; rel Operation.RobotToExecute? -> Robot ; diff --git a/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/MainB.java b/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/MainB.java index 5548372b3bf8ec3a29bbfeab62ee46e292611885..0c193b3e77021dd6d6bcff534443a1d197d269d3 100644 --- a/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/MainB.java +++ b/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/MainB.java @@ -3,16 +3,12 @@ package de.tudresden.inf.st.placeB; import de.tudresden.inf.st.ceti.Reachability; import de.tudresden.inf.st.placeB.ast.*; import de.tudresden.inf.st.ros3rag.common.Configuration; +import de.tudresden.inf.st.ros3rag.common.SharedMainParts; import de.tudresden.inf.st.ros3rag.common.Util; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import java.io.File; -import java.nio.charset.StandardCharsets; +import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import static de.tudresden.inf.st.ros3rag.common.Util.mqttUri; import static de.tudresden.inf.st.ros3rag.common.Util.readScene; @@ -22,49 +18,50 @@ import static de.tudresden.inf.st.ros3rag.common.Util.readScene; * * @author rschoene - Initial contribution */ -public class MainB { - private static final Logger logger = LogManager.getLogger(SimpleMainB.class); - - private final static String TOPIC_MODEL = "place-b/model"; - private final static String TOPIC_STATUS = "place-b/status"; - private final static String TOPIC_EXIT = "place-b/exit"; - - private final static String TOPIC_MY_SCENE_INIT = "place-b/init"; - private final static String TOPIC_MY_SCENE_UPDATE_FROM_ROS = "place-b/scene/update"; - private final static String TOPIC_COMMANDS = "place-b/commands"; - - private final static String TOPIC_OTHER_SCENE_UPDATE_FROM_PLACE_A = "place-a/logical/update"; - - private MqttHandler mainHandler; - private WorldModelB model; - - public static void main(String[] args) throws Exception { - new MainB().run(args); +public class MainB extends SharedMainParts<MqttHandler, WorldModelB> { + private final String TOPIC_ROBOT_REACHABILITY; + private final String TOPIC_MY_SCENE_UPDATE_FROM_ROS; + private final String TOPIC_COMMAND; + private final String TOPIC_OTHER_SCENE_UPDATE_FROM_PLACE_A; + private final String TOPIC_DEMO_MOVE_objectRed1_RED; + + MainB(String configFile) { + super("place-b", UtilB.pathToDirectoryOfPlaceB().resolve(configFile)); + this.TOPIC_ROBOT_REACHABILITY = cellName + "/reachability/%s"; + this.TOPIC_MY_SCENE_UPDATE_FROM_ROS = cellName + "/scene/update"; + this.TOPIC_COMMAND = cellName + "/command"; + + this.TOPIC_OTHER_SCENE_UPDATE_FROM_PLACE_A = "place-a/logical/update"; + + this.TOPIC_DEMO_MOVE_objectRed1_RED = cellName + "/demo/move/objectRed1/red"; } - private void run(String[] args) throws Exception { + public static void main(String[] args) throws Exception { String configFile = args.length == 0 ? "src/main/resources/config-b.yaml" : args[0]; + new MainB(configFile).run(); + } - // --- No configuration below this line --- - - final var config = Util.parseConfig(UtilB.pathToDirectoryOfPlaceB().resolve(configFile).toFile()); - model = new WorldModelB(); - - /// Prepare main handler - mainHandler = new MqttHandler("mainHandler").dontSendWelcomeMessage(); - mainHandler.setHost(config.mqttHost); - mainHandler.waitUntilReady(2, TimeUnit.SECONDS); - CountDownLatch exitCondition = new CountDownLatch(1); - mainHandler.newConnection(TOPIC_EXIT, bytes -> exitCondition.countDown()); - mainHandler.newConnection(TOPIC_MODEL, bytes -> logStatus(new String(bytes))); + @Override + protected void createSpecificMainHandlerConnections() { + mainHandler.newConnection(TOPIC_DEMO_MOVE_objectRed1_RED, bytes -> + UtilB.updatePositionOfObjectToLocation(model.getMyScene(), "objectRed1", "binRed") + ); + } + @Override + protected de.tudresden.inf.st.ceti.Scene readSceneAndRobots() throws Exception { /// Reading scene and robots de.tudresden.inf.st.ceti.Scene scene = readScene( UtilB.pathToDirectoryOfPlaceB().resolve(config.filenameInitialScene) ); Scene myScene = UtilB.convert(scene); model.setMyScene(myScene); - Util.extractRobotNames(scene).forEach(name -> model.addRobot(UtilB.createRobot(name))); + for (String name : Util.extractRobotNames(scene)) { + Robot robot = UtilB.createRobot(name); + model.addRobot(robot); + // assumption: robots do not change during runtime, so we have stable connections + robot.connectCanReachObjectOfInterestWrapper(mqttUri(String.format(TOPIC_ROBOT_REACHABILITY, name), config)); + } /// Set (dummy) reachability, if configured if (config.useReachability) { @@ -79,41 +76,29 @@ public class MainB { } } - /// Setup model connection - model.ragconnectCheckIncremental(); - model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + return scene; + } - /// Connect endpoints + @Override + protected void connectEndpoints() throws IOException { model.connectMyScene(mqttUri(TOPIC_MY_SCENE_UPDATE_FROM_ROS, config)); - model.connectOtherScene(mqttUri(TOPIC_OTHER_SCENE_UPDATE_FROM_PLACE_A, config)); - model.connectNextOperation(mqttUri(TOPIC_COMMANDS, config), false); - - logStatus("Start"); - - logger.info("To print the current model states, send a message to the topic '{}'.", TOPIC_MODEL); - logger.info("To exit the system cleanly, send a message to the topic '{}', or use Ctrl+C.", TOPIC_EXIT); - -// mainHandler.publish(mqttUri(TOPIC_SCENE_INIT, config), scene.toByteArray()); - logger.fatal("Skipping publishing to {} for now", TOPIC_MY_SCENE_INIT); - - Runtime.getRuntime().addShutdownHook(new Thread(this::close)); + model.connectOtherScene1(mqttUri(TOPIC_OTHER_SCENE_UPDATE_FROM_PLACE_A, config)); + model.connectNextOperation(mqttUri(TOPIC_COMMAND, config), false); + } - exitCondition.await(); + @Override + protected MqttHandler createMqttHandler() { + return new MqttHandler("mainHandlerB"); } - private void logStatus(String message) { - logger.info(message); - String content = UtilB.getModelInfos(model); - logger.info("WorldModelB\n{}", content); - if (mainHandler != null) { - mainHandler.publish(TOPIC_STATUS, content.getBytes(StandardCharsets.UTF_8)); - } + @Override + protected WorldModelB createWorldModel() { + return new WorldModelB(); } - private void close() { - logger.info("Exiting ..."); - mainHandler.close(); - model.ragconnectCloseConnections(); + @Override + protected String getModelInfos(WorldModelB worldModelB, boolean detailed) { + return UtilB.getModelInfos(model, detailed); } } diff --git a/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/SimpleMainB.java b/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/SimpleMainB.java index 20b29d23ca5e2cb583a4e6ae15616304d26507c4..b8023e726595aff10197fecbb8d07bc1850b3e5e 100644 --- a/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/SimpleMainB.java +++ b/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/SimpleMainB.java @@ -1,17 +1,21 @@ package de.tudresden.inf.st.placeB; -import com.google.protobuf.util.JsonFormat; import de.tudresden.inf.st.ceti.Reachability; import de.tudresden.inf.st.placeB.ast.*; +import de.tudresden.inf.st.ros3rag.common.Configuration; import de.tudresden.inf.st.ros3rag.common.Util; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static de.tudresden.inf.st.ros3rag.common.Util.mqttUri; +import static de.tudresden.inf.st.ros3rag.common.Util.readScene; /** * Testing features for placeB. @@ -30,10 +34,183 @@ public class SimpleMainB { private void run(String[] args) throws Exception { // testBuildModelB(); // testReceivingModelB(); - testReachability(); +// testReachability(); +// testWeirdBehaviour(); +// testReachability(); +// testBFS(); + testRobotReachabilityBFS(); + } + + private void testRobotReachabilityBFS() throws Exception { + WorldModelB model = new WorldModelB(); + de.tudresden.inf.st.ceti.Scene scene = readScene( + UtilB.pathToDirectoryOfPlaceB().resolve("src/main/resources/config-scene-b.json") + ); + Scene myScene = UtilB.convert(scene); + model.setMyScene(myScene); + for (String name : Util.extractRobotNames(scene)) { + Robot robot = UtilB.createRobot(name); + model.addRobot(robot); + String filenameReachability = "src/main/resources/dummy-reachability-b-" + name + ".json"; + Path path = UtilB.pathToDirectoryOfPlaceB().resolve(Paths.get(filenameReachability)); + Reachability reachability = UtilB.readReachability(path); + CanReachObjectOfInterestWrapper reachabilityWrapper = UtilB.convert(reachability); + robot.setCanReachObjectOfInterestWrapper(reachabilityWrapper); + } + + printReachability(model); + printShortestPath(model, "binRed", "binGreen"); + printShortestPath(model, "binBlue", "binYellow"); + printShortestPath(model, "binRed", "binPurple"); + } + + private void printReachability(WorldModelB model) { + System.out.println("ModelInfos:"); + System.out.println(UtilB.getModelInfos(model, true)); + + System.out.println("Reachability:"); + model.getRobotList().forEach(r -> System.out.println(r.getName() + ": " + r.reachableObjects().stream().map(LogicalObjectOfInterest::getName).collect(Collectors.joining(", ")))); + + System.out.println("ReachabilityGraph:"); + System.out.println(model.toReachabilityGraph().prettyPrint()); + } + + private void printShortestPath(WorldModelB model, String source, String target) { + LogicalDropOffLocation sourceLocation = model.getMyScene().getLogicalScene() + .resolveLogicalObjectOfInterest(source).asLogicalDropOffLocation(); + LogicalDropOffLocation targetLocation = model.getMyScene().getLogicalScene() + .resolveLogicalObjectOfInterest(target).asLogicalDropOffLocation(); + + List<Edge> bfs = sourceLocation.correspondingVertex().orElseThrow().BFS(targetLocation.correspondingVertex().orElseThrow()); + + System.out.println("Shortest path from " + sourceLocation.getName() + " to " + targetLocation.getName() + ": " + bfs); + } + + private void testBFS() { + /* + A B + | \ | + C - D + */ + Graph g = new Graph(); + Vertex a = makeVertex("a"); + Vertex b = makeVertex("b"); + Vertex c = makeVertex("c"); + Vertex d = makeVertex("d"); + Edge ac = makeEdge(a, c); + Edge ad = makeEdge(a, d); + Edge cd = makeEdge(c, d); + Edge db = makeEdge(d, b); + g.addVertex(a); + g.addVertex(b); + g.addVertex(c); + g.addVertex(d); + g.addEdge(ac); + g.addEdge(ad); + g.addEdge(cd); + g.addEdge(db); + + System.out.println(a.BFS(b)); + } + + private Vertex makeVertex(String name) { + return new Vertex() { + @Override + public String toString() { + return name; + } + }; + } + + private Edge makeEdge(Vertex from, Vertex to) { + Edge result = new Edge(); + result.setFrom(from); + result.setTo(to); + return result; + } + + private void testDistance() throws Exception { + de.tudresden.inf.st.ceti.Scene scene = readScene( + UtilB.pathToDirectoryOfPlaceB().resolve("src/main/resources/config-scene-b.json") + ); + + Scene myScene = UtilB.convert(scene); + float arm1X = 0f; + float arm1Y = 0f; + float arm1Z = 0.75f; + + float arm2X = 1f; + float arm2Y = 0f; + float arm2Z = 0.75f; + + for (var location : myScene.getDropOffLocationList()) { + check(arm1X, arm1Y, arm1Z, location.getPosition(), "arm1", location.getName()); + check(arm2X, arm2Y, arm2Z, location.getPosition(), "arm2", location.getName()); + } + } + + private void check(float x, float y, float z, Position position, String robotName, String locationName) { + float dx = x - position.getX(); + float dy = y - position.getY(); + float dz = z - position.getZ(); + + if (Math.sqrt(dx*dx + dy*dy) < 0.05) { + System.out.println(robotName + " too close to " + locationName); + } + if (Math.sqrt(dx*dx + dy*dy + dz*dz) > 0.75) { + System.out.println(robotName + " too far away from " + locationName); + } + } + + private void testWeirdBehaviour() throws IOException { + WorldModelB model = new WorldModelB(); + Scene scene = new Scene(); + + MovableObject obj1 = new MovableObject(); + obj1.setPosition(Position.of(1, 1, 1)); + obj1.setOrientation(Orientation.of(0, 0, 0, 0)); + obj1.setSize(Size.of(1, 1, 1)); + scene.addMovableObject(obj1); + + DropOffLocation loc = new DropOffLocation(); + loc.setPosition(Position.of(1, 1, 1)); + loc.setOrientation(Orientation.of(0, 0, 0, 0)); + loc.setSize(Size.of(1, 1, 1)); + scene.addDropOffLocation(loc); + + model.setMyScene(scene); + + Configuration config = new Configuration(); + config.mqttHost = "localhost"; + /// Setup model connection + model.ragconnectCheckIncremental(); + model.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + /// Connect endpoints + model.connectMyScene(mqttUri("TOPIC_MY_SCENE_UPDATE_FROM_ROS", config)); + model.connectOtherScene1(mqttUri("TOPIC_OTHER_SCENE_UPDATE_FROM_PLACE_A", config)); + model.connectNextOperation(mqttUri("TOPIC_COMMANDS", config), false); + + System.out.println("before"); + System.out.println(scene.prettyPrint()); + + obj1.dumpDependencies(); + + Position evilPosition = Position.of(99, 99, 99); + obj1.setPosition(evilPosition); + + System.out.println("after"); + obj1.dumpDependencies(); + + System.out.println(scene.prettyPrint()); + System.out.println(scene.prettyPrint()); + + obj1.dumpDependencies(); + + model.ragconnectCloseConnections(); } - private void testReachability() throws Exception { + private void testDummyReachability() throws Exception { WorldModelB model = new WorldModelB(); Robot arm1 = UtilB.createRobot("ARM1"); model.addRobot(arm1); @@ -87,7 +264,7 @@ public class SimpleMainB { LogicalDropOffLocation otherGamma = new LogicalDropOffLocation().setName("placeGamma"); otherGamma.addContainedObject(otherObj1); otherScene.addLogicalDropOffLocation(otherGamma); - model.setOtherScene(otherScene); + model.setOtherScene1(otherScene); // printing and testing printModelInfos(model); diff --git a/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/UtilB.java b/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/UtilB.java index 80b67ac249e4fefbae14e04e19d965c63a6877b2..64b88e1562b3a5b6739aaefad95214e0ca3d2119 100644 --- a/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/UtilB.java +++ b/ros3rag.placeB/src/main/java/de/tudresden/inf/st/placeB/UtilB.java @@ -1,7 +1,6 @@ package de.tudresden.inf.st.placeB; import com.google.protobuf.util.JsonFormat; -import de.tudresden.inf.st.ceti.Object; import de.tudresden.inf.st.ceti.Reachability; import de.tudresden.inf.st.placeB.ast.*; import de.tudresden.inf.st.ros3rag.common.Util; @@ -9,12 +8,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; /** * Static utility methods used only for place B. @@ -47,12 +42,73 @@ public class UtilB { return result; } + static void updatePositionOfObjectToLocation(Scene scene, String objName, String locationName) { + ObjectOfInterest obj = scene.resolveObjectOfInterest(objName); + ObjectOfInterest location = scene.resolveObjectOfInterest(locationName); + if (obj != null && location != null) { + // move objectRed1 to binBlue + logger.info("Got " + obj + " and location " + location); + logger.debug("before to {} at {}\n{}", locationName, location.getPosition(), + scene.prettyPrint()); + logger.debug("X={}, pos.prettyPrint={}, obj.prettyPrint={}", + obj.getPosition().getX(), + obj.getPosition().prettyPrint(), + ((MovableObject) obj).prettyPrint()); + + obj.dumpDependencies(); + + obj.setPosition(Position.of(location.getPosition().getX(), + location.getPosition().getY(), + location.getPosition().getZ())); + + logger.debug("after"); + obj.dumpDependencies(); + + logger.debug(scene.prettyPrint()); + logger.debug("X={}, pos.prettyPrint={}, obj.prettyPrint={}", + obj.getPosition().getX(), + obj.getPosition().prettyPrint(), + ((MovableObject) obj).prettyPrint()); + + obj.dumpDependencies(); + } else { + logger.error("Obj (" + obj + ") or location (" + location + ") are null"); + } + } + + private static String resolveObjName(WorldModelB model, String objName) { + if (!model.hasMyScene()) { + return "\"" + objName + "\""; + } + try { + return model.getMyScene().getLogicalScene().resolveLogicalObjectOfInterest(objName).toString(); + } catch (NullPointerException ignore) { + return "+" + objName + "(not resolved)+"; + } + } + static String getModelInfos(WorldModelB model) { + return getModelInfos(model, false); + } + + static String getModelInfos(WorldModelB model, boolean detailed) { StringBuilder sb = new StringBuilder(); sb.append("myRobots: ") .append(model.getRobotList().prettyPrint( - robot -> robot.getName() + "(canReach: " + robot.getCanReachObjectOfInterestWrapper().getCanReachObjectOfInterestList().prettyPrint(CanReachObjectOfInterest::getObjectName) + ")")) + robot -> robot.getName() + "(canReach: " + robot.getCanReachObjectOfInterestWrapper() + .getCanReachObjectOfInterestList() + .prettyPrint(canReachObj -> resolveObjName(model, canReachObj.getObjectName())) + + ")")) .append("\n"); + if (detailed) { + // also include "normal" scene + sb.append("myScene:"); + if (model.hasMyScene()) { + sb.append("\n").append(model.getMyScene().prettyPrint()); + } else { + sb.append(" (unset)\n"); + } + } sb.append("myLogicalScene:"); if (model.hasMyScene()) { sb.append("\n").append(model.getMyScene().getLogicalScene().prettyPrint()); @@ -61,13 +117,13 @@ public class UtilB { } sb.append("otherScene:"); if (model.hasOtherScene()) { - sb.append("\n").append(model.getOtherScene().prettyPrint()); + sb.append("\n").append(model.mergedOtherScene().prettyPrint()); } else { sb.append(" (unset)\n"); } - sb.append("Diff: ").append(model.diffScenes().prettyPrint()).append("\n"); - sb.append("Operations: ").append(model.diffToOperations().prettyPrint()).append("\n"); - sb.append("Next operation: ").append(model.getNextOperation()).append("\n"); + sb.append("Diff: ").append(model.diffScenes().prettyPrint(Difference::prettyPrint)).append("\n"); + sb.append("Operations: ").append(model.diffToOperations().prettyPrint(Operation::prettyPrint)).append("\n"); + sb.append("Next operation: ").append(model.getNextOperation().prettyPrint()).append("\n"); return sb.toString(); } @@ -82,10 +138,10 @@ public class UtilB { @SuppressWarnings("rawtypes") static class ExposingASTNode extends ASTNode { public Scene exposed_apply_ConvertScene(de.tudresden.inf.st.ceti.Scene pbScene) throws Exception { - return ASTNode._apply_ConvertScene(pbScene); + return ASTNode._ragconnect__apply_ConvertScene(pbScene); } public CanReachObjectOfInterestWrapper exposed_apply_ConvertReachability(de.tudresden.inf.st.ceti.Reachability r) throws Exception { - return ASTNode._apply_ConvertReachability(r); + return ASTNode._ragconnect__apply_ConvertReachability(r); } } diff --git a/ros3rag.placeB/src/main/resources/config-b.yaml b/ros3rag.placeB/src/main/resources/config-b.yaml index 91aa9417a2e917f521e4a87944d9f96efd78cb34..657f5ba3915b109991428609720b061f7aa45f12 100644 --- a/ros3rag.placeB/src/main/resources/config-b.yaml +++ b/ros3rag.placeB/src/main/resources/config-b.yaml @@ -1,8 +1,11 @@ mqttHost: "localhost" filenameInitialScene: "src/main/resources/config-scene-b.json" -useReachability: true +useReachability: false reachability: - idRobot: "arm1" filename: "src/main/resources/dummy-reachability-b-arm1.json" - idRobot: "arm2" filename: "src/main/resources/dummy-reachability-b-arm2.json" + - idRobot: "arm3" + filename: "src/main/resources/dummy-reachability-b-arm3.json" +coordinatorMqttTopicPrefix: "coordinating/rag-b" diff --git a/ros3rag.placeB/src/main/resources/config-scene-b.json b/ros3rag.placeB/src/main/resources/config-scene-b.json index f14e17fe05da23db662d514b49ae47d406a0ddd4..7cec4ff886a6b3c30d5e97f135fbaf5def049061 100644 --- a/ros3rag.placeB/src/main/resources/config-scene-b.json +++ b/ros3rag.placeB/src/main/resources/config-scene-b.json @@ -4,18 +4,23 @@ { "id": "tablePillar3","pos": { "x": -0.77,"y": 0.77,"z": 0.325 },"size": { "length": 0.06,"width": 0.06,"height": 0.65 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } }, { "id": "tablePillar4","pos": { "x": 0.77,"y": -0.77,"z": 0.325 },"size": { "length": 0.06,"width": 0.06,"height": 0.65 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } }, { "id": "table","pos": { "z": 0.7 },"size": { "length": 1.6,"width": 1.6,"height": 0.1 },"orientation": { "w": 1 },"color": { "r": 255,"g": 222,"b": 173 } }, - { "id": "binBlue","type": "BIN","pos": { "x": -0.34,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "b": 1 } }, - { "id": "binRed","type": "BIN","pos": { "x": 0.06,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "r": 1 } }, - { "id": "binGreen","type": "BIN","pos": { "x": 0.46,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "g": 1 } }, + { "id": "binBlue","type": "DROP_OFF_LOCATION","pos": { "x": 1.34,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "b": 1 } }, + { "id": "binRed","type": "DROP_OFF_LOCATION","pos": { "x": 0.06,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "r": 1 } }, + { "id": "binGreen","type": "DROP_OFF_LOCATION","pos": { "x": 0.46,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "g": 1 } }, + { "id": "binYellow","type": "DROP_OFF_LOCATION","pos": { "x": 1.46,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "r": 1, "g": 1 } }, + { "id": "binPurple","type": "DROP_OFF_LOCATION","pos": { "x": 2.46,"y": 0.49,"z": 0.8325 },"size": { "length": 0.21,"width": 0.3,"height": 0.165 },"orientation": { "w": 1 },"color": { "r": 1, "b": 1 } }, { "id": "objectRed1","type": "BOX","pos": { "x": 0.5,"y": -0.1,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "r": 1 } }, { "id": "objectRed2","type": "BOX","pos": { "x": 0.25,"y": -0.2,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "w": 1 },"color": { "r": 1 } }, { "id": "objectRed3","type": "BOX","pos": { "x": -0.4,"z": 0.819 },"size": { "length": 0.031,"width": 0.031,"height": 0.138 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "r": 1 } }, { "id": "objectGreen1","type": "BOX","pos": { "x": 0.45,"y": -0.3,"z": 0.8105 },"size": {"length":0.031,"width":0.062,"height":0.121},"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "g": 1 } }, - { "id": "objectGreen2","type": "BOX","pos": { "x": -0.45,"y": -0.2,"z": 0.8105 },"size": {"length":0.031,"width":0.031,"height":0.138},"orientation": { "z": 1,"w": 0.92388 },"color": { "g": 1 } }, + { "id": "objectGreen2","type": "BOX","pos": { "x": -0.45,"y": -0.2,"z": 0.8105 },"size": {"length":0.031,"width":0.031,"height":0.138},"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "g": 1 } }, { "id": "objectGreen3","type": "BOX","pos": { "x": 0.1,"y": -0.3,"z": 0.819 },"size": {"length":0.031,"width":0.062,"height":0.121},"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "g": 1 } }, { "id": "objectBlue1","type": "BOX","pos": { "x": 0.25,"y": -0.4,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "b": 1 } }, { "id": "objectBlue2","type": "BOX","pos": { "x": -0.3,"y": -0.3,"z": 0.8105 },"size": { "length": 0.031,"width": 0.062,"height": 0.121 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "b": 1 } }, { "id": "objectBlue3","type": "BOX","pos": { "x": 0.4,"z": 0.819 },"size": { "length": 0.031,"width": 0.031,"height": 0.138 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "b": 1 } }, + { "id": "objectYellow1","type": "BOX","pos": { "x": 0.4,"z": 0.819 },"size": { "length": 0.031,"width": 0.031,"height": 0.138 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "r": 1, "g": 1 } }, + { "id": "objectPurple1","type": "BOX","pos": { "x": 0.3,"z": 0.819 },"size": { "length": 0.031,"width": 0.031,"height": 0.138 },"orientation": { "z": 0.382683,"w": 0.92388 },"color": { "r": 1, "b": 1 } }, { "id": "arm1","type": "ARM","pos": { "z": 0.75 },"size": { },"orientation": { "w": 1 },"color": { "r": 1.00,"g": 1.00,"b": 1.00 } }, - { "id": "arm2","type": "ARM","pos": { "x": 1, "z": 0.75 },"size": { },"orientation": { "w": 1 },"color": { "r": 1.00,"g": 1.00,"b": 1.00 } } + { "id": "arm2","type": "ARM","pos": { "x": 1, "z": 0.75 },"size": { },"orientation": { "w": 1 },"color": { "r": 1.00,"g": 1.00,"b": 1.00 } }, + { "id": "arm3","type": "ARM","pos": { "x": 2, "z": 0.75 },"size": { },"orientation": { "w": 1 },"color": { "r": 1.00,"g": 1.00,"b": 1.00 } } ] } diff --git a/ros3rag.placeB/src/main/resources/dummy-reachability-b-arm3.json b/ros3rag.placeB/src/main/resources/dummy-reachability-b-arm3.json new file mode 100644 index 0000000000000000000000000000000000000000..9e8b59b043810181586d8b2515f46a43d8d3f56f --- /dev/null +++ b/ros3rag.placeB/src/main/resources/dummy-reachability-b-arm3.json @@ -0,0 +1,13 @@ +{ + "idRobot": "ARM3", + "objects": [ + { "idObject": "objectRed1", "reachable": true }, + { "idObject": "objectRed2", "reachable": true }, + { "idObject": "objectRed3", "reachable": false }, + { "idObject": "binRed", "reachable": false }, + { "idObject": "binBlue", "reachable": false }, + { "idObject": "binGreen", "reachable": true }, + { "idObject": "binYellow", "reachable": false }, + { "idObject": "binPurple", "reachable": true } + ] +} diff --git a/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestComputeOperations.java b/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestComputeOperations.java new file mode 100644 index 0000000000000000000000000000000000000000..fe53722780e481e0c6c086de7e4c4374205cea1d --- /dev/null +++ b/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestComputeOperations.java @@ -0,0 +1,175 @@ +package de.tudresden.inf.st.placeB; + +import de.tudresden.inf.st.placeB.ast.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.List; + +import static de.tudresden.inf.st.placeB.TestUtils.*; +import static de.tudresden.inf.st.placeB.TestUtils.addMyObjects; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Testing computing operations from differences. + * + * @author rschoene - Initial contribution + */ +public class TestComputeOperations { + + @ParameterizedTest(name = "testNoMovePossible [emptyPreviousLocation = {argumentsWithNames}]") + @ValueSource(booleans = {true, false}) + public void testNoMovePossible(boolean emptyPreviousLocation) { + WorldModelB model = newModel(); + addMyObjects(model, "x"); + addMyLocations(model, "red", "blue", "green"); + addRobots(model, "arm1", "arm2"); + addReachability(model, "arm1", "red", "blue", "x"); + addReachability(model, "arm2", "blue"); + + LogicalScene logicalScene = model.getMyScene().getLogicalScene(); + LogicalMovableObject x = logicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject(); + LogicalDropOffLocation red = logicalScene.resolveLogicalObjectOfInterest("red").asLogicalDropOffLocation(); + LogicalDropOffLocation green = logicalScene.resolveLogicalObjectOfInterest("green").asLogicalDropOffLocation(); + DifferenceObjectAtWrongPlace diff = createDifferenceObjectAtWrongPlace(x, emptyPreviousLocation ? null : red, green); + + List<Operation> operations = diff.computeOperations(); + assertThat(operations).isNotEmpty(); + assertThat(operations).hasSize(1); + Operation op = operations.get(0); + assertTrue(op.isErrorOperation()); + assertEquals(emptyPreviousLocation ? + "+Error: No sequence of operations to move x to green+" : + "+Error: No sequence of operations to move x from red to green+", op.prettyPrint()); + } + + @ParameterizedTest(name = "testDirectMove [emptyPreviousLocation = {argumentsWithNames}]") + @ValueSource(booleans = {true, false}) + public void testDirectMove(boolean emptyPreviousLocation) { + WorldModelB model = newModel(); + addMyObjects(model, "x"); + addMyLocations(model, "red", "blue", "green"); + addRobots(model, "arm1", "arm2"); + addReachability(model, "arm1", "red", "blue", "x"); + addReachability(model, "arm2", "blue"); + + LogicalScene logicalScene = model.getMyScene().getLogicalScene(); + LogicalMovableObject x = logicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject(); + LogicalDropOffLocation red = logicalScene.resolveLogicalObjectOfInterest("red").asLogicalDropOffLocation(); + LogicalDropOffLocation blue = logicalScene.resolveLogicalObjectOfInterest("blue").asLogicalDropOffLocation(); + Robot arm1 = model.findRobot("arm1").orElseThrow(); + DifferenceObjectAtWrongPlace diff = createDifferenceObjectAtWrongPlace(x, emptyPreviousLocation ? null : red, blue); + + List<Operation> operations = diff.computeOperations(); + assertThat(operations).isNotEmpty(); + assertThat(operations).hasSize(1); + Operation op = operations.get(0); + assertThat(op).isInstanceOf(PickAndPlace.class); + PickAndPlace pickAndPlace = (PickAndPlace) op; + assertEquals(arm1, pickAndPlace.getRobotToExecute()); + assertEquals(x, pickAndPlace.getObjectToPick()); + assertEquals(blue, pickAndPlace.getTargetLocation()); + } + + @ParameterizedTest(name = "testIndirectMoveWith2Robots [emptyPreviousLocation = {argumentsWithNames}]") + @ValueSource(booleans = {true, false}) + public void testIndirectMoveWith2Robots(boolean emptyPreviousLocation) { + WorldModelB model = newModel(); + addMyObjects(model, "x"); + addMyLocations(model, "red", "blue", "green"); + addRobots(model, "arm1", "arm2"); + addReachability(model, "arm1", "red", "blue", "x"); + addReachability(model, "arm2", "blue", "green"); + + LogicalScene logicalScene = model.getMyScene().getLogicalScene(); + LogicalMovableObject x = logicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject(); + LogicalDropOffLocation red = logicalScene.resolveLogicalObjectOfInterest("red").asLogicalDropOffLocation(); + LogicalDropOffLocation blue = logicalScene.resolveLogicalObjectOfInterest("blue").asLogicalDropOffLocation(); + LogicalDropOffLocation green = logicalScene.resolveLogicalObjectOfInterest("green").asLogicalDropOffLocation(); + Robot arm1 = model.findRobot("arm1").orElseThrow(); + Robot arm2 = model.findRobot("arm2").orElseThrow(); + DifferenceObjectAtWrongPlace diff = createDifferenceObjectAtWrongPlace(x, emptyPreviousLocation ? null : red, green); + + List<Operation> operations = diff.computeOperations(); + assertThat(operations).isNotEmpty(); + assertThat(operations).hasSize(2); + Operation op1 = operations.get(0); + assertThat(op1).isInstanceOf(PickAndPlace.class); + PickAndPlace pickAndPlace1 = (PickAndPlace) op1; + assertEquals(arm1, pickAndPlace1.getRobotToExecute()); + assertEquals(x, pickAndPlace1.getObjectToPick()); + assertEquals(blue, pickAndPlace1.getTargetLocation()); + + Operation op2 = operations.get(1); + assertThat(op2).isInstanceOf(PickAndPlace.class); + PickAndPlace pickAndPlace2 = (PickAndPlace) op2; + assertEquals(arm2, pickAndPlace2.getRobotToExecute()); + assertEquals(x, pickAndPlace2.getObjectToPick()); + assertEquals(green, pickAndPlace2.getTargetLocation()); + } + + @ParameterizedTest(name = "testIndirectMoveWith3Robots [emptyPreviousLocation = {argumentsWithNames}]") + @ValueSource(booleans = {true, false}) + public void testIndirectMoveWith3Robots(boolean emptyPreviousLocation) { + /* + * red -(arm1)-> blue --(arm3)-, + * `-(arm2)-> green <-------' + * `--(arm4)-> yellow -(arm5)-> purple + */ + WorldModelB model = newModel(); + addMyObjects(model, "x"); + addMyLocations(model, "red", "blue", "green", "yellow", "purple"); + addRobots(model, "arm1", "arm2", "arm3", "arm4", "arm5"); + addReachability(model, "arm1", "red", "blue", "x"); + addReachability(model, "arm2", "red", "green", "x"); + addReachability(model, "arm3", "blue", "green"); + addReachability(model, "arm4", "green", "yellow"); + addReachability(model, "arm5", "yellow", "purple"); + + LogicalScene logicalScene = model.getMyScene().getLogicalScene(); + LogicalMovableObject x = logicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject(); + LogicalDropOffLocation red = logicalScene.resolveLogicalObjectOfInterest("red").asLogicalDropOffLocation(); + LogicalDropOffLocation green = logicalScene.resolveLogicalObjectOfInterest("green").asLogicalDropOffLocation(); + LogicalDropOffLocation yellow = logicalScene.resolveLogicalObjectOfInterest("yellow").asLogicalDropOffLocation(); + LogicalDropOffLocation purple = logicalScene.resolveLogicalObjectOfInterest("purple").asLogicalDropOffLocation(); + Robot arm2 = model.findRobot("arm2").orElseThrow(); + Robot arm4 = model.findRobot("arm4").orElseThrow(); + Robot arm5 = model.findRobot("arm5").orElseThrow(); + DifferenceObjectAtWrongPlace diff = createDifferenceObjectAtWrongPlace(x, emptyPreviousLocation ? null : red, purple); + + List<Operation> operations = diff.computeOperations(); + assertThat(operations).isNotEmpty(); + assertThat(operations).hasSize(3); + + Operation op1 = operations.get(0); + assertThat(op1).isInstanceOf(PickAndPlace.class); + PickAndPlace pickAndPlace1 = (PickAndPlace) op1; + assertEquals(arm2, pickAndPlace1.getRobotToExecute()); + assertEquals(x, pickAndPlace1.getObjectToPick()); + assertEquals(green, pickAndPlace1.getTargetLocation()); + + Operation op2 = operations.get(1); + assertThat(op2).isInstanceOf(PickAndPlace.class); + PickAndPlace pickAndPlace2 = (PickAndPlace) op2; + assertEquals(arm4, pickAndPlace2.getRobotToExecute()); + assertEquals(x, pickAndPlace2.getObjectToPick()); + assertEquals(yellow, pickAndPlace2.getTargetLocation()); + + Operation op3 = operations.get(2); + assertThat(op3).isInstanceOf(PickAndPlace.class); + PickAndPlace pickAndPlace3 = (PickAndPlace) op3; + assertEquals(arm5, pickAndPlace3.getRobotToExecute()); + assertEquals(x, pickAndPlace3.getObjectToPick()); + assertEquals(purple, pickAndPlace3.getTargetLocation()); + } + + private DifferenceObjectAtWrongPlace createDifferenceObjectAtWrongPlace(LogicalMovableObject x, LogicalDropOffLocation previousLocation, LogicalDropOffLocation newLocation) { + DifferenceObjectAtWrongPlace result = new DifferenceObjectAtWrongPlace(); + result.setObject(x); + result.setPreviousLocation(previousLocation); + result.setNewLocation(newLocation); + return result; + } +} diff --git a/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestDifference.java b/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestDifference.java new file mode 100644 index 0000000000000000000000000000000000000000..fb2ffaae2f255e8e5ffd7cc5210d365e5864743b --- /dev/null +++ b/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestDifference.java @@ -0,0 +1,190 @@ +package de.tudresden.inf.st.placeB; + +import de.tudresden.inf.st.placeB.ast.*; +import org.junit.jupiter.api.Test; + +import static de.tudresden.inf.st.placeB.TestUtils.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Testing computing differences. + * + * @author rschoene - Initial contribution + */ +public class TestDifference { + + @Test + public void testDifferenceObjectAtWrongPlaceNoPreviousLocation() { + // myScene: object (x) not contained in any location + // targetScene: object (x) in location (red) + WorldModelB model = newModel(); + addMyObject(model, "x", new Position(4, 4, 4), noRotation(), new Size(0.5f, 0.5f, 0.5f)); + addMyLocation(model, "red", new Position(0, 0, 0), noRotation(), new Size(1, 1, 1)); + + addOtherObjects(model, "x"); + addOtherLocations(model, "red"); + addContainedObjects(model, "red", "x"); + + LogicalScene myLogicalScene = model.getMyScene().getLogicalScene(); + LogicalMovableObject x = myLogicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject(); + assertFalse(x.hasLocatedAt()); + + JastAddList<Difference> diffs = model.diffScenes(); + assertNotNull(diffs); + assertEquals(1, diffs.getNumChild()); + Difference diff = diffs.getChild(0); + assertThat(diff).isInstanceOf(DifferenceObjectAtWrongPlace.class); + DifferenceObjectAtWrongPlace wrongPlace = (DifferenceObjectAtWrongPlace) diff; + assertEquals("x", wrongPlace.getObject().getName()); + assertNull(wrongPlace.getPreviousLocation()); + assertEquals("red", wrongPlace.getNewLocation().getName()); + } + + @Test + public void testDifferenceObjectAtWrongPlaceOtherPreviousLocation() { + // myScene: object (x) contained in another location (blue) + // targetScene: object (x) in location (red) + WorldModelB model = newModel(); + addMyObject(model, "x", new Position(0.5f, 0.5f, 0.5f), noRotation(), new Size(0.5f, 0.5f, 0.5f)); + addMyLocation(model, "blue", new Position(0, 0, 0), noRotation(), new Size(1, 1, 1)); + + addOtherObjects(model, "x"); + addOtherLocations(model, "red"); + addContainedObjects(model, "red", "x"); + + LogicalScene myLogicalScene = model.getMyScene().getLogicalScene(); + LogicalMovableObject x = myLogicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject(); + assertEquals("blue", x.getLocatedAt().getName()); + + JastAddList<Difference> diffs = model.diffScenes(); + assertNotNull(diffs); + assertEquals(1, diffs.getNumChild()); + Difference diff = diffs.getChild(0); + assertThat(diff).isInstanceOf(DifferenceObjectAtWrongPlace.class); + DifferenceObjectAtWrongPlace wrongPlace = (DifferenceObjectAtWrongPlace) diff; + assertEquals("x", wrongPlace.getObject().getName()); + assertEquals("blue", wrongPlace.getPreviousLocation().getName()); + assertEquals("red", wrongPlace.getNewLocation().getName()); + } + + @Test + public void testDifferenceObjectMisplaced() { + // myScene: object (x) contained in a location (red) + // targetScene: object (x) not contained in any location + WorldModelB model = newModel(); + addMyObject(model, "x", new Position(0.5f, 0.5f, 0.5f), noRotation(), new Size(0.5f, 0.5f, 0.5f)); + addMyLocation(model, "red", new Position(0, 0, 0), noRotation(), new Size(1, 1, 1)); + + addOtherObjects(model, "x"); + addOtherLocations(model, "red"); + + LogicalScene myLogicalScene = model.getMyScene().getLogicalScene(); + LogicalMovableObject x = myLogicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject(); + assertEquals("red", x.getLocatedAt().getName()); + + JastAddList<Difference> diffs = model.diffScenes(); + assertNotNull(diffs); + assertEquals(1, diffs.getNumChild()); + Difference diff = diffs.getChild(0); + assertThat(diff).isInstanceOf(DifferenceObjectMisplaced.class); + DifferenceObjectMisplaced misplaced = (DifferenceObjectMisplaced) diff; + assertEquals("x", misplaced.getObject().getName()); + assertEquals("red", misplaced.getPreviousLocation().getName()); + } + + @Test + public void testNoDifferenceNoContainment() { + // myScene: object (x) not contained in any location + // targetScene: object (x) not contained in any location + WorldModelB model = newModel(); + addMyObject(model, "x", new Position(8, 8, 8), noRotation(), new Size(0.5f, 0.5f, 0.5f)); + addMyLocation(model, "red", new Position(0, 0, 0), noRotation(), new Size(1, 1, 1)); + + addOtherObjects(model, "x"); + addOtherLocations(model, "red"); + + LogicalScene myLogicalScene = model.getMyScene().getLogicalScene(); + LogicalMovableObject x = myLogicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject(); + assertFalse(x.hasLocatedAt()); + + JastAddList<Difference> diffs = model.diffScenes(); + assertNotNull(diffs); + assertThat(diffs).isEmpty(); + } + + @Test + public void testNoDifferenceSameLocation() { + // myScene: object (x) contained in same location (red) + // targetScene: object (x) contained in a location (red) + WorldModelB model = newModel(); + addMyObject(model, "x", new Position(0.5f, 0.5f, 0.5f), noRotation(), new Size(0.5f, 0.5f, 0.5f)); + addMyLocation(model, "red", new Position(0, 0, 0), noRotation(), new Size(1, 1, 1)); + + addOtherObjects(model, "x"); + addOtherLocations(model, "red"); + addContainedObjects(model, "red", "x"); + + LogicalScene myLogicalScene = model.getMyScene().getLogicalScene(); + LogicalMovableObject x = myLogicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject(); + assertEquals("red", x.getLocatedAt().getName()); + + JastAddList<Difference> diffs = model.diffScenes(); + assertNotNull(diffs); + assertThat(diffs).isEmpty(); + } + + @Test + public void testDifferenceNewObject() { + // myScene: object (x) not known + // targetScene: object (x) exists, not contained in any location + WorldModelB model = newModel(); + addMyLocation(model, "red", new Position(0, 0, 0), noRotation(), new Size(1, 1, 1)); + + addOtherObjects(model, "x"); + addOtherLocations(model, "red"); + + LogicalScene myLogicalScene = model.getMyScene().getLogicalScene(); + assertNull(myLogicalScene.resolveLogicalObjectOfInterest("x")); + + JastAddList<Difference> diffs = model.diffScenes(); + assertNotNull(diffs); + assertEquals(1, diffs.getNumChild()); + Difference diff = diffs.getChild(0); + assertThat(diff).isInstanceOf(DifferenceNewObject.class); + DifferenceNewObject diffNewObject = (DifferenceNewObject) diff; + assertEquals("x", diffNewObject.getObject().getName()); + } + + @Test + public void testMultipleDifferenceObjectAtWrongPlace() { + // myScene: objects (x, y) contained in other locations (blue, green) + // targetScene: objects (x, y) in location (red) + WorldModelB model = newModel(); + addMyObject(model, "x", new Position(0.5f, 0.5f, 0.5f), noRotation(), new Size(0.5f, 0.5f, 0.5f)); + addMyObject(model, "y", new Position(2.5f, 2.5f, 2.5f), noRotation(), new Size(0.5f, 0.5f, 0.5f)); + addMyLocation(model, "blue", new Position(0, 0, 0), noRotation(), new Size(1, 1, 1)); + addMyLocation(model, "green", new Position(2, 2, 2), noRotation(), new Size(1, 1, 1)); + + addOtherObjects(model, "x", "y"); + addOtherLocations(model, "red"); + addContainedObjects(model, "red", "x", "y"); + + LogicalScene myLogicalScene = model.getMyScene().getLogicalScene(); + LogicalMovableObject x = myLogicalScene.resolveLogicalObjectOfInterest("x").asLogicalMovableObject(); + LogicalMovableObject y = myLogicalScene.resolveLogicalObjectOfInterest("y").asLogicalMovableObject(); + assertEquals("blue", x.getLocatedAt().getName()); + assertEquals("green", y.getLocatedAt().getName()); + + JastAddList<Difference> diffs = model.diffScenes(); + assertNotNull(diffs); + assertEquals(2, diffs.getNumChild()); + assertThat(diffs).allMatch(d -> d instanceof DifferenceObjectAtWrongPlace); + //noinspection unchecked + assertThat(diffs).map(diff -> (DifferenceObjectAtWrongPlace) diff) + .extracting(d -> d.getObject().getName(), d -> d.getPreviousLocation().getName(), d -> d.getNewLocation().getName()) + .containsOnly(tuple("x", "blue", "red"), + tuple("y", "green", "red")); + } +} diff --git a/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestMultiScenes.java b/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestMultiScenes.java new file mode 100644 index 0000000000000000000000000000000000000000..dc1080a5f69832879c19ba4ad849c729fdcf9776 --- /dev/null +++ b/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestMultiScenes.java @@ -0,0 +1,142 @@ +package de.tudresden.inf.st.placeB; + +import de.tudresden.inf.st.placeB.ast.LogicalDropOffLocation; +import de.tudresden.inf.st.placeB.ast.LogicalMovableObject; +import de.tudresden.inf.st.placeB.ast.LogicalScene; +import de.tudresden.inf.st.placeB.ast.WorldModelB; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Collections; + +import static de.tudresden.inf.st.placeB.TestUtils.newModel; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Testing merging of multiple (other) scenes. + * + * @author rschoene - Initial contribution + */ +public class TestMultiScenes { + + @Test + public void noOtherScenes() { + WorldModelB model = newModel(); + + LogicalScene actual = model.mergedOtherScene(); + + assertThat(actual.getLogicalDropOffLocationList()).isEmpty(); + assertThat(actual.getLogicalMovableObjectList()).isEmpty(); + } + + @ParameterizedTest(name = "twoScenesContains [scene1 = {argumentsWithNames}]") + @ValueSource(booleans = {true, false}) + public void oneSceneNoContains(boolean scene1) { + WorldModelB model = newModel(); + TestUtils.addOtherLocations(scene1 ? model.getOtherScene1() : model.getOtherScene2(), "red"); + TestUtils.addOtherObjects(scene1 ? model.getOtherScene1() : model.getOtherScene2(), "a"); + + LogicalScene actual = model.mergedOtherScene(); + + assertThat(actual.getLogicalDropOffLocationList()) + .extracting("Name").containsOnly("red"); + assertThat(actual.getLogicalMovableObjectList()) + .extracting("Name").containsOnly("a"); + } + + @Test + public void twoScenesNoContains() { + WorldModelB model = newModel(); + TestUtils.addOtherLocations(model, "red"); + TestUtils.addOtherObjects(model, "a"); + TestUtils.addOtherLocations(model.getOtherScene2(), "blue"); + TestUtils.addOtherObjects(model.getOtherScene2(), "b"); + + LogicalScene actual = model.mergedOtherScene(); + + assertThat(actual.getLogicalDropOffLocationList()) + .size().isEqualTo(2); + assertThat(actual.getLogicalDropOffLocationList()) + .doesNotContainAnyElementsOf(model.getOtherScene1().getLogicalDropOffLocationList()) + .doesNotContainAnyElementsOf(model.getOtherScene2().getLogicalDropOffLocationList()) + .extracting("Name").containsOnly("red", "blue"); + assertThat(actual.getLogicalDropOffLocationList()) + .extracting("ContainedObjects").containsOnly(Collections.emptyList()); + + assertThat(actual.getLogicalMovableObjectList()) + .size().isEqualTo(2); + assertThat(actual.getLogicalMovableObjectList()) + .doesNotContainAnyElementsOf(model.getOtherScene1().getLogicalMovableObjectList()) + .doesNotContainAnyElementsOf(model.getOtherScene2().getLogicalMovableObjectList()) + .extracting("Name").containsOnly("a", "b"); + assertThat(actual.getLogicalMovableObjectList()) + .extracting("LocatedAt").containsOnly((LogicalDropOffLocation) null); + } + + @ParameterizedTest(name = "twoScenesContains [scene1 = {argumentsWithNames}]") + @ValueSource(booleans = {true, false}) + public void twoScenesContains1(boolean scene1) { + WorldModelB model = newModel(); + TestUtils.addOtherLocations(model, "red", "blue"); + TestUtils.addOtherObjects(model, "a", "b"); + model.getOtherScene2().addLogicalDropOffLocation(new LogicalDropOffLocation().setName("red")); + model.getOtherScene2().addLogicalMovableObject(new LogicalMovableObject().setName("a")); + TestUtils.addContainedObjects(scene1 ? model.getOtherScene1() : model.getOtherScene2(), + "red", "a"); + + LogicalScene actual = model.mergedOtherScene(); + + assertThat(actual.getLogicalDropOffLocationList()) + .size().isEqualTo(2); + assertThat(actual.getLogicalDropOffLocationList()) + .doesNotContainAnyElementsOf(model.getOtherScene1().getLogicalDropOffLocationList()) + .doesNotContainAnyElementsOf(model.getOtherScene2().getLogicalDropOffLocationList()) + .extracting("Name").containsExactly("red", "blue"); + LogicalDropOffLocation red = actual.resolveLogicalObjectOfInterest("red").asLogicalDropOffLocation(); + assertThat(red.getContainedObjectList()) + .extracting("Name").containsOnly("a"); + + assertThat(actual.getLogicalMovableObjectList()) + .size().isEqualTo(2); + assertThat(actual.getLogicalMovableObjectList()) + .doesNotContainAnyElementsOf(model.getOtherScene1().getLogicalMovableObjectList()) + .extracting("Name").containsOnly("a", "b"); + LogicalMovableObject a = actual.resolveLogicalObjectOfInterest("a").asLogicalMovableObject(); + assertThat(a.getLocatedAt()) + .extracting("Name").isEqualTo("red"); + } + + @Test + public void twoScenesRedContainsATwice() { + WorldModelB model = newModel(); + TestUtils.addOtherLocations(model, "red", "blue"); + TestUtils.addOtherObjects(model, "a", "b"); + TestUtils.addContainedObjects(model, "red", "a"); + + TestUtils.addOtherLocations(model.getOtherScene2(), "red"); + TestUtils.addOtherObjects(model.getOtherScene2(), "a", "b"); + TestUtils.addContainedObjects(model.getOtherScene2(), "red", "a"); + + LogicalScene actual = model.mergedOtherScene(); + + assertThat(actual.getLogicalDropOffLocationList()) + .size().isEqualTo(2); + assertThat(actual.getLogicalDropOffLocationList()) + .doesNotContainAnyElementsOf(model.getOtherScene1().getLogicalDropOffLocationList()) + .doesNotContainAnyElementsOf(model.getOtherScene2().getLogicalDropOffLocationList()) + .extracting("Name").containsExactly("red", "blue"); + LogicalDropOffLocation red = actual.resolveLogicalObjectOfInterest("red").asLogicalDropOffLocation(); + assertThat(red.getContainedObjectList()) + .extracting("Name").containsOnly("a"); + + assertThat(actual.getLogicalMovableObjectList()) + .size().isEqualTo(2); + assertThat(actual.getLogicalMovableObjectList()) + .doesNotContainAnyElementsOf(model.getOtherScene1().getLogicalMovableObjectList()) + .extracting("Name").containsOnly("a", "b"); + LogicalMovableObject a = actual.resolveLogicalObjectOfInterest("a").asLogicalMovableObject(); + assertThat(a.getLocatedAt()) + .extracting("Name").isEqualTo("red"); + } +} diff --git a/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestUtils.java b/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..6e5f8fe77601bf88ef82f6f3ef0764c60e932e7c --- /dev/null +++ b/ros3rag.placeB/src/test/java/de/tudresden/inf/st/placeB/TestUtils.java @@ -0,0 +1,103 @@ +package de.tudresden.inf.st.placeB; + +import de.tudresden.inf.st.placeB.ast.*; + +/** + * Utility methods for testing. + * + * @author rschoene - Initial contribution + */ +public class TestUtils { + public static WorldModelB newModel() { + WorldModelB model = new WorldModelB(); + Scene scene = new Scene(); + model.setMyScene(scene); + model.setOtherScene1(new LogicalScene()); + model.setOtherScene2(new LogicalScene()); + return model; + } + + public static Orientation noRotation() { + return new Orientation().setW(1); + } + + public static void addMyObjects(WorldModelB model, String... names) { + for (String name : names) { + int index = model.getMyScene().getNumMovableObject(); + addMyObject(model, name, new Position(-index, -index, -index), new Orientation(), new Size(1, 1, 1)); + } + } + + public static void addMyObject(WorldModelB model, String name, Position position, Orientation orientation, Size size) { + MovableObject obj = new MovableObject() + .setName(name) + .setPosition(position) + .setOrientation(orientation) + .setSize(size); + model.getMyScene().addMovableObject(obj); + } + + public static void addMyLocations(WorldModelB model, String... names) { + for (String name : names) { + int index = model.getMyScene().getNumDropOffLocation(); + addMyLocation(model, name, new Position(+index, +index, +index), new Orientation(), new Size(1, 1, 1)); + } + } + + public static void addMyLocation(WorldModelB model, String name, Position position, Orientation orientation, Size size) { + DropOffLocation obj = new DropOffLocation() + .setName(name) + .setPosition(position) + .setOrientation(orientation) + .setSize(size); + model.getMyScene().addDropOffLocation(obj); + } + + public static void addRobots(WorldModelB model, String... names) { + for (String name : names) { + Robot obj = new Robot().setName(name); + model.addRobot(obj); + } + } + + public static void addReachability(WorldModelB model, String robotName, String... reachableObjects) { + Robot robot = model.findRobot(robotName).orElseThrow(); + CanReachObjectOfInterestWrapper wrapper = new CanReachObjectOfInterestWrapper(); + for (String reachableObjectName : reachableObjects) { + wrapper.addCanReachObjectOfInterest(new CanReachObjectOfInterest().setObjectName(reachableObjectName)); + } + robot.setCanReachObjectOfInterestWrapper(wrapper); + } + + public static void addOtherObjects(WorldModelB model, String... names) { + addOtherObjects(model.getOtherScene1(), names); + } + + public static void addOtherObjects(LogicalScene scene, String... names) { + for (String name : names) { + scene.addLogicalMovableObject(new LogicalMovableObject().setName(name)); + } + } + + public static void addOtherLocations(WorldModelB model, String... names) { + addOtherLocations(model.getOtherScene1(), names); + } + + public static void addOtherLocations(LogicalScene scene, String... names) { + for (String name : names) { + scene.addLogicalDropOffLocation(new LogicalDropOffLocation().setName(name)); + } + } + + public static void addContainedObjects(WorldModelB model, String locationName, String... objectNames) { + addContainedObjects(model.getOtherScene1(), locationName, objectNames); + } + + public static void addContainedObjects(LogicalScene scene, String locationName, String... objectNames) { + LogicalDropOffLocation location = scene.resolveLogicalObjectOfInterest(locationName).asLogicalDropOffLocation(); + for (String objectName : objectNames) { + location.addContainedObject(scene.resolveLogicalObjectOfInterest(objectName).asLogicalMovableObject()); + } + } + +} diff --git a/settings.gradle b/settings.gradle index bd2c1a8453b44141ce617dbfeb3570e9786f6637..19ff0e6b692daa207124b6394d01c1dabf1c14f7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,8 +1,11 @@ +pluginManagement { + plugins { + id 'org.jastadd' version "${jastadd_gradle_version}" + } +} + rootProject.name = 'ros3rag' include 'ros3rag.placeA' include 'ros3rag.placeB' include 'ros3rag.common' - -// include 'ros3rag.senderstub' -// include 'ros3rag.receiverstub'