[ROS Study Notes 4] Topic Communication

[ROS Study Notes 4] Topic Communication


0. Topic communication overview

Topic communication is the most frequently used communication method in ROS. Topic communication is based on the publish-subscribe model, that is: one node publishes a message, and another node subscribes to the message. The application scenarios of topic communication are also extremely extensive.

concept

A communication mode that implements data interaction between different nodes in a publish-subscribe manner

effect

For data transfer scenarios that are constantly updated and less logically processed.


1. Theoretical model of topic communication

The implementation model of topic communication is relatively complicated. The model is shown in the figure below. There are three roles involved in the model:

  • ROS Master (manager)
  • Talker (Publisher)
  • Listener (subscriber)

ROS Master is responsible for keeping the registered information of Talker and Listener, matching Talker and Listener with the same topic, and helping Talker and Listener establish a connection. After the connection is established, Talker can publish messages, and the published messages will be subscribed by Listener.

The whole process is realized by the following steps:

0. Talker registration

After Talker starts, it will register its own information in ROS Master through RPC, which includes the topic name of the published message. ROS Master will add the registration information of the node to the registry.

1. Listener registration

After the Listener is started, it will also register its own information in the ROS Master through RPC, including the topic name that needs to subscribe to the message. ROS Master will add the registration information of the node to the registry.

2. ROS Master implements information matching

ROS Master will match Talker and Listener according to the information in the registry, and send Talker's RPC address information to Listener through RPC.

3. Listener sends a request to Talker

After the Talker receives the Listener's request, it also confirms the connection information to the Listener through RPC, and sends its own TCP address information.

4. The Listener establishes a connection with the Talker

The Listener uses TCP to establish a network connection with the Talker according to the message returned in step 4.

5. Talker sends message to Listener

After the connection is established, the Talker starts publishing messages to the Listener

Attention1: In the above implementation process, the first five steps use the RPC protocol, and the last two steps use the TCP protocol

Attention2: There is no sequence requirement for the startup of Talker and Listener

Attention3: Both Talker and Listener can have multiple

Attention4: After the connection between Talker and Listener is established, ROS Master is no longer needed

Attention5: The above process has been encapsulated and can be called directly in the future.

Topic communication focus:

Point1: Most of the implementation has been encapsulated

Point2: topic setting

Point3: Focus on the implementation of the publisher

Point4: Focus on the implementation of subscribers

Point5: Pay attention to the message carrier


2. Cpp implementation of basic operations of topic communication

1. Demand

Write a publish-subscribe implementation, requiring the publisher to publish a text message at a frequency of 10HZ, and the subscriber to print out the message content

2. Analysis

In the model implementation, ROS Master has been automatically implemented, we don’t need to implement it again, and the connection package has also been packaged. There are three points that need attention

  1. Publisher
  2. receiver
  3. data (plain text here)

3. Process

  1. Write the publisher implementation
  2. Write the Subscriber Implementation
  3. edit configuration file
  4. compile and execute

4. Implementation by the publisher

/*
    需求: 实现基本的话题通信,一方发布数据,一方接收数据,
         实现的关键点:
         1.发送方
         2.接收方
         3.数据(此处为普通文本)
         PS: 二者需要设置相同的话题
    消息发布方:
        循环发布信息:HelloWorld 后缀数字编号
    实现流程:
        1.包含头文件 
        2.初始化 ROS 节点:命名(唯一)
        3.实例化 ROS 句柄
        4.实例化 发布者 对象
        5.组织被发布的数据,并编写逻辑发布数据

*/
// 1.包含头文件 
#include "ros/ros.h"
#include "std_msgs/String.h" //普通文本类型的消息
#include <sstream>

