ROS2×複数台に関する実装アプローチの考察

# はじめに
本記事では、ROS2で複数台のロボットを扱う際の方法や実装アプローチの概要を説明します。あくまで、概要やその所感であり、具体的な実装については、別の記事や筆者らの出版物をご参考いただければと思います。
また、複数台のロボットを取り扱うための実装について知見をご紹介するだけであり、制御の戦略やユースケースについては本記事では取り扱いませんのでご承知ください。

# 大目的＝ロボットの情報の識別
複数台のロボットを取り扱ううえで、まず、達成しなくてはならない目的は「ロボットの情報の識別」です。したがって、ROS2で複数台のロボットを扱うためには、トピックやノード、tfなど全ての情報が「**どの**」ロボットによるものかを識別できるようになっていないといけません。

## ROS DOMAIN ID
ROS DOMAIN IDは、ネットワーク内に複数のROS2システムがある際に、それら相互を切り分けて識別するためのIDです。例えば、単一のネットワークルータを使用しつつ、複数あるそれぞれの実機が速度指示値として`/cmd_vel`トピックを扱っている場合、標準的には、単一のノードが速度指示をすると、指示をサブスクライブした複数のロボットが同時に動いてしまいます。一方で、それぞれのロボットに対してROS DOMAIN IDを設定し、それぞれのIDを介して速度指示した場合には、IDに対応づいた各ロボットを動かすことができます。

https://docs.ros.org/en/foxy/Concepts/About-Domain-ID.html

ROS DOMAIN IDは環境変数として設定するものであり、ターミナルで宣言したのち、以降そのIDに準じる形式です。とくに設定をしない場合、デフォルト値は「0」で設定されています。
```.bash
$ export ROS_DOMAIN_ID=30
```
### 用途
ROS DOMAIN IDは、いわばネットワークのポートを分割しているので、ロボットの情報の出元を識別できているわけではないといえます。加えて、異なるID間でやりとりが効かない（ROS2の範囲内では）ため、各ロボット同士の情報を一括管理、統合することはできません。

とはいえ、ロボットの情報の混線には手っ取り早い手法なので、ロボット同士が連携しない動作を実装するのであれば、理にかなっているといえるのかもしれません。ただし、筆者らの所感としては、ROS DOMAIN IDは、実装に含めるというより同一ネットワークを複数の開発者が共有しているときに、他者のシステムが扱っている情報が混ざらないための識別子という開発環境的な要素として考えています。

## Namespace
ROS2では、「**Namspace**」と呼ばれる機能があります。これは、各ノードシステムが通信する情報（トピック、サービス、アクション）に対して、識別子を与えることで、情報の出元を明確にしたり、ノードの再利用性を高めたりする機能です。

