Rclex on Nerves!! なプロジェクトをイチからはじめて開発する！ 〜e-RT3 Plusを例題として〜

この記事は「[#NervesJP Advent Calendar 2022](https://qiita.com/advent-calendar/2022/nervesjp)」の9日目の記事です（遅れてスミマセン（あとネタ変更しました:D

# まえおき

2022年12月17日(土)に開催の「[第11回FA設備技術勉強会](https://fa-study.connpass.com/event/260896/)」では，ElixirのROS 2クライアントライブラリである [Rclex](https://github.com/rclex/rclex) が [Nerves](https://www.nerves-project.org/) でいごきまっせ！てなLTを披露しました．

**イベント情報**

https://fa-study.connpass.com/event/260896/

**スライド資料**

https://speakerdeck.com/takasehideki/rclex-on-nerves-on-e-rt3-plus

デモで披露したNervesアプリのリポジトリは，こちらにあります．

https://github.com/b5g-ex/rclex_on_nerves_on_f3rp70

デモ動画はコチラです．

https://twitter.com/takasehideki/status/1604043248020131841

リポジトリの README はそれなりに整理してあるつもりなので，試してみたかったらぜひにどうぞです！
横河電機のリアルタイムOSコントローラである [e-RT3 Plus](https://www.yokogawa.co.jp/solutions/products-and-services/control/control-devices/real-time-os-controller/) を対象としていますが，他のボードでもいごくはずです．

# この記事の目的

上記のリポジトリではあらかじめ作成済みのプロジェクトをビルドするだけでいいわけですが，せっかくなら Rclex on Nerves なIoTシステムをイチからつくってみませんか？？
ということでこの記事ではこの方法を紹介します．

っといいつつ，実はこの方法は Rclex のドキュメントにちゃんとまとめてあります．ってえいごなんですが;(

https://hexdocs.pm/rclex/use_on_nerves.html

要は今回はこれを和訳しただけです．ただし Rclex on Nerves はまだ experimental な感じでして，今後またやりかたが改善される可能性があります（あるいはご意見ください！）．つまりこの記事の情報が古くなる可能性がありますので，公式の情報もご参照ください．

# 動作対象のボード

現時点では，下記表のボードで Rclex on Nerves の動作が確認されています．
頑張ってゲトってください！

| board | tag | arch | support for nerves_system |
| :--- | :--- | :---| :---|
| [Raspberry Pi 4](https://github.com/nerves-project/nerves_system_rpi4) | rpi4 | arm64v8 | Officially supported, recommended |
| [BeagleBone Green](https://github.com/nerves-project/nerves_system_bbb) | bbb | arm32v7 | Officially supported |
| [Kria KR260](https://github.com/b5g-ex/nerves_system_kr260) | kr260 | arm64v8 | Third-party supported |
| [ODYSSEY - STM32MP157C](https://github.com/b5g-ex/nerves_system_stm32mp157c_odyssey) | stm32mp157c_odyssey | arm32v7 | Third-party supported |
| [F3RP70 (e-RT3 Plus)](https://github.com/pojiro/nerves_system_f3rp70) | f3rp70 | arm32v7 | Third-party supported |

# 準備

ホストPCのOSは不問です！（WindowsはWSLのほうがいいかも？
もちろんElixir/Nerves開発環境は準備してください．

https://hexdocs.pm/nerves/installation.html

Rclex をネイティブに動作させる場合には ROS 2 の開発・実行環境のインストールも必要でした．
Rclex on Nerves では，Dockerがうまく活用されているので，これらのインストールは不要です！！（開発環境が不要なのにROS 2のアプリが開発できる，コレ実はすごいことなんですよ:D
その代わり，Docker（DesktopまたはEngine）をインストールしてください．Engineの場合はクロス環境向けの qemu や binfmt も必要です（Ubuntuの例： `sudo apt-get install qemu binfmt-support qemu-user-static` ）

# 開発手順

この記事では e-RT3 Plus (F3RP70) を例題として紹介しています．プロセッサアーキは `arm32v7` になります．このあたりは使用されるボードの tag と arch に合わせて変更してください．

- `export MIX_TARGET=f3rp70`
- `--arch arm32v7`

あと，以降に示す開発手順に従った作業履歴がコチラにあります．合わせて参考にすると良いかと思います．

https://github.com/b5g-ex/rclex_on_nerves_on_f3rp70/commits/main


## Nervesの新規作成と依存ライブラリの取得

ここは通常のNervesアプリの開発手順と一緒です．

```
mix nerves.new rclex_usage_on_nerves
cd rclex_usage_on_nerves
```

nerves_system_f3rp70 は Nerves Project 公式のパッケージではないため，このあとに `mix.exs` を編集してやる必要があります（rpi4 とか公式のものでは編集不要です）

```diff_elixir:mix.exs
-      {:nerves_system_rpi, "~> 1.19", runtime: false, targets: :rpi},
-      {:nerves_system_rpi0, "~> 1.19", runtime: false, targets: :rpi0},
-      {:nerves_system_rpi2, "~> 1.19", runtime: false, targets: :rpi2},
-      {:nerves_system_rpi3, "~> 1.19", runtime: false, targets: :rpi3},
-      {:nerves_system_rpi3a, "~> 1.19", runtime: false, targets: :rpi3a},
-      {:nerves_system_rpi4, "~> 1.19", runtime: false, targets: :rpi4},
-      {:nerves_system_bbb, "~> 2.14", runtime: false, targets: :bbb},
-      {:nerves_system_osd32mp1, "~> 0.10", runtime: false, targets: :osd32mp1},
-      {:nerves_system_x86_64, "~> 1.19", runtime: false, targets: :x86_64},
-      {:nerves_system_grisp2, "~> 0.3", runtime: false, targets: :grisp2}
+      {:nerves_system_f3rp70, "~> 0.4", runtime: false, targets: :f3rp70}
```

```diff_elixir:mix.exs
-  @all_targets [:rpi, :rpi0, :rpi2, :rpi3, :rpi3a, :rpi4, :bbb, :osd32mp1, :x86_64, :grisp2]
+  @all_targets [:f3rp70]
```

このあとに依存ライブラリの取得を行ってください．

```
export MIX_TARGET=f3rp70
mix deps.get
```

## rclex のインストール

`rclex` は hex パッケージとして開済みです！

https://hex.pm/packages/rclex

`mix.exs` に `rclex` を依存ライブラリのリストとして追加してください．

```diff_elixir:mix.exs
  defp deps do
    [
      ...
+      {:rclex, "~> 0.8.3"},
      ...
    ]
  end
```


その後，普通に `mix deps.get` するだけです．

```
mix deps.get
```

## ROS 2リソースの用意

ROS 2 のリソースやライブラリを Nerves のファイルシステムに取り込みます．
ここで Docker コマンドを使用するわけです．

```
export ROS_DISTRO=foxy
mix rclex.prep.ros2 --arch arm32v7
```

rpi4 とか arm64 アーキのものでは `--arch arm64v8` にしてやる必要があります．

ホストとターゲットのアーキが異なる場合には下記のメッセージが何度か表示されますが，これは無視しても問題ありません．

> WARNING: The requested image's platform (linux/arm/v7) does not match the detected host platform (linux/amd64) and no specific platform was requested

## ROS 2メッセージ型の生成

Rclex は ROS 2 で規定されているトピックによる出版購読型通信を実現します（ROS 2のメッセージ型についての詳しい話は[コチラ](https://docs.ros.org/en/foxy/Concepts/About-ROS-Interfaces.html)）

この通信メッセージのための型を生成します
`config/config.exs` に `ros2_message_types` というフィールドを用意して，アプリで使用したい型の情報を記述します．コンマ区切りで複数の型が指定できます．

```diff_elixir:config/config.exs
+config :rclex, :ros2_message_types, [
+  "std_msgs/msg/String",
+  "geometry_msgs/msg/Twist",
+  "turtlesim/msg/Pose"
+]
```

このあと，次のコマンドを実行してください．

```
mix rclex.gen.msgs
```

使用したいメッセージの型を追加・変更したい場合は，ファイルの変更とこのコマンドを改めて実行してください．

## `erlinit.config` のコピーと編集

`erlinit.config` というファイルを Nerves のファイルシステムにコピーします．
（これはErlang VMの設定ファイルです）

```
cp deps/nerves_system_f3rp70/rootfs_overlay/etc/erlinit.config rootfs_overlay/etc
```

このファイルに `-e LD_LIBRARY_PATH=/opt/ros/foxy/lib` という行を追加します．
`foxy` は `ROS_DISTRO` に合わせてください．

```deps/nerves_system_f3rp70/rootfs_overlay/etc/erlinit.config
# Enable UTF-8 filename handling in Erlang and custom inet configuration
-e LANG=en_US.UTF-8;LANGUAGE=en;ERL_INETRC=/etc/erl_inetrc;ERL_CRASH_DUMP=/root/crash.dump
-e LD_LIBRARY_PATH=/opt/ros/foxy/lib
```

## Rclexのコードを実装！

これで Rclex on Nerves を実現する準備が整いました！！

[Rclex API](https://hexdocs.pm/rclex/api-reference.html)を自在に使いこなしてください！

ここでは最も単純な例 `lib/rclex_usage_on_nerves.ex` を示します．文字列型のメッセージを `/chatter` のトピックに出版します．

```elixir:lib/rclex_usage_on_nerves.ex
defmodule RclexUsageOnNerves do
  def publish_message do
    context = Rclex.rclexinit()
    {:ok, node} = Rclex.ResourceServer.create_node(context, 'talker')
    {:ok, publisher} = Rclex.Node.create_publisher(node, 'StdMsgs.Msg.String', 'chatter')

    msg = Rclex.Msg.initialize('StdMsgs.Msg.String')
    data = "Hello World from Rclex!"
    msg_struct = %Rclex.StdMsgs.Msg.String{data: String.to_charlist(data)}
    Rclex.Msg.set(msg, msg_struct, 'StdMsgs.Msg.String')

    # This sleep is essential for now, see Issue #212
    Process.sleep(100)

    IO.puts("Rclex: Publishing: #{data}")
    Rclex.Publisher.publish([publisher], [msg])

    Rclex.Node.finish_job(publisher)
    Rclex.ResourceServer.finish_node(node)
    Rclex.shutdown(context)
  end
end
```

いろいろサンプルを公開していますので，いろいろ参考にしてください．

https://github.com/rclex/rclex_examples

https://github.com/b5g-ex/rclex_on_nerves

## ファームウェアの生成とSDカードへの書き込み

これはもう通常のNervesアプリ開発とと一緒です．

```
mix firmware
mix burn   # or, mix upload
```

# 実行例

Nervesデバイスに SSH で接続します．

```
ssh nerves.local
```

Nerves の IEx 上で次のように実行してみてください．

```
iex()> RclexUsageOnNerves.publish_message
Rclex: Publishing: Hello World from Rclex!
{:ok, #Reference<0.2970499651.1284374532.3555>}
```

出版されたメッセージを確認する方法はいくつかありますが，ROS 2のネイティブ環境で購読する例をお示しします（ROS 2環境やっぱいるんかーい！

```
$ source /opt/ros/foxy/setup.bash
$ ros2 topic echo /chatter std_msgs/msg/String
data: Hello World from Rclex!
---
```

やったねっ！:tada:

# おわりに

スターくださぁい！！

https://github.com/rclex/rclex

完全に蛇足ですが，筆者は Rclex をコアメンテしている立場ですが，この Nerves 対応についてはリポジトリに多大な contribution をいただけた成果となっています．貢献いただけましたこと，ここで深く御礼申し上げます m(_ _)m

ということでみなさんからも contribution くださぁい！！