ROS 1 Noetic の基礎

この章では、reComputer Jetson 上の ROS 1 Noetic 開発ワークフローを紹介します。ワークスペース、パッケージ、一般的なツール、トピック/サービス通信、カスタムメッセージ、TF について説明します。

長時間実行するサンプルは ./code/ に保存され、関連する図は ./images/ に保存されています。

目次

7.2.1.1 ROS 1 の紹介

ROS 1 (Robot Operation System 1) は、Open Robotics によってメンテナンスされているオープンソースのロボットソフトウェアフレームワークです。従来の意味でのオペレーティングシステムではなく、ロボットアプリケーションに通信メカニズム、ツールチェーン、共通機能ライブラリを提供し、ロボットソフトウェア開発の難易度を大幅に低減します。ハードウェア抽象化、下位デバイス制御、一般的な機能の実装、プロセス間メッセージング、パッケージ管理など、オペレーティングシステムに必要なサービスを提供します。また、コードの取得、コンパイル、準備、クロスコンピュータ実行に必要なツールとライブラリ関数も提供します。

ROS 1 のリリース

一般的な ROS 1 エディションは以下の通りです:

バージョン名Ubuntuメンテナンス状況
Kinetic16.04停止
Melodic18.04停止
Noetic20.04最後の ROS 1 バージョン (LTS)

この章の以降の例は、ROS 1 の noetic バージョンに基づいています。

ROS の主な目的は、ロボット研究開発のためのコード再利用サポートを提供することです。ROS は、パッケージに封入されたプロセス(「ノード」)と、簡単に共有・公開できるパッケージの分散フレームワークです。ROS はまた、コードリポジトリに似た共同システムをサポートしており、エンジニアリングの協業と普及を実現できます。この設計により、ファイルシステムからユーザーインターフェースまで、完全に独立した意思決定(ROS の制限なし)でエンジニアリングプロジェクトを開発することができます。同時に、すべての作業を基本的な ROS ツールに統合することができます。

ROS 1 の主な特徴

(1) 分散構造(各作業プロセスはノードとして扱われ、ノードマネージャーを使用して管理されます)、

(2) 多言語サポート(例:C++、Python など)、

(3) 優れた柔軟性(単一のノードを作成することも、roslaunch を通じて多数のノードをより大きなプロジェクトに編成することもできます)、

(4) オープンソースコード(ROS は BSD 契約に従い、個人および商用アプリケーションと修正に対して完全に無料です)。

ROS 1 の全体アーキテクチャ

オープンソースコミュニティレベル:開発者間の知識共有、コード、アルゴリズムなどが含まれます。

ファイルシステムレベル:ハードドライブ上にあるコードと実行可能ファイルの説明。

計算スケール:プロセスとプロセス、プロセスとシステム間の通信を反映します。

ROS 1 開発環境の起動

SeeedStudio Jetson Orin Nano Super DevKit は、ローカルシステムバージョンが ubuntu22.04 であり、ROS 1 の使用をサポートしていませんが、ファームウェアに事前評価されています。ただし、弊社が提供する BSP ファームウェアを使用している場合は、以下のコマンドを使用して、Jetson デバイスのターミナルウィンドウで ubuntu22.04 を使用して ROS 1 を含む Docker コンテナを起動できます:

bash
xhost +
sudo docker run -it \
  --net=host \
  --privileged \
  -v /dev:/dev \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  -e DISPLAY=$DISPLAY \
  -e QT_X11_NO_MITSHM=1 \
  ros:noetic

ROS 1 開発環境が事前評価されていない Jetson デバイスを購入した場合は、インストールについてはこちらを参照してください。

計算グラフの概要

ノード

ノードは ROS 1 における最も基本的な計算実装モジュールであり、通常は独立して実行されるプロセスに対応します。 ROS システムは単一のプログラムではなく、複数のノードが連携して動作する分散システムです。

ROS 1 では、各ノードは通常、比較的単一で明確な機能を持ちます。例えば:

センサーデータ取得(カメラ、レーダー、IMU)

アルゴリズム処理(位置推定、地図構築、経路計画)

制御出力(速度制御、電気制御)

データ転送とデバッグ(ログ、可視化)

ROS 1 ノードの特徴

独立プロセス 各ノードは通常、独立した Linux プロセスであり、ノード間の対話は ROS 通信メカニズムを通じて行われます。

疎結合設計 ノード間で関数が直接呼び出されるのではなく、トピック、サービス、アクション、パラメータなどを通じて通信し、システムの拡張とメンテナンスを容易にします。

名前の一意性 各ノードは ROS 計算グラフ内で一意の名前を持つ必要があります。例:/turtle_velocity_publisher

分散実行 ノードは異なるホスト上で実行でき、同じ ROS Master に接続されていれば動作します。

ライフサイクルは ROS Master によって管理 ノードは起動時に自身の情報(名前、配信/購読など)を ROS Master に登録し、各ノードが実行されます。

ROS 1 におけるノード間の一般的な通信手段

トピック 配信/購読モデルを通じてノード間のオフライン通信に使用され、高頻度データストリームに適しています。

サービス リクエスト-レスポンスに基づく同期通信。

アクション 時間のかかるタスクに適用可能で、フィードバックとキャンセルをサポートします。

パラメータサーバー システム実行パラメータの保存用。

ノードは主要な計算実装プロセスです。ROS は多くのノードで構成されています。

入力時に [Tab] キーで補完できます。

以下はノードグラフの例です:

コマンドラインで [rosnode] と入力し、Tab キーをダブルクリックすると、コマンドラインの下に以下のキーワードが表示されます。

ROS コマンドラインツール rosnode:

現在のノードとノード情報は、開発デバッグ時によく必要になるため、これらのよく使うコマンドを覚えておいてください。不可能な場合は、rosnode ヘルプを通じて rosenode コマンドの使用法を確認することもできます。

メッセージ

ノード間の論理リンクとデータ交換はメッセージを通じて実現されます。

コマンドラインで [rosmsg] と入力し、Tab キーをダブルクリックすると、コマンドラインの下に以下のキーワードが表示されます。

ROS コマンドラインツール rosmsg:

トピック

トピックは情報を配信する方法(配信/購読)です。すべてのメッセージは対応するトピックに配信され、各トピックは強い型を持ちます。

ROS のトピックメッセージは TCP/IP または UDP を使用して送信でき、ROS のデフォルトは TCP/IP です。TCP ベースの伝送は TCPROS と呼ばれ、長期間接続です。UDP ベースの UDPROS は低遅延で効率的な伝送モードですが、データが失われやすく、遠隔操作に適しています。

コマンドラインで [rostopic] と入力し、Tab キーをダブルクリックすると、コマンドラインの下に以下のキーワードが表示されます。

ROS コマンドラインツール rostopic:

サービス

サービスもまた、リクエスト-レスポンスモデルに対して一意の名前を持つ必要があります。サービスがノードによって提供される場合、すべてのノードは ROS クライアントが開発したコードを使用して通信できます。

コマンドラインで [rosservice] と入力し、Tab キーをダブルクリックすると、コマンドラインの下に以下のキーワードが表示されます。

ROS コマンドラインツール rosservice:

メッセージログパッケージ

メッセージ記録パッケージは、ROS メッセージデータを保存および再生するためのファイル形式で、.bag ファイルに保存されます。データを保存するための重要なメカニズムです。

コマンドラインで [rosbag] と入力し、Tab キーをダブルクリックすると、コマンドラインの下に以下のキーワードが表示されます。

ROS コマンドラインツール rosbag:

パラメータサーバー

パラメータサーバーは、オンラインでアクセス可能な共有多変数辞書であり、キーワードでノードマネージャーに保存されます。

コマンドラインで [rosparam] と入力し、Tab キーをダブルクリックすると、コマンドラインの下に以下のキーワードが表示されます。

ROS コマンドラインツール rosparam:

ノードマネージャー (Master)

ノードマネージャーは、トピック、サービス名の登録と検索などに使用されます。ROS システム全体にノードマネージャーがないと、ノード間の通信は行われません。

ファイルシステムレベル

パッケージ間の依存関係を設定できます。パッケージ A がパッケージ B に依存する場合、B は ROS ビルドシステムで A よりも古くなければならず、A は B のヘッダーとライブラリファイルを使用できます。

ドキュメントシステムレベルの概念は以下の通りです:

機能パッケージリスト:

このリストは、パッケージの依存関係、ソースドキュメントのドキュメントなどを示します。パッケージの package.xml ファイルはパッケージリストです。

機能パッケージ:

パッケージは ROS システムにおけるソフトウェア組織の基本形式であり、実行ノードと設定ファイルなどを含みます。

ROS パッケージ関連コマンド

統合機能キット

複数のパッケージの組み合わせを形成できます。

メッセージタイプ

ROS のノード間でメッセージを送信するには、事前の情報ノートが必要です。ROS では標準タイプのメッセージが提供されており、独自に定義することもできます。メッセージタイプの説明は、パッケージの下の msg ファイルに保存されます。

サービスタイプ

ROS の各プロセスが提供するサービスリクエストとレスポンスのデータ構造が定義されています。

オープンソースコミュニティレベル

ディストリビューション:ROS リリースは、バージョン番号付きで独立してインストールできる統合パッケージのシリーズです。ROS リリースは Linux ディストリビューションと同様の役割を果たします。これにより、ROS ソフトウェアのインストールが容易になり、ソフトウェアプールを通じて一貫したバージョンを維持できます。

リポジトリ:ROS は、共有オープンソースおよびソフトウェアリポジトリのウェブサイトまたはホストサービスに依存しており、異なる機関が自身のロボットソフトウェアとプログラムを公開・共有できます。

ROSWiki:ROSWiki は ROS システムに関する情報を記録するための主要なフォーラムです。誰でもアカウントを登録し、自身のドキュメントを投稿したり、修正や更新を提供したり、カリキュラムを作成したりできます。

バグチケットシステム:問題を発見したり、新しい機能を提案したい場合、ROS はこれを行うためのリソースを提供します。

メーリングリスト:ROS ユーザーメーリングリストは、ROS の主要なコミュニケーションチャネルであり、ROS ソフトウェアの更新から ROS ソフトウェアの使用に関する質問や情報の交換が可能で、フォーラムと同様です。

ROS Answer:ユーザーはこのリソースを使用して質問できます。

通信メカニズムの概要

トピック

配信-購読通信モードは ROS で広く使用されています。トピックは一般的に一方向のストリーミング通信に使用されます。トピックは通常、強い型定義を持ちます:トピックのタイプは特定のデータ型のメッセージのみを受け入れ/送信できます。パブリッシャーは型の一貫性を要求されませんが、サブスクライバーは受信時に md5 の型をチェックし、エラーが発生します。

サービス

サービスは ROS 通信で同期通信を処理するために使用され、サーバー/クライアントのセマンティクスを使用します。各サービスタイプには request と response の 2 つの部分があります。serviceserver の場合、ROS はエイリアスをチェックせず、最後に登録されたサーバーのみが有効で、クライアントに接続されます。

アクション

アクションは複数のトピックを使用してタスクを定義し、目標 (Goal)、フィードバック (Feedback)、結果 (Result) を含みます。アクションのコンパイルにより、Action、ActionGoal、ActionFeedback、ActionResult、Goal、Feedback、Result の 7 つの構造が自動的に生成されます。

アクションの特徴:

質問応答通信メカニズム

継続的なフィードバックあり

ミッション中に終了可能

ROS ベースの情報メカニズムで実現

アクションのインターフェース:

Goal:ミッション目標の投稿

Cancel:キャンセルリクエスト

Status:現在の状態をクライアントに通知

Feedback:タスク実行に関する定期的なフィードバックのための制御データ

Result:タスクの結果をクライアントに送信(1 回のみ)

通信パターンの比較

一般的なコンポーネント

launch 起動ファイル;TF 座標変換;Rviz;Gazebo;QT ツールボックス;(a) Navigation;Movelt!

Launch:起動ファイルは、ROS で複数のノードを同時に起動する方法です。ROS Master ノードマネージャーも自動的に起動し、各ノードの設定が可能で、複数ノードの操作を大幅に容易にします。

TF 座標変換:ロボット工学では、作業環境に多数のコンポーネントがあり、ロボット設計やロボットアプリケーションでは異なるコンポーネントの位置と姿勢が関係します。TF は、ユーザーが時間の経過とともに複数の座標系を追跡できるパッケージで、ツリー状のデータ構造を使用し、時間をバッファリングして複数の座標系間の座標間関係を維持します。開発者がいつでも座標変換、座標間の点、ベクトルなどを計算するのを支援します。

