[ROS] Topic communication

Topic communication is the most frequently used communication mode 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 wide, such as the following common scenario:

The robot is performing the navigation function, and the sensor used is the lidar. The robot will collect and calculate the information sensed by the lidar, and then generate motion control information to drive the robot chassis to move.

In the above scenario, topic communication is used more than once.

  • Taking the collection and processing of lidar information as an example, there is a node in ROS that releases the data collected by the current radar from time to time when needed, and there are also nodes in the navigation module that will subscribe to and parse the radar data.

  • Taking the release of motion information as an example, the navigation module will calculate the motion control information from time to time based on the data collected by the sensor and publish it to the chassis. The chassis can also have a node to subscribe to the motion information and finally convert it into a pulse signal for controlling the motor.

By analogy, the collection of sensor data such as radar, camera, GPS, etc., also uses topical communication. In other words, topical communication is suitable for constantly updated data transmission-related application scenarios.

concept

The communication mode of data interaction between different nodes is realized in the way of publish and subscribe.

effect

It is used for data transmission scenarios that are constantly updated and less logically processed.

1 Theoretical model

The topic communication implementation model is more complicated. The model is shown in the figure below. Three roles are involved in the model:

  • ROS Master (Manager)

  • Talker (publisher)

  • Listener (subscriber)

ROS Master is responsible for keeping the information registered by Talker and Listener, and matching Talker and Listener with the same topic, helping Talker and Listener to 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 via RPC, which contains 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 name of the topic that needs to be subscribed to. ROS Master will add the registration information of the node to the registry.

(2). ROS Master realizes 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

According to the received RPC address, the Listener sends a connection request to the Talker via RPC, and transmits the subscribed topic name, message type and communication protocol (TCP/UDP).

(4) Talker confirmation request

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.

(5). Listener and Talker are connected

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

(6) Talker sends messages to Listener

After the connection is established, Talker starts to publish messages to the Listener.

Note 1: In the above implementation process, the RPC protocol used in the first five steps, and the TCP protocol used in the last two steps

Note 2: There is no order requirement for the startup of Talker and Listener

Note 3: There can be multiple Talker and Listener

Note 4: After the connection between Talker and Listener is established, ROS Master is no longer needed. That is, even if ROS Master is closed, Talker and Listern communicate as usual.

Points to pay attention to in topic communication:

0: Most of the implementation has been encapsulated;

1: Topic setting;

2: Follow the publisher's implementation;

3: Follow the subscriber's realization;

4: Pay attention to the message carrier

2 Basic operation of topic communication A (C++)

demand:

Write a publish and subscribe implementation, requiring the publisher to publish text messages at a frequency of 10HZ (10 times per second), and the subscriber to subscribe to the message and print out the content of the message.

analysis:

In the model implementation, the ROS master does not need to be implemented, and the connection establishment has been encapsulated. There are three key points that need to be paid attention to:

(1) Issuer

(2) Recipient

(3) Data (normal text here)

Process:

(1) Write the release party to achieve;

(2) Write subscriber implementation;

(3) Edit the configuration file;

(4) Compile and execute.

1. Issuer

Simple version

#include "ros/ros.h"
#include  "std_msgs/String.h"

/**
 * 发布方实现:
 *          1 包含头文件;   ROS中的文本类型是-------》std_msgs/String.h
 *          2 初始化ros结点;
 *          3 创建结点句柄;
 *          4 创建发布者对象;
 *          5 编写发布逻辑并发布数据
*/

int main(int argc, char  *argv[])
{

    //设置编码
    setlocale(LC_ALL,"");

  	//2.初始化 ROS 节点:命名(唯一)
    // 参数1和参数2 后期为节点传值会使用
    // 参数3 是节点名称,是一个标识符,需要保证运行后,在 ROS 网络拓扑中唯一
    ros::init(argc,argv,"pub");

    // 3  创建结点句柄;
    ros::NodeHandle nh;    //该类封装了 ROS 中的一些常用功能

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

    // 5 编写发布逻辑并发布数据【循环发布】
    //先创建被发布的消息
    std_msgs::String msg;


    //编写循环,循环中发布数据
    while(ros::ok())          //如果结点还存在,则一直循环
    {
        msg.data = "hello";
        pub.publish(msg);               //发布数据
    }

    return 0;
}

Modify the configuration file again

