简单的Topic发布者和订阅者
在上一节中,我们介绍了ROS运行时的基本单元Node,也提到了ROS系统中各个Node之间通过发布订阅Topic,
请求响应服务,来实现相互之间的通信。在本文中,我们将进一步讨论Topic,实现一个发布者Node和一个订阅者Node。
1. C++版本
先直接上代码,左边是发布者talker.cpp,右边的上半部分是订阅者listener.cpp,下半部分是beginner_tutorial目录下的CMakeLists.txt的内容。
talker.cpp和listener.cpp是保存在beginner_tutorial目录下的src子目录中的。
#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 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());
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 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;
}
cmake_minimum_required(VERSION 2.8.3)
project(beginner_tutorials)
add_definitions(-std=c++11)
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs)
catkin_package()
include_directories(${catkin_INCLUDE_DIRS})
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
|
在talker和listener的main函数的一开始,都先调用了函数ros::init()完成一些初始化的工作。这里传给ros::init()的第三个参数就是node的默认运行时名称。
我们可以在运行node时通过参数__name指定一个新的名称,这点是由argc和argv两个参数完成的。通过直接把来自终端的调用参数直接传给ros::init(),ros系统就可以根据用户的指令做出一些调整。
当然ros::init()有好几个重载的版本,我们可以根据需要选择调用其中的一个。但是对于一个ros系统的程序,我们必须保证在程序的一开始就调用一个ros::init()函数,做出一些初始化的操作。
接着我们定义了一个ros::NodeHandle的对象n,我们通过对它的访问来调用ROS的各种API。在这里,我们分别调用了它的advertise()和subscribe()来发布和订阅了主题"chatter"。
advertise()是一个模板函数,<std_msgs::String>指定了发布者发布的Topic类型为std_msgs::String,这是一个ROS系统中已经定义的一种类型。advertise()的第一个参数表示将要发布的主题名称,
第二个参数则是消息缓存队列的大小。
subscribe()函数中的前两个参数与advertise()是一样的,分别描述了主题的名称和消息缓存队列的大小,第三个参数则是一个回调函数指针。
所谓的回调函数指针是指每次listener接收到一条订阅的消息后,都会调用一下这个函数。我们可以在回调函数中写一些代码,完成一些预期的功能。
这里我们在回调函数chatterCallback中通过ROS_INFO把接收到的消息打印出来。
在talker中,我们还定义了一个ros::Rate的对象,我们传了一个参数10,这样后面的while循环中调用它的sleep()函数就可以实现以10Hz的频率发布消息的功能。
最后,我们在talker的while循环中不断的发布消息,直到我们以某种手段关闭了talker为止,这里的ros::ok()实际上就是对talker的退出状态的判定。
在listener的最后,我们调用了ros::spin()进入一个循环,同样也是为了保证listener一直持续运行下去,直到被我们或者其它node中止。
我们知道C++的代码是需要编译成机器语言才能够运行的,所以这里我们还需要修改编译文件CMakeLists.txt,用来指引编译talker和listener。
至于CMakeLists.txt中的内容这里就不再做详细介绍了,读者可以参考其它关于CMake的文章。
2. Python版本
一样还是先上代码。左边是发布者talker,右边是订阅者listener。
#!/usr/bin/env python
import rospy
from std_msgs.msg import String
def talker():
rospy.init_node('talker', anonymous=True)
pub = rospy.Publisher('chatter', String, queue_size=10)
rate = rospy.Rate(10) # 10hz
while not rospy.is_shutdown():
hello_str = "hello world %s" % rospy.get_time()
rospy.loginfo(hello_str)
pub.publish(hello_str)
rate.sleep()
if __name__ == '__main__':
try:
talker()
except rospy.ROSInterruptException:
pass
|
#!/usr/bin/env python
import rospy
from std_msgs.msg import String
def callback(data):
rospy.loginfo(rospy.get_caller_id() + ': %s', data.data)
def listener():
rospy.init_node('listener', anonymous=True)
rospy.Subscriber('chatter', String, callback)
rospy.spin()
if __name__ == '__main__':
listener()
|
python版本与C++版本在程序逻辑上没什么不同,都是先对node进行初始化,然后定义了发布者和订阅者对象,最后进入一个循环中直到我们或者其它node把它中止。
订阅者的实现也一样是通过回调函数实现的。
因为Python是解释性语言,不要编译就可以运行,所以我们不需要对CMakeLists.txt做出什么特别的修改。但是,我们要保证这两个文件放在了beginner_tutorials的根目录下,
或者beginner_tutorials/src/scripts下,这样ROS系统就可以直接搜索到它们,而不需要我们在运行时指定路径了。此外,还要注意我们应当具有对着两个文件的执行权限。
如果没有可以通过Linux的指令赋予:
$ roscd beginner_tutorials/src/scripts
$ chmod 755 *.py
3. 编译运行
编译的过程很简单,直接在工作空间的根目录下执行catkin_make就可以实现一键编译了。
$ cd ~/catkin_ws/
catkin_make
编译完,我们就可以通过rosrun运行它们:
$ rosrun beginner_tutorials listener.py
$ rosrun beginner_tutorials listener
$ rosrun beginner_tutorials talker.py
$ rosrun beginner_tutorials talker
通过rosnode可以查看talker和listener的详细信息:
$ rosnode info /talker
-----------------------------------------------------------
Node [/talker]
Publications:
* /chatter [std_msgs/String]
* /rosout [rosgraph_msgs/Log]
Subscriptions: None
Services:
* /talker/set_logger_level
* /talker/get_loggers
contacting node http://gyc:43047/ ...
Pid: 12012
Connections:
* topic: /rosout
* to: /rosout
* direction: outbound
* transport: TCPROS
* topic: /chatter
* to: /listener_11989_1511700227987
* direction: outbound
* transport: TCPROS
|
$ rosnode info /listener
-----------------------------------------------------------
Node [/listener]
Publications:
* /rosout [rosgraph_msgs/Log]
Subscriptions:
* /chatter [std_msgs/String]
Services:
* /listener/set_logger_level
* /listener/get_loggers
contacting node http://gyc:42776/ ...
Pid: 12057
Connections:
* topic: /rosout
* to: /rosout
* direction: outbound
* transport: TCPROS
* topic: /chatter
* to: /talker (http://gyc:43047/)
* direction: inbound
* transport: TCPROS
|
4. 总结
在本文中,我们只是列出了C++和Python实现的两个发布者和订阅者node。没有做出更多的解释,只是两个例程而已。