QT ツールボックス:視覚的なデバッグと表示を容易にするために、ROS は Qt アーキテクチャのバックグラウンドグラフィックツールパッケージ - rqt 共通プラグインを提供します。これには多くの実用的なツールが含まれています:ログ出力ツール (rqt console)、計算可視化ツール (rqt graph)、データマッピングツール (rqt plot)、パラメータ動的設定ツール (rqt reconfigure)

Rviz:rviz は、ROS ソフトウェアフレームワークに基づく様々なロボットプラットフォームと互換性のある 3 次元可視化ツールです。rviz では、XML を使用してロボット、周囲のオブジェクトなどの寸法、質量、位置、材質、関節などを記述し、インターフェースに表示できます。同時に、rviz はロボットセンサーの情報、ロボットの運動状態、周囲環境の変化などをリアルタイムでグラフィカルに表示できます。

Gazebo:Gazebo は強力な 3 次元物理シミュレーションプラットフォームで、強力な物理エンジン、高品質なグラフィックレンダリング、便利なプログラミングとグラフィックインターフェースを備え、最も重要なのはオープンソースで無料であることです。Gazebo のロボットモデルは rviz で使用されるものと同じですが、質量、摩擦係数、弾性係数などのロボットと周囲環境の物理的特性をモデルに追加する必要があります。ロボットのセンサー情報も、プラグインを通じてシミュレーション環境を追加することで視覚的に表示できます。

Navigation:navigation は ROS 2D ナビゲーションキットで、簡単に言えば、入力走行距離計などの情報の流れとロボットの全体的な位置に基づいて、安全で信頼性の高いロボット速度制御コマンドを計算します。

Moviet:Moviet! 機能キットは最も一般的に使用されるツールキットで、主に軌道計画に使用されます。Moveit! は、計画で使用する必要があるいくつかのドキュメントに対してアシスタントが設定されることが重要です。

すべての ROS 1 リリース

参考リンク:http://wiki.ros.org/Distributions

ROS リリース(ROS ディストリビューション)は、Linux ディストリビューション(例:Ubuntu)と同様の ROS ソフトウェアパッケージを指します。ROS バージョンのロールアウトは、開発者がすべてのコンテンツをアップグレードする準備ができるまで、比較的安定したコードリポジトリを使用できるようにすることを目的としています。そのため、ROS 開発者は通常、各リリースがロールアウトされた後にこのバージョンのバグのみを修正し、少数のコアパッケージの改善を提供します。2019 年 10 月現在、ROS の主要ディストリビューションバージョンの名前、公開日、ライフサイクルは以下の表の通りです:

参考リンク

ROS 公式 wiki:

ROS 公式ガイダンス:http://wiki.ros.org/ROS/Tutorials

ROS インストール:https://wiki.ros.org/noetic/Installation/Ubuntu (ROS が事前インストールされている場合はスキップしてください)

7.2.1.1 Introduction to ROS 1 figure 1

7.2.1.1 Introduction to ROS 1 figure 2

7.2.1.1 Introduction to ROS 1 figure 3

7.2.1.1 Introduction to ROS 1 figure 4

7.2.1.1 Introduction to ROS 1 figure 5

7.2.1.1 Introduction to ROS 1 figure 6

7.2.1.1 Introduction to ROS 1 figure 7

7.2.1.1 Introduction to ROS 1 figure 8

7.2.1.1 Introduction to ROS 1 figure 9

7.2.1.1 Introduction to ROS 1 figure 10

7.2.1.1 Introduction to ROS 1 figure 11

7.2.1.1 Introduction to ROS 1 figure 12

7.2.1.2 ワークスペースの準備

ワークスペースディレクトリ

ROS のドキュメント構造は、すべてのフォルダが必須ではなく、ビジネスニーズに応じて設計されます。

ワークスペースについて

ワークスペースは、ROS プロジェクトのドキュメントを管理・整理する場所です。視覚的には、ROS の様々なプロジェクト作業を含む倉庫であり、システムの管理を容易にします。視覚的なグラフィカルインターフェースではフォルダです。私たち自身の ROS コードは通常ワークスペースにあります。主に以下の 4 つの第 1 階層ディレクトリがあります:

src:ソーススペース;ROS Catkin パッケージ(ソースパッケージ)

build:コンパイルスペース;Catkin (CMake) キャッシュ情報と中間ファイル

devel:開発スペース;ターゲットファイルの出力(ヘッダー、動的リンクライブラリ、静的リンクライブラリ、実行可能ファイルなど)、環境変数

install:インストールスペース

最上位のワークスペース(任意の名前を付けられます)と src(src である必要があります)フォルダは自分で作成する必要があります;

build と devel フォルダは catkin make コマンドによって自動的に作成されます;

install フォルダは catkin make install コマンドによって自動的に作成されますが、ほとんど使用されず、通常は作成されません。

注意:catkin make を使用する前に、ワークスペースを最上位に戻す必要があります。(a) 同じワークスペース下に同名のパッケージが存在することは許可されません。異なるワークスペースでは同名のパッケージが許可されます。

bash
mkdir -p ~/catkin_ws/src  # create
cd catkin_ws/             # enter the workspace
catkin_make               # build
source devel/setup.bash   # source the workspace environment

パッケージ

パッケージは特定のファイル構造とフォルダの組み合わせです。同じ機能を実現するプログラムコードは通常 1 つのパッケージに配置されます。CMakeLists.txt と package.xml のみが [必須] で、残りのパスはパッケージが必要かどうかによって異なります。

機能パッケージの作成

bash
cd ~/catkin_ws/src
catkin_create_pkg my_pkg rospy rosmsg roscpp

[Rospy]、[rosmsg]、[roscpp] は依存ライブラリで、ビジネスニーズに応じて追加したり、他のものを追加したりできます。作成時に再設定せずに、追加を忘れた場合は設定が必要です。

ファイル構造

bash
|-- CMakeLists.txt  # (必須) 現在のパッケージのビルドルール
|—— package.xml     # (必須) パッケージメタデータと ROS 依存関係
|—— include ディレクトリ    # C++ ヘッダーファイルを保存
|—— config ディレクトリ     # パラメータファイルを保存
|—— launch ディレクトリ     # 起動ファイルを保存 (.launch または .xml)
|—— meshes ディレクトリ     # ロボットまたはシミュレーション 3D モデルを保存 (.sda, .stl, .dae など)
|—— urdf ディレクトリ       # ロボットモデル記述を保存 (.urdf または .xacro)
|—— rviz ディレクトリ       # rviz ファイル
|—— src ディレクトリ        # C++ ソースコード
|—— scripts ディレクトリ    # 実行可能スクリプト (shell スクリプト (.sh) や Python スクリプト (.py) など)
|—— srv ディレクトリ        # カスタムサービス
|—— msg ディレクトリ        # カスタムトピック
|—— action ディレクトリ     # カスタムアクション

CMakeLists.txt の概要

概要

CMakeLists.txt はもともと CMake ビルドシステムのルールベースのドキュメントでしたが、Catkin ビルドは CMake スタイルのビルドにほぼ従いながら、ROS プロジェクトにいくつかのマクロ定義を追加しました。そのため、記述において Catkin の CMakeLists.txt は基本的に CMake と同じです。

このドキュメントは、パッケージが依存するプロセス、コンパイルするターゲット、コンパイル方法などを直接定義します。そのため CMakeLists.txt は非常に重要で、ソースコードからターゲットファイルまでのルールを指定し、catkin ビルドはまず各パッケージの下の CMakeLists.txt を見つけて、そのルールに従ってコンパイルとビルドを行います。

フォーマット

CMakeLists.txt の基本構文は CMake と同じで、Catkin が少数のマクロを追加しています。全体の構造は以下の通りです:

典型的な catkin CMakeLists.txt には以下の部分が含まれます:

cmake
cmake_minimum_required(VERSION 3.0.2)
project(package_name)
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs)
catkin_package()
include_directories(${catkin_INCLUDE_DIRS})
add_executable(node_name src/node_name.cpp)
target_link_libraries(node_name ${catkin_LIBRARIES})

カスタムメッセージ、サービス、またはアクションを定義するパッケージでは、add_message_files()add_service_files()add_action_files()generate_messages() も使用します。

Boost の有効化

C++ と Boost を使用する場合、Boost に対して Find package() を呼び出し、Boost のどの側面をコンポーネントとして使用するかを指定する必要があります。例えば、Boost スレッドを使用したい場合は、次のようにします:

Find package

catkin package()

Catkin package() は catkin が提供する CMake マクロです。これはビルドシステムに catkin 固有の情報を割り当てるために必要で、pkg-config および CMake ファイルの生成に使用されます。

この関数は、add library() または add executable() を使用してオブジェクトが宣言される前に呼び出す必要があります。この関数には 5 つのオプションパラメータがあります:

INCLUDE DIRS - インクルードパスのエクスポート

LIBRARIES - プロジェクトからエクスポートされるライブラリ

CATKIN DEPENDS - プロジェクトが依存する他の catkin プロジェクト

DEPENDS - プロジェクトが依存する非 catkin CMake プロジェクト。より良い理解のために、この説明を参照してください。

CFG EXTRAS - その他の設定オプション

完全なマクロドキュメントはこちらで確認できます。

例えば:

bash
catkin_package(
   INCLUDE_DIRS include
   LIBRARIES ${PROJECT_NAME}
   CATKIN_DEPENDS roscpp nodelet
   DEPENDS eigen opencv)

これは、パッケージフォルダ内の "include" フォルダがヘッダーファイルのエクスポート先であることを示します。CMake 環境変数 ${PROJECT_NAME} は、以前に project() 関数に渡されたものを評価し、この場合は "robot_brain" になります。"roscpp" + "nodelet" は、このパッケージをビルド/実行するために存在する必要があるソフトウェアパッケージで、"eigen" + "opencv" は、このパッケージをビルド/実行するために存在する必要があるシステム依存エントリです。

インクルードパスとライブラリ

ターゲットを指定する前に、指定された目標のリソースが見つかる場所、特にヘッダーファイルとライブラリを指定する必要があります:

インクルードパス - ヘッダーファイル(最も一般的には C/C++)が見つかる場所

ライブラリパス - アクティブなターゲットと共に配置されるライブラリ

Include directories

link directories

Include directories()

include directories のパラメータは、find package 呼び出しと、含める必要があるその他のディレクトリである必要があります。catkin と Boost を使用する場合、include directories() は以下のようになります:

Include directories

最初のパラメータ "include" は、パッケージ内の include/ ディレクトリもパスの一部であることを意味します。

link directories()

例:

link directories (~)

CMake の link directories() 関数を使用して追加のライブラリパスを追加できますが、これは推奨されません。すべての catkin および CMake パッケージは、find package 時に target_link_libraries() のターゲットにリンク情報を自動的に追加します。

target_link_libraries() における Link directories() の詳細な使用例については、cmake のドキュメントを参照してください。

実行可能ターゲット

ビルドする必要がある実行可能ファイルを指定するには、add executable() CMake 関数を使用する必要があります。

bash
edd executeable

これは、3 つのソースファイル(src/main.cpp、src/some_file.cpp、src/other_file.cpp)からビルドされる MyProgram というターゲット実行可能ファイルをビルドします。

ライブラリターゲット

パッケージが再利用可能なライブラリターゲットをビルドする必要がある場合は、add_library() を使用します。多くのシンプルなチュートリアルパッケージでは実行可能ファイルのみが必要です。

cmake
add_library(${PROJECT_NAME} src/library_file.cpp)

target_link_libraries

add_executable() または add_library() の後に target_link_libraries() を使用して、ターゲットを catkin およびその他の必要なライブラリにリンクします。

cmake
target_link_libraries(node_name ${catkin_LIBRARIES})

例:

bash
(foo src/foo.cpp)
Add library (moo src/moo.cpp)
This links fly against libmoo.so

ほとんどの場合、link directories() の使用は必要ありません。情報は find package() を通じて自動的に導入されるためです。

メッセージ、サービス、アクション

メッセージ (.msg)、サービス (.srv)、アクション (.action) ファイルは、ROS パッケージが構築され使用される前に、特別なプリプロセッサビルダーが必要です。これらのマクロの重要な要素は、プログラミング言語固有のファイルを生成し、選択したプログラミング言語でメッセージ、サービス、アクションを使用できるようにすることです。ビルドシステムは、利用可能なすべてのジェネレーター(例:gencpp、genpy、genlisp など)を使用してバインドされます。

メッセージ、サービス、アクションをそれぞれ処理するために 3 つのマクロが提供されています:

add_message_files()

add_service_files()

add_action_files()

これらのマクロの後には、結果を生成するマクロが必要です:

generate_messages()

