-
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:
- 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 Legged Robotics Group from the Autonomous Systems Lab, ETH Zurich.
Author: Péter Fankhauser, [email protected]
Affiliation: Autonomous Systems Lab, ETH Zurich
Research other products out there already: http://www.ros.org/browse/list.php
Refer to the ROS C++ Style Guide. At the Autonomous Systems Lab we use 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).
- 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.
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.
Never use global names. Define published topics and parameters relative to the nodes namespace. This helps for overview and avoids collisions. A good approach is to setup the node handle with a ~
(tilde) like this
ros::NodeHandle nodeHandle("~");
where the tilde refers to the current node’s namespace. This will result in:
/my_node/foo
In a launch-file, this corresponds to the name
parameter:
<launch>
<node pkg=“my_package” type=“my_node” name=“my_name” output="screen" />
</launch>
or with rosrun
rosrun my_package my_node __name:=my_name
This way you can run multiple instances of the same node without collision.
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>
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
http://courses.csail.mit.edu/6.141/spring2012/pub/lectures/Lec06-ROS.pdf
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 build 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.
TODO.
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 (rosbag play --clock …
sets it for you).rosparam set use_sim_time true
References:
Use ros::Time
, ros::Duration
, and ros::Rate
instead of
system time.
Péter Fankhauser, [email protected]