reference: http://wiki.ros.org/navigation/Tutorials/RobotSetup/Odom
- Odom Publish
消息类型nav_msgs/Odometry
robot@ubuntu:~/nav_setup_ws$ rosmsg show nav_msgs/Odometry
std_msgs/Header header#头
uint32 seq
time stamp
string frame_id
string child_frame_id #子框架
geometry_msgs/PoseWithCovariance pose #位置
geometry_msgs/Pose pose # xyz+四元数
geometry_msgs/Point position
float64 x
float64 y
float64 z
geometry_msgs/Quaternion orientation
float64 x
float64 y
float64 z
float64 w
float64[36] covariance
geometry_msgs/TwistWithCovariance twist #速度
geometry_msgs/Twist twist
geometry_msgs/Vector3 linear
float64 x
float64 y
float64 z
geometry_msgs/Vector3 angular
float64 x
float64 y
float64 z
float64[36] covariance
里程计发布:
#include <ros/ros.h>
#include <tf/transform_broadcaster.h> // 新建里程计转换,表示两个框架之间的几何关系
#include <nav_msgs/Odometry.h>
int main(int argc, char** argv)
{
ros::init(argc,argv,"Odom_publisher");
ros::NodeHandle n;
ros::Publisher odom_pub = n.advertise<nav_msgs::Odometry>("odom",1000);
tf::TransformBroadcaster odom_broadcaster;
double x = 0;
double y = 0 ;
double th = 0;
double vx = 0.1;
double vy = -0.1;
double vth = 0.1;
ros::Time current_time, last_time;
current_time = ros::Time::now();
last_time = ros::Time::now();
ros::Rate r(1);
while(ros::ok())
{
ros::spinOnce(); // check for incoming messages
current_time = ros::Time::now();
//compute odometry in a typical way given the velocities of the robot
//典型的里程计计算方法
double dt = (current_time - last_time).toSec();
double delta_x = ( vx * cos(th) - vy * sin(th) ) * dt;
double delta_y = ( vx * sin(th) + vy * cos(th) ) * dt;
double delta_th = vth * dt ;
x += delta_x;
y += delta_y;
th += delta_th;
//since all odometry is 6DOF we'll need a quaternion created from yaw
//还记得rpy 与xyz的关系系吗?
geometry_msgs::Quaternion odom_quat = tf::createQuaternionMsgFromYaw(th);
//first, we'll publish the transform over tf
geometry_msgs::TransformStamped odom_trans;
odom_trans.header.stamp = current_time;
odom_trans.header.frame_id = "odom";
odom_trans.child_frame_id = "base_link";
//odom data from original world frame
odom_trans.transform.translation.x = x;
odom_trans.transform.translation.y = y;
odom_trans.transform.translation.z = 0.0;
odom_trans.transform.rotation = odom_quat;
//send the transform
odom_broadcaster.sendTransform(odom_trans);
//next, we'll publish the odometry message over ROS
nav_msgs::Odometry odom;
odom.header.stamp = current_time;
odom.header.frame_id = "odom";
//set the position
odom.pose.pose.position.x = x;
odom.pose.pose.position.y = y;
odom.pose.pose.position.z = 0.0;
odom.pose.pose.orientation = odom_quat;
//set the velocity
odom.child_frame_id = "base_link";
odom.twist.twist.linear.x = vx;
odom.twist.twist.linear.y = vy;
odom.twist.twist.angular.z = vth;
//publish the message
odom_pub.publish(odom);
last_time = current_time;
r.sleep();
}
}
CMakeLists.txt
cmake_minimum_required(VERSION 2.8.3)
project(odom_publish)
## Compile as C++11, supported in ROS Kinetic and newer
# add_compile_options(-std=c++11)
## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
nav_msgs
roscpp
rospy
std_msgs
tf
)
catkin_package(
INCLUDE_DIRS include
LIBRARIES odom_publish
CATKIN_DEPENDS nav_msgs roscpp rospy std_msgs tf
DEPENDS system_lib
)
include_directories( include ${catkin_INCLUDE_DIRS})
add_executable(odom_publisher src/odom_publish.cpp)
target_link_libraries(odom_publisher ${catkin_LIBRARIES} )
tf_setup
http://wiki.ros.org/navigation/Tutorials/RobotSetup/TF
当我们需要将激光的数据转换到机器人本体的坐标系时,更省事的办法是直接将设置两个坐标系之间的tf变换。
base_link(parent) —-> base_laser(child)
tf_broadcaster.cpp 创建base_laser 坐标系并创建两者之间变换关系
#include <ros/ros.h>
#include <tf/transform_broadcaster.h>
int main(int argc,char** argv)
{
ros::init(argc,argv,"robot_tf_publisher");
ros::NodeHandle n;
ros::Rate r(10);
tf::TransformBroadcaster broadcaster;
while(ros::ok())
{
broadcaster.sendTransform(tf::StampedTransform(tf::Transform(tf::Quaternion(0,0,0,1),tf::Vector3(0.1,0,0.2)),ros::Time::now(),"base_link","base_laser"));
r.sleep();
}
}
Transform_broadcaster 将会自动创建/tf 话题,创建的坐标系框架自动添加在tf tree中
tf_listener.cpp
#include <ros/ros.h>
#include <geometry_msgs/PointStamped.h>
#include <tf/transform_listener.h>
#include <ros/ros.h>
#include <geometry_msgs/PointStamped.h>
#include <tf/transform_listener.h>
void transformPoint(const tf::TransformListener& listener){
//we'll create a point in the base_laser frame that we'd like to transform to the base_link frame
/*
The "Stamped" on the end of the message name just means that it includes a header, allowing us to associate both a timestamp and a frame_id with the message.
*/
geometry_msgs::PointStamped laser_point;
laser_point.header.frame_id = "base_laser";
//we'll just use the most recent transform available for our simple example
/*
ros::Time() which is a special time value that allows us to ask the TransformListener for the latest available transform.
*/
laser_point.header.stamp = ros::Time();
//just an arbitrary point in space
laser_point.point.x = 1.0;
laser_point.point.y = 0.2;
laser_point.point.z = 0.0;
/*
Now that we have the point in the "base_laser" frame we want to transform it into the "base_link" frame. To do this, we'll use the TransformListener object, and call transformPoint() with three arguments: the name of the frame we want to transform the point to ("base_link" in our case), the point we're transforming, and storage for the transformed point. So, after the call to transformPoint(), base_point holds the same information as laser_point did before only now in the "base_link" frame.
*/
try{
geometry_msgs::PointStamped base_point;
listener.transformPoint("base_link", laser_point, base_point);
ROS_INFO("base_laser: (%.2f, %.2f. %.2f) -----> base_link: (%.2f, %.2f, %.2f) at time %.2f",
laser_point.point.x, laser_point.point.y, laser_point.point.z,
base_point.point.x, base_point.point.y, base_point.point.z, base_point.header.stamp.toSec());
}
catch(tf::TransformException& ex){
ROS_ERROR("Received an exception trying to transform a point from \"base_laser\" to \"base_link\": %s", ex.what());
}
}
int main(int argc, char** argv){
ros::init(argc, argv, "robot_tf_listener");
ros::NodeHandle n;
/*
A TransformListener object automatically subscribes to the transform message topic over ROS and manages all transform data coming in over the wire.
*/
tf::TransformListener listener(ros::Duration(10));
//we'll transform a point once every second
ros::Timer timer = n.createTimer(ros::Duration(1.0), boost::bind(&transformPoint, boost::ref(listener)));
ros::spin();
}
Transform_listener 自动监听tf tree。自动订阅到 /tf 话题
此外ros提供了静态变换的包,方便调用
<!--static tf-->
<node pkg=”static_transform_publisher” name=”static_transform_publisher” args=”0.2 0 0.2 0 0 0 /map /odom 100”/>
<node pkg=”static_transform_publisher” name=”static_transform_publisher” args=”0.2 0 0.2 0 0 0 base_link laser 100”/>
命令格式
命令的格式如下:
static_transform_publisher x y z yaw pitch roll frame_id child_frame_id period_in_ms
static_transform_publisher x y z qx qy qz qw frame_id child_frame_id period_in_ms
遇到一个包时,可以通过rosrun tf view_frames 查看默认的坐标系设置情况.
该包的源码https://github.com/ros/geometry/blob/indigo-devel/tf/src/static_transform_publisher.cpp
#include <cstdio>
#include "tf/transform_broadcaster.h"
class TransformSender
{
public:
ros::NodeHandle node_;
//constructor
TransformSender(double x, double y, double z, double yaw, double pitch, double roll, ros::Time time, const std::string& frame_id, const std::string& child_frame_id)
{
tf::Quaternion q;
q.setRPY(roll, pitch,yaw);
transform_ = tf::StampedTransform(tf::Transform(q, tf::Vector3(x,y,z)), time, frame_id, child_frame_id );
};
TransformSender(double x, double y, double z, double qx, double qy, double qz, double qw, ros::Time time, const std::string& frame_id, const std::string& child_frame_id) :
transform_(tf::Transform(tf::Quaternion(qx,qy,qz,qw), tf::Vector3(x,y,z)), time, frame_id, child_frame_id){};
//Clean up ros connections
~TransformSender() { }
//A pointer to the rosTFServer class
tf::TransformBroadcaster broadcaster;
// A function to call to send data periodically
void send (ros::Time time) {
transform_.stamp_ = time;
broadcaster.sendTransform(transform_);
};
private:
tf::StampedTransform transform_;
};
int main(int argc, char ** argv)
{
//Initialize ROS
ros::init(argc, argv,"static_transform_publisher", ros::init_options::AnonymousName);
if(argc == 11)
{
ros::Duration sleeper(atof(argv[10])/1000.0);
if (strcmp(argv[8], argv[9]) == 0)
ROS_FATAL("target_frame and source frame are the same (%s, %s) this cannot work", argv[8], argv[9]);
TransformSender tf_sender(atof(argv[1]), atof(argv[2]), atof(argv[3]),
atof(argv[4]), atof(argv[5]), atof(argv[6]), atof(argv[7]),
ros::Time() + sleeper, //Future dating to allow slower sending w/o timeout
argv[8], argv[9]);
while(tf_sender.node_.ok())
{
tf_sender.send(ros::Time::now() + sleeper);
ROS_DEBUG("Sending transform from %s with parent %s\n", argv[8], argv[9]);
sleeper.sleep();
}
return 0;
}
else if (argc == 10)
{
ros::Duration sleeper(atof(argv[9])/1000.0);
if (strcmp(argv[7], argv[8]) == 0)
ROS_FATAL("target_frame and source frame are the same (%s, %s) this cannot work", argv[7], argv[8]);
TransformSender tf_sender(atof(argv[1]), atof(argv[2]), atof(argv[3]),
atof(argv[4]), atof(argv[5]), atof(argv[6]),
ros::Time() + sleeper, //Future dating to allow slower sending w/o timeout
argv[7], argv[8]);
while(tf_sender.node_.ok())
{
tf_sender.send(ros::Time::now() + sleeper);
ROS_DEBUG("Sending transform from %s with parent %s\n", argv[7], argv[8]);
sleeper.sleep();
}
return 0;
}
else
{
printf("A command line utility for manually sending a transform.\n");
printf("It will periodicaly republish the given transform. \n");
printf("Usage: static_transform_publisher x y z yaw pitch roll frame_id child_frame_id period(milliseconds) \n");
printf("OR \n");
printf("Usage: static_transform_publisher x y z qx qy qz qw frame_id child_frame_id period(milliseconds) \n");
printf("\nThis transform is the transform of the coordinate frame from frame_id into the coordinate frame \n");
printf("of the child_frame_id. \n");
ROS_ERROR("static_transform_publisher exited due to not having the right number of arguments");
return -1;
}
};
发布消息,导航包只能使用PointCloud 和LaserScan 消息格式
robot@ubuntu:~/nav_setup_ws/src/laser_pub/src$ rosmsg show PointCloud
[sensor_msgs/PointCloud]:
std_msgs/Header header
uint32 seq
time stamp
string frame_id
geometry_msgs/Point32[] points
float32 x
float32 y
float32 z
sensor_msgs/ChannelFloat32[] channels
string name
float32[] values
robot@ubuntu:~/nav_setup_ws/src/laser_pub/src$ rosmsg show LaserScan
[sensor_msgs/LaserScan]:
std_msgs/Header header
uint32 seq
time stamp
string frame_id
float32 angle_min
float32 angle_max
float32 angle_increment
float32 time_increment
float32 scan_time
float32 range_min
float32 range_max
float32[] ranges
float32[] intensities
laser_pub.cpp
#include <ros/ros.h>
#include <sensor_msgs/LaserScan.h>
int main(int argc, char** argv)
{
ros::init(argc,argv,"laser_publisher");
ros::NodeHandle n;
ros::Publisher scan_pub = n.advertise<sensor_msgs::LaserScan>("scan",50);
unsigned int num_readings = 100;
double laser_frequency = 40;
double ranges[num_readings];
double intensities[num_readings];
int count=0;
ros::Rate(1.0);
while(ros::ok())
{
//generate some fake data for our laser scan
for(unsigned int i = 0; i < num_readings; ++i){
ranges[i] = count;
intensities[i] = 100 + count;
}
ros::Time scan_time = ros::Time::now();
//populate the LaserScan message
sensor_msgs::LaserScan scan;
scan.header.stamp = scan_time;
scan.header.frame_id = "laser_frame";
scan.angle_min = -1.57;
scan.angle_max = 1.57;
scan.angle_increment = 3.14 / num_readings;
scan.time_increment = (1 / laser_frequency) / (num_readings);
scan.range_min = 0.0;
scan.range_max = 100.0;
scan.ranges.resize(num_readings);
scan.intensities.resize(num_readings);
for(unsigned int i = 0; i < num_readings; ++i){
scan.ranges[i] = ranges[i];
scan.intensities[i] = intensities[i];
}
scan_pub.publish(scan);
++count;
r.sleep();
}
}
pointcloud_pub.cpp
#include <ros/ros.h>
#include <sensor_msgs/PointCloud.h>
int main(int argc, char** argv){
ros::init(argc, argv, "point_cloud_publisher");
ros::NodeHandle n;
ros::Publisher cloud_pub = n.advertise<sensor_msgs::PointCloud>("cloud", 50);
unsigned int num_points = 100;
int count = 0;
ros::Rate r(1.0);
while(n.ok()){
sensor_msgs::PointCloud cloud;
cloud.header.stamp = ros::Time::now();
cloud.header.frame_id = "sensor_frame";
//数组可以resize指定空间大小
cloud.points.resize(num_points);
//we'll also add an intensity channel to the cloud
cloud.channels.resize(1);
cloud.channels[0].name = "intensities";
cloud.channels[0].values.resize(num_points);
//generate some fake data for our point cloud
for(unsigned int i = 0; i < num_points; ++i){
cloud.points[i].x = 1 + count;
cloud.points[i].y = 2 + count;
cloud.points[i].z = 3 + count;
cloud.channels[0].values[i] = 100 + count;
}
cloud_pub.publish(cloud);
++count;
r.sleep();
}
}
利用move_base 发送目标位置点移动
/*********************************************************************
*
* Software License Agreement (BSD License)
*
* Copyright (c) 2008, Willow Garage, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Willow Garage, Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* Author: Eitan Marder-Eppstein
*
* For a discussion of this tutorial, please see:
* http://pr.willowgarage.com/wiki/navigation/Tutorials/SendingSimpleGoals
*********************************************************************/
#include <ros/ros.h>
#include <move_base_msgs/MoveBaseAction.h>
#include <actionlib/client/simple_action_client.h>
#include <tf/transform_datatypes.h>
#include <boost/thread.hpp>
typedef actionlib::SimpleActionClient<move_base_msgs::MoveBaseAction> MoveBaseClient;
void spinThread(){
ros::spin();
}
int main(int argc, char** argv){
ros::init(argc, argv, "simple_navigation_goals");
ros::NodeHandle n;
boost::thread spin_thread = boost::thread(boost::bind(&spinThread));
MoveBaseClient ac("pose_base_controller");
//要求与movebase server 名称一致
//give some time for connections to register
sleep(2.0);
move_base_msgs::MoveBaseGoal goal;
//we'll send a goal to the robot to move 2 meters forward
goal.target_pose.header.frame_id = "base_link";
goal.target_pose.header.stamp = ros::Time::now();
goal.target_pose.pose.position.x = 2.0;
goal.target_pose.pose.position.y = 0.2;
goal.target_pose.pose.orientation = tf::createQuaternionMsgFromYaw(M_PI);
ROS_INFO("Sending goal");
ac.sendGoal(goal);
ac.waitForResult();
if(ac.getState() == actionlib::SimpleClientGoalState::SUCCEEDED)
ROS_INFO("Hooray, the base moved 2 meters forward");
else
ROS_INFO("The base failed to move forward 2 meters for some reason");
return 0;
}
}
python version
https://github.com/markwsilliman/turtlebot/blob/master/go_to_specific_point_on_map.py
#!/usr/bin/env python
'''
Copyright (c) 2015, Mark Silliman
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
# TurtleBot must have minimal.launch & amcl_demo.launch
# running prior to starting this script
# For simulation: launch gazebo world & amcl_demo prior to run this script
import rospy
from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
import actionlib
from actionlib_msgs.msg import *
from geometry_msgs.msg import Pose, Point, Quaternion
class GoToPose():
def __init__(self):
self.goal_sent = False
# What to do if shut down (e.g. Ctrl-C or failure)
rospy.on_shutdown(self.shutdown)
# Tell the action client that we want to spin a thread by default
self.move_base = actionlib.SimpleActionClient("move_base", MoveBaseAction)
rospy.loginfo("Wait for the action server to come up")
# Allow up to 5 seconds for the action server to come up
self.move_base.wait_for_server(rospy.Duration(5))
def goto(self, pos, quat):
# Send a goal
self.goal_sent = True
goal = MoveBaseGoal()
goal.target_pose.header.frame_id = 'map'
goal.target_pose.header.stamp = rospy.Time.now()
goal.target_pose.pose = Pose(Point(pos['x'], pos['y'], 0.000),
Quaternion(quat['r1'], quat['r2'], quat['r3'], quat['r4']))
# Start moving
self.move_base.send_goal(goal)
# Allow TurtleBot up to 60 seconds to complete task
success = self.move_base.wait_for_result(rospy.Duration(60))
state = self.move_base.get_state()
result = False
if success and state == GoalStatus.SUCCEEDED:
# We made it!
result = True
else:
self.move_base.cancel_goal()
self.goal_sent = False
return result
def shutdown(self):
if self.goal_sent:
self.move_base.cancel_goal()
rospy.loginfo("Stop")
rospy.sleep(1)
if __name__ == '__main__':
try:
rospy.init_node('nav_test', anonymous=False)
navigator = GoToPose()
# Customize the following values so they are appropriate for your location
position = {'x': 1.22, 'y' : 2.56}
quaternion = {'r1' : 0.000, 'r2' : 0.000, 'r3' : 0.000, 'r4' : 1.000}
rospy.loginfo("Go to (%s, %s) pose", position['x'], position['y'])
success = navigator.goto(position, quaternion)
if success:
rospy.loginfo("Hooray, reached the desired pose")
else:
rospy.loginfo("The base failed to reach the desired pose")
# Sleep to give the last log messages time to be sent
rospy.sleep(1)
except rospy.ROSInterruptException:
rospy.loginfo("Ctrl-C caught. Quitting")