-
Notifications
You must be signed in to change notification settings - Fork 64
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
Enable generation of ROS2 nodes directly from C++ code #984
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks pretty cool to me. It seems to offer a nice simple way to make an LF program into a ROS node.
|
||
main reactor { | ||
private preamble {= | ||
// FIXME: forward declaration to make the node visible |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this really necessary? Can't you generate this declaration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps, lf_ros_node
can be a reserved state variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a temporary workaround until I found a better solution. I am trying to keep the code for generating reactor classes agnostic of the target platform, as it keeps the code generator clean. However, there needs to be some way of extending the generated code. I will fix this mechanism as soon as I have found a satisfactory solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
I agree with @edwardalee that this provide a simple way of turning an LF program into a ROS node.
I left a few comments based on my knowledge of ROS 2.
|<?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>${fileConfig.name}</name> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be converted to all lowercase to conform to ROS2's package naming convention?
Package names should follow common C variable naming conventions: lower case, start with a letter, use underscore separators, e.g. laser_viewer
I guess we don't have a naming convention for LF programs yet, but the tests, for example, are all capitalized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, I don't think automagically converting the name is a good idea. My current idea is to add a package-name
property to the ros2
section. There we could also add other details such as the license and maintainer information required in the package.xml
.
| <depend>rclcpp</depend> | ||
| <depend>rclcpp_components</depend> | ||
| <depend>std_msgs</depend> | ||
| <depend>$reactorCppName</depend> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have a method in mind for programmers to be able to add to these dependencies?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not yet, but it is planned and I would also add this to the ros2
property.
""".trimMargin() | ||
} | ||
|
||
fun generatePackageCmake(sources: List<Path>): String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would the ordinary CMake generator for the Cpp target plus an extension with added lines such as
find_package(ament_cmake_auto REQUIRED)
ament_auto_find_build_dependencies()
work here? That design strikes me as more future-proof.
Here is how we do this in the C target.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so. Basically, only the header section is the same. While this could be factored out in a method to facilitate code reuse, I don't think we would gain too much from it here. Overall, I don't like to make predictions as to what the future might bring ;) I prefer a simple design and only add complexity when I am sure it is needed.
import java.nio.file.Path | ||
import java.nio.file.Paths | ||
|
||
class CppStandaloneGenerator(generator: CppGenerator) : |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing documentation for the class.
ref: ${{ inputs.runtime-ref }} | ||
if: ${{ inputs.runtime-ref }} | ||
- name: Setup ROS2 | ||
uses: ros-tooling/[email protected] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be very useful to test the C target's ROS 2 support as well.
Is it okay to call this just ros2-tests
, or should we extract the setup here and share it among two separate workflows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do something similar as in the benchmark tests, by adding a target parameter and making the jobs conditional. Feel free to add the C tests (but probably in another PR).
fun generateBinScript(): String { | ||
val relPath = fileConfig.binPath.relativize(fileConfig.outPath).toUnixString() | ||
|
||
return """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You might want to consider generating a ROS2 launch file instead of a bash script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the pointer! I was vaguely aware of launch files, but did not think of them when creating the script.
|
||
main reactor { | ||
private preamble {= | ||
// FIXME: forward declaration to make the node visible |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps, lf_ros_node
can be a reserved state variable?
reaction(startup) -> message {= | ||
subscription = lf_node->create_subscription<std_msgs::msg::String>( | ||
"topic", 10, [&message](const std_msgs::msg::String::SharedPtr msg) { message.schedule(msg->data); } ); | ||
// FIXME: Why can't we use a reference type in the lambda argument? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean something like this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess so, but I am not sure. I tried to use const std_msgs::msg::String::SharedPtr& msg
in the signature, but that fails with a lot of weird C++ errors. I just realized after checking the documentation again that galactic uses const std_msgs::msg::String& msg
in the signature and foxy uses const std_msgs::msg::String::SharedPtr msg
. So I guess const std_msgs::msg::String::SharedPtr& msg
is not supported, but const std_msgs::msg::String& msg
is.
auto message = std_msgs::msg::String(); | ||
message.data = "Hello, world! " + std::to_string(count++); | ||
reactor::log::Info() << "Publishing: " << message.data; | ||
publisher->publish(message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the default rclcpp executor is thread-safe, so calling publish for the same topic from multiple LF worker threads will be problematic without acquiring a mutex. This is of course fine for this example, but I think we should be mindful of this limitation, especially if reactors other than the main reactor will be publishing messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, thread-safety is not discussed in the documentation. According to this post, however, these operations should be thread-safe.
class __lf_Timeout : public reactor::Reactor { | ||
private: | ||
reactor::Timer timer; | ||
reactor::Reaction r_timer{"r_timer", 1, this, [this]() { environment()->sync_shutdown();} }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is timeout coordinated with rclcpp's shutdown?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The node invokes rclcpp's shutdown after the reactor execution terminated (i.e. after all shutdown reactions were processed and the main scheduler thread terminates).
Thanks for your suggestions! I consider this PR just as the start and there will be more PRs in the future to extend functionality and fix things where needed. |
This PR provides basic support for compilation of ROS2 nodes directly from LF programs in the C++ target. There are still many features missing that will be part of subsequent PRs. Also, it is unclear to me how the new ROS2 specific generator should interact with the LSP implementation. However, I would also address this in a future PR.
With this PR basic compilation works in lfc and Epoch. To validate it, I added a ros2 CI flow which compiles all the C++ tests for the ros2 platform.
Note that this PR is based on #978 and hence currently includes all changes from this PR in the diff.