getAttributeValues(EObject node, String attrNa
/**
* Retrieve a specific annotation in a comment associated with the given model element in the AST.
*
- * This will look for a comment. If one is found, it searches for the given annotation {@code
- * key}. and extracts any string that follows the annotation marker.
+ *
This will look for a comment. If one is found, it searches for the given annotation `key`.
+ * and extracts any string that follows the annotation marker.
*
* @param object the AST model element to search a comment for
* @param key the specific annotation key to be extracted
- * @return {@code null} if no JavaDoc style comment was found or if it does not contain the given
- * key. The string immediately following the annotation marker otherwise.
+ * @return `null` if no JavaDoc style comment was found or if it does not contain the given key.
+ * The string immediately following the annotation marker otherwise.
*/
public static String findAnnotationInComments(EObject object, String key) {
if (!(object.eResource() instanceof XtextResource)) return null;
@@ -289,6 +289,23 @@ public static boolean isEnclave(Instantiation node) {
return getEnclaveAttribute(node) != null;
}
+ /**
+ * Return the {@code @federate} attribute annotated on the given node.
+ *
+ *
Returns null if there is no such attribute.
+ */
+ public static Attribute getFederateAttribute(Instantiation node) {
+ return findAttributeByName(node, "federate");
+ }
+
+ /**
+ * Return true if the specified instance has an {@code @federate} attribute. TODO: this needs some
+ * other name bec of c target federate
+ */
+ public static boolean isFederate(Instantiation node) {
+ return getFederateAttribute(node) != null;
+ }
+
/**
* Annotate @{code node} with enclave @attribute
*
diff --git a/core/src/main/java/org/lflang/validation/AttributeSpec.java b/core/src/main/java/org/lflang/validation/AttributeSpec.java
index a685e98ca7..258de78eaa 100644
--- a/core/src/main/java/org/lflang/validation/AttributeSpec.java
+++ b/core/src/main/java/org/lflang/validation/AttributeSpec.java
@@ -224,9 +224,15 @@ enum AttrParamType {
ATTRIBUTE_SPECS_BY_NAME.put(
"enclave",
new AttributeSpec(List.of(new AttrParamSpec(EACH_ATTR, AttrParamType.BOOLEAN, true))));
+
+ ATTRIBUTE_SPECS_BY_NAME.put(
+ "federate",
+ new AttributeSpec(List.of(new AttrParamSpec(EACH_ATTR, AttrParamType.BOOLEAN, true))));
+
ATTRIBUTE_SPECS_BY_NAME.put("_fed_config", new AttributeSpec(List.of()));
// @property(name="", tactic="", spec="")
// SMTL is the safety fragment of Metric Temporal Logic (MTL).
+
ATTRIBUTE_SPECS_BY_NAME.put(
"property",
new AttributeSpec(
diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/ConnectionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/ConnectionGenerator.kt
new file mode 100644
index 0000000000..a9ebb8772e
--- /dev/null
+++ b/core/src/main/kotlin/org/lflang/generator/cpp/ConnectionGenerator.kt
@@ -0,0 +1,6 @@
+package org.lflang.generator.cpp
+
+interface ConnectionGenerator {
+ abstract fun generateDeclarations() : String
+ abstract fun generateInitializers() : String
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt
index 1a12f6424a..56cc8f75a9 100644
--- a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt
+++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt
@@ -212,7 +212,7 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) {
* The body of this method will declare all triggers, dependencies and antidependencies to the runtime.
*/
fun generateDefinition() = with(PrependOperator) {
- val indexedConnections = reactor.connections.withIndex()
+ val indexedConnections = reactor.connections.filter { !it.isFederateConnection }.withIndex()
"""
|${reactor.templateLine}
|void ${reactor.templateName}::assemble() {
diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppInstanceGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppInstanceGenerator.kt
index 19466c2fce..029e90d498 100644
--- a/core/src/main/kotlin/org/lflang/generator/cpp/CppInstanceGenerator.kt
+++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppInstanceGenerator.kt
@@ -135,13 +135,13 @@ class CppInstanceGenerator(
/** Generate C++ include statements for each reactor that is instantiated */
fun generateIncludes(): String =
- reactor.instantiations.map { fileConfig.getReactorHeaderPath(it.reactor) }
+ reactor.instantiations.filter { !AttributeUtils.isFederate(it) }.map { fileConfig.getReactorHeaderPath(it.reactor) }
.distinct()
.joinToString(separator = "\n") { """#include "${it.toUnixString()}" """ }
/** Generate declaration statements for all reactor instantiations */
fun generateDeclarations(): String {
- return reactor.instantiations.joinToString(
+ return reactor.instantiations.filter { !AttributeUtils.isFederate(it) }.joinToString(
prefix = "// reactor instances\n",
separator = "\n"
) { generateDeclaration(it) }
@@ -152,6 +152,6 @@ class CppInstanceGenerator(
/** Generate constructor initializers for all reactor instantiations */
fun generateInitializers(): String =
- reactor.instantiations.mapNotNull { generateInitializer(it) }
+ reactor.instantiations.filter { !AttributeUtils.isFederate(it) }.mapNotNull { generateInitializer(it) }
.joinToString(prefix = "//reactor instances\n", separator = "\n")
}
diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt
index a7d8f0e4ae..3bee50965a 100644
--- a/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt
+++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppReactorGenerator.kt
@@ -23,6 +23,7 @@
***************/
package org.lflang.generator.cpp
+
import org.lflang.MessageReporter
import org.lflang.generator.PrependOperator
import org.lflang.isGeneric
@@ -33,6 +34,7 @@ import org.lflang.toUnixString
/**
* A C++ code generator that produces a C++ class representing a single reactor
*/
+
class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfig, messageReporter: MessageReporter) {
/** Comment to be inserted at the top of generated files */
diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt
new file mode 100644
index 0000000000..dc0901cd4d
--- /dev/null
+++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt
@@ -0,0 +1,70 @@
+package org.lflang.generator.cpp
+
+import org.lflang.*
+import org.lflang.ast.ASTUtils
+import org.lflang.generator.cpp.CppInstanceGenerator.Companion.isEnclave
+import org.lflang.generator.cpp.CppTypes.getTargetTimeExpr
+import org.lflang.lf.*
+
+
+val Reactor.allCppMessageTypes: Set
+ get() = with (this ){
+ val reactors : MutableList = mutableListOf(this)
+ val types : MutableSet = mutableSetOf()
+ while (reactors.isNotEmpty()) {
+ val r = reactors.removeFirst()
+ types.addAll(r.inputs.map{ROSMsgType(it.inferredType.cppType)})
+ types.addAll(r.outputs.map{ROSMsgType(it.inferredType.cppType)})
+ for (inst in r.instantiations) reactors.add(inst.reactor)
+ }
+ return types
+ }
+
+data class ROSMsgType (private val _cppUserType : String){
+
+ val cppUserType : String
+ get() = _cppUserType
+
+ // Transforms a ROS message type from namespaced CamelCase to snake_case for header file inclusion, e.g., "std_msgs::String" becomes "stdmsgsmsg_string_wrapped.hpp"
+ val wrappedMsgCppInclude : String
+ get() {
+ // std_msgs have an extra "_" which needs to be removed
+ var msgT = cppUserType.replace("::", "")
+ msgT = msgT.replace("_", "")
+ msgT = msgT.replaceFirstChar(Char::lowercase)+ "Wrapped.hpp\""
+ msgT = msgT.map{ if (it.isUpperCase()) "_${it.lowercase()}" else it}.joinToString("")
+ return "#include \"lf_wrapped_msgs/msg/$msgT"
+ }
+
+ // include for the .msg file that wraps the userType
+ val userTypeMsgInclude : String
+ get() {
+ return cppUserType.replace("::", "/").replace("msg/", "")
+ }
+
+
+ val wrappedCppType : String
+ get() {
+ return "lf_wrapped_msgs::msg::" + cppUserType.replace("::", "").replace("_", "").capitalize() + "Wrapped"
+ }
+
+
+ val wrappedMsgFileName : String
+ get() {
+ // ROS message file names must follow this regex: '^[A-Z][A-Za-z0-9]*$'
+ return cppUserType.replace("::", "").replace("_", "").capitalize() + "Wrapped.msg"
+ }
+}
+
+val Connection.isFederateConnection: Boolean
+ get() {
+ for (port in leftPorts + rightPorts) {
+ if (port.container != null && AttributeUtils.isFederate(port.container)) {
+ return true
+ }
+ }
+ return false
+ }
+
+
+
diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt
index 76969269c0..0b210b503d 100644
--- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt
+++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt
@@ -1,6 +1,10 @@
package org.lflang.generator.cpp
+import org.lflang.AttributeUtils
import org.lflang.generator.LFGeneratorContext
+import org.lflang.lf.Input
+import org.lflang.lf.Output
+import org.lflang.reactor
import org.lflang.util.FileUtil
import java.nio.file.Path
@@ -9,20 +13,56 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator
override val srcGenPath: Path = generator.fileConfig.srcGenPath.resolve("src")
private val packagePath: Path = generator.fileConfig.srcGenPath
- private val nodeGenerator = CppRos2NodeGenerator(mainReactor, targetConfig, fileConfig);
- private val packageGenerator = CppRos2PackageGenerator(generator, nodeGenerator.nodeName)
+ private val nodeGenerators : MutableList = mutableListOf()
+ private val packageGenerator = CppRos2PackageGenerator(generator)
+ private val lfMsgsRosPackageName = "lf_msgs_ros"
override fun generatePlatformFiles() {
- FileUtil.writeToFile(
- nodeGenerator.generateHeader(),
- packagePath.resolve("include").resolve("${nodeGenerator.nodeName}.hh"),
- true
- )
- FileUtil.writeToFile(
- nodeGenerator.generateSource(),
- packagePath.resolve("src").resolve("${nodeGenerator.nodeName}.cc"),
- true
- )
+
+ nodeGenerators.add(CppRos2NodeGenerator(mainReactor, targetConfig, fileConfig))
+ val reactorsToSearch : MutableList = mutableListOf(mainReactor)
+ /** Recursively searching for federates */
+ while (reactorsToSearch.isNotEmpty()) {
+ reactorsToSearch[0].instantiations.forEach {
+ reactorsToSearch.add(it.reactor)
+ if (AttributeUtils.isFederate(it)) {
+ nodeGenerators.add(
+ CppRos2NodeGenerator(it.reactor, targetConfig, fileConfig))
+ }
+ }
+ reactorsToSearch.removeFirst()
+ }
+
+ packageGenerator.nodeGenerators = nodeGenerators
+
+ // tag message package
+ val lfMsgsRosDir = "/lib/cpp/$lfMsgsRosPackageName"
+ FileUtil.copyFromClassPath(lfMsgsRosDir, fileConfig.srcGenBasePath, true, false)
+
+ val rosMsgTypes : MutableSet = mutableSetOf()
+ for (nodeGen in nodeGenerators) {
+ rosMsgTypes.addAll(nodeGen.reactor.allCppMessageTypes)
+ }
+ // generate wrapped messages
+ val msgWrapGen = CppRos2MessageWrapperGenerator(rosMsgTypes)
+ for ((messageFileName, messageFileContent) in msgWrapGen.generateMessageFiles()) {
+ FileUtil.writeToFile(messageFileContent, fileConfig.srcGenBasePath.resolve("lf_wrapped_msgs").resolve("msg").resolve(messageFileName))
+ }
+ FileUtil.writeToFile(msgWrapGen.generatePackageCmake(), fileConfig.srcGenBasePath.resolve("lf_wrapped_msgs").resolve("CMakeLists.txt"))
+ FileUtil.writeToFile(msgWrapGen.generatePackageXml(), fileConfig.srcGenBasePath.resolve("lf_wrapped_msgs").resolve("package.xml"))
+
+ for (nodeGen in nodeGenerators) {
+ FileUtil.writeToFile(
+ nodeGen.generateHeader(),
+ packagePath.resolve("include").resolve("${nodeGen.nodeName}.hh"),
+ true
+ )
+ FileUtil.writeToFile(
+ nodeGen.generateSource(),
+ packagePath.resolve("src").resolve("${nodeGen.nodeName}.cc"),
+ true
+ )
+ }
FileUtil.writeToFile(packageGenerator.generatePackageXml(), packagePath.resolve("package.xml"), true)
FileUtil.writeToFile(
@@ -30,6 +70,9 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator
packagePath.resolve("CMakeLists.txt"),
true
)
+ FileUtil.writeToFile(packageGenerator.generateLaunchFile(),
+ packagePath.resolve("launch").resolve("default.launch.py"),
+ true)
val scriptPath = fileConfig.binPath.resolve(fileConfig.name);
FileUtil.writeToFile(packageGenerator.generateBinScript(), scriptPath)
scriptPath.toFile().setExecutable(true);
@@ -51,13 +94,18 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator
"colcon", listOf(
"build",
"--packages-select",
+ lfMsgsRosPackageName,
+ "lf_wrapped_msgs",
fileConfig.name,
packageGenerator.reactorCppName,
"--cmake-args",
"-DLF_REACTOR_CPP_SUFFIX=${packageGenerator.reactorCppSuffix}",
+ "-DLF_SRC_PKG_PATH=${fileConfig.srcPkgPath}"
) + cmakeArgs,
- fileConfig.outPath
+ fileConfig.srcGenBasePath
)
+
+
val returnCode = colconCommand?.run(context.cancelIndicator);
if (returnCode != 0 && !messageReporter.errorsOccurred) {
// If errors occurred but none were reported, then the following message is the best we can do.
diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt
new file mode 100644
index 0000000000..bd9473c8f6
--- /dev/null
+++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt
@@ -0,0 +1,78 @@
+package org.lflang.generator.cpp
+
+import org.lflang.capitalize
+import org.lflang.joinWithLn
+
+class CppRos2MessageWrapperGenerator (private val messageTypesToWrap : Set){
+ val ROSMsgType.fileContent : String
+ get() {
+ return """
+ |lf_msgs_ros/Tag tag
+ |${"$userTypeMsgInclude message"}
+ """.trimMargin()
+ }
+
+ val fileContents : List
+ get() {
+ return messageTypesToWrap.map{it.fileContent}
+ }
+
+ fun generateMessageFiles() : List> {
+ return messageTypesToWrap.map{
+ Pair(
+ it.wrappedMsgFileName,
+ it.fileContent
+ )
+ }
+
+ }
+ fun generatePackageCmake(): String {
+ val S = '$'
+ messageTypesToWrap.forEach{ println(it.cppUserType) }
+ val rosidl_generate_interfaces = if (messageTypesToWrap.isEmpty()) "" else { """
+ |rosidl_generate_interfaces($S{PROJECT_NAME}
+ | ${messageTypesToWrap.joinWithLn{"\"msg/${it.wrappedMsgFileName}\""}}
+ |DEPENDENCIES std_msgs lf_msgs_ros
+ |)"""
+ }
+ return """
+ |cmake_minimum_required(VERSION 3.5)
+ |project(lf_wrapped_msgs)
+ |
+ |find_package(rosidl_default_generators REQUIRED)
+ |find_package(lf_msgs_ros REQUIRED)
+ |find_package(std_msgs REQUIRED)
+ |
+ |
+ |$rosidl_generate_interfaces
+ |
+ |ament_package()
+ """.trimMargin()
+
+ }
+
+ fun generatePackageXml(): String {
+ return """
+ |
+ |
+ |
+ | lf_wrapped_msgs
+ | 0.0.0
+ | Generated message wrappers including original message type and a LF-tag
+ | Todo
+ | Todo
+ |
+ | rosidl_default_generators
+ | std_msgs
+ | lf_msgs_ros
+ | rosidl_default_runtime
+ |
+ | rosidl_interface_packages
+ |
+ | ament_cmake
+ |
+ |
+ |
+ """.trimMargin()
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt
index 69629f06db..3cb15c2728 100644
--- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt
+++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt
@@ -1,5 +1,7 @@
package org.lflang.generator.cpp
+import org.lflang.*
+import org.lflang.lf.*
import org.lflang.target.TargetConfig
import org.lflang.lf.Reactor
import org.lflang.target.property.FastProperty
@@ -7,33 +9,231 @@ import org.lflang.target.property.TimeOutProperty
import org.lflang.target.property.WorkersProperty
import org.lflang.toUnixString
-/** A C++ code generator for creating a ROS2 node from a main reactor definition */
+/** A C++ code generator for creating a ROS2 node from reactor definition */
class CppRos2NodeGenerator(
- private val main: Reactor,
+ public val reactor: Reactor,
private val targetConfig: TargetConfig,
private val fileConfig: CppFileConfig
) {
- val nodeName = "${fileConfig.name}Node"
+ val nodeName = "${reactor.name}Node"
+
+ private var subReactorEndpointDeclarations : String = ""
+ private var subReactorEndpointInitializers : String = ""
+
+ // generating endPoint declarations and initializers for non-federate-childs of this federate
+ private fun generateSubReactorCode() {
+ // recursively searching through all reactors of this federate to add the corresponding endpoints if they are in a federate connection
+ // maybe TODO: resolve multiports banks etc
+ val todo: MutableList>> = mutableListOf(Triple("", reactor, listOf()))
+ while (todo.isNotEmpty()) {
+ val (prefix, r, preInst) = todo.removeFirst()
+
+ for (inst in r.instantiations) {
+ if (!AttributeUtils.isFederate(inst)) todo.add(Triple(prefix + "/" + inst.name, inst.reactor, preInst + inst))
+ }
+
+ for (con in r.connections)
+ processSubReactorCon(prefix, con, preInst)
+ }
+ }
+
+ private fun createPubSubUniquePtrDecl(isPub: Boolean, isPhysical: Boolean, userType: String, wrappedType: String, varName: String): String {
+ var decl : String = "std::unique_ptr> $varName;"
+ return decl
+ }
+
+ private fun createPubSubUniquePtrInit(isPub: Boolean, isPhysical: Boolean, delay: Expression?, userType: String, wrappedType: String, varName: String, topicName: String): String {
+ var init : String = "$varName = std::make_unique>(lf_federate_prefix + \"$topicName\""
+ if (isPub)
+ init += ");"
+ else {
+ init += ", \"${varName}_sub\", lf_env.get()"
+ if (delay != null) init += ", " + delay.toCppTime()
+ init += ");"
+ }
+ return init
+ }
+
+ private fun createEndPointDeclInit(isPub: Boolean, isPhysical: Boolean, delay: Expression?, userType: String, wrappedType: String, varName: String, topicName: String, debugMsg: Boolean = true) {
+ var newVarName : String = varName
+ if (isPhysical) newVarName += "_physical"
+ subReactorEndpointDeclarations += System.lineSeparator() +
+ "| ${createPubSubUniquePtrDecl(isPub, isPhysical,
+ userType,
+ wrappedType,
+ newVarName)}"
+
+ subReactorEndpointInitializers += System.lineSeparator() +
+ "| ${createPubSubUniquePtrInit(isPub,
+ isPhysical,
+ delay, userType,
+ wrappedType,
+ newVarName,
+ topicName)}"
+
+ if (debugMsg)
+ subReactorEndpointInitializers += System.lineSeparator() +
+ "| RCLCPP_DEBUG_STREAM(this->get_logger(), \"subreactor endpoint $newVarName got topic \"+ lf_federate_prefix + \"/$topicName\");"
+ }
+
+ private fun createEndpointPortBindingCode(isPub: Boolean, isPhysical: Boolean, varName: String, preInst: List, container: Instantiation, portName: String) {
+ var newVarName : String = varName
+ if (isPhysical) newVarName += "_physical"
+ subReactorEndpointInitializers += """
+ | reactor::Reactor* ${newVarName}_reactor = lf_reactor.get();
+ | bool ${newVarName}_subreactor_found;
+ | ${(preInst + container).joinToString(separator = System.lineSeparator()) {
+ """
+ | ${newVarName}_subreactor_found = false;
+ | for(auto r : ${newVarName}_reactor->reactors())
+ | if (r->name() == "${it.name}") {
+ | ${newVarName}_subreactor_found = true;
+ | ${newVarName}_reactor = r;
+ | }
+ | if (!${newVarName}_subreactor_found)
+ | RCLCPP_ERROR(this->get_logger(), "Failed to find subreactor \"${it.name}\"");
+ """
+ }
+ }
+ | $newVarName->${if (isPub) "set_port" else "add_port"}(&dynamic_cast<${container.reactor.name}*>(${newVarName}_reactor)->$portName);
+ """
+ }
+
+
+
+ private fun processSubReactorCon(prefix: String, con: Connection, preInst: List) {
+ for ((l, r) in con.leftPorts.zip(con.rightPorts)){
+ if (l.container != null && !AttributeUtils.isFederate(l.container)
+ && r.container != null && AttributeUtils.isFederate(r.container)) {
+ val lPort = l.variable as Output
+ val lPortVarName = prefix + l.container.name + "_" + lPort.name
+ val topicName = prefix + l.container.name + "/" + lPort.name
+
+ createEndPointDeclInit(true, con.isPhysical, null,
+ lPort.inferredType.cppType,
+ ROSMsgType(lPort.inferredType.cppType).wrappedCppType,
+ lPortVarName,
+ "/$topicName")
+
+ createEndpointPortBindingCode(true, con.isPhysical, lPortVarName, preInst, l.container, lPort.name)
+ }
+ if (r.container != null && !AttributeUtils.isFederate(r.container)
+ && l.container != null && AttributeUtils.isFederate(l.container)) {
+ val rPort = r.variable as Input
+ val rPortVarName = prefix + r.container.name + "_" + rPort.name
+ var topicName= prefix + l.container.name + "/" + (l.variable as Port).name
+ // if the container is a federate where the out port is remapped check until we find one that should not be remapped
+ if (AttributeUtils.isFederate(l.container)) {
+ topicName = prefix
+ var prev_l = l
+ while(true) {
+ topicName += prev_l.container.name
+ var next_l : VarRef? = null;
+ for (fed_con in prev_l.container.reactor.connections) {
+ for ((fed_con_index, fed_con_rPort) in fed_con.rightPorts.withIndex()) {
+ if (fed_con_rPort.name == (prev_l.variable as Port).name && fed_con_rPort.container == null)
+ next_l = fed_con.leftPorts[fed_con_index]
+ }
+ }
+ if (next_l == null) {
+ topicName += "/" +(prev_l.variable as Port).name
+ break
+ }
+ else {
+ topicName += "/"
+ prev_l = next_l
+ next_l = null
+ }
+ }
+ }
+
+ createEndPointDeclInit(false, con.isPhysical, con.delay,
+ rPort.inferredType.cppType,
+ ROSMsgType(rPort.inferredType.cppType).wrappedCppType,
+ rPortVarName,
+ "/$topicName")
+
+ createEndpointPortBindingCode(false, con.isPhysical, rPortVarName, preInst, r.container, rPort.name)
+ }
+
+ }
+ }
+ init {
+ generateSubReactorCode()
+ }
+
+
+ private fun generateEndpointDeclarations(): String {
+ // for now we dont care if the outputs/inputs of this federate are used in physical or non-physical connections
+ // hence both are generated and initialized based on parameters of launch script
+ // maybe TODO: one could search for any connections of this federate starting from main reactor
+ // to figure out if and how this federates' ports are used and then generate only what is needed
+ // note: for banks/multiports would somehow need to know what exact instance this is (maybe via launch params)
+ return """
+ | // toplevel federates' input and output endpoints
+ | ${reactor.inputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()) {
+ """
+ | std::unique_ptr> ${it.name};
+ | std::unique_ptr> ${it.name}_physical;
+ """
+ }
+ }
+ | ${reactor.outputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()) {
+ """
+ | std::unique_ptr> ${it.name};
+ | std::unique_ptr> ${it.name}_physical;
+ """
+ }
+ }
+ |
+ | // endPoint declarations for this federates' subreactors
+ $subReactorEndpointDeclarations
+ """
+ }
+
fun generateHeader(): String {
return """
|#pragma once
|
|#include
+ |#include "${reactor.name}.hh"
|#include "reactor-cpp/reactor-cpp.hh"
+ |#include
+ |#include "reactor-cpp/ros2_connection_endpoint.hh"
+ |
+ |${reactor.allCppMessageTypes.map { it.wrappedMsgCppInclude }.joinLn() }
|
- |#include "${fileConfig.getReactorHeaderPath(main).toUnixString()}"
+ |#include "${fileConfig.getReactorHeaderPath(reactor).toUnixString()}"
|
|rclcpp::Node* lf_node{nullptr};
|
|class $nodeName : public rclcpp::Node {
|private:
| std::unique_ptr lf_env;
- | std::unique_ptr<${main.name}> lf_main_reactor;
- |
- | // main thread of the LF execution
- | std::thread lf_main_thread;
+ | std::unique_ptr<${reactor.name}> lf_reactor;
+ | std::string lf_federate_prefix;
+ | static const std::string LF_FEDERATE_PREFIX_PARAM_NAME;
+ | ${
+ if (reactor.isMain) """
+ | std::shared_ptr> start_time_publisher;
+ """ else """
+ | std::shared_ptr> start_time_subscription;
+ """
+ }
+ |
+ | ${generateEndpointDeclarations()}
+ |
+ | // thread of the LF execution
+ | std::thread lf_thread;
| // an additional thread that we use for waiting for LF termination
| // and then shutting down the LF node
| std::thread lf_shutdown_thread;
@@ -47,18 +247,26 @@ class CppRos2NodeGenerator(
}
fun generateSource(): String {
+ // maybe TODO: naming a reactor "Node" leads to naming collisions with the rclcpp::Node
+ // for now just dont do that
+
return """
|#include "$nodeName.hh"
+ |#include "${reactor.name}.hh"
|#include
|
|#include
|
|void $nodeName::wait_for_lf_shutdown() {
- | lf_main_thread.join();
+ | RCLCPP_DEBUG_STREAM(this->get_logger(), "$nodeName waiting before shutting down");
+ | lf_thread.join();
+ | RCLCPP_DEBUG_STREAM(this->get_logger(), "$nodeName shutting down");
| this->get_node_options().context()->shutdown("LF execution terminated");
|}
|
- |$nodeName::$nodeName(const rclcpp::NodeOptions& node_options)
+ |const std::string $nodeName::LF_FEDERATE_PREFIX_PARAM_NAME = "lf_federate_prefix";
+ |
+ |$nodeName::$nodeName(const rclcpp::NodeOptions& node_options = rclcpp::NodeOptions())
| : Node("$nodeName", node_options) {
| unsigned workers = ${if (targetConfig.get(WorkersProperty.INSTANCE) != 0) targetConfig.get(WorkersProperty.INSTANCE) else "std::thread::hardware_concurrency()"};
| bool fast{${targetConfig.get(FastProperty.INSTANCE)}};
@@ -71,14 +279,95 @@ class CppRos2NodeGenerator(
| lf_env = std::make_unique(workers, fast, lf_timeout);
|
| // instantiate the main reactor
- | lf_main_reactor = std::make_unique<${main.name}> ("${main.name}", lf_env.get(), ${main.name}::Parameters{});
+ | lf_reactor = std::make_unique<${reactor.name}> ("${reactor.name}", lf_env.get(), ${reactor.name}::Parameters{});
+ |
+ | this->declare_parameter(LF_FEDERATE_PREFIX_PARAM_NAME);
+ | rclcpp::Parameter lf_federate_prefix_param;
+ | if (this->get_parameter(LF_FEDERATE_PREFIX_PARAM_NAME, lf_federate_prefix_param))
+ | lf_federate_prefix = lf_federate_prefix_param.as_string();
+ | else RCLCPP_WARN_STREAM(this->get_logger(), "parameter \"" + LF_FEDERATE_PREFIX_PARAM_NAME + "\" missing");
+ |
|
+ | ${reactor.inputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()) {
+ """
+ | this->declare_parameter("${it.name}");
+ | rclcpp::Parameter ${it.name}_topic_name_param;
+ | if (this->get_parameter("${it.name}", ${it.name}_topic_name_param)) {
+ | std::string ${it.name}_topic_name_string = ${it.name}_topic_name_param.as_string();
+ | rclcpp::Parameter ${it.name}_delay;
+ | this->declare_parameter("${it.name}_delay");
+ | if (this->get_parameter("${it.name}_delay", ${it.name}_delay))
+ | ${it.name} = std::make_unique>(${it.name}_topic_name_string,"${it.name}_sub", lf_env.get(), std::chrono::nanoseconds(${it.name}_delay.as_int()));
+ | else ${it.name} = std::make_unique>(${it.name}_topic_name_string,"${it.name}_sub", lf_env.get());
+ | ${it.name}->add_port(&lf_reactor->${it.name});
+ | }
+ |
+ | this->declare_parameter("${it.name}_physical");
+ | rclcpp::Parameter ${it.name}_physical_topic_name_param;
+ | if (this->get_parameter("${it.name}_physical", ${it.name}_physical_topic_name_param)) {
+ | std::string ${it.name}_physical_topic_name_string = ${it.name}_physical_topic_name_param.as_string();
+ | rclcpp::Parameter ${it.name}_delay;
+ | this->declare_parameter("${it.name}_physical_delay");
+ | if (this->get_parameter("${it.name}_physical_delay", ${it.name}_delay))
+ | ${it.name}_physical = std::make_unique>(${it.name}_physical_topic_name_string,"${it.name}_physical_sub", lf_env.get(), std::chrono::nanoseconds(${it.name}_delay.as_int()));
+ | else ${it.name}_physical = std::make_unique>(${it.name}_physical_topic_name_string,"${it.name}_physical_sub", lf_env.get());
+ | ${it.name}_physical->add_port(&lf_reactor->${it.name});
+ | }
+ """
+ }
+ }
+ | ${reactor.outputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()) {
+ """
+ | this->declare_parameter("${it.name}");
+ | rclcpp::Parameter ${it.name}_topic_name_param;
+ | if (this->get_parameter("${it.name}", ${it.name}_topic_name_param)) {
+ | std::string ${it.name}_topic_name_string = ${it.name}_topic_name_param.as_string();
+ | ${it.name} = std::make_unique>(${it.name}_topic_name_string);
+ | ${it.name}->set_port(&lf_reactor->${it.name});
+ | }
+ |
+ | this->declare_parameter("${it.name}_physical");
+ | rclcpp::Parameter ${it.name}_physical_topic_name_param;
+ | if (this->get_parameter("${it.name}_physical", ${it.name}_physical_topic_name_param)) {
+ | std::string ${it.name}_physical_topic_name_string = ${it.name}_physical_topic_name_param.as_string();
+ | ${it.name}_physical = std::make_unique>(${it.name}_physical_topic_name_string);
+ | ${it.name}_physical->set_port(&lf_reactor->${it.name});
+ | }
+ """
+ }
+ }
+ $subReactorEndpointInitializers
| // assemble reactor program
| lf_env->assemble();
- |
- | // start execution
- | lf_main_thread = lf_env->startup();
- | lf_shutdown_thread = std::thread([this] { wait_for_lf_shutdown(); });
+ |
+ | const std::string LF_STARTUP_TIME_TOPIC = "__lf_startup_time__";
+ | ${ if (reactor.isMain) """
+ | reactor::TimePoint start_time = reactor::get_physical_time();
+ | start_time_publisher = this->create_publisher(LF_STARTUP_TIME_TOPIC, rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local());
+ | {
+ | std_msgs::msg::Int64 time_point_message;
+ | time_point_message.data = start_time.time_since_epoch().count();
+ | start_time_publisher->publish(time_point_message);
+ | }
+ |
+ | // start execution
+ | lf_thread = lf_env->startup(start_time);
+ | lf_shutdown_thread = std::thread([this] { wait_for_lf_shutdown(); });
+ """ else """
+ | start_time_subscription = this->create_subscription(LF_STARTUP_TIME_TOPIC, rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(),
+ | [&](const std_msgs::msg::Int64::SharedPtr msg) {
+ | reactor::TimePoint start_time(std::chrono::nanoseconds(msg->data));
+ | // start execution
+ | lf_thread = lf_env->startup(start_time);
+ | lf_shutdown_thread = std::thread([this] { wait_for_lf_shutdown(); });
+ | start_time_subscription.reset();
+ | });
+ """ }
+ |
|}
|
|$nodeName::~$nodeName() {
@@ -88,7 +377,13 @@ class CppRos2NodeGenerator(
| lf_shutdown_thread.join();
|}
|
- |RCLCPP_COMPONENTS_REGISTER_NODE($nodeName)
+ |int main(int argc, char **argv) {
+ | rclcpp::init(argc, argv);
+ | auto node = std::make_shared<$nodeName>();
+ | rclcpp::spin(node);
+ | rclcpp::shutdown();
+ |}
+ |
""".trimMargin()
}
}
diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt
index ea7568e2c4..71a9ab8e1e 100644
--- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt
+++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt
@@ -1,7 +1,13 @@
package org.lflang.generator.cpp
+import org.lflang.AttributeUtils
+import org.lflang.ast.ASTUtils
import org.lflang.generator.PrependOperator
+import org.lflang.joinLn
import org.lflang.joinWithLn
+import org.lflang.lf.Input
+import org.lflang.lf.Reactor
+import org.lflang.reactor
import org.lflang.target.property.BuildTypeProperty
import org.lflang.target.property.CmakeIncludeProperty
import org.lflang.target.property.Ros2DependenciesProperty
@@ -10,7 +16,7 @@ import org.lflang.toUnixString
import java.nio.file.Path
/** A C++ code generator for creating the required files for defining a ROS2 package. */
-class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: String) {
+class CppRos2PackageGenerator(generator: CppGenerator) {
private val fileConfig = generator.fileConfig
private val targetConfig = generator.targetConfig
val reactorCppSuffix: String = if (targetConfig.isSet(RuntimeVersionProperty.INSTANCE)) targetConfig.get(RuntimeVersionProperty.INSTANCE) else "default"
@@ -22,6 +28,8 @@ class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: Str
@Suppress("PrivatePropertyName") // allows us to use capital S as variable name below
private val S = '$' // a little trick to escape the dollar sign with $S
+ var nodeGenerators = emptyList()
+
fun generatePackageXml(): String {
return """
|
@@ -32,12 +40,13 @@ class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: Str
| Autogenerated from ${fileConfig.srcFile}
| Todo
| Todo
- |
+ |
| ament_cmake
| ament_cmake_auto
|
${" |"..dependencies.joinWithLn { "$it" }}
- |
+ | lf_msgs_ros
+ | lf_wrapped_msgs
| ament_lint_auto
| ament_lint_common
|
@@ -72,40 +81,284 @@ class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: Str
|
|# Invoke find_package() for all build and buildtool dependencies.
|find_package(ament_cmake_auto REQUIRED)
+ |find_package(lf_msgs_ros REQUIRED)
+ |find_package(lf_wrapped_msgs REQUIRED)
|ament_auto_find_build_dependencies()
+ |include_directories(
+ | include
+ | src/${fileConfig.srcGenBasePath.relativize(fileConfig.srcGenPkgPath).toUnixString()}
+ | "$S{LF_SRC_PKG_PATH}/src"
+ | "$S{PROJECT_SOURCE_DIR}/include/"
+ | "$S{PROJECT_SOURCE_DIR}/src/"
+ | "$S{PROJECT_SOURCE_DIR}/src/__include__"
+ | )
+ ${ nodeGenerators.map {
+ """
+ |add_executable(${it.nodeName}
+ | src/${it.nodeName}.cc
+ ${" | src/"..sources.joinWithLn { it.toUnixString() }}
+ |)
+ |ament_target_dependencies(${it.nodeName} ${dependencies.joinToString(" ")} lf_msgs_ros lf_wrapped_msgs)
+ |target_link_libraries(${it.nodeName} $reactorCppName)
+ |
+ |install(
+ | TARGETS ${it.nodeName}
+ | DESTINATION lib/$S{PROJECT_NAME}
+ | )
+ |install(DIRECTORY launch
+ | DESTINATION share/$S{PROJECT_NAME})
+ |
+ |if(MSVC)
+ | target_compile_options(${it.nodeName} PRIVATE /W4)
+ |else()
+ | target_compile_options(${it.nodeName} PRIVATE -Wall -Wextra -pedantic)
+ |endif()
+ """
+ }.joinLn()}
|
- |set(LF_MAIN_TARGET ${fileConfig.name})
|
- |ament_auto_add_library($S{LF_MAIN_TARGET} SHARED
- | src/$nodeName.cc
- ${" | "..sources.joinWithLn { "src/$it" }}
- |)
- |ament_target_dependencies($S{LF_MAIN_TARGET} ${dependencies.joinToString(" ")})
- |target_include_directories($S{LF_MAIN_TARGET} PUBLIC
- | "$S{LF_SRC_PKG_PATH}/src"
- | "$S{PROJECT_SOURCE_DIR}/src/"
- | "$S{PROJECT_SOURCE_DIR}/src/__include__"
- |)
- |target_link_libraries($S{LF_MAIN_TARGET} $reactorCppName)
- |
- |rclcpp_components_register_node($S{LF_MAIN_TARGET}
- | PLUGIN "$nodeName"
- | EXECUTABLE $S{LF_MAIN_TARGET}_exe
- |)
- |
- |if(MSVC)
- | target_compile_options($S{LF_MAIN_TARGET} PRIVATE /W4)
- |else()
- | target_compile_options($S{LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic)
- |endif()
|
- |ament_auto_package()
+ |ament_package()
|
${" |"..(includeFiles?.joinWithLn { "include(\"$it\")" } ?: "")}
""".trimMargin()
}
}
}
+ // Generates a Python code string that represents the structure of a given Reactor object, detailing its instantiations and connections
+ private fun createReactorStructurePython(reactor : Reactor, nodeName : String = "") : String {
+ var s = "Reactor(\"$nodeName\""
+ s += "," + System.lineSeparator()
+ s+= "["
+ var isFirst : Boolean = true
+ for (inst in reactor.instantiations) {
+ if (isFirst) isFirst = false
+ else s+=", "
+ s+="Instantiation(\"${inst.name}\", "
+ if (AttributeUtils.isFederate(inst)) {
+ val instGen = nodeGenerators.filter{ it.reactor == inst.reactor}.first()
+ s+="\"${instGen.nodeName}\", ${createReactorStructurePython(inst.reactor, instGen.nodeName)}"
+ } else {
+ s+="None, ${createReactorStructurePython(inst.reactor)}"
+ }
+ s+= ")"
+ }
+ s+= "]," + System.lineSeparator()
+ s+= "["
+ isFirst = true
+ for (con in reactor.connections) {
+ if (isFirst) isFirst = false
+ else s+=", "
+ s+="Connection("
+ s+= "["
+ isFirst = true
+ for (leftP in con.leftPorts) {
+ if (isFirst) isFirst = false
+ else s+=", "
+
+ s+= "Port(${if (leftP.container != null) "\"${leftP.container.name}\"" else "None" }" +
+ ", \"${leftP.variable.name}\", ${if (leftP.variable is Input) "True" else "False"})"
+ }
+ s+= "], "
+ s+= "["
+ isFirst = true
+ for (rightP in con.rightPorts) {
+ if (isFirst) isFirst = false
+ else s+=", "
+ s+= "Port(${if (rightP.container != null) "\"${rightP.container.name}\"" else "None" }, " +
+ "\"${rightP.variable.name}\", ${if (rightP.variable is Input) "True" else "False"})"
+ }
+ s+= "], "
+ s+= if (con.isPhysical) "True" else "False"
+ s+= ", "
+ s+= if (con.delay != null) ASTUtils.getLiteralTimeValue(con.delay).toNanoSeconds() else "None"
+ s+=")"
+ }
+ s+= "]"
+ s+= ")" + System.lineSeparator()
+ return s
+ }
+
+ fun generateLaunchFile(): String {
+ val mainReactorNodeGen = nodeGenerators.filter{ it.reactor.isMain}.first()
+ val reactorStructurePython = createReactorStructurePython(mainReactorNodeGen.reactor)
+ return """
+ |from __future__ import annotations
+ |from typing import List, Tuple, Dict, Optional, Deque
+ |from dataclasses import dataclass
+ |from collections import deque
+ |from launch import LaunchContext, LaunchDescription
+ |from launch_ros.actions import Node
+ |from launch.actions import OpaqueFunction, DeclareLaunchArgument
+ |from launch.substitutions import LocalSubstitution, LaunchConfiguration
+ |from pprint import pprint as prettyprint
+ |
+ |# if a node fails (exit code != 0) the launch file is exited with the same exit code
+ |# this got implemented to allow LF tests with ROS2 launch files
+ |def relay_node_fail_to_launch(context: LaunchContext):
+ | assert(isinstance(context, LaunchContext))
+ | # event is instance of launch.events.process.ProcessExited
+ | return_code = LocalSubstitution("event.returncode").perform(context)
+ | assert(isinstance(return_code, int))
+ | if return_code != 0:
+ | exit(return_code)
+ |
+ |class Port:
+ | instance_name: str
+ | name: str
+ | is_input: bool
+ |
+ | def __init__(self, _inst_name, _port_name, _is_input):
+ | self.instance_name = _inst_name
+ | self.name = _port_name
+ | self.is_input = _is_input
+ |
+ |class Connection:
+ | leftPorts: List[Port]
+ | rightPorts: List[Port]
+ | physical: bool
+ | delay: Optional[int]
+ |
+ | def __init__(self, lPorts, rPorts, isPhysical, _delay):
+ | self.leftPorts = lPorts
+ | self.rightPorts = rPorts
+ | self.physical = isPhysical
+ | self.delay = _delay
+ |
+ |class Instantiation:
+ | name: str
+ | executable: str
+ | reactor: Reactor
+ |
+ | def __init__(self, _name, _executable, _reactor):
+ | self.name = _name
+ | self.executable = _executable
+ | self.reactor = _reactor
+ |
+ |class Reactor:
+ | classname: str
+ | instantiations: List[Instantiation]
+ | connections: List[Connection]
+ |
+ | def __init__(self, _classname, _instantiations, _connections):
+ | self.classname = _classname
+ | self.instantiations = _instantiations
+ | self.connections = _connections
+ |
+ |mainInstance : Reactor = Instantiation("${mainReactorNodeGen.reactor.name}", "${mainReactorNodeGen.nodeName}",
+ |$reactorStructurePython
+ |)
+ |
+ |NODE_SELECTION_LIST_ARG_NAME = "node_selection_list"
+ |ALL_NODES_INDICATOR = "__all__" # Special value to indicate all nodes should be launched
+ |RELAY_FAIL_ARG_NAME = "relay_node_fail_to_launch"
+ |
+ |
+ |def generate_launch_description():
+ | # Declare launch argument for node list
+ | node_selection_list_arg = DeclareLaunchArgument(
+ | NODE_SELECTION_LIST_ARG_NAME, default_value=ALL_NODES_INDICATOR,
+ | description='Comma-separated list of nodes to launch based on their lf federate prefix (if not specified, all nodes will be launched)'
+ | )
+ |
+ | # Declare launch argument for exit on node fail
+ | relay_fail_arg = DeclareLaunchArgument(
+ | RELAY_FAIL_ARG_NAME, default_value="True",
+ | description='If true, when a node exits with an exit code other than 0 the launch will exit with the same code.'
+ | )
+ |
+ | # this dict accumulates parameters for each node based on the connections (of the containing node)
+ | prefix_param_dict : Dict[str, Dict[str,str]] = {}
+ | # str is container prefix, list is connections from container regarding this instance
+ | # instances are processed in a breath-first manner
+ | # (to make sure that the respective parent has already been processed for each instance)
+ | instances_todo : Deque[Tuple[str, Instantiation, List[Connection]]] = deque([("", mainInstance, [])])
+ |
+ | while instances_todo:
+ | prefix, instance, connections = instances_todo.popleft()
+ | lf_federate_prefix = prefix + "/" + instance.name
+ |
+ | # add child instances to queue for later processing
+ | for next_inst in instance.reactor.instantiations:
+ | # filter connections that concern the child instance
+ | next_conns = [con for con in instance.reactor.connections if
+ | any(p.instance_name == next_inst.name for p in con.leftPorts + con.rightPorts)]
+ | instances_todo.append((lf_federate_prefix, next_inst, next_conns))
+ |
+ | # create node parameters
+ | generate_instance_parameters(lf_federate_prefix, prefix, instance, connections, prefix_param_dict)
+ |
+ | return LaunchDescription([node_selection_list_arg, relay_fail_arg,
+ | OpaqueFunction(function=create_node_launch_descriptions, args=[prefix_param_dict])])
+ |
+ |def create_node_launch_descriptions(context, prefix_param_dict) -> List[Node]:
+ | # launch nodes if they are federate (have an executable) with parameters from prefix_param_dict
+ | to_search_feds : Deque[Tuple[str,Instantiation]] = deque([("", mainInstance)])
+ | node_selection_list = LaunchConfiguration(NODE_SELECTION_LIST_ARG_NAME).perform(context)
+ | relay_node_fail = LaunchConfiguration(RELAY_FAIL_ARG_NAME).perform(context)
+ | # dict is use to pass parameter conditionally
+ | relay_node_fail_dict = dict(on_exit=OpaqueFunction(function=relay_node_fail_to_launch))
+ | if relay_node_fail.lower() in ['0', 'false']:
+ | relay_node_fail_dict = {}
+ | launch_all_nodes = False
+ | if node_selection_list == ALL_NODES_INDICATOR:
+ | launch_all_nodes = True
+ | else:
+ | node_selection_list = node_selection_list.split(",")
+ | nodes = []
+ | while to_search_feds:
+ | [prefix, instance] = to_search_feds.popleft()
+ | fed_prefix = prefix + "/" + instance.name
+ | for inst in instance.reactor.instantiations:
+ | to_search_feds.append((fed_prefix, inst))
+ | if instance.executable != None:
+ | if launch_all_nodes or fed_prefix in node_selection_list:
+ | nodes.append(Node(package='${fileConfig.name}',
+ | executable=instance.executable,
+ | name=fed_prefix.replace("/","_"),
+ | parameters=[prefix_param_dict[fed_prefix]],
+ | **relay_node_fail_dict
+ | )
+ | )
+ | print("launching node with prefix " + fed_prefix)
+ | prettyprint(prefix_param_dict[fed_prefix])
+ | return nodes
+ |
+ |def generate_instance_parameters(lf_federate_prefix: str, prefix: str, instance: Instantiation, connections: List[Connection], prefix_param_dict):
+ | param_dict = {"lf_federate_prefix": lf_federate_prefix}
+ | for con in connections:
+ | update_param_dict_for_con(con, instance, lf_federate_prefix, prefix, param_dict, prefix_param_dict)
+ | prefix_param_dict[lf_federate_prefix] = param_dict
+ |
+ |
+ |def update_param_dict_for_con(con, instance, lf_federate_prefix, prefix, instance_param_dict, prefix_param_dict):
+ | # con is from parent concerning the instance
+ | physical_string = "_physical" if con.physical else ""
+ | for leftP, rightP in zip(con.leftPorts, con.rightPorts):
+ | if leftP.instance_name == instance.name and not leftP.is_input:
+ | instance_param_dict[leftP.name + physical_string] = lf_federate_prefix + "/" + leftP.name
+ | if rightP.instance_name == instance.name and leftP.is_input and leftP.instance_name == None:
+ | if leftP.name in prefix_param_dict[prefix]:
+ | instance_param_dict[rightP.name] = prefix_param_dict[prefix][leftP.name]
+ | if leftP.name + "_physical" in prefix_param_dict[prefix]:
+ | instance_param_dict[rightP.name+"_physical"] = prefix_param_dict[prefix][leftP.name+"_physical"]
+ | if rightP.instance_name == instance.name and rightP.is_input and leftP.instance_name != None:
+ | instance_param_dict[rightP.name + physical_string] = prefix +"/"+ leftP.instance_name + "/" + leftP.name
+ | if con.delay is not None:
+ | instance_param_dict[rightP.name + physical_string + "_delay"] = con.delay
+ | if leftP.instance_name == instance.name and rightP.instance_name == None and not rightP.is_input:
+ | # connection like r{x.port -> out} while current instance is x
+ | # we go through the reactors to see if parent.out is used to replace it with x.port
+ | name_to_search_for = prefix + "/" + rightP.name
+ | print(name_to_search_for)
+ | for key in prefix_param_dict:
+ | for key2 in prefix_param_dict[key]:
+ | if prefix_param_dict[key][key2] == name_to_search_for:
+ | prefix_param_dict[key][key2] = lf_federate_prefix + "/" + leftP.name
+ |
+ """.trimMargin()
+
+ }
fun generateBinScript(): String {
val relPath = fileConfig.binPath.relativize(fileConfig.outPath).toUnixString()
@@ -113,8 +366,8 @@ class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: Str
return """
|#!/bin/bash
|script_dir="$S(dirname -- "$S(readlink -f -- "${S}0")")"
- |source "$S{script_dir}/$relPath/install/setup.sh"
- |ros2 run ${fileConfig.name} ${fileConfig.name}_exe
+ |source "$S{script_dir}/$relPath/src-gen/install/setup.sh"
+ |ros2 launch ${fileConfig.name} default.launch.py
""".trimMargin()
}
}
diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c
index b19fff3339..1d710c6b1c 160000
--- a/core/src/main/resources/lib/c/reactor-c
+++ b/core/src/main/resources/lib/c/reactor-c
@@ -1 +1 @@
-Subproject commit b19fff33391b19a01fa45c37d425d76f9f6ea21b
+Subproject commit 1d710c6b1ca8b3f3ef8cebb4e102152d6592cf06
diff --git a/core/src/main/resources/lib/cpp/lf_msgs_ros/CMakeLists.txt b/core/src/main/resources/lib/cpp/lf_msgs_ros/CMakeLists.txt
new file mode 100644
index 0000000000..4159095e44
--- /dev/null
+++ b/core/src/main/resources/lib/cpp/lf_msgs_ros/CMakeLists.txt
@@ -0,0 +1,12 @@
+cmake_minimum_required(VERSION 3.5)
+project(lf_msgs_ros)
+
+find_package(rosidl_default_generators REQUIRED)
+
+rosidl_generate_interfaces(${PROJECT_NAME}
+ "msg/Tag.msg"
+ )
+
+ament_package()
+
+
diff --git a/core/src/main/resources/lib/cpp/lf_msgs_ros/msg/Tag.msg b/core/src/main/resources/lib/cpp/lf_msgs_ros/msg/Tag.msg
new file mode 100644
index 0000000000..50e3f12146
--- /dev/null
+++ b/core/src/main/resources/lib/cpp/lf_msgs_ros/msg/Tag.msg
@@ -0,0 +1,2 @@
+int64 time_point
+uint64 microstep
\ No newline at end of file
diff --git a/core/src/main/resources/lib/cpp/lf_msgs_ros/package.xml b/core/src/main/resources/lib/cpp/lf_msgs_ros/package.xml
new file mode 100644
index 0000000000..dfe2bb15ad
--- /dev/null
+++ b/core/src/main/resources/lib/cpp/lf_msgs_ros/package.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ lf_msgs_ros
+ 0.0.0
+ ROS messages for lingua franca
+ user
+ TODO: License declaration
+ ament_cmake
+ ament_lint_auto
+ ament_lint_common
+ rosidl_default_generators
+ rosidl_default_runtime
+ rosidl_interface_packages
+
+ ament_cmake
+
+
+
diff --git a/core/src/main/resources/lib/cpp/reactor-cpp b/core/src/main/resources/lib/cpp/reactor-cpp
index 3b25ffe3e2..56caade05c 160000
--- a/core/src/main/resources/lib/cpp/reactor-cpp
+++ b/core/src/main/resources/lib/cpp/reactor-cpp
@@ -1 +1 @@
-Subproject commit 3b25ffe3e270ab10181552a7e08ba37a471d8868
+Subproject commit 56caade05c56f47b432a9f463b28cd334e4981a5
diff --git a/test/Cpp/src/ros-federated/FederateCycle.lf b/test/Cpp/src/ros-federated/FederateCycle.lf
new file mode 100644
index 0000000000..ad9b4c3874
--- /dev/null
+++ b/test/Cpp/src/ros-federated/FederateCycle.lf
@@ -0,0 +1,76 @@
+target Cpp {
+ ros2: true,
+ timeout: 5s,
+ ros2-dependencies: ["std_msgs"],
+}
+
+public preamble {=
+ #include "rclcpp/rclcpp.hpp"
+ #include "std_msgs/msg/empty.hpp"
+=}
+
+reactor Ping {
+ timer t(0, 100 ms)
+ input in: std_msgs::msg::Empty
+ output out: std_msgs::msg::Empty
+ state counter: int = 0
+ state received: bool = false
+
+ reaction(t) -> out {=
+ std_msgs::msg::Empty msg;
+ out.set(msg);
+ =}
+
+ reaction(in) {=
+ received = true;
+ reactor::log::Info() << "Ping Received at " << get_elapsed_logical_time();
+ auto expected = 50ms + 100ms * counter++;
+ if (get_elapsed_logical_time() != expected) {
+ reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time();
+ exit(1);
+ }
+ =}
+
+ reaction(shutdown) {=
+ if(!received) {
+ reactor::log::Error() << "Nothing received.";
+ exit(1);
+ }
+ =}
+}
+
+reactor Pong {
+ input in: std_msgs::msg::Empty
+ output out: std_msgs::msg::Empty
+ state received: bool = false
+ state counter: int = 0
+
+ reaction(in) -> out {=
+ received = true;
+ reactor::log::Info() << "Pong Received at " << get_elapsed_logical_time();
+ auto expected = 100ms * counter++;
+ if (get_elapsed_logical_time() != expected) {
+ reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time();
+ exit(1);
+ }
+ std_msgs::msg::Empty msg;
+ out.set(msg);
+ =}
+
+ reaction(shutdown) {=
+ if(!received) {
+ reactor::log::Error() << "Nothing received.";
+ exit(1);
+ }
+ =}
+}
+
+main reactor {
+ @federate
+ ping = new Ping()
+ @federate
+ pong = new Pong()
+
+ ping.out -> pong.in
+ pong.out -> ping.in after 50 ms
+}
diff --git a/test/Cpp/src/ros-federated/FederateEmptyConnection.lf b/test/Cpp/src/ros-federated/FederateEmptyConnection.lf
new file mode 100644
index 0000000000..6eec4592f1
--- /dev/null
+++ b/test/Cpp/src/ros-federated/FederateEmptyConnection.lf
@@ -0,0 +1,72 @@
+target Cpp {
+ ros2: true,
+ timeout: 5s,
+ ros2-dependencies: ["std_msgs"],
+}
+
+public preamble {=
+ #include "rclcpp/rclcpp.hpp"
+ #include "std_msgs/msg/empty.hpp"
+=}
+
+reactor Pub {
+ private preamble {=
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+ =}
+
+ timer t(0, 100 ms)
+ output out : std_msgs::msg::Empty
+
+ reaction(startup) {=
+ RCLCPP_INFO(lf_node->get_logger(), "Pub here");
+ =}
+
+ reaction(t) -> out {=
+ std_msgs::msg::Empty msg;
+ out.set(msg);
+ =}
+}
+
+reactor Sub {
+ private preamble {=
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+ =}
+ state count: unsigned(0)
+ state received: bool = false
+
+ input in : std_msgs::msg::Empty
+
+ reaction(startup) {=
+ RCLCPP_INFO(lf_node->get_logger(), "Sub here");
+ =}
+
+ reaction(in) {=
+ received = true;
+ reactor::log::Info() << "Received empty message at " << get_elapsed_logical_time();
+ auto expected = 100ms * count;
+ count++;
+ if (get_elapsed_logical_time() != expected) {
+ reactor::log::Error() << "Expected empty message at " << expected << " but received it at " << get_elapsed_logical_time();
+ exit(1);
+ }
+ =}
+
+ reaction(shutdown) {=
+ if(!received) {
+ reactor::log::Error() << "Nothing received.";
+ exit(1);
+ }
+ =}
+}
+
+
+main reactor {
+ @federate
+ pub = new Pub()
+ @federate
+ sub = new Sub()
+
+ pub.out -> sub.in
+}
\ No newline at end of file
diff --git a/test/Cpp/src/ros-federated/FederateHierarchicalConnections.lf b/test/Cpp/src/ros-federated/FederateHierarchicalConnections.lf
new file mode 100644
index 0000000000..668a5f4ea5
--- /dev/null
+++ b/test/Cpp/src/ros-federated/FederateHierarchicalConnections.lf
@@ -0,0 +1,69 @@
+// Test data transport across hierarchy.
+target Cpp {
+ ros2: true,
+ ros2-dependencies: ["std_msgs"],
+ timeout: 3 sec
+}
+
+public preamble {=
+ #include "rclcpp/rclcpp.hpp"
+ #include "std_msgs/msg/int64.hpp"
+
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+=}
+
+reactor Source {
+ output out: std_msgs::msg::Int64
+ timer t
+
+ reaction(t) -> out {=
+ std_msgs::msg::Int64 i;
+ i.data = 1;
+ out.set(i);
+ =}
+}
+
+reactor Gain {
+ input in: std_msgs::msg::Int64
+ output out: std_msgs::msg::Int64
+
+ reaction(in) -> out {=
+ std_msgs::msg::Int64 i;
+ i.data = in.get()->data*2;
+ out.set(i);
+ =}
+}
+
+reactor Print {
+ input in: std_msgs::msg::Int64
+
+ reaction(in) {=
+ std_msgs::msg::Int64 i = *in.get();
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "Received: " << i.data);
+ if (i.data != 2) {
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "Expected 2");
+ exit(1);
+ }
+ =}
+}
+
+reactor GainContainer {
+ input in: std_msgs::msg::Int64
+ output out: std_msgs::msg::Int64
+ output out2: std_msgs::msg::Int64
+ gain = new Gain()
+ in -> gain.in
+ gain.out -> out
+ gain.out -> out2
+}
+
+main reactor {
+ source = new Source()
+ container = new GainContainer()
+ print = new Print()
+ print2 = new Print()
+ source.out -> container.in
+ container.out -> print.in
+ container.out -> print2.in
+}
diff --git a/test/Cpp/src/ros-federated/FederateHierarchicalConnections2.lf b/test/Cpp/src/ros-federated/FederateHierarchicalConnections2.lf
new file mode 100644
index 0000000000..a5515961aa
--- /dev/null
+++ b/test/Cpp/src/ros-federated/FederateHierarchicalConnections2.lf
@@ -0,0 +1,86 @@
+// Test data transport across hierarchy.
+target Cpp {
+ ros2: true,
+ ros2-dependencies: ["std_msgs"],
+ timeout: 3 sec
+}
+
+public preamble {=
+ #include "rclcpp/rclcpp.hpp"
+ #include "std_msgs/msg/int64.hpp"
+
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+=}
+
+reactor Source {
+ output out: std_msgs::msg::Int64
+ timer t
+
+ reaction(t) -> out {=
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "source sending");
+ std_msgs::msg::Int64 i;
+ i.data = 1;
+ out.set(i);
+ =}
+}
+
+reactor Gain {
+ input in: std_msgs::msg::Int64
+ output out: std_msgs::msg::Int64
+
+ reaction(in) -> out {=
+ std_msgs::msg::Int64 i;
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain gaining");
+ i.data = in.get()->data*2;
+ out.set(i);
+ =}
+}
+
+reactor Print {
+ input in: std_msgs::msg::Int64
+
+ reaction(in) {=
+ std_msgs::msg::Int64 i = *in.get();
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "Received: " << i.data);
+ if (i.data != 2) {
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "Expected 2");
+ exit(1);
+ }
+ =}
+}
+
+
+
+reactor GainContainer {
+ input in_cont: std_msgs::msg::Int64
+ output out_cont: std_msgs::msg::Int64
+
+ @federate
+ gain = new Gain()
+ in_cont -> gain.in
+
+ reaction(in_cont) {=
+ //std_msgs::msg::Int64 i;
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain container reacting");
+ //i.data = in.get()->data*2;
+ // this does not work since gain instance does not exist inside of GainContainer if it is a federate
+ // could be implemented with stubs or use relays (see FederateHierarchicalConnections3)
+ // However, it will still not work until zero delay loop problem is solved
+ //gain.in.set(i);
+ =}
+
+ gain.out -> out_cont
+}
+
+main reactor {
+ source = new Source()
+
+ @federate
+ container = new GainContainer()
+
+ print = new Print()
+
+ source.out -> container.in_cont
+ container.out_cont -> print.in after 1s
+}
diff --git a/test/Cpp/src/ros-federated/FederateHierarchicalConnections2relay.lf b/test/Cpp/src/ros-federated/FederateHierarchicalConnections2relay.lf
new file mode 100644
index 0000000000..d89747b01d
--- /dev/null
+++ b/test/Cpp/src/ros-federated/FederateHierarchicalConnections2relay.lf
@@ -0,0 +1,93 @@
+// Test data transport across hierarchy.
+// this test microstep overflows (until zero delay loops problem is solved)
+target Cpp {
+ ros2: true,
+ ros2-dependencies: ["std_msgs"],
+ timeout: 3 sec
+}
+
+public preamble {=
+ #include "rclcpp/rclcpp.hpp"
+ #include "std_msgs/msg/int64.hpp"
+
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+=}
+
+reactor Source {
+ output out: std_msgs::msg::Int64
+ timer t
+
+ reaction(t) -> out {=
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "source sending");
+ std_msgs::msg::Int64 i;
+ i.data = 1;
+ out.set(i);
+ =}
+}
+
+reactor Gain {
+ input in: std_msgs::msg::Int64
+ output out: std_msgs::msg::Int64
+
+ reaction(in) -> out {=
+ std_msgs::msg::Int64 i;
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain gaining");
+ i.data = in.get()->data*2;
+ out.set(i);
+ =}
+}
+
+reactor Relay {
+ input in: std_msgs::msg::Int64
+ output out: std_msgs::msg::Int64
+
+ reaction(in) -> out {=
+ out.set(in.get());
+ =}
+}
+
+reactor Print {
+ input in: std_msgs::msg::Int64
+
+ reaction(in) {=
+ std_msgs::msg::Int64 i = *in.get();
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "Received: " << i.data);
+ if (i.data != 4) {
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "Expected 4");
+ exit(1);
+ }
+ =}
+}
+
+reactor GainContainer {
+ input in_cont: std_msgs::msg::Int64
+ output out_cont: std_msgs::msg::Int64
+
+ @federate
+ gain = new Gain()
+ relay = new Relay()
+
+
+ reaction(in_cont) -> relay.in {=
+ std_msgs::msg::Int64 i;
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain container gaining");
+ i.data = in_cont.get()->data*2;
+ relay.in.set(i);
+ =}
+ relay.out -> gain.in
+ gain.out -> out_cont
+}
+
+main reactor {
+ source = new Source()
+
+
+ @federate
+ container = new GainContainer()
+
+ print = new Print()
+
+ source.out -> container.in_cont
+ container.out_cont -> print.in after 1s
+}
diff --git a/test/Cpp/src/ros-federated/FederateHierarchicalConnections3.lf b/test/Cpp/src/ros-federated/FederateHierarchicalConnections3.lf
new file mode 100644
index 0000000000..83da759432
--- /dev/null
+++ b/test/Cpp/src/ros-federated/FederateHierarchicalConnections3.lf
@@ -0,0 +1,114 @@
+// Test data transport across hierarchy.
+// this test microstep overflows (until zero delay loops problem is solved)
+target Cpp {
+ ros2: true,
+ ros2-dependencies: ["std_msgs"],
+ timeout: 3 sec
+}
+
+public preamble {=
+ #include "rclcpp/rclcpp.hpp"
+ #include "std_msgs/msg/int64.hpp"
+
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+=}
+
+reactor Source {
+ output out: std_msgs::msg::Int64
+ timer t
+
+ reaction(t) -> out {=
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "source sending");
+ std_msgs::msg::Int64 i;
+ i.data = 1;
+ out.set(i);
+ =}
+}
+
+reactor Gain {
+ input in: std_msgs::msg::Int64
+ output out: std_msgs::msg::Int64
+
+ reaction(in) -> out {=
+ std_msgs::msg::Int64 i;
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain gaining");
+ i.data = in.get()->data*2;
+ out.set(i);
+ =}
+}
+
+reactor Relay {
+ input in: std_msgs::msg::Int64
+ output out: std_msgs::msg::Int64
+
+ reaction(in) -> out {=
+ out.set(in.get());
+ =}
+}
+
+reactor Print {
+ input in: std_msgs::msg::Int64
+
+ reaction(in) {=
+ std_msgs::msg::Int64 i = *in.get();
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "Received: " << i.data);
+ if (i.data != 8) {
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "Expected 8");
+ exit(1);
+ }
+ =}
+}
+
+reactor GainContainerContainer {
+ input in_cont: std_msgs::msg::Int64
+ output out_cont: std_msgs::msg::Int64
+
+ @federate
+ gaincont = new GainContainer()
+ relay = new Relay()
+
+ reaction(in_cont) -> relay.in {=
+ std_msgs::msg::Int64 i;
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain containercontainer gaining");
+ i.data = in_cont.get()->data*2;
+ relay.in.set(i);
+ =}
+
+ relay.out -> gaincont.in_cont
+ gaincont.out_cont -> out_cont
+}
+
+
+
+reactor GainContainer {
+ input in_cont: std_msgs::msg::Int64
+ output out_cont: std_msgs::msg::Int64
+
+ @federate
+ gain = new Gain()
+ relay = new Relay()
+
+
+ reaction(in_cont) -> relay.in {=
+ std_msgs::msg::Int64 i;
+ RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain container gaining");
+ i.data = in_cont.get()->data*2;
+ relay.in.set(i);
+ =}
+ relay.out -> gain.in
+ gain.out -> out_cont
+}
+
+main reactor {
+ source = new Source()
+
+
+ @federate
+ container = new GainContainerContainer()
+
+ print = new Print()
+
+ source.out -> container.in_cont
+ container.out_cont -> print.in after 1s
+}
diff --git a/test/Cpp/src/ros-federated/FederateHierarchy.lf b/test/Cpp/src/ros-federated/FederateHierarchy.lf
new file mode 100644
index 0000000000..cfd2295d1b
--- /dev/null
+++ b/test/Cpp/src/ros-federated/FederateHierarchy.lf
@@ -0,0 +1,82 @@
+// This is a smoke test for nested federates
+target Cpp {
+ ros2: true,
+ ros2-dependencies: ["std_msgs"],
+ timeout: 3 sec
+}
+
+public preamble {=
+ #include "rclcpp/rclcpp.hpp"
+=}
+
+reactor InnerNode {
+ state id: {=const std::string=} = "inner"
+ timer t(0, 1 sec)
+
+ reaction(t) {=
+ reactor::log::Info() << id << " reaction executes.";
+ std::this_thread::sleep_for(std::chrono::milliseconds(70));
+ reactor::log::Info() << id << " reaction done.";
+ =} deadline(300 msec) {=
+ reactor::log::Error() << id << " deadline was violated!";
+ exit(1);
+ =}
+}
+
+reactor SomeNode {
+ state id: {=const std::string=} = "node"
+
+ timer t(0, 2 sec)
+
+ reaction(t) {=
+ reactor::log::Info() << id << " reaction executes.";
+ std::this_thread::sleep_for(std::chrono::milliseconds(500));
+ reactor::log::Info() << id << " reaction done.";
+ =} deadline(300 msec) {=
+ reactor::log::Error() << id << " deadline was violated!";
+ exit(1);
+ =}
+}
+
+reactor MiddleNode {
+ state id: {=const std::string=} = "middle"
+
+ @federate
+ inner = new InnerNode()
+
+ timer t(0, 200 msec)
+
+ reaction(t) {=
+ reactor::log::Info() << id << " reaction executes.";
+ std::this_thread::sleep_for(std::chrono::milliseconds(70));
+ reactor::log::Info() << id << " reaction done.";
+ =} deadline(300 msec) {=
+ reactor::log::Error() << id << " deadline was violated!";
+ exit(1);
+ =}
+}
+
+reactor OuterNode {
+ state id: {=const std::string=} = "outer"
+
+ @federate
+ middle = new MiddleNode()
+
+ timer t(0, 500 msec)
+
+ reaction(t) {=
+ reactor::log::Info() << id << " reaction executes.";
+ std::this_thread::sleep_for(std::chrono::milliseconds(200));
+ reactor::log::Info() << id << " reaction done.";
+ =} deadline(300 msec) {=
+ reactor::log::Error() << id << " deadline was violated!";
+ exit(1);
+ =}
+
+}
+
+main reactor {
+ outer = new OuterNode()
+ @federate
+ node = new SomeNode()
+}
diff --git a/test/Cpp/src/ros-federated/FederateMultipleReactions.lf b/test/Cpp/src/ros-federated/FederateMultipleReactions.lf
new file mode 100644
index 0000000000..7a68c45d84
--- /dev/null
+++ b/test/Cpp/src/ros-federated/FederateMultipleReactions.lf
@@ -0,0 +1,88 @@
+target Cpp {
+ ros2: true,
+ timeout: 5s,
+ ros2-dependencies: ["std_msgs"],
+}
+
+public preamble {=
+ #include "rclcpp/rclcpp.hpp"
+ #include "std_msgs/msg/string.hpp"
+=}
+
+reactor Pub {
+ private preamble {=
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+ =}
+
+ timer t(0, 100 ms)
+ output out : std_msgs::msg::String
+
+ reaction(startup) {=
+ RCLCPP_INFO(lf_node->get_logger(), "Pub here");
+ =}
+
+ reaction(t) -> out {=
+ std_msgs::msg::String msg;
+ msg.data = "Hello";
+ out.set(msg);
+ =}
+}
+
+reactor Sub {
+ private preamble {=
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+ =}
+ state count: unsigned(0)
+ state count2: unsigned(0)
+ state received: bool = false
+ state received2: bool = false
+
+ input in : std_msgs::msg::String
+
+ reaction(startup) {=
+ RCLCPP_INFO(lf_node->get_logger(), "Sub here");
+ =}
+
+ reaction(in) {=
+ received = true;
+ auto value = in.get()->data;
+ reactor::log::Info() << "Received " << value << " at " << get_elapsed_logical_time();
+ auto expected = 100ms * count;
+ count++;
+ if (get_elapsed_logical_time() != expected) {
+ reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time();
+ exit(1);
+ }
+ =}
+
+ reaction(in) {=
+ received2 = true;
+ auto value = in.get()->data;
+ reactor::log::Info() << "In second reaction " << value << " at " << get_elapsed_logical_time();
+ auto expected = 100ms * count2;
+ count2++;
+ if (get_elapsed_logical_time() != expected) {
+ reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time();
+ exit(1);
+ }
+ =}
+
+ reaction(shutdown) {=
+ if(!received || !received2) {
+ reactor::log::Error() << "Nothing received.";
+ exit(1);
+ }
+ =}
+}
+
+
+main reactor {
+ @federate
+ pub = new Pub()
+ @federate
+ sub = new Sub()
+
+ pub.out -> sub.in
+}
\ No newline at end of file
diff --git a/test/Cpp/src/ros-federated/FederateStringConnection.lf b/test/Cpp/src/ros-federated/FederateStringConnection.lf
new file mode 100644
index 0000000000..b7d4205b19
--- /dev/null
+++ b/test/Cpp/src/ros-federated/FederateStringConnection.lf
@@ -0,0 +1,74 @@
+target Cpp {
+ ros2: true,
+ timeout: 5s,
+ ros2-dependencies: ["std_msgs"],
+}
+
+public preamble {=
+ #include "rclcpp/rclcpp.hpp"
+ #include "std_msgs/msg/string.hpp"
+=}
+
+reactor Pub {
+ private preamble {=
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+ =}
+
+ timer t(0, 100 ms)
+ output out : std_msgs::msg::String
+
+ reaction(startup) {=
+ RCLCPP_INFO(lf_node->get_logger(), "Pub here");
+ =}
+
+ reaction(t) -> out {=
+ std_msgs::msg::String msg;
+ msg.data = "Hello";
+ out.set(msg);
+ =}
+}
+
+reactor Sub {
+ private preamble {=
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+ =}
+ state count: unsigned(0)
+ state received: bool = false
+
+ input in : std_msgs::msg::String
+
+ reaction(startup) {=
+ RCLCPP_INFO(lf_node->get_logger(), "Sub here");
+ =}
+
+ reaction(in) {=
+ received = true;
+ auto value = in.get()->data;
+ reactor::log::Info() << "Received " << value << " at " << get_elapsed_logical_time();
+ auto expected = 100ms * count;
+ count++;
+ if (get_elapsed_logical_time() != expected) {
+ reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time();
+ exit(1);
+ }
+ =}
+
+ reaction(shutdown) {=
+ if(!received) {
+ reactor::log::Error() << "Nothing received.";
+ exit(1);
+ }
+ =}
+}
+
+
+main reactor {
+ @federate
+ pub = new Pub()
+ @federate
+ sub = new Sub()
+
+ pub.out -> sub.in
+}
\ No newline at end of file
diff --git a/test/Cpp/src/ros-federated/FederateStringConnectionDelayed.lf b/test/Cpp/src/ros-federated/FederateStringConnectionDelayed.lf
new file mode 100644
index 0000000000..4839ab0e35
--- /dev/null
+++ b/test/Cpp/src/ros-federated/FederateStringConnectionDelayed.lf
@@ -0,0 +1,74 @@
+target Cpp {
+ ros2: true,
+ timeout: 5s,
+ ros2-dependencies: ["std_msgs"],
+}
+
+public preamble {=
+ #include "rclcpp/rclcpp.hpp"
+ #include "std_msgs/msg/string.hpp"
+=}
+
+reactor Pub {
+ private preamble {=
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+ =}
+
+ timer t(0, 100 ms)
+ output out : std_msgs::msg::String
+
+ reaction(startup) {=
+ RCLCPP_INFO(lf_node->get_logger(), "Pub here");
+ =}
+
+ reaction(t) -> out {=
+ std_msgs::msg::String msg;
+ msg.data = "Hello";
+ out.set(msg);
+ =}
+}
+
+reactor Sub {
+ private preamble {=
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+ =}
+ state count: unsigned(0)
+ state received: bool = false
+
+ input in : std_msgs::msg::String
+
+ reaction(startup) {=
+ RCLCPP_INFO(lf_node->get_logger(), "Sub here");
+ =}
+
+ reaction(in) {=
+ received = true;
+ auto value = in.get()->data;
+ reactor::log::Info() << "Received " << value << " at " << get_elapsed_logical_time();
+ auto expected = 100ms * count + 50ms;
+ count++;
+ if (get_elapsed_logical_time() != expected) {
+ reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time();
+ exit(1);
+ }
+ =}
+
+ reaction(shutdown) {=
+ if(!received) {
+ reactor::log::Error() << "Nothing received.";
+ exit(1);
+ }
+ =}
+}
+
+
+main reactor {
+ @federate
+ pub = new Pub()
+ @federate
+ sub = new Sub()
+
+ pub.out -> sub.in after 50ms
+}
\ No newline at end of file
diff --git a/test/Cpp/src/ros-federated/FederateStringConnectionPhysical.lf b/test/Cpp/src/ros-federated/FederateStringConnectionPhysical.lf
new file mode 100644
index 0000000000..3b74f16a55
--- /dev/null
+++ b/test/Cpp/src/ros-federated/FederateStringConnectionPhysical.lf
@@ -0,0 +1,74 @@
+target Cpp {
+ ros2: true,
+ timeout: 5s,
+ ros2-dependencies: ["std_msgs"],
+}
+
+public preamble {=
+ #include "rclcpp/rclcpp.hpp"
+ #include "std_msgs/msg/string.hpp"
+=}
+
+reactor Pub {
+ private preamble {=
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+ =}
+
+ timer t(0, 100 ms)
+ output out : std_msgs::msg::String
+
+ reaction(startup) {=
+ RCLCPP_INFO(lf_node->get_logger(), "Pub here");
+ =}
+
+ reaction(t) -> out {=
+ std_msgs::msg::String msg;
+ msg.data = "Hello";
+ out.set(msg);
+ =}
+}
+
+reactor Sub {
+ private preamble {=
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+ =}
+ state count: unsigned(0)
+ state received: bool = false
+
+ input in : std_msgs::msg::String
+
+ reaction(startup) {=
+ RCLCPP_INFO(lf_node->get_logger(), "Sub here");
+ =}
+
+ reaction(in) {=
+ received = true;
+ auto value = in.get()->data;
+ reactor::log::Info() << "Received " << value << " at " << get_elapsed_logical_time();
+ auto expected = 100ms * count;
+ count++;
+ if (get_elapsed_logical_time() < expected) {
+ reactor::log::Error() << "Expected value not before " << expected << " but received it at " << get_elapsed_logical_time();
+ exit(1);
+ }
+ =}
+
+ reaction(shutdown) {=
+ if(!received) {
+ reactor::log::Error() << "Nothing received.";
+ exit(1);
+ }
+ =}
+}
+
+
+main reactor {
+ @federate
+ pub = new Pub()
+ @federate
+ sub = new Sub()
+
+ pub.out ~> sub.in
+}
\ No newline at end of file
diff --git a/test/Cpp/src/ros-federated/StringConnection.lf b/test/Cpp/src/ros-federated/StringConnection.lf
new file mode 100644
index 0000000000..d229d67eff
--- /dev/null
+++ b/test/Cpp/src/ros-federated/StringConnection.lf
@@ -0,0 +1,72 @@
+target Cpp {
+ ros2: true,
+ timeout: 5s,
+ ros2-dependencies: ["std_msgs"],
+}
+
+public preamble {=
+ #include "rclcpp/rclcpp.hpp"
+ #include "std_msgs/msg/string.hpp"
+=}
+
+reactor Pub {
+ private preamble {=
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+ =}
+
+ timer t(0, 100 ms)
+ output out : std_msgs::msg::String
+
+ reaction(startup) {=
+ RCLCPP_INFO(lf_node->get_logger(), "Pub here");
+ =}
+
+ reaction(t) -> out {=
+ std_msgs::msg::String msg;
+ msg.data = "Hello";
+ out.set(msg);
+ =}
+}
+
+reactor Sub {
+ private preamble {=
+ // FIXME: forward declaration to make the node visible
+ extern rclcpp::Node* lf_node;
+ =}
+ state count: unsigned(0)
+ state received: bool = false
+
+ input in : std_msgs::msg::String
+
+ reaction(startup) {=
+ RCLCPP_INFO(lf_node->get_logger(), "Sub here");
+ =}
+
+ reaction(in) {=
+ received = true;
+ auto value = in.get()->data;
+ reactor::log::Info() << "Received " << value;
+ auto expected = 100ms * count;
+ count++;
+ if (get_elapsed_logical_time() != expected) {
+ reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time();
+ exit(1);
+ }
+ =}
+
+ reaction(shutdown) {=
+ if(!received) {
+ reactor::log::Error() << "Nothing received.";
+ exit(1);
+ }
+ =}
+}
+
+
+main reactor {
+ pub = new Pub()
+ sub = new Sub()
+
+ pub.out -> sub.in
+}
\ No newline at end of file