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; + } + +}