Skip to content

Commit c86a781

Browse files
committed
Add ROS Setting up TF on Robot
1 parent 1d422a9 commit c86a781

File tree

2 files changed

+280
-1
lines changed

2 files changed

+280
-1
lines changed

Courses/ROS/Tutorials.md

Lines changed: 280 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1840,6 +1840,146 @@ catkin_metapackage()
18401840
```
18411841
5. Additional tags: Additional tags inlcude the <url> and <author> tags. These are self-explanatory.
18421842
1843+
## TF Tutorials
1844+
We assume that you know the basics of TF. Here we learn how to use TF2 package in ROS. Then we will setup TF for our custom robot.
1845+
1846+
### Static Broadcaster
1847+
First we create a custom package
1848+
```
1849+
> mkdir -p ~/tutorial_ws/src
1850+
> cd ~/tutorial_ws/src
1851+
> catkin_create_pkg learning_tf2 tf2 tf2_ros roscpp turtlesim
1852+
```
1853+
Then we write a static broadcaster node by creating a cpp file
1854+
```
1855+
> cd learning_tf2/src
1856+
> touch static_turtle_tf2_broadcaster.cpp
1857+
```
1858+
1859+
The file looks like so
1860+
```
1861+
#include <ros/ros.h>
1862+
#include <tf2_ros/static_transform_broadcaster.h>
1863+
#include <geometry_msgs/TransformStamped.h>
1864+
#include <cstdio>
1865+
#include <tf2/LinearMath/Quaternion.h>
1866+
1867+
1868+
std::string static_turtle_name;
1869+
1870+
int main(int argc, char **argv)
1871+
{
1872+
ros::init(argc,argv, "my_static_tf2_broadcaster");
1873+
if(argc != 8)
1874+
{
1875+
ROS_ERROR("Invalid number of parameters\nusage: static_turtle_tf2_broadcaster child_frame_name x y z roll pitch yaw");
1876+
return -1;
1877+
}
1878+
if(strcmp(argv[1],"world")==0)
1879+
{
1880+
ROS_ERROR("Your static turtle name cannot be 'world'");
1881+
return -1;
1882+
1883+
}
1884+
static_turtle_name = argv[1];
1885+
static tf2_ros::StaticTransformBroadcaster static_broadcaster;
1886+
geometry_msgs::TransformStamped static_transformStamped;
1887+
1888+
static_transformStamped.header.stamp = ros::Time::now();
1889+
static_transformStamped.header.frame_id = "world";
1890+
static_transformStamped.child_frame_id = static_turtle_name;
1891+
static_transformStamped.transform.translation.x = atof(argv[2]);
1892+
static_transformStamped.transform.translation.y = atof(argv[3]);
1893+
static_transformStamped.transform.translation.z = atof(argv[4]);
1894+
tf2::Quaternion quat;
1895+
quat.setRPY(atof(argv[5]), atof(argv[6]), atof(argv[7]));
1896+
static_transformStamped.transform.rotation.x = quat.x();
1897+
static_transformStamped.transform.rotation.y = quat.y();
1898+
static_transformStamped.transform.rotation.z = quat.z();
1899+
static_transformStamped.transform.rotation.w = quat.w();
1900+
static_broadcaster.sendTransform(static_transformStamped);
1901+
ROS_INFO("Spinning until killed publishing %s to world", static_turtle_name.c_str());
1902+
ros::spin();
1903+
return 0;
1904+
};
1905+
```
1906+
Lets understand the code. First we create a broadcaster object of type `StaticTransformBroadcaster`. This will be used to broadcast transforms over ROS. Then we create the transform packet, which is of type `geometry_msgs::TransformStamped`.
1907+
1908+
1909+
Instead of writing a custom broadcaster, we privilige the use of tf2_ros api for broadcasting messages. tf2_ros::static_broadcast_publisher can be used as a node to publish transforms. Normally, we invoke these nodes from roslaunch files.
1910+
1911+
### Broadcaster
1912+
```
1913+
> cd ~/tutorial_ws/src/learning_tf2/src
1914+
> touch turtle_tf2_broadcaster.cpp
1915+
```
1916+
The file looks like so
1917+
```
1918+
#include <ros/ros.h>
1919+
#include <tf2/LinearMath/Quaternion.h>
1920+
#include <tf2_ros/transform_broadcaster.h>
1921+
#include <geometry_msgs/TransformStamped.h>
1922+
#include <turtlesim/Pose.h>
1923+
1924+
std::string turtle_name;
1925+
1926+
void poseCallback(const turtlesim::PoseConstPtr& msg){
1927+
static tf2_ros::TransformBroadcaster br;
1928+
geometry_msgs::TransformStamped transformStamped;
1929+
1930+
transformStamped.header.stamp = ros::Time::now();
1931+
transformStamped.header.frame_id = "world";
1932+
transformStamped.child_frame_id = turtle_name;
1933+
transformStamped.transform.translation.x = msg->x;
1934+
transformStamped.transform.translation.y = msg->y;
1935+
transformStamped.transform.translation.z = 0.0;
1936+
tf2::Quaternion q;
1937+
q.setRPY(0, 0, msg->theta);
1938+
transformStamped.transform.rotation.x = q.x();
1939+
transformStamped.transform.rotation.y = q.y();
1940+
transformStamped.transform.rotation.z = q.z();
1941+
transformStamped.transform.rotation.w = q.w();
1942+
1943+
br.sendTransform(transformStamped);
1944+
}
1945+
1946+
int main(int argc, char** argv){
1947+
ros::init(argc, argv, "my_tf2_broadcaster");
1948+
1949+
ros::NodeHandle private_node("~");
1950+
if (! private_node.hasParam("turtle"))
1951+
{
1952+
if (argc != 2){ROS_ERROR("need turtle name as argument"); return -1;};
1953+
turtle_name = argv[1];
1954+
}
1955+
else
1956+
{
1957+
private_node.getParam("turtle", turtle_name);
1958+
}
1959+
1960+
ros::NodeHandle node;
1961+
ros::Subscriber sub = node.subscribe(turtle_name+"/pose", 10, &poseCallback);
1962+
1963+
ros::spin();
1964+
return 0;
1965+
};
1966+
```
1967+
Here, we create a node representing the parent frame, which has a callback function. The callback function is passed the sensor message that tells it the relative pose of the child frame with respect to it. Itthen calculate the transform and populates the TransformStamped object. This is then broadcast.
1968+
1969+
### Listener
1970+
The basic code for a listener is as follows
1971+
```
1972+
tf2_ros::Buffer tfBuffer;
1973+
tf2_ros::TransformListener tfListener(tfBuffer);
1974+
```
1975+
Once the listener is instantiated, it starts receiving tf2 transformations over the wire and buffers them for upto 10 seconds. The TransformListener object should be scoped to persist. If it does not persist, then its cache will be unable to fill and almost every query will fail.
1976+
1977+
To obtain transform between two frames "turtle1" and "turtle2", we invoke the following
1978+
geometry_msgs::TransformStamped transform_stamped = tfBuffer.lookupTransform("turtle2", "turtle2", ros::Time(0));
1979+
1980+
### Adding a Frame
1981+
tf2 allows you to define a local frame for each sensor, link in your system. For every frame we need to add to the system, we need to create a node. In the code for that node, we instantiate a broadcaster and TransformStamped object. We assign our new frame as child_frame_id and one of the existing frames as parent frame.
1982+
18431983
## Navigation Stack
18441984
The job of thenavigation stack is to take information from odometry and sensor streams and output velocity commands to a mobile base. A navigation stack requires that the robot should be publishing sensor messages of the correct type and have a tf transform tree in place. The navigation stack also needs to be configured for the shape and dynamics of the specific robot to perform at a high level.
18451985
@@ -1851,7 +1991,146 @@ Navigation stack assumes that robot is configured in a particular manner in orde
18511991
The white components are required components. The blue components must be created for each robot. Gray components are optional and already implemented.
18521992
18531993
1. Setup up the [transform configuration](http://wiki.ros.org/navigation/Tutorials/RobotSetup/TF)
1854-
2.
1994+
2. Sensor Information
1995+
3. Odometry Information
1996+
4. Base Controller
1997+
5. Mapping
1998+
1999+
### Transform Configuration
2000+
We will understand the basics of transform configuration. We will use these techniques to configure the transform for our turtlebot.
2001+
2002+
Lets assume we have a robot with a geometry identified in the image below
2003+
![robot geometry](images/robot_geometry.png)
2004+
2005+
2006+
To define and store the relationship between base_link and base_laser frames using tf, we need to add them to a transform tree. To create a trasnform tree for our simple example, we'll create two nodes, one for "base_link" and other for "base_laser" coordinate frames. Before creating edges, we need to assign one node as parent or reference node. Assuming base_link to be the parent, we calculate transform as
2007+
x:0.1, y:0, z:0.2.
2008+
2009+
Once this transform tree is setup, converting the laser scan received in base_laser to base_link is done by making a call to tf library.
2010+
2011+
Let's make this concrete by looking at some code
2012+
2013+
#### Code Description
2014+
Our goal is to transform the laser data to base_link frame. So how to build the transform tree.
2015+
2016+
First, we create a node responsible for publishing the transform in our system. Next we create a listener node to transform data published over ROS. This node will be capable of applying the transform to a point.
2017+
2018+
We will create a catkin package for our robot.
2019+
```
2020+
> mkdir -p ~/tutorial_ws/src
2021+
> cd ~/tutorial_ws/src/
2022+
> catkin_create_pkg robot_setup_tf roscpp tf geometry_msgs # these are dependencies for this package
2023+
```
2024+
**Broadcaster Node**</br>
2025+
Now we create the broadcaster node, which will broadcast the base_laser -> base_link transform over ROS.
2026+
```
2027+
> cd robot_setup_tf/src
2028+
> touch tf_broadcaster.cpp
2029+
```
2030+
The file snippet is shown below
2031+
```
2032+
#include <ros/ros.h>
2033+
#include <tf/transform_broadcaster.h>
2034+
2035+
int main(int argc, char** argv){
2036+
ros::init(argc, argv, "robot_tf_publisher");
2037+
ros::NodeHandle n;
2038+
2039+
ros::Rate r(100);
2040+
2041+
tf::TransformBroadcaster broadcaster;
2042+
2043+
while(n.ok()){
2044+
broadcaster.sendTransform(
2045+
tf::StampedTransform(
2046+
tf::Transform(tf::Quaternion(0, 0, 0, 1), tf::Vector3(0.1, 0.0, 0.2)),
2047+
ros::Time::now(),"base_link", "base_laser"));
2048+
r.sleep();
2049+
}
2050+
}
2051+
```
2052+
We use the `tf::TransformBroadcaster` object, to publish transforms. The `sendTransform` method requires 5 arguments which are
2053+
1. rotation transform: this is in quaternion format
2054+
2. translation transform: we determine the position of laser_link w.r.t. base_link.
2055+
3. timestamp: time at which this transform was calculated
2056+
4. parent node: name of parent node
2057+
5. child node: name of child node
2058+
2059+
2060+
**Using Transform: Listener Node**</br>
2061+
We are going to write a node that uses the published transform. It will take a point in laser_link frame as input and transform it to represent it in base_link frame.
2062+
2063+
```
2064+
> touch tf_listener.cpp
2065+
```
2066+
The file snippet is shown below
2067+
```
2068+
#include <ros/ros.h>
2069+
#include <geometry_msgs/PointStamped.h>
2070+
#include <tf/transform_listener.h>
2071+
2072+
void transformPoint(const tf::TransformListener& listener){
2073+
//we'll create a point in the base_laser frame that we'd like to transform to the base_link frame
2074+
geometry_msgs::PointStamped laser_point;
2075+
laser_point.header.frame_id = "base_laser";
2076+
2077+
//we'll just use the most recent transform available for our simple example
2078+
laser_point.header.stamp = ros::Time();
2079+
2080+
//just an arbitrary point in space
2081+
laser_point.point.x = 1.0;
2082+
laser_point.point.y = 0.2;
2083+
laser_point.point.z = 0.0;
2084+
2085+
try{
2086+
geometry_msgs::PointStamped base_point;
2087+
listener.transformPoint("base_link", laser_point, base_point);
2088+
2089+
ROS_INFO("base_laser: (%.2f, %.2f. %.2f) -----> base_link: (%.2f, %.2f, %.2f) at time %.2f",
2090+
laser_point.point.x, laser_point.point.y, laser_point.point.z,
2091+
base_point.point.x, base_point.point.y, base_point.point.z, base_point.header.stamp.toSec());
2092+
}
2093+
catch(tf::TransformException& ex){
2094+
ROS_ERROR("Received an exception trying to transform a point from \"base_laser\" to \"base_link\": %s", ex.what());
2095+
}
2096+
}
2097+
2098+
int main(int argc, char** argv){
2099+
ros::init(argc, argv, "robot_tf_listener");
2100+
ros::NodeHandle n;
2101+
2102+
tf::TransformListener listener(ros::Duration(10));
2103+
2104+
//we'll transform a point once every second
2105+
ros::Timer timer = n.createTimer(ros::Duration(1.0), boost::bind(&transformPoint, boost::ref(listener)));
2106+
2107+
ros::spin();
2108+
2109+
}
2110+
```
2111+
2112+
We need to create a `tf:TransformListener` object. It automatically subscribes to the transform message topic over ROS and manager all transform data coming in over the wire.
2113+
2114+
We create a custom function `transformPoint(const tf::TransformListener & listener)`, that takes a point in laser_link frame and transforms it to base_link frame. This function will serve as callback for ros::Timer created in main() of our program.
2115+
2116+
2117+
Suppose we have a point in the laser_link frame like `geometry_msgs::PointStamped laser_point`. We want to transform it to `base_point` which is of the same type. To perform this transform, we'll call the `transformPoint` method of the `tf::TransformListener` object. This method takes 3 arguments.
2118+
2119+
1. target frame name: base_link in our case
2120+
2. TransformPoint object to be transformed: the point we are transforming
2121+
3. TransformPoint object to hold the result:
2122+
2123+
#### Building the code
2124+
Now we modify the CMakeLists.txt file and add the following lines
2125+
```
2126+
add_executable(tf_broadcaster src/tf_broadcaster.cpp)
2127+
add_executable(tf_listener src/tf_listener.cpp)
2128+
target_link_libraries(tf_broadcaster ${catkin_LIBRARIES})
2129+
target_link_libraries(tf_listener ${catkin_LIBRARIES})
2130+
```
2131+
2132+
2133+
18552134
18562135
## References
18572136
- [Client/Service examples](https://github.com/fairlight1337/ros_service_examples/)

Courses/ROS/images/robot_geometry.png

13.3 KB
Loading

0 commit comments

Comments
 (0)