[ROS 学習ノート 4] トピック コミュニケーション

[ROS 学習ノート 4] トピック コミュニケーション


0.トピックコミュニケーションの概要

トピック通信は ROS で最も頻繁に使用される通信方法です. トピック通信はパブリッシュ-サブスクライブ モデルに基づいています. つまり, あるノードがメッセージをパブリッシュし, 別のノードがメッセージをサブスクライブします. トピック コミュニケーションの適用シナリオも非常に広範です。

コンセプト

パブリッシュ/サブスクライブ方式で異なるノード間のデータ相互作用を実装する通信モード

効果

常に更新され、論理的にあまり処理されないデータ転送シナリオ用。


1. トピックコミュニケーションの理論モデル

トピック通信の実装モデルは比較的複雑です. モデルは下の図に示されています. モデルには3つの役割があります.

  • ROSマスター(マネージャー)
  • トーカー(出版社)
  • リスナー (サブスクライバー)

ROS Master は、Talker と Listener の登録情報を保持し、Talker と Listener を同じトピックに一致させ、Talker と Listener が接続を確立するのを支援します. 接続が確立されると、Talker はメッセージを発行でき、発行されたメッセージは購読されますリスナーによる。

プロセス全体は、次の手順で実現されます。

0.話者登録

Talker が起動すると、RPC を介して ROS Master に自身の情報を登録します。これには、発行されたメッセージのトピック名が含まれます。ROS マスターは、ノードの登録情報をレジストリに追加します。

1.リスナー登録

Listener が開始されると、メッセージをサブスクライブする必要があるトピック名など、独自の情報も RPC を介して ROS マスターに登録します。ROS マスターは、ノードの登録情報をレジストリに追加します。

2. ROSマスターが情報マッチングを実装

ROS Master は、レジストリの情報に従って Talker と Listener を照合し、Talker の RPC アドレス情報を RPC 経由で Listener に送信します。

3. リスナーがトーカーにリクエストを送信する

Talker は Listener の要求を受信した後、RPC を介して Listener への接続情報も確認し、自身の TCP アドレス情報を送信します。

4. リスナーはトーカーとの接続を確立します

リスナーは、手順 4 で返されたメッセージに従って、TCP を使用してトーカーとのネットワーク接続を確立します。

5. トーカーがリスナーにメッセージを送る

接続が確立されると、Talker は Listener へのメッセージの発行を開始します。

注意 1: 上記の実装プロセスでは、最初の 5 つのステップで RPC プロトコルを使用し、最後の 2 つのステップで TCP プロトコルを使用します。

注意 2: Talker と Listener の起動にシーケンス要件はありません。

注意 3: Talker と Listener の両方に複数の

注意 4: Talker と Listener 間の接続が確立されると、ROS Master は不要になります。

Attention5: 上記のプロセスはカプセル化されており、将来的に直接呼び出すことができます。

トピック コミュニケーションの焦点:

Point1: 実装のほとんどがカプセル化されている

Point2: トピック設定

Point3: パブリッシャーの実装に注力

Point4: サブスクライバーの実装に注力する

Point5: メッセージキャリアに注意


2.トピック通信の基本操作のCpp実装

1.需要

パブリッシャーが 10HZ の頻度でテキスト メッセージをパブリッシュし、サブスクライバーがメッセージ コンテンツを出力することを要求する、パブリッシュ/サブスクライブの実装を作成します。

2.分析

モデルの実装では、ROS Masterが自動で実装されており、再度実装する必要がなく、接続パッケージもパッケージ化されています。

  1. 出版社
  2. 受信機
  3. データ(ここではプレーンテキスト)

3. プロセス

  1. パブリッシャーの実装を書く
  2. サブスクライバーの実装を作成する
  3. 構成ファイルを編集する
  4. コンパイルして実行する

4. 発行者による実施

