Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cpp ROS2 federate impl #1736

Draft
wants to merge 22 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 21 additions & 4 deletions core/src/main/java/org/lflang/AttributeUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,13 @@ public static Map<String, String> getAttributeValues(EObject node, String attrNa
/**
* Retrieve a specific annotation in a comment associated with the given model element in the AST.
*
* <p>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.
* <p>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;
Expand Down Expand Up @@ -289,6 +289,23 @@ public static boolean isEnclave(Instantiation node) {
return getEnclaveAttribute(node) != null;
}

/**
* Return the {@code @federate} attribute annotated on the given node.
*
* <p>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
*
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/java/org/lflang/validation/AttributeSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -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="<property_name>", tactic="<induction|bmc>", spec="<SMTL_spec>")
// SMTL is the safety fragment of Metric Temporal Logic (MTL).

ATTRIBUTE_SPECS_BY_NAME.put(
"property",
new AttributeSpec(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.lflang.generator.cpp

interface ConnectionGenerator {
abstract fun generateDeclarations() : String
abstract fun generateInitializers() : String
}
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Expand All @@ -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")
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
***************/
package org.lflang.generator.cpp


import org.lflang.MessageReporter
import org.lflang.generator.PrependOperator
import org.lflang.isGeneric
Expand All @@ -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 */
Expand Down
70 changes: 70 additions & 0 deletions core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt
Original file line number Diff line number Diff line change
@@ -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<ROSMsgType>
get() = with (this ){
val reactors : MutableList<Reactor> = mutableListOf(this)
val types : MutableSet<ROSMsgType> = 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
}



74 changes: 61 additions & 13 deletions core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -9,27 +13,66 @@ 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<CppRos2NodeGenerator> = 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<org.lflang.lf.Reactor> = 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<ROSMsgType> = 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(
packageGenerator.generatePackageCmake(generator.cppSources),
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);
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package org.lflang.generator.cpp

import org.lflang.capitalize
import org.lflang.joinWithLn

class CppRos2MessageWrapperGenerator (private val messageTypesToWrap : Set<ROSMsgType>){
val ROSMsgType.fileContent : String
get() {
return """
|lf_msgs_ros/Tag tag
|${"$userTypeMsgInclude message"}
""".trimMargin()
}

val fileContents : List<String>
get() {
return messageTypesToWrap.map{it.fileContent}
}

fun generateMessageFiles() : List<Pair<String, String>> {
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 """
|<?xml version="1.0"?>
|<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|<package format="3">
| <name>lf_wrapped_msgs</name>
| <version>0.0.0</version>
| <description>Generated message wrappers including original message type and a LF-tag</description>
| <maintainer email="[email protected]">Todo</maintainer>
| <license>Todo</license>
|
| <build_depend>rosidl_default_generators</build_depend>
| <depend>std_msgs</depend>
| <depend>lf_msgs_ros</depend>
| <exec_depend>rosidl_default_runtime</exec_depend>
|
| <member_of_group>rosidl_interface_packages</member_of_group>
| <export>
| <build_type>ament_cmake</build_type>
| </export>
|
|</package>
""".trimMargin()
}
}
Loading