int main(int argc, char  *argv[])
{
    
       
    //设置编码
    setlocale(LC_ALL,"");

    //2.初始化 ROS 节点:命名(唯一)
    // 参数1和参数2 后期为节点传值会使用
    // 参数3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一
    ros::init(argc,argv,"talker");
    //3.实例化 ROS 句柄
    ros::NodeHandle nh;//该类封装了 ROS 中的一些常用功能

    //4.实例化 发布者 对象
    //泛型: 发布的消息类型
    //参数1: 要发布到的话题
    //参数2: 队列中最大保存的消息数,超出此阀值时,先进的先销毁(时间早的先销毁)
    ros::Publisher pub = nh.advertise<std_msgs::String>("chatter",10);

    //5.组织被发布的数据,并编写逻辑发布数据
    //数据(动态组织)
    std_msgs::String msg;
    // msg.data = "你好啊!!!";
    std::string msg_front = "Hello 你好!"; //消息前缀
    int count = 0; //消息计数器

    //逻辑(一秒10次)
    ros::Rate r(1);

    //节点不死
    while (ros::ok())
    {
    
    
        //使用 stringstream 拼接字符串与编号
        std::stringstream ss;
        ss << msg_front << count;
        msg.data = ss.str();
        //发布消息
        pub.publish(msg);
        //加入调试,打印发送的消息
        ROS_INFO("发送的消息:%s",msg.data.c_str());

        //根据前面制定的发送贫频率自动休眠 休眠时间 = 1/频率;
        r.sleep();
        count++;//循环结束前,让 count 自增
        //暂无应用
        ros::spinOnce();
    }

    return 0;
}

5. Realization of subscribers

/*
    需求: 实现基本的话题通信,一方发布数据,一方接收数据,
         实现的关键点:
         1.发送方
         2.接收方
         3.数据(此处为普通文本)
    消息订阅方:
        订阅话题并打印接收到的消息
    实现流程:
        1.包含头文件 
        2.初始化 ROS 节点:命名(唯一)
        3.实例化 ROS 句柄
        4.实例化 订阅者 对象
        5.处理订阅的消息(回调函数)
        6.设置循环调用回调函数

*/
// 1.包含头文件 
#include "ros/ros.h"
#include "std_msgs/String.h"

void doMsg(const std_msgs::String::ConstPtr& msg_p){
    
    
    ROS_INFO("我听见:%s",msg_p->data.c_str());
    // ROS_INFO("我听见:%s",(*msg_p).data.c_str());
}
int main(int argc, char  *argv[])
{
    
    
    setlocale(LC_ALL,"");
    //2.初始化 ROS 节点:命名(唯一)
    ros::init(argc,argv,"listener");
    //3.实例化 ROS 句柄
    ros::NodeHandle nh;

    //4.实例化 订阅者 对象
    ros::Subscriber sub = nh.subscribe<std_msgs::String>("chatter",10,doMsg);
    //5.处理订阅的消息(回调函数)

    //     6.设置循环调用回调函数
    ros::spin();//循环读取接收的数据,并调用回调函数处理

    return 0;
}

6. Configure CMakeLists.txt

add_executable(Hello_pub
  src/Hello_pub.cpp
)
add_executable(Hello_sub
  src/Hello_sub.cpp
)

target_link_libraries(Hello_pub
  ${catkin_LIBRARIES}
)
target_link_libraries(Hello_sub
  ${catkin_LIBRARIES}
)

7. Execution

  1. start roscore
  2. Start the publishing node
  3. Start the subscription node

The final effect is as follows, you rosrun rqt_graph rqt_graphcan view the calculation graph with


3. Python implementation of basic operations of topic communication

1. Process

  1. Write the publisher implementation
  2. Write the Subscriber Implementation
  3. Add executable permissions to python files
  4. edit configuration file
  5. compile and execute

2. Publisher

#! /usr/bin/env python
"""
    需求: 实现基本的话题通信,一方发布数据,一方接收数据,
         实现的关键点:
         1.发送方
         2.接收方
         3.数据(此处为普通文本)

         PS: 二者需要设置相同的话题


    消息发布方:
        循环发布信息:HelloWorld 后缀数字编号

    实现流程:
        1.导包 
        2.初始化 ROS 节点:命名(唯一)
        3.实例化 发布者 对象
        4.组织被发布的数据,并编写逻辑发布数据


"""
#1.导包 
import rospy
from std_msgs.msg import String

if __name__ == "__main__":
    #2.初始化 ROS 节点:命名(唯一)
    rospy.init_node("talker_p")
    #3.实例化 发布者 对象
    pub = rospy.Publisher("chatter",String,queue_size=10)
    #4.组织被发布的数据,并编写逻辑发布数据
    msg = String()  #创建 msg 对象
    msg_front = "hello 你好"
    count = 0  #计数器 
    # 设置循环频率
    rate = rospy.Rate(1)
    while not rospy.is_shutdown():

        #拼接字符串
        msg.data = msg_front + str(count)

        pub.publish(msg)
        rate.sleep()
        rospy.loginfo("写出的数据:%s",msg.data)
        count += 1

2. Subscriber

