diff --git a/ros3rag.coordinator/.gitignore b/ros3rag.coordinator/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..87b4cdd3d7c6a41502ca98703abeeb69a1d536fb --- /dev/null +++ b/ros3rag.coordinator/.gitignore @@ -0,0 +1,5 @@ +build +src/gen-res/ +src/gen/ +out/ +*.class diff --git a/ros3rag.coordinator/build.gradle b/ros3rag.coordinator/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..3b03cd87dc7084cfa0ab5df80615c94bff662b6d --- /dev/null +++ b/ros3rag.coordinator/build.gradle @@ -0,0 +1,159 @@ +buildscript { + repositories.mavenCentral() + dependencies { + classpath group: 'org.jastadd', name: 'jastaddgradle', version: '1.13.3' + } +} + +plugins { + id 'ros3rag.java-application-conventions' + id 'ros3rag.java-ragconnect-conventions' +} + +mainClassName = 'de.tudresden.inf.st.coordinator.MainCoordinator' + +dependencies { +} + +ext.ragConnectInputGrammar = 'src/main/jastadd/Coordinator.relast' +ext.ragConnectInputConnect = 'src/main/jastadd/Coordinator.connect' +ext.ragConnectRootNode = 'Coordinator' +ext.relastFiles = ["src/gen/jastadd/Coordinator.relast", "src/gen/jastadd/RagConnect.relast"] +ext.jastaddAstPackage = 'de.tudresden.inf.st.coordinator.ast' + + +apply plugin: 'jastadd' + +dependencies { +// jastadd2 "org.jastadd:jastadd:2.3.5" + jastadd2 fileTree(include: ['jastadd2.jar'], dir: '../libs') + api group: 'org.fusesource.mqtt-client', name: 'mqtt-client', version: '1.15' + +} + +sourceCompatibility = 11 +targetCompatibility = 11 + +// phases: ragConnect -> RelAst -> JastAdd +// phase: ragConnect +task ragConnect(type: JavaExec) { + group = 'Build' + main = 'org.jastadd.ragconnect.compiler.Compiler' + classpath = configurations.ragconnectClasspath + + args([ + '--o=src/gen/jastadd', + project.ext.ragConnectInputGrammar, + project.ext.ragConnectInputConnect, + '--logReads', + '--logWrites', +// '--verbose', + '--rootNode=' + project.ext.ragConnectRootNode, + '--incremental=param', + "--tracing=cache,flush" + ]) + +} + +// Input files for relast +//def relastFiles = ["src/gen/jastadd/MinimalModel.relast", "src/gen/jastadd/RagConnect.relast"] + +task grammar2uml(type: JavaExec) { + main = 'de.tudresden.inf.st.jastadd.grammar2uml.compiler.Compiler' + classpath = configurations.grammar2umlClasspath + + args([ + '--verbose', + 'src/gen/jastadd/types.relast' + ] + project.ext.relastFiles) +} + +// phase: RelAst +task relastToJastAdd(type: JavaExec) { + group = 'Build' + main = "-jar" + + args(["../libs/relast.jar", + "--grammarName=./src/gen/jastadd/model", + "--useJastAddNames", + "--listClass=java.util.ArrayList", + "--jastAddList=JastAddList", + "--serializer=jackson", + "--resolverHelper", + "--file", + "src/gen/jastadd/types.relast" + ] + project.ext.relastFiles) + + inputs.files project.ext.relastFiles + ['src/gen/jastadd/types.relast'] + outputs.files file("./src/gen/jastadd/model.ast"), file("./src/gen/jastadd/model.jadd") +} + +// phase: JastAdd +jastadd { + configureModuleBuild() + modules { + //noinspection GroovyAssignabilityCheck + module("coordinator") { + +// 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/Coordinator.flex" + } + + parser { + include "src/main/jastadd/Coordinator.parser" + } + } + } + + cleanGen.doFirst { + delete "src/gen/java/de" + delete "src/gen-res/BuildInfo.properties" + } + + module = "coordinator" + + astPackage = project.ext.jastaddAstPackage + + genDir = 'src/gen/java' + + buildInfoDir = 'src/gen-res' + + parser.name = 'CoordinatorParser' + + scanner.genDir = "src/gen/java/de/tudresden/inf/st/coordinator/scanner" + parser.genDir = "src/gen/java/de/tudresden/inf/st/coordinator/parser" + + // jastaddOptions = ["--lineColumnNumbers", "--visitCheck=true", "--rewrite=cnta", "--cache=all"] + // default options are: '--rewrite=cnta', '--safeLazy', '--visitCheck=false', '--cacheCycle=false' + extraJastAddOptions = [ + '--lineColumnNumbers', + '--List=JastAddList', + '--cache=all', + "--flush=api", + "--incremental=param,debug", + "--tracing=cache,flush", + ] +} + +cleanGen.doFirst { + delete "src/gen/jastadd" +} + +// Workflow configuration for phases +generateAst.dependsOn relastToJastAdd +relastToJastAdd.dependsOn ragConnect diff --git a/ros3rag.coordinator/src/main/jastadd/Coordinator.connect b/ros3rag.coordinator/src/main/jastadd/Coordinator.connect new file mode 100644 index 0000000000000000000000000000000000000000..d7bca5d0925174df4c372f77c2f155fd7abdee94 --- /dev/null +++ b/ros3rag.coordinator/src/main/jastadd/Coordinator.connect @@ -0,0 +1,15 @@ +receive Component.IncomingStatus ; +//send tree Coordinator.NextComponentToStart using NameOfComponent ; +// +//NameOfComponent maps Component c to String {: +// return c.getName(); +//:} + +send Component.NextCommand using CommandCheck ; + +CommandCheck maps String command to String {: + if (command == null) { + reject(); + } + return command; +:} diff --git a/ros3rag.coordinator/src/main/jastadd/Coordinator.flex b/ros3rag.coordinator/src/main/jastadd/Coordinator.flex new file mode 100644 index 0000000000000000000000000000000000000000..39f328d99e5c3dd0f7a151adef8c3b43d5b29fad --- /dev/null +++ b/ros3rag.coordinator/src/main/jastadd/Coordinator.flex @@ -0,0 +1,65 @@ +package de.tudresden.inf.st.coordinator.scanner; + +import de.tudresden.inf.st.coordinator.parser.CoordinatorParser.Terminals; + +%% + +// define the signature for the generated scanner +%public +%final +%class CoordinatorScanner +%extends beaver.Scanner + +// the interface between the scanner and the parser is the nextToken() method +%type beaver.Symbol +%function nextToken +%yylexthrow beaver.Scanner.Exception + +// store line and column information in the tokens +%line +%column + +// this code will be inlined in the body of the generated scanner class +%{ + private beaver.Symbol sym(short id) { + return new beaver.Symbol(id, yyline + 1, yycolumn + 1, yylength(), yytext()); + } + private beaver.Symbol symText(short id) { + return new beaver.Symbol(id, yyline + 1, yycolumn + 1, yylength(), yytext().substring(1, yytext().length() - 1)); + } +%} + +WhiteSpace = [ ] | \t | \f | \n | \r | \r\n +//Identifier = [:jletter:][:jletterdigit:]* +Text = \" ([^\"]*) \" + +//Integer = [:digit:]+ // | "+" [:digit:]+ | "-" [:digit:]+ +//Real = [:digit:]+ "." [:digit:]* | "." [:digit:]+ + +Comment = "//" [^\n\r]+ + +%% + +// discard whitespace information and comments +{WhiteSpace} { } +{Comment} { } + +// ** token definitions ** +// Begin of line with capital letter +"components" { return sym(Terminals.COMPONENTS); } +"component" { return sym(Terminals.COMPONENT); } +"docker compose" { return sym(Terminals.DOCKER_COMPOSE); } +"mqtt topic" { return sym(Terminals.MQTT_TOPIC); } +"=" { return sym(Terminals.EQUALS); } +"," { return sym(Terminals.COMMA); } +"<" { return sym(Terminals.LT); } +"{" { return sym(Terminals.LB_CURLY); } +"}" { return sym(Terminals.RB_CURLY); } + +//{Identifier} { return sym(Terminals.NAME); } +{Text} { return symText(Terminals.TEXT); } +//{Integer} { return sym(Terminals.INTEGER); } +//{Real} { return sym(Terminals.REAL); } +<<EOF>> { return sym(Terminals.EOF); } +/* error fallback */ +[^] { throw new Error("Illegal character '"+ yytext() +"' at line " + (yyline+1) + " column " + (yycolumn+1)); } diff --git a/ros3rag.coordinator/src/main/jastadd/Coordinator.jrag b/ros3rag.coordinator/src/main/jastadd/Coordinator.jrag new file mode 100644 index 0000000000000000000000000000000000000000..33bd6e56a684c7e06ee36fdc9839353d0bc408e2 --- /dev/null +++ b/ros3rag.coordinator/src/main/jastadd/Coordinator.jrag @@ -0,0 +1,62 @@ +aspect Computation { + syn String Component.getNextCommand() { + if (!getIncomingStatus().equals("up")) { + System.out.println(getName() + " not up"); + // component is not "up" yet + return null; + } + for (Component predecessor : getPredecessorList()) { + if (!predecessor.getIncomingStatus().equals("ready")) { + // one of required component is not "ready" yet + System.out.println(getName() + " missing " + predecessor.getName()); + return null; + } + } + // all required components are "ready", and this component is "up" + System.out.println(getName() + " ready to be started"); + return "start"; + } + + public void Component.callDockerCompose() { + String[] args = { "docker-compose", "up", "-d", getDockerComposeName() }; + System.out.println("Would start > " + java.util.Arrays.toString(args)); + ProcessBuilder builder = new ProcessBuilder(args); +// builder.redirectOutput(err); +// builder.redirectError(err); + +// Process process = builder.start(); +// process.waitFor(); +// return process.exitValue(); + return; + } +} + +aspect Printing { + syn String Coordinator.prettyPrint() { + StringBuilder sb = new StringBuilder(); + for (Component comp : getComponentList()) { + sb.append(comp.prettyPrint()).append("\n"); + } + return sb.toString(); + } + + syn String Component.prettyPrint() { + return "<Name: " + getName() + ", DockerComposeName: " + getDockerComposeName() + ", MqttTopicPrefix: " + getMqttTopicPrefix() + ", IncomingStatus: " + getIncomingStatus() + ", NextCommand: " + getNextCommand() + ">"; + } +} + +aspect AdditionalTypes { + public class ReversedList<T> extends beaver.Symbol implements Iterable<T> { + private java.util.Deque<T> delegatee = new java.util.ArrayDeque<>(); + + public java.util.Iterator<T> iterator() { + return delegatee.descendingIterator(); + } + + public void add(T t) { + delegatee.add(t); + } + } + + public class StringList extends ReversedList<String> {} +} diff --git a/ros3rag.coordinator/src/main/jastadd/Coordinator.parser b/ros3rag.coordinator/src/main/jastadd/Coordinator.parser new file mode 100644 index 0000000000000000000000000000000000000000..e00f826825b8a9825f4a3abc666324cdc2639dfa --- /dev/null +++ b/ros3rag.coordinator/src/main/jastadd/Coordinator.parser @@ -0,0 +1,65 @@ +%header {: +package de.tudresden.inf.st.coordinator.parser; +import de.tudresden.inf.st.coordinator.ast.*; +import java.util.Map; +import java.util.HashMap; +:} ; + +%embed {: + private static <T extends ASTNode<?>> void insertZero(JastAddList<T> listNode, T child) { + listNode.insertChild(child, 0); + } + + private void replaceRelations(Coordinator o) { + for (ParsedPrecedenceRelation rel : o.getParsedPrecedenceRelationList()) { + for (Component pred : rel.getPredecessorList()) { + for (Component succ : rel.getSuccessorList()) { + pred.addSuccessor(succ); + } + } + } + } +:} ; + +%goal goal; +Coordinator goal = + COMPONENTS LB_CURLY coordinator_body.o RB_CURLY {: o.treeResolveAll(); replaceRelations(o); return o; :} + +Coordinator coordinator_body = + component.c goal.o {: o.addComponent(c); return o; :} + string_list.preds LT string_list.succs goal.o + {: + ParsedPrecedenceRelation rel = new ParsedPrecedenceRelation(); + o.addParsedPrecedenceRelation(rel); + preds.forEach(pred -> rel.addPredecessor(Component.createRef(pred))); + succs.forEach(succ -> rel.addPredecessor(Component.createRef(succ))); + return o; + :} + | {: return new Coordinator(); :} + ; + +Component component = + COMPONENT TEXT.name DOCKER_COMPOSE EQUALS TEXT.dc MQTT_TOPIC EQUALS TEXT.mqtt + {: + Component result = new Component(); + result.setName(name); + result.setDockerComposeName(dc); + result.setMqttTopicPrefix(mqtt); + return result; + :} + ; + +StringList string_list = + LB_SQUARE string_list_body.slb RB_SQUARE {: return slb; :} + | LB_SQUARE RB_SQUARE {: return new StringList(); :} + ; + +StringList string_list_body = + TEXT.n COMMA string_list_body.slb {: slb.add(n); return slb; :} + | TEXT.n + {: + StringList result = new StringList(); + result.add(n); + return result; + :} + ; diff --git a/ros3rag.coordinator/src/main/jastadd/Coordinator.relast b/ros3rag.coordinator/src/main/jastadd/Coordinator.relast new file mode 100644 index 0000000000000000000000000000000000000000..f5acd03b7aa2ae41cd3dd2609b6156e2e5e07a9d --- /dev/null +++ b/ros3rag.coordinator/src/main/jastadd/Coordinator.relast @@ -0,0 +1,6 @@ +Coordinator ::= Component* ParsedPrecedenceRelation* ; // /NextComponentToStart:Component/ ; +Component ::= <Name:String> <DockerComposeName:String> <MqttTopicPrefix:String> <IncomingStatus:String> /<NextCommand:String>/ ; +rel Component.Predecessor* <-> Component.Successor* ; +ParsedPrecedenceRelation ; +rel ParsedPrecedenceRelation.Predecessor* -> Component ; +rel ParsedPrecedenceRelation.Successor* -> Component ; diff --git a/ros3rag.coordinator/src/main/jastadd/dummy/types.connect b/ros3rag.coordinator/src/main/jastadd/dummy/types.connect new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ros3rag.coordinator/src/main/jastadd/dummy/types.jrag b/ros3rag.coordinator/src/main/jastadd/dummy/types.jrag new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ros3rag.coordinator/src/main/jastadd/dummy/types.relast b/ros3rag.coordinator/src/main/jastadd/dummy/types.relast new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ros3rag.coordinator/src/main/java/de/tudresden/inf/st/coordinator/MainCoordinator.java b/ros3rag.coordinator/src/main/java/de/tudresden/inf/st/coordinator/MainCoordinator.java new file mode 100644 index 0000000000000000000000000000000000000000..e184767ad145c9bcc9b773fa9d938e4945c9dcae --- /dev/null +++ b/ros3rag.coordinator/src/main/java/de/tudresden/inf/st/coordinator/MainCoordinator.java @@ -0,0 +1,166 @@ +package de.tudresden.inf.st.coordinator; + +import beaver.Parser; +import de.tudresden.inf.st.coordinator.ast.Component; +import de.tudresden.inf.st.coordinator.ast.Coordinator; +import de.tudresden.inf.st.coordinator.ast.MqttHandler; +import de.tudresden.inf.st.coordinator.parser.CoordinatorParser; +import de.tudresden.inf.st.coordinator.scanner.CoordinatorScanner; + +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * Entrypoint for Coordinator. + * + * @author rschoene - Initial contribution + */ +public class MainCoordinator { + + private static final String TOPIC_EXIT = "coordinator/exit"; + private static final String TOPIC_MODEL = "coordinator/model"; + private static final String TOPIC_STATUS = "coordinator/status"; + private static final String TOPIC_READY = "coordinator/ready"; + private Coordinator coordinator; + private MqttHandler mainHandler; + private Component rosCore; + private Component mosquitto; + + public static void main(String[] args) throws Exception { + MainCoordinator main = new MainCoordinator(); +// main.manuallyBuild(); + main.parsedBuild(); + main.start(); + } + + private void manuallyBuild() { + coordinator = new Coordinator(); + Component robotCtrlA = newComponent("Robot Control A", "ros_place_a", "ros-place-a"); + Component robotCtrlB = newComponent("Robot Control B", "ros_place_b", "ros-place-b"); + Component ragA = newComponent("RAG A", "rag_place_a", "rag-a"); + Component ragB = newComponent("RAG B", "rag_place_b", "rag-b"); + Component random = newComponent("Random Dummy", "cgv_random_dummy", "random"); + rosCore = newComponent("ROS core", "ros_core", ""); + mosquitto = newComponent("MQTT broker", "mosquitto", ""); + + coordinator.addComponent(robotCtrlA); + coordinator.addComponent(robotCtrlB); + coordinator.addComponent(ragA); + coordinator.addComponent(ragB); + coordinator.addComponent(random); + coordinator.addComponent(rosCore); + coordinator.addComponent(mosquitto); + + // ros core needed for all other ros nodes + rosCore.addSuccessor(robotCtrlA); + rosCore.addSuccessor(robotCtrlB); + rosCore.addSuccessor(random); + + // mqtt broker needed for all robot ctrl and rag nodes + mosquitto.addSuccessor(robotCtrlA); + mosquitto.addSuccessor(robotCtrlB); + mosquitto.addSuccessor(ragA); + mosquitto.addSuccessor(ragB); + + // robot control needs to start before rag + robotCtrlA.addSuccessor(ragA); + robotCtrlB.addSuccessor(ragB); + + // rag place B needs to start before rag place A + ragB.addSuccessor(ragA); + + // random should start last + robotCtrlA.addSuccessor(random); + robotCtrlB.addSuccessor(random); + ragA.addSuccessor(random); + ragB.addSuccessor(random); + } + + private void parsedBuild() throws IOException, Parser.Exception { + Reader in = Files.newBufferedReader(Paths.get("src", "main", "resources", "ros3rag.coordinator")); + + CoordinatorScanner scanner = new CoordinatorScanner(in); + CoordinatorParser parser = new CoordinatorParser(); + + coordinator = (Coordinator) parser.parse(scanner); + + for (Component comp : coordinator.getComponentList()) { + if (comp.getName().equals("rosCore")) { + rosCore = comp; + } + if (comp.getName().equals("mosquitto")) { + mosquitto = comp; + } + } + + in.close(); + } + + private void start() throws Exception { + final String mqttHost = "localhost"; + + coordinator.ragconnectSetupMqttWaitUntilReady(2, TimeUnit.SECONDS); + + for (Component comp : coordinator.getComponentList()) { + comp.connectIncomingStatus("mqtt://" + mqttHost + "/" + comp.getMqttTopicPrefix() + "/status"); + comp.connectNextCommand("mqtt://" + mqttHost + "/" + comp.getMqttTopicPrefix() + "/command", false); + } + + for (Component comp : coordinator.getComponentList()) { + comp.callDockerCompose(); + } + + mainHandler = new MqttHandler().dontSendWelcomeMessage(); + mainHandler.setHost(mqttHost); + mainHandler.waitUntilReady(2, TimeUnit.SECONDS); + CountDownLatch exitCondition = new CountDownLatch(1); + mainHandler.newConnection(TOPIC_EXIT, bytes -> exitCondition.countDown()); + mainHandler.newConnection(TOPIC_MODEL, bytes -> this.printStatus()); + mainHandler.newConnection(TOPIC_READY, bytes -> this.setReady()); + Runtime.getRuntime().addShutdownHook(new Thread(this::close)); + + // wait some time for ros_core and mqtt_broker to be started + TimeUnit.SECONDS.sleep(2); + + setReady(); + + exitCondition.await(); + } + + private void setReady() { + // set ros-core and mqtt-broker to ready + rosCore.setIncomingStatus("ready"); + mosquitto.setIncomingStatus("ready"); + } + + private void printStatus() { + String content = coordinator.prettyPrint(); + System.out.println(content); + if (mainHandler != null) { + mainHandler.publish(TOPIC_STATUS, content.getBytes(StandardCharsets.UTF_8)); + } + } + + private void close() { + if (coordinator != null) { + coordinator.ragconnectCloseConnections(); + } + if (mainHandler != null) { + mainHandler.close(); + } + } + + private static Component newComponent(String name, String dockerComposeName, String mqttTopicPrefix) { + Component result = new Component(); + result.setName(name); + result.setDockerComposeName(dockerComposeName); + result.setMqttTopicPrefix(mqttTopicPrefix); + return result; + } + +} diff --git a/ros3rag.coordinator/src/main/resources/ros3rag.coordinator b/ros3rag.coordinator/src/main/resources/ros3rag.coordinator new file mode 100644 index 0000000000000000000000000000000000000000..c37c6a6aaa076ebf9d6dc6a8dee8c52217267bdf --- /dev/null +++ b/ros3rag.coordinator/src/main/resources/ros3rag.coordinator @@ -0,0 +1,25 @@ +components { + component "robotCtrlA" docker compose = "ros_place_a" mqtt topic = "ros-place-a" + component "robotCtrlB" docker compose = "ros_place_b" mqtt topic = "ros-place-b" + component "ragA" docker compose = "rag_place_a" mqtt topic = "rag-a" + component "ragB" docker compose = "rag_place_b" mqtt topic = "rag-b" + component "random" docker compose = "cgv_random_dummy" mqtt topic = "random" + component "rosCore" docker compose = "ros_core" mqtt topic = "" + component "mosquitto" docker compose = "mosquitto" mqtt topic = "" + + // ros core needed for all other ros nodes + rosCore < robotCtrlA, robotCtrlB, random + + // mqtt broker needed for all robot ctrl and rag nodes + mosquitto < robotCtrlA, robotCtrlB, ragA, ragB + + // robot control needs to start before rag + robotCtrlA < ragA + robotCtrlB < ragB + + // rag place B needs to start before rag place A + ragB < ragA + + // random should start last + robotCtrlA, robotCtrlB, ragA, ragB < random +} diff --git a/settings.gradle b/settings.gradle index bd2c1a8453b44141ce617dbfeb3570e9786f6637..b2e62ebd90e287a75d9234f132668859ec1bb9ff 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,7 @@ rootProject.name = 'ros3rag' include 'ros3rag.placeA' include 'ros3rag.placeB' include 'ros3rag.common' +include 'ros3rag.coordinator' // include 'ros3rag.senderstub' // include 'ros3rag.receiverstub'