Class design#
We'll use the autoware_gnss_poser package as an example.
Namespaces#
namespace autoware::gnss_poser
{
...
} // namespace autoware::gnss_poser
- Everything should be under
autoware::gnss_posernamespace. - Closing braces must contain a comment with the namespace name. (
clang-formatadds the comment, andcpplintchecks it.)
Classes#
Nodes#
gnss_poser_node.hpp#
class GNSSPoser : public rclcpp::Node
{
public:
explicit GNSSPoser(const rclcpp::NodeOptions & node_options);
...
};
gnss_poser_node.cpp#
GNSSPoser::GNSSPoser(const rclcpp::NodeOptions & node_options)
: Node("gnss_poser", node_options)
{
...
}
- The class name should be in
CamelCase. - Node classes should inherit from
rclcpp::Node. A node that supports Agnocast inherits fromautoware::agnocast_wrapper::Nodeinstead. See Nodes that support Agnocast. - The constructor must be marked explicit.
- The constructor must take
rclcpp::NodeOptionsas an argument. - Default node name:
- should not have
autoware_prefix. - should NOT have
_nodesuffix.- Rationale: Node names are useful in the runtime. And output of
ros2 node listwill show only nodes anyway. Having_nodeis redundant.
- Rationale: Node names are useful in the runtime. And output of
- Example:
gnss_poser.
- should not have
Component registration#
...
} // namespace autoware::gnss_poser
#include <rclcpp_components/register_node_macro.hpp>
RCLCPP_COMPONENTS_REGISTER_NODE(autoware::gnss_poser::GNSSPoser)
- The component should be registered at the end of the
gnss_poser_node.cppfile, outside the namespaces. - A node that supports Agnocast uses the same macro in the source file. The CMake registration is different. See Directory structure.
Nodes that support Agnocast#
Agnocast is a zero-copy middleware that works together with ROS 2. The autoware_agnocast_wrapper package of Autoware Core lets one node run on rclcpp or on Agnocast. Many nodes in Autoware Core and Autoware Universe use it. The real autoware_gnss_poser is one of them:
#include <autoware/agnocast_wrapper/node.hpp>
namespace autoware::gnss_poser
{
class GNSSPoser : public autoware::agnocast_wrapper::Node
{
public:
explicit GNSSPoser(const rclcpp::NodeOptions & node_options);
...
};
} // namespace autoware::gnss_poser
GNSSPoser::GNSSPoser(const rclcpp::NodeOptions & node_options)
: autoware::agnocast_wrapper::Node("gnss_poser", node_options)
{
...
}
- The rules for the class name, the constructor, and the node name stay the same.
autoware::agnocast_wrapper::Nodedoes not derive fromrclcpp::Node. It gives a subset of therclcpp::NodeAPI, with the same member names and arguments.- Write the types of publishers, subscriptions, and timers with the macros of the wrapper, for example
AUTOWARE_PUBLISHER_PTR(MessageT),AUTOWARE_SUBSCRIPTION_PTR(MessageT), andAUTOWARE_TIMER_PTR. Then the same source code builds with Agnocast and without it. - The build uses Agnocast only when the
ENABLE_AGNOCASTenvironment variable is1. The default build usesrclcpponly. - For all the rules, see the README of
autoware_agnocast_wrapper.
Libraries#
A library package gives classes and functions to other packages, and it has no node. We'll use the autoware_interpolation package of Autoware Core as an example.
include/autoware/interpolation/linear_interpolation.hpp#
namespace autoware::interpolation
{
double lerp(const double src_val, const double dst_val, const double ratio);
...
} // namespace autoware::interpolation
- Put everything under the
autoware::namespace, followed by the package name without theautoware_prefix. Example:autoware::interpolation. - Put the exported headers in
include/autoware/interpolation/. See Exporting headers. Build the library as a shared library, with the package name as the target name:
ament_auto_add_library(autoware_interpolation SHARED src/linear_interpolation.cpp ... )
- Do not make core library classes depend on
rclcpp::Node(advisory). If a class writes logs, give it anrclcpp::Logger. See Console logging.
Separate the algorithm from the node#
In a node package, keep the algorithm in its own class. The node class declares the parameters, receives and sends the messages, and calls the algorithm class. Then you can test the algorithm without ROS communication.
The autoware_simple_pure_pursuit package of Autoware Core uses this structure:
// src/simple_pure_pursuit.hpp
namespace autoware::control::simple_pure_pursuit
{
struct SimplePurePursuitParameters
{
double lookahead_gain;
double lookahead_min_distance;
...
};
class SimplePurePursuit
{
public:
explicit SimplePurePursuit(const SimplePurePursuitParameters & params);
[[nodiscard]] autoware_control_msgs::msg::Control create_control_command(
const nav_msgs::msg::Odometry & odom,
const autoware_planning_msgs::msg::Trajectory & traj) const;
...
};
} // namespace autoware::control::simple_pure_pursuit
SimplePurePursuitholds the algorithm. It gets its parameters in a struct, and it gets the messages as function arguments.SimplePurePursuitNodeinsrc/simple_pure_pursuit_node.hppinherits fromrclcpp::Node. It keeps aSimplePurePursuitobject and callscreate_control_command()from its timer callback.test/test_simple_pure_pursuit.cpptestsSimplePurePursuitwithout a node.