Docker上でROSチュートリアルをやってみた

# はじめに
本記事は、ubuntu 16.04 の入ったPCに Docker をインストールし、ubuntu 18.04 と ROS melodic の入った image を取得して、 Docker上で rostopic 通信をする ROS のチュートリアルをやってみた時のメモです。

#### 対象読者の方

- ROS を使ったことがある（チュートリアルレベル）、Docker 初心者
- Dockerfile とか難しいこと言わずにコンテナ上で色々やってみたい方

# 準備
#### Docker のインストール
以下の記事を参考に、ubuntu 16.04 に Docker をインストールします。
参考記事
[[Docker] ubuntu 14.04/16.04にDockerをインストール](https://qiita.com/koara-local/items/ee887bab8c7186d00a88)

[2020/05/01追記]
上の参考記事内でも触れられていますが，2017年に商用と無償版の Docker に分かれていたらしく，無償版は Docker CE という名前で提供されているようです．（詳しくは[こちらの記事](https://qiita.com/s-suefusa/items/cb3c4044da3b3657dbd0)を参照）
Docker CE は下記の記事を参考にインストールしました．
[Ubuntu16.04にDocker CEをインストール](https://qiita.com/mogamin3/items/f9483aad5153957fc6dc)

#### Docker 実行時に `sudo` を必要としない設定 (Optional)
参考記事
[ユーザをdockerグループに入れる](https://qiita.com/tifa2chan/items/9dc28a56efcfb50c7fbe)
※このあとは全てこの設定をしたものとして書いています。
※必要であれば `docker` コマンド実行時に `sudo` を足してください。

#### ROSの入ったubuntuのimageを取得
今回は ubuntu 18.04 に ROS melodic が入った image を取得します。

```shell
$ docker pull ros:melodic
```

取得した image を確認は

```shell
$ docker images
```

でできます。

```
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ros                 melodic             9ca8b5b3f443        5 weeks ago         1.21GB
```

#### Dockerコンテナの作成

```shell
$ docker run -it --name work ros:melodic
```

ここではコンテナの名前を `work` にしてみました。

```shell
$ docker ps
```

を実行すると、現在起動しているコンテナ一覧を確認することができます。

```
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
09b23b1203b4        ros:melodic         "/ros_entrypoint.s..."   7 seconds ago       Up 6 seconds                            work
```

#### [参考] コンテナの停止、起動
コンテナの停止は起動しているコンテナ上で `exit` コマンドでできる。
再びコンテナを起動するときは

```shell
$ docker ps -a
```

で、先ほど停止したコンテナIDか名前を確認し、起動後コンテナに入ります。

```
$ docker start [コンテナID or 名前]
$ docker attach [コンテナID or 名前]
```

今回の場合は、コンテナの名前を `work` にしたので、

```
$ docker start work
$ docker attach work
```

でコンテナに復帰できます。

# ROSチュートリアル
基本的には以下の ROS 公式のチュートリアルを Docker上で行いますが、適宜不要な箇所は省いています。

- [ROSパッケージを作る](http://wiki.ros.org/ja/ROS/Tutorials/CreatingPackage)
- [シンプルな配信者(Publisher)と購読者(Subscriber)を書く(C++)](http://wiki.ros.org/ja/ROS/Tutorials/WritingPublisherSubscriber%28c%2B%2B%29)

#### ROSのワークスペースの作成
先ほど作成したコンテナ上で、

```shell
$ cd ~
$ mkdir -p catkin_ws/src
```

#### packageの作成

```
$ cd ~/catkin_ws/src
$ catkin_create_pkg beginner_tutorials std_msgs rospy roscpp
```

#### c++ で talker と listener の node を書く

```shell
$ cd ~/catkin_ws/src/beginner_tutorials
$ mkdir src
```

`beginner_tutorials/src`に以下の `talker.cpp` と `listener.cpp` をコピペする。(コメントは削除済)

```cpp:talker.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"

#include <sstream>

int main(int argc, char **argv)
{
  ros::init(argc, argv, "talker");
  ros::NodeHandle n;
  ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
  ros::Rate loop_rate(10);

  int count = 0;
  while (ros::ok())
  {
    std_msgs::String msg;
    std::stringstream ss;
    ss << "hello world " << count;
    msg.data = ss.str();
    ROS_INFO("%s", msg.data.c_str());
    chatter_pub.publish(msg);
    ros::spinOnce();
    loop_rate.sleep();
    ++count;
  }

  return 0;
}
```

```cpp:listener.cpp
#include "ros/ros.h"
#include "std_msgs/String.h"

void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}

int main(int argc, char **argv)
{
  ros::init(argc, argv, "listener");
  ros::NodeHandle n;
  ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
  ros::spin();

  return 0;
}
```

#### CMakeLists.txtの編集
以下を `CMakeLists.txt` の末尾に追加

```text:CMakeLists.txt
## Build talker and listener                                                                                                                                                                                
include_directories(include ${catkin_INCLUDE_DIRS})

add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})

add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})

```

#### ソースファイルのビルド

```
$ apt install python-catkin-tools
$ source /opt/ros/melodic
$ cd ~/catkin_ws
$ catkin build
```

#### Docker image の作成
ここまで作成したコンテナを image として保存します。
`exit` で現在いるコンテナから抜け、

```shell
$ docker commit work ros:melodic-tutorials
```

を実行すると、先ほどまで作業していた `work` コンテナを `melodic-tutorials` というタグの image として保存します。

#### Docker のネットワークの作成
コンテナ間で通信をするためのネットワークを作成します。

```
$ docker network create rosnet
```

ROS では同一ネットワークにコンテナを入れるだけでなく、`ROS_MASTER_URI` を適切に設定する必要があります。（後述）

#### talker と listener による topic の通信
roscore の起動

```shell
$ docker run -it --rm --net rosnet --name master ros:melodic-tutorials
$ roscore
```

別の端末で、

```shell
$ docker run -it --rm --net rosnet --name talker ros:melodic-tutorials
$ source ~/catkin_ws/devel/setup.bash
$ ROS_MASTER_URI=http://master:11311
$ rosrun beginner_tutorials talker
```

これで端末上に以下のような表示がされます。

```
[ INFO] [1539528505.137138966]: hello world 0
[ INFO] [1539528505.237245343]: hello world 1
[ INFO] [1539528505.337358401]: hello world 2
[ INFO] [1539528505.437382734]: hello world 3
[ INFO] [1539528505.537352617]: hello world 4
[ INFO] [1539528505.637456156]: hello world 5
[ INFO] [1539528505.737825399]: hello world 6
```

さらに別の端末で、

```shell
$ docker run -it --rm --net rosnet --name listener ros:melodic-tutorials
$ source ~/catkin_ws/devel/setup.bash
$ ROS_MASTER_URI=http://master:11311
$ rosrun beginner_tutorials listener
```

これで、talker の publish した topic を subscribe して以下のように端末上に表示します。

```
[ INFO] [1539528505.438633299]: I heard: [hello world 3]
[ INFO] [1539528505.538363467]: I heard: [hello world 4]
[ INFO] [1539528505.638404233]: I heard: [hello world 5]
[ INFO] [1539528505.739407586]: I heard: [hello world 6]
```

#### [参考] コンテナの作成時に node を実行する方法
beginner_tutorials ではないですが、こちらの記事で紹介されていたので、興味のある方は参考にしてみてください。
参考記事
[Docker + ROS(kinetic)でチュートリアル](https://qiita.com/Leonardo-mbc/items/cfd38a4fae8667593cf1)


# 参考文献
### Docker
- [[Docker] ubuntu 14.04/16.04にDockerをインストール](https://qiita.com/koara-local/items/ee887bab8c7186d00a88)
- [ユーザをdockerグループに入れる](https://qiita.com/tifa2chan/items/9dc28a56efcfb50c7fbe)
- [Dockerコンテナの作成、起動〜停止まで](https://qiita.com/kooohei/items/0e788a2ce8c30f9dba53)
- [Dockerの作業済みコンテナからイメージを作って移植を楽にする](https://qiita.com/tubone/items/a3bad04abf4c700cae3d)
- [Docker + ROS(kinetic)でチュートリアル](https://qiita.com/Leonardo-mbc/items/cfd38a4fae8667593cf1)
- [DockerとDocker-CEの違いについて](https://qiita.com/s-suefusa/items/cb3c4044da3b3657dbd0)
- [Ubuntu16.04にDocker CEをインストール](https://qiita.com/mogamin3/items/f9483aad5153957fc6dc)
 
### ROS チュートリアル
- [ROSパッケージを作る](http://wiki.ros.org/ja/ROS/Tutorials/CreatingPackage)
- [シンプルな配信者(Publisher)と購読者(Subscriber)を書く(C++)](http://wiki.ros.org/ja/ROS/Tutorials/WritingPublisherSubscriber%28c%2B%2B%29)