ElixirによるROS2クライアントライブラリRclexを、DDS観点で試す(2021/06/11更新版)

## はじめに (ROS2って、DDSって、Rclexってなに？)
ROS2(Robot Operating System)はロボット開発支援フレームワークです。ROS2環境ではプログラムコードはC++やPythonで書かれることが主に想定されているようですが、これをElixirでも開発したい！ということでElixirのROS2クライアントライブラリ(=RCL)、Rclexが開発されています。

https://github.com/rclex/rclex

ROS2は通信レイヤにDDS(Data Distribution Service)という通信プロトコル/ライブラリ/パッケージが採用されていて、開発言語を問わず実装にDDSを用いていれば、ノード間で簡単に通信することが可能です（C++で書かれたROS2ノードとPythonで書かれたROS2ノード間で簡単に通信可能）。このRclexを用いればElixirで書いたコードで他のROS2ノードと通信することが可能です。

この記事は、Rclexがバージョンアップした(0.3.1->0.4.0)ことにあわせて、ROS2をFoxyにし、パッケージインストール版で試した内容になっています。 

(旧記事はこちらです。ROS2をソースからビルドしたい場合などでなければ、本記事を参照したほうが良いです。)

https://qiita.com/kikuzo/items/cbfe6a4737cd517ea220

なおDDSについては(ROS2観点ですが)こちらの記事が参考になります。

https://ros.youtalk.jp/2017/05/28/dds.html

なお本記事は、2021年6月10日時点の内容です。

## 環境とバージョン

Rclexは環境を選びます。以下の環境で動かしました。

- Ubuntu 20.04.2 LTS （amd64)
- ROS 2 Foxy Fitzroy
- Elixir 1.9.1
- Erlang/OTP 22

![スクリーンショット 2021-06-11 16.57.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172921/8f2e7455-08d3-bc0d-678d-efef2e421d39.png)

rclex 0.4.0になってROS2 Foxyに対応しました！ (Dashingでもいけるようです。私は0.4.0では未試用。)
ErlangとElixirはUbuntu環境でパッケージインストールしました。特に問題ありませんでした。

## 環境構築、インストール

### Ubuntu 20.04.2環境の構築
私はUbuntuをintel Mac(10.15 Catalina)上にVirtualBoxをインストールしその上で動かしました。ROS2に不慣れなのでGUIツール(rviz2など)も使うかなと思ったためですが、今回のようにDDSで通信を試すだけであればGUIは不要です。たぶんクラウド上のVMでも大丈夫だと思います。

VirtualBoxのインストール、またUbuntuのインストールは割愛します。調べればすぐに見つかるでしょう。

OS本体のインストールが完了したら、VirtualBoxのGuest Additionsもインストールしておきましょう。Guest AddtionsはROS2/Rclexトライアル的には不要ですが、入れたほうが便利です。このあたりを参考にしました。
https://www.linuxmania.jp/virtualbox_02.html

### ROS2 Foxyのインストール
Ubuntu環境ができたら、ROS2 Foxyをインストールしていきます。ROS2はパッケージインストールとソースからの自前構築の２つの方法がありますが、ここではパッケージインストールを試します。
パッケージインストールにも、1)aptコマンドでインストールする、2)ROS2公式サイトからダウンロードするバイナリパッケージ、の２通りがあります。ここでは両方について説明します。

**1) aptパッケージでのインストール**

非常に簡単です。

```terminal:bash
% sudo apt update
% sudo apt install ros-foxy-desktop
```
少々時間はかかります。

**2) ROS2公式サイトからバイナリパッケージをダウンロードしてインストール**

基本的な手順はROS2のドキュメント通りです。簡単に解説していきます。

https://docs.ros.org/en/foxy/Installation/Ubuntu-Install-Binary.html

(なおROS2のドキュメントは、バージョンが異なっても全般的には非常に似ていますので注意が必要です。)

以下の手順では、ROS2のフルセットをインストールしていますが、多分、いらない物がたくさんあるはずなので、ROS2に詳しい方は不要なもののインストールは省略できるはずです。RclexのビルドにはROS２の開発環境があればよいはず。

**2-1) ROS2レポジトリを追加**

```terminal:bash
% sudo apt update && sudo apt install curl gnupg2 lsb-release
% sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key  -o /usr/share/keyrings/ros-archive-keyring.gpg

% echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null
```

この手順に失敗すると、次の手順のパッケージインストールの際に謎のエラーが出るので、間違えないように注意しましょう。

**2-2) ROS2 Foxy本体をダウンロードして展開**

