diff --git a/.gitignore b/.gitignore
index 1e36626d306f081a5319e89e9214bc182fcc2a7b..a78076875b36829500e0d58bf05998ee5ade757a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,8 +5,3 @@
 .classpath
 .idea/
 .gradle/
-build
-src/gen-res/
-src/gen/
-out/
-*.class
diff --git a/build.gradle b/build.gradle
index 97ea027a25f30ce1cd59aa273a2b059d880d4b84..ab55170b8ab3438287f5ebcf73653634f5d0da52 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,149 +1,47 @@
-
-apply plugin: 'java'
-apply plugin: 'jastadd'
-apply plugin: 'application'
-apply plugin: "idea"
-
-sourceCompatibility = 1.8
-
-mainClassName = 'org.jastadd.ros2rag.compiler.Compiler'
-
-repositories {
-    jcenter()
-}
-
-buildscript {
-    repositories.jcenter()
-    dependencies {
-        classpath 'org.jastadd:jastaddgradle:1.13.3'
-    }
+plugins {
+    id "com.github.ben-manes.versions" version "0.20.0"
 }
 
-dependencies {
-    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.0'
-    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.0'
-    testCompile 'org.assertj:assertj-core:3.12.1'
-    compile 'com.fasterxml.jackson.core:jackson-core:2.9.8'
-    compile 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
-    compile 'org.jastadd:jastadd:2.3.4'
-    runtime 'org.jastadd:jastadd:2.3.4'
-    compile group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
-    compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.10.0'
-    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.10.0'
+allprojects  {
+    group = 'de.tudresden.inf.st'
+    version = '0.1'
 }
 
-sourceSets {
-    main {
-        java.srcDir "src/gen/java"
-        java.srcDir "buildSrc/gen/java"
-    }
-}
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'idea'
 
-test {
-    useJUnitPlatform()
-
-    maxHeapSize = '1G'
-}
+    sourceCompatibility = 1.8
+    targetCompatibility = 1.8
 
-jar {
-    manifest {
-        attributes "Main-Class": 'org.jastadd.ros2rag.compiler.Compiler'
+    task packageSources(type: Jar) {
+        classifier = 'sources'
+        from sourceSets.main.allSource
     }
 
-    from {
-        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
+    artifacts.archives packageSources
+    configurations {
+        testArtifacts.extendsFrom testRuntime
     }
-}
-
-task relast(type: JavaExec) {
-    group = 'Build'
-    main = "-jar"
 
-    doFirst {
-        delete "src/gen/jastadd/*.ast"
-        delete "src/gen/jastadd/RelAst.jadd"
-        delete "src/gen/jastadd/RelAstRefResolver.jadd"
-        delete "src/gen/jastadd/RelAstResolverStubs.jrag"
-        mkdir  "src/gen/jastadd/"
+    task testJar(type: Jar) {
+        classifier "test"
+        from sourceSets.test.output
     }
 
-    args = [
-            "libs/relast.jar",
-            "./src/main/jastadd/RelAst.relast",
-            "--listClass=java.util.ArrayList",
-            "--jastAddList=JastAddList",
-            "--useJastAddNames",
-            "--file",
-            "--resolverHelper",
-            "--grammarName=./src/gen/jastadd/RelAST"
-    ]
-
-    inputs.files file("src/main/jastadd/RelAST.relast"),
-            file("libs/relast.jar")
-    outputs.files file("./src/gen/jastadd/RelAst.ast"),
-            file("src/gen/jastadd/RelAst.jadd"),
-            file("src/gen/jastadd/RelAstRefResolver.jadd"),
-            file('src/gen/jastadd/RelAstResolverStubs.jrag')
-}
-
-jastadd {
-    configureModuleBuild()
-    modules {
-        //noinspection GroovyAssignabilityCheck
-        module("RelAst") {
-
-            java {
-                basedir "."
-                include "src/main/**/*.java"
-                include "src/gen/**/*.java"
-            }
-
-            jastadd {
-                basedir "."
-                include "src/main/jastadd/**/*.ast"
-                include "src/main/jastadd/**/*.jadd"
-                include "src/main/jastadd/**/*.jrag"
-                include "src/gen/jastadd/**/*.ast"
-                include "src/gen/jastadd/**/*.jadd"
-                include "src/gen/jastadd/**/*.jrag"
-            }
-
-            scanner {
-                include "src/main/jastadd/RelAst.flex"
-            }
-
-            parser {
-                include "src/main/jastadd/Preamble.parser"
-                include "src/main/jastadd/RelAst.parser"
-            }
-        }
+    artifacts {
+        testArtifacts testJar
     }
 
-    cleanGen.doFirst {
-        delete "src/gen/java/org"
-        delete "src/gen-res/BuildInfo.properties"
+    repositories {
+        mavenCentral()
     }
 
-    preprocessParser.doFirst {
-
-        args += ["--no-beaver-symbol"]
-
+    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'
     }
 
-    module = "RelAst"
-
-    astPackage = 'org.jastadd.ros2rag.ast'
-
-    parser.name = 'RelAstParser'
-
-    genDir = 'src/gen/java'
-
-    buildInfoDir = 'src/gen-res'
-
-    scanner.genDir = "src/gen/java/org/jastadd/ros2rag/scanner"
-    parser.genDir = "src/gen/java/org/jastadd/ros2rag/parser"
-
-    jastaddOptions = ["--lineColumnNumbers", "--List=JastAddList", "--safeLazy", "--visitCheck=true", "--rewrite=cnta", "--cache=all"]
 }
-
-generateAst.dependsOn relast
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000000000000000000000000000000000000..8dfaf2b21cba4862b382e4bb5d7a3173956f99d0
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,2 @@
+jackson_version = 2.9.8
+apache_httpcomponents_version = 4.5.8
diff --git a/ros2rag.base/.gitignore b/ros2rag.base/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..87b4cdd3d7c6a41502ca98703abeeb69a1d536fb
--- /dev/null
+++ b/ros2rag.base/.gitignore
@@ -0,0 +1,5 @@
+build
+src/gen-res/
+src/gen/
+out/
+*.class
diff --git a/ros2rag.base/build.gradle b/ros2rag.base/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..f9afaaf64eb1bc07821b85b8888dd4f183918f7b
--- /dev/null
+++ b/ros2rag.base/build.gradle
@@ -0,0 +1,149 @@
+
+apply plugin: 'java'
+apply plugin: 'jastadd'
+apply plugin: 'application'
+apply plugin: "idea"
+
+sourceCompatibility = 1.8
+
+mainClassName = 'org.jastadd.ros2rag.compiler.Compiler'
+
+repositories {
+    jcenter()
+}
+
+buildscript {
+    repositories.jcenter()
+    dependencies {
+        classpath 'org.jastadd:jastaddgradle:1.13.3'
+    }
+}
+
+dependencies {
+    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.0'
+    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.0'
+    testCompile 'org.assertj:assertj-core:3.12.1'
+    compile 'com.fasterxml.jackson.core:jackson-core:2.9.8'
+    compile 'com.fasterxml.jackson.core:jackson-databind:2.9.8'
+    compile 'org.jastadd:jastadd:2.3.4'
+    runtime 'org.jastadd:jastadd:2.3.4'
+    compile group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11'
+}
+
+sourceSets {
+    main {
+        java.srcDir "src/gen/java"
+        java.srcDir "buildSrc/gen/java"
+    }
+}
+
+test {
+    useJUnitPlatform()
+
+    maxHeapSize = '1G'
+}
+
+jar {
+    manifest {
+        attributes "Main-Class": 'org.jastadd.ros2rag.compiler.Compiler'
+    }
+
+    from {
+        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
+    }
+}
+
+task relast(type: JavaExec) {
+    group = 'Build'
+    main = "-jar"
+
+    doFirst {
+        delete "src/gen/jastadd/*.ast"
+        delete "src/gen/jastadd/Ros2Rag.jadd"
+        delete "src/gen/jastadd/Ros2RagRefResolver.jadd"
+        delete "src/gen/jastadd/Ros2RagResolverStubs.jrag"
+        mkdir  "src/gen/jastadd/"
+    }
+
+    args = [
+            "../libs/relast.jar",
+            "./src/main/jastadd/RelAst.relast",
+            "./src/main/jastadd/Ros2Rag.relast",
+            "--listClass=java.util.ArrayList",
+            "--jastAddList=JastAddList",
+            "--useJastAddNames",
+            "--file",
+            "--resolverHelper",
+            "--grammarName=./src/gen/jastadd/RelAST"
+    ]
+
+    inputs.files file("../libs/relast.jar"),
+            file("src/main/jastadd/RelAST.relast"),
+            file("src/main/jastadd/Ros2Rag.relast")
+    outputs.files file("./src/gen/jastadd/RelAst.ast"),
+            file("src/gen/jastadd/RelAst.jadd"),
+            file("src/gen/jastadd/RelAstRefResolver.jadd"),
+            file('src/gen/jastadd/RelAstResolverStubs.jrag')
+}
+
+jastadd {
+    configureModuleBuild()
+    modules {
+        //noinspection GroovyAssignabilityCheck
+        module("RelAst") {
+
+            java {
+                basedir "."
+                include "src/main/**/*.java"
+                include "src/gen/**/*.java"
+            }
+
+            jastadd {
+                basedir "."
+                include "src/main/jastadd/**/*.ast"
+                include "src/main/jastadd/**/*.jadd"
+                include "src/main/jastadd/**/*.jrag"
+                include "src/gen/jastadd/**/*.ast"
+                include "src/gen/jastadd/**/*.jadd"
+                include "src/gen/jastadd/**/*.jrag"
+            }
+
+            scanner {
+                include "src/main/jastadd/RelAst.flex"
+            }
+
+            parser {
+                include "src/main/jastadd/Preamble.parser"
+                include "src/main/jastadd/RelAst.parser"
+            }
+        }
+    }
+
+    cleanGen.doFirst {
+        delete "src/gen/java/org"
+        delete "src/gen-res/BuildInfo.properties"
+    }
+
+    preprocessParser.doFirst {
+
+        args += ["--no-beaver-symbol"]
+
+    }
+
+    module = "RelAst"
+
+    astPackage = 'org.jastadd.ros2rag.ast'
+
+    parser.name = 'RelAstParser'
+
+    genDir = 'src/gen/java'
+
+    buildInfoDir = 'src/gen-res'
+
+    scanner.genDir = "src/gen/java/org/jastadd/ros2rag/scanner"
+    parser.genDir = "src/gen/java/org/jastadd/ros2rag/parser"
+
+    jastaddOptions = ["--lineColumnNumbers", "--List=JastAddList", "--safeLazy", "--visitCheck=true", "--rewrite=cnta", "--cache=all"]
+}
+
+generateAst.dependsOn relast
diff --git a/src/main/jastadd/Analysis.jrag b/ros2rag.base/src/main/jastadd/Analysis.jrag
similarity index 100%
rename from src/main/jastadd/Analysis.jrag
rename to ros2rag.base/src/main/jastadd/Analysis.jrag
diff --git a/src/main/jastadd/DumpTree.jrag b/ros2rag.base/src/main/jastadd/DumpTree.jrag
similarity index 100%
rename from src/main/jastadd/DumpTree.jrag
rename to ros2rag.base/src/main/jastadd/DumpTree.jrag
diff --git a/src/main/jastadd/Errors.jrag b/ros2rag.base/src/main/jastadd/Errors.jrag
similarity index 100%
rename from src/main/jastadd/Errors.jrag
rename to ros2rag.base/src/main/jastadd/Errors.jrag
diff --git a/src/main/jastadd/NameResolution.jrag b/ros2rag.base/src/main/jastadd/NameResolution.jrag
similarity index 100%
rename from src/main/jastadd/NameResolution.jrag
rename to ros2rag.base/src/main/jastadd/NameResolution.jrag
diff --git a/src/main/jastadd/Navigation.jrag b/ros2rag.base/src/main/jastadd/Navigation.jrag
similarity index 89%
rename from src/main/jastadd/Navigation.jrag
rename to ros2rag.base/src/main/jastadd/Navigation.jrag
index c00ad13fd1c29f71e60d3756916837409f3fff09..b6028726b4406353f1acb0711d69ff66c509d661 100644
--- a/src/main/jastadd/Navigation.jrag
+++ b/ros2rag.base/src/main/jastadd/Navigation.jrag
@@ -13,6 +13,9 @@ aspect Navigation {
     to Program.relations()
     for program();
 
+  inh TypeDecl Component.containingTypeDecl();
+  eq TypeDecl.getChild().containingTypeDecl() = this;
+
 //  syn boolean RelationComponent.multiplicityOne() = false;
 //  eq OneRelationComponent.multiplicityOne() = true;
 //  syn boolean RelationComponent.multiplicityOpt() = false;
diff --git a/src/main/jastadd/Preamble.parser b/ros2rag.base/src/main/jastadd/Preamble.parser
similarity index 100%
rename from src/main/jastadd/Preamble.parser
rename to ros2rag.base/src/main/jastadd/Preamble.parser
diff --git a/src/main/jastadd/RelAst.flex b/ros2rag.base/src/main/jastadd/RelAst.flex
similarity index 100%
rename from src/main/jastadd/RelAst.flex
rename to ros2rag.base/src/main/jastadd/RelAst.flex
diff --git a/src/main/jastadd/RelAst.parser b/ros2rag.base/src/main/jastadd/RelAst.parser
similarity index 100%
rename from src/main/jastadd/RelAst.parser
rename to ros2rag.base/src/main/jastadd/RelAst.parser
diff --git a/src/main/jastadd/RelAst.relast b/ros2rag.base/src/main/jastadd/RelAst.relast
similarity index 100%
rename from src/main/jastadd/RelAst.relast
rename to ros2rag.base/src/main/jastadd/RelAst.relast
diff --git a/ros2rag.base/src/main/jastadd/Ros2Rag.relast b/ros2rag.base/src/main/jastadd/Ros2Rag.relast
new file mode 100644
index 0000000000000000000000000000000000000000..fb737a8cb950588c404f71181f2bb42c0077beb5
--- /dev/null
+++ b/ros2rag.base/src/main/jastadd/Ros2Rag.relast
@@ -0,0 +1,16 @@
+Ros2Rag ::= MappingDefinition* SyncDefinition* Program;
+
+abstract SyncDefinition ::= <AlwaysApply:Boolean> ;
+
+rel SyncDefinition.Mapping? -> MappingDefinition;
+
+abstract TokenSyncDefinition : SyncDefinition;
+rel TokenSyncDefinition.token -> TokenComponent;
+
+ReadFromMqttDefinition : TokenSyncDefinition;
+WriteToMqttDefinition  : TokenSyncDefinition;
+
+MappingDefinition ::= <ID> <Content> ;
+
+rel MappingDefinition.from -> TypeDecl;
+rel MappingDefinition.to -> TypeDecl;
diff --git a/src/main/jastadd/backend/AbstractGrammar.jadd b/ros2rag.base/src/main/jastadd/backend/AbstractGrammar.jadd
similarity index 100%
rename from src/main/jastadd/backend/AbstractGrammar.jadd
rename to ros2rag.base/src/main/jastadd/backend/AbstractGrammar.jadd
diff --git a/ros2rag.base/src/main/jastadd/backend/Aspect.jadd b/ros2rag.base/src/main/jastadd/backend/Aspect.jadd
new file mode 100644
index 0000000000000000000000000000000000000000..c63ec13624c2c10589b070917eea9cc7fba2fa0e
--- /dev/null
+++ b/ros2rag.base/src/main/jastadd/backend/Aspect.jadd
@@ -0,0 +1,81 @@
+aspect Aspect {
+
+  public static final String ASTNode.aspectIndent = "  ";
+
+  public String Program.generateAspect() {
+    StringBuilder sb = new StringBuilder();
+    generateAspect(sb);
+    return sb.toString();
+  }
+
+  @Deprecated
+  public void Program.generateAspect(StringBuilder sb) {
+
+    sb.append("aspect ROS2RAG {\n");
+
+    // TODO generate getters and setters for ROS2RAG terminals (and attributes?)
+
+    sb.append("}\n");
+  }
+
+  public String Ros2Rag.generateAspect() {
+    StringBuilder sb = new StringBuilder();
+    generateAspect(sb);
+    return sb.toString();
+  }
+
+  // from "[always] read Joint.CurrentPosition using PoseToPosition;" generate method connectTo
+//    Joint j;
+//    j.getCurrentPosition().connectTo("/robot/joint2/pos");
+
+  public void Ros2Rag.generateAspect(StringBuilder sb) {
+    sb.append("aspect ROS2RAG {\n");
+
+    for (SyncDefinition def : getSyncDefinitionList()) {
+      def.generateAspect(sb);
+    }
+
+    sb.append("}\n");
+  }
+
+  abstract void SyncDefinition.generateAspect(StringBuilder sb);
+//  @Override
+//  void UpdateDefinition.generateAspect(StringBuilder sb) {
+//    // TODO
+//  }
+
+  // will be "addConnectionJoint_CurrentPosition" in example
+/* // see discussion in codimd (InstanceLocation), why this won't work here
+  Position.connectTo(String topic) {
+    mqttUpdater().addConnectionJoint_CurrentPosition(this, topic);
+  }
+  MqttUpdater.addConnectionJoint_CurrentPosition(Position target, String topic) {
+    // either
+    topicActionMap.put(topic, new Action(JOINT_CURRENTPOSITION, target));
+    // or
+    topicForJoint_CurrentPosition.put(topic, target);
+  }
+ */
+  @Override
+  void ReadFromMqttDefinition.generateAspect(StringBuilder sb) {
+    sb.append("public void ").append("type").append(".connectTo(String topic) {\n")
+      .append(aspectIndent).append("mqttUpdater().addConnection")
+      .append(getToken().containingTypeDecl().getName())
+      .append("_")
+      .append(getToken().getName())
+      .append("(this, topic);\n")
+      .append("}\n");
+  }
+
+  @Override
+  void WriteToMqttDefinition.generateAspect(StringBuilder sb) {
+    sb.append("public void ").append("type").append(".connectTo(String topic) {\n")
+      .append(aspectIndent).append("mqttUpdater().addConnection")
+      .append(getToken().containingTypeDecl().getName())
+      .append("_")
+      .append(getToken().getName())
+      .append("(this, topic);\n")
+      .append("}\n");
+  }
+
+}
diff --git a/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
similarity index 100%
rename from src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
rename to ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java
diff --git a/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/SimpleMain.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/SimpleMain.java
new file mode 100644
index 0000000000000000000000000000000000000000..ae2b4a0312f43c3c82df5ff0d1f477e8888a25b4
--- /dev/null
+++ b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/SimpleMain.java
@@ -0,0 +1,84 @@
+package org.jastadd.ros2rag.compiler;
+
+import beaver.Parser;
+import org.jastadd.ros2rag.ast.*;
+import org.jastadd.ros2rag.parser.RelAstParser;
+import org.jastadd.ros2rag.scanner.RelAstScanner;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * Testing Ros2Rag without parser.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class SimpleMain {
+  public static void main(String[] args) {
+    /*
+    // as soon as the cache of isInSafetyZone is invalidated, update the value of Robot.ShouldUseLowSpeed with its value
+    [always] update Robot.ShouldUseLowSpeed with isInSafetyZone() using transformation();
+
+    // when a (new?) value for ShouldUseLowSpeed is set, send it over via mqtt
+    [always] write Robot.ShouldUseLowSpeed;
+
+    // when an update of pose is read via mqtt, then update current position
+    [always] read Joint.CurrentPosition using PoseToPosition;
+
+    // PBPose is a datatype defined in protobuf
+    PoseToPosition: map PBPose to Position using
+      pose.position.x += sqrt(.5 * size.x)
+      MAP round(2)
+      x = x / 100
+      IGNORE_IF_SAME
+    ;
+
+    --- using generated methods ---
+    Joint j;
+    j.getCurrentPosition().connectTo("/robot/joint2/pos");
+
+    RobotArm r;
+    // this should not be required
+    r.getShouldUseLowSpeed().addObserver(j.getCurrentPosition());
+    r.getShouldUseLowSpeed().connectTo("/robot/config/speed");
+     */
+    Ros2Rag model = new Ros2Rag();
+    Program program = parseProgram(Paths.get("src", "test", "resources", "MinimalExample.relast"));
+    model.setProgram(program);
+
+    MappingDefinition mappingDefinition = new MappingDefinition();
+    mappingDefinition.setID("PoseToPosition");
+    mappingDefinition.setFrom(TypeDecl.createRef("PBPose"));
+    mappingDefinition.setTo(TypeDecl.createRef("Position"));
+    mappingDefinition.setContent("      pose.position.x += sqrt(.5 * size.x)\n" +
+        "      MAP round(2)\n" +
+        "      x = x / 100\n" +
+        "      IGNORE_IF_SAME\n" +
+        "    ;");
+    model.addMappingDefinition(mappingDefinition);
+
+    ReadFromMqttDefinition readFromMqttDefinition = new ReadFromMqttDefinition();
+    readFromMqttDefinition.setAlwaysApply(false);
+    readFromMqttDefinition.setToken(TokenComponent.createRef("Joint.CurrentPosition"));
+    readFromMqttDefinition.setMapping(mappingDefinition);
+    model.addSyncDefinition(readFromMqttDefinition);
+
+    model.treeResolveAll();
+
+    System.out.println(model.generateAspect());
+  }
+
+  private static Program parseProgram(Path path) {
+    try (BufferedReader reader = Files.newBufferedReader(path)) {
+      RelAstScanner scanner = new RelAstScanner(reader);
+      RelAstParser parser = new RelAstParser();
+      return (Program) parser.parse(scanner);
+    } catch (IOException | Parser.Exception e) {
+      e.printStackTrace();
+    }
+    return null;
+  }
+}
diff --git a/src/main/java/org/jastadd/ros2rag/compiler/Utils.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Utils.java
similarity index 100%
rename from src/main/java/org/jastadd/ros2rag/compiler/Utils.java
rename to ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Utils.java
diff --git a/src/main/java/org/jastadd/ros2rag/compiler/options/CommandLine.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/options/CommandLine.java
similarity index 100%
rename from src/main/java/org/jastadd/ros2rag/compiler/options/CommandLine.java
rename to ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/options/CommandLine.java
diff --git a/src/main/java/org/jastadd/ros2rag/compiler/options/EnumOption.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/options/EnumOption.java
similarity index 100%
rename from src/main/java/org/jastadd/ros2rag/compiler/options/EnumOption.java
rename to ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/options/EnumOption.java
diff --git a/src/main/java/org/jastadd/ros2rag/compiler/options/FlagOption.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/options/FlagOption.java
similarity index 100%
rename from src/main/java/org/jastadd/ros2rag/compiler/options/FlagOption.java
rename to ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/options/FlagOption.java
diff --git a/src/main/java/org/jastadd/ros2rag/compiler/options/Option.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/options/Option.java
similarity index 100%
rename from src/main/java/org/jastadd/ros2rag/compiler/options/Option.java
rename to ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/options/Option.java
diff --git a/src/main/java/org/jastadd/ros2rag/compiler/options/StringOption.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/options/StringOption.java
similarity index 100%
rename from src/main/java/org/jastadd/ros2rag/compiler/options/StringOption.java
rename to ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/options/StringOption.java
diff --git a/src/main/resources/log4j2.xml b/ros2rag.base/src/main/resources/log4j2.xml
similarity index 100%
rename from src/main/resources/log4j2.xml
rename to ros2rag.base/src/main/resources/log4j2.xml
diff --git a/src/test/java/org/jastadd/ros2rag/tests/RosToRagTest.java b/ros2rag.base/src/test/java/org/jastadd/ros2rag/tests/RosToRagTest.java
similarity index 100%
rename from src/test/java/org/jastadd/ros2rag/tests/RosToRagTest.java
rename to ros2rag.base/src/test/java/org/jastadd/ros2rag/tests/RosToRagTest.java
diff --git a/src/test/resources/MinimalExample.relast b/ros2rag.base/src/test/resources/MinimalExample.relast
similarity index 63%
rename from src/test/resources/MinimalExample.relast
rename to ros2rag.base/src/test/resources/MinimalExample.relast
index c9cf30085267ac7df5b2d2a13fbdb31e9321d626..7152bd150d0a8daba192dff6993d8760ff390a41 100644
--- a/src/test/resources/MinimalExample.relast
+++ b/ros2rag.base/src/test/resources/MinimalExample.relast
@@ -1,12 +1,13 @@
 Model ::= RobotArm ZoneModel ;
 
-ZoneModel ::= <Size:Position> SafetyZone:Zone*;
+ZoneModel ::= Size:Position SafetyZone:Zone*;
 
 Zone ::= Position*;
 
 RobotArm ::= Joint* EndEffector /<ShouldUseLowSpeed:Boolean>/ ;
 
-Joint ::= <Name> <CurrentPosition:Position>;
+Joint ::= <Name> ;
+rel Joint.CurrentPosition -> Position ;
 
 EndEffector : Joint;
 
diff --git a/ros2rag.example/.gitignore b/ros2rag.example/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..87b4cdd3d7c6a41502ca98703abeeb69a1d536fb
--- /dev/null
+++ b/ros2rag.example/.gitignore
@@ -0,0 +1,5 @@
+build
+src/gen-res/
+src/gen/
+out/
+*.class
diff --git a/ros2rag.example/build.gradle b/ros2rag.example/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..ddfeaf8dcdd434f23161c1f04a8865b93f394697
--- /dev/null
+++ b/ros2rag.example/build.gradle
@@ -0,0 +1,134 @@
+apply plugin: 'jastadd'
+apply plugin: 'application'
+apply plugin: 'com.google.protobuf'
+
+sourceCompatibility = 1.8
+
+mainClassName = 'de.tudresden.inf.st.ros2rag.example.Main'
+
+repositories {
+    jcenter()
+}
+
+buildscript {
+    repositories.jcenter()
+    dependencies {
+        classpath 'org.jastadd:jastaddgradle:1.13.3'
+        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.12'
+    }
+}
+
+sourceSets.main.java.srcDir "src/gen/java"
+jar.manifest.attributes('Main-Class': 'de.tudresden.inf.st.ros2rag.example.Main')
+
+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'
+
+    jastadd2 "org.jastadd:jastadd:2.3.4"
+}
+
+test {
+    useJUnitPlatform()
+
+    maxHeapSize = '1G'
+}
+
+jastadd {
+    configureModuleBuild()
+    modules {
+        //noinspection GroovyAssignabilityCheck
+        module("ros2rag example") {
+
+            java {
+                basedir "src/"
+                include "main/**/*.java"
+                include "gen/**/*.java"
+            }
+
+            jastadd {
+                basedir "src/"
+                include "main/jastadd/**/*.ast"
+                include "main/jastadd/**/*.jadd"
+                include "main/jastadd/**/*.jrag"
+                include "gen/jastadd/**/*.ast"
+                include "gen/jastadd/**/*.jadd"
+                include "gen/jastadd/**/*.jrag"
+            }
+
+            // scanner {
+            //     include "src/main/jastadd/RelAst.flex"
+            // }
+
+            // parser {
+            //     include "src/main/jastadd/Preamble.parser"
+            //     include "src/main/jastadd/RelAst.parser"
+            // }
+        }
+    }
+
+    cleanGen.doFirst {
+        delete "src/gen/java/org"
+        delete "src/gen-res/BuildInfo.properties"
+    }
+
+    preprocessParser.doFirst {
+
+        args += ["--no-beaver-symbol"]
+
+    }
+
+    module = "ros2rag example"
+
+    astPackage = 'de.tudresden.inf.st.ros2rag.ast'
+
+    // parser.name = 'RelAstParser'
+
+    genDir = 'src/gen/java'
+
+    buildInfoDir = 'src/gen-res'
+
+    // scanner.genDir = "src/gen/java/org/jastadd/ros2rag/scanner"
+    // parser.genDir = "src/gen/java/org/jastadd/ros2rag/parser"
+
+    // jastaddOptions = ["--lineColumnNumbers", "--visitCheck=true", "--rewrite=cnta", "--cache=all"]
+    // default options are: '--rewrite=cnta', '--safeLazy', '--visitCheck=false', '--cacheCycle=false'
+    extraJastAddOptions = ["--lineColumnNumbers", '--List=JastAddList']
+}
+
+// Input files
+def relastFiles = ["src/main/jastadd/Example.relast", "src/main/jastadd/Generated.relast"]
+
+// phase: RelAst -> JastAdd
+task relastToJastAdd(type: JavaExec) {
+    group = 'Build'
+    main = "-jar"
+
+    args(["../libs/relast.jar",
+            "--grammarName=./src/gen/jastadd/model",
+            "--useJastAddNames",
+            "--listClass=ArrayList",
+            "--jastAddList=JastAddList",
+            "--resolverHelper",
+            "--file"]
+    +
+            relastFiles)
+
+    inputs.files relastFiles
+    outputs.files file("./src/gen/jastadd/model.ast"), file("./src/gen/jastadd/model.jadd")
+}
+
+// Workflow configuration for phases
+generateAst.dependsOn relastToJastAdd
+
+protobuf {
+    // create strange directories, so use default here
+//    generatedFilesBaseDir = "$projectDir/src/gen/java"
+    protoc {
+        // The artifact spec for the Protobuf Compiler
+        artifact = 'com.google.protobuf:protoc:3.0.0'
+    }
+}
diff --git a/ros2rag.example/hpps.build.gradle b/ros2rag.example/hpps.build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..80a1c4a28828b123023c1f2f07e5dd14f2ca0e83
--- /dev/null
+++ b/ros2rag.example/hpps.build.gradle
@@ -0,0 +1,145 @@
+// General configuration (plugins, settings, dependencies)
+
+buildscript {
+    repositories.mavenLocal()
+    repositories.mavenCentral()
+    dependencies {
+        classpath group: 'org.jastadd', name: 'jastaddgradle', version: '1.13.3'
+    }
+}
+
+plugins {
+    id 'java'
+    id 'application'
+    id 'idea'
+    id 'jacoco'
+}
+
+apply plugin: 'jastadd'
+
+group 'de.tudresden.inf.st'
+version '0.1'
+
+sourceCompatibility = 1.8
+
+repositories.mavenCentral()
+
+idea.module.generatedSourceDirs += file('src/gen/java')
+
+configurations {
+    ragdoc
+}
+
+sourceSets.main.java.srcDir "src/gen/java"
+jar.manifest.attributes('Main-Class': 'de.tudresden.inf.st.hybridpps.Main')
+
+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'
+
+    jastadd2 "org.jastadd:jastadd:2.3.4"
+    ragdoc files('../libs/rd-builder.jar')
+}
+
+test {
+    testLogging {
+        events "passed", "skipped", "failed"
+        exceptionFormat "full"
+    }
+}
+
+jacocoTestReport {
+    reports {
+        xml.enabled true
+        html.enabled false
+    }
+}
+
+run {
+    mainClassName = 'de.tudresden.inf.st.hybridpps.Main'
+//    standardInput = System.in
+}
+run.enabled = false
+
+// Input files
+def relastFiles = ["./src/main/jastadd/problem.relast", "./src/main/jastadd/solution.relast"]
+
+// phase: RelAst -> JastAdd
+task relastToJastAdd(type: JavaExec) {
+    group = 'Build'
+    main = "-jar"
+
+    args(["../libs/relast.jar",
+            "--grammarName=./src/gen/jastadd/model",
+            "--useJastAddNames",
+            "--listClass=ArrayList",
+            "--jastAddList=JastAddList",
+            "--resolverHelper",
+            "--file"]
+    +
+            relastFiles)
+
+    inputs.files relastFiles
+    outputs.files file("./src/gen/jastadd/model.ast"), file("./src/gen/jastadd/model.jadd")
+}
+
+// phase: JastAdd -> Java (using JastAdd Gradle plugin)
+jastadd {
+    configureModuleBuild()
+    modules {
+        module("hybridpps") {
+
+            java {
+                basedir "src/"
+                include "main/**/*.java"
+                include "gen/**/*.java"
+            }
+
+            jastadd {
+                basedir "src/"
+                include "main/jastadd/**/*.ast"
+                include "main/jastadd/**/*.jadd"
+                include "main/jastadd/**/*.jrag"
+                include "gen/jastadd/**/*.ast"
+                include "gen/jastadd/**/*.jadd"
+                include "gen/jastadd/**/*.jrag"
+            }
+
+            scanner {
+                include "src/main/jastadd/HybridPPSModelScanner.flex"
+            }
+
+            parser {
+                include "src/main/jastadd/HybridPPSModelParser.parser"
+            }
+        }
+    }
+
+    cleanGen.doFirst {
+        delete "src/gen/java/de"
+        delete "src/gen-res/BuildInfo.properties"
+    }
+
+    module = "hybridpps"
+
+    astPackage = 'de.tudresden.inf.st.hybridpps.jastadd.model'
+
+    parser.name = 'HybridPPSModelParser'
+
+    genDir = 'src/gen/java'
+
+    buildInfoDir = 'src/gen-res'
+
+    scanner.genDir = "src/gen/java/de/tudresden/inf/st/hybridpps/jastadd/scanner"
+    parser.genDir = "src/gen/java/de/tudresden/inf/st/hybridpps/jastadd/parser"
+
+//  default options are: '--rewrite=cnta', '--safeLazy', '--visitCheck=false', '--cacheCycle=false'
+    extraJastAddOptions = ['--List=JastAddList']
+}
+
+// Workflow configuration for phases
+generateAst.dependsOn relastToJastAdd
+
+//// always run jastadd
+//jastadd.outputs.upToDateWhen {false}
diff --git a/ros2rag.example/src/main/jastadd/Example.relast b/ros2rag.example/src/main/jastadd/Example.relast
new file mode 100644
index 0000000000000000000000000000000000000000..b72ddd50ee92242e425145b373ad32d304f8d87b
--- /dev/null
+++ b/ros2rag.example/src/main/jastadd/Example.relast
@@ -0,0 +1,17 @@
+Model ::= RobotArm ZoneModel ;
+
+ZoneModel ::= <Size:Position> SafetyZone:Zone*;
+
+Zone ::= PositionWrapper*;
+
+// Do not use terminal-NTA's for now, as relast has problems with it "/<ShouldUseLowSpeed:Boolean>/" ;
+RobotArm ::= Joint* EndEffector ;
+
+Joint ::= <Name> <CurrentPosition:Position> ;
+//rel Joint.CurrentPosition -> Position_Old ;
+
+EndEffector : Joint;
+
+Position_Old ::= <x:int> <y:int> <z:int> ;
+PositionWrapper ::= <Position:Position> ;
+
diff --git a/ros2rag.example/src/main/jastadd/Generated.jrag b/ros2rag.example/src/main/jastadd/Generated.jrag
new file mode 100644
index 0000000000000000000000000000000000000000..0c0fce1b28b8d85447d4603101f1526a6a62e778
--- /dev/null
+++ b/ros2rag.example/src/main/jastadd/Generated.jrag
@@ -0,0 +1,59 @@
+import de.tudresden.inf.st.ros2rag.example.MqttUpdater;
+import panda.Linkstate.PandaLinkState.Position;
+
+// this aspect depends on the actual grammar. probably we need to provide the root node type, in this case "Model"
+// it is somewhat problematic, because it assumes a single root to store the mqtt-host
+aspect GrammarExtension {
+  // kind of private NTA typed "MqttRoot" and named "_MqttRoot"
+  syn nta MqttRoot Model.get_MqttRoot() {
+    return new MqttRoot();
+  }
+
+  public void Model.updateMqttHost(String host) throws java.io.IOException {
+    get_MqttRoot().updateHost(host);
+  }
+
+  public void Model.updateMqttHost(String host, int port) throws java.io.IOException {
+    get_MqttRoot().updateHost(host, port);
+  }
+
+  public boolean Model.waitUntilReady(long time, java.util.concurrent.TimeUnit unit) {
+    return get_MqttRoot().getUpdater().waitUntilReady(time, unit);
+  }
+
+  inh MqttUpdater Joint._mqttUpdater();
+  eq Model.getRobotArm()._mqttUpdater() = get_MqttRoot().getUpdater();
+  eq Model.getZoneModel()._mqttUpdater() = get_MqttRoot().getUpdater();
+}
+
+// this aspect is generic and will be always generated in the same way
+aspect Mqtt {
+  // --- default values ---
+  private static final int MqttRoot.DEFAULT_PORT = 1883;
+
+  void MqttRoot.updateHost(String host) throws java.io.IOException {
+    updateHost(host, DEFAULT_PORT);
+  }
+
+  void MqttRoot.updateHost(String host, int port) throws java.io.IOException {
+    setHost(ExternalHost.of(host, port));
+    if (getUpdater() != null) {
+      // close connection to old updater first
+      getUpdater().close();
+    }
+    setUpdater(new MqttUpdater().setHost(host, port));
+  }
+
+  public static ExternalHost ExternalHost.of(String hostName, int defaultPort) {
+    String host = hostName;
+    int port = defaultPort;
+    if (hostName.contains(":")) {
+      String[] parts = hostName.split(":");
+      host = parts[0];
+      port = Integer.parseInt(parts[1]);
+    }
+    return new ExternalHost(host, port);
+  }
+
+  syn String ExternalHost.urlAsString() = String.format("http://%s:%s", getHostName(), getPort());
+}
diff --git a/ros2rag.example/src/main/jastadd/Generated.relast b/ros2rag.example/src/main/jastadd/Generated.relast
new file mode 100644
index 0000000000000000000000000000000000000000..7c6b9db0fe73fc86677f33ec7e0cdc446fb0943c
--- /dev/null
+++ b/ros2rag.example/src/main/jastadd/Generated.relast
@@ -0,0 +1,3 @@
+MqttRoot ::= [Host:ExternalHost] <Updater:MqttUpdater> ;
+ExternalHost ::= <HostName:String> <Port:int> ;
+
diff --git a/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedJoint.java b/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedJoint.java
new file mode 100644
index 0000000000000000000000000000000000000000..6641c840b1166876f1d89e6c6a3823ecc3340d72
--- /dev/null
+++ b/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedJoint.java
@@ -0,0 +1,39 @@
+package de.tudresden.inf.st.ros2rag.example;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import de.tudresden.inf.st.ros2rag.ast.Joint;
+import panda.Linkstate.PandaLinkState;
+import panda.Linkstate.PandaLinkState.Position;
+
+/**
+ * Manually written code for Joint to be actually generated later.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class GeneratedJoint extends Joint {
+
+  /*
+  Input for this to be generated:
+
+  // when an update of pose is read via mqtt, then update current position
+  [always] read Joint.CurrentPosition using LinkStateToPosition;
+
+  // panda.LinkState is a datatype defined in protobuf
+  LinkStateToPosition: map panda.Linkstate x to Position y using {
+    y = x.getPos();
+  }
+   */
+  public void connectCurrentPosition(String topic) {
+    _mqttUpdater().newConnection(topic, message -> {
+      // Parse message into a LinkState
+      try {
+        PandaLinkState x = PandaLinkState.parseFrom(message);
+        Position y = x.getPos();
+        setCurrentPosition(y);
+      } catch (InvalidProtocolBufferException e) {
+        e.printStackTrace();
+      }
+    });
+  }
+
+}
diff --git a/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedRobotArm.java b/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedRobotArm.java
new file mode 100644
index 0000000000000000000000000000000000000000..56d326a696fea1019783120b2437ce371a4367dc
--- /dev/null
+++ b/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/GeneratedRobotArm.java
@@ -0,0 +1,11 @@
+package de.tudresden.inf.st.ros2rag.example;
+
+import de.tudresden.inf.st.ros2rag.ast.RobotArm;
+
+/**
+ * Manually written code for RobotArm to be actually generated later.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class GeneratedRobotArm extends RobotArm {
+}
diff --git a/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/Main.java b/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/Main.java
new file mode 100644
index 0000000000000000000000000000000000000000..8606a19ed07ed77b57b71be4464b490d73441f19
--- /dev/null
+++ b/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/Main.java
@@ -0,0 +1,72 @@
+package de.tudresden.inf.st.ros2rag.example;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import de.tudresden.inf.st.ros2rag.ast.*;
+import panda.Linkstate.PandaLinkState.Position;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Testing Ros2Rag without generating something.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class Main {
+  public static void main(String[] args) throws InvalidProtocolBufferException, IOException, InterruptedException {
+    Model model = new Model();
+    model.updateMqttHost("localhost");
+
+    ZoneModel zoneModel = new ZoneModel();
+    zoneModel.setSize(makePosition(1, 1, 1));
+
+    Position myPosition = makePosition(0, 0, 0);
+    PositionWrapper myPositionWrapper = new PositionWrapper(myPosition);
+    PositionWrapper leftPosition = new PositionWrapper(makePosition(-1, 0, 0));
+    PositionWrapper rightPosition = new PositionWrapper(makePosition(1, 0, 0));
+
+    Zone safetyZone = new Zone();
+    safetyZone.addPositionWrapper(myPositionWrapper);
+    safetyZone.addPositionWrapper(leftPosition);
+    safetyZone.addPositionWrapper(rightPosition);
+    zoneModel.addSafetyZone(safetyZone);
+
+    RobotArm robotArm = new GeneratedRobotArm();
+
+    GeneratedJoint joint1 = new GeneratedJoint();
+    joint1.setName("joint1");
+    joint1.setCurrentPosition(myPosition);
+
+    EndEffector endEffector = new EndEffector();
+    endEffector.setName("gripper");
+    endEffector.setCurrentPosition(myPosition);
+
+    robotArm.addJoint(joint1);
+    robotArm.setEndEffector(endEffector);
+    model.setRobotArm(robotArm);
+
+    model.waitUntilReady(2, TimeUnit.SECONDS);
+
+    joint1.connectCurrentPosition("robot/joint1");
+    System.out.println("BEFORE joint1.getCurrentPosition() = " + stringify(joint1.getCurrentPosition()));
+
+    Thread.sleep(10000);
+
+    System.out.println("AFTER joint1.getCurrentPosition() = " + stringify(joint1.getCurrentPosition()));
+
+    // TODO close/shutdown should be exposed
+    model.get_MqttRoot().getUpdater().close();
+  }
+
+  private static Position makePosition(int x, int y, int z) {
+    return Position.newBuilder()
+        .setPositionX(x)
+        .setPositionY(y)
+        .setPositionZ(z)
+        .build();
+  }
+
+  private static String stringify(Position position) {
+    return "(" + position.getPositionX() + ", " + position.getPositionY() + ", " + position.getPositionZ() + ")";
+  }
+}
diff --git a/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/MqttUpdater.java b/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/MqttUpdater.java
new file mode 100644
index 0000000000000000000000000000000000000000..cc9be87ca4aeb63085144b2b9cbd16b8049c7e14
--- /dev/null
+++ b/ros2rag.example/src/main/java/de/tudresden/inf/st/ros2rag/example/MqttUpdater.java
@@ -0,0 +1,214 @@
+package de.tudresden.inf.st.ros2rag.example;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.fusesource.hawtbuf.Buffer;
+import org.fusesource.hawtbuf.UTF8Buffer;
+import org.fusesource.mqtt.client.*;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
+
+/**
+ * Helper class to receive updates via MQTT and use callbacks to handle those messages.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class MqttUpdater {
+
+  private final Logger logger;
+
+  /** The host running the MQTT broker. */
+  private URI host;
+  /** The connection to the MQTT broker. */
+  private CallbackConnection connection;
+  /** Whether we are subscribed to the topics yet */
+  private Condition readyCondition;
+  private Lock readyLock;
+  private boolean ready;
+  private QoS qos;
+  /** Dispatch knowledge */
+  private final Map<String, Consumer<byte[]>> callbacks;
+
+  public MqttUpdater() {
+    this.logger = LogManager.getLogger(MqttUpdater.class);
+    this.callbacks = new HashMap<>();
+    this.readyLock = new ReentrantLock();
+    this.readyCondition = readyLock.newCondition();
+    this.ready = false;
+    this.qos = QoS.AT_LEAST_ONCE;
+  }
+
+  /**
+   * Sets the host to receive messages from, and connects to it.
+   * @throws IOException if could not connect, or could not subscribe to a topic
+   * @return self
+   */
+  public MqttUpdater setHost(String host, int port) throws IOException {
+    this.host = URI.create("tcp://" + host + ":" + port);
+    logger.debug("Host is {}", this.host);
+
+    Objects.requireNonNull(this.host, "Host need to be set!");
+    MQTT mqtt = new MQTT();
+    mqtt.setHost(this.host);
+    connection = mqtt.callbackConnection();
+    AtomicReference<Throwable> error = new AtomicReference<>();
+
+    // add the listener to dispatch messages later
+    connection.listener(new ExtendedListener() {
+      public void onConnected() {
+        logger.debug("Connected");
+      }
+
+      @Override
+      public void onDisconnected() {
+        logger.debug("Disconnected");
+      }
+
+      @Override
+      public void onPublish(UTF8Buffer topic, Buffer body, Callback<Callback<Void>> ack) {
+        String topicString = topic.toString();
+        Consumer<byte[]> callback = callbacks.get(topicString);
+        if (callback == null) {
+          logger.debug("Got a message, but no callback to call. Forgot to unsubscribe?");
+        } else {
+          byte[] message = body.toByteArray();
+//          System.out.println("message = " + Arrays.toString(message));
+          callback.accept(message);
+        }
+        ack.onSuccess(null);  // always acknowledge message
+      }
+
+      @Override
+      public void onPublish(UTF8Buffer topicBuffer, Buffer body, Runnable ack) {
+        logger.warn("onPublish should not be called");
+      }
+
+      @Override
+      public void onFailure(Throwable cause) {
+//        logger.catching(cause);
+        error.set(cause);
+      }
+    });
+    throwIf(error);
+
+    // actually establish the connection
+    connection.connect(new Callback<Void>() {
+      @Override
+      public void onSuccess(Void value) {
+        connection.publish("components", "Ros2Rag is listening".getBytes(), QoS.AT_LEAST_ONCE, false, new Callback<Void>() {
+          @Override
+          public void onSuccess(Void value) {
+            logger.debug("success sending welcome message");
+            try {
+              readyLock.lock();
+              ready = true;
+              readyCondition.signalAll();
+            } finally {
+              readyLock.unlock();
+            }
+          }
+
+          @Override
+          public void onFailure(Throwable value) {
+            logger.debug("failure sending welcome message", value);
+          }
+        });
+      }
+
+      @Override
+      public void onFailure(Throwable cause) {
+//        logger.error("Could not connect", cause);
+        error.set(cause);
+      }
+    });
+    throwIf(error);
+    return this;
+  }
+
+  private void throwIf(AtomicReference<Throwable> error) throws IOException {
+    if (error.get() != null) {
+      throw new IOException(error.get());
+    }
+  }
+
+  public void setQoSForSubscription(QoS qos) {
+    this.qos = qos;
+  }
+
+  public void newConnection(String topic, Consumer<byte[]> callback) {
+    if (!ready) {
+      // TODO should maybe be something more kind than throwing an exception here
+      throw new IllegalStateException("Updater not ready");
+    }
+    // register callback
+    callbacks.put(topic, callback);
+
+    // subscribe at broker
+    Topic[] topicArray = { new Topic(topic, this.qos) };
+    connection.subscribe(topicArray, new Callback<byte[]>() {
+      @Override
+      public void onSuccess(byte[] qoses) {
+        logger.debug("Subscribed, qoses: {}", qoses);
+      }
+
+      @Override
+      public void onFailure(Throwable cause) {
+        logger.error("Could not subscribe", cause);
+      }
+    });
+  }
+
+  /**
+   * Waits until this updater is ready to receive MQTT messages.
+   * If it already is ready, return immediately with the value <code>true</code>.
+   * Otherwise waits for the given amount of time, and either return <code>true</code> within the timespan,
+   * if it got ready, or <code>false</code> upon a timeout.
+   * @param time the maximum time to wait
+   * @param unit the time unit of the time argument
+   * @return whether this updater is ready
+   */
+  public boolean waitUntilReady(long time, TimeUnit unit) {
+    try {
+      readyLock.lock();
+      if (ready) {
+        return true;
+      }
+      return readyCondition.await(time, unit);
+    } catch (InterruptedException e) {
+      e.printStackTrace();
+    } finally {
+      readyLock.unlock();
+    }
+    return false;
+  }
+
+
+  public void close() {
+    if (connection == null) {
+      logger.warn("Stopping without connection. Was setHost() called?");
+      return;
+    }
+    connection.disconnect(new Callback<Void>() {
+      @Override
+      public void onSuccess(Void value) {
+        logger.info("Disconnected from {}", host);
+      }
+
+      @Override
+      public void onFailure(Throwable ignored) {
+        // Disconnects never fail. And we do not care either.
+      }
+    });
+  }
+
+}
diff --git a/ros2rag.example/src/main/proto/dataconfig.proto b/ros2rag.example/src/main/proto/dataconfig.proto
new file mode 100644
index 0000000000000000000000000000000000000000..472b8c6bda8637ec7f637909b33322933a61a053
--- /dev/null
+++ b/ros2rag.example/src/main/proto/dataconfig.proto
@@ -0,0 +1,13 @@
+syntax = "proto3";
+
+package config;
+
+message DataConfig {
+
+  bool enablePosition = 1;
+  bool enableOrientation = 2;
+  bool enableTwistLinear = 3;
+  bool enableTwistAngular = 4;
+
+  int32 publishRate = 5;
+}
diff --git a/ros2rag.example/src/main/proto/linkstate.proto b/ros2rag.example/src/main/proto/linkstate.proto
new file mode 100644
index 0000000000000000000000000000000000000000..dc95138ba49f35497f4bdf061496145168292c9d
--- /dev/null
+++ b/ros2rag.example/src/main/proto/linkstate.proto
@@ -0,0 +1,38 @@
+syntax = "proto3";
+
+package panda;
+
+message PandaLinkState {
+
+  string name = 1;
+
+  message Position {
+    float positionX = 1;
+    float positionY = 2;
+    float positionZ = 3;
+  }
+
+  message Orientation {
+    float orientationX = 1;
+    float orientationY = 2;
+    float orientationZ = 3;
+    float orientationW = 4;
+  }
+
+  message TwistLinear {
+    float twistLinearX = 1;
+    float twistLinearY = 2;
+    float twistLinearZ = 3;
+  }
+
+  message TwistAngular {
+    float twistAngularX = 1;
+    float twistAngularY = 2;
+    float twistAngularZ = 3;
+  }
+
+  Position pos = 2;
+  Orientation orient = 3;
+  TwistLinear tl = 4;
+  TwistAngular ta = 5;
+}
diff --git a/ros2rag.example/src/main/resources/log4j2.xml b/ros2rag.example/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9566029b0cd86cf4a23425159c85150b13e4882c
--- /dev/null
+++ b/ros2rag.example/src/main/resources/log4j2.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="INFO">
+    <Appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
+        </Console>
+    </Appenders>
+    <Loggers>
+        <Root level="info">
+            <AppenderRef ref="Console"/>
+        </Root>
+    </Loggers>
+</Configuration>
diff --git a/ros2rag.senderstub/.gitignore b/ros2rag.senderstub/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..87b4cdd3d7c6a41502ca98703abeeb69a1d536fb
--- /dev/null
+++ b/ros2rag.senderstub/.gitignore
@@ -0,0 +1,5 @@
+build
+src/gen-res/
+src/gen/
+out/
+*.class
diff --git a/ros2rag.senderstub/build.gradle b/ros2rag.senderstub/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..b5a2762a4d84934f60808a09cd8ff32585e9e643
--- /dev/null
+++ b/ros2rag.senderstub/build.gradle
@@ -0,0 +1,45 @@
+apply plugin: 'application'
+apply plugin: 'com.google.protobuf'
+
+sourceCompatibility = 1.8
+
+mainClassName = 'de.tudresden.inf.st.ros2rag.senderstub.Main'
+
+repositories {
+    jcenter()
+}
+
+buildscript {
+    repositories.jcenter()
+    dependencies {
+        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.12'
+    }
+}
+
+sourceSets.main.java.srcDir "src/gen/java"
+jar.manifest.attributes('Main-Class': 'de.tudresden.inf.st.ros2rag.senderstub.Main')
+
+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'
+
+    protobuf files("$projectDir/../ros2rag.example/src/main/proto")
+}
+
+test {
+    useJUnitPlatform()
+
+    maxHeapSize = '1G'
+}
+
+protobuf {
+    // create strange directories, so use default here
+//    generatedFilesBaseDir = "$projectDir/src/gen/java"
+    protoc {
+        // The artifact spec for the Protobuf Compiler
+        artifact = 'com.google.protobuf:protoc:3.0.0'
+    }
+}
diff --git a/ros2rag.senderstub/src/main/java/de/tudresden/inf/st/ros2rag/senderstub/MQTTSender.java b/ros2rag.senderstub/src/main/java/de/tudresden/inf/st/ros2rag/senderstub/MQTTSender.java
new file mode 100644
index 0000000000000000000000000000000000000000..53ecd227a8de4861cfb86901464bd36b3f54baf0
--- /dev/null
+++ b/ros2rag.senderstub/src/main/java/de/tudresden/inf/st/ros2rag/senderstub/MQTTSender.java
@@ -0,0 +1,62 @@
+package de.tudresden.inf.st.ros2rag.senderstub;
+
+import org.fusesource.mqtt.client.QoS;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Small helper to publish messages to a MQTT broker.
+ *
+ * @author rschoene - Initial contribution
+ */
+public interface MQTTSender extends AutoCloseable {
+
+  /**
+   * Sets the host running the MQTT broker.
+   * @param host host name (IP address or domain name)
+   * @param port port to use
+   */
+  MQTTSender setHost(String host, int port);
+
+  /**
+   * Set the timeout used for connecting and disconnecting.
+   * @param connectTimeout     Timeout value
+   * @param connectTimeoutUnit Timeout unit
+   */
+  void setConnectTimeout(long connectTimeout, TimeUnit connectTimeoutUnit);
+
+  /**
+   * Set the timeout used for publishing messages.
+   * @param publishTimeout     Timeout value
+   * @param publishTimeoutUnit Timeout unit
+   */
+  void setPublishTimeout(long publishTimeout, TimeUnit publishTimeoutUnit);
+
+  /**
+   * Publishes a message in a topic at most once.
+   * @param topic   the topic to publish at
+   * @param message the message to publish
+   * @throws Exception if the underlying connection throws an error
+   */
+  default void publish(String topic, byte[] message) throws Exception {
+    this.publish(topic, message, QoS.AT_MOST_ONCE);
+  }
+
+  /**
+   * Publishes a message in a topic with the given quality of service (QoS).
+   * @param topic   the topic to publish at
+   * @param message the message to publish
+   * @param qos     the needed quality of service (at most once, at least once, exactly once)
+   * @throws Exception if the underlying connection throws an error
+   */
+  void publish(String topic, byte[] message, QoS qos) throws Exception;
+
+  /**
+   * Checks, whether the connection to the host (set in the constructor) is established.
+   * @return <code>true</code> if this sender is connected to the host
+   */
+  boolean isConnected();
+
+  @Override
+  void close() throws Exception;
+}
diff --git a/ros2rag.senderstub/src/main/java/de/tudresden/inf/st/ros2rag/senderstub/MQTTSenderImpl.java b/ros2rag.senderstub/src/main/java/de/tudresden/inf/st/ros2rag/senderstub/MQTTSenderImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..f020cac8f876154ec302c43797aedb4114b9e19c
--- /dev/null
+++ b/ros2rag.senderstub/src/main/java/de/tudresden/inf/st/ros2rag/senderstub/MQTTSenderImpl.java
@@ -0,0 +1,97 @@
+package de.tudresden.inf.st.ros2rag.senderstub;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.fusesource.mqtt.client.*;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of a MQTT sender using <code>org.fusesource.mqtt.client</code>.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class MQTTSenderImpl implements MQTTSender {
+
+  private final Logger logger = LogManager.getLogger(MQTTSenderImpl.class);
+  /** The connection to the MQTT broker. */
+  private FutureConnection connection;
+
+  /** Timeout for connect/disconnect methods */
+  private long connectTimeout;
+  /** Unit of timeout for connect/disconnect methods */
+  private TimeUnit connectTimeoutUnit;
+
+  /** Timeout for publish method */
+  private long publishTimeout;
+  /** Unit of timeout for publish method */
+  private TimeUnit publishTimeoutUnit;
+
+  @Override
+  public MQTTSender setHost(String host, int port) {
+    /* The host running the MQTT broker. */
+    URI hostUri = URI.create("tcp://" + host + ":" + port);
+    logger.debug("Host is {}", hostUri);
+    MQTT mqtt = new MQTT();
+    mqtt.setHost(hostUri);
+    connection = mqtt.futureConnection();
+    setConnectTimeout(2, TimeUnit.SECONDS);
+    setPublishTimeout(1, TimeUnit.SECONDS);
+    ensureConnected();
+    return this;
+  }
+
+  @Override
+  public void setConnectTimeout(long connectTimeout, TimeUnit connectTimeoutUnit) {
+    this.connectTimeout = connectTimeout;
+    this.connectTimeoutUnit = connectTimeoutUnit;
+  }
+
+  @Override
+  public void setPublishTimeout(long publishTimeout, TimeUnit publishTimeoutUnit) {
+    this.publishTimeout = publishTimeout;
+    this.publishTimeoutUnit = publishTimeoutUnit;
+  }
+
+  @Override
+  public void publish(String topic, byte[] message, QoS qos) throws Exception {
+    if (ensureConnected()) {
+      logger.debug("Send: {} -> {}", topic, message);
+      connection.publish(topic, message, qos, false).await(publishTimeout, publishTimeoutUnit);
+    }
+  }
+
+  /**
+   * Ensures an established connection.
+   * If already connected, return immediately. Otherwise try to connect.
+   * @return <code>true</code> if the connected was established successfully, <code>false</code> if there was an error
+   */
+  private boolean ensureConnected() {
+    if (!isConnected()) {
+      try {
+        connection.connect().await(connectTimeout, connectTimeoutUnit);
+      } catch (Exception e) {
+        logger.warn("Could not connect", e);
+        return false;
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public boolean isConnected() {
+    return connection != null && connection.isConnected();
+  }
+
+  @Override
+  public void close() throws Exception {
+    if (connection == null) {
+      logger.warn("Stopping without connection.");
+      return;
+    }
+    if (isConnected()) {
+      connection.disconnect().await(connectTimeout, connectTimeoutUnit);
+    }
+  }
+}
diff --git a/ros2rag.senderstub/src/main/java/de/tudresden/inf/st/ros2rag/senderstub/Main.java b/ros2rag.senderstub/src/main/java/de/tudresden/inf/st/ros2rag/senderstub/Main.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3d0ae1ed41d1401b4a4cacab9848e5fa1df4654
--- /dev/null
+++ b/ros2rag.senderstub/src/main/java/de/tudresden/inf/st/ros2rag/senderstub/Main.java
@@ -0,0 +1,35 @@
+package de.tudresden.inf.st.ros2rag.senderstub;
+
+import panda.Linkstate;
+
+import java.util.Arrays;
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    String topic;
+    if (args.length < 1) {
+      topic = "robot/joint1";
+    } else {
+      topic = args[0];
+    }
+    Linkstate.PandaLinkState pls = Linkstate.PandaLinkState.newBuilder()
+        .setName("Joint1")
+        .setPos(Linkstate.PandaLinkState.Position.newBuilder()
+            .setPositionX(0.5f)
+            .setPositionY(0.5f)
+            .setPositionZ(0.5f)
+            .build())
+        .setOrient(Linkstate.PandaLinkState.Orientation.newBuilder()
+            .setOrientationX(0)
+            .setOrientationY(0)
+            .setOrientationZ(0)
+            .setOrientationW(0)
+            .build())
+        .build();
+    MQTTSender sender = new MQTTSenderImpl();
+    sender.setHost("localhost", 1883);
+//    System.out.println("pls.toByteArray() = " + Arrays.toString(pls.toByteArray()));
+    sender.publish(topic, pls.toByteArray());
+    sender.close();
+  }
+}
diff --git a/ros2rag.senderstub/src/main/resources/log4j2.xml b/ros2rag.senderstub/src/main/resources/log4j2.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9566029b0cd86cf4a23425159c85150b13e4882c
--- /dev/null
+++ b/ros2rag.senderstub/src/main/resources/log4j2.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="INFO">
+    <Appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
+        </Console>
+    </Appenders>
+    <Loggers>
+        <Root level="info">
+            <AppenderRef ref="Console"/>
+        </Root>
+    </Loggers>
+</Configuration>
diff --git a/send_one.sh b/send_one.sh
new file mode 100755
index 0000000000000000000000000000000000000000..5af156b01aca6b785ef00e7cd45e573a92274ff1
--- /dev/null
+++ b/send_one.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+./ros2rag.senderstub/build/install/ros2rag.senderstub/bin/ros2rag.senderstub $@
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..5a1c0e7e56e1b7f1066244567969d6de3ffb8c60
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,5 @@
+rootProject.name = 'ros2rag'
+
+include 'ros2rag.base'
+include 'ros2rag.example'
+include 'ros2rag.senderstub'
diff --git a/src/main/jastadd/backend/Aspect.jadd b/src/main/jastadd/backend/Aspect.jadd
deleted file mode 100644
index 76b74a88b6e16cdc511350f75b53041441d399f1..0000000000000000000000000000000000000000
--- a/src/main/jastadd/backend/Aspect.jadd
+++ /dev/null
@@ -1,19 +0,0 @@
-aspect Aspect {
-
-  public static final String ASTNode.aspectIndent = "  ";
-
-  public String Program.generateAspect() {
-    StringBuilder sb = new StringBuilder();
-    generateAspect(sb);
-    return sb.toString();
-  }
-
-  public void Program.generateAspect(StringBuilder sb) {
-
-    sb.append("aspect ROS2RAG {\n");
-
-    // TODO generate getters and setters for ROS2RAG terminals (and attributes?)
-
-    sb.append("}\n");
-  }
-}