1.工作空间
目录结构
- src:代码空间(Source Space)存放工程文件的文件夹。
- build:编译空间(Build Space)编译产生的中间文件(很少打开)。
- devel:开发空间(Development Space)编译完成的可执行文件和链接、环境配置的脚本。
创建工作空间
#创建工作空间
mkrdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
#编译工作空间
cd ~/catkin_ws
catkin_make
#设置环境变量
sudo vim ~/.zshrc
source ~/catkin_ws/devel/setup.zsh
source ~/.zshrc
#检查环境变量
echo $ROS_PACKAGE_PATH
#查看所有的变量
env | grep ros
创建功能包和编译代码
#创建功能包
cd ~/catkin_ws/src
catkin_create_pkg myPackName std_msgs rospy roscpp
#编译功能包
cd ~/catkin_ws
catkin_make
source ~/catkin_ws/devel/setup.bash
- CMakeLists.txt:编译功能包的编译选项
- package.xml:管理功能包信息的文件(包括依赖等信息)
2.ROS通信编程
话题通信编程
#include <ros/ros.h>
#include <std_msgs/String.h>
int main(int argc,char **argv)
{
//ROS节点初始化
ros::init(argc,argv,"talker");
//创建节点句柄
ros::NodeHandle n;
//创建一个Publisher,发布名为chatter的topic,消息类型为std_msgs::String,缓存空间1000
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter",1000);
//设置循环的频率10Hz
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok()) //监测但前节点是否正常运行
{
//初始化消息
std_msgs::String msg;
std::stringstream ss;
ss << "hello ros" << count;
msg.data = ss.str();
//终端打印变量
ROS_INFO("I publish a message: %s",msg.data.c_str());
//发布消息
chatter_pub.publish(msg);
//循环等待回调函数
ros::spinOnce();
//按照巡回频率延时
loop_rate.sleep();
++count;
}
return 0;
}
#include <ros/ros.h>
#include <std_msgs/String.h>
//受到订阅的消息后,会进入消息回调函数
void chatterCallBack(const std_msgs::String::ConstPtr& msg)
{
//将收到的消息打印出来
ROS_INFO("I hear a message: %s",msg->data.c_str());
}
int main(int argc,char **argv)
{
//初始化ros节点
ros::init(argc,argv,"listener");
//创建节点句柄
ros::NodeHandle n;
//创建一个订阅者,订阅名为chatter的topic,消息类型为std_msgs::String,缓存空间1000,回调函数为chatterCallBack
ros::Subscriber sub = n.subscribe("chatter",1000,chatterCallBack);
//循环并等待回调函数
ros::spin();
return 0;
}
- 添加编译配置文件CMake_Lists.txt,并编译
include_directories(include ${catkin_INCLUDE_DIRS}) #包含ROS路径的依赖(ros的头文件)
add_executable(talker src/talker.cpp) #可执行文件名 文件目录
target_link_libraries(talker ${catkin_LIBRARIES}) #把库文件链接到可执行文件中
add_executable(listener src/listener.cpp) #可执行文件名 文件目录
target_link_libraries(listener ${catkin_LIBRARIES}) #把库文件链接到可执行文件中
roscore
rosrun myPackName talker
rosrun myPackName listener
自定义话题消息
- 在包下面创建一个msg文件加,再在文件夹里创建一个.msg文件,我这里创建了student.msg
string name
bool sex
Float32 height
bool male = true
bool female = false
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<!-- 部分ROS版本中exec_depend需改成run_depend -->
#添加功能包
find_package(catkin REQUIRED COMPONENTS
...
message_generation
)
#添加具体依赖
catkin_package(
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
)
#添加具体msg文件
add_message_files(FILES student.msg)
generate_messages(DEPENDENCIES std_msgs)
服务请求编程
- 在包下面创建一个srv文件加,再在文件夹里创建一个.srv文件,我这里创建了AddTwoInts.srv
int64 a
int64 b
---
int64 sum
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
<!-- 部分ROS版本中exec_depend需改成run_depend -->
- 修改编译配置文件CMake_Lists.txt,并编译工程
#添加功能包
find_package(catkin REQUIRED COMPONENTS
...
message_generation
)
#添加具体依赖
catkin_package(
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
)
#添加具体文件
add_service_files(FILES AddTwoInts.srv)
#include <ros/ros.h>
#include "my_first_pack/Add.h"
//service回调函数,输入参数req,输出参赛res
bool add(my_first_pack::Add::Request &req,
my_first_pack::Add::Response &res)
{
//将输入参数中的请求数据相加
res.sum = req.a + req.b;
ROS_INFO("request:a=%ld,b=%ld response:%ld",(long int)req.a,(long int)req.b,(long int)res.sum);
return true;
}
int main(int argc,char **argv)
{
//ROS节点初始化
ros::init(argc,argv,"addfunction_server");
//创建节点句柄
ros::NodeHandle n;
//创建一个名为addfunction的服务,注册回调函数add()
ros::ServiceServer service = n.advertiseService("addfunction",add);
//循环等待回调函数
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
#include <cstdlib>
#include <ros/ros.h>
#include "my_first_pack/Add.h"
int main(int argc,char** argv)
{
//ROS节点初始化
ros::init(argc,argv,"addfunction_client");
//从终端命令行获取两个加数
if(argc != 3)
{
ROS_INFO("usage: addfunction_client a b");
return 1;
}
//创建节点句柄
ros::NodeHandle n;
//创建一个client,请求addfunction_server,service消息类型是my_first_pack::Add
ros::ServiceClient client = n.serviceClient<my_first_pack::Add>("addfunction");
//创建my_first_pack::Add典型的服务消息
my_first_pack::Add srv;
srv.request.a = atoll(argv[1]); //把字符串转换成长长整型数
srv.request.b = atoll(argv[2]);
//发布服务请求,等待加法运算应答结果
if(client.call(srv))
{
ROS_INFO("Sum: %ld",(long int)srv.response.sum);
}else{
ROS_ERROR("Failed to call service Add");
return 1;
}
return 0;
}
add_executable(server src/server.cpp) #可执行文件名 文件目录
target_link_libraries(server ${catkin_LIBRARIES}) #把库文件链接到可执行文件中
add_dependencies(server ${PROJECT_NAME}_gencpp) #生产自定义请求类型,添加依赖到可执行程序中
add_executable(client src/client.cpp) #可执行文件名 文件目录
target_link_libraries(client ${catkin_LIBRARIES}) #把库文件链接到可执行文件中
add_dependencies(client ${PROJECT_NAME}_gencpp)
roscore
rosrun myPackName server
rosrun myPackName client 5 6
动作编程
- 在包下面创建一个action文件加,再在文件夹里创建一个.action文件,我这里创建了DoDishes.action
uint32 ds_id #定义目标信息
---
uint32 total_cleaned #定义结果信息
---
float32 percent_complete #定义周期反馈消息
<build_depend>actionlib</build_depend>
<build_depend>actionlib_msgs</build_depend>
<exec_depend>actionlib</exec_depend>
<exec_depend>actionlib_msgs</exec_depend>
#添加功能包
find_package(catkin REQUIRED COMPONENTS
...
actionlib_msgs
actionlib
)
#添加具体文件
add_action_files(DIRECTORY action FILES DoDishes.action)
generate_messages(DEPENDENCIES actionlib_msgs)
- 创建服务器程序DoDishes_server.cpp
#include <actionlib/server/simple_action_server.h>
#include "my_first_pack/DoDishesAction.h"
typedef actionlib::SimpleActionServer<my_first_pack::DoDishesAction> Server;
// 收到action的goal后调用该回调函数
void execute(const my_first_pack::DoDishesGoalConstPtr& goal,
actionlib::SimpleActionServer<my_first_pack::DoDishesAction> *as)
{
ros::Rate r(1);
my_first_pack::DoDishesFeedback freedback;
ROS_INFO("Dishwasher %d is working.",goal->ds_id);
// 模拟洗盘子的进度,并且按照1hz的频率发布进度feedback
for(int i=1;i<=10;i++)
{
freedback.percent_complete = i*10;
as->publishFeedback(freedback);
r.sleep();
}
// 当action完成后,向客户端返回结果
ROS_INFO("Dishwasher %d finish working.",goal->ds_id);
as->setSucceeded();
}
int main(int argc,char**argv)
{
ros::init(argc,argv,"do_dishes_server");
ros::NodeHandle n;
//定义一个服务器
Server server(n,"do_dishes",boost::bind(&execute,_1,&server),false);
//服务器开始运行
server.start();
ros::spin();
return 0;
}
- 创建客户端程序DoDishes_client.cpp
#include <actionlib/client/simple_action_client.h>
#include "my_first_pack/DoDishesAction.h"
typedef actionlib::SimpleActionClient<my_first_pack::DoDishesAction> Client;
//完成
void doneCb(const actionlib::SimpleClientGoalState& state,
const my_first_pack::DoDishesResultConstPtr& result)
{
ROS_INFO("Yay! The dishes are clean now");
ros::shutdown();
}
//开始
void activeCb()
{
ROS_INFO("Goal just went active");
}
// 进度feedback
void feedbackCb(const my_first_pack::DoDishesFeedbackConstPtr& feedback)
{
ROS_INFO(" percent_complete : %f ",feedback->percent_complete);
}
int main(int argc,char **argv)
{
ros::init(argc,argv,"do_dishes_client");
// 定义一个客户端
Client client("do_dishes",true);
// 等待服务端
ROS_INFO("Waiting for action server to start.");
client.waitForServer();
ROS_INFO("Action server started, sending goal.");
//创建一个action的goal
my_first_pack::DoDishesGoal goal;
goal.ds_id = 1;
//发送action的goal给服务器端,并且设置回调函数
client.sendGoal(goal,&doneCb,&activeCb,&feedbackCb);
ros::spin();
return 0;
}
add_executable(DoDishes_client src/DoDishes_client.cpp) #可执行文件名 文件目录
target_link_libraries(DoDishes_client ${catkin_LIBRARIES}) #把库文件链接到可执行文件中
add_dependencies(DoDishes_client ${PROJECT_NAME}_gencpp) #生产自定义请求类型,添加依赖到可执行程序中
add_executable(DoDishes_server src/DoDishes_server.cpp) #可执行文件名 文件目录
target_link_libraries(DoDishes_server ${catkin_LIBRARIES}) #把库文件链接到可执行文件中
add_dependencies(DoDishes_server ${PROJECT_NAME}_gencpp) #生产自定义请求类型,添加依赖到可执行程序中
roscore
rosrun myPackName DoDishes_client
rosrun myPackName DoDishes_server
3.ROS中关键组件
Launch文件
<launch>
<node pkg="节点所在的功能包名称" type="节点的可执行文件名称" name="节点运行时的名称"/>
</launch>
<!--
output:打印输出位置(screen)
respawn:失效后是否重新启动
required:是否把此节点设置为必须的节点
ns:命名空间
args:输入参数
-->
<!-- 参数设置:单个参数设置、多个参数设置 -->
<param name="参数名" value="参数值"/>
<rosparam file="params.yaml" command="load" ns="params"/>
<!-- 局部变量参数设置与调用 -->
<arg name="参数名" value="参数值" />
<param name="foo" value="$(arg arg-name)">
<remap from="原名" to="映射后的名称" />
<include file="$(dirname)other.launch"/>
TF坐标变换
- 创建小乌龟广播文件turtle_tf_broadcaster.cpp。
#include <ros/ros.h>
#include <tf/transform_broadcaster.h>
#include <turtlesim/Pose.h>
std::string turtle_name;
void poseCallback(const turtlesim::PoseConstPtr& msg)
{
//定义tf广播器
static tf::TransformBroadcaster bc;
//根据乌龟当前的姿态,创建相对于世界坐标系的坐标变换
tf::Transform mtf;
mtf.setOrigin(tf::Vector3(msg->x,msg->y,0.0));
tf::Quaternion q;
q.setRPY(0,0,msg->theta);
mtf.setRotation(q);
//发布坐标变换
bc.sendTransform(tf::StampedTransform(mtf,ros::Time::now(),"world",turtle_name));
}
int main(int argc,char** argv)
{
//初始化节点
ros::init(argc,argv,"my_tf_broadcaster");
if(argc != 2)
{
ROS_ERROR("need turtle name as argument");
return -1;
}
turtle_name = argv[1];
// 订阅乌龟的pose信息
ros::NodeHandle node;
ros::Subscriber sub = node.subscribe(turtle_name + "/pose",10,&poseCallback);
ros::spin();
return 0;
}
- 创建小乌龟监听文件turtle_tf_listener.cpp
#include <ros/ros.h>
#include <tf/transform_listener.h>
#include <geometry_msgs/Twist.h>
#include <turtlesim/Spawn.h>
int main(int argc,char** argv)
{
//初始化节点
ros::init(argc,argv,"my_tf_listener");
ros::NodeHandle node;
//通过服务调用,产生第二只乌龟turtle2
ros::service::waitForService("spawn");
ros::ServiceClient add_turtle = node.serviceClient<turtlesim::Spawn>("spawn");
turtlesim::Spawn srv;
//发布服务请求,等待应答结果
add_turtle.call(srv);
//定义turtle2的速度控制发布器
ros::Publisher turtle_vel = node.advertise<geometry_msgs::Twist>("turtle2/cmd_vel",10);
node.advertise<geometry_msgs::Twist>("turtle2/cmd_vel",10);
//tf监听器
tf::TransformListener listener;
ros::Rate rate(10.0);
while (node.ok())
{
tf::StampedTransform mtf;
try
{
//查找turtle2与turtle1的坐标变换
listener.waitForTransform("/turtle2","/turtle1",ros::Time(0),ros::Duration(3.0));
listener.lookupTransform("/turtle2","/turtle1",ros::Time(0),mtf);
}
catch(const tf::TransformException &ex)
{
ROS_ERROR("%s",ex.what());
ros::Duration(1.0).sleep();
continue;
}
//根据turtle1和turtle2之间的坐标变换,计算turtle2需要运动的线速度和角速度
//并发布速度控制指令,使turtle2向turtle1移动
geometry_msgs::Twist vel_msg;
vel_msg.angular.z = 4.0 * atan2(mtf.getOrigin().y(),mtf.getOrigin().x());
vel_msg.linear.x = 0.5 * sqrt(pow(mtf.getOrigin().x(),2) + pow(mtf.getOrigin().y(),2));
turtle_vel.publish(vel_msg);
rate.sleep();
}
return 0;
}
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
...
roscpp
tf
turtlesim
geometry_msgs
)
add_executable(turtle_tf_broadcaster src/turtle_tf_broadcaster.cpp)
target_link_libraries(turtle_tf_broadcaster ${catkin_LIBRARIES})
add_executable(turtle_tf_listener src/turtle_tf_listener.cpp)
target_link_libraries(turtle_tf_listener ${catkin_LIBRARIES})
<launch>
<!-- 海龟仿真器 -->
<node pkg="turtlesim" type="turtlesim_node" name="sim"/>
<!-- 键盘控制 -->
<node pkg="turtlesim" type="turtle_teleop_key" name="teleop" output="screen" />
<!-- 两只海龟的tf广播 -->
<node pkg="my_first_pack" type="turtle_tf_broadcaster" args="/turtle1" name="turtle1_tf_broadcaster"/>
<node pkg="my_first_pack" type="turtle_tf_broadcaster" args="/turtle2" name="turtle2_tf_broadcaster"/
<!-- 监听tf广播,并且控制turtle2移动 -->
<node pkg="my_first_pack" type="turtle_tf_listener" name="listener" />
</launch>
roslaunch my_first_pack my_turtle.launch
