ROS components
Contents
ROS in AIRWiki
The page you are reading provides additional information about systems that are an integral part of ROS or that are frequently needed when running or developing a ROS system. Such systems include pieces of the ROS infrastructure (e.g., the parameter server), key ROS packages (e.g., tf) and useful ROS tools (e.g., rosbag, rviz).
Basic information about ROS and its elements is available in the ROS HOWTO page. Such page also includes links to other AIRWiki pages related to ROS.
ROS parameter server
ROS includes a parameter server that can be used to store in a centralized way all the configuration parameters of a robot system. As autonomous robots are generally a collection of hetereogeneous modules, each having its own parameters and its own methods for storing and retrieving them, this is a valuable step towards making robot software more easy to use, organized and reusable. Configuration parameters managed by the ROS parameter server are specified using the YAML language. They can be stored, modified and retrieved at runtime both from the command line and (more importantly) from ROS nodes.
The key element in using parameters is that by storing them outside of the software, you don't need to recompile it every time you change a value. Moreover (ideally, that is...) you have all the parameters at hand in the same place. The usual way to do this is to (manually) write configuration files, i.e. text files complying to a specified syntax. When the system is run, each software module parses its own config files and extracts the values of its parameters.
There are several problems with this approach:
- every software module has its own configuration files, usually located within its own directories: so you end up with config files scattered through the filesystem instead of in a single place;
- different programmers tend to use different syntaxes for their config files, so in the same robot system you often have to write configuration parameters using several different (though equivalent) ways, which leads to errors;
- worse still, the number, name, position and syntax of configuration files is not usually well documented (that's an euphemism :-) );
- finally, devising ways to let the system set or modify its own config parameters while it is running is difficult.
As previously said, the ROS parameter server is a good attempt to make the configuration mechanism standard and common to all software modules, and therefore less prone to errors and more easy to use.
The most common usage pattern for the parameters of a robot system is this:
- parameter are defined and values are set by writing the relevant configuration files;
- as soon as each module of the system is started, it parses its own configuration files and extracts parameter values.
For what concerns defining configuration parameters and setting their values, ROS offers several options:
- using rosparam from the command line, like this:
-
rosparam set parameter_name value
- putting a <param> statement into a launchfile:
-
<param name="my_param" value="my_value" />
- writing a text file with extension .yaml (say, my_file.yaml) including the parameter definition expressed in YAML syntax (
my_param: "my_value"
), then loading such file by including in a launchfile the following statement:
-
<rosparam command="load" file="$(find my_pkg)/path_within_pkg_directory/my_file.yaml" />
In the code above, my_pkg is the package that the .yaml file belongs to. Also note how $(find my_pkg) is used in place of the actual path to the package, so that the launchfile will work wherever the package is located within the filesystem. Such use of find is very handy when writing launchfiles, and is an example of substitution arguments in launchfiles.
For what concerns how nodes can retrieve parameter values from the parameter server, see AIRLab's general ROS node template.
As other elements of ROS systems, parameters have a scope. If the statement that defines a parameter (either directly or by loading a .yaml file) in a launchfile is included into a <node> block, the parameter is defined in the namespace of the node. This is the preferred way to define parameters, because it minimizes conflicts.
Finally, a word of warning. If you set a parameter and then remove the lines that set it from your configuration files, the parameter remains set until you restart roscore!. Not knowing this leads to much head-scratching as you try to figure out why your ROS system continues to behave in the wrong way even after you removed the parameter setting that led to such wrong behaviour.
Fortunately, there's an easy way to check, at any time, what parameters are set in your system: by using
rosparam list
If you want to know the current value of parameter named /full/name/of/the/param (this is the full name, starting from base namespace "/", as shown by rosparam list
), you can use
rosparam get /full/name/of/the/param
tf
From the the ROS wiki: "tf is a package that lets the user keep track of multiple coordinate frames over time. tf maintains the relationship between coordinate frames in a tree structure buffered in time, and lets the user transform points, vectors, etc between any two coordinate frames at any desired point in time". tf is a key element of ROS and, if you work on mobile robotics, there's not much that you can do with ROS before you discover that you need tf.
Basically, every time you have a non-rigid coupling in your robot (including the non-fixed coupling between robot and floor!) it's a good idea to set up a new coordinate frame (i.e., a set of Cartesian xyz axes) on the movable element. Therefore, you will need to define and use the transforms that -given the coordinates in space of a point when measured in one of the frames you defined- produce the coordinates of the same point when measured in another frame. tf lets you define, manage and use such coordinate frames and transforms. Here is a nice tutorial about how to use tf.
Transform tree
Many ROS packages assume that in your ROS system a correct transform tree has been set up. This means that there are some coordinate systems, and some relations among them, that most ROS packages assume to exist. One of the things that are badly documented in ROS is precisely what these coordinate systems are, and how they relate to each other. Although it's well hidden in the documentation, ROS does define a quasi-standard transform tree for ROS, which looks like this:
- map -> odom -> base_link -> sensor_link
In this transform tree (where each coordinate frame is the child of the one on its left):
- map is the fixed frame, sometimes also called "world". It is considered static in real space.
- odom is the frame where the robot localizes itself thanks to its own odometry system.
- base_link is a coordinate frame fixed to the base of the robot in a convenient place (dependent on the specific robot used);
- sensor_link is a frame fixed to one of the sensors mounted on the robot (a common example for this frame is base_laser_link): there will be one such frame for each sensor.
For what concerns axis orientation, ROS provides some guidelines.
Of course, the applicability of the transform tree described above to your own robot is not guaranteed, so be prepared to make alternative choices. However, this transform tree is more or less taken for granted by most of the ROS wiki (especially where the PR2 robot is concerned, although in that case odom is often called odom_combined).
The relation between coordinate frames map and odom is an especially critical one, so it's a good idea to discuss it in more detail.
Even before the robot starts moving, odom usually differs from map because its origin is defined by the robot's starting pose. That said, if odometry were perfect, the transform between map and odom would stay constant over time. In the real world, such transform is not fixed: it drifts over time due to odometry errors, and can even experience abrupt changes (e.g., the "jumps" that occur in case of wheel slippage, when the robot perceives a big displacement of its body while the actual displacement is very small).
In practice, the relation between base_link and odom assumes that odometry is perfect, i.e. that it never introduces errors. The transform between base_link and odom represents the change of pose of the robot from its initial pose to the current one, as estimated by the robot's odometry subsystem.
For this reason, a mobile robot needs a localization system with the task of continuously updating the transform between map and odom. The odometry system of the robot uses data from the odometers to update the transform from frame odom to frame base_link: this transform represents how the robot thinks to have moved with respect to the surrounding physical environment (such as the floor). The localization system uses sensor data to update the transform from frame map to frame odom in order to correct the errors in the above transform. Whenever the odom -> base_link transform imperfectly captures the movement of the robot in the real world, the localization system modifies the map -> odom transform so that the base_link frame is shifted to the correct pose with respect to the world. The presence of the odometry system is important to provide the localization subsystem with a good guess of how the robot has moved: in fact over short time intervals odometry does not introduce large errors.
You can also see the situation from the point of view of maps. ROS includes a navigation stack which includes an implementation of all the elements required to manage robot motion. Among these elements, there are a global map and a local map.
Generally a mobile robot is provided with (or builds by itself) a map of its environment, showing obstacles and other features. This is the global map, that by definition is considered fixed with respect to the map coordinate frame (hence the name of the latter). However, while the robot moves it also builds a local map, which includes the obstacles and features located in its immediate surroundings. The local map is considered fixed, by definition, with respect to the odom coordinate frame. The local map is a portion of the global map: so localization can be performed by comparing the two to find the correct alignment between them. Finding such alignment corresponds to finding the correct transform between coordinate frame map (to which the global map is affixed) and coordinate frame odom (to which is affixed the local map).
Finally, a practical note. In ROS, to establish a transform tree, you have to define and publish (on the /tf topic) the transforms from each of the component frames to its parent. Some ROS packages, like gmapping, specify in their page of the ROS wiki what transforms they require; most packages, unfortunately, do not.
Tip: to publish fixed transforms you can use the handy static_transform_publisher.
Who defines /world or /map?
One puzzling aspect of the ROS documentation is that it contains countless references to coordinate frames called /world or /map, which do not seem to be defined anywhere. The solution to this problem is simply that the /world' or /map coordinate frame is defined implicitly. In fact, in any ROS applications where tf is employed, a "default" coordinate frame is used as the starting point to define (through suitable transforms) all the coordinate frames used in the system. Such "default" frame, considered fixed, is called /map, or sometimes /world.
tf maintains a tree of coordinate frames, where each frame has one (and only one) parent and as many children as needed. The only exception to this rule is the frame that constitutes the root of the tree, which has no parent. This is accepted (and indeed necessary, given how coordinate frames are defined using tf), as long as in the system there is a single root frame: i.e., as long as every other frame in the tree has a parent. Which frame is the root, i.e. which coordinate frame acts as the "default" coordinate frame of the system, is defined implicitly. In fact, any frame F in the tf tree is there because a transform between another frame (parent) and F (child) has been specified. So, the one frame that has been used one or more times as a parent but has never been used as a child is the root of the tree: i.e., the "default" coordinate frame of the ROS application.
The name of the root coordinate frame is arbitrary; however, as ROS packages generally call it "/map", it is advisable to stick to this rule. By the way, if you used (for instance) "/map" and find out that some ROS package you are using requires instead that the name (for instance) /world is used for the fixed frame, it's easy to define a static transform that both defines the reference frame /world and makes frame /map coincident with it. This can be done by using a static_transform_publisher. The easiest way to define and run the static_transform_publisher is to insert in your launchfile (assuming your ROS application has one)
-
<node pkg="tf" type="static_transform_publisher" name="map_broadcaster" args="0 0 0 0 0 0 world map 100" />
The above statement creates a static_transform_publisher node that every 100ms broadcasts (on the /tf topic) a message specifying that the transform from /world to /map has zero translation and zero rotation. (By the way, that value of 100ms comes from the ROS wiki, which says it's a "good value".)
rviz
rviz is an extremely handy visualizer for data transmitted on ROS topics. It lets the user quickly set up visualizations for most types of ROS messages, including tf transforms, laser data, point clouds, maps, and so on.
To open rviz you need roscore running in background (execute roscore
in a different shell). Then run the following command:
rosrun rviz rviz
The rviz interface will pop up, to visualize something published by a node you will have to
- Add a type by clicking Add and selecting a display type (e.g.: poing cloud)
- Set the topic that you want to listen (the one specified in the node that is publishing)
- Set in the Global Options menu the desired Fixed Frame (typically
/map
)
rosbag
rosbag is an extremely useful tool to record on file the messages published on ROS topics. You can also use rosbag to "play back" at a later time such messages: for instance, to inspect their content with rviz.
"Stale" transforms?
Some ROS tutorials make use of bagfiles containing sensor data and transforms. If you try to play a bagfile with rosbag play
and do something with its contents (e.g., visualize the data using rviz), you can get nasty but obscure errors like this:
-
MessageFilter [target=/map ]: Dropped 100.00% of messages so far.
Such errors will prevent you from doing anything with the data being played (including visualizing it).
Depending on what ROS tools you are using, the error message can change; but the point is that data have been discarded because the transforms between reference frames in the bagfile are too old to be considered reliable. For instance in rviz you will get something like "ignoring data from the past for frame name_of_the_reference_frame".
The solution is to force ROS to use the time when the bagfile was prepared instead of the current time: i.e., to get the time from the bagfile instead of getting it from the "wall clock" (i.e., from your computer's clock). This is done by setting to true the ROS parameter called use_sim_time, stored by the parameter server. See the section about the ROS parameter server to read how you can do this.
After you have set the parameter, you have to run rosbag like this:
-
rosbag play --clock <name_of_the_bagfile>
In this way, rosbag acts as a ROS clock server, publishing time readings (on the /clock topic) that are coherent with the timestamps of the data in the bagfile. Other ROS nodes will take time readings from the /clock topic, ignoring the wall clock... and the transforms will appear to be "fresh" instead of "stale". See the Clock page of the ROS wiki if you want more information about clock and time management in ROS.
Warning: setting use_sim_time to true is something that you will only have to do while testing or debugging your system: it should not be done when ROS is used on running robots.