在ROS中重要的是理解各种通信机制以及组成结构,学习时多想想计算机网络课程上的相关概念会更好理解
节点与节点管理器
节点(Node)
节点是ROS的基本执行单元。通过节点可以执行一个具体任务的进程、独立运行的可执行文件;
在不同节点之间可以使用不同的编程语言,并且可以分布在不同的主机上运行,ROS提供了一整套通信的底层方案用于节点或不同设备间的通信
节点管理器(ROS Master)
节点管理器是ROS所有节点任务的控制中心。它负责为节点提供命名和注册服务;
节点控制器会跟踪和记录话题/服务通信,辅助节点之间相互查找、建立连接;
它提供了参数服务器,各个节点之间可以通过服务器存储和检索运行时的参数。
通信方式
通信协议的底层为ROSTCP/ROSUDP
话题通信(Topic)
话题通信属于异步通信,通信模型为发布/订阅,多用于数据传输
话题是节点间用于传输数据的重要总线,话题的模型是发布/订阅
模型,数据由发布者传输到订阅者,同一个话题的订阅者可以不唯一。属于一对多的结构。
消息 Message
消息是话题的数据,消息具有一定的类型和数据结构,包括ROS提供的标准类型和用户自定义类型;消息使用的是与编程语言无关的.msg文件定义内容,在编译的过程中生成对应的代码。通过独立的文件定义,使得消息数据可以不依赖于某一种具体的编程语言实现通信。
Node(Publisher)->(Topic\Message Type)->Node(Subscriber)…多对多
在话题模型中,我们使用发布+订阅
的方式进行通信。Publisher发布一个Topic,其中包含消息Message,订阅者Subscriber来订阅这个Topic获得其中的Message。
创建功能包
cd ~/workplace-path/src
catkin_create_pkg learning_topic roscpp rospy std_msgs geometry_msgs turtlesim
创建发布者和订阅者代码
Publisher
下面的代码以pose_subscriber为例演示了:初始化ROS节点;向ROS Master注册节点信息,包括发布的话题名和话题中的消息类型;创建了消息数据;按照一定的评率循环发布消息;
#include <ros/ros.h>
#include "learning_topic/Person.h"
int main(int argc, char **argv)
{
ros::init(argc, argv, "person_publisher"); //ROS节点初始化
ros::NodeHandle n; //创建节点句柄
//创建一个Publisher,发布名为/person_info的topic,消息类型为learning_topic::Person,队列长度为10
ros::Publisher person_info_pub = n.advertise<learning_topic::Person>("/person_info", 10);
ros::Rate loop_rate(1); //设置循环的频率
int count = 0;
while(ros::ok())
{
//初始化learning_topic::Person类型的消息
learning_topic::Person person_msg;
person_msg.name = "Tom";
person_msg.age = 18;
person_msg.sex = learning_topic::Person::male;
//发布消息
person_info_pub.publish(person_msg);
ROS_INFO("Publish Person Info: name:%s age:%d sex:%d",
person_msg.name.c_str(), person_msg.age, person_msg.sex);
//按照循环的频率延时
loop_rate.sleep();
}
return 0;
}
Subscriber
代码流程:初始化ROS节点;订阅需要的话题;循环等待话题消息,接收到消息后进入回调函数;在回调函数中完成具体的操作。
#include <ros/ros.h>
#include <learning_topic/Person.h>
//接受到订阅消息后,进入消息回调函数
void personInfoCallback(const learning_topic::Person::ConstPtr& msg)
{
//在回调函数中执行具体操作:将接收到的消息打印到控制台
ROS_INFO("Subcribe Person Info:name:%s age:%d sex:%d",
msg->name.c_str(), msg->age, msg->sex);
}
int main(int argc, char **argv)
{
//初始化ROS节点
ros::init(argc, argv, "person_subscriber");
//创建节点句柄
ros::NodeHandle n;
//创建一个Subscriber,订阅名为/person_info的topic,注册回调函数personInfoCallback
ros::Subscriber person_info_sub = n.subscribe("/person_info", 10, personInfoCallback);
//循环等待回调函数
ros::spin();
return 0;
}
CMakeLists.txt
需要设置编译的代码和生成可执行文件,并且设置链接库;
add_executable(person_publisher src/person_publisher.cpp)
target_link_libraries(person_publisher ${catkin_LIBRARIES})
add_dependencies(person_publisher ${PROJECT_NAME}_generate_messages_cpp)
add_executable(person_subscriber src/person_subscriber.cpp)
target_link_libraries(person_subscriber ${catkin_LIBRARIES})
add_dependencies(person_subscriber ${PROJECT_NAME}_generate_messages_cpp)
编译和运行
#进入工作空间目录
cd ~/workplace-Path
#编译
catkin_make
#添加环境变量
source devel/setup.bash
#启动ROS
roscore
#运行publisher和subscriber的可执行文件
rosrun person_publisher person_subscriber
服务(Service)
服务属于同步通信,通信模型为服务器/客户端,多用于逻辑处理
Service是使用客户端/服务端(C/S)模型,客户端发送请求数据,服务器完成处理后返回应答数据,不同于话题通信具有数据缓冲区,服务是没有缓冲区的。在Service中与Topic类似的,使用和编程语言无关的.srv文件定义请求和应答数据结构,编译过程中生成相对应的代码。
Node(Service Server) -->(>request,response<)–>Node(Service Client)…一对多(一个server)
Server
#include <ros/ros.h>
#include <learning_service/Person.h>
//回调函数,输入参数req,输出参数res
bool personCallback( learning_service::Person::Request &req,
learning_service::Person::Response &res)
{
//显示请求数据
ROS_INFO("Person :name:%s age:%d sex:%d",req.name.c_str(), req.age, req.sex);
res.result = "OK";
return true;
}
int main(int argc, char **argv)
{
//ROS节点初始化
ros::init(argc, argv, "person_server");
//创建节点句柄
ros::NodeHandle n;
//创建一个名为/show_person的service,注册回调函数personCallback
ros::ServiceServer person_service = n.advertiseService("/show_person", personCallback);
//进入循环等待回调函数
ROS_INFO("Ready to show person informtion");
ros::spin();
return 0;
}
Client
#include <ros/ros.h>
#include <learning_service/Person.h>
int main(int argc, char **argv)
{
//初始化ROS节点
ros::init(argc, argv, "person_client");
//创建节点句柄
ros::NodeHandle node;
//发现/show_person服务后,创建一个服务客户端,连接名为/show_person的Service
ros::service::waitForService("/show_person");
ros::ServiceClient person_client = node.serviceClient<learning_service::Person>("/show_person");
//初始化learning_service::Person的请求数据
learning_service::Person srv;
srv.request.name = "Tom";
srv.request.age = 20;
srv.request.sex = learning_service::Person::Request::male;
//请求服务调用
ROS_INFO("Call service to show person[name:%s, age:%d, sex:%d]",
srv.request.name.c_str(), srv.request.age, srv.request.sex);
person_client.call(srv);
//显示服务调用的结果
ROS_INFO("Show person result:%s", srv.response.result.c_str());
return 0;
}
CMakeLists.txt
add_executable(person_server src/person_server.cpp)
target_link_libraries(person_server ${catkin_LIBRARIES})
add_dependencies(person_server ${PROJECT_NAME}_gencpp)
add_executable(person_client src/person_client.cpp)
target_link_libraries(person_client ${catkin_LIBRARIES})
add_dependencies(person_client ${PROJECT_NAME}_gencpp)
参数(Parameter)
参数是一种全局共享字典
它可以提供通过网络访问的共享、多变量字典。节点可以使用参数服务器来存储和检索运行时的参数。在使用上更加合适用来存储静态、非二进制的配置参数,不适合用来存储动态的配置数据。
#include <string>
#include <ros/ros.h>
#include <std_srvs/Empty.h>
int main(int argc, char **argv)
{
int red, green, blue;
ros::init(argc, argv, "parameter_config");
ros::NodeHandle node;
ros::param::get("/turtlesim/background_r", red);
ros::param::get("/turtlesim/background_g", green);
ros::param::get("/turtlesim/background_b", blue);
ROS_INFO("Get Backgroud Colorp[%d, %d, %d]",red, green, blue);
ros::param::set("/turtlesim/background_r", 255);
ros::param::set("/turtlesim/background_g", 255);
ros::param::set("/turtlesim/background_b", 255);
ROS_INFO("Set Background Color[255, 255, 255]");
ros::service::waitForService("/clear");
ros::ServiceClient clear_background = node.serviceClient<std_srvs::Empty>("/clear");
std_srvs::Empty srv;
clear_background.call(srv);
sleep(1);
return 0;
}
文件系统
功能包(Package)
是ROS软件中的基本单元,包含节点源码、配置文件、数据定义;
功能包清单(Pack manifest)
用来记录功能包的基本信息,包含作者信息、依赖选项、编译标志等
元功能包(Meta Packages)
组织多个用于同一目的的功能包