ROS2 Foxyの最新は[このあたり](https://github.com/ros2/ros2/releases/tag/release-foxy-20201211)のようです。

```terminal:bash
% cd ~/Downloads
% wget https://github.com/ros2/ros2/releases/download/release-foxy-20201211/ros2-foxy-20201211-linux-focal-amd64.tar.bz2
% mkdir -p ~/ros2_foxy
% cd ~/ros2_foxy
% tar xv ~/Downloads/ros2-foxy-20201211-linux-focal-amd64.tar.bz2
```
**2-3) rosdepコマンドのインストールと、依存パッケージのインストール**

rosdepコマンドを入れます。

```terminal:bash
% sudo apt update
% sudo apt install -y python3-rosdep
% sudo rosdep init
% rosdep update
```

続いて、rosdepコマンドで依存関係パッケージをインストールしていきます。

```terminal:bash
% rosdep install --from-paths ~/ros2_foxy/ros2-linux/share --ignore-src --rosdistro foxy -y --skip-keys "console_bridge fastcdr fastrtps osrf_testing_tools_cpp poco_vendor rmw_connext_cpp rosidl_typesupport_connext_c rosidl_typesupport_connext_cpp rti-connext-dds-5.3.1 tinyxml_vendor tinyxml2_vendor urdfdom urdfdom_headers"
```

このあと、python3のライブラリを入れます。

```terminal:bash
% sudo apt install -y libpython3-dev python3-pip
% pip3 install -U argcomplete
```

ドキュメントではこの後、独自のDDS環境をインストールしたい場合には入れよ、と書いてあるのですが、ここではそれはしません。

これでインストールができました。

#### 環境変数をセット
本体や依存パッケージのインストールが終わった時点で、ROS2環境が /opt/ros/foxy (ディストリビューションパッケージの場合)もしくは ~/ros2_foxy/ros2-linux(ROS2公式パッケージの場合)配下にできています。ROS2環境を使う(動かす)ためには環境変数(PATH等)をセットする必要があり、それをするためのスクリプトが用意されています。ターミナル実行時に都度環境変数をセットするか(下記)、.bashrc等でシェル起動時に自動で読み込ませるかします。

```terminal:bash
% . /opt/ros/foxy/setup.bash  (ディストリビューションのパッケージインストールの場合)
% . ~/ros2_foxy/ros2-linux/setup.bash  (ROS2公式パッケージインストールの場合)
```
bash以外のシェル用のスクリプトもあります。(sh, zah, powershell)

#### 簡単にサンプルを動かしてみる
ROS2の環境構築ができたら、サンプルを動かしてみましょう。
ターミナルを２つ起動し、双方でsetup.bashを実行させてros2コマンドが動くようにしておきます（下記には含んでいます）。その状態で、

```:ターミナル1
% . /opt/ros/foxy/setup.bash
% ros2 run demo_nodes_cpp talker
```

```:ターミナル２
% . /opt/ros/foxy/setup.bash
% ros2 run demo_nodes_py listener
```

とすると、ターミナル1側でpublishした文字列が、ターミナル2側のsubscriber側で受信できています。やりました！

実際の実行時の画面はこんな感じです。

![スクリーンショット 2021-05-25 12.43.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172921/c58a04e5-bfb2-cb0b-91cb-77d5ffddd7e3.png)


### Elixir環境の構築
ROS2環境ができたので、ようやくRclexですが、前提となるElixirを入れます。

Elixirのドキュメントに従います。

https://elixir-lang.jp/install.html

```terminal:bash
% wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb
% sudo apt-get update
% sudo apt-get install elixir
```

(手順では、elixir本体をインストールする前にsudo apt-get install esl-erlangするように書いてあるのですが、エラーが出ました。elixirをインストールすると依存パッケージとして勝手に入るようです。)

その他、Rclexのビルドに必要なパッケージを入れておきます。

```terminal:bash
% sudo apt-get install build-essential
% sudo apt-get install erlang-dev
```

### Rclexをインストールする
ここからがようやく本番です！
Rclexはライブラリであり、単体で使用するものではないので、ここでは同じ開発者から提供されているサンプルコードを使います。Rclex本体はサンプルコードの中で依存パッケージとして指定されており、サンプルコードをビルドするときに自動的に、インストール・ビルドされます。

https://github.com/rclex/rclex_samples

```terminal:bash
% git clone https://github.com/rclex/rclex_samples.git
% cd rclex_samples
```

```shell-session:bash
kikuzo@kikuzo-VirtualBox:~/rclex_samples$ mix deps.get
Resolving Hex dependencies...
Dependency resolution completed:
Unchanged:
  elixir_make 0.6.2
  rclex 0.4.0
* Getting rclex (Hex package)
* Getting elixir_make (Hex package)
kikuzo@kikuzo-VirtualBox:~/rclex_samples$ 
```