CMake の構文に触れたことがない場合は、CMake 実践を読んでください:https://github.com/Akagi201/learning-cmake/blob/master/docs/cmake-practice.pdf 。CMake の習得は ROS プロジェクトの理解に非常に役立ちます。

package.xml の概要

概要

パッケージリストは、Package.xml という名前の XML ファイルのルートフォルダで、互換性のあるパッケージには必ず含める必要があります。Package.xml は catkin のパッケージにも必要なもので、パッケージの説明です。以前の ROS バージョン(Rosbuild ビルドシステム)では、パッケージの基本情報を記述するために "manifest.xml" と呼ばれていました。インターネット上で manifest.xml を含む ROS プロジェクトを見かけた場合、それはおそらく hydro バージョンより前のものです。package.xml には、パッケージの名前、バージョン番号、内容の説明、メンテナンス担当者、ソフトウェアライセンス、コンパイルビルドツール、ビルド依存関係、実行依存関係に関する情報が含まれています。

package.xml ファイルにはメッセージ生成を含める必要があり、run depend にはメッセージランタイムを含める必要があります。

フォーマット

典型的な package.xml には、パッケージメタデータと依存関係宣言が含まれます:

xml
<package format="2">
  <name>package_name</name>
  <version>0.0.0</version>
  <description>Package description</description>
  <maintainer email="user@example.com">Maintainer Name</maintainer>
  <license>BSD</license>
  <buildtool_depend>catkin</buildtool_depend>
  <depend>roscpp</depend>
  <depend>rospy</depend>
  <depend>std_msgs</depend>
</package>

依存関係

最小限のラベルを持つパッケージリストは、他のパッケージへの依存関係を指定しません。パッケージには 6 つの依存関係があります:

ビルド依存関係 <build_depend> は、このパッケージをビルドするために必要なパッケージを指定します。これは、これらのパッケージ内のファイルがビルドに必要な場合です。コンパイル時のヘッダーファイル、これらのパッケージのライブラリファイルへのリンク、またはビルドに必要なその他のリソースが含まれる可能性があります(特に、これらのパッケージが CMake で find package() される場合)。クロスコンパイルシナリオでは、依存関係はターゲットシステム向けに構築されます。

ビルドエクスポート依存関係 <build_export_depend> は、このパッケージのライブラリをビルドするために必要なパッケージを指定します。これは、このパッケージのパブリックヘッダーファイルにこのヘッダーを含める場合です(特に、CMake で catkin パッケージが宣言されている場合 (CATKIN DEPENDS))。

実行依存関係 <exec_depend> は、このパッケージのコードを実行するために必要なソフトウェアパッケージを指定します。これは、このパッケージの共有ライブラリに依存する場合です(特に、CMake で Catkin package() が宣言されている場合)。

テスト依存関係 <test_depend> は、ユニットテストのための追加の依存関係のみを指定します。ビルドまたは実行依存関係としてすでに言及されている依存関係を重複させるべきではありません。

ビルドツール依存関係 <buildtool_depend> は、このパッケージが自身のビルドシステムツールをビルドするために必要であることを指定します。通常、唯一のビルダーは catkin です。クロスコンパイルシナリオでは、ビルドツールはコンパイルアーキテクチャを実装するために関係に依存します。

ドキュメントツール依存関係 <doc_depend> は、パッケージがドキュメントを生成するためのドキュメントツールを指定します。

付属タブ

<url> - パッケージに関する情報の URL、通常は ros.org の wiki ページ。

  • パッケージの作成者

例えば:

"Website"

Seed.

ワークスペースの公式例の準備

ROS 1 の操作は既存の Docker コンテナ内で実行する必要があることに注意してください。

Docker コンテナに入った後、公式 ROS サンプルをダウンロードします(利用可能な場合):

bash
git clone https://github.com/ros/ros_tutorials.git -b noetic-devel

これは C++ に基づく ROS 1 のケースです:

bash
cd ros_tutorials/
mkdir  src
cp roscpp_tutorials/ src/ -r
catkin_make # start building

コンパイルは以下のように完了しました:

コンパイル完了後、ワークスペースの後続作業の前に初期化が必要です:

bash
source devel/setup.bash

7.2.1.2 Preparing the Workspace figure 1

7.2.1.2 Preparing the Workspace figure 2

7.2.1.3 一般的なコマンドとツール

ノード起動方法

launch ファイル

roslaunch コマンドで launch ファイルを起動する方法は少なくとも 2 つあります:

  1. ros パッケージパスで起動

フォーマットは以下の通りです:

bash
roslaunch package_name launch_file_name
roslaunch pkg_name launchfile_name.launch
  1. launch ファイルの絶対パス

フォーマットは以下の通りです:

bash
roslaunch path_to_launchfile

どちらの方法で launch ファイルを起動しても、後ろにパラメータを追加できます。これらはより一般的です。

--screen:ROS ノードのデバッグ情報(存在する場合)をログファイルに保存するのではなく、画面に出力しやすくします

arg:=value:launch ファイルで指定する必要がある変数が与えられている場合、この方法で値を指定できます。例:

bash
roslaunch pkg_name launchfile_name model:=urdf/myfile.urdf # the launch file has a `model` argument that must be set

または

bash
roslaunch pkg_name launchfile_name model:='$(find urdf_pkg)/urdf/myfile.urdf' # use `find` to provide the path

roslaunch コマンドは、まずシステムの rosmaster が実行中かどうかを検出し、起動している場合は既存の rosmaster を使用します。起動していない場合は、まず rosmaster を起動し、その後 launch ファイルの設定を実行し、事前設定に従って複数のノードを起動できます。

launch ファイルはコンパイルする必要がなく、上記のように直接実行できることに注意してください。

rosrun

ノードマネージャー (master) を起動する必要があります。master はシステム内の多くのプロセスを管理するために使用され、すべてのノードは起動時に登録し、ノード間の通信を管理します。master が起動した後、master を通じて各ノードを登録します。Ubuntu ターミナルでコマンドを入力します:

bash
roscore

ノード起動、rosrun+パッケージ名+ノード名;rosrun メソッドは一度に 1 つのノードのみを実行します。

bash
rosrun [--prefix cmd] [--debug] pkg_name node_name [ARGS]

Rosrun は Package という実行可能プログラムを探し、オプションの ARGS を取り込みます。

Python

コードが Python の場合、py ファイルがあるディレクトリで直接起動できます。Python2 と python3 の違いに注意してください。

小さな亀の起動

bash
roscore    # start roscore in the first terminal
rosrun turtlesim turtlesim_node     # start the turtlesim node in the second terminal
rosrun turtlesim turtle_teleop_key  # start keyboard teleoperation in the third terminal

起動が完了すると、キーボード入力で小さな亀の動きを操作できます。カーソルは rosrun turtlesim turtle_teleop_key コマンドの下でキーボードの [上]、[下]、[左]、[右] をクリックして小さな亀の動きを制御する必要があります。

そして、rosrun turtlesim turtlesim_node ターミナルには小さな亀のログが表示されます。

bash
[ INFO] [1607648666.226328691]: Starting turtlesim with node name /turtlesim
[ INFO] [1607648666.229275030]: Spawning turtle [turtle1] at x=[5.544445], y=[5.544445], theta=[0.000000]

2 匹目の亀の起動

最初のターミナルで、launch ファイルを通じてノードを起動します:

bash
roslaunch turtle_tf turtle_tf_demo.launch

前のキーボード制御ノードをアクティブに保ちます

この時点で、キーボードの [上]、[下]、[左]、[右] を押して小さな亀の動きを駆動します。小さな亀が別の亀の動きに追従するのが観察できます。

launch ファイル

概要

ROS のノードプログラムは通常、単一の機能のみを実行しますが、完全な ROS ロボットは通常、多数のノードプログラムを同時に動作させ、相互に協力して複雑なタスクを実行します。そのため、ロボットを起動する際に多数のノードプログラムを起動する必要があり、ノードを 1 つずつ起動するのは面倒です。launch ファイルと roslaunch コマンドを使用すると、複数のノードを一度に起動でき、「ワンキー」操作を容易にし、豊富なパラメータを設定できます。

ファイルのフォーマット

launch ファイルは本質的に xml ファイルであり、一部のエディタでハイライト表示され、読みやすくなります。ヘッダーを追加しても追加しなくてもかまいません

