Dockerを使ったROS2の環境構築　～複数端末間通信まで～

# 概要
ROS2をDocker上で動かして複数の端末間での通信を実現するまで、色々手間取ったのでその手順や発生した事象についてここに記す。

なお、ここではROS2 Jazzyを使用することを前提とする。
別のバージョンをセットアップする場合は適宜jazzyとなっているところを読み替えること。

# 環境構築手順

## Raspberry Pi 4B
### Raspbianのインストール
Raspberry Pi Imagerを使ってmicroSDカードにOSをインストールする。
今回はRaspberry Pi OS（Raspbian）をインストールする。[^RPImager]
（Ubuntu 24.04LTSを使用するつもりだったが後述する事象を回避するためにやむなく変更した）
Imagerを使えばwifiとかsshの設定は完了しているはずなので、あとはとりあえずupdate & upgradeを叩く。
```shell
sudo apt update
sudo apt upgrade
```
### Dockerのインストール
下記のコマンドでDockerをインストールする。[^DockerInstall]
最新の方法があればそちらを使用するほうが良い。（今ならComposeは不要かも）
```shell
sudo apt install docker.io docker-compose
```
またdockerコマンドのたびにsudoを打たなくていいよう、管理者権限の設定をする。[^DockerUser]
（前者はすでにあると言われたらスルーでOK）
```shell
sudo groupadd docker
sudo usermod -aG docker <ユーザー名>
```

### Dockerコンテナ構築
ROS公式の手順に従いDocker Imageを持ってくる。[^RPROSImage]
ただしこの後demoプログラムをビルドしやすいよう、ros-coreではなくros-baseを持ってくる。
```shell
docker pull ros:jazzy-ros-base
```

コンテナを作る。その際、外部との通信がしやすいようホストOSと同じIPを使えるよう設定をする。また同様にメモリ共有も有効にする。さらにコンテナを触りやすいよう適当な名前を付ける。[^DockerCon]今回は適当にrp4n1-1とした
これを実行するとコンテナの中に入る。
```shell
docker run --net host --ipc host --name rp4n1-1 -it ros:jazzy-ros-base
```

コンテナの内部で、動作確認用のデモ用プログラムをインストールする。[^ROSDemo]
またrosdep updateでaptのリポジトリを参照するようなので、事前にupdateをかける。
環境変数はすでに設定されているので省略。
またros2_wsは適宜好きな名前にするとよい。（ROS2前提ならwsだけでもいいかも）
```shell
$ cd ~
~$ apt update
~$ source /opt/ros/dashing/setup.bash
~$ sudo rosdep init
~$ rosdep update
~$ mkdir ros2_ws
~$ cd ros2_ws
~/ros2_ws$ mkdir src
~/ros2_ws$ cd src
~/ros2_ws/src$ git clone https://github.com/ros2/demos.git
~/ros2_ws/src$ cd demos
~/ros2_ws/src/demo$ git checkout jazzy
~/ros2_ws/src/demo$ cd ../..
~/ros2_ws$ rosdep install --from-paths src --ignore-src -r -y
~/ros2_ws$ colcon build
```

以上の手順が終わって動作確認出来たらコンテナを抜ける。
そして、コンテナを終了したのち環境を再利用しやすいようローカルのリポジトリにImageを追加する。[^DockerImg]
イメージ名は仮に「kikukou/ros:jazzy-ros-demos」とした。（正確には<イメージ名:タグ>になっている）
```shell
docker commit rp4n1-1 kikukou/ros:jazzy-ros-demos
```

また、Zero 2Wに環境を移植するためイメージを圧縮して持ち運べる形にしておく。[^DockerZip]
```shell
docker save -o image.tar kikukou/ros:jazzy-ros-demos
```

## Raspberry Pi Zero 2W
### Raspbianのインストール
Raspberry Pi 4Bと同じ。
その際、なんかapt upgrageがこけた。記事に従いルーターを再起動したらなんか直った。[^APTError]