/*
    需求: 实现基本的话题通信,一方发布数据,一方接收数据,
         实现的关键点:
         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.加入者の実現

/*
    需求: 实现基本的话题通信,一方发布数据,一方接收数据,
         实现的关键点:
         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. 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.実行

  1. ロススコアを開始
  2. パブリッシング ノードを開始する
  3. サブスクリプション ノードを開始する

最終的な効果は次のとおりです。rosrun rqt_graph rqt_graph計算グラフは次のように表示できます


3.トピック通信の基本操作のPython実装

1. プロセス

  1. パブリッシャーの実装を書く
  2. サブスクライバーの実装を作成する
  3. Python ファイルに実行権限を追加する
  4. 構成ファイルを編集する
  5. コンパイルして実行する

2.発行者

#! /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.加入者

#! /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.実行権限を追加する

cd scripts
chomd +x *.py

4. CMakeLists.txt を構成する

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

具体的な実施効果


4.トピックコミュニケーション用のカスタムメッセージ

ROS 通信プロトコルでは, データ キャリアは重要な部分です. ROS は std_msgs を介して次のようなネイティブ データ型をカプセル化します: String, Int32, Int64, Char, Bool, Empty... しかし、これらのデータは通常 1 つのデータしか含みません。単一のフィールドと構造は機能上の制限を意味します. いくつかの複雑なデータを送信する場合, LIDAR 情報など. std_msgs は記述性が低いため無力です. このシナリオでは, カスタムメッセージタイプを使用できます.

メッセージは単純なテキスト ファイルで、各行にはフィールド タイプとフィールド名があり、使用できるフィールド タイプは次のとおりです。

  • int8、int16、int32、int64 (または unsigned 型: uint*)
  • float32,float64
  • 時間、期間
  • その他の msg ファイル
  • 可変長配列[]と固定長配列[C]

ROS には特別なタイプもあります: Header, ヘッダーにはタイムスタンプと ROS で一般的に使用される座標フレーム情報が含まれます. msg ファイルの最初の行にヘッダーがあることがよく見られますHeader.

1.需要

名前、身長、年齢など、個人に関する情報を含むカスタム メッセージを作成します。

2. プロセス

  1. msg ファイルを固定形式で作成する
  2. 構成ファイルを編集する
  3. Python と Cpp で呼び出すことができる中間ファイルをコンパイルして生成する

3. msg ファイルを定義する

関数パッケージの下に新しい msg ディレクトリを作成し、ファイルを追加します。Person.msg

string name
uint16 age
float64 height

4. 設定ファイルの編集

package.xmlコンパイルの依存関係と実行の依存関係をに追加します

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

CMakeLists.txtメッセージ関連の構成を編集する

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. コンパイル

コンパイル済み中間ファイル ビュー

C++ が呼び出す必要がある中間ファイル (.../workspace/devel/include/package name/xxx.h)

Python が呼び出す必要のある中間ファイル (.../workspace/devel/lib/python3/dist-packages/package name/msg)

関連するメッセージへの後続の呼び出しは、これらの中間ファイルから呼び出されます


5. トピック通信カスタム msg 呼び出しの Cpp 実装

0.vscode 設定

コード プロンプトを容易にし、誤って例外をスローするのを避けるために、最初に vscode を構成し、以前に生成されたファイルheadパスをプロパティにc_pp_properties.json構成する必要がありますincludepath

{
    
    
    "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.発行者

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

*/

#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.加入者

/*
    需求: 订阅人的信息

*/

#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. CMakeLists.txt を構成する

add_dependencies依存メッセージを設定するために中間ファイルを追加する必要があります

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.実行

1.ロスコアを起動する

2.発行ノードを開始します

3. サブスクリプション ノードを開始する

効果は図に示されています


6.トピック通信カスタムメッセージのPython実装

0.vscode 設定

コード プロンプトを容易にし、誤って例外をスローするには、最初に vscode を構成し、以前に生成された Python ファイル パスを次の場所に構成する必要があります。settings.json

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

1.発行者

#! /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.加入者

#! /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.権限設定

chmod +x *.py

4. 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.実行

1. roscore を開始します。

2. パブリッシング ノードを開始します。

3. サブスクリプション ノードを開始します。

効果は次のとおりです。


7. 参照

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

おすすめ

転載: blog.csdn.net/qq_44940689/article/details/129215609