diff --git a/src/main/jastadd/MustacheNodes.relast b/src/main/jastadd/MustacheNodes.relast
index 30277272d713c062307b3679dff7db75b71b91ef..87c4d35cd0883cd40a35b17ebb9eb8ce2ee1bd2a 100644
--- a/src/main/jastadd/MustacheNodes.relast
+++ b/src/main/jastadd/MustacheNodes.relast
@@ -1,2 +1,19 @@
-TypeComponentMustache ;
-rel TypeComponentMustache.TypeComponent -> TypeComponent ;
+//TypeComponentMustache ;
+//rel TypeComponentMustache.TypeComponent -> TypeComponent ;
+
+MRos2Rag ::= ReadDefinition:MReadDefinition* WriteDefinition:MWriteDefinition* MappingDefinition:MMappingDefinition* DependencyDefinition:MDependencyDefinition* TypeComponent:MTypeComponent*;
+MUpdateDefinition ::= <FirstInputVarName>;
+MReadDefinition : MUpdateDefinition;
+MWriteDefinition : MUpdateDefinition;
+MMappingDefinition;
+MDependencyDefinition;
+MTypeComponent;
+
+MUpdateDefinition.InnerMappingDefinition* -> MInnerMappingDefinition;
+MInnerMappingDefinition.MappingDefinition -> MappingDefinition;
+MReadDefinition.ReadFromMqttDefinition -> ReadFromMqttDefinition;
+MWriteDefinition.WriteToMqttDefinition -> WriteToMqttDefinition;
+MMappingDefinition.MappingDefinition -> MappingDefinition;
+MDependencyDefinition.DependencyDefinition -> DependencyDefinition;
+MTypeComponent.TypeComponent -> TypeComponent;
+MTypeComponent.DependencyDefinition* -> MDependencyDefinition;
diff --git a/src/main/jastadd/Navigation.jrag b/src/main/jastadd/Navigation.jrag
index e034804ba6f4151c744e7fb495c17f483be7e685..e181bc92401ef3e8e098685ac6047dba88e46954 100644
--- a/src/main/jastadd/Navigation.jrag
+++ b/src/main/jastadd/Navigation.jrag
@@ -49,6 +49,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/src/main/jastadd/backend/Generation.jadd b/src/main/jastadd/backend/Generation.jadd
index abce6a07b80fb96313de4760511c5339e9da92a0..2f2ff547924d3464c170cd995e6b10ddffeba3e2 100644
--- a/src/main/jastadd/backend/Generation.jadd
+++ b/src/main/jastadd/backend/Generation.jadd
@@ -21,6 +21,154 @@ aspect GenerationUtils {
   }
 }
 