### Dockerのインストール
Raspberry Pi 4Bと同じ。[^DockerZip]

### Dockerコンテナ構築
Raspberry Pi 4Bで構築したイメージを利用する。
（命令セットarm64のものなら多分同じようにできるはず）
scp等を用いて持ってきたイメージの圧縮ファイルを読み込む。
```shell
docker load -i image.tar
```

あとはraspberry Pi 4Bと同じようにrunすればOK。

## Beelink MINI-S
### Ubuntuのインストール
Ubuntu 24.04.1LTSのイメージをダウンロードしてくる。[^UbuntuIns]
適当なUSBメモリにRufusあたりを使ってLiveUSBを作る。[^Rufus]
用意したPCにUSBメモリを挿して電源を入れる。LiveUSBが立ち上がったらOSをインストールする。

### Dockerのインストール
Raspberry Pi 4Bと同じ。

### Dockerコンテナ構築
ROS2の公式に従いコンテナを引っ張ってくる。[^UROSImage]
```shell
docker pull osrf/ros:jazzy-desktop
```

コンテナを作る。その際、外部との通信がしやすいようホストOSと同じIPを使えるよう設定をする。また同様にメモリ共有も有効にする。さらにコンテナを触りやすいよう適当な名前を付ける。[^DockerCon]今回は適当にn100n1-1とした。
これを実行するとコンテナの中に入る。
```shell
docker run --net host --ipc host --name n100n1-1 -it osrf/ros:jazzy-desktop
```

このイメージはすでにDemoプログラムがビルドされているので省略する。

# コンテナ間通信テスト
Raspberry Pi 4B上で2つコンテナを建てて通信してみる。
1つ目のターミナルで以下を実行する。
```shell
docker run --net host --ipc host --name rp4n1-2 -it ros:jazzy-ros-base
```
コンテナに入ったら以下を実行。[^ROSDemo]
1つ目のコマンドは毎回うつのが面倒なら.bashrcあたりに記載しておくとよい。
```shell
source ~/ros2_ws/install/setup.bash
ros2 demo_nodes_cpp listener
```

2つ目のターミナルで以下を実行する。
```shell
docker run --net host --ipc host --name rp4n1-1 -it ros:jazzy-ros-base
```
コンテナに入ったら以下を実行。[^ROSDemo]
1つ目のコマンドは毎回うつのが面倒なら.bashrcあたりに記載しておくとよい。
```shell
source ~/ros2_ws/install/setup.bash
ros2 demo_nodes_cpp talker
```

これでtalkerから出したメッセージがlistenerで表示されてればOK。

# 端末間通信テスト
同じように端末間で通信する。

Raspberry Pi 4B上のターミナルで以下を実行する。
```shell
docker run --net host --ipc host --name rp4n1-1 -it ros:jazzy-ros-base
```
コンテナに入ったら以下を実行。[^ROSDemo]
1つ目のコマンドは毎回うつのが面倒なら.bashrcあたりに記載しておくとよい。
```shell
source ~/ros2_ws/install/setup.bash
ros2 demo_nodes_cpp listener
```

Raspberry Pi 4B上のターミナルで以下を実行する。
また、Raspberry Zewo 2W上のターミナルで以下を実行する。
```shell
docker run --net host --ipc host --name rpz2wn1-1 -it ros:jazzy-ros-base
```
コンテナに入ったら以下を実行。[^ROSDemo]
1つ目のコマンドは毎回うつのが面倒なら.bashrcあたりに記載しておくとよい。
```shell
source ~/ros2_ws/install/setup.bash
ros2 demo_nodes_cpp listener
```

Beelink MINI-S上のターミナルで以下を実行する。
```shell
docker run --net host --ipc host --name n100n1-1 -it ros:jazzy-ros-base
```
コンテナに入ったら以下を実行。[^ROSDemo]
```shell
ros2 demo_nodes_cpp talker
```

これでtalkerから出したメッセージがlistenerで表示されてればOK。
なおtalkerとlistenerを色々入れ替えても動く。

