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"); - } -}