5.Client Library CPP
client library:
提供ros编程的库/接口
例如:建立node、发布消息、调用服务等
提供cpp和python两种语言支持,一般使用cpp
5.1 roscpp
ros提供的使用cpp和topic,service,param等交互的接口
roscpp位于 /opt/ros/kinetic 之下, 用cpp实现了ROS通信。 在ROS中, cpp的代码是通过catkin这个编译系统( 扩展的CMake) 来进行编译构建的。 所以简单地理解, 你也可以把roscpp就当作为一个cpp的库, 我们创建一个CMake工程, 在其中include了roscpp等ROS的libraries, 这样就可以在工程中使用ROS提供的函数了。通常我们要调用ROS的cpp接口, 首先就需要 #include <ros/ros.h> 。
roscpp的主要部分包括:
-
ros::init() : 解析传入的ROS参数, 创建node第一步需要用到的函数 -
ros::NodeHandle : 和topic、 service、 param等交互的公共接口 -
ros::master : 包含从master查询信息的函数 -
ros::this_node: 包含查询这个进程(node)的函数 -
ros::service: 包含查询服务的函数 -
ros::param: 包含查询参数服务器的函数, 而不需要用到NodeHandle -
ros::names: 包含处理ROS图资源名称的函数 -
具体可见: http://docs.ros.org/api/roscpp/html/index.html
5.1.1 ros::init()
void ros::init()
5.1.2 ros::NodeHandle Class (类)
ros::Publisher advertise(const string &topic, uint32_t queue_size, bool latch=false);
个较小的数即可。
这两种情况下, /map才会发布消息。 这里就用到了锁存。
ros::Subscriber subscribe(const string &topic, uint32_t queue_size, void(*)(M));
ros::ServiceServer advertiseService(const string &service, bool(*srv_func)(Mreq &, Mre
s &));
ros::ServiceClient serviceClient(const string &service_name, bool persistent=false);
快一些。 通常我们设为flase
bool getParam(const string &key, std::string &s);
bool getParam (const std::string &key, double &d) const;
bool getParam (const std::string &key, int &i) const;
void setParam (const std::string &key, const std::string &s) const;
void setParam (const std::string &key, const char *s) const;
void setParam (const std::string &key, int i) const;
5.1.3 ros::spin() 和 ros::spinOnce() 区别及详解
1 函数意义
首先要知道,这俩兄弟学名叫ROS消息回调处理函数。它俩通常会出现在ROS的主循环中,程序需要不断调用ros::spin() 或 ros::spinOnce(),两者区别在于前者调用后不会再返回,也就是你的主程序到这儿就不往下执行了,而后者在调用后还可以继续执行之后的程序。
其实消息回调处理函数的原理非常简单。我们都知道,ROS存在消息发布订阅机制,什么?不知道?不知道还不快去:http://wiki.ros.org/ROS/Tutorials (ROS官方基础教程) 瞅瞅。
好,我们继续,如果你的程序写了相关的消息订阅函数,那么程序在执行过程中,除了主程序以外,ROS还会自动在后台按照你规定的格式,接受订阅的消息,但是所接到的消息并不是立刻就被处理,而是必须要等到ros::spin()或ros::spinOnce()执行的时候才被调用,这就是消息回调函数的原理
2 区别
就像上面说的,ros::spin() 在调用后不会再返回,也就是你的主程序到这儿就不往下执行了,而 ros::spinOnce() 后者在调用后还可以继续执行之后的程序。
其实看函数名也能理解个差不多,一个是一直调用;另一个是只调用一次,如果还想再调用,就需要加上循环了。
这里一定要记住,ros::spin()函数一般不会出现在循环中,因为程序执行到spin()后就不调用其他语句了,也就是说该循环没有任何意义,还有就是spin()函数后面一定不能有其他语句(return 0 除外),有也是白搭,不会执行的。ros::spinOnce()的用法相对来说很灵活,但往往需要考虑调用消息的时机,调用频率,以及消息池的大小,这些都要根据现实情况协调好,不然会造成数据丢包或者延迟的错误
3 常见使用方法
这里需要特别强调一下,如果大兄弟你的程序写了相关的消息订阅函数,那千万千万千万不要忘了在相应位置**加上ros::spin()或者ros::spinOnce()**函数,不然你是永远都得不到另一边发出的数据或消息的,博主血的教训,万望紧记
3.1 ros::spin()
ros::spin()函数用起来比较简单,一般都在主程序的最后,加入该语句就可。例子如下:
发送端:
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
int main(int argc, char **argv)
{
ros::init(argc, argv, "talker");
ros::NodeHandle n;
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
ros::Rate loop_rate(10);
int count = 0;
while (ros::ok())
{
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
ROS_INFO("%s", msg.data.c_str());
chatter_pub.publish(msg);
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 heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "listener");
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
ros::spin();
return 0;
}
3.2 ros::spinOnce()
对于ros::spinOnce()的使用,虽说比ros::spin()更自由,可以出现在程序的各个部位,但是需要注意的因素也更多。比如:
1 对于有些传输特别快的消息,尤其需要注意合理控制消息池大小和ros::spinOnce()执行频率; 比如消息送达频率为10Hz, ros::spinOnce()的调用频率为5Hz,那么消息池的大小就一定要大于2,才能保证数据不丢失,无延迟。
#include "ros/ros.h"
#include "std_msgs/String.h"
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "listener");
ros::NodeHandle n;
ros::Subscriber sub = n.subscribe("chatter", 2, chatterCallback);
ros::Rate loop_rate(5);
while (ros::ok())
{
ros::spinOnce();
loop_rate.sleep();
}
return 0;
2. ros::spinOnce()用法很灵活,也很广泛,具体情况需要具体分析。但是对于用户自定义的周期性的函数,最好和ros::spinOnce并列执行,不太建议放在回调函数中;
ros::Rate loop_rate(100);
while (ros::ok())
{
user_handle_events_timeout(...);
ros::spinOnce();
loop_rate.sleep();
}
5.2 topic_demo
Topic是ROS里一种异步通信的模型, 一般是节点间分工明确, 有的只负责发送, 有的只负责接收处理。 对于绝大多数的机器人应用场景, 比如传感器数据收发, 速度控制指令的收发,Topic模型是最适合的通信方式。
一个消息收发的例子: 自定义一个类型为gps的消息( 包括位置x, y和工作状态state信息), 一个node 以一定频率发布模拟的gps消息, 另一个node接收并处理, 算出到原点的距离。
步骤:
- 建立package
- 自定义msg文件
- talker.cpp
- listener.cpp
- CmakeList.txt & package.xml
1.创建package
cd ~/tutorial_ws/src
catkin_creat_pkg topic_demo roscpp rospy std_msgs
2. 新建自定义msg
cd topic_demo/
mkdir msg
cd msg
gedit gps.msg
catkin_make 会把*.msg 文件编译成*.h 文件,#include 一下就可以使用了
#include "topic_demo/gps.h"
topic_demo::gps msg;
3. talk.cpp
- .pro文件的写法,主要是加入ros和msg生成的h文件
TEMPLATE = app
CONFIG += console cpp11
CONFIG -= app_bundle
CONFIG -= qt
SOURCES += \
talker.cpp
LIBS += \
-L/usr/local/lib \
-L/opt/ros/kinetic/lib \
-lroscpp -lrospack -lpthread -lrosconsole -lrosconsole_log4cxx\
-lrosconsole_backend_interface -lxmlrpcpp -lroscpp_serialization -lrostime -lcpp_common -lroslib -lroslib
HEADERS += \
INCLUDEPATH +=\
/opt/ros/kinetic/include\
/home/swc/tutorial_ws/devel/include/
#include <iostream>
#include "ros/ros.h"
#include "topic_demo/gps.h"
using namespace std;
int main(int argc,char** argv)
{
ros::init(argc,argv,"talker");
ros::NodeHandle nh;
topic_demo::gps msg;
msg.x = 1.0;
msg.y = 1.0;
msg.state = "working";
ros::Publisher pub = nh.advertise<topic_demo::gps>("gps_info",1);
ros::Rate loop_rate(1.0);
while(ros::ok())
{
msg.x = 1.03 * msg.x;
msg.y = 1.01 * msg.y;
ROS_INFO("Talker_____:GPS:x = %f,y = %f",msg.x,msg.y);
pub.publish(msg);
loop_rate.sleep();
}
return 0;
}
4.listener.cpp
#include "ros/ros.h"
#include "topic_demo/gps.h"
void gpsCallback(const topic_demo::gps::ConstPtr& msg)
{
float distance;
float x = msg->x;
float y = msg->y;
distance = sqrt(pow(x,2)+pow(y,2));
ROS_INFO("Listener_____distance to ogigin:%f,state:%s",distance,msg->state);
}
int main(int argc, char **argv)
{
ros::init(argc,argv,"listener");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("gps_info",1,gpsCallback);
ros::spin();
return 0;
}
5.修改CMakeLists.txt和package.xml
CMakeLists.txt
cmake_minimum_required(VERSION 2.8.3) # CMAKE版本
project(topic_demo) # 项目名称
find_package(catkin REQUIRED COMPONENTS # 指定依赖
roscpp
rospy
std_msgs
message_generation
)
add_message_files( # 添加自定义的msg
FILES
gps.msg
)
generate_messages(DEPENDENCIES std_msgs)# 生成msg对应的h文件
catkin_package(CATKIN_DEPENDS roscpp rospy std_msgs message_runtime)# 用于配置ROS和package配置文件和Cmake文件
include_directories(include ${catkin_INCLUDE_DIRS}) # 指定C/cpp的头文件路径
add_executable(talker src/talker.cpp) # 生成可执行目标文件
add_dependencies(talker topic_demo_generate_message_cpp)# 添加依赖,必须有这句来生成msg?
target_link_libraries(talker ${catkin_LIBRARIES}) # 链接
add_executable(listener src/listener.cpp)
add_dependencies(listener topic_demo_generate_message_cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
package.xml 中添加两句
<build_depend>message_generation</build_depend> <run_depend>message_runtime</run_depend>
6.编译运行
catkin_make
rosrun topic_demo talker
rosrun topic_demo listener 也可以在qt里面编译运行listener.cpp,效果一样的
查看话题间关系:rqt_graph
7.先接收消息,处理之后发出去
#include "ros/ros.h"
#include "topic_demo/gps.h"
topic_demo::gps gps_data;
void gpsCallback(const topic_demo::gps::ConstPtr& msg)
{
gps_data = *msg;
gps_data.state = "received";
float distance;
float x = msg->x;
float y = msg->y;
distance = sqrt(pow(x,2)+pow(y,2));
}
int main(int argc, char **argv)
{
ros::init(argc,argv,"listener");
ros::NodeHandle nh;
ros::Publisher pub = nh.advertise<topic_demo::gps>("gps_receive",1);
ros::Subscriber sub = nh.subscribe("gps_info",1,gpsCallback);
ros::Rate loop_rate(1.0);
while(ros::ok())
{
ros::spinOnce();
pub.publish(gps_data);
ROS_INFO("publish success");
loop_rate.sleep();
}
return 0;
}
#include "ros/ros.h"
#include "topic_demo/gps.h"
void gpsCallback(const topic_demo::gps::ConstPtr& msg)
{
float distance;
float x = msg->x;
float y = msg->y;
distance = sqrt(pow(x,2)+pow(y,2));
ROS_INFO("Listener2_____distance to ogigin:%f,state:%s",distance,msg->state.c_str());
}
int main(int argc, char **argv)
{
ros::init(argc,argv,"listener2");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("gps_receive",1,gpsCallback);
ros::Rate loop_rate(1.0);
while(ros::ok())
{
ros::spin();
loop_rate.sleep();
}
return 0;
}
8.话题名称相关
以下代码显示了常用的话题的声明,我们通过修改话题名称来理解名称的用法。
int main(int argc, char** argv)
{
ros::init( argc, argv, "node1");
{
ros::NodeHandle nh;
ros::Publisher node1_pub = nh.advertise<std_msg::Int32>("bar" , 10);
}
}
? 这里的节点名称是/node1。如果您用一个没有任何字符的相对形式的bar来声明一个发布者,这个话题将和/bar具有相同的名字。
? 如果以如下所示使用斜杠(/)字符用作全局形式,话题名也是/bar。
ros::Publisher node1_pub = nh.advertise<std_msg::Int32>(“/bar”, 10); 但是,如果使用波浪号(~)字符将其声明为私有,则话题名称将变为/node1/bar。
ros::Publisher node1_pub = nh.advertise<std_msg::Int32>(“~bar”, 10);
这可以按照下表所示的各种方式使用。其中/wg意味着命名空间的修改。这在下面的描述中更详细地讨论。
Node | Relative(基本) | Global | Private |
---|
/node1 | bar->/bar | /bar->/bar | ~bar->/node1/bar | /wg/node2 | bar->/wg/bar | /bar->/bar | ~bar->/wg/node2/bar | /wg/node3 | foo/bar->/wg/foo/bar | /foo/bar->/foo/ba | ~foo/bar->/wg/node3/foo/bar |
5.3 service_demo
services是另一种ros通信的方式,具体介绍3.2.4 3.2.5
demo功能:
实现两个整数的相加
客户端:发送两个整数
服务端:返回求和
1.自定义srv文件
和topic的msg类似,在功能包中新建一个srv文件夹,里面保存自定义的srv文件
# 客户端发送的
int64 a
int64 b
# 客户端和服务端用---隔开
---
# 服务端返回的
int64 sum
2.生成cpp可以调用的格式
修改Cmakelists.txt package.xml
在package.xml中添加功能包依赖:
<?xml version="1.0"?>
<package format="2">
<name>service_demo</name>
<version>0.0.0</version>
<description>service_demo</description>
<maintainer email="swc@todo.todo">swc</maintainer>
<license>TODO</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>message_generation</build_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>rospy</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>message_runtime</exec_depend>
<export>
</export>
</package>
在Cmakelists.txt中添加依赖选项
cmake_minimum_required(VERSION 3.0.2)
project(service_demo)
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
################################################
## Declare ROS messages, services and actions ##
################################################
## Generate services in the 'srv' folder
add_service_files(
FILES
AddTwoInts.srv
)
## Generate added messages and services with any dependencies listed here
generate_messages(
DEPENDENCIES
std_msgs
)
###################################
## catkin specific configuration ##
###################################
catkin_package(
# INCLUDE_DIRS include
# LIBRARIES service_demo
CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
# DEPENDS system_lib
)
###########
## Build ##
###########
## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
include
${catkin_INCLUDE_DIRS}
)
然后编译,生成h文件
3.客户端程序编写
#include <cstdlib>
#include "ros/ros.h"
#include "learning_communication/AddTwoInts.h"
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_client");
if (argc != 3)
{
ROS_INFO("usage: add_two_ints_client X Y");
return 1;
}
ros::NodeHandle n;
ros::ServiceClient client = n.serviceClient<learning_communication::AddTwoInts>("add_two_ints");
learning_communication::AddTwoInts 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_two_ints");
return 1;
}
return 0;
}
4.服务端程序编写
#include "ros/ros.h"
#include "learning_communication/AddTwoInts.h"
bool add(learning_communication::AddTwoInts::Request &req,
learning_communication::AddTwoInts::Response &res)
{
res.sum = req.a + req.b;
ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("sending back response: [%ld]", (long int)res.sum);
return true;
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_server");
ros::NodeHandle n;
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
|