Nampspaceの扱いやすさとして、既存のノードの中身に手を入れることなく、外部的にNamespaceを付与することができる点です。
例えば、以下のような[ノード](https://emanual.robotis.com/docs/en/platform/turtlebot3/basic_operation/#basic-operation)を実行したうえで、
```
$ ros2 run turtlebot3_teleop teleop_keyboard
```
`topic list`でトピックを確認すると、
```
$ ros2 topic list
/cmd_vel
/parameter_events
/rosout
```
となりますが、末尾にNamespaceの設定を記述すると、
```
$ ros2 run turtlebot3_teleop teleop_keyboard --ros-args -r __ns:=/robot1
```
```
$ ros2 topic list
/robot1/cmd_vel
/parameter_events
/rosout
```
となり、元のノードを再利用しつつ、トピックを識別可能なものに変更できています。

複数のノードを起動するLaunchでも、同様に起動時にNamespaceを設定する部分があるので、実装コストは非常に小さいといえます。

### 用途
Namespaceは、同一のROS DOMAIN IDの中で情報の識別を行っているという点において、複数のロボットを統合管理したシステムを設計するのに向いていると言えます。そのため、筆者らが複数台制御の実装を行う際は、基本的には、Namespaceを編集する形式を採用しています。

一方で、同一のROS DOMAIN IDで情報を管理すると特定のポートに対してトラフィックが集中してしまうので、ロボットの台数が増えるなどして扱うシステムが大規模になるほどパフォーマンスの劣化が懸念されます。これについては、別の我々の[記事](https://qiita.com/sfc_nakanishi_lab/items/4e4789c7c92d093cb707)にあるFast DDSの機能によって、解決可能なものなのでぜひご参照ください。

## tf
ROS2を学習する上で話をややこしくしているのが「tf」です。tfとは、ロボットの静的な骨格情報と動的な計測値・推定値をもとにしたロボットの座標に関する情報です。ツリー状で表現され、ツリー間の相対座標をデータとして保持しています。

筆者らはtfをロボットごとに識別する方法が大きく2つあると考えています。1つが、tfにNamespaceを付与する方法で、もう一つが、URDF等に記載されるリンク名をユニークにする方法です。

### tfにNamespaceを付与する
tfにNamespaceを付与するのは、非常にシンプルなアプローチで、最初に思いつくアイデアだと思います。ただし、若干注意する必要があり、トピックとしてのtfが絶対パスでBroadcastされているという点です。

ROS2で扱う情報には絶対パスと相対パスという概念があり、単刀直入に言えば、先頭に「/（スラッシュ）」があれば絶対パス、なければ、相対パスという扱いになります。厄介なのが、`topic list`で見た時はすべて「/（スラッシュ）」付なので、どれが相対パスで絶対パスなのかは認識できません。

とりあえず、少なくともトピックとしてのtfは絶対パスです。絶対パスに対しては、Namespaceを付与することができません。そこで、tfトピックを相対パス化してから、Namespaceを付与するという作業がいくつかのライブラリですでに散見されます。
具体的には、以下のような処理がLaunchに書かれていることが多いです。
```sample.launch.py
#Launchのどこかの行で書かれている場合
remappings=[('/tf', 'tf'), ('/tf_static', 'tf_static')],
```

これさえ記述できれば問題ないのですが、逆に、これに気づけないと罠に引っかかってしまいます。

### tfのリンク名をユニークにする
tfをトピックとして扱うと、その分だけtfツリーが生成されることになります。一方で、ツリー内のリンク名をきちんと編集できれば、単一のツリー内でロボットを識別可能になります。
この手法については、筆者らが昨年出版した技術書の中でも紹介していますし、オンラインのROS2セミナー（海外）でも紹介されている手法です。

https://amzn.asia/d/eEuPp6i

<iframe width="560" height="315" src="https://www.youtube.com/embed/cGUueuIAFgw?si=q-sfsqYOt6KTvUSL" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

この手法は、トピックの出方を編集するのではなく、大元のURDFファイルを編集する形式をとります。静的なデータを予め台数分、必要に応じて用意しておく必要があります。

### 複数台×tfの運用は結局どちらの手法がいいのかー
どちらが良いか、というのは観点にもよるかと思うのですが、筆者はNamespaceを付与する方式を現在の実装で使用しています。URDFを編集するのは、ヒューマンエラーを招きやすく、出元だけでなく、取得先のパラメータファイルでも書き損じが生じるのは少し頂けないかなと思っています。現に、Navigation2の複数台シミュレーションのサンプルでは、tfにNamespaceを付与する形式を取っています。

https://github.com/ros-navigation/navigation2/blob/main/nav2_bringup/launch/navigation_launch.py#L61


一方で、この手法では、Navigation2を使用する際に使用するRvizにおいてロボットごとの画面でしかtfフレームが表示されません。単一のtfツリーを使用する場合の手法では、一つのRviz画面にツリー内すべてのフレーム、つまり、すべてのロボットを画面上に表現できるので、それはそれで直感的かと思われます。