他の xml フォーマットのファイルと同様に、launch ファイルはタグによって記述され、主なタグは以下の通りです: コードファイル:[7-2-1-3-common-commands-and-tools-example-01.xml](./code/7-2-1-3-common-commands-and-tools-example-01.xml) ```xml ``` 1. ラベル [node] ラベル [node] は launch ファイルのコア部分です。 コードファイル:[7-2-1-3-common-commands-and-tools-example-02.xml](./code/7-2-1-3-common-commands-and-tools-example-02.xml) ```xml ... ``` ここで、 pkg はノードのパッケージ名です type はパッケージ内の実行可能ファイルで、Python で作成された場合は .py の可能性があり、C++ で作成された場合はソースファイルがコンパイルされた後の実行可能ファイルの名前です。 name はノード起動後の名前で、各ノードは独自の一意の名前を持ちます。 注意:roslaunch はノードの起動順序を保証できないため、launch ファイル内のすべてのノードは起動順序に依存しないようにする必要があります。 より多くのパラメータを設定できます。以下の通りです: コードファイル:[7-2-1-3-common-commands-and-tools-example-03.xml](./code/7-2-1-3-common-commands-and-tools-example-03.xml) ```xml ``` 上記のコマンドにおいて、 respawn:このノードが閉じられた場合、自動的に再起動するかどうか required:このノードが閉じられた場合、他のすべてのノードも閉じるかどうか launch-prefix:実行用に新しいウィンドウを開くかどうか。例えば、ウィンドウを通じてロボットの動きを制御する必要がある場合、ノードの制御用に新しいウィンドウを開く必要があります。または、ノードに他のノード情報と混ざらないようにしたい出力情報がある場合。 output:デフォルトでは、launch はノード情報をログファイルに起動しますが、ここでパラメータを設定することで画面に表示できます ns:ノードを異なる名前空間に統合します。つまり、ノード名の前に ns プレフィックスを追加します。このタイプの操作を実現するには、ノードソースファイルで相対名(つまり / 記号なし)を使用してノード名とトピック名を定義します。 計算ソースの名前は次のように分類されます: 1) ベース名、例:topic 2) グローバル名、例:/A/topic 3) 相対名、例:A/topic 4) プライベート名、例:~topic 配信または購読時に次のコード行があります。 ```bash ros::init(argc, argv, "publish_node"); ros::NodeHandle nh; ros::Publisher pub = nh.advertise("topic",1000); ``` 2. ラベル [remap] ノードラベルのサブラベルとしてよく表示され、トピックを変更するために使用されます。多くの rosnode ファイルでは、受信または送信トピックが指定されておらず、input topic と output topic に置き換えられているだけの場合があります。これにより、特定のシーンでのトピック名の代わりに抽象的なトピック名が使用されます。 要するに、remap の機能は、同じノードファイルを異なる環境に適用しやすくするために、ソースファイルを変更せずに外部から remap topic を使用することです。 remap の一般的な使用フォーマットは以下の通りです: コードファイル:[7-2-1-3-common-commands-and-tools-example-04.xml](./code/7-2-1-3-common-commands-and-tools-example-04.xml) ```xml ``` 3. Include このラベルは、別の launch ファイルをこの launch ファイルに追加するために使用され、launch ファイルのネストに似ています。基本フォーマット: トップファイルパスは特定のパスを与えることができますが、一般的にプログラムの移植性のために、find コマンドでファイルパスを与えるのが最適です: 上記のコマンドでは、$(find package-name) の値は、このマシン上の対応するパッケージのパスと等しくなります。これにより、同じパッケージで他のマシンに置き換えても、対応するパスを見つけることができます。 時には、launch によって導入された別のノードが統一された名前や類似の特性を持つノード名を必要とする場合があります。例えば、/my/gps、/my/lidar、/my/imu のように、ノードに統一されたプレフィックスがあると検索しやすくなります。これは ns(名前空間)プロパティを設定することで実現できます。以下のコマンドで: 4. ラベル [arg] [arg] によるパラメータの繰り返しが可能で、複数の場所で簡単に変更できます。3 つの一般的な方法: :宣言 [arg] するが値は与えない。後でコマンドラインまたは [include] ラベルで値を割り当てることができます。 :デフォルト値。固定値。 コマンドラインで値を付与 roslaunch package_name file_name.launch arg1:=value1 arg2:=value2 5. 変数の置換 launch ファイルで一般的に使用される 2 つの変数置換があります `$(find pkg)`:例:`$(find rospy)/manifest.xml`。可能な場合はパッケージベースのパスを強く推奨します。 `$(arg arg_name):デフォルト値を設定します。オーバーライドが提供されない場合に使用します` 例えば: コードファイル:[7-2-1-3-common-commands-and-tools-manifest.xml](./code/7-2-1-3-common-commands-and-tools-manifest.xml) ```xml ``` 別の例: ```cpp ``` この値を設定した後、roslaunch 起動時に args パラメータに値を与えることができます ```bash roslaunch package_name file_name.launch a:=1 b:=5 ``` 6. ラベル [param] [arg] とは異なり、[param] は共有され、その値は value に限定されず、ファイルやコマンド行も可能です。 フォーマット コードファイル:[7-2-1-3-common-commands-and-tools-example-06.xml](./code/7-2-1-3-common-commands-and-tools-example-06.xml) ```bash # type can be omitted; ROS infers it # read file content as a string Example: # store command output in the parameter ``` [Param] はグローバルコンテキストで使用でき、その名前は元の名前です。または Node のような小さな範囲で使用でき、その全体名は node/param です。 例えば、グローバルコンテキストでは、次のように定義します: ```bash ``` ノード範囲で次のように定義します コードファイル:[7-2-1-3-common-commands-and-tools-example-07.xml](./code/7-2-1-3-common-commands-and-tools-example-07.xml) ```xml ``` rosparam list でサーバーの [param] リストを表示すると、以下のようになります ```bash /publish_frequency /node1/param1 # namespace prefix is added automatically ``` 注意:[param] 名に名前空間が追加されましたが、それでもグローバルです。 [Rosparam] [Param] は単一の [Param] に対してのみ操作でき、value、textfile、command の 3 つの形式のみで、個々の [Param] 内容を返します。[Rosparam] はバッチ操作を許可し、dump、delete などのパラメータ設定コマンドを含みます。 load:YAML ファイルからパラメータのバッチをロードします。フォーマットは以下の通りです: ```bash ``` Delete:一部の param を削除します ```bash ``` [param] のような付与操作 ```bash [1,2,3,4] ``` または... コードファイル:[7-2-1-3-common-commands-and-tools-example-08.xml](./code/7-2-1-3-common-commands-and-tools-example-08.xml) ```bash a: 1 b: 2 ``` [Rosparam] は [node] 内にも配置でき、その場合ノード名前空間が適用されます。 8. Group 複数のノードに同じ設定を適用したい場合、例えば同じ名前空間で同じトピックを remap したい場合、[group] を使用できます。[group] 内ではすべての共通ラベルを使用できます。例えば: コードファイル:[7-2-1-3-common-commands-and-tools-example-09.xml](./code/7-2-1-3-common-commands-and-tools-example-09.xml) ```xml # applies to following nodes in this group # each node can override the remap ``` ### TF 座標変換 tf は、ユーザーがいつでも複数の座標系を追跡できるパッケージです。tf はリアルタイムバッファの構造で座標間の関係を維持し、ユーザーが任意の 2 つのフレーム間で任意の時点の点、ベクトルなどを変換できるようにします。 Tf パッケージは、ある座標系の点の座標を別の座標系の座標に変換するものです。センサーはある座標系を見ることができ、機械はある座標系を見ることができ、障害物はある点を見ることができます。 2 匹の小さな亀を起動した後、以下の操作を実行します。 ### tf 共通ツール 1. view_frames ツール 現在の時刻に ROS を通じてブロードキャストされているすべての tf 座標をリッスンし、座標間の接続を示すツリー図を描画して、frame.pdf というファイルを生成し、現在のローカル位置に保存します。 ```bash rosrun tf view_frames ``` 2. rqt_tf_tree ツール view_frames は現在の座標関係をオフラインファイルに保存できますが、座標関係をリアルタイムに反映することはできません。そのため、rqt_tf_tree を使用して座標関係をリアルタイムに更新することが可能です。 ```bash rosrun rqt_tf_tree rqt_tf_tree ``` 3. tf_echo ツール tf_echo ツールを使用すると、2 つのブロードキャスト基準座標系間の関係を確認できます。 ```bash rosrun tf tf_echo ``` ソースフレームからターゲットフレームへの回転変換を表示します。例えば: ```bash rosrun tf tf_echo turtle1 turtle2 ``` 4. 静的変換パブリッシャー 2 つの座標系間の静的座標を配信します。これらの座標系は相対位置が変化しません。コマンドフォーマット: ```bash static_transform_publisher x y z yaw pitch roll frame_id child_frame_id period_in_ms static_transform_publisher x y z qx qy qz qw frame_id child_frame_id period_in_ms ``` launch での使用: コードファイル:[7-2-1-3-common-commands-and-tools-example-10.xml](./code/7-2-1-3-common-commands-and-tools-example-10.xml) ```xml ``` 5. Roswtf プラグイン 現在の tf 設定を分析し、一般的な問題を特定しようとするプラグインです。 ```bash roswtf ``` ### 一般的な座標系 通常の座標系は frame_id で、map、odom、base_link、base_footprint、base_laser などがあります。 ワールド座標系 (map) Map 座標系は世界の固定座標系で、Z 軸が上を指します。Map 座標系に対する移動プラットフォームの姿勢は、時間の経過とともに大きく変動すべきではありません。Map 座標系は連続的ではないため、Map 座標系における移動プラットフォームの姿勢はいつでも分離できます。一般的な設定では、測位モジュールはセンサー監視に基づいて、世界座標系におけるロボットの位置を継続的に再計算し、偏差を排除しますが、新しいセンサー情報が到着するとジャンプする可能性があります。Map 座標系は長期的なグローバルリファレンスとして有用ですが、ジャンプがあるためローカルセンサーやセンサーには悪いリファレンスとなります。 odom Odom は、走行距離計を通じてロボットの現在の運動姿勢を記録するグローバル座標系です。odom 座標系における移動プラットフォームの位置は、境界なく自由に移動できるため、odom 座標系は長期的なグローバルリファレンスとして機能しません。これは、エンコーダー(またはビジュアルなど)に基づいて計算された座標と走行距離の概念を区別するためです。しかし、関係もあります。odom トピックの変換行列は odom->base_link の tf 関係です。odom と map 座標系はロボットの動きの開始時に一致します。しかし、時間の経過とともに重ならなくなり、偏差は走行距離計の累積誤差です。amcl などの一部の座標補正パッケージでは、Map->base_link の tf によって取得できる位置推定(ローカリゼーション)が提供されます。そのため、位置と走行距離位置の差が odom と Map の座標差です。odom 計算が正しければ、map->odom の tf はゼロです。odom 座標系は短期的なローカルリファレンスとして有用ですが、偏差があるため長期的なリファレンスにはなりません。 ベース座標系 (base_link) ロボットのホーム座標系(行列)座標はロボットの中心と重なり、通常はロボットの回転中心です。 Base_footprint:原点は base_link 原点の地面上への投影で、多少の差(z 値)があります。 座標系間の関係 ロボットシステムでは、ツリーを使用してすべての座標系を接続します。そのため、各座標系には親座標系と任意の子座標系があります。以下のように:Map -> odom -> base_link。ワールド座標系は odom 座標系の親で、odom 座標系は base_link の親です。直感的には Map と odom が base_link に接続されるべきですが、各座標系には 1 つの親しか見つからないため、これは許可されません。 座標系の権限 odom から base_link への変換は、走行距離計ソースによって計算および配信されます。しかし、ロケーターモジュールは Map から base_link への変換(transform)を配信しません。代わりに、ロケーターモジュールは odom から base_link への transform を受信し、この情報を使用して map から odom への transform を配信します。 ### rqt(QT ツール) コマンドラインウィンドウを開き、rosrun rqt と入力して Tab キーをダブルクリックすると、ROS の QT ツールに含まれるものを確認できます。以下の図のように: では、小さな亀の例を使って、使用されるいくつかの QT ツールを簡単に紹介します: 1. rqt_graph 計算可視化 コマンドラインウィンドウを開き、以下のコマンドを入力するとダイアログウィンドウが表示されます。 rosrun rqt_graph rqt_graph 図から、/teleop_turtle ノードが /turtle1/cmd_vel トピックを通じて /turtlesim ノードに送信していることが明確にわかります。 /teleop_turtle は Publisher 機能を持つノードです。 /turtlesim は subscriber 機能を持つノードです。 この投稿はザンビア特別報道の一部です。 2. rqt_topic トピック表示 rosrun rqt_topic rqt_topic このツールを通じて、小さな亀の変化に関するリアルタイム情報を明確に確認できます。 3. rqt_publisher rqt_publisher は、固定または計算されたフィールド値を持つ任意のメッセージを配信する GUI プラグインを提供します。コマンドラインウィンドウを開き、以下のコマンドを入力するとダイアログウィンドウが表示されます。 ```bash rosrun rqt_publisher rqt_publisher ``` Topic の右側の選択ボックスをクリックして、必要な /turtle1/cmd_vel トピックを見つけ、右側をクリックして以下のように数値を追加します: 4. rqt_plot データマッピング 参考コマンドは以下の通りです: ```bash rosrun rqt_plot rqt_plot ``` 6. rqt_console ログ出力 ROS ログシステムの機能は、画面に表示したり、特定のトピックに送信したり、特定のログファイルに保存したりして、デバッグ、記録、警告などを容易にするログメッセージを生成することです。 ROS のログメッセージは、重大度に応じて DEBUG、INFO、WARN、ERROR、FATAL の 5 つのレベルに分類できます。プログラムが実行できる限り、注意を払う必要はありませんが、ERROR と FATAL の存在は、プログラムに実行不能な深刻な問題があることを示します。 ```bash rosrun rqt_console rqt_console ``` ログ出力ツールは ROS ロギングフレームの一部で、ノードの出力情報を表示します。図では、亀が壁にぶつかったことがわかります。 一般的な API 7. rqt_reconfigure 動的パラメータ設定 参考コマンドは以下の通りです: ```bash rosrun rqt_reconfigure rqt_reconfigure ``` 画像ソース ROS wiki: ### Rviz rviz は、ROS プログラムを簡単にグラフィカルに実行できるグラフィカルツールです。使用も簡単です。 [Set initial pose]、[Set target pose] rviz インターフェースは主に以下で構成されています: 1:3D ビュー領域。視覚表示データ用で、現在は利用できないため黒くなっています。 2:ツールバー。視点制御、目標設定、配信位置などのツールを提供します。 3:現在選択されている表示プラグインを表示するアイテムリスト。各プラグインのプロパティを設定できます。 4:視点設定。複数の観察オプションが利用可能です。 5:時間表示領域。現在のシステム時間と ROS 時間を表示します。 Display の追加 ステップ 1:[Add] ボタンをクリックします。ボックスがポップアップ表示されます。 ステップ 2:表示タイプ [By display type] で追加します。対応するトピックを変更した場合のみ座標を表示できます。また、トピック [By topic] を選択して直接追加することもでき、適切に表示されます。 ステップ 3:[OK] をクリックします。 ### 一般的な ROS コマンド ## 図 ![7.2.1.3 Common Commands and Tools figure 1](./images/7-2-1-3-common-commands-and-tools-01.png) ![7.2.1.3 Common Commands and Tools figure 2](./images/7-2-1-3-common-commands-and-tools-02.png) ![7.2.1.3 Common Commands and Tools figure 3](./images/7-2-1-3-common-commands-and-tools-03.png) ![7.2.1.3 Common Commands and Tools figure 4](./images/7-2-1-3-common-commands-and-tools-04.png) ![7.2.1.3 Common Commands and Tools figure 5](./images/7-2-1-3-common-commands-and-tools-05.png) ![7.2.1.3 Common Commands and Tools figure 6](./images/7-2-1-3-common-commands-and-tools-06.png) ![7.2.1.3 Common Commands and Tools figure 7](./images/7-2-1-3-common-commands-and-tools-07.png) ![7.2.1.3 Common Commands and Tools figure 8](./images/7-2-1-3-common-commands-and-tools-08.png) ![7.2.1.3 Common Commands and Tools figure 9](./images/7-2-1-3-common-commands-and-tools-09.png) ![7.2.1.3 Common Commands and Tools figure 10](./images/7-2-1-3-common-commands-and-tools-10.png) ![7.2.1.3 Common Commands and Tools figure 11](./images/7-2-1-3-common-commands-and-tools-11.png) ![7.2.1.3 Common Commands and Tools figure 12](./images/7-2-1-3-common-commands-and-tools-12.jpeg) ![7.2.1.3 Common Commands and Tools figure 13](./images/7-2-1-3-common-commands-and-tools-13.png) # 7.2.1.4 パブリッシャー ### パブリッシャー パブリッシャーは、その名の通り、配信者として機能します。下位マシンからマシン上のセンサー情報に送信される可能性のあるこのメッセージは、パッケージ化されてトピックのサブスクライバーに送信されます。また、マシン上のデータを計算し、パッケージ化してトピックを購読しているサブスクライバーに送信することも可能です。 ### ワークスペースとトピックキットの作成 ### ワークスペースの作成 ```bash mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src catkin_init_workspace ``` ### ワークスペースのコンパイル ```bash cd ~/catkin_ws/ catkin_make ``` ### 環境変数の更新 ```bash source devel/setup.bash ``` ### 環境変数の確認 ```bash echo $ROS_PACKAGE_PATH ``` ### パッケージの作成 ```bash cd ~/catkin_ws/src catkin_create_pkg learning_topic std_msgs rospy roscpp geometry_msgs turtlesim ``` 説明:learning_topic は機能キットの名前です ### パッケージのビルド ```bash cd ~/catkin_ws catkin_make source ~/catkin_ws/devel/setup.bash ``` ### パブリッシャーの作成 ### 作成手順 ROS ノードの初期化 2. ハンドルの作成 3) ノード情報を ROS Master に登録(配信するメッセージの名前とタイプ、キューの長さを含む) 4. メッセージデータの作成と初期化 5. 一定の頻度でメッセージを循環配信 ### C++ 実装 1) パッケージの src フォルダに turtle_velocity_publisher.cpp という名前の C++ ファイル(ファイル拡張子 .cpp)を作成します (vim 基本使用の復習:14 Vim エディタで) ```bash touch turtle_velocity_publisher.cpp # create the file vim turtle_velocity_publisher.cpp # edit the file ``` 2) 以下のプログラムコードを turtle_velocity_publisher.cpp ファイルにコピーします コードファイル:[7-2-1-4-publisher-turtle_velocity_publisher.cpp](./code/7-2-1-4-publisher-turtle_velocity_publisher.cpp) ```cpp /*Create a turtlesim velocity publisher.*/ #include #include int main(int argc, char **argv){ ros::init(argc, argv, "turtle_velocity_publisher");//Initialize the ROS node. ros::NodeHandle n;//Create a node handle. //Create a publisher for /turtle1/cmd_vel with geometry_msgs::Twist messages and queue size 10. ros::Publisher turtle_vel_pub = n.advertise("/turtle1/cmd_vel", 10); ros::Rate loop_rate(10);//Set the loop rate. while (ros::ok()){ //Initialize a message with the same type as the publisher. geometry_msgs::Twist turtle_vel_msg; turtle_vel_msg.linear.x = 0.8; turtle_vel_msg.angular.z = 0.6; turtle_vel_pub.publish(turtle_vel_msg);// Publish the velocity message. //Print the published velocity. ROS_INFO("Publsh turtle velocity command[%0.2f m/s, %0.2f rad/s]", turtle_vel_msg.linear.x, turtle_vel_msg.angular.z); loop_rate.sleep();//Sleep according to the loop rate. } return 0; } ``` 編集されたプロジェクトディレクトリ構造は以下の通りです: ```bash catkin_ws/ ├── CMakeLists.txt └── src/ ├── CMakeLists.txt └── learning_topic/ ├── CMakeLists.txt ├── package.xml └── src/ └── turtle_velocity_publisher.cpp ``` 3) プログラムのフローチャート。1.3.1 の内容に対応します 4) catkin_ws/src/learning_topic/CMakeLists.txt のビルド領域の下に、以下を追加します: (vim 基本使用の復習:14 Vim エディタで) コードファイル:[7-2-1-4-publisher-example-02.cmake](./code/7-2-1-4-publisher-example-02.cmake) ```cmake add_executable(turtle_velocity_publisher src/turtle_velocity_publisher.cpp) target_link_libraries(turtle_velocity_publisher ${catkin_LIBRARIES}) ``` 正しいパスで CMakeLists.txt を変更することに注意してください 5. ワークスペースディレクトリでコードを再コンパイル ```bash cd ~/catkin_ws catkin_make source devel/setup.bash # source the workspace so ROS can find the program ``` プログラムの実行 最初のターミナルを開いて roscore を実行します: ```bash roscore ``` 小さな亀ノードを実行 ```bash rosrun turtlesim turtlesim_node ``` ランチャーを実行し、亀に速度を送信し続けます。 ```bash rosrun learning_topic turtle_velocity_publisher ``` 7. 期待される結果 8. プログラムの動作説明 ターミナルでトピックリストを入力すると、/turtle1/cmd_vel トピックが見つかります。 rostopic info /turtle1/cmd_vel で確認します これは、亀が /turtle1/cmd_vel の速度トピックのサブスクライバーであることを意味します。そのため、パブリッシャーが速度データを送信し続け、亀が受信すると、その速度で動き始めます。 ### Python 実装 1) パッケージディレクトリの下に、新しいフォルダ scripts を作成し、次に scripts フォルダの下に turtle_velocity_publisher.py という名前の新しい Python ファイル(ファイル拡張子 .py)を作成します 2) 次のプログラムコードを turtle_velocity_publisher.py ファイルにコピーします コードファイル:[7-2-1-4-publisher-suffix.py](./code/7-2-1-4-publisher-suffix.py) ```python #!/usr/bin/env python3 import rospy from geometry_msgs.msg import Twist def turtle_velocity_publisher(): rospy.init_node('turtle_velocity_publisher', anonymous=True) # Initialize the ROS node. # Create a turtlesim velocity publisher on /turtle1/cmd_vel. The message type is geometry_msgs/Twist and the queue size is 8. turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=8) rate = rospy.Rate(10) # Set the loop rate. while not rospy.is_shutdown(): # Initialize a geometry_msgs::Twist message. turtle_vel_msg = Twist() turtle_vel_msg.linear.x = 0.8 turtle_vel_msg.angular.z = 0.6 # Publish the message. turtle_vel_pub.publish(turtle_vel_msg) rospy.loginfo("linear is:%0.2f m/s, angular is:%0.2f rad/s", turtle_vel_msg.linear.x, turtle_vel_msg.angular.z) rate.sleep()# Sleep according to the loop rate. if __name__ == '__main__': try: turtle_velocity_publisher() except rospy.ROSInterruptException: pass ``` 3. プロジェクトのフローチャート 4. プログラムの実行 最初のターミナルを開いて roscore を実行 ```bash roscore ``` 小さな亀ノードを実行 ```bash rosrun turtlesim turtlesim_node ``` ランチャーを実行し、亀に速度を送信し続けます。 ```bash rosrun learning_topic turtle_velocity_publisher.py ``` 注意:実行前に、turtle_velocity_publisher.py に実行権限を追加する必要があります。turtle_velocity_publisher.py ファイルがあるフォルダでターミナルを開きます。 ```bash sudo chmod a+x turtle_velocity_publisher.py ``` > すべての Python ファイルに実行権限を追加する必要があります。そうしないとエラーが発生します! ## 図 ![7.2.1.4 Publisher figure 1](./images/7-2-1-4-publisher-01.png) ![7.2.1.4 Publisher figure 2](./images/7-2-1-4-publisher-02.png) ![7.2.1.4 Publisher figure 3](./images/7-2-1-4-publisher-03.png) ![7.2.1.4 Publisher figure 4](./images/7-2-1-4-publisher-04.png) ![7.2.1.4 Publisher figure 5](./images/7-2-1-4-publisher-05.png) ![7.2.1.4 Publisher figure 6](./images/7-2-1-4-publisher-06.png) # 7.2.1.5 サブスクライバー ### サブスクライバー サブスクライバーは、パブリッシャーが配信したデータを受信し、コールバック関数に入り、受信したデータを処理します。コアコンテンツはコールバック関数で、各サブスクライバーはトピックを購読します。 ### サブスクライバーの作成 ### 作成手順 ROS ノードの初期化 2. ハンドルの作成 3. 購読するトピック 4. トピックメッセージをループしてコールバック関数に戻す 5) コールバック関数でメッセージ処理を完了する > この章のワークスペースは、第 IV 節で作成したワークスペースを引き続き使用します。 ### C++ 実装 1) 作成したパッケージの src フォルダの下の `publishing` チュートリアルディレクトリに turtle_pose_subscriber.cpp という名前の新しい C++ ファイルを作成します 2) 以下のプログラムコードを turtle_pose_subscriber.cpp ファイルにコピーします コードファイル:[7-2-1-5-subscriber-subscriber.cpp](./code/7-2-1-5-subscriber-subscriber.cpp) ```cpp /*Create a subscriber for the current turtlesim pose.*/ #include #include "turtlesim/Pose.h" // The callback runs when a subscribed message is received. void turtle_poseCallback(const turtlesim::Pose::ConstPtr& msg){ // Print the received message. ROS_INFO("Turtle pose: x:%0.3f, y:%0.3f", msg->x, msg->y); } int main(int argc, char **argv){ ros::init(argc, argv, "turtle_pose_subscriber");// Initialize the ROS node. ros::NodeHandle n;//Create a node handle. // Create a subscriber for /turtle1/pose and register poseCallback. ros::Subscriber pose_sub = n.subscribe("/turtle1/pose", 10, turtle_poseCallback); ros::spin(); // Wait for callbacks. return 0; } ``` ```bash catkin_ws/ ├── CMakeLists.txt └── src/ ├── CMakeLists.txt └── learning_topic/ ├── CMakeLists.txt ├── package.xml └── src/ └── turtle_velocity_publisher.cpp └── turtle_pose_subscriber.cpp ``` 3) プログラムのフローチャート。5.2.1 の内容に対応します 4) catkin_ws/src/learning_topic/CMakeLists.txt のビルド領域の下に、以下を追加します: (vim 基本使用の復習:14 Vim エディタで) ```bash Add executeable (turtle pose subscriber src/turtle_pose_subscriber.cpp) {\cHFFFFFF}{\cH00FFFF} ``` 5. ワークスペースディレクトリでコードをコンパイル ```bash cd ~/catkin_ws catkin_make source devel/setup.bash # source the workspace so ROS can find the program ``` プログラムの実行 最初のターミナルを開いて roscore を実行 ```bash roscore ``` 2 番目のターミナルで亀ノードを実行します。 ```bash rosrun turtlesim turtlesim_node ``` 3 番目のターミナルで購読ノードを実行し、亀の配信位置データを受信し続けます ```bash rosrun learning_topic turtle_pose_subscriber ``` 7) 8. プログラムの動作説明 小さな亀のノードを実行した後、亀は位置メッセージを送信し続け、トピックは /turtle1/pose です。実行時に、亀が送信したデータメッセージを受信し、エコー関数で表示します。 ### Python 実装 1) パッケージディレクトリの下に、新しいフォルダ scripts を作成し、次に scripts フォルダに turtle_pose_subscriber.py という名前の新しい Python ファイル(ファイル拡張子 .py)を作成します 2) 次のプログラムコードを turtle_pose_subscriber.py にコピーします コードファイル:[7-2-1-5-subscriber-suffix.py](./code/7-2-1-5-subscriber-suffix.py) ```python #!/usr/bin/env python3 import rospy from turtlesim.msg import Pose def poseCallback(msg): rospy.loginfo("Turtle pose: x:%0.3f, y:%0.3f", msg.x, msg.y) def turtle_pose_subscriber(): rospy.init_node('turtle_pose_subscriber', anonymous=True)# Initialize the ROS node. # Create a subscriber for /turtle1/pose and register poseCallback. rospy.Subscriber("/turtle1/pose", Pose, poseCallback) rospy.spin()# Wait for callbacks. if __name__ == '__main__': turtle_pose_subscriber() ``` 3. プロジェクトのフローチャート 4. プログラムの実行 roscore を実行 ```bash roscore ``` 小さな亀ノードを実行 ```bash rosrun turtlesim turtlesim_node ``` サブスクライバーを実行し、亀の配信位置データを受信し続けます ```bash rosrun learning_topic turtle_pose_subscriber.py ``` ## 図 ![7.2.1.5 Subscriber figure 1](./images/7-2-1-5-subscriber-01.png) ![7.2.1.5 Subscriber figure 2](./images/7-2-1-5-subscriber-02.png) ![7.2.1.5 Subscriber figure 3](./images/7-2-1-5-subscriber-03.png) ![7.2.1.5 Subscriber figure 4](./images/7-2-1-5-subscriber-04.png) ![7.2.1.5 Subscriber figure 5](./images/7-2-1-5-subscriber-05.png) # 7.2.1.6 カスタムトピックメッセージと使用方法 > [7.2.1.1 ROS 1 の紹介](#7211-introduction-to-ros-1) で説明した ROS 1 Noetic Docker コンテナ内でコマンドを実行してください。 このセクションでは、`Information.msg` という名前のカスタムトピックメッセージを作成して使用します。例では、以前に作成した `learning_topic` パッケージを引き続き使用します。 ## メッセージファイルの作成 `msg` ディレクトリを作成し、カスタムメッセージを定義します: ```bash cd ~/catkin_ws/src/learning_topic mkdir -p msg vim msg/Information.msg ``` コードファイル:[7-2-1-6-custom-topic-messages-and-usage-Information.msg](./code/7-2-1-6-custom-topic-messages-and-usage-Information.msg) ```msg string company string city ``` ## package.xml の更新 メッセージ生成/実行時依存関係を `package.xml` に追加します: ```xml message_generation message_runtime ``` ## CMakeLists.txt の更新 メッセージ生成を `CMakeLists.txt` に追加します: ```cmake find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation ) add_message_files( FILES Information.msg ) generate_messages( DEPENDENCIES std_msgs ) catkin_package( CATKIN_DEPENDS message_runtime ) ``` ワークスペースをビルドします: ```bash cd ~/catkin_ws catkin_make source devel/setup.bash ``` ## C++ パブリッシャーとサブスクライバー 以下のファイルを `~/catkin_ws/src/learning_topic/src` の下に作成します。 コードファイル:[7-2-1-6-custom-topic-messages-and-usage-Information_publisher.cpp](./code/7-2-1-6-custom-topic-messages-and-usage-Information_publisher.cpp) ```cpp /** * Publish /company_info with the custom learning_topic::Information message type. */ #include #include "learning_topic/Information.h" int main(int argc, char **argv) { ros::init(argc, argv, "company_information_publisher"); ros::NodeHandle nh; ros::Publisher info_pub = nh.advertise("/company_info", 10); ros::Rate loop_rate(1); while (ros::ok()) { learning_topic::Information info_msg; info_msg.company = "Seeed"; info_msg.city = "Shenzhen"; info_pub.publish(info_msg); ROS_INFO("Information: company:%s city:%s", info_msg.company.c_str(), info_msg.city.c_str()); loop_rate.sleep(); } return 0; } ``` コードファイル:[7-2-1-6-custom-topic-messages-and-usage-Information_subscriber.cpp](./code/7-2-1-6-custom-topic-messages-and-usage-Information_subscriber.cpp) ```cpp /** * Subscribe to /company_info with the custom learning_topic::Information message type. */ #include #include "learning_topic/Information.h" void companyInfoCallback(const learning_topic::Information::ConstPtr& msg) { ROS_INFO("Company: %s, city: %s", msg->company.c_str(), msg->city.c_str()); } int main(int argc, char **argv) { ros::init(argc, argv, "company_information_subscriber"); ros::NodeHandle nh; ros::Subscriber sub = nh.subscribe("/company_info", 10, companyInfoCallback); ros::spin(); return 0; } ``` 実行可能ファイルを `CMakeLists.txt` に追加します: ```cmake add_executable(Information_publisher src/Information_publisher.cpp) target_link_libraries(Information_publisher ${catkin_LIBRARIES}) add_dependencies(Information_publisher ${PROJECT_NAME}_generate_messages_cpp) add_executable(Information_subscriber src/Information_subscriber.cpp) target_link_libraries(Information_subscriber ${catkin_LIBRARIES}) add_dependencies(Information_subscriber ${PROJECT_NAME}_generate_messages_cpp) ``` ビルドして実行: ```bash cd ~/catkin_ws catkin_make source devel/setup.bash roscore rosrun learning_topic Information_publisher rosrun learning_topic Information_subscriber ``` ## Python パブリッシャーとサブスクライバー 以下のファイルを `~/catkin_ws/src/learning_topic/scripts` の下に作成し、実行可能にします。 コードファイル:[7-2-1-6-custom-topic-messages-and-usage-Information_publisher.py](./code/7-2-1-6-custom-topic-messages-and-usage-Information_publisher.py) ```python #!/usr/bin/env python3 import rospy from learning_topic.msg import Information def information_publisher(): rospy.init_node('information_publisher', anonymous=True) info_pub = rospy.Publisher('/company_info', Information, queue_size=10) rate = rospy.Rate(1) while not rospy.is_shutdown(): info_msg = Information() info_msg.company = 'Seeed' info_msg.city = 'Shenzhen' info_pub.publish(info_msg) rospy.loginfo('Information: company:%s city:%s', info_msg.company, info_msg.city) rate.sleep() if __name__ == '__main__': information_publisher() ``` コードファイル:[7-2-1-6-custom-topic-messages-and-usage-Information_subscriber.py](./code/7-2-1-6-custom-topic-messages-and-usage-Information_subscriber.py) ```python #!/usr/bin/env python3 import rospy from learning_topic.msg import Information def company_info_callback(msg): rospy.loginfo('Company: %s, city: %s', msg.company, msg.city) def information_subscriber(): rospy.init_node('information_subscriber', anonymous=True) rospy.Subscriber('/company_info', Information, company_info_callback) rospy.spin() if __name__ == '__main__': information_subscriber() ``` ```bash chmod +x scripts/Information_publisher.py scripts/Information_subscriber.py roscore rosrun learning_topic Information_publisher.py rosrun learning_topic Information_subscriber.py ``` ## 図 ![7.2.1.6 Custom Topic Messages and Usage figure 1](./images/7-2-1-6-custom-topic-messages-and-usage-01.png) ![7.2.1.6 Custom Topic Messages and Usage figure 2](./images/7-2-1-6-custom-topic-messages-and-usage-02.png) ![7.2.1.6 Custom Topic Messages and Usage figure 3](./images/7-2-1-6-custom-topic-messages-and-usage-03.png) ![7.2.1.6 Custom Topic Messages and Usage figure 4](./images/7-2-1-6-custom-topic-messages-and-usage-04.png) ![7.2.1.6 Custom Topic Messages and Usage figure 5](./images/7-2-1-6-custom-topic-messages-and-usage-05.png) ![7.2.1.6 Custom Topic Messages and Usage figure 6](./images/7-2-1-6-custom-topic-messages-and-usage-06.png) ![7.2.1.6 Custom Topic Messages and Usage figure 7](./images/7-2-1-6-custom-topic-messages-and-usage-07.png) ![7.2.1.6 Custom Topic Messages and Usage figure 8](./images/7-2-1-6-custom-topic-messages-and-usage-08.png) # 7.2.1.7 クライアント > [7.2.1.1 ROS 1 の紹介](#7211-introduction-to-ros-1) で説明した ROS 1 Noetic Docker コンテナ内でコマンドを実行してください。 トピック通信に加えて、サービス通信もあります。クライアントがリクエストを送信し、サーバーがレスポンスを返します。このセクションでは、C++ と Python でクライアントを実装する方法を示します。 ### 準備作業 > このセクションで作成した `learning_server` パッケージを引き続き使用します。 ### パッケージの作成 1. ~/catkin_ws/src に切り替え、ターミナルで実行: ```bash catkin_create_pkg learning_server std_msgs rospy roscpp geometry_msgs turtlesim ``` ~ ディレクトリに切り替えてコンパイルを実行: ```bash catkin_make ``` ### C++ 実装 ### 実装手順 ROS ノードの初期化 2. ハンドルの作成 3) クライアントのインスタンスを作成 4. サービスリクエストデータの初期化と配信 5. サーバーから受信したレスポンス ### `~/catkin_ws/src/learning_server/src` の下に `a_new_turtle.cpp` を作成し、以下のコードを貼り付けます。 a_new_turtle.cpp コードファイル:[7-2-1-7-client-new.cpp](./code/7-2-1-7-client-new.cpp) ```cpp /** This example calls the turtlesim /spawn service to create a new turtle at the specified position. */ #include #include int main(int argc, char** argv) { ros::init(argc, argv, "a_new_turtle");// Initialize the ROS node. ros::NodeHandle node; ros::service::waitForService("/spawn"); // Wait for the /spawn service. ros::ServiceClient new_turtle = node.serviceClient("/spawn");//Create a service client for /spawn. // Initialize the turtlesim::Spawn request. turtlesim::Spawn new_turtle_srv; new_turtle_srv.request.x = 6.0; new_turtle_srv.request.y = 8.0; new_turtle_srv.request.name = "turtle2"; // Call the service with x/y position and name parameters. ROS_INFO("Call service to create a new turtle name is %s,at the x:%.1f,y:%.1f", new_turtle_srv.request.name.c_str(), new_turtle_srv.request.x, new_turtle_srv.request.y); new_turtle.call(new_turtle_srv); ROS_INFO("Spawn turtle successfully [name:%s]", new_turtle_srv.response.name.c_str());// Display the service call result. return 0; }; ``` 1. プログラムのフローチャート 2) CMakeLists.txt 設定のビルド領域の下に、以下を追加します: (vim 基本使用の復習:14 Vim エディタで) コードファイル:[7-2-1-7-client-example-02.cmake](./code/7-2-1-7-client-example-02.cmake) ```cmake add_executable(a_new_turtle src/a_new_turtle.cpp) target_link_libraries(a_new_turtle ${catkin_LIBRARIES}) ``` 3. ワークスペースディレクトリでコードを再コンパイル ```bash cd ~/catkin_ws catkin_make source devel/setup.bash # source the workspace so ROS can find the program ``` 4) 3 つのターミナルを開いてプログラムを実行 ```bash roscore rosrun turtlesim turtlesim_node rosrun learning_server a_new_turtle ``` 5. 期待される結果 6. プロセス 小さな亀ノードが起動した後、a_new_turtle を再実行すると、画像に別の亀が表示されます。これは、小さな亀ノードが /spawn サービスを提供しており、別の亀 turtle2 を生成するためです。以下のように rosservice list コマンドで確認できます。 このサービスに必要なパラメータは、以下の図のように rosservice info /spawn で確認できます。 x、y、theta、name の 4 つのパラメータが必要であることがわかります。これらは a_new_turtle.cpp で初期化されています コードファイル:[7-2-1-7-client-turtle.cpp](./code/7-2-1-7-client-turtle.cpp) ```cpp srv.request.x = 6.0; srv.request.y = 8.0; srv.request.name = "turtle2"; ``` 注意:Theta は割り当てられておらず、デフォルトは 0 です ### Python 実装 ### `~/catkin_ws/src/learning_server` の下に `scripts/a_new_turtle.py` を作成し、以下のコードを貼り付けます。 a_new_turtle.py コードファイル:[7-2-1-7-client-a_new_turtle.py](./code/7-2-1-7-client-a_new_turtle.py) ```python #!/usr/bin/env python3 import rospy from turtlesim.srv import Spawn def turtle_spawn(): rospy.init_node('new_turtle') rospy.wait_for_service('/spawn') try: spawn_client = rospy.ServiceProxy('/spawn', Spawn) response = spawn_client(2.0, 2.0, 0.0, 'turtle2') return response.name except rospy.ServiceException as exc: rospy.logerr('Failed to call /spawn: %s', exc) return None if __name__ == '__main__': name = turtle_spawn() if name: rospy.loginfo('Created a new turtle named %s.', name) ``` 1. プログラムのフローチャート 2. 3 つのターミナルを開いてプログラムを実行 ```bash roscore rosrun turtlesim turtlesim_node rosrun learning_server a_new_turtle.py ``` 3) 動作の効果とプログラムの説明は C++ で実現された結果と一致します。ここでは Python がサービスを提供するパラメータについて説明します。 `response = spawn_client(2.0, 2.0, 0.0, "turtle2")` 対応するパラメータは x、y、theta、name です。 ## 図 ![7.2.1.7 Client figure 1](./images/7-2-1-7-client-01.png) ![7.2.1.7 Client figure 2](./images/7-2-1-7-client-02.png) ![7.2.1.7 Client figure 3](./images/7-2-1-7-client-03.png) ![7.2.1.7 Client figure 4](./images/7-2-1-7-client-04.png) ![7.2.1.7 Client figure 5](./images/7-2-1-7-client-05.png) ![7.2.1.7 Client figure 6](./images/7-2-1-7-client-06.png) ![7.2.1.7 Client figure 7](./images/7-2-1-7-client-07.png) # 7.2.1.8 サーバー > [7.2.1.1 ROS 1 の紹介](#7211-introduction-to-ros-1) で説明した ROS 1 Noetic Docker コンテナ内でコマンドを実行してください。 クライアントリクエストについて説明した後、次はサービスの提供について説明します。 > このセクションで作成した `learning_server` パッケージを引き続き使用します。 ### C++ 実装 ### 実装手順 ROS ノードの初期化 2. Server のインスタンスを作成 3) サービスリクエストをループ待機し、コールバック関数に入る 4) コールバック関数でサービスの機能処理を完了し、レスポンスデータをフィードバックする ### `~/catkin_ws/src/learning_server/src` の下に `turtle_vel_command_server.cpp` を作成し、以下のコードを貼り付けます。 コードファイル:[7-2-1-8-server-new.cpp](./code/7-2-1-8-server-new.cpp) ```cpp /** This example provides /turtle_vel_command with the std_srvs/Trigger service type. */ #include #include #include ros::Publisher turtle_vel_pub; bool pubvel = false; // Service callback: req is the request and res is the response. bool pubvelCallback(std_srvs::Trigger::Request &req, std_srvs::Trigger::Response &res) { pubvel = !pubvel; ROS_INFO("Do you want to publish the vel?: [%s]", pubvel==true?"Yes":"No");// Print the client request. // Set response data. res.success = true; res.message = "The status is changed!"; return true; } int main(int argc, char **argv) { ros::init(argc, argv, "turtle_vel_command_server"); ros::NodeHandle n; // Create the /turtle_vel_command server and register pubvelCallback. ros::ServiceServer command_service = n.advertiseService("/turtle_vel_command", pubvelCallback); // Create a publisher for /turtle1/cmd_vel. The message type is geometry_msgs::Twist and the queue size is 8. turtle_vel_pub = n.advertise("/turtle1/cmd_vel", 8); ros::Rate loop_rate(10);// Set the loop rate. while(ros::ok()) { ros::spinOnce();// Process callbacks once. // Publish turtle velocity commands when pubvel is true. if(pubvel) { geometry_msgs::Twist vel_msg; vel_msg.linear.x = 0.6; vel_msg.angular.z = 0.8; turtle_vel_pub.publish(vel_msg); } loop_rate.sleep();//Sleep according to the loop rate. } return 0; } ``` 1. プログラムのフローチャート 2) CMakeLists.txt のビルド領域の下に、以下を追加します: コードファイル:[7-2-1-8-server-example-02.cmake](./code/7-2-1-8-server-example-02.cmake) ```cmake add_executable(turtle_vel_command_server src/turtle_vel_command_server.cpp) target_link_libraries(turtle_vel_command_server ${catkin_LIBRARIES}) ``` 3. ワークスペースディレクトリでコードをコンパイル ```bash cd ~/catkin_ws catkin_make source devel/setup.bash # source the workspace so ROS can find the program ``` 4. 4 つのターミナルでプログラムを起動 ```bash roscore rosrun turtlesim turtlesim_node rosrun learning_server turtle_vel_command_server rosservice call /turtle_vel_command ``` 5. 期待される結果 6. プロセス まず、小さな亀ノードを実行する際に、ターミナルで rosservice list と入力して現在のサービスを確認できます。以下のように: 次に turtle_vel_command_server プログラムを実行し、rosservice list と入力すると、追加の turtle_vel_command_server が見つかります。以下の図のように: そして、ターミナルで入力してこのサービスを呼び出すと、小さな亀が円運動をしているのがわかり、再度呼び出すと停止します。これは、コールバックサービスで pubvel の値を反転させ、フィードバックを返し、メイン関数が pubvel の値を判断し、True の場合は速度指示を与え、False の場合は与えないためです。 ### Python 実装 ### `~/catkin_ws/src/learning_server` の下に `scripts/turtle_vel_command_server.py` を作成し、以下のコードを貼り付けます。 turtle_vel_command_server.py コードファイル:[7-2-1-8-server-turtle_vel_command_server.py](./code/7-2-1-8-server-turtle_vel_command_server.py) ```python #!/usr/bin/env python3 import threading import rospy from geometry_msgs.msg import Twist from std_srvs.srv import Trigger, TriggerResponse pubvel = False turtle_vel_pub = None def publish_velocity_loop(): rate = rospy.Rate(10) while not rospy.is_shutdown(): if pubvel: vel_msg = Twist() vel_msg.linear.x = 0.6 vel_msg.angular.z = 0.8 turtle_vel_pub.publish(vel_msg) rate.sleep() def pubvel_callback(req): global pubvel pubvel = not pubvel rospy.loginfo('Publish turtle velocity: %s', pubvel) return TriggerResponse(success=True, message='Velocity publishing toggled.') def turtle_pubvel_command_server(): global turtle_vel_pub rospy.init_node('turtle_vel_command_server') turtle_vel_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=8) rospy.Service('/turtle_vel_command', Trigger, pubvel_callback) threading.Thread(target=publish_velocity_loop, daemon=True).start() rospy.loginfo('Ready to receive /turtle_vel_command requests.') rospy.spin() if __name__ == '__main__': turtle_pubvel_command_server() ``` 1. プログラムのフローチャート 2) 3 つのターミナルを開いてプログラムを実行: ```bash roscore rosrun turtlesim turtlesim_node rosrun learning_server turtle_vel_command_server.py ``` 3. プログラムの動作効果とプログラムの説明は C++ で実現されたものと一致します。 ## 図 ![7.2.1.8 Server figure 1](./images/7-2-1-8-server-01.png) ![7.2.1.8 Server figure 2](./images/7-2-1-8-server-02.png) ![7.2.1.8 Server figure 3](./images/7-2-1-8-server-03.png) ![7.2.1.8 Server figure 4](./images/7-2-1-8-server-04.png) ![7.2.1.8 Server figure 5](./images/7-2-1-8-server-05.png) ![7.2.1.8 Server figure 6](./images/7-2-1-8-server-06.png) ![7.2.1.8 Server figure 7](./images/7-2-1-8-server-07.png) # 7.2.1.9 カスタムサービスメッセージと使用方法 > [7.2.1.1 ROS 1 の紹介](#7211-introduction-to-ros-1) で説明した ROS 1 Noetic Docker コンテナ内でコマンドを実行してください。 このセクションでは、`IntPlus.srv` という名前のカスタムサービスを定義し、C++ と Python でサーバー/クライアントペアを実装します。 ## サービスファイルの作成 ```bash cd ~/catkin_ws/src/learning_server mkdir -p srv vim srv/IntPlus.srv ``` コードファイル:[7-2-1-9-custom-service-messages-and-usage-IntPlus.srv](./code/7-2-1-9-custom-service-messages-and-usage-IntPlus.srv) ```srv int64 a int64 b --- int64 result ``` ## package.xml と CMakeLists.txt の更新 これらの依存関係を `package.xml` に追加します: ```xml message_generation message_runtime ``` サービス生成を `CMakeLists.txt` に追加します: ```cmake find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs message_generation ) add_service_files( FILES IntPlus.srv ) generate_messages( DEPENDENCIES std_msgs ) catkin_package( CATKIN_DEPENDS message_runtime ) ``` ワークスペースをビルドします: ```bash cd ~/catkin_ws catkin_make source devel/setup.bash ``` ## C++ サーバーとクライアント コードファイル:[7-2-1-9-custom-service-messages-and-usage-IntPlus_server.cpp](./code/7-2-1-9-custom-service-messages-and-usage-IntPlus_server.cpp) ```cpp #include #include "learning_server/IntPlus.h" bool intPlusCallback(learning_server::IntPlus::Request &req, learning_server::IntPlus::Response &res) { ROS_INFO("number 1 is:%ld, number 2 is:%ld", req.a, req.b); res.result = req.a + req.b; return true; } int main(int argc, char **argv) { ros::init(argc, argv, "IntPlus_server"); ros::NodeHandle nh; ros::ServiceServer service = nh.advertiseService("/Two_Int_Plus", intPlusCallback); ROS_INFO("Ready to calculate two integers."); ros::spin(); return 0; } ``` コードファイル:[7-2-1-9-custom-service-messages-and-usage-IntPlus_client.cpp](./code/7-2-1-9-custom-service-messages-and-usage-IntPlus_client.cpp) ```cpp #include #include "learning_server/IntPlus.h" int main(int argc, char **argv) { ros::init(argc, argv, "IntPlus_client"); ros::NodeHandle nh; ros::service::waitForService("/Two_Int_Plus"); ros::ServiceClient client = nh.serviceClient("/Two_Int_Plus"); learning_server::IntPlus srv; srv.request.a = 8; srv.request.b = 6; if (client.call(srv)) { ROS_INFO("Result: %ld", srv.response.result); } else { ROS_ERROR("Failed to call /Two_Int_Plus"); } return 0; } ``` 実行可能ファイルを `CMakeLists.txt` に追加します: ```cmake add_executable(IntPlus_server src/IntPlus_server.cpp) target_link_libraries(IntPlus_server ${catkin_LIBRARIES}) add_dependencies(IntPlus_server ${PROJECT_NAME}_generate_messages_cpp) add_executable(IntPlus_client src/IntPlus_client.cpp) target_link_libraries(IntPlus_client ${catkin_LIBRARIES}) add_dependencies(IntPlus_client ${PROJECT_NAME}_generate_messages_cpp) ``` 例を実行: ```bash roscore rosrun learning_server IntPlus_server rosrun learning_server IntPlus_client ``` サービスを直接呼び出すこともできます: ```bash rosservice call /Two_Int_Plus 5 6 ``` ## Python サーバーとクライアント コードファイル:[7-2-1-9-custom-service-messages-and-usage-IntPlus_server.py](./code/7-2-1-9-custom-service-messages-and-usage-IntPlus_server.py) ```python #!/usr/bin/env python3 import rospy from learning_server.srv import IntPlus, IntPlusResponse def int_plus_callback(req): rospy.loginfo('Ints: a:%d b:%d', req.a, req.b) return IntPlusResponse(req.a + req.b) if __name__ == '__main__': rospy.init_node('IntPlus_server') rospy.Service('/Two_Int_Plus', IntPlus, int_plus_callback) rospy.loginfo('Ready to calculate two integers.') rospy.spin() ``` コードファイル:[7-2-1-9-custom-service-messages-and-usage-IntPlus_client.py](./code/7-2-1-9-custom-service-messages-and-usage-IntPlus_client.py) ```python #!/usr/bin/env python3 import rospy from learning_server.srv import IntPlus if __name__ == '__main__': rospy.init_node('IntPlus_client') rospy.wait_for_service('/Two_Int_Plus') plus_client = rospy.ServiceProxy('/Two_Int_Plus', IntPlus) response = plus_client(22, 20) rospy.loginfo('Result: %d', response.result) ``` ```bash chmod +x scripts/IntPlus_server.py scripts/IntPlus_client.py roscore rosrun learning_server IntPlus_server.py rosrun learning_server IntPlus_client.py ``` ## 図 ![7.2.1.9 Custom Service Messages and Usage figure 1](./images/7-2-1-9-custom-service-messages-and-usage-01.png) ![7.2.1.9 Custom Service Messages and Usage figure 2](./images/7-2-1-9-custom-service-messages-and-usage-02.png) ![7.2.1.9 Custom Service Messages and Usage figure 3](./images/7-2-1-9-custom-service-messages-and-usage-03.png) ![7.2.1.9 Custom Service Messages and Usage figure 4](./images/7-2-1-9-custom-service-messages-and-usage-04.png) ![7.2.1.9 Custom Service Messages and Usage figure 5](./images/7-2-1-9-custom-service-messages-and-usage-05.png) ![7.2.1.9 Custom Service Messages and Usage figure 6](./images/7-2-1-9-custom-service-messages-and-usage-06.png) ![7.2.1.9 Custom Service Messages and Usage figure 7](./images/7-2-1-9-custom-service-messages-and-usage-07.png) # 7.2.1.10 TF を使った配信と受信 > [7.2.1.1 ROS 1 の紹介](#7211-introduction-to-ros-1) で説明した ROS 1 Noetic Docker コンテナ内でコマンドを実行してください。 ### tf パッケージ ### tf は、ユーザーが時間の経過とともに複数の座標系を追跡できるパッケージで、ツリー状のデータ構造を使用し、時間バッファを使用して複数の座標系間の座標変換を維持します。開発者がいつでも座標変換、座標間の点、ベクトルなどを計算するのを支援します。 ### 使用手順 1. tf 変換の傍受 キャッシュシステムに配信されたすべての座標を受信し、データを変換し、必要な座標を検索します。 2. tf 変換のブロードキャスト (b) システム内の座標間の座標変換をブロードキャストします。システムの複数の部分に tf 変更ブロードキャストがある場合があります。各ブロードキャストは、さらなる同期なしに tf ツリーに直接挿入できます。 ### tf 座標ブロードキャストとリスニングのプログラミング実装 ### パッケージの作成とコンパイル ```bash cd ~/catkin_ws/src catkin_create_pkg learning_tf rospy roscpp turtlesim tf cd.. catkin_make ``` ### tf ブロードキャスターの実装方法 1. TF ブロードキャスター (Transform Broadcaster) の定義; 2. tf データの初期化と座標系の作成; 3) 座標変換の配信 (sendTransform); ### tf リスナーの実装方法 1. TF リスナー (TransformListener) の定義; 2) 座標系の検索 (waitForTransform、lookupTransform) ### C++ 言語による tf ブロードキャスターの実装 1) パッケージの src フォルダに C++ ファイル(ファイル拡張子 .cpp)を作成します 2) 以下のプログラムコードを turtle_tf_broadcaster.cpp ファイルにコピーします コードファイル:[7-2-1-10-publishing-and-listening-with-tf-with.cpp](./code/7-2-1-10-publishing-and-listening-with-tf-with.cpp) ```cpp #include #include #include std::string turtle_name; void poseCallback(const turtlesim::PoseConstPtr& msg) { static tf::TransformBroadcaster br;// Create a TF broadcaster. // Initialize TF data. tf::Transform transform; transform.setOrigin( tf::Vector3(msg->x, msg->y, 0.0) );//Set xyz coordinates. tf::Quaternion q; q.setRPY(0, 0, msg->theta);//Set Euler angles for x, y, and z rotation. transform.setRotation(q); br.sendTransform(tf::StampedTransform(transform, ros::Time::now(), "world", turtle_name));// Broadcast TF data between world and the turtle frame. } int main(int argc, char** argv) { ros::init(argc, argv, "turtle_world_tf_broadcaster");// Initialize the ROS node. if (argc != 2) { ROS_ERROR("Missing a parameter as the name of the turtle!"); return -1; } turtle_name = argv[1];// Use the input argument as the turtle name. // Subscribe to the turtle pose topic. ros::NodeHandle node; ros::Subscriber sub = node.subscribe(turtle_name+"/pose", 10, &poseCallback); // Wait for callbacks. ros::spin(); return 0; }; ``` 3. プロジェクトのフローチャート 4. コード解析 まず、亀の /pose 位置を購読し、トピックが配信されるとコールバック関数に入ります。次に tf のブロードキャスターに戻り、tf データを初期化します。その値は /pose トピックへの購読です。最後に、sendTransform の関数である br.sendTransform を通じて、小さな亀によるワールド座標の変換が配信されます。4 つのパラメータがあり、最初のパラメータは Transform 型の tf(つまり、以前に初期化された tf データ)座標を表し、2 番目のパラメータはタイムスタンプ、3 番目と 4 番目は変数ソースとターゲット座標です。 ### C++ 言語による tf リスナーの実装 1) パッケージの src フォルダに C++ ファイル(ファイル拡張子 .cpp)を作成します 2) 以下のプログラムコードを turtle_tf_listener.cpp ファイルにコピーします コードファイル:[7-2-1-10-publishing-and-listening-with-tf-with.cpp](./code/7-2-1-10-publishing-and-listening-with-tf-with.cpp) ```cpp /** This example listens to TF data, computes velocity commands, and publishes them to turtle2. turtle2->turtle1 = world->turtle*world->turtle2 */ #include #include #include #include int main(int argc, char** argv) { ros::init(argc, argv, "turtle1_turtle2_listener");// Initialize the ROS node. ros::NodeHandle node; // Create a node handle. // Call the service to spawn turtle2. ros::service::waitForService("/spawn"); ros::ServiceClient add_turtle = node.serviceClient("/spawn"); turtlesim::Spawn srv; add_turtle.call(srv); // Create a publisher for turtle2 velocity commands. ros::Publisher vel = node.advertise("/turtle2/cmd_vel", 10); tf::TransformListener listener;// Create a TF listener. ros::Rate rate(10.0); while (node.ok()) { // Get TF data between turtle1 and turtle2. tf::StampedTransform transform; try { listener.waitForTransform("/turtle2", "/turtle1", ros::Time(0), ros::Duration(3.0)); listener.lookupTransform("/turtle2", "/turtle1", ros::Time(0), transform); } catch (tf::TransformException &ex) { ROS_ERROR("%s",ex.what()); ros::Duration(1.0).sleep(); continue; } // Compute angular and linear velocity from the relative pose between turtle1 and turtle2, then publish commands for turtle2. geometry_msgs::Twist turtle2_vel_msg; turtle2_vel_msg.angular.z = 6.0 * atan2(transform.getOrigin().y(), transform.getOrigin().x()); turtle2_vel_msg.linear.x = 0.8 * sqrt(pow(transform.getOrigin().x(), 2) + pow(transform.getOrigin().y(), 2)); vel.publish(turtle2_vel_msg); rate.sleep(); } return 0; }; ``` 3. プロジェクトのフローチャート 4. コード解析 まず、サービス呼び出しで別の小さな亀 turtle2 を作成し、次に turtle2 速度コントローラーを作成します。次にリスナーを作成し、turtle1 と turtle2 の間の変換をリッスンして検索します。これには 2 つの関数が関係します: コードファイル:[7-2-1-10-publishing-and-listening-with-tf-example-03.cpp](./code/7-2-1-10-publishing-and-listening-with-tf-example-03.cpp) ```cpp waitForTransform and lookupTransform waitForTransform(target_frame,source_frame,time,timeout) ``` 2 つのフレームはそれぞれターゲット座標系とソース座標系を表し、時間は 2 つの座標系間の変換を待つ時間を示します。座標変換はブロッキングプロセスであるため、時間制限を示すように設定する必要があります。 lookupTransform(target_frame, source_frame, transform):ソースフレームとターゲット座標系 (target_frame) を与え、2 つの座標系間の時間 (transform) を与えます。 lookupTransform を通じて座標変換の結果を取得し、x、y 座標を使用して turtle2 の角速度と線速度を計算し、turtle2 に配信します。 ### CMakeLists.txt の変更とコンパイル 1. CMakeLists.txt の変更 パッケージの下の src/learning_tf/CMakeLists.txt を変更し、以下を追加します: (vim 基本使用の復習:14 Vim エディタで) コードファイル:[7-2-1-10-publishing-and-listening-with-tf-example-04.cpp](./code/7-2-1-10-publishing-and-listening-with-tf-example-04.cpp) ```cpp add_executable(turtle_tf_listener src/turtle_tf_listener.cpp) target_link_libraries(turtle_tf_listener ${catkin_LIBRARIES}) add_executable(turtle_tf_broadcaster src/turtle_tf_broadcaster.cpp) target_link_libraries(turtle_tf_broadcaster ${catkin_LIBRARIES}) ``` 2. 実装ファイルのコンパイル ```bash cd ~/catkin_ws catkin_make source devel/setup.bash # source the workspace so ROS can find the program ``` ### 起動と動作効果のデモンストレーション 1) 6 つのターミナルを開き、以下のコマンドを実行します: ```bash roscore rosrun turtlesim turtlesim_node rosrun learning_tf turtle_tf_broadcaster __name:=turtle1_tf_broadcaster /turtle1 rosrun learning_tf turtle_tf_broadcaster __name:=turtle2_tf_broadcaster /turtle2 rosrun learning_tf turtle_tf_listener rosrun turtlesim turtle_teleop_key # start keyboard teleoperation for the turtle ``` 2. デモンストレーション効果 III. プログラムの説明 roscore が起動され、小さな亀ノードが起動されると、最後に小さな亀が表示されます。次に、turtle1->world、turtle2->world の 2 つの tf 変換を配信します。turtle2 と turtle1 の間の変換を知るには、それらと world の間の変換を知る必要があるためです。次に tf リスニングプログラムが開かれ、この時点でターミナルに別の亀が生成され、turtle2 が turtle1 に向かって移動します。次にキーボード制御をオンにし、矢印をクリックして turtle1 の動きを制御すると、turtle2 が turtle1 の動きに追従します。 ### Python 言語による tf ブロードキャスター 1) パッケージ learning_tf にフォルダ scripts を作成し、このディレクトリに切り替えて、turtle_tf_broadcaster.py という名前の新しい .py ファイルを作成します 2) 以下のプログラムコードを turtle_tf_broadcaster.py ファイルにコピーします コードファイル:[7-2-1-10-publishing-and-listening-with-tf-new.py](./code/7-2-1-10-publishing-and-listening-with-tf-new.py) ```python #!/usr/bin/env python3 import roslib roslib.load_manifest('learning_tf') import rospy import tf import turtlesim.msg def handle_turtle_pose(msg, turtlename): br = tf.TransformBroadcaster()# Create a TF broadcaster. # Broadcast the TF transform between world and the named turtle. br.sendTransform((msg.x, msg.y, 0), tf.transformations.quaternion_from_euler(0, 0, msg.theta), rospy.Time.now(), turtlename, "world") if __name__ == '__main__': rospy.init_node('turtle1_turtle2_tf_broadcaster')# Initialize the ROS node. turtlename = rospy.get_param('~turtle') # Get the turtle name from the parameter server. # Subscribe to the turtle pose topic. rospy.Subscriber('/%s/pose' % turtlename, turtlesim.msg.Pose, handle_turtle_pose, turtlename) rospy.spin() ``` 3. プロジェクトのフローチャート ### Python 言語による tf リスナーの実装 1) パッケージ learning_tf の scripts フォルダに turtle_tf_listener.py という名前の Python ファイル(ファイル拡張子 .py)を作成します 2) 以下のプログラムコードを turtle_tf_listener.py ファイルにコピーします コードファイル:[7-2-1-10-publishing-and-listening-with-tf-with.py](./code/7-2-1-10-publishing-and-listening-with-tf-with.py) ```python #!/usr/bin/env python3 import rospy import math import tf import geometry_msgs.msg import turtlesim.srv if __name__ == '__main__': rospy.init_node('turtle_tf_listener')# Initialize the ROS node. listener = tf.TransformListener()# Initialize a TF listener. rospy.wait_for_service('spawn') # Call the service to create another turtle named turtle2. spawner = rospy.ServiceProxy('spawn', turtlesim.srv.Spawn) spawner(8, 6, 0, 'turtle2') # Declare a publisher for turtle2 velocity. turtle_vel = rospy.Publisher('turtle2/cmd_vel', geometry_msgs.msg.Twist,queue_size=1) rate = rospy.Rate(10.0) while not rospy.is_shutdown(): try: # Look up the TF transform between turtle2 and turtle1. (trans,rot) = listener.lookupTransform('/turtle2', '/turtle1', rospy.Time(0)) except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException): continue # Compute linear and angular velocity, then publish them. angular = 6.0 * math.atan2(trans[1], trans[0]) linear = 0.8 * math.sqrt(trans[0] ** 2 + trans[1] ** 2) cmd = geometry_msgs.msg.Twist() cmd.linear.x = linear cmd.angular.z = angular turtle_vel.publish(cmd) rate.sleep() ``` 3. プロジェクトのフローチャート ### 起動と動作効果のデモンストレーション 1. launch ファイルの準備 パッケージディレクトリに新しいフォルダ launch を作成し、launch に切り替えて、start_tf_demo_py.launch という名前の新しい launch ファイルを作成し、以下をコピーします: コードファイル:[7-2-1-10-publishing-and-listening-with-tf-py.xml](./code/7-2-1-10-publishing-and-listening-with-tf-py.xml) ```xml ``` 2. 起動 ```bash roslaunch learning_tf start_tf_demo_py.launch ``` アプリケーションの実行中に、launch を実行しているウィンドウでマウスをクリックし、矢印キーを押すと、turtle2 が turtle1 に追従して動きます。 3) 動作効果は C++ とほぼ一致します ## 図 ![7.2.1.10 Publishing and Listening with TF figure 1](./images/7-2-1-10-publishing-and-listening-with-tf-01.png) ![7.2.1.10 Publishing and Listening with TF figure 2](./images/7-2-1-10-publishing-and-listening-with-tf-02.png) ![7.2.1.10 Publishing and Listening with TF figure 3](./images/7-2-1-10-publishing-and-listening-with-tf-03.jpeg) ![7.2.1.10 Publishing and Listening with TF figure 4](./images/7-2-1-10-publishing-and-listening-with-tf-04.png) ![7.2.1.10 Publishing and Listening with TF figure 5](./images/7-2-1-10-publishing-and-listening-with-tf-05.png) ![7.2.1.10 Publishing and Listening with TF figure 6](./images/7-2-1-10-publishing-and-listening-with-tf-06.png) ![7.2.1.10 Publishing and Listening with TF figure 7](./images/7-2-1-10-publishing-and-listening-with-tf-07.png)