ctrl+shift+b
roscore
source ./devel/setup.bash
rosrun plumbing_pub_sub demo01_pub_c

Simple test

 rostopic echo fang

 

Request to publish data at a frequency of 10hz, and add a number after the text

#include "ros/ros.h"
#include  "std_msgs/String.h"
#include <sstream>

/**
 * 发布方实现:
 *          1 包含头文件;   ROS中的文本类型是-------》std_msgs/String.h
 *          2 初始化ros结点;
 *          3 创建结点句柄;
 *          4 创建发布者对象;
 *          5 编写发布逻辑并发布数据
*/

int main(int argc, char  *argv[])
{

    //设置编码
    setlocale(LC_ALL,"");


    //2 初始化ros结点
    ros::init(argc,argv,"pub");


    //3 创建结点句柄
    ros::NodeHandle nh;


    //4 创建发布者对象
    ros::Publisher publisher = nh.advertise<std_msgs::String>("fang",10);


    //5 编写发布逻辑并且发布数据
    //5.5要求以10hz的频率发布数据,并且在文本后添加编号
    //5.1先创建被发布的消息
    std_msgs::String msg;

    //5.6 设置发布频率
    ros::Rate rate(10);            //10hz的频率

    //5.2编写循环,在循环中发布数据

    int count = 0;

    while(ros::ok())
    {

        count++;
     //   msg.data = "hello";                  //5.3数据赋值

        //实现字符串拼接数据
        std::stringstream ss;
        ss<<"hello----->"<<count;
        msg.data = ss.str();         //将ss流的数据转换成字符串付给msg.data

        publisher.publish(msg);                  //5.4发布数据

        //添加日志
        ROS_INFO("发布的数据是:%s",ss.str().c_str());

        rate.sleep();
    }

    return 0;
}
编译
ctrl+shift+b
roscore
source ./devel/setup.bash
rosrun plumbing_pub_sub demo01_pub

查看
rostopic echo fang

2. Subscriber

#include "ros/ros.h"
#include "std_msgs/String.h"



/**
 * 订阅方实现:
 *          1 包含头文件;   ROS中的文本类型是-------》std_msgs/String.h
 *          2 初始化ros结点;
 *          3 创建结点句柄;
 *          4 创建订阅者者对象;
 *          5 处理订阅到的数据
 *          6 声明spin函数
*/

  //  5 处理订阅到的数据
//编写回调函数:参数是订阅到的消息
void doMsg(const std_msgs::String::ConstPtr &msg)
{
    //通过msg参数获取并操作订阅的数据
    ROS_INFO("订阅到数据是:%s",msg->data.c_str());
}

int main(int argc, char  *argv[])
{
   
     // 2 初始化ros结点;
    ros::init(argc,argv,"sub");

    // 3 创建结点句柄;
    ros::NodeHandle nh;

    // 4 创建订阅者者对象;
    ros::Subscriber subscriber = nh.subscribe("fang",10,doMsg);

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

    return 0;
}

Modify the configuration file

编译
ctrl+shift+b
roscore
source ./devel/setup.bash
rosrun plumbing_pub_sub demo02_sub_c

3 Attention

Supplement 0:

The main function in vscode declares int main(int argc, char const *argv[]){}, the default generated argv is modified by const, this modifier needs to be removed

Supplement 1:

ros/ros.h No such file or directory .....

Check CMakeList.txt and find_package is duplicated, just delete the ones with less content

Reference material: https://answers.ros.org/question/237494/fatal-error-rosrosh-no-such-file-or-directory/

Supplement 2:

Find_package can run without adding some packages. The answer from ros.wiki is as follows

 You may notice that sometimes your project builds fine even if you did not call find_package with all dependencies. This is because catkin combines all your projects into one, so if an earlier project calls find_package, yours is configured with the same values. But forgetting the call means your project can easily break when built in isolation.

Supplement 3:

When subscribing, the previous data is lost

Run the subscriber first, then the publisher

Reason: When sending the previous data, the publisher has not yet registered with roscore, so it cannot be subscribed.

Solution: After registration, add sleep ros::Duration(3.0).sleep(); delay the sending of the first data

You can use rqt_graph to view the node relationship.

3 topic communication custom msg

