-
Notifications
You must be signed in to change notification settings - Fork 414
Home
This is a loose collection of best practices, conventions, and tricks for using the Robot Operating System (ROS). It builds up on the official ROS documentation and other resources and is meant as summary and overview.
Official ROS documentation:
Other References:
- Programming for Robotics - Introduction to ROS: Péter Fankhauser, ETH Zürich, March 2017,
- ROS Best Practices: Lorenz Mösenlechner, Technische Universität München, July 2012,
- ROS Best Practices: Tully Foote, Open Source Robotics Foundation, October 2014,
- ROS Design Patterns, C++ APIs, and Best Practices: Jonathan Bohren, Laboratory for Computational Sensing and Robotics, The John Hopkins University,
- ROS Answers.
In parts, the document describes opinionated best practices established within the Robotic Systems Lab and Autonomous Systems Lab, ETH Zurich and ANYbotics.
Author: Péter Fankhauser, pfankhauser@anybotics.com
Affiliation: ANYbotics
This work is conducted as part of ANYmal Research, a community to advance legged robotics.
Research other products out there already: http://www.ros.org/browse/list.php
Refer to the ROS C++ Style Guide. At the Robotic Systems Lab and ANYbotics we use an adapted version of the Google style-guide.
Refer to Standard Units of Measure and Coordinate Conventions.
Refer to http://wiki.ros.org/UnitTesting.
- The overhead of a ROS package is not large. Define separate packages wherever they make sense. Often, code can be useful in contexts other than those for which it was built.
- Avoid combining nodes that pull in mutually unneeded dependencies and are often used separately (to eliminate unnecessary build overhead).
- The package dependency graph must be acyclic, i.e. no package may depend on another that directly or indirectly depends on it.
- If programs with similar dependencies are generally used together, consider combining them into a single package.
- If some nodes have common dependencies on shared code that you do not wish to export publicly, they can be combined internally within a single package.
- Create separate packages that contain only messages, services and actions (separation of interface and implementation). Examples for separate message packages are the ros/common_msgs packages.
- Group packages in stacks.
Sources:
- When should I split my code into multiple packages, and what’s a good way to split it?
- Packages vs nodes
References:
Refer to Naming ROS Resources and REP-144: ROS Package Naming (draft).
Choose the name carefully:
- They are messy to change later.
- Package names are global to the entire ROS ecosystem.
- Try to pick names that will make sense to others who may wish to use your code.
- Package names should be specific enough to identify what the package does. Do not over scope, e.g. planner is a bad name, use wavefront_planner instead.
- Do not use “utils” or other catchalls.
- Prefixing a package name is recommended only when the package is not meant to be used more widely (e.g., packages that are specific to the StlarETH robot use the ‘starleth_’ prefix).
Adapted from ROS Best Practices: Lorenz Mösenlechner, Technische Universität München, July 2012:
- Package names are lower case.
- Packages must not contain dashes (“-”), only underscores (“_”).
- Nodes, topics, services, actions, parameters are all lower case with underscores as separator.
- Messages, services and actions are named in camel case:
geometry_msgs/PoseStamped
. - Names in a message/service/action definition are all lower case with underscores as separator:
geometry_msgs/Pose end_effector
. - Do not use the word “action” in an action definition:
Foo.action
, notFooAction.action
.
-
Use standard data types whenever possible (try to prevent
.msg
proliferation)! For example, instead of creating a customEstimatorUpdateTime.msg
, use thestd_msgs/Time.msg
definition. Another example is an empty service callTriggerComputation.msg
, use [std_srvs/Empty.srv
] (http://docs.ros.org/api/std_srvs/html/srv/Empty.html) instead. -
Do not define a new msg/srv/action definition for each topic/service/action! For example, instead of creating two definitions
LoadMapFromFile.srv
andSaveMapToFile.srv
with the same contentstring file_path —
define one type ‘ProcessFile.srv’ which can be used from both services,
~/load_map
and~/save_map
, respectively. -
Complex messages are built through composition (e.g.
geometry_msgs/PoseWithCovarianceStamped
). -
Try to avoid building messages that tend to not get completely filled out.
References:
- Create a short README.md/Wiki for each package:
- Document what the node does,
- Document topics, services and actions that are required and provided,
- Document ROS parameters and their default values,
A template for the
README.md
is provided here
- Provide launch files,
- Provide a rosinstall file.
Use this file/folder structure for a general ROS package:
package_name
|— config
|— robots
|— my_robot.yaml
|— sensors
|— velodyne.yaml
|— hokuyo_laser_range.yaml
|— include/package_name
|— Class1.hpp
|— Class2.hpp
|— launch
|— node1_name.launch
|— node2_name.launch
|— rviz
|— package_name.rviz
|— scripts
|— my_script.py
|— src
|— Class1.cpp
|— Class2.cpp
|— node1_name_node.cpp
|— node2_name_node.cpp
|— test
|— Class1Test.cpp
|— Class2Test.cpp
|— test_package_name.cpp
|— CMakeLists.txt
|— package.xml
For ROS message and service definitions use:
package_name_msgs
|— action
|— MyAction.action
|— msg
|— MyMessage.msg
|— srv
|— MyService.srv
|— CMakeLists.txt
|— package.xml
References:
Refer to ROS Patterns - Communication.
Summary:
- Use topics for publishing continuous streams of data, e.g. sensor data, continuous detection results, …
- Use services only for short calculations.
- Use actions for all longer running processes, e.g. grasping, navigation, perception, …
- Use parameters for values which are known at launch and are not likely to change during run time.
- Use dynamic parameters (
dynamic_reconfigure
) for parameter which are likely to change during run time.
Refer to http://wiki.ros.org/ROS/Patterns/Communication.
There are four main types of node handles:
- Default (public) node handle:
nh_ = ros::NodeHandle();
- Private node handle:
nh_private_ = ros::NodeHandle(“~”);
- Namespaced node handle:
nh_aslam_ = ros::NodeHandle(“aslam”);
- Global node handle:
nh_global_ = ros::NodeHandle(“/“);
(You probably shouldn’t use this ever.)
Generally you will only use the first 2 -- you could also use the namespaced node handle for separating out publishers for nodes that have many.
To explain what these do and how they should be used, let’s assume your ROS node is named ros_node
, in namespace blah
, and you are trying to look up the name topic
. Here is what they will resolve to using all 4 node handles:
/blah/topic
/blah/ros_node/topic
/blah/aslam/topic
/topic
If, instead, your try to resolve /topic
, this will skip the namespace of the node and resolve to /topic
.
These are just general guidelines, but when possible, prefer to use the following in each case:
- Subscribers - usually public node handles.
-
Publishers - usually private node handles for most output/visualization, occasionally necessary to use public for globally-used data (i.e.,
/odom
topic). - Parameters - almost always private node handle.
Never use global names. This is because they do not resolve properly when you push nodes into namespaces, and does not allow you to run more than one of your node at a time properly. Or use multiple robots on the same master. Define published topics and parameters relative to the nodes namespace:
Good: odometry
, grid_map
, cam0/camera_info
Bad: /odometry
, /grid_map
, /helicopter/cam0/camera_info
Topics should be named in the context of the node. Very simple and clear names are preferred for a easy to understand “ROS API”. Topic names not cause collision as long as they are published within the namespace of the node (see Namespace for Topics and Parameters).
In order to tell another node where to subscribe, set the topic name as ROS parameter (preferred). Alternatively, for third-party nodes, you can use the remap
tag in roslaunch.
References:
Use a hierarchical scheme for parameters, such as
camera/left/name: left_camera
camera/left/exposure: 1
camera/right/name: right_camera
camera/right/exposure: 1.1
instead of
camera_left_name: left_camera
etc. This protects parameter names from colliding and allows parameters to be access individually or as a tree. In a YAML-file, the structure would be
camera:
left:
name: left_camera
exposure: 1
right:
name: right_camera
exposure: 1.1
References:
If your node has only one or two parameters, you can set them in a launch file with the <param>
tag:
<launch>
<node pkg="my_package" type="my_node" name="my_name" output="screen">
<param name="my_parameter" value="10" />
</node>
</launch>
In general (preferred), organize the parameters in YAML-files and load them via the rosparam
-tag:
<launch>
<node pkg="my_package" type="my_node" name="my_name" output="screen">
<rosparam command="load" file="$(find my_package)/config/robots/starleth.yaml" />
<rosparam command="load" file="$(find my_package)/config/sensors/default.yaml" />
</node>
</launch>
Moreover, you can decide to always load a set of default parameters, again using the rosparam
-tag, and then allow the user of the launch file to overlay/overwrite these default settings.
An example is provided by ros_package_template_overlying_params.launch.
The user of this launch file can then do something like
<launch>
<include file="$(find ros_package_template)/launch/ros_package_template_overlying_params.launch">
<arg name="overlying_param_file" value="$(find my_package)/config/robots/custom_params.yaml" />
</include>
</launch>
Do not use command line parameters but the ROS parameter server. For parameters that are likely to change at runtime, use dynamic_reconfigure.
References:
Encourages standalone libraries with no ROS dependencies. Don’t put ROS dependencies in the core of your algorithm!
If you can develop a ROS independent library and release a parallel ROS wrapper
Refer to Using Third-Party Libraries.
- If possible, try to use libraries from Debian packages.
- Specify rosdep dependencies (tool for installing system packages).
- If you need to compile a library from source create a ROS wrapper package that downloads and compiles the package.
- Don’t use sudo in wrapper packages.
- Don’t require manual system wide installations.
- Don’t copy libraries into packages that need them.
Never call cmake by hand in a package.
Keep your dependencies clean:
- Only depend on what you need,
- Specify all dependencies,
- Do not use implicit dependencies.
If multiple runs of catkin_make
are required for your workspace to be built, something is fishy!
Do not require a specific startup order for nodes. Use waitForService
, waitForTransform
, waitForServer
, …
Refer to Roslaunch tips for large projects.
<include file=“$(find package_name)/launch/another.launch”/>
- Use rosconsole utilities for logging(
ROS_INFO
,ROS_DEBUG
, …). - Use appropriate console logging: Debug, info, warn, error, fatal.
- Provide introspection/debug topics.
Debugging through VS Code ROS debugger. Install ROS extension
In a VS Code workspace there is a directory named .vscode
.
You need to create a file launch.json
there, with the following content:
{
"configurations": [
{
"name": "ROS: Attach",
"type": "ros",
"request": "attach"
},
{
"name": "ROS: Attach to Python",
"type": "ros",
"request": "attach",
"runtime": "Python"
}
]
}
Build the workspace
catkin build --cmake-args -DCMAKE_BUILD_TYPE=Debug
You can launch the ROS nodes from wherever in your PC, either using rosrun
or roslaunch
.
Once the ROS nodes are running, navigate to the debug tab on the VS code.
Click on the ROS: Attach
button, choose if you need C++ or Python node to debug and type the ROS node name in order to select it.
You will be asked to enter your password.
To avoid computational overhead for topics which no nodes are subscribed to, check the number of subscribers with
if (publisher.getNumSubscribers() < 1) return;
-
Recording of a bag:
rosbag record <topic> <topic> …
-
Play a bag:
rosbag play foo.bag
-
Play a bag using recorded time (important when stamped data and TF was recorded):
rosbag play --clock foo.bag
Note: The
/use_sim_time
parameter must be set to true before the node is initialized.rosparam set use_sim_time true
References:
Use ros::Time
, ros::Duration
, and ros::Rate
instead of
system time.
To convert to/from messages, use eigen_conversions (or kindr- or minkindr-conversions).
Example:
Eigen::Vector3d my_super_cool_vector(1.0, 2.0, 3.0);
geometry_msgs::Point point_msg;
tf::pointEigenToMsg(my_super_cool_vector, point_msg);
super_cool_publisher_.publish(point_msg);
To go to/from TF, use tf_conversions (or also kindr- or minkindr-conversions).
Example:
Eigen::Vector3d my_super_cool_vector(1.0, 2.0, 3.0);
tf::Vector3 my_super_cool_vector_tf;
tf::vectorEigenToTF(my_super_cool_vector, my_super_cool_vector_tf);
tf::Transform transform;
transform.setOrigin(my_super_cool_vector_tf);
transform_broadcaster_.sendTransform(
tf::StampedTransform(transform, ros::Time::now(), “map”, “world”));
References:
Use the cv_bridge. This allows very easy conversions to/from ROS messages.
Example:
const stereo_msgs::DisparityImageConstPtr& msg; // We got this from a subscription callback.
cv::Mat output_image;
cv_bridge::CvImageConstPtr cv_img_ptr = cv_bridge::toCvShare(msg->image, msg);
// This is a shallow copy.
output_image = cv_img_ptr->image;
cv_bridge::CvImage image_cv_bridge;
image_cv_bridge.header.frame_id = “map”;
image_cv_bridge.image = output_image;
publisher_.publish(image_cv_bridge.toImageMsg());
References:
These are some useful CMake flags for catkin. To use them with catkin_tools, add them as arguments with
catkin config [list of your flags]
So for example
catkin config -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_ARG1=-std=c++11
Useful catkin build flags:
-
Build in C++ release mode
-DCMAKE_BUILD_TYPE=Release
-
Build with C++11
-DCMAKE_CXX_COMPILER_ARG1=-std=c++11
-
Build Eclipse projects
-G"Eclipse CDT4 - Unix Makefiles"
-
Build Eclipse projects with C++11 indexing
-G"Eclipse CDT4 - Unix Makefiles" -D__GXX_EXPERIMENTAL_CXX0X__=1 -D__cplusplus=201103L
Péter Fankhauser, pfankhauser@ethz.ch