diff --git a/build.gradle b/build.gradle
index 4f57507f7e9f39a2fd8fb84a7820360bd9d4fdcb..5f86f7d094af70ac9fc2241e78dbd8df18c53c4d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -40,7 +40,6 @@ subprojects {
     dependencies {
         implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.11.2'
         implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.11.2'
-        testImplementation group: 'junit', name: 'junit', version: '4.12'
         testImplementation group: 'org.hamcrest', name: 'hamcrest-junit', version: '2.0.0.0'
     }
 
diff --git a/ros2rag.base/build.gradle b/ros2rag.base/build.gradle
index 9e0bde27ffc2908022aa1a0148b6dc8acec2be35..a6a7882eeee6db3cc34887d5b8cce3fe267905d5 100644
--- a/ros2rag.base/build.gradle
+++ b/ros2rag.base/build.gradle
@@ -17,10 +17,9 @@ buildscript {
 }
 
 dependencies {
-    implementation 'com.fasterxml.jackson.core:jackson-core:2.9.8'
-    implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
-//    api 'org.jastadd:jastadd:2.3.4'
-    runtime 'org.jastadd:jastadd:2.3.4'
+    implementation group: 'com.github.spullara.mustache.java', name: 'compiler', version: '0.9.6'
+    implementation group: 'org.apache.logging.log4j', name: 'log4j-jul', version: '2.11.2'
+    runtime group: 'org.jastadd', name: 'jastadd', version: '2.3.4'
     api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
 }
 
@@ -62,6 +61,7 @@ task relast(type: JavaExec) {
             "../libs/relast.jar",
             "../relast.preprocessor/src/main/jastadd/RelAst.relast",
             "./src/main/jastadd/Ros2Rag.relast",
+            "./src/main/jastadd/MustacheNodes.relast",
             "--listClass=java.util.ArrayList",
             "--jastAddList=JastAddList",
             "--useJastAddNames",
@@ -72,11 +72,12 @@ task relast(type: JavaExec) {
 
     inputs.files file("../libs/relast.jar"),
             file("../relast.preprocessor/src/main/jastadd/RelAST.relast"),
-            file("src/main/jastadd/Ros2Rag.relast")
+            file("./src/main/jastadd/Ros2Rag.relast")
+            file("./src/main/jastadd/MustacheNodes.relast")
     outputs.files file("./src/gen/jastadd/Ros2Rag.ast"),
-            file("src/gen/jastadd/Ros2Rag.jadd"),
-            file("src/gen/jastadd/Ros2RagRefResolver.jadd"),
-            file('src/gen/jastadd/Ros2RagResolverStubs.jrag')
+            file("./src/gen/jastadd/Ros2Rag.jadd"),
+            file("./src/gen/jastadd/Ros2RagRefResolver.jadd"),
+            file('./src/gen/jastadd/Ros2RagResolverStubs.jrag')
 }
 
 jastadd {
diff --git a/ros2rag.base/src/main/jastadd/MustacheNodes.relast b/ros2rag.base/src/main/jastadd/MustacheNodes.relast
new file mode 100644
index 0000000000000000000000000000000000000000..69d4280f9d9038c838e83767c6e72be9a8d6d5d8
--- /dev/null
+++ b/ros2rag.base/src/main/jastadd/MustacheNodes.relast
@@ -0,0 +1,22 @@
+//TypeComponentMustache ;
+//rel TypeComponentMustache.TypeComponent -> TypeComponent ;
+
+MRos2Rag ::= ReadDefinition:MReadDefinition* WriteDefinition:MWriteDefinition* MappingDefinition:MMappingDefinition* DependencyDefinition:MDependencyDefinition* RootTypeComponent:MTypeComponent* TokenComponent:MTokenComponent*;
+abstract MUpdateDefinition ::= InnerMappingDefinition:MInnerMappingDefinition*;
+MReadDefinition : MUpdateDefinition;
+MWriteDefinition : MUpdateDefinition;
+MMappingDefinition;
+MInnerMappingDefinition;
+MDependencyDefinition;
+MTypeComponent;
+MTokenComponent;
+
+rel MRos2Rag.Ros2Rag -> Ros2Rag;
+rel MInnerMappingDefinition.MappingDefinition -> MappingDefinition;
+rel MReadDefinition.ReadFromMqttDefinition -> ReadFromMqttDefinition;
+rel MWriteDefinition.WriteToMqttDefinition -> WriteToMqttDefinition;
+rel MMappingDefinition.MappingDefinition -> MappingDefinition;
+rel MDependencyDefinition.DependencyDefinition -> DependencyDefinition;
+rel MTypeComponent.TypeComponent -> TypeComponent;
+rel MTokenComponent.TokenComponent -> TokenComponent;
+rel MTokenComponent.DependencyDefinition* -> MDependencyDefinition;
diff --git a/ros2rag.base/src/main/jastadd/Navigation.jrag b/ros2rag.base/src/main/jastadd/Navigation.jrag
index f56d99825c376248ce8f9a2dae1388ed3898ea64..a4f21e6e8d79c549a63d54474792382457bf74fd 100644
--- a/ros2rag.base/src/main/jastadd/Navigation.jrag
+++ b/ros2rag.base/src/main/jastadd/Navigation.jrag
@@ -2,10 +2,12 @@ aspect Navigation {
 
   // --- program ---
   eq Ros2Rag.getChild().program() = getProgram();
+  eq MRos2Rag.getChild().program() = getRos2Rag().program();
 
-  // --- ros2rag
+  // --- ros2rag ---
   inh Ros2Rag ASTNode.ros2rag();
   eq Ros2Rag.getChild().ros2rag() = this;
+  eq MRos2Rag.getChild().ros2rag() = getRos2Rag();
 
   // --- containedFile (first equation should be in preprocessor) ---
   eq Program.getChild().containedFile() = null;
@@ -32,6 +34,7 @@ aspect Navigation {
   eq GrammarFile.getChild().containedFileName() = getFileName();
   eq Ros2Rag.getChild().containedFileName() = getFileName();
   eq Program.getChild().containedFileName() = null;
+  eq MRos2Rag.getChild().containedFileName() = null;
 
   // --- isTokenUpdateDefinition ---
   syn boolean UpdateDefinition.isTokenUpdateDefinition() = false;
@@ -49,6 +52,10 @@ aspect Navigation {
   syn WriteToMqttDefinition UpdateDefinition.asWriteToMqttDefinition() = null;
   eq WriteToMqttDefinition.asWriteToMqttDefinition() = this;
 
+  // --- asReadFromMqttDefinition ---
+  syn ReadFromMqttDefinition UpdateDefinition.asReadFromMqttDefinition() = null;
+  eq ReadFromMqttDefinition.asReadFromMqttDefinition() = this;
+
   // --- targetUpdateDefinition ---
   syn WriteToMqttDefinition DependencyDefinition.targetUpdateDefinition() {
     // resolve definition in here, as we do not need resolveMethod in any other place (yet)
diff --git a/ros2rag.base/src/main/jastadd/Ros2Rag.relast b/ros2rag.base/src/main/jastadd/Ros2Rag.relast
index a5499702f44facd5b649a811a958b890d4565d0a..e1a84e8beac44fcbc23d09fc56ed536586bde4e8 100644
--- a/ros2rag.base/src/main/jastadd/Ros2Rag.relast
+++ b/ros2rag.base/src/main/jastadd/Ros2Rag.relast
@@ -10,7 +10,6 @@ rel TokenUpdateDefinition.Token -> TokenComponent;
 ReadFromMqttDefinition : TokenUpdateDefinition;
 WriteToMqttDefinition  : TokenUpdateDefinition;
 
-// example: RobotArm._AppropriateSpeed canDependOn Joint.CurrentPosition as dependency1
 DependencyDefinition ::= <ID>;
 rel DependencyDefinition.Source <-> TokenComponent.DependencySourceDefinition*;
 rel DependencyDefinition.Target -> TokenComponent;
diff --git a/ros2rag.base/src/main/jastadd/backend/Configuration.jadd b/ros2rag.base/src/main/jastadd/backend/Configuration.jadd
index 711ba2511d1e0c8535007cacccac99872128e58f..d4f5b43f27cb329f1ff82318c6f383f0f69ebbd6 100644
--- a/ros2rag.base/src/main/jastadd/backend/Configuration.jadd
+++ b/ros2rag.base/src/main/jastadd/backend/Configuration.jadd
@@ -1,4 +1,5 @@
 aspect Configuration {
   public static boolean ASTNode.loggingEnabledForReads = false;
   public static boolean ASTNode.loggingEnabledForWrites = false;
+  public static TypeDecl ASTNode.rootNode;
 }
diff --git a/ros2rag.base/src/main/jastadd/backend/Generation.jadd b/ros2rag.base/src/main/jastadd/backend/Generation.jadd
index 1b6dfff3386d079e75163c4725681a87904f6786..7aa915a421b4034ffa7f20406b22a041a0868bb9 100644
--- a/ros2rag.base/src/main/jastadd/backend/Generation.jadd
+++ b/ros2rag.base/src/main/jastadd/backend/Generation.jadd
@@ -21,11 +21,188 @@ aspect GenerationUtils {
   }
 }
 
+/* Open questions
+- Should all string constants be defined on the normal AST, or on the special mustache AST?
+*/
+
+aspect AttributesForMustache {
+  // --- MRos2Rag ---
+  eq MRos2Rag.getChild().mqttUpdaterAttribute() = mqttUpdaterAttribute();
+  eq MRos2Rag.getChild().mqttUpdaterField() = mqttUpdaterField();
+
+  syn String MRos2Rag.mqttUpdaterAttribute() = getRos2Rag().mqttUpdaterAttribute();
+  syn String MRos2Rag.mqttUpdaterField() = getRos2Rag().mqttUpdaterField();
+  syn String MRos2Rag.mqttSetHostMethod() = getRos2Rag().mqttSetHostMethod();
+  syn String MRos2Rag.mqttWaitUntilReadyMethod() = getRos2Rag().mqttWaitUntilReadyMethod();
+  syn String MRos2Rag.mqttCloseMethod() = getRos2Rag().mqttCloseMethod();
+
+  // --- MUpdateDefinition ---
+  syn String MUpdateDefinition.preemptiveExpectedValue();
+  syn String MUpdateDefinition.preemptiveReturn();
+  syn TokenUpdateDefinition MUpdateDefinition.updateDef();
+  syn String MUpdateDefinition.firstInputVarName();
+
+  eq MUpdateDefinition.getInnerMappingDefinition(int i).isLast() = i == getNumInnerMappingDefinition() - 1;
+  eq MUpdateDefinition.getInnerMappingDefinition().resultVarPrefix() = resultVarPrefix();
+  eq MUpdateDefinition.getInnerMappingDefinition(int i).inputVarName() = i == 0 ? firstInputVarName() : resultVarPrefix() + getInnerMappingDefinition(i - 1).getMappingDefinition().methodName();
+
+  inh String MUpdateDefinition.mqttUpdaterAttribute();
+
+  syn String MUpdateDefinition.connectMethod() = updateDef().connectMethod();
+  syn TokenComponent MUpdateDefinition.token() = updateDef().getToken();
+  syn boolean MUpdateDefinition.alwaysApply() = updateDef().getAlwaysApply();
+  syn String MUpdateDefinition.resultVarPrefix() = "result";  // we do not need "_" here, because methodName begins with one
+  syn String MUpdateDefinition.parentTypeName() = token().containingTypeDecl().getName();
+  syn String MUpdateDefinition.tokenName() = token().getName();
+  syn MInnerMappingDefinition MUpdateDefinition.lastDefinition() = getInnerMappingDefinition(getNumInnerMappingDefinition() - 1);
+  syn String MUpdateDefinition.lastDefinitionToType() = lastDefinition().ToType();
+  syn String MUpdateDefinition.lastDefinitionName() = lastDefinition().methodName();
+  syn String MUpdateDefinition.lastResult() = resultVarPrefix() + lastDefinitionName();
+  syn String MUpdateDefinition.condition() {
+    if (lastDefinition().getMappingDefinition().getToType().isArray()) {
+      return "java.util.Arrays.equals(" + preemptiveExpectedValue() + ", " + lastResult() + ")";
+    }
+    if (token().isPrimitiveType() && lastDefinition().getMappingDefinition().getToType().isPrimitiveType()) {
+      return preemptiveExpectedValue() + " == " + lastResult();
+    }
+    if (lastDefinition().getMappingDefinition().isDefaultMappingDefinition()) {
+      return preemptiveExpectedValue() + " != null && " + preemptiveExpectedValue() + ".equals(" + lastResult() + ")";
+    }
+    return preemptiveExpectedValue() + " != null ? " + preemptiveExpectedValue() + ".equals(" + lastResult() + ") : " + lastResult() + " == null";
+  }
+
+  // --- MInnerMappingDefinition ---
+  inh boolean MInnerMappingDefinition.isLast();
+  inh String MInnerMappingDefinition.resultVarPrefix();
+  syn String MInnerMappingDefinition.ToType() = getMappingDefinition().getToType().prettyPrint();
+  syn String MInnerMappingDefinition.methodName() = getMappingDefinition().methodName();
+  inh String MInnerMappingDefinition.inputVarName();
+
+  // --- MReadDefinition ---
+  eq MReadDefinition.preemptiveExpectedValue() = "get" + tokenName() + "()";
+  eq MReadDefinition.preemptiveReturn() = "return;";
+  eq MReadDefinition.updateDef() = getReadFromMqttDefinition();
+  eq MReadDefinition.firstInputVarName() = "message";
+
+  // --- MWriteDefinition ---
+  eq MWriteDefinition.preemptiveExpectedValue() = lastValue();
+  eq MWriteDefinition.preemptiveReturn() = "return false;";
+  eq MWriteDefinition.updateDef() = getWriteToMqttDefinition();
+  eq MWriteDefinition.firstInputVarName() = "get" + tokenName() + "()";
+
+  syn String MWriteDefinition.writeTopic() = getWriteToMqttDefinition().writeTopic();
+  syn String MWriteDefinition.lastValue() = getWriteToMqttDefinition().lastValue();
+  syn String MWriteDefinition.updateMethod() = getWriteToMqttDefinition().updateMethod();
+  syn String MWriteDefinition.writeMethod() = getWriteToMqttDefinition().writeMethod();
+  syn String MWriteDefinition.tokenResetMethod() = getWriteToMqttDefinition().tokenResetMethod();
+
+  // --- MMappingDefinition ---
+  syn String MMappingDefinition.toType() = getMappingDefinition().getToType().prettyPrint();
+  syn String MMappingDefinition.methodName() = getMappingDefinition().methodName();
+  syn String MMappingDefinition.fromType() = getMappingDefinition().getFromType().prettyPrint();
+  syn String MMappingDefinition.fromVariableName() = getMappingDefinition().getFromVariableName();
+  syn String MMappingDefinition.content() = getMappingDefinition().getContent();
+
+  // --- MDependencyDefinition ---
+  syn String MDependencyDefinition.targetParentTypeName() = getDependencyDefinition().getTarget().containingTypeDecl().getName();
+  syn String MDependencyDefinition.dependencyMethod() = getDependencyDefinition().dependencyMethod();
+  syn String MDependencyDefinition.sourceParentTypeName() = getDependencyDefinition().getSource().containingTypeDecl().getName();
+  syn String MDependencyDefinition.internalRelationPrefix() = getDependencyDefinition().internalRelationPrefix();
+  syn nta MUpdateDefinition MDependencyDefinition.targetUpdateDefinition() {
+    return getDependencyDefinition().targetUpdateDefinition().toMustache();
+  }
+
+  // --- MTypeComponent ---
+  syn String MTypeComponent.name() = getTypeComponent().getName();
+  inh String MTypeComponent.mqttUpdaterAttribute();
+  inh String MTypeComponent.mqttUpdaterField();
+
+  // --- MTokenComponent ---
+  syn String MTokenComponent.parentTypeName() = getTokenComponent().containingTypeDecl().getName();
+  syn String MTokenComponent.name() = getTokenComponent().getName();
+  syn String MTokenComponent.javaType() = getTokenComponent().getJavaTypeUse().prettyPrint();
+  syn String MTokenComponent.internalName() = getTokenComponent().internalName();
+
+  // --- toMustache ---
+  syn lazy MRos2Rag Ros2Rag.toMustache() {
+    MRos2Rag result = new MRos2Rag();
+    result.setRos2Rag(this);
+    for (UpdateDefinition def : getUpdateDefinitionList()) {
+      if (def.isWriteToMqttDefinition()) {
+        result.addWriteDefinition(def.asWriteToMqttDefinition().toMustache());
+      } else {
+        result.addReadDefinition(def.asReadFromMqttDefinition().toMustache());
+      }
+    }
+    for (MappingDefinition def : allMappingDefinitions()) {
+      result.addMappingDefinition(def.toMustache());
+    }
+    for (DependencyDefinition def : getDependencyDefinitionList()) {
+      result.addDependencyDefinition(def.toMustache());
+    }
+    for (TokenComponent token : getProgram().allTokenComponents()) {
+      if (!token.getDependencySourceDefinitionList().isEmpty()) {
+        result.addTokenComponent(token.toMustache());
+      }
+    }
+    for (Component child : rootNode.getComponentList()) {
+      if (child.isTypeComponent()) {
+        result.addRootTypeComponent(child.asTypeComponent().toMustache());
+      }
+    }
+    return result;
+  }
+
+//MInnerMappingDefinition.MappingDefinition -> MappingDefinition;
+  protected void MUpdateDefinition.addInnerMappings() {
+    for (MappingDefinition def : updateDef().effectiveMappings()) {
+      MInnerMappingDefinition inner = new MInnerMappingDefinition();
+      inner.setMappingDefinition(def);
+      addInnerMappingDefinition(inner);
+    }
+  }
+  syn lazy MReadDefinition ReadFromMqttDefinition.toMustache() {
+    MReadDefinition result = new MReadDefinition();
+    result.setReadFromMqttDefinition(this);
+    result.addInnerMappings();
+    return result;
+  }
+  syn lazy MWriteDefinition WriteToMqttDefinition.toMustache() {
+    MWriteDefinition result = new MWriteDefinition();
+    result.setWriteToMqttDefinition(this);
+    result.addInnerMappings();
+    return result;
+  }
+  syn lazy MMappingDefinition MappingDefinition.toMustache() {
+    MMappingDefinition result = new MMappingDefinition();
+    result.setMappingDefinition(this);
+    return result;
+  }
+  syn lazy MDependencyDefinition DependencyDefinition.toMustache() {
+    MDependencyDefinition result = new MDependencyDefinition();
+    result.setDependencyDefinition(this);
+    return result;
+  }
+  syn lazy MTypeComponent TypeComponent.toMustache() {
+    MTypeComponent result = new MTypeComponent();
+    result.setTypeComponent(this);
+    return result;
+  }
+  syn lazy MTokenComponent TokenComponent.toMustache() {
+    MTokenComponent result = new MTokenComponent();
+    result.setTokenComponent(this);
+    for (DependencyDefinition def : getDependencySourceDefinitionList()) {
+      result.addDependencyDefinition(def.toMustache());
+    }
+    return result;
+  }
+}
+
 aspect AspectGeneration {
+  // naming convention attributes
   syn String TokenComponent.internalName() = getDependencySourceDefinitionList().isEmpty() ? externalName() : "_internal_" + getName();
   syn String TokenComponent.externalName() = getName();
 
-  // naming convention attributes
   syn String TokenUpdateDefinition.connectMethod() = "connect" + getToken().getName();
   syn String WriteToMqttDefinition.writeTopic() = "_topic_" + getToken().getName();
   syn String WriteToMqttDefinition.lastValue() = "_lastValue" + getToken().getName();
@@ -39,10 +216,6 @@ aspect AspectGeneration {
   syn String DependencyDefinition.internalRelationPrefix() = "_internal_" + getID();
   syn String DependencyDefinition.internalTokenName() = getSource().internalName();
 
-  inh String UpdateDefinition.mqttUpdaterAttribute();
-  inh String MappingDefinition.mqttUpdaterAttribute();
-  inh String DependencyDefinition.mqttUpdaterAttribute();
-  eq Ros2Rag.getChild().mqttUpdaterAttribute() = mqttUpdaterAttribute();
   syn String Ros2Rag.mqttUpdaterAttribute() = "_mqttUpdater";
   syn String Ros2Rag.mqttUpdaterField() = "_mqttUpdater";
 
@@ -50,247 +223,43 @@ aspect AspectGeneration {
   syn String Ros2Rag.mqttWaitUntilReadyMethod() = "MqttWaitUntilReady";
   syn String Ros2Rag.mqttCloseMethod() = "MqttCloseConnections";
 
-  public String Ros2Rag.generateAspect(String rootNodeName) {
-    StringBuilder sb = new StringBuilder();
-    TypeDecl rootNode = getProgram().resolveTypeDecl(rootNodeName);
-    generateMqttAspect(sb, rootNode);
-    generateGrammarExtension(sb);
-    return sb.toString();
-  }
-
-  public void Ros2Rag.generateMqttAspect(StringBuilder sb, TypeDecl rootNode) {
-    String rootNodeName = rootNode.getName();
-    sb.append("aspect MQTT {\n");
-    sb.append(ind(1)).append("private MqttUpdater ").append(rootNodeName)
-      .append(".").append(mqttUpdaterField()).append(" = new MqttUpdater();\n");
-
-    // mqttSetHost(String host)
-    sb.append(ind(1)).append("public void ").append(rootNodeName).append(".")
-      .append(mqttSetHostMethod()).append("(String host) throws java.io.IOException {\n");
-    sb.append(ind(2)).append(mqttUpdaterField()).append(".setHost(host);\n");
-    sb.append(ind(1)).append("}\n");
-
-    // mqttSetHost(String host, int port)
-    sb.append(ind(1)).append("public void ").append(rootNodeName).append(".")
-      .append(mqttSetHostMethod()).append("(String host, int port) throws java.io.IOException {\n");
-    sb.append(ind(2)).append(mqttUpdaterField()).append(".setHost(host, port);\n");
-    sb.append(ind(1)).append("}\n\n");
-
-    // mqttWaitUntilReady
-    sb.append(ind(1)).append("public boolean ").append(rootNodeName).append(".")
-      .append(mqttWaitUntilReadyMethod()).append("(long time, java.util.concurrent.TimeUnit unit) {\n");
-    sb.append(ind(2)).append("return ").append(mqttUpdaterField()).append(".waitUntilReady(time, unit);\n");
-    sb.append(ind(1)).append("}\n\n");
-
-    // mqttClose
-    sb.append(ind(1)).append("public void ").append(rootNodeName).append(".")
-      .append(mqttCloseMethod()).append("() {\n");
-    sb.append(ind(2)).append(mqttUpdaterField()).append(".close();\n");
-    sb.append(ind(1)).append("}\n\n");
-
-    // mqttUpdater
-    sb.append(ind(1)).append("inh MqttUpdater ASTNode.").append(mqttUpdaterAttribute()).append("();\n");
-    for (Component child : rootNode.getComponentList()) {
-      if (child.isTypeComponent()) {
-        sb.append(ind(1)).append("eq ").append(rootNodeName)
-          .append(".get").append(child.getName()).append("().")
-          .append(mqttUpdaterAttribute()).append("() = ").append(mqttUpdaterField()).append(";\n");
-      }
-    }
-    sb.append("}\n\n");
-  }
-
-  public void Ros2Rag.generateGrammarExtension(StringBuilder sb) {
-    sb.append("aspect ROS2RAG {\n");
-
-    for (UpdateDefinition def : getUpdateDefinitionList()) {
-      def.generateAspect(sb);
-    }
-    for (MappingDefinition def : allMappingDefinitions()) {
-      def.generateAspect(sb);
-    }
-    for (DependencyDefinition def : getDependencyDefinitionList()) {
-      def.generateAspect(sb);
-    }
-    for (TokenComponent token : getProgram().allTokenComponents()) {
-      token.generateAspect(sb);
-    }
-
-    sb.append("}\n");
-  }
-
-  abstract void UpdateDefinition.generateAspect(StringBuilder sb);
-
-  String TokenUpdateDefinition.generateMappingApplication(StringBuilder sb, int indent,
-      String initialInputVariableName) {
-    final String resultVariablePrefix = "result";  // we do not need "_" here, because methodName begins with one
-    String inputVariableName = initialInputVariableName;
-    // last variable need to be declared before begin of try
-    MappingDefinition lastDefinition = effectiveMappings().get(effectiveMappings().size() - 1);
-    sb.append(ind(indent)).append(lastDefinition.getToType().prettyPrint()).append(" ")
-      .append(resultVariablePrefix).append(lastDefinition.methodName()).append(";\n");
-    sb.append(ind(indent)).append("try {\n");
-    for (MappingDefinition mappingDefinition : effectiveMappings()) {
-      String resultVariableName = resultVariablePrefix + mappingDefinition.methodName();
-      sb.append(ind(indent + 1));
-      if (mappingDefinition != lastDefinition) {
-        sb.append(mappingDefinition.getToType().prettyPrint()).append(" ");
-      }
-      sb.append(resultVariablePrefix).append(mappingDefinition.methodName())
-        .append(" = ").append(mappingDefinition.methodName()).append("(")
-        .append(inputVariableName).append(");\n");
-      inputVariableName = resultVariableName;
-    }
-    sb.append(ind(indent)).append("} catch (Exception e) {\n");
-    sb.append(ind(indent + 1)).append("e.printStackTrace();\n");
-    sb.append(ind(indent + 1)).append(preemptiveReturnStatement()).append("\n");
-    sb.append(ind(indent)).append("}\n");
-    if (!getAlwaysApply()) {
-      MappingDefinition lastMapping = effectiveMappings().get(effectiveMappings().size() - 1);
-      sb.append(ind(indent)).append("if (");
-      if (lastMapping.getToType().isArray()) {
-        sb.append("java.util.Arrays.equals(").append(preemptiveExpectedValue())
-          .append(", ").append(inputVariableName).append(")");
-      } else {
-        sb.append(preemptiveExpectedValue());
-        if (getToken().isPrimitiveType() && lastMapping.getToType().isPrimitiveType()) {
-          sb.append(" == ").append(inputVariableName);
-        } else if (lastMapping.isDefaultMappingDefinition()) {
-          sb.append(" != null && ").append(preemptiveExpectedValue()).append(".equals(")
-            .append(inputVariableName).append(")");
-        } else {
-          sb.append(" != null ? ").append(preemptiveExpectedValue()).append(".equals(")
-            .append(inputVariableName).append(")").append(" : ")
-            .append(inputVariableName).append(" == null");
-        }
-      }
-      sb.append(") { ").append(preemptiveReturnStatement()).append(" }\n");
-    }
-    return inputVariableName;
-  }
-
-  syn String TokenUpdateDefinition.preemptiveExpectedValue();
-  eq ReadFromMqttDefinition.preemptiveExpectedValue() = "get" + getToken().getName() + "()";
-  eq WriteToMqttDefinition.preemptiveExpectedValue() = lastValue();
-
-  syn String TokenUpdateDefinition.preemptiveReturnStatement();
-  eq ReadFromMqttDefinition.preemptiveReturnStatement() = "return;";
-  eq WriteToMqttDefinition.preemptiveReturnStatement() = "return false;";
-
-  @Override
-  void ReadFromMqttDefinition.generateAspect(StringBuilder sb) {
-    sb.append(ind(1)).append("public void ").append(getToken().containingTypeDecl().getName()).append(".")
-      .append(connectMethod()).append("(String topic) {\n");
-    sb.append(ind(2)).append(mqttUpdaterAttribute()).append("().newConnection(topic, message -> {\n");
-    String lastResult = generateMappingApplication(sb, 3, "message");
-    if (loggingEnabledForReads) {
-      sb.append(ind(3)).append("System.out.println(\"[Read] \" + topic + \" -> ")
-        .append(getToken().getName()).append(" = \" + ").append(lastResult)
-        .append(");\n");
-    }
-    sb.append(ind(3)).append("set").append(getToken().getName()).append("(").append(lastResult).append(");\n");
-    sb.append(ind(2)).append("});\n");
-    sb.append(ind(1)).append("}\n\n");
-  }
-
-  @Override
-  void WriteToMqttDefinition.generateAspect(StringBuilder sb) {
-    String parentTypeName = getToken().containingTypeDecl().getName();
-    // fields
-    sb.append(ind(1)).append("private String ").append(parentTypeName).append(".")
-      .append(writeTopic()).append(" = null;\n");
-    sb.append(ind(1)).append("private byte[] ").append(parentTypeName).append(".")
-      .append(lastValue()).append(" = null;\n");
-
-    // connect method
-    sb.append(ind(1)).append("public void ").append(parentTypeName).append(".")
-      .append(connectMethod()).append("(String topic, boolean writeCurrentValue) {\n");
-    sb.append(ind(2)).append(writeTopic()).append(" = topic;\n");
-    sb.append(ind(2)).append(updateMethod()).append("();\n");
-    sb.append(ind(2)).append("if (writeCurrentValue) {\n");
-    sb.append(ind(3)).append(writeMethod()).append("();\n");
-    sb.append(ind(2)).append("}\n");
-    sb.append(ind(1)).append("}\n\n");
+  // naming copy attributes
+  // --- mqttUpdaterAttribute ---
+  inh String UpdateDefinition.mqttUpdaterAttribute();
+  inh String MappingDefinition.mqttUpdaterAttribute();
+  inh String DependencyDefinition.mqttUpdaterAttribute();
+  eq Ros2Rag.getChild().mqttUpdaterAttribute() = mqttUpdaterAttribute();
 
-    // update method
-    sb.append(ind(1)).append("protected boolean ").append(parentTypeName).append(".")
-      .append(updateMethod()).append("() {\n");
-    sb.append(ind(2)).append(tokenResetMethod()).append("();\n");
-    String lastResult = generateMappingApplication(sb, 2, "get" + getToken().getName() + "()");
-    sb.append(ind(2)).append(lastValue()).append(" = ").append(lastResult).append(";\n");
-    sb.append(ind(2)).append("return true;\n");
-    sb.append(ind(1)).append("}\n\n");
+  // --- rootNodeName ---
+  syn String ASTNode.rootNodeName() = rootNode.getName();
 
-    // write method
-    sb.append(ind(1)).append("protected void ").append(parentTypeName).append(".")
-      .append(writeMethod()).append("() {\n");
-    if (loggingEnabledForWrites) {
-      sb.append(ind(2)).append("System.out.println(\"[Write] ").append(getToken().getName())
-        .append(" = \" + ")
-        .append("get").append(getToken().getName()).append("() + \" -> \" + ")
-        .append(writeTopic()).append(");\n");
-    }
-    // _mqttUpdater().publish(${writeTopic()}, ${lastValue()});
-    sb.append(ind(2)).append(mqttUpdaterAttribute()).append("().publish(")
-      .append(writeTopic()).append(", ").append(lastValue()).append(");\n");
-    sb.append(ind(1)).append("}\n\n");
+  public String Ros2Rag.generateAspect(String rootNodeName) {
+    rootNode = getProgram().resolveTypeDecl(rootNodeName);
+    return toMustache().generateAspect();
   }
 
-  void MappingDefinition.generateAspect(StringBuilder sb) {
-    sb.append(ind(1)).append("protected static ").append(getToType().prettyPrint())
-      .append(" ASTNode.").append(methodName()).append("(")
-      .append(getFromType().prettyPrint()).append(" ").append(getFromVariableName())
-      .append(") throws Exception {\n");
-    for (String line : getContent().split("\n")) {
-      if (!line.trim().isEmpty()) {
-        sb.append(ind(2)).append(line).append("\n");
+  public String MRos2Rag.generateAspect() {
+    StringBuilder sb = new StringBuilder();
+    com.github.mustachejava.reflect.ReflectionObjectHandler roh = new com.github.mustachejava.reflect.ReflectionObjectHandler() {
+      @Override
+      public com.github.mustachejava.Binding createBinding(String name, final com.github.mustachejava.TemplateContext tc, com.github.mustachejava.Code code) {
+        return new com.github.mustachejava.reflect.GuardedBinding(this, name, tc, code) {
+          @Override
+          protected synchronized com.github.mustachejava.util.Wrapper getWrapper(String name, java.util.List<Object> scopes) {
+            com.github.mustachejava.util.Wrapper wrapper = super.getWrapper(name, scopes);
+            if (wrapper instanceof com.github.mustachejava.reflect.MissingWrapper) {
+              throw new com.github.mustachejava.MustacheException(name + " not found in " + tc);
+            }
+            return wrapper;
+          }
+        };
       }
-    }
-    sb.append(ind(1)).append("}\n\n");
-  }
-
-  void DependencyDefinition.generateAspect(StringBuilder sb) {
-    String targetParentTypeName = getTarget().containingTypeDecl().getName();
-    String sourceParentTypeName = getSource().containingTypeDecl().getName();
-
-    // dependency method
-    sb.append(ind(1)).append("public void ").append(targetParentTypeName).append(".")
-      .append(dependencyMethod()).append("(").append(sourceParentTypeName).append(" source) {\n");
-    sb.append(ind(2)).append("add").append(internalRelationPrefix()).append("Source(source);\n");
-    sb.append(ind(1)).append("}\n\n");
-  }
-
-  void TokenComponent.generateAspect(StringBuilder sb) {
-    if (getDependencySourceDefinitionList().isEmpty()) { return; }
-
-    String parentTypeName = containingTypeDecl().getName();
-    // virtual setter
-    sb.append(ind(1)).append("public ").append(parentTypeName).append(" ")
-      .append(parentTypeName).append(".set").append(getName()).append("(")
-      .append(getJavaTypeUse().prettyPrint()).append(" value) {\n");
-    sb.append(ind(2)).append("set").append(internalName()).append("(value);\n");
-
-    for (DependencyDefinition dependencyDefinition : getDependencySourceDefinitionList()) {
-      String targetParentTypeName = dependencyDefinition.getTarget().containingTypeDecl().getName();
-      sb.append(ind(2)).append("for (").append(targetParentTypeName).append(" target : get")
-        .append(dependencyDefinition.internalRelationPrefix()).append("TargetList()) {\n");
-      sb.append(ind(3)).append("if (target.")
-        .append(dependencyDefinition.targetUpdateDefinition().updateMethod())
-        .append("()) {\n");
-      sb.append(ind(4)).append("target.")
-        .append(dependencyDefinition.targetUpdateDefinition().writeMethod())
-        .append("();\n");
-      sb.append(ind(3)).append("}\n");
-      sb.append(ind(2)).append("}\n");
-    }
-    sb.append(ind(2)).append("return this;\n");
-    sb.append(ind(1)).append("}\n\n");
-
-    // virtual getter
-    sb.append(ind(1)).append("public ").append(getJavaTypeUse().prettyPrint())
-      .append(" ").append(parentTypeName).append(".get").append(getName()).append("() {\n");
-    sb.append(ind(2)).append("return get").append(internalName()).append("();\n");
-    sb.append(ind(1)).append("}\n\n");
+    };
+    com.github.mustachejava.DefaultMustacheFactory mf = new com.github.mustachejava.DefaultMustacheFactory();
+    mf.setObjectHandler(roh);
+    com.github.mustachejava.Mustache m = mf.compile("ros2rag.mustache");
+    m.execute(new java.io.PrintWriter(new org.jastadd.ros2rag.compiler.AppendableWriter(sb)), this);
+    return sb.toString();
   }
 }
 
diff --git a/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/AppendableWriter.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/AppendableWriter.java
new file mode 100644
index 0000000000000000000000000000000000000000..97680b18dc4fd1d8af3df75f35221e059355e19e
--- /dev/null
+++ b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/AppendableWriter.java
@@ -0,0 +1,37 @@
+package org.jastadd.ros2rag.compiler;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Writer appending to a StringBuilder.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class AppendableWriter extends Writer {
+  private final StringBuilder sb;
+
+  public AppendableWriter(StringBuilder sb) {
+    this.sb = sb;
+  }
+
+  @Override
+  public void write(char[] chars, int off, int len) throws IOException {
+    sb.append(chars, off, len);
+  }
+
+  @Override
+  public void write(String str) throws IOException {
+    sb.append(str);
+  }
+
+  @Override
+  public void flush() {
+
+  }
+
+  @Override
+  public void close() {
+
+  }
+}
diff --git a/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
index ee575b9495f53eca9925c6828ba96225734fe31a..8428bf27961070afa6410a349b3412e7bff729e6 100644
--- a/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
+++ b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
@@ -37,6 +37,8 @@ public class Compiler {
   }
 
   public void run(String[] args) throws CommandLineException, CompilerException {
+    System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
+    System.setProperty("mustache.debug", "true");
     options = new ArrayList<>();
     addOptions();
     commandLine = new CommandLine(options);
diff --git a/ros2rag.base/src/main/resources/dependencyDefinition.mustache b/ros2rag.base/src/main/resources/dependencyDefinition.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..b8d74ffe8f8aa5aaa8979682e4442215af1974e8
--- /dev/null
+++ b/ros2rag.base/src/main/resources/dependencyDefinition.mustache
@@ -0,0 +1,3 @@
+  public void {{targetParentTypeName}}.{{dependencyMethod}}({{sourceParentTypeName}} source) {
+    add{{internalRelationPrefix}}Source(source);
+  }
diff --git a/ros2rag.base/src/main/resources/mappingApplication.mustache b/ros2rag.base/src/main/resources/mappingApplication.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..95ce2fe29329d87f880497b37697d50c8c0687be
--- /dev/null
+++ b/ros2rag.base/src/main/resources/mappingApplication.mustache
@@ -0,0 +1,14 @@
+{{lastDefinitionToType}} {{resultVarPrefix}}{{lastDefinitionName}};
+try {
+  {{#InnerMappingDefinitions}}
+  {{^last}}{{ToType}} {{/last}}{{resultVarPrefix}}{{methodName}} = {{methodName}}({{inputVarName}});{{!inputVarName has to be computed beforehand}}
+  {{/InnerMappingDefinitions}}
+} catch (Exception e) {
+  e.printStackTrace();
+  {{preemptiveReturn}}
+}
+{{^alwaysApply}}
+if ({{{condition}}}) {
+  {{preemptiveReturn}}
+}
+{{/alwaysApply}}
diff --git a/ros2rag.base/src/main/resources/mappingDefinition.mustache b/ros2rag.base/src/main/resources/mappingDefinition.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..920c5a610b0ce0cff1687122c97b6e18ce63bf7f
--- /dev/null
+++ b/ros2rag.base/src/main/resources/mappingDefinition.mustache
@@ -0,0 +1,3 @@
+  protected static {{toType}} ASTNode.{{methodName}}({{fromType}} {{fromVariableName}}) throws Exception {
+    {{{content}}}{{!maybe print line by line to get better indentation}}
+  }
diff --git a/ros2rag.base/src/main/resources/mqtt.mustache b/ros2rag.base/src/main/resources/mqtt.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..2474e16525f6058540c3d73191369f4d2d056c4f
--- /dev/null
+++ b/ros2rag.base/src/main/resources/mqtt.mustache
@@ -0,0 +1,22 @@
+aspect MQTT {
+  private MqttUpdater {{rootNodeName}}.{{mqttUpdaterField}} = new MqttUpdater();
+  public void {{rootNodeName}}.{{mqttSetHostMethod}}(String host) throws java.io.IOException {
+    {{mqttUpdaterField}}.setHost(host);
+  }
+  public void {{rootNodeName}}.{{mqttSetHostMethod}}(String host, int port) throws java.io.IOException {
+    {{mqttUpdaterField}}.setHost(host, port);
+  }
+
+  public boolean {{rootNodeName}}.{{mqttWaitUntilReadyMethod}}(long time, java.util.concurrent.TimeUnit unit) {
+    return {{mqttUpdaterField}}.waitUntilReady(time, unit);
+  }
+
+  public void {{rootNodeName}}.{{mqttCloseMethod}}() {
+    {{mqttUpdaterField}}.close();
+  }
+
+  inh MqttUpdater ASTNode.{{mqttUpdaterAttribute}}();
+  {{#getRootTypeComponents}}
+  eq {{rootNodeName}}.get{{name}}().{{mqttUpdaterAttribute}}() = {{mqttUpdaterField}};
+  {{/getRootTypeComponents}}
+}
diff --git a/ros2rag.base/src/main/resources/readDefinition.mustache b/ros2rag.base/src/main/resources/readDefinition.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..f908ef166db8eb781d7b7d46e7bae1719ea5fb1d
--- /dev/null
+++ b/ros2rag.base/src/main/resources/readDefinition.mustache
@@ -0,0 +1,9 @@
+  public void {{parentTypeName}}.{{connectMethod}}(String topic) {
+    {{mqttUpdaterAttribute}}().newConnection(topic, message -> {
+      {{> mappingApplication}}
+      {{#loggingEnabledForReads}}
+      System.out.println("[Read] " + topic + " -> {{tokenName}} = " + {{lastResult}});{{!lastResult has to be a new attribute}}
+      {{/loggingEnabledForReads}}
+      set{{tokenName}}({{lastResult}});
+    });
+  }
diff --git a/ros2rag.base/src/main/resources/ros2rag.mustache b/ros2rag.base/src/main/resources/ros2rag.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..dd5e73cf554e35cc20da014b2d83f0db1b88521b
--- /dev/null
+++ b/ros2rag.base/src/main/resources/ros2rag.mustache
@@ -0,0 +1,22 @@
+{{> mqtt}}
+aspect ROS2RAG {
+  {{#ReadDefinitions}}
+    {{> readDefinition}}
+  {{/ReadDefinitions}}
+
+  {{#WriteDefinitions}}
+    {{> writeDefinition}}
+  {{/WriteDefinitions}}
+
+  {{#MappingDefinitions}}
+    {{> mappingDefinition}}
+  {{/MappingDefinitions}}
+
+  {{#DependencyDefinitions}}
+    {{> dependencyDefinition}}
+  {{/DependencyDefinitions}}
+
+  {{#TokenComponents}}
+    {{> tokenComponent}}
+  {{/TokenComponents}}
+}
diff --git a/ros2rag.base/src/main/resources/tokenComponent.mustache b/ros2rag.base/src/main/resources/tokenComponent.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..a3615c4526e5725bdd49121b16a87a011562f0cd
--- /dev/null
+++ b/ros2rag.base/src/main/resources/tokenComponent.mustache
@@ -0,0 +1,17 @@
+  public {{parentTypeName}} {{parentTypeName}}.set{{name}}({{javaType}} value) {
+    set{{internalName}}(value);
+    {{#DependencyDefinitions}}
+    for ({{targetParentTypeName}} target : get{{internalRelationPrefix}}TargetList()) {
+      {{#targetUpdateDefinition}}
+      if (target.{{updateMethod}}()) {
+        target.{{writeMethod}}();
+      }
+      {{/targetUpdateDefinition}}
+    }
+    {{/DependencyDefinitions}}
+    return this;
+  }
+
+  public {{javaType}} {{parentTypeName}}.get{{name}}() {
+    return get{{internalName}}();
+  }
diff --git a/ros2rag.base/src/main/resources/writeDefinition.mustache b/ros2rag.base/src/main/resources/writeDefinition.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..3d8aab888ed0a39131f5cf00b20cfa2e9ade9048
--- /dev/null
+++ b/ros2rag.base/src/main/resources/writeDefinition.mustache
@@ -0,0 +1,24 @@
+  private String {{parentTypeName}}.{{writeTopic}} = null;
+  private byte[] {{parentTypeName}}.{{lastValue}} = null;
+
+  public void {{parentTypeName}}.{{connectMethod}}(String topic, boolean writeCurrentValue) {
+    {{writeTopic}} = topic;
+    {{updateMethod}}();
+    if (writeCurrentValue) {
+      {{writeMethod}}();
+    }
+  }
+
+  protected boolean {{parentTypeName}}.{{updateMethod}}() {
+    {{tokenResetMethod}}();
+    {{> mappingApplication}}
+    {{lastValue}} = {{lastResult}};
+    return true;
+  }
+
+  protected void {{parentTypeName}}.{{writeMethod}}() {
+    {{#loggingEnabledForWrites}}
+    System.out.println("[Write] {{tokenName}} = " + get{{tokenName}}() + " -> " + {{writeTopic}});
+    {{/loggingEnabledForWrites}}
+    {{mqttUpdaterAttribute}}().publish({{writeTopic}}, {{lastValue}});
+  }
diff --git a/ros2rag.receiverstub/build.gradle b/ros2rag.receiverstub/build.gradle
index 0fe6ce127a31540f68c2a16f6f0a7bdf6326c249..59f2bcc96d6aa72f86c9a96f0221ada6a7c65139 100644
--- a/ros2rag.receiverstub/build.gradle
+++ b/ros2rag.receiverstub/build.gradle
@@ -25,8 +25,8 @@ dependencies {
     implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: "${jackson_version}"
     implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${jackson_version}"
     implementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
-    compile 'com.google.protobuf:protobuf-java:3.0.0'
-    compile group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'
+    implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.0.0'
+    implementation group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'
 
     protobuf files("$projectDir/../ros2rag.example/src/main/proto")
 }
diff --git a/ros2rag.receiverstub/src/main/java/de/tudresden/inf/st/ros2rag/receiverstub/Main.java b/ros2rag.receiverstub/src/main/java/de/tudresden/inf/st/ros2rag/receiverstub/Main.java
index f954120848dad5a83d498f91c6c33bdceb32969f..f4a635989356bb01b2940e3ca93d76bd034e98ba 100644
--- a/ros2rag.receiverstub/src/main/java/de/tudresden/inf/st/ros2rag/receiverstub/Main.java
+++ b/ros2rag.receiverstub/src/main/java/de/tudresden/inf/st/ros2rag/receiverstub/Main.java
@@ -4,9 +4,10 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.protobuf.InvalidProtocolBufferException;
 import config.Dataconfig.DataConfig;
 import config.Robotconfig.RobotConfig;
+import de.tudresden.inf.st.ros2rag.starter.Util;
 import de.tudresden.inf.st.ros2rag.starter.ast.MqttUpdater;
 import de.tudresden.inf.st.ros2rag.starter.data.DataConfiguration;
-import de.tudresden.inf.st.ros2rag.starter.data.DataJoint;
+import de.tudresden.inf.st.ros2rag.starter.data.DataConfiguration.ActualConfiguration;
 import org.apache.logging.log4j.Level;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
@@ -16,6 +17,7 @@ import java.io.File;
 import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class Main {
 
@@ -27,23 +29,24 @@ public class Main {
     ObjectMapper mapper = new ObjectMapper();
     File configFile = new File(args[0]);
     System.out.println("Using config file: " + configFile.getAbsolutePath());
-    DataConfiguration config = mapper.readValue(configFile, DataConfiguration.class);
+    ActualConfiguration config = mapper.readValue(configFile, DataConfiguration.class).panda_mqtt_connector;
     main.run(config);
   }
 
-  private void run(DataConfiguration config) throws IOException, InterruptedException {
+  private void run(ActualConfiguration config) throws IOException, InterruptedException {
     final CountDownLatch finish = new CountDownLatch(1);
 
-    int topicMaxLength = 0;
+    AtomicInteger topicMaxLength = new AtomicInteger();
     MqttUpdater receiver = new MqttUpdater("receiver stub");
-    receiver.setHost(config.mqttHost);
+    Util.setMqttHost(receiver, config);
     receiver.waitUntilReady(2, TimeUnit.SECONDS);
-    receiver.newConnection(config.robotConfigTopic, this::printRobotConfig);
-    receiver.newConnection(config.dataConfigTopic, this::printDataConfig);
-    for (DataJoint joint : config.joints) {
-      receiver.newConnection(joint.topic, this::printPandaLinkState);
-      topicMaxLength = Math.max(topicMaxLength, joint.topic.length());
-    }
+    receiver.newConnection(config.topics.robotConfig, this::printRobotConfig);
+    receiver.newConnection(config.topics.dataConfig, this::printDataConfig);
+
+    Util.iterateLinks((isEndEffector, topic, name) -> {
+      receiver.newConnection(topic, this::printPandaLinkState);
+      topicMaxLength.set(Math.max(topicMaxLength.get(), topic.length()));
+    }, config);
     this.topicPattern = "%" + topicMaxLength + "s";
 
     receiver.newConnection("components", bytes -> {
@@ -59,11 +62,7 @@ public class Main {
     });
 
     Runtime.getRuntime().addShutdownHook(new Thread(receiver::close));
-    if (config.exitAfterSeconds > 0) {
-      finish.await(config.exitAfterSeconds, TimeUnit.SECONDS);
-    } else {
-      finish.await();
-    }
+    finish.await();
     receiver.close();
   }
 
diff --git a/ros2rag.senderstub/build.gradle b/ros2rag.senderstub/build.gradle
index 362a729af0aa2f6b7335b4cbd6a1164c22fe2dbf..a7900a991d5a4b44ebb52e8e7b9d81f4d0e11fe2 100644
--- a/ros2rag.senderstub/build.gradle
+++ b/ros2rag.senderstub/build.gradle
@@ -25,8 +25,8 @@ dependencies {
     implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: "${jackson_version}"
     implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${jackson_version}"
     implementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
-    compile 'com.google.protobuf:protobuf-java:3.0.0'
-    compile group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'
+    implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.0.0'
+    implementation group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'
 
     protobuf files("$projectDir/../ros2rag.example/src/main/proto")
 }
diff --git a/ros2rag.starter/build.gradle b/ros2rag.starter/build.gradle
index 42aa7ecc0de7c5201e7847f41d607a79e8cbc1e6..b4aec98a67e1ddc6657e293bb90d4d9dfdf5ec29 100644
--- a/ros2rag.starter/build.gradle
+++ b/ros2rag.starter/build.gradle
@@ -30,9 +30,10 @@ dependencies {
     baseRuntimeClasspath project (':ros2rag.base')
     implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-xml', version: "${jackson_version}"
     implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "${jackson_version}"
+    implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: "${jackson_version}"
     implementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
-    compile 'com.google.protobuf:protobuf-java:3.0.0'
-    compile group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'
+    implementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.0.0'
+    implementation group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'
 
     jastadd2 "org.jastadd:jastadd:2.3.4"
 }
diff --git a/ros2rag.starter/src/main/jastadd/Computation.jrag b/ros2rag.starter/src/main/jastadd/Computation.jrag
index bddac5a4db5245259f19cfb72c86b9533fbf3fa7..d7c003c29cffe8d885430c929c0ffa89ccc514b3 100644
--- a/ros2rag.starter/src/main/jastadd/Computation.jrag
+++ b/ros2rag.starter/src/main/jastadd/Computation.jrag
@@ -1,8 +1,8 @@
 aspect Computation {
   syn boolean RobotArm.isInSafetyZone() {
     System.out.println("isInSafetyZone()");
-    for (Joint joint : getJointList()) {
-      if (model().getZoneModel().isInSafetyZone(joint.getCurrentPosition())) {
+    for (Link link : getLinkList()) {
+      if (model().getZoneModel().isInSafetyZone(link.getCurrentPosition())) {
         return true;
       }
     }
diff --git a/ros2rag.starter/src/main/jastadd/Definitions.ros2rag b/ros2rag.starter/src/main/jastadd/Definitions.ros2rag
index 94c7aa11611efe3a594bf5b1a8652ffdccd8cc57..99aec4416578d7af313846eb2ed33645b58cc358 100644
--- a/ros2rag.starter/src/main/jastadd/Definitions.ros2rag
+++ b/ros2rag.starter/src/main/jastadd/Definitions.ros2rag
@@ -2,11 +2,11 @@
  * Version 2020-05-28
  */
 // --- update definitions ---
-read Joint.CurrentPosition using ParseLinkState, LinkStateToIntPosition ;
+read Link.CurrentPosition using ParseLinkState, LinkStateToIntPosition ;
 write RobotArm.AppropriateSpeed using CreateSpeedMessage, SerializeRobotConfig ;
 
 // --- dependency definitions ---
-RobotArm.AppropriateSpeed canDependOn Joint.CurrentPosition as dependency1 ;
+RobotArm.AppropriateSpeed canDependOn Link.CurrentPosition as dependency1 ;
 
 // --- mapping definitions ---
 ParseLinkState maps byte[] bytes to panda.Linkstate.PandaLinkState {:
diff --git a/ros2rag.starter/src/main/jastadd/Navigation.jrag b/ros2rag.starter/src/main/jastadd/Navigation.jrag
index 855a2d87d6bbb1374c5ae23569435fe2a9b329ac..db53288f4c4b61f0c5d315f8a18ab3f5bac9b720 100644
--- a/ros2rag.starter/src/main/jastadd/Navigation.jrag
+++ b/ros2rag.starter/src/main/jastadd/Navigation.jrag
@@ -2,7 +2,7 @@ aspect Navigation {
   inh Model RobotArm.model();
   eq Model.getRobotArm().model() = this;
 
-  inh RobotArm Joint.containingRobotArm();
-  eq RobotArm.getJoint().containingRobotArm() = this;
+  inh RobotArm Link.containingRobotArm();
+  eq RobotArm.getLink().containingRobotArm() = this;
   eq RobotArm.getEndEffector().containingRobotArm() = this;
 }
diff --git a/ros2rag.starter/src/main/jastadd/RobotModel.relast b/ros2rag.starter/src/main/jastadd/RobotModel.relast
index 415d3e5e25b8d03fa1d53bc6f95c05ad5e1987d8..b8d932ae470e4bf3dc433f8610eec1f05016e595 100644
--- a/ros2rag.starter/src/main/jastadd/RobotModel.relast
+++ b/ros2rag.starter/src/main/jastadd/RobotModel.relast
@@ -4,10 +4,10 @@ ZoneModel ::= SafetyZone:Zone* ;
 
 Zone ::= Coordinate* ;
 
-RobotArm ::= Joint* EndEffector /<AppropriateSpeed:double>/ ;
+RobotArm ::= Link* EndEffector /<AppropriateSpeed:double>/ ;
 
-Joint ::= <Name:String> <CurrentPosition:IntPosition> ;
+Link ::= <Name:String> <CurrentPosition:IntPosition> ;
 
-EndEffector : Joint;
+EndEffector : Link;
 
 Coordinate ::= <Position:IntPosition> ;
diff --git a/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/StarterMain.java b/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/StarterMain.java
index 66a48f60b39cb7c5d48af9993eeca7159dc92e51..570b219e35c9f6defbe5d6041eece66a21af508e 100644
--- a/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/StarterMain.java
+++ b/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/StarterMain.java
@@ -1,15 +1,18 @@
 package de.tudresden.inf.st.ros2rag.starter;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 import config.Dataconfig;
 import de.tudresden.inf.st.ros2rag.starter.ast.*;
 import de.tudresden.inf.st.ros2rag.starter.data.DataConfiguration;
-import de.tudresden.inf.st.ros2rag.starter.data.DataJoint;
+import de.tudresden.inf.st.ros2rag.starter.data.DataConfiguration.ActualConfiguration;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Map.Entry;
+import java.util.SortedMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -25,26 +28,26 @@ public class StarterMain {
   private Model model;
 
   public void run(String[] args) throws IOException, InterruptedException {
-    File configFile = new File(args[0]);
-
-    final int[][] safetyZoneCoordinates = {
-        {1, 1, 0},
-        {-1, -1, 1}
-    };
+    File configFile = new File(args.length == 0 ? "./src/main/resources/config.yaml" : args[0]);
 
     // --- No configuration below this line ---
 
-    ObjectMapper mapper = new ObjectMapper();
+    ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
     System.out.println("Using config file: " + configFile.getAbsolutePath());
-    DataConfiguration config = mapper.readValue(configFile, DataConfiguration.class);
+    ActualConfiguration config = mapper.readValue(configFile, DataConfiguration.class).panda_mqtt_connector;
 
     model = new Model();
-    model.MqttSetHost(config.mqttHost);
+    Util.setMqttHost(model::MqttSetHost, config);
 
     ZoneModel zoneModel = new ZoneModel();
 
     Zone safetyZone = new Zone();
-    for (int[] coordinate : safetyZoneCoordinates) {
+    for (String zone : config.zones) {
+      int[] coordinate = {0, 0, 0};
+      String[] zoneSplit = zone.split(" ");
+      for (int i = 0; i < zoneSplit.length; i++) {
+        coordinate[i] = Integer.parseInt(zoneSplit[i]);
+      }
       safetyZone.addCoordinate(new Coordinate(
           IntPosition.of(coordinate[0], coordinate[1], coordinate[2])));
     }
@@ -55,24 +58,23 @@ public class StarterMain {
     RobotArm robotArm = new RobotArm();
     model.setRobotArm(robotArm);
 
-    for (DataJoint dataJoint : config.joints) {
-      final Joint jointOrEndEffector;
-      if (dataJoint.isEndEffector) {
+    Util.iterateLinks((isEndEffector, topic, name) -> {
+      Link link;
+      if (isEndEffector) {
         EndEffector endEffector = new EndEffector();
         robotArm.setEndEffector(endEffector);
-        jointOrEndEffector = endEffector;
+        link = endEffector;
       } else {
-        Joint joint = new Joint();
-        robotArm.addJoint(joint);
-        jointOrEndEffector = joint;
+        link = new Link();
+        robotArm.addLink(link);
       }
-      jointOrEndEffector.setName(dataJoint.name);
-      jointOrEndEffector.setCurrentPosition(makePosition(0, 0, 0));
-      robotArm.addDependency1(jointOrEndEffector);
-      jointOrEndEffector.connectCurrentPosition(dataJoint.topic);
-    }
+      link.setName(name);
+      link.setCurrentPosition(makePosition(0, 0, 0));
+      link.containingRobotArm().addDependency1(link);
+      link.connectCurrentPosition(topic);
+    }, config);
 
-    robotArm.connectAppropriateSpeed(config.robotConfigTopic, true);
+    robotArm.connectAppropriateSpeed(config.topics.robotConfig, true);
 
     logStatus("Start", robotArm);
     CountDownLatch exitCondition = new CountDownLatch(1);
@@ -81,12 +83,12 @@ public class StarterMain {
     logger.info("To exit the system cleanly, send a message to the topic 'exit', or use Ctrl+C.");
 
     mainHandler = new MqttUpdater("mainHandler");
-    mainHandler.setHost(config.mqttHost);
+    Util.setMqttHost(mainHandler, config);
     mainHandler.waitUntilReady(2, TimeUnit.SECONDS);
     mainHandler.newConnection("exit", bytes -> exitCondition.countDown());
     mainHandler.newConnection("model", bytes -> logStatus(new String(bytes), robotArm));
 
-    sendInitialDataConfig(mainHandler, config.dataConfigTopic);
+    sendInitialDataConfig(mainHandler, config.topics.dataConfig);
 
     Runtime.getRuntime().addShutdownHook(new Thread(this::close));
 
@@ -110,7 +112,7 @@ public class StarterMain {
     StringBuilder sb = new StringBuilder(prefix).append("\n")
         .append("robotArm.isInSafetyZone: ").append(robotArm.isInSafetyZone())
         .append(", robotArm.getAppropriateSpeed = ").append(robotArm.getAppropriateSpeed()).append("\n");
-    for (Joint joint : robotArm.getJointList()) {
+    for (Link joint : robotArm.getLinkList()) {
       sb.append(joint.getName()).append(": ").append(joint.getCurrentPosition()).append("\n");
     }
     sb.append("endEffector ").append(robotArm.getEndEffector().getName()).append(": ")
diff --git a/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/Util.java b/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/Util.java
new file mode 100644
index 0000000000000000000000000000000000000000..22a27e5cfbfac0f1cec983238a34ffec14501aad
--- /dev/null
+++ b/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/Util.java
@@ -0,0 +1,67 @@
+package de.tudresden.inf.st.ros2rag.starter;
+
+import de.tudresden.inf.st.ros2rag.starter.ast.Link;
+import de.tudresden.inf.st.ros2rag.starter.ast.MqttUpdater;
+import de.tudresden.inf.st.ros2rag.starter.data.DataConfiguration;
+import de.tudresden.inf.st.ros2rag.starter.data.DataConfiguration.ActualConfiguration;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.SortedMap;
+
+/**
+ * Helper method dealing with config.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class Util {
+  public static void setMqttHost(RootElement model, ActualConfiguration config) throws IOException {
+    HostAndPort hostAndPort = split(config.server);
+    model.MqttSetHost(hostAndPort.host, hostAndPort.port);
+  }
+
+  public static void setMqttHost(MqttUpdater handler, ActualConfiguration config) throws IOException {
+    HostAndPort hostAndPort = split(config.server);
+    handler.setHost(hostAndPort.host, hostAndPort.port);
+  }
+
+  public static void iterateLinks(HandleLink callback, ActualConfiguration config) {
+    for (Map.Entry<String, SortedMap<String, String>> dataRobot : config.parts.entrySet()) {
+      String topicPrefix = dataRobot.getKey() + "/";
+      for (Map.Entry<String, String> dataLink : dataRobot.getValue().entrySet()) {
+        String name = dataLink.getKey();
+        callback.handle(false, topicPrefix + name, name);
+      }
+    }
+    for (Map.Entry<String, SortedMap<String, String>> dataRobot : config.end_effectors.entrySet()) {
+      String topicPrefix = dataRobot.getKey() + "/";
+      for (Map.Entry<String, String> dataLink : dataRobot.getValue().entrySet()) {
+        String name = dataLink.getKey();
+        callback.handle(true, topicPrefix + name, name);
+      }
+    }
+  }
+
+  private static HostAndPort split(String serverString) {
+    HostAndPort result = new HostAndPort();
+    String[] serverTokens = serverString.replace("tcp://", "").split(":");
+    result.host = serverTokens[0];
+    result.port = Integer.parseInt(serverTokens[1]);
+    return result;
+  }
+
+  private static class HostAndPort {
+    String host;
+    int port;
+  }
+
+  @FunctionalInterface
+  public interface RootElement {
+    void MqttSetHost(String host, int port) throws IOException;
+  }
+
+  @FunctionalInterface
+  public interface HandleLink {
+    void handle(boolean isEndEffector, String topic, String name);
+  }
+}
diff --git a/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/data/DataConfiguration.java b/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/data/DataConfiguration.java
index b2ec3639dc485971c58a91b0cd4916b37e79c0b2..092d498d6fcb7535eb016d06efeffeb54a8b9a52 100644
--- a/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/data/DataConfiguration.java
+++ b/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/data/DataConfiguration.java
@@ -1,7 +1,9 @@
 package de.tudresden.inf.st.ros2rag.starter.data;
 
-import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
 
 /**
  * Data class for initial configuration.
@@ -9,9 +11,17 @@ import java.util.List;
  * @author rschoene - Initial contribution
  */
 public class DataConfiguration {
-  public List<DataJoint> joints = new ArrayList<>();
-  public String robotConfigTopic;
-  public String dataConfigTopic;
-  public int exitAfterSeconds = 0;
-  public String mqttHost = "localhost";
+  public ActualConfiguration panda_mqtt_connector;
+  public static class ActualConfiguration {
+    public String server = "tcp://localhost:1883";
+    public DataTopics topics;
+    public int zone_size;
+    public List<String> zones;
+    public Map<String, SortedMap<String, String>> parts;
+    public Map<String, SortedMap<String, String>> end_effectors;
+  }
+  public static class DataTopics {
+    public String robotConfig;
+    public String dataConfig;
+  }
 }
diff --git a/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/data/DataJoint.java b/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/data/DataJoint.java
deleted file mode 100644
index 2431827d30a9742f0366283c39d1dc156b876da2..0000000000000000000000000000000000000000
--- a/ros2rag.starter/src/main/java/de/tudresden/inf/st/ros2rag/starter/data/DataJoint.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package de.tudresden.inf.st.ros2rag.starter.data;
-
-/**
- * Data class to describe a joint.
- *
- * @author rschoene - Initial contribution
- */
-public class DataJoint {
-  public String topic;
-  public String name;
-  public boolean isEndEffector = false;
-}
diff --git a/ros2rag.starter/src/main/resources/config.yaml b/ros2rag.starter/src/main/resources/config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a18ba849e3714314ed726f9e2780839dc1cae3ae
--- /dev/null
+++ b/ros2rag.starter/src/main/resources/config.yaml
@@ -0,0 +1,23 @@
+panda_mqtt_connector:
+  server: "tcp://localhost:1883"
+  topics:
+    robotConfig: "robotconfig"
+    dataConfig: "dataconfig"
+  zone_size: 0.5
+  zones:
+    - "1 1"
+    - "-1 -1 1"
+  parts:
+    panda:
+      Link0: "panda::panda_link0"
+      Link1: "panda::panda_link1"
+      Link2: "panda::panda_link2"
+      Link3: "panda::panda_link3"
+      Link4: "panda::panda_link4"
+      Link5: "panda::panda_link5"
+      Link6: "panda::panda_link6"
+      LeftFinger: "panda::panda_leftfinger"
+      RightFinger: "panda::panda_rightfinger"
+  end_effectors:
+    panda:
+      EndEffector: "panda::panda_link7"
diff --git a/ros2rag.tests/build.gradle b/ros2rag.tests/build.gradle
index 568f441a595ac78644f2d216c850ab92ef8660d9..add5c201afc959a8054b2f196b4ad59ca959b9a6 100644
--- a/ros2rag.tests/build.gradle
+++ b/ros2rag.tests/build.gradle
@@ -19,14 +19,15 @@ buildscript {
 }
 
 dependencies {
-    runtime 'org.jastadd:jastadd:2.3.4'
     implementation project(':ros2rag.base')
-    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.0'
-    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.0'
-    testImplementation 'org.assertj:assertj-core:3.12.1'
+
+    runtime group: 'org.jastadd', name: 'jastadd', version: '2.3.4'
+    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.0'
+    testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.4.0'
+    testImplementation group: 'org.assertj', name: 'assertj-core', version: '3.12.1'
     testImplementation group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15'
     testImplementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
-    testImplementation 'com.google.protobuf:protobuf-java:3.0.0'
+    testImplementation group: 'com.google.protobuf', name: 'protobuf-java', version: '3.0.0'
 }
 
 test {