+/* Open questions
+- Should all string constants be defined on the normal AST, or on the special mustache AST?
+*/
+
+aspect AttributesForMustache {
+  // --- MRos2Rag ---
+
+  // --- MUpdateDefinition ---
+  syn String MUpdateDefinition.preemptiveExpectedValue();
+  syn String MUpdateDefinition.preemptiveReturn();
+  syn String MUpdateDefinition.mqttUpdaterAttribute();
+  syn UpdateDefinition MUpdateDefinition.updateDef();
+
+  eq MUpdateDefinition.getMInnerMappingDefinition(int i).isLast() = i == numInnerMappingDefinition();
+  eq MUpdateDefinition.getInnerMappingDefinition().resultVarPrefix() = resultVarPrefix();
+  eq MUpdateDefinition.getInnerMappingDefinition(int i).inputVarName() = i == 0 ? getFirstInputVarName() : resultVarPrefix() + getMInnerMappingDefinition(i - 1).getMappingDefinition().methodName();
+
+  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(numInnerMappingDefinition() - 1);
+  syn String MUpdateDefinition.lastDefinitionType() = lastDefinition().ToType();
+  syn String MUpdateDefinition.lastDefinitionName() = lastDefinition().methodName();
+  syn String MUpdateDefinition.condition() {
+    String lastVarName = resultVarPrefix() + lastDefinitionDefinitionName();
+    if (lastDefinition().getMappingDefinition().getToType().isArray()) {
+      return "java.util.Arrays.equals(" + preemptiveExpectedValue() + ", " + lastVarName + ")";
+    }
+    if (token().isPrimitiveType() && lastDefinition().getMappingDefinition().getToType().isPrimitiveType()) {
+      return preemptiveExpectedValue() + " == " + lastVarName;
+    }
+    if (lastDefinition().getMappingDefinition().isDefaultMappingDefinition()) {
+      return preemptiveExpectedValue() + " != null && " + preemptiveExpectedValue() + ".equals(" + lastVarName ")";
+    }
+    return preemptiveExpectedValue() + " != null ? " + preemptiveExpectedValue() + ".equals(" + lastVarName ") : " + inputVariableName + " == 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.connectMethod() = getReadFromMqttDefinition().connectMethod();
+  eq MReadDefinition.preemptiveExpectedValue() = "get" + TokenName() + "()";
+  eq MReadDefinition.preemptiveReturn() = "return;";
+  eq MReadDefinition.updateDef() = getReadFromMqttDefinition();
+
+  syn String MReadDefinition.mqttUpdaterAttribute() = getReadFromMqttDefinition().mqttUpdaterAttribute;
+  syn String MReadDefinition.lastResult() = getReadFromMqttDefinition();
+
+  // --- MWriteDefinition ---
+  eq MWriteDefinition.preemptiveExpectedValue() = lastValue();
+  eq MWriteDefinition.preemptiveReturn() = "return false;";
+  eq MWriteDefinition.updateDef() = getWriteToMqttDefinition();
+
+  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 wrap(getDependencyDefinition().targetUpdateDefinition());
+  }
+
+  // --- MTypeComponent ---
+  syn String MTypeComponent.parentTypeName() = getTypeComponent().containingTypeDecl().getName();
+  syn String MTypeComponent.name() = getTypeComponent().getName();
+  syn String MTypeComponent.javaType() = getTypeComponent().getJavaTypeUse().prettyPrint();
+  syn String MTypeComponent.internalName() = getTypeComponent().internalName();
+
+  // --- wrap ---
+  syn lazy MRos2Rag Ros2Rag.wrap() {
+    MRos2Rag result = new MRos2Rag();
+    for (UpdateDefinition def : getUpdateDefinitionList()) {
+      if (def.isWriteToMqttDefinition()) {
+        result.addWriteDefinition(def.asWriteToMqttDefinition.wrap());
+      } else {
+        result.addReadDefinition(def.asReadFromMqttDefinition().wrap());
+      }
+    }
+    for (MappingDefinition def : allMappingDefinitions()) {
+      result.addMappingDefinition(def.wrap());
+    }
+    for (DependencyDefinition def : getDependencyDefinitionList()) {
+      result.addDependencyDefinition(def.wrap());
+    }
+    for (TokenComponent token : getProgram().allTokenComponents()) {
+      result.addTokenComponent(token.wrap());
+    }
+    return result;
+  }
+//MInnerMappingDefinition.MappingDefinition -> MappingDefinition;
+  private void MUpdateDefinition.addInnerMappings() {
+    for (MappingDefinition def : updateDefinition().effectiveMappings()) {
+      addInnerMappingDefinition(def.wrap());
+    }
+  }
+  syn lazy MReadDefinition ReadFromMqttDefinition.wrap() {
+    MReadDefinition result = new MReadDefinition();
+    result.setReadFromMqttDefinition(this);
+    result.addInnerMappings();
+    return result;
+  }
+  syn lazy MWriteDefinition WriteToMqttDefinition.wrap() {
+    MWriteDefinition result = new MWriteDefinition();
+    result.setWriteToMqttDefinition(this);
+    result.addInnerMappings();
+    return result;
+  }
+  syn lazy MMappingDefinition MappingDefinition.wrap() {
+    MMappingDefinition result = new MMappingDefinition();
+    result.setMappingDefinition(this);
+    return result;
+  }
+  syn lazy MDependencyDefinition DependencyDefinition.wrap() {
+    MDependencyDefinition result = new MDependencyDefinition();
+    result.setDependencyDefinition(this);
+    return result;
+  }
+  syn lazy MTypeComponent TypeComponent.wrap() {
+    MTypeComponent result = new MTypeComponent();
+    result.setTypeComponent(this);
+    for (DependencyDefinition def : getDependencySourceDefinitionList()) {
+      result.addDependencyDefinition(def.wrap());
+    }
+    return result;
+  }
+}
+
 aspect AspectGeneration {
   // naming convention attributes
   syn String TokenComponent.internalName() = getDependencySourceDefinitionList().isEmpty() ? externalName() : "_internal_" + getName();
diff --git a/src/main/resources/dependencyDefinition.mustache b/src/main/resources/dependencyDefinition.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..b8d74ffe8f8aa5aaa8979682e4442215af1974e8
--- /dev/null
+++ b/src/main/resources/dependencyDefinition.mustache
@@ -0,0 +1,3 @@
+  public void {{targetParentTypeName}}.{{dependencyMethod}}({{sourceParentTypeName}} source) {
+    add{{internalRelationPrefix}}Source(source);
+  }
diff --git a/src/main/resources/mappingApplication.mustache b/src/main/resources/mappingApplication.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..61020400b11622dd09069118c50e6b42c166dcaf
--- /dev/null
+++ b/src/main/resources/mappingApplication.mustache
@@ -0,0 +1,14 @@
+{{lastDefinitionToType}} {{resultVarPrefix}}{{lastDefinitionName}};
+try {
+  {{#InnerMappingDefinitions}}
+  {{^isLast}}{{ToType}} {{/isLast}}{{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/src/main/resources/mappingDefinition.mustache b/src/main/resources/mappingDefinition.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..5cc1580a724bc75a15bbdf896974b7dcaa8c926a
--- /dev/null
+++ b/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/src/main/resources/readDefinition.mustache b/src/main/resources/readDefinition.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..4d07c8fa2d1464ad774badfa02d9b5e1ac774e7e
--- /dev/null
+++ b/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/src/main/resources/ros2rag.mustache b/src/main/resources/ros2rag.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..b224c6c1018fd405ca2a5802e544295e3a5a7cfa
--- /dev/null
+++ b/src/main/resources/ros2rag.mustache
@@ -0,0 +1,21 @@
+aspect ROS2RAG {
+  {{#ReadDefinitions}}
+    {{> readDefinition}}
+  {{/ReadDefinitions}}
+
+  {{#WriteDefinitions}}
+    {{> writeDefinition}}
+  {{/WriteDefinitions}}
+
+  {{#MappingDefinitions}}
+    {{> mappingDefinition}}
+  {{/MappingDefinitions}}
+
+  {{#DependencyDefinitions}}
+    {{> dependencyDefinition}}
+  {{/DependencyDefinitions}}
+
+  {{#TokenComponents}}
+    {{> tokenComponent}}
+  {{/TokenComponents}}
+}
diff --git a/src/main/resources/tokenComponent.mustache b/src/main/resources/tokenComponent.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..1e58287748affc8405e4daf47ff1b54d3f19f12e
--- /dev/null
+++ b/src/main/resources/tokenComponent.mustache
@@ -0,0 +1,17 @@
+  public {{parentTypeName}} {{parentTypeName}}.set{{name}}({{javaType}} value) {
+    set{{internalName}}(value);
+    {{#DependencyDefinitions}}
+    for ({{targetParentType}} 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/src/main/resources/writeDefinition.mustache b/src/main/resources/writeDefinition.mustache
new file mode 100644
index 0000000000000000000000000000000000000000..d94f85d51d679cd4571979664cdcfc53fc28cc1a
--- /dev/null
+++ b/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}}
+    {{mqttUpdateAttribute}}().publish({{writeTopic}}, {{lastValue}});
+  }