#! /usr/bin/env python
"""
    需求: 实现基本的话题通信,一方发布数据,一方接收数据,
         实现的关键点:
         1.发送方
         2.接收方
         3.数据(此处为普通文本)


    消息订阅方:
        订阅话题并打印接收到的消息

    实现流程:
        1.导包 
        2.初始化 ROS 节点:命名(唯一)
        3.实例化 订阅者 对象
        4.处理订阅的消息(回调函数)
        5.设置循环调用回调函数



"""
#1.导包 
import rospy
from std_msgs.msg import String

def doMsg(msg):
    rospy.loginfo("I heard:%s",msg.data)

if __name__ == "__main__":
    #2.初始化 ROS 节点:命名(唯一)
    rospy.init_node("listener_p")
    #3.实例化 订阅者 对象
    sub = rospy.Subscriber("chatter",String,doMsg,queue_size=10)
    #4.处理订阅的消息(回调函数)
    #5.设置循环调用回调函数
    rospy.spin()

3. Add executable permissions

cd scripts
chomd +x *.py

4. Configure CMakeLists.txt

catkin_install_python(PROGRAMS
  scripts/talker_p.py
  scripts/listener_p.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

specific implementation effect


4. Custom msg for topic communication

In the ROS communication protocol, the data carrier is an important part. ROS encapsulates some native data types through std_msgs, such as: String, Int32, Int64, Char, Bool, Empty... However, these data generally only contain one data The single field and structure means functional limitations. When transmitting some complex data, such as: lidar information... std_msgs is powerless due to poor descriptiveness. In this scenario, a custom message type can be used

msgs are simply text files, each line has a field type and field name, the field types that can be used are:

  • int8, int16, int32, int64 (or unsigned type: uint*)
  • float32,float64
  • string
  • time,duration
  • other msg files
  • variable-length array[] and fixed-length array[C]

There is also a special type in ROS: Header, the header contains the timestamp and coordinate frame information commonly used in ROS, and it is often seen that the first line of the msg file has a Headerheader.

1. Demand

Create a custom message that includes information about the person: name, height, age, etc.

2. Process

  1. Create msg files in a fixed format
  2. edit configuration file
  3. Compile and generate intermediate files that can be called by Python and Cpp

3. Define the msg file

Create a new msg directory under the function package and add filesPerson.msg

string name
uint16 age
float64 height

4. Edit configuration file

package.xmlAdd compilation dependencies and execution dependencies to

  <build_depend>message_generation</build_depend>
  <exec_depend>message_runtime</exec_depend>
  <!-- 
  exce_depend 以前对应的是 run_depend 现在非法
  -->

CMakeLists.txtEdit msg related configuration

find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)
# 需要加入 message_generation,必须有 std_msgs
## 配置 msg 源文件
add_message_files(
  FILES
  Person.msg
)
# 生成消息时依赖于 std_msgs
generate_messages(
  DEPENDENCIES
  std_msgs
)
#执行时依赖
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES demo02_talker_listener
  CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

5. Compile

Compiled intermediate file view

The intermediate file that C++ needs to call (.../workspace/devel/include/package name/xxx.h)

Intermediate files that Python needs to call (.../workspace/devel/lib/python3/dist-packages/package name/msg)

Subsequent calls to related msgs are called from these intermediate files


5. Cpp implementation of topic communication custom msg call

0. vscode configuration

In order to facilitate code prompts and avoid throwing exceptions by mistake, you need to configure vscode first, and configure the previously generated headfile path into c_pp_properties.jsonthe includepathproperties:

{
    
    
    "configurations": [
        {
    
    
            "browse": {
    
    
                "databaseFilename": "",
                "limitSymbolsToIncludedHeaders": true
            },
            "includePath": [
                "/opt/ros/noetic/include/**",
                "/usr/include/**",
                "/xxx/yyy工作空间/devel/include/**" //配置 head 文件的路径 
            ],
            "name": "ROS",
            "intelliSenseMode": "gcc-x64",
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c11",
            "cppStandard": "c++17"
        }
    ],
    "version": 4
}

1. Publisher

/*
    需求: 循环发布人的信息

*/

#include "ros/ros.h"
#include "demo02_talker_listener/Person.h"

