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

Enable generation of ROS2 nodes directly from C++ code #984

Merged
merged 51 commits into from
Mar 7, 2022

Conversation

cmnrd
Copy link
Collaborator

@cmnrd cmnrd commented Feb 28, 2022

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.

@cmnrd cmnrd added this to the 0.1.0 milestone Mar 1, 2022
@cmnrd cmnrd marked this pull request as ready for review March 1, 2022 18:08
@cmnrd cmnrd requested review from lhstrh and Soroosh129 March 2, 2022 10:10
Copy link
Collaborator

@edwardalee edwardalee left a 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
Copy link
Collaborator

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?

Copy link
Contributor

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?

Copy link
Collaborator Author

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.

Copy link
Contributor

@Soroosh129 Soroosh129 left a 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>
Copy link
Contributor

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.

Copy link
Collaborator Author

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>
Copy link
Contributor

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?

Copy link
Collaborator Author

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 {
Copy link
Contributor

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.

Copy link
Collaborator Author

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) :
Copy link
Contributor

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/setup-ros@0.2.2
Copy link
Contributor

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?

Copy link
Collaborator Author

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 """
Copy link
Contributor

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.

Copy link
Collaborator Author

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
Copy link
Contributor

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?
Copy link
Contributor

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?

Copy link
Collaborator Author

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);
Copy link
Contributor

@Soroosh129 Soroosh129 Mar 5, 2022

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.

Copy link
Collaborator Author

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();} };
Copy link
Contributor

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?

Copy link
Collaborator Author

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).

@cmnrd
Copy link
Collaborator Author

cmnrd commented Mar 7, 2022

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.

@cmnrd cmnrd merged commit 1534be4 into master Mar 7, 2022
@cmnrd cmnrd deleted the cpp-ros2-platform branch March 7, 2022 13:33
@lhstrh lhstrh added the enhancement Enhancement of existing feature label Mar 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Enhancement of existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants