1.话题中的发布者与订阅者
创建Publisher(发布者)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include <sstream> #include "ros/ros.h" #include "std_msgs/String.h"
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);
ros::spinOnce();
loop_rate.sleep(); ++count; }
return 0; }
|
创建Subscriber(订阅者)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
#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; }
|
然后在该功能包中的CmakeLsit.txt中:
1 2 3 4 5 6 7
| add_executable(talker src/talker.cpp) target_link_libraries(talker ${catkin_LIBRARIES}) ##add_dependencies(talker ${PROJECT_NAME}_generate_messages_cpp)不需
add_executable(listener src/talker.cpp) target_link_libraries(listener ${catkin_LIBRARIES}) ##add_dependencies(listener ${PROJECT_NAME}_generate_messages_cpp)不需
|
其中:
add_ex...:为设置需编译的代码和可执行文件。第一个参数为期望生成可执行文件的名字,一般与节点名相同,方便使用;第二个参数为要编译的文件。
target...:设置链接库。第一个参数为需链接的可执行文件名(同上的名字);第二个为要链接的库
add_de...:设置依赖。为可执行文件添加能动态产生消息代码的依赖。但是先版本好像已不需要添加这个
自定义消息类型
前两节中使用的消息类型为ROS元功能包定义的std_msgs(标准数据类型)中预定义的String类型,除此之外,用户可以自定义msg文件,使用自定义的类型,流程如下:
1.编写msg文件
在本功能包中创建msg文件夹(与src平行),并创建person.msg
1 2 3 4 5 6 7
| string name uint8 sex uint8 age
uint8 unknown=0 uint8 male=1 uint8 female=2
|
2.修改本功能包下的package.xml
添加:
1 2
| <build_depend>message_generation</build_depend> <exec_depend>message_runtime</exec_depend>
|
保证msg文件能转化为cpp,py等语言的源文件,第一行为编译依赖;第二行为执行依赖
3.修改本功能包下的CMakeLists.txt
3.编译
编译成功后,对于C++而言,编译器帮我们自动编写一个头文件:sensor.h,文件位于workspace/devel/include中,通过引用头文件就可以使用这个自定义数据了。
将自定义消息用于刚刚的话题中
发布者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include <sstream> #include "ros/ros.h" #include "std_msgs/String.h" #include "learning_communication/person.h" int main(int argc, char **argv) { ros::init(argc, argv, "talker");
ros::NodeHandle n;
ros::Publisher chatter_pub = n.advertise<learning_communication::person>("chatter", 1000);
ros::Rate loop_rate(10);
int count = 0; while (ros::ok()) { learning_communication::person msg; std::stringstream ss; ss << "dhk" << count; msg.name = ss.str();
ROS_INFO("%s", msg.name.c_str()); chatter_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep(); ++count; }
return 0; }
|
订阅者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include "ros/ros.h" #include "std_msgs/String.h" #include "learning_communication/person.h"
void chatterCallback(const learning_communication::person::ConstPtr& msg) { ROS_INFO("I heard: [%s]", msg->name.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; }
|
如图,talker节点发布了名为chatter的话题,被listener节点订阅。
2.服务中的服务端与客户端
服务(service)为节点之间通信的一种方式,由客户端(Client)发布请求(Request),服务端(Server)处理后返回应答(Response)
自定义服务数据
1.编写srv文件
同话题中的自定义消息一样,服务数据可以通过srv文件进行定义,且同msg文件一样放置在具体功能包文件夹中,但由于文件包含请求与应答两个数据域,因此要特别分割一下:
1 2 3 4
| int64 a int64 b --- int64 sum
|
上为请求域,下为应答域
2.修改package.xml
内容同自定义订阅消息中的操作
1 2
| <build_depend>message_generation</build_depend> <exec_depend>message_runtime</exec_depend>
|
3.修改CMakeLists.txt
找到被注释或已有的add_service_files()语句:
1 2 3 4
| add_service_files( FILES AddTwoInts.srv )
|
其他操作同自定义订阅消息
创建Server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #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; }
|
创建Client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #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; }
|
编译功能包
在CMakeLists.txt中添加相关内容
1 2 3 4 5
| add_executable(add_two_ints_server src/server.cpp) target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_executable(add_two_ints_client src/client.cpp) target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
|
这里要注意add_executable第一个参数为期望的可执行文件名,可以不与节点名相同,但我喜欢相同(哼)
运行
这里要注意
- 运行节点时不要运行成cpp文件
- Client节点需添加2个初始参数
话题与服务的区别
- 话题中建立两节点关系的阶段是在listener中订阅相关话题并注册回调函数;而服务中建立两节点关系的阶段是在client中请求名为xx的服务。因此顺序应该为:
- 话题:先编写talker,再写listener
- 服务:先编写server,再写client
- 两种通信的具体顺序:
- 话题
- 创建发布者,话题,并规定话题的消息形式
- 发布消息
- 创建订阅者,并订阅话题,注册回调函数,等待回调函数运行
- 服务
- 创建服务端,服务名,并注册服务回调函数
- 创建客户端,并请求相关服务名的服务,此时规定服务的消息形式
- 客户端请求服务并输入相关内容,服务端回调函数进行处理时客户端堵塞,等待服务完成,服务完成后客户端内与回调函数相关的参数已改变
- 需要注意的是,话题的回调函数只在订阅文件生效,不是反馈;而服务的回调函数会生效于客户端,是真正的反馈
ROS命名规则
- 基础名称:dhk
- 全局名称:/xx/dhk
- 相对名称:xx/dhk
- 私有名称:~xx/dhk
起始为/的都为全局名称