ROS⼊门21讲笔记(总结⾃⽤)
简述
⼀周看完了古⽉⽼师的ROS⼊门21讲,理清了ROS⼯作空间和各个功能包是如何相互通讯的,还有如何⾃建功能包编写内部的代码等。本⽂跳过Linux命令⾏、ROS安装等环境搭建问题,重点记录如何写ROS的程序。
创建⼯作空间和功能包
在home中创建项⽬⽂件夹,在项⽬⽂件夹中再创建src⽂件夹,随后进⼊src⽂件夹中进⾏⼯作空间的初始化,和功能包的创建,最后到⼯作空间⽂件夹中进⾏编译,最后还需要设置环境变量。具体的命令⾏代码如下:
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
catkin_init_workspace
catkin_pkg_create pkg_name depend1 depend2  ....
cd ..
catkin_make
source ~/catkin_ws/devel/setup.bash
echo "source /WORKSPACE/devel/setup.bash">>~/.bashrc //添加到系统环境变量中
后续的所有代码编写都会在此⼯作空间下和功能包内进⾏。
话题(Topic)
话题是发布者Publisher和订阅者Subscriber相互之间传输消息Messages的通道,是⼀种单向的通讯,⼀个话题可以包括多个msg对象,需要掌握消息,发布者和订阅者的编写⽅法。可以⽤C++或者Python编写发布者和订阅者,C++需要在CMakelist⽂件中进⾏修改,使得编译成功;⽽Python⽆需编译但需要改执⾏权限。以下代码使⽤C++编写,下⾯做整体的总结。
消息(Messages)
在功能包内创建msg⽂件夹,并在其中创建.msg⽂件,⽂件名即对象名,如asoul.msg即创建asoul这个新对象。 随后需要在功能包内的和l⽂件中配置依赖和编译规则。
每个消息都是⼀种严格的数据结构,⽀持标准数据类型(整型、浮点型、布尔型等),也⽀持嵌套结构和数组(类似于C语⾔的结构体struct),还可以根据需求由开发者⾃主定义。——《ROS机器⼈开发实践》
MESSAGE.msg
string name
uint8 age
…………
<
find_package(..... message_generation)
add_message_files(FILES MESSAGE.msg)
generate_messages(DEPENDENCIES std_msgs)
catkin_package(... message_runtime)
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
发布者(Publisher)
需要在功能包的src⽂件下创建.cpp⽂件,⽂件名最好与发布名相同,以便后续的使⽤。不同于message,发布者仅需要对 ⽂件进⾏配置。
PUBLISHER.cpp
#include<ros/ros.h>
#include<package_name/msgs_name.h>//发布某个功能包下的某个消息
int main(int argc,char*argv){
// 节点初始化
ros::init(argc,argv,"node_name");
//创建节点句柄
ros::NodeHandle n;
//创建Publisher对象,发布名为/topic_name的topic,此处topic可以选择已存在的topic,消息类型是msgs定义的类,队列长度为10
ros::Publisher publisher_name = n.advertise<msgs的类型>("/topic_name",10);
//设置循环频率,10ms
ros::Rate loop_rate(10);
while(ros::ok()){
//初始化msgs下类型的消息,及新建msg对象并给对象变量赋值
msgs::asoul  idol;
idol.name ="";
idol.age ="";
//发布消息
publisher_name.publish(idol);
ROS_INFO()//⽇志内容
//按照循环频率延时
loop_rate.sleep();
}
return0;
}
<
add_executable(PUBLISHER src/PUBLISHER.cpp)
target_link_libraries(PUBLISHER ${catkin_LIBRARIES})
订阅者(Subscriber)
与发布者相同,需要在功能包的src⽂件夹下创建.cpp⽂件,也需要对⽂件进⾏配置。
SUBSCRIBER.cpp
#include<ros/ros.h>
#include<package_name/msgs_name.h>//订阅某个功能包下的某个消息
// 接收到订阅的消息类型后,会进⼊回调函数
void callback(const package::msg_name::ConstPtr& msg){
//回调函数下的任务不可以过于复杂,否则会在处理数据的时候丢失发布者的实时数据
ROS_INFO();
}
int main(int argc,char*argv){
/
/ 节点初始化
ros::init(argc,argv,"node_name");
//创建节点句柄
ros::NodeHandle n;
//创建Subscriber对象,订阅/topic_name的topic,注册回调函数
ros::Subscriber subscriber_name = n.subscribe("/topic_name",10,callback);
//循环等待回调函数
ros::spin();
return0;
}
<
add_executable(SUBSCRIBER src/SUBSCRIBER.cpp)
target_link_libraries(SUBSCRIBER ${catkin_LIBRARIES})
服务(Service)
服务不同于话题,是⼀种同步通信的⽅式,允许客户端(Client)向服务端发送请求(Request),同时服务端(Server)处理后反馈应答(Response)。与话题相似也需要掌握服务数据,客户端和服务端的编写⽅法,编写完成也需要进⾏⽂件配置。
服务数据(srv)
在功能包内创建srv⽂件夹,并在其中创建.srv⽂件,srv⽂件包括请求和应答两个数据域,数据类型与消息msg类似,但需要使⽤---进⾏数据域的分割。同样也需要在功能包内的l和⽂件中配置依赖和编译规则。
Test.srv
int64 id
............... //客户端发送请求的数据类型
---
String name
............... //服务端发送应答的数据类型
<
find_package(..... message_generation)
add_message_files(FILES Test.srv)
generate_messages(DEPENDENCIES std_msgs)
catkin_package(... message_runtime)
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
客户端
同样需要在功能包下的src⽂件夹内创建.cpp⽂件,
CLIENT.cpp
#include<ros/ros.h>
#include<package_name/srv_name.h>//发布某个功能包下的某个服务数据cmake如何使用
int main(int argc,char** argv){
//初始化ROS节点
ros::init(argc, argv,"Node_NAME");
//创建节点句柄
ros::NodeHandle n;
//等待SERVER服务端启动,创建客户端并连接
ros::service::waitForService("/SERVER_NAME");
ros::ServiceClient client = n.serviceClient<package_name::srv_name>("/SERVER_NAME");
//初始化请求数据,即给srv请求数据赋值
package_name::srv_name srv;
.....
//请求服务调⽤ call waitForService spin均为阻塞型函数,若没有反馈后续代码不会运⾏
client.call(srv);
//如果得到服务器的反馈,则可以运⾏查看是否调⽤成功sponse
ROS_INFO("%s",sponse.name.c_str());
return0;
}
<
add_executable(CLIENT src/CLIENT.cpp)
target_link_libraries(CLIENT ${catkin_LIBRARIES})
服务端
古⽉⽼师在21讲的服务端加⼊了发布者,来实现⼩乌龟的绕圈运动。服务和话题可以同时写⼊⼀个⽂件中使⽤,从⽽实现我们想要的功能。由于是总结性笔记,此处仅仅编写如何接受客户端发送的请求,和如何将回应发出,具体代码如下。
SERVER.cpp
#include<ros/ros.h>
#include<package_name/srv_name.h>//发布某个功能包下的某个服务数据
//回调函数,输⼊参数req,输出参数res
void callback(package_name::srv_name::Request &req, package_name::srv_name::Response &res){
//与订阅者的回调函数相似,区别在于有两个数据进⼊函数,可以获取请求数据进⾏查询或计算,还可以设置反馈数据
ROS_INFO("%d", req.id);
res.name ="JR";
.......
}
int main(int argc,char**argv){
//ROS节点初始化
ros::init(argc, argv,"Node_Name");
//创建节点句柄
ros::NodeHandle n;
//创建名为/Server_name的server,注册回调函数
ros::ServiceServer server = n.advertiseService("/Server_name", callback);
//循环等待回调函数
ROS_INFO("");
ros::spin();
//如果在while循环内查询队列,则需要使⽤ros::spinOnce()⽅法,使得while正常运⾏
return0;
}
<
add_executable(SERVER src/SERVER.cpp)
target_link_libraries(SERVER ${catkin_LIBRARIES})
坐标管理系统(TF)
在机器⼈设计和机器⼈应⽤过程中,都会涉及到不同组件的位置和姿态,这就需要引⼊坐标系以及坐标变换的概念。TF功能包是⼀个让⽤户随时间跟踪多个坐标系的功能包,使⽤树形数据结构,根据时间缓冲并维护多个坐标系之间的坐标变换关系,可以帮助我们在任意时间完成坐标系之间的点、向量等坐标变换。⽽使⽤TF功能包需要编写⼴播(Broadcaster)和监听(Listener)两个部分。
⼴播(Broadcaster)
古⽉⽼师使⽤了⼩乌龟⽰例,更容易理解,此处仅编写如何将坐标系和坐标变换⼴播出去。同样的需要在功能包的src下建⽴.cpp⽂件,对功能包内的配置编译规则。
Broadcaster.cpp
#include<ros/ros.h>
#include<tf/transform_broadcaster.h>
#include<package_name/pose_msg.h>//此处需要添加需要⼴播的坐标数据
//回调函数中的msg可能包含x,y,z和r,p,y,存在则⽤msg->赋值
void poseCallback(const package_name::pose_msgConstPtr& msg){
//创建tf的⼴播器
static tf::TransformBroadcaster br;
//初始化tf数据,即创建坐标变换值
tf::Transform transform;
transform.setOrigin(tf::Vector3(msg->x,msg->y,msg->z));//设置坐标变换平移
tf::Quaternion q;//此处采⽤RPY设置坐标变换旋转,即xyz三轴旋转的⽅式,还可以直接使⽤四元数
q.setRPY(roll, pitch, yaw);
transform.setRotation(q)//transform.setRotation(tf::Quaternion( , , , ));四元数⽅法
//⼴播两个坐标系之间的tf数据
br.sendTransform(tf::StampedTransform(transform, ros::Time::now(),"frame1","frame2"));
}
int main(int argc,char** argv){
//初始化ROS节点
ros::init(argc, argv,"node_name");
//创建节点句柄
ros::NodeHandle n;
//订阅存有坐标信息的位姿话题
ros::Subscriber sub = n.subscribe("",10.&poseCallback);
//循环等待回调函数
ros::spin();
return0;
}
<
add_executable(Broadcaster src/Broadcaster.cpp)
target_link_libraries(Broadcaster ${catkin_LIBRARIES})
监听(Listener)
可以⽤于监听和计算tf数据,在此同样仅仅写出监听相关的代码,不对具体的应⽤做拓展。Listener.cpp

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。