int main(int argc, char *argv[])
{
    
    
    setlocale(LC_ALL,"");

    //1.初始化 ROS 节点
    ros::init(argc,argv,"talker_person");

    //2.创建 ROS 句柄
    ros::NodeHandle nh;

    //3.创建发布者对象
    ros::Publisher pub = nh.advertise<demo02_talker_listener::Person>("chatter_person",1000);

    //4.组织被发布的消息,编写发布逻辑并发布消息
    demo02_talker_listener::Person p;
    p.name = "sunwukong";
    p.age = 2000;
    p.height = 1.45;

    ros::Rate r(1);
    while (ros::ok())
    {
    
    
        pub.publish(p);
        p.age += 1;
        ROS_INFO("我叫:%s,今年%d岁,高%.2f米", p.name.c_str(), p.age, p.height);

        r.sleep();
        ros::spinOnce();
    }



    return 0;
}

2. Subscriber

/*
    需求: 订阅人的信息

*/

#include "ros/ros.h"
#include "demo02_talker_listener/Person.h"

void doPerson(const demo02_talker_listener::Person::ConstPtr& person_p){
    
    
    ROS_INFO("订阅的人信息:%s, %d, %.2f", person_p->name.c_str(), person_p->age, person_p->height);
}

int main(int argc, char *argv[])
{
    
       
    setlocale(LC_ALL,"");

    //1.初始化 ROS 节点
    ros::init(argc,argv,"listener_person");
    //2.创建 ROS 句柄
    ros::NodeHandle nh;
    //3.创建订阅对象
    ros::Subscriber sub = nh.subscribe<demo02_talker_listener::Person>("chatter_person",10,doPerson);

    //4.回调函数中处理 person

    //5.ros::spin();
    ros::spin();    
    return 0;
}

3. Configure CMakeLists.txt

Need to add add_dependenciesintermediate files to set the dependent messages

add_executable(person_talker src/person_talker.cpp)
add_executable(person_listener src/person_listener.cpp)



add_dependencies(person_talker ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(person_listener ${PROJECT_NAME}_generate_messages_cpp)


target_link_libraries(person_talker
  ${catkin_LIBRARIES}
)
target_link_libraries(person_listener
  ${catkin_LIBRARIES}
)

4. Execution

1. Start roscore

2. Start the publishing node

3. Start the subscription node

The effect is shown in the figure


6. Python implementation of topic communication custom msg

0. vscode configuration

In order to facilitate code prompts and throw exceptions by mistake, you need to configure vscode first, and configure the previously generated python file path intosettings.json

{
    
    
    "python.autoComplete.extraPaths": [
        "/opt/ros/noetic/lib/python3/dist-packages",
        "/xxx/yyy工作空间/devel/lib/python3/dist-packages"
    ]
}

1. Publisher

#! /usr/bin/env python
"""
    发布方:
        循环发送消息

"""
import rospy
from demo02_talker_listener.msg import Person


if __name__ == "__main__":
    #1.初始化 ROS 节点
    rospy.init_node("talker_person_p")
    #2.创建发布者对象
    pub = rospy.Publisher("chatter_person",Person,queue_size=10)
    #3.组织消息
    p = Person()
    p.name = "葫芦瓦"
    p.age = 18
    p.height = 0.75

    #4.编写消息发布逻辑
    rate = rospy.Rate(1)
    while not rospy.is_shutdown():
        pub.publish(p)  #发布消息
        rate.sleep()  #休眠
        rospy.loginfo("姓名:%s, 年龄:%d, 身高:%.2f",p.name, p.age, p.height)

2. Subscriber

#! /usr/bin/env python
"""
    订阅方:
        订阅消息

"""
import rospy
from demo02_talker_listener.msg import Person

def doPerson(p):
    rospy.loginfo("接收到的人的信息:%s, %d, %.2f",p.name, p.age, p.height)


if __name__ == "__main__":
    #1.初始化节点
    rospy.init_node("listener_person_p")
    #2.创建订阅者对象
    sub = rospy.Subscriber("chatter_person",Person,doPerson,queue_size=10)
    rospy.spin() #4.循环

3. Permission configuration

chmod +x *.py

4. Configure CMakeLists.txt

catkin_install_python(PROGRAMS
  scripts/talker_p.py
  scripts/listener_p.py
  scripts/person_talker.py
  scripts/person_listener.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

5. Execution

1. Start roscore;

2. Start the publishing node;

3. Start the subscription node.

The effect is as follows:


7. Reference

http://www.autolabor.com.cn/book/ROSTutorials/di-2-zhang-ros-jia-gou-she-ji/22hua-ti-tong-xin/213-hua-ti-tong-xin-zhi-python-shi-xian.html

Guess you like

Origin blog.csdn.net/qq_44940689/article/details/129215609