ここで、ros2をディストリビューションのパッケージインストールした場合は問題ないのですが、ROS2公式の方から落としてきてインストールした場合には、rclexのMakefileの修正が必要です。

冒頭部分のROSDIRを修正します。

```makefile:~/rclex_samples/deps/rclex/Makefile
# set directory for ROSDISTRO
#ROS_DIR = /opt/ros/$(ROS_DISTRO) # ここをコメントアウトして、自分でインストールしたディレクトリに書き換える
ROS_DIR = /home/kikuzo/ros2_foxy/ros2-linux

ifeq ($(ROS_DISTRO), dashing)
	ROS_VERSION = DASHING
	TYPE_STRUCTURE_DIR = rosidl_generator_c
else ifeq ($(ROS_DISTRO), foxy)
	ROS_VERSION = FOXY
	TYPE_STRUCTURE_DIR = rosidl_runtime_c
endif

CC = gcc
LD = ld
RM = rm

PREFIX = $(MIX_APP_PATH)/priv
BUILD  = $(MIX_APP_PATH)/obj

(..省略..)
```

(ROS2公式のインストール手順ではパッケージは作業者のホームディレクトリ配下に入れるようになっているのですが、これを任意の場所（端的に言えば/opt/ros/foxy）に変更できないものでしょうか。(未試用))

さて、mixコマンドを実行すると、すーっとビルドが通ります。

```shell-session:bash
kikuzo@kikuzo-VirtualBox:~/rclex_samples$ mix deps.compile
==> rclex
mkdir -p /home/kikuzo/rclex_samples/_build/dev/lib/rclex/priv
mkdir -p /home/kikuzo/rclex_samples/_build/dev/lib/rclex/obj
gcc -c -I/usr/lib/erlang/usr/include -I/opt/ros/foxy/include -g -O2 -Wall -Wextra -Wno-unused-parameter -pedantic -fPIC -DFOXY -o 

(..省略..)

gcc -o /home/kikuzo/rclex_samples/_build/dev/lib/rclex/priv/rclex.so -L/usr/lib/erlang/usr/lib -g -shared /home/kikuzo/rclex_samples/_build/dev/lib/rclex/obj/total_nif.o /home/kikuzo/rclex_samples/_build/dev/lib/rclex/obj/init_nif.o /home/kikuzo/rclex_samples/_build/dev/lib/rclex/obj/node_nif.o /home/kikuzo/rclex_samples/_build/dev/lib/rclex/obj/publisher_nif.o /home/kikuzo/rclex_samples/_build/dev/lib/rclex/obj/subscription_nif.o /home/kikuzo/rclex_samples/_build/dev/lib/rclex/obj/wait_nif.o /home/kikuzo/rclex_samples/_build/dev/lib/rclex/obj/msg_int16_nif.o /home/kikuzo/rclex_samples/_build/dev/lib/rclex/obj/msg_string_nif.o -L/opt/ros/foxy/lib -lrcl -lrmw -lrcutils -lrosidl_runtime_c -lrosidl_typesupport_c -lrosidl_typesupport_introspection_c -lstd_msgs__rosidl_generator_c -lstd_msgs__rosidl_typesupport_c -lfastcdr -lfastrtps -lrmw_fastrtps_cpp
Compiling 6 files (.ex)
Generated rclex app
kikuzo@kikuzo-VirtualBox:~/rclex_samples$ 
```

## 動かしてみる
rclex_samplesにある、simple_pubを使ってみましょう。
ターミナルを２つ起動し、一つでrclex_samples/simple_pubを動かして送信しし、もう一つはROS2のコマンドでsubscriberを動かして受信してみます。

```terminal:bash
kikuzo@kikuzo-VirtualBox:~/rclex_samples$ iex -S mix
Erlang/OTP 24 [erts-12.0] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:1] [jit]

Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> RclexSamples.SimplePub. 
callback/1    pub_main/1    
iex(1)> RclexSamples.SimplePub.pub_main 1
finished rcl_init
                 publish message:hello,world
publish ok

```
実際の様子。

![スクリーンショット 2021-06-11 18.09.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/172921/02bedbd0-0803-d37d-2426-7a6ee6d492ff.png)

なお、iexコマンドの起動に先立って、ros2環境のセットアップ(setup.bashの実行)をしておかないと実行時にエラーが出ます。

## まとめ
Rclexを動かしてみました。DDSでメッセージを送受信するだけですが、とりあえず動いていることは確認できました。

みなさんも、いろいろ試してみて、様子を教えて下さい。