In the ROS communication protocol, the data carrier is a relatively important part. In ROS, some native data types are encapsulated through std_msgs, such as: String, Int32, Int64, Char, Bool, Empty... However, these data are generally only Contains a data field. The single structure means functional limitations. When transmitting some complex data, such as: lidar information... std_msgs is weak due to poor descriptiveness. You can use custom in this scenario Message type

msgs is just a simple text file, each line has 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:, the Headerheader contains time stamp and coordinate frame information commonly used in ROS. You will often see that the first line of the msg file has Header标头.

Requirements: Create a custom message, the message contains the person's information: name, height, age, etc.

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 or C++

1. Define the msg file

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

string name
uint16 age
float64 height

2. Edit the configuration file

Add compilation dependency and execution dependency in package.xml

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

CMakeLists.txt edit 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
)

3. Compile

View the compiled intermediate file:

Intermediate files that C++ needs to call (.../workspace/devel/include/package name/xxx.h)

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

When the related msg is subsequently called, it is called from these intermediate files

4 topic communication custom msg call A (C++)

demand:

Write a publish and subscribe implementation, requiring the publisher to publish a custom message at a frequency of 10HZ (10 times per second), and the subscriber to subscribe to the custom message and print out the content of the message.

analysis:

In the model implementation, the ROS master does not need to be implemented, and the connection establishment has been encapsulated. There are three key points that need to be paid attention to:

  1. Publisher

  2. receiver

  3. Data (custom message here)

Process:

  1. Write the publisher's implementation;

  2. Write subscriber implementation;

  3. Edit the configuration file;

  4. Compile and execute.

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 head file path into the includepath property of c_cpp_properties.json:

{
    "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. Issuer

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

/*
发布方:发布人消息
            1 包含头文件
            2 初始化ros结点
            3 创建结点句柄
            4 创建发布者对象
            5 编写发布逻辑,发布数据


*/

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

    //2 初始化ros结点
    ros::init(argc,argv,"banZhuRen");

    //3 创建结点句柄
    ros::NodeHandle nh;

    // 4 创建发布者对象
       ros::Publisher pubPerson = nh.advertise<plumbing_pub_sub::Person>("chat",100);

    //5 编写发布逻辑,发布数据
    //创建被发布的数据
    plumbing_pub_sub::Person person;
    person.name = "张三";
    person.age = 18;
    person.height = 1.73;

    //发布的频率
    ros::Rate  rate(1);
    
    //循环发布数据
    while(ros::ok())
    {
       //修改被发布的数据
       person.age++;


       //核心是发布数据
       pubPerson.publish(person);

       //休眠
       rate.sleep();

       //回头函数
       ros::spinOnce();
    }



    return 0;
}

Configuration file

To use custom msg, you also need to configure

add_dependencies(demo03_sub_person ${PROJECT_NAME}_generate_messages_cpp)

Ensure that when compiling, compile the customized msg first, and compile the file that calls msg

Compile and test

roscore
source ./devel/setup.bash
rosrun plumbing_pub_sub demo03_pub_person 
这里不能直接rostopic echo chat
需要先进入工作空间,再source ./devel/setup.bash
然后rostopic echo chat

2. Subscriber

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

/**
 * *
  发布方:发布人消息
            1 包含头文件
            2 初始化ros结点
            3 创建结点句柄
            4 创建订阅者对象
            5 编写回调函数,处理数据
            6 spin()函数


*/

//回调函数
void doMsg(const plumbing_pub_sub::Person::ConstPtr& person)
{
    ROS_INFO("订阅的人的信息是:%s,%d,%.2f",person->name.c_str(),person->age,person->height);
}


int main(int argc, char  *argv[])
{
    
    setlocale(LC_ALL,"");
    ROS_INFO("这是订阅方:");

     //2 初始化ros结点
    ros::init(argc,argv,"student");

    // 3 创建结点句柄
    ros::NodeHandle nh;

    // 4 创建订阅者对象
    ros::Subscriber subPerson = nh.subscribe("chat",100,doMsg);

    // 5 编写回调函数,处理数据
   	// 6 spin()函数
    ros::spin();
  
    return 0;
}

Configuration file

Compile and test

roscore
需要先进入工作空间,再source ./devel/setup.bash
然后rosrun plumbing_pub_sub demo03_pub_person 
rosrun plumbing_pub_sub demo04_sub_person 

Calculation graph view

Guess you like

Origin blog.csdn.net/Zhouzi_heng/article/details/114496792