diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2119e45bafa4d99b58d418f8dcc6f5d06ebbf9af..5b20de79ae14abf02272bfc9a22db6735c71a5f2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,10 @@
 variables:
   GIT_SUBMODULE_STRATEGY: recursive
 
+services:
+  - name: "eclipse-mosquitto:1.6.9"
+    alias: "mqtt"
+
 stages:
   - build
 
@@ -8,14 +12,6 @@ build:
   image: openjdk:8
   stage: build
   before_script:
-    # - git submodule init
-    # - git submodule sync
-    # - cat .gitmodules
-    # - cat .git/config
-    # - ls -lah *
-    # - rm -rf relast.preprocessor/*
-    # - ls -lah *
-    # - git submodule update --remote
     - ls -lah *
   script:
     - ./gradlew --no-daemon build
diff --git a/ros2rag.base/src/main/resources/MqttUpdater.jadd b/ros2rag.base/src/main/resources/MqttUpdater.jadd
index ef3e483fc80a389f4d5311cbfc6b9c0dccab7885..6e986f2df4fc44f67e28b0015e524e1455f5e5fd 100644
--- a/ros2rag.base/src/main/resources/MqttUpdater.jadd
+++ b/ros2rag.base/src/main/resources/MqttUpdater.jadd
@@ -145,16 +145,18 @@ public class MqttUpdater {
 
     // subscribe at broker
     org.fusesource.mqtt.client.Topic[] topicArray = { new org.fusesource.mqtt.client.Topic(topic, this.qos) };
-    connection.subscribe(topicArray, new org.fusesource.mqtt.client.Callback<byte[]>() {
-      @Override
-      public void onSuccess(byte[] qoses) {
-        logger.debug("Subscribed to {}, qoses: {}", topic, qoses);
-      }
+    connection.getDispatchQueue().execute(() -> {
+      connection.subscribe(topicArray, new org.fusesource.mqtt.client.Callback<byte[]>() {
+        @Override
+        public void onSuccess(byte[] qoses) {
+          logger.debug("Subscribed to {}, qoses: {}", topic, qoses);
+        }
 
-      @Override
-      public void onFailure(Throwable cause) {
-        logger.error("Could not subscribe to {}", topic, cause);
-      }
+        @Override
+        public void onFailure(Throwable cause) {
+          logger.error("Could not subscribe to {}", topic, cause);
+        }
+      });
     });
   }
 
@@ -187,30 +189,34 @@ public class MqttUpdater {
       logger.warn("Stopping without connection. Was setHost() called?");
       return;
     }
-    connection.disconnect(new org.fusesource.mqtt.client.Callback<Void>() {
-      @Override
-      public void onSuccess(Void value) {
-        logger.info("Disconnected {} from {}", name, host);
-      }
+    connection.getDispatchQueue().execute(() -> {
+      connection.disconnect(new org.fusesource.mqtt.client.Callback<Void>() {
+        @Override
+        public void onSuccess(Void value) {
+          logger.info("Disconnected {} from {}", name, host);
+        }
 
-      @Override
-      public void onFailure(Throwable ignored) {
-        // Disconnects never fail. And we do not care either.
-      }
+        @Override
+        public void onFailure(Throwable ignored) {
+          // Disconnects never fail. And we do not care either.
+        }
+      });
     });
   }
 
   public void publish(String topic, byte[] bytes) {
-    connection.publish(topic, bytes, qos, false, new org.fusesource.mqtt.client.Callback<Void>() {
-      @Override
-      public void onSuccess(Void value) {
-        logger.debug("Published some bytes to {}", topic);
-      }
+    connection.getDispatchQueue().execute(() -> {
+      connection.publish(topic, bytes, qos, false, new org.fusesource.mqtt.client.Callback<Void>() {
+        @Override
+        public void onSuccess(Void value) {
+          logger.debug("Published some bytes to {}", topic);
+        }
 
-      @Override
-      public void onFailure(Throwable value) {
-        logger.warn("Could not publish on topic '{}'", topic);
-      }
+        @Override
+        public void onFailure(Throwable value) {
+          logger.warn("Could not publish on topic '{}'", topic);
+        }
+      });
     });
   }
 }
diff --git a/ros2rag.tests/build.gradle b/ros2rag.tests/build.gradle
index f8225fc931fe5dba242d61c53d93fc83a5daa28b..c37cfdd23f9a7ffd87393fda2e722c821983eff7 100644
--- a/ros2rag.tests/build.gradle
+++ b/ros2rag.tests/build.gradle
@@ -89,6 +89,7 @@ task preprocessExampleTest(type: JavaExec, group: 'verification') {
 task compileExampleTest(type: RelastTest) {
     verbose = true
     useJastAddNames = true
+    jastAddList = 'JastAddList'
     relastFiles 'src/test/02-after-ros2rag/example/Grammar.relast'
     grammarName = 'src/test/03-after-relast/example/example'
     packageName = 'example.ast'
diff --git a/ros2rag.tests/src/test/01-input/example/Example.jadd b/ros2rag.tests/src/test/01-input/example/Example.jadd
index 40ae586a2a0feb5c4fe274cc71ba5f05da4cbaad..53f41b6b1d5f7c181d0e4ea06a34ab8240eb82d1 100644
--- a/ros2rag.tests/src/test/01-input/example/Example.jadd
+++ b/ros2rag.tests/src/test/01-input/example/Example.jadd
@@ -79,7 +79,10 @@ aspect GrammarTypes {
     return false;
   }
 
+  syn double RobotArm.speedLow() = 0.4d;
+  syn double RobotArm.speedHigh() = 1.0d;
+
   syn double RobotArm.getAppropriateSpeed() {
-    return isInSafetyZone() ? 0.4d : 1.0d;
+    return isInSafetyZone() ? speedLow() : speedHigh();
   }
 }
diff --git a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java
index 9ff94b7b15bbbce1a2d2936abd53921c7b140591..d70f89378395d32ff0b88cc0be72fab72e85f84c 100644
--- a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java
+++ b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/ExampleTest.java
@@ -1,9 +1,18 @@
 package org.jastadd.ros2rag.tests;
 
+import com.google.protobuf.InvalidProtocolBufferException;
+import config.Robotconfig.RobotConfig;
 import example.ast.*;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.Test;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
 
 /**
  * Test case "example".
@@ -12,10 +21,90 @@ import java.io.IOException;
  */
 public class ExampleTest {
 
+  private static final double DELTA = 0.001d;
+  private static final String TOPIC_CONFIG = "robot/config";
+  private static final String TOPIC_JOINT1 = "robot/arm/joint1";
+
+  private Model model;
+  private RobotArm robotArm;
+  private Joint joint1;
+
+  private CountDownLatch stop;
+
+  @AfterEach
+  public void signalCondition() {
+    if (stop != null) {
+      stop.countDown();
+    }
+  }
+
   @Test
-  public void buildModel() throws IOException {
-    Model model = new Model();
-    model.MqttSetHost("localhost");
+  public void buildModel() {
+    createModel();
+  }
+
+  @Test
+  public void communicate() throws IOException, InterruptedException {
+    createModel();
+    List<RobotConfig> receivedConfigs = new ArrayList<>();
+
+    CountDownLatch start = new CountDownLatch(1);
+    stop = new CountDownLatch(1);
+    createListenerThread(receivedConfigs, start, stop);
+    start.await();
+
+    model.MqttSetHost(TestUtils.getMqttHost());
+    assertTrue(model.MqttWaitUntilReady(2, TimeUnit.SECONDS));
+    // joint is currently within the safety zone, so speed should be low
+    robotArm.connectAppropriateSpeed(TOPIC_CONFIG, true);
+    joint1.connectCurrentPosition(TOPIC_JOINT1);
+
+    // now change the position of the joint out of the safety zone
+    joint1.setCurrentPosition(makePosition(2, 2, 2));
+
+    // and wait for MQTT to send/receive messages
+    TimeUnit.SECONDS.sleep(2);
+
+    // there should be two configs received by now (the initial one, and the updated)
+    assertEquals(2, receivedConfigs.size());
+    RobotConfig actualInitialConfig = receivedConfigs.get(0);
+    assertEquals(robotArm.speedLow(), actualInitialConfig.getSpeed(), DELTA);
+
+    RobotConfig actualUpdatedConfig = receivedConfigs.get(1);
+    assertEquals(robotArm.speedHigh(), actualUpdatedConfig.getSpeed(), DELTA);
+    model.MqttCloseConnections();
+  }
+
+  private void createListenerThread(List<RobotConfig> receivedConfigs,
+                                    CountDownLatch startCondition, CountDownLatch stopCondition) {
+    new Thread(() -> {
+      MqttUpdater receiver = new MqttUpdater("receiver");
+      try {
+        receiver.setHost(TestUtils.getMqttHost(), TestUtils.getMqttDefaultPort());
+      } catch (IOException e) {
+        fail("Could not set host: " + e.getMessage());
+      }
+      assertTrue(receiver.waitUntilReady(2, TimeUnit.SECONDS));
+      receiver.newConnection(TOPIC_CONFIG, bytes -> {
+        try {
+          RobotConfig config = RobotConfig.parseFrom(bytes);
+          receivedConfigs.add(config);
+        } catch (InvalidProtocolBufferException e) {
+          fail("Received bad config: " + e.getMessage());
+        }
+      });
+      startCondition.countDown();
+      try {
+        stopCondition.await();
+        receiver.close();
+      } catch (InterruptedException e) {
+        e.printStackTrace();
+      }
+    }).start();
+  }
+
+  private void createModel() {
+    model = new Model();
 
     ZoneModel zoneModel = new ZoneModel();
     zoneModel.setSize(makePosition(1, 1, 1));
@@ -32,10 +121,10 @@ public class ExampleTest {
     zoneModel.addSafetyZone(safetyZone);
     model.setZoneModel(zoneModel);
 
-    RobotArm robotArm = new RobotArm();
+    robotArm = new RobotArm();
     robotArm.setAttributeTestSource(1);  // set initial value, no trigger
 
-    Joint joint1 = new Joint();
+    joint1 = new Joint();
     joint1.setName("joint1");
     joint1.setCurrentPosition(myPosition);
 
diff --git a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/TestUtils.java b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/TestUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..083b9982cfce12243080dd10d75b15a22dd2c8cf
--- /dev/null
+++ b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/TestUtils.java
@@ -0,0 +1,24 @@
+package org.jastadd.ros2rag.tests;
+
+/**
+ * Utility methods for tests.
+ *
+ * @author rschoene - Initial contribution
+ */
+public class TestUtils {
+
+  public static String getMqttHost() {
+    if (System.getenv("GITLAB_CI") != null) {
+      // we are in the CI, so use "mqtt" as host
+      return "mqtt";
+    } {
+      // else assume a locally running mqtt broker
+      return "localhost";
+    }
+  }
+
+  public static int getMqttDefaultPort() {
+    return 1883;
+  }
+
+}