ただし、talkerとlistenerがどちらもwifiのみで接続されている場合、メッセージをlistenerが受け取れない。（今後解消が必要なら調査する予定）


# 端末間通信の失敗とその対策
## 発生事象
最初にRaspberry Pi 4BへUbuntuServer 24.04LTS、Beelink MINI-SへUbuntu 24.04LTSをそれぞれインストールし、Docker上で環境を構築して端末間通信テストをした際、上手くトピックの送受信ができなかった。また、マルチキャストの通信もうまくいかなかった。

## 対策１
ファイアウォールをすべて無効化、無線接続とした結果、マルチキャストでの通信はできるようになったが、トピックの送受信に関する事象は解決しなかった。マルチキャストを用いたデータの送受信ができることはpythonでコードを書いて確認した。その際のコードは以下のリポジトリに配置した。

https://github.com/KIKUKOU/udptest

またこの時、tcpdumpコマンドを用いてパケットを確認したところ、対向の端末からのUDPパケット及びマルチキャストのUDPパケットが届いていることが確認できた。また同じネットワーク上にあるWindows端末でWireSharkを用いてパケットを確認したところ、通信開始及び終了時にIGMPv3パケットが出力されていないことが確認できた。

上記のことから、通信開始のコネクションが上手くできていないことが原因と考えられる。

## 対策2
下記のリンクの対策を試したが効果が無かった。

https://qiita.com/tchofu/items/c7d3617b41207a912e3e


## 対策3
異なるOSを持ってきてOSの再インストールを実施した。（本記事の環境構築）

⇒事象が改善した。

詳しい原因は不明なままだが、正常に動作するようになったため詳しい原因の追究は実施していない。

# まとめ

色々あったが、とりあえずROS2 on Dockerで複数の端末間で通信できる環境を構築することができた。

wifiのみでの通信に関してはルーターの問題もありそうなので、時間があればもう1台端末を用意して自作ルーターを建てて調査をしてみようかと思う。

[^RPImager]: [Raspberry Pi Imagerの使い方 ― v1.7.2以降 対応版](https://blog.soracom.com/ja-jp/2022/06/13/how-to-use-raspberry-pi-imager-1-7-2/)

[^DockerInstall]: [【2023年最新】Raspberry PiにDockerとDocker Composeをインストールする](https://raspi.lflab.work/how-to-install-docker/)

[^DockerUser]: [Ubuntu で Docker のインストール](https://qiita.com/tf63/items/c21549ba44224722f301)

[^RPROSImage]: [ROS 2 on Raspberry Pi](https://docs.ros.org/en/jazzy/How-To-Guides/Installing-on-Raspberry-Pi.html)

[^DockerCon]: [ROS 2のDockerコンテナ間でpub/subするとき、docker runでどのオプションを付ければいいの？](https://qiita.com/dandelion1124/items/9c0a9c16956bb8fb9065)

[^ROSDemo]: [ROS2チュートリアルのdummy robot demoを動かしてみる](https://qiita.com/lutecia16v/items/9d139b8b7e4a6f726a70)

[^DockerImg]: [コンテナイメージを実際に作成してみよう !　スペシャリストから学ぶコンテナ技術 第 3 回](https://aws.amazon.com/jp/builders-flash/202108/create-container-image/)

[^DockerZip]: [Dockerをローカルの別PCに移植する手順](https://www.lisz-works.com/entry/docker-transplantation-other-local-pc)

[^APTError]: [apt-get update failing RESOLVED](https://forums.raspberrypi.com/viewtopic.php?t=360401)

[^UbuntuIns]: [Ubuntuを入手する](https://jp.ubuntu.com/download)

[^Rufus]: [WindowsでUbuntuのインストールUSBメディアを作成する](https://diagnose-fix.com/topic2-003/
)

[^UROSImage]: [Running ROS 2 nodes in Docker [community-contributed]](https://docs.ros.org/en/jazzy/How-To-Guides/Run-2-nodes-in-single-or-separate-docker-containers.html)