首页 关于
树枝想去撕裂天空 / 却只戳了几个微小的窟窿 / 它透出天外的光亮 / 人们把它叫做月亮和星星
目录

简单的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。没有做出更多的解释,只是两个例程而已。




Copyright @ 高乙超. All Rights Reserved. 京ICP备16033081号-1