Elixir・Nervesで複数のラズパイと通信したい

複数のラズパイと通信する方法は色々考えられると思いますが、せっかくElixir・[Nerves]でファームウエア開発をしているので[分散Erlang][Distributed Erlang]に挑戦したいと思います。

## 準備

### ラズパイのhostnameまたはIPアドレスを調べる

[nerves_pack] v0.5.0以降のバージョンをお使いであれば、[nerves_motd]で表示させるのが手っ取り早いです。

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/97xt3gij2f3d6afcm53f.png)

[nerves_motd]: https://github.com/nerves-project/nerves_motd

### DNS Bridgeを設定する

ネットワーク関係に疎いので詳しくはよくわかりませんが、[mdns_lite - DNS Bridge configuration](https://hexdocs.pm/mdns_lite/readme.html#dns-bridge-configuration)ドキュメントによると、Erlang/OTPのビルトインDNSリゾルバーにmDNSについての情報を教えてあげなければならないそうです。
[Underjordのデモ（YouTube）](https://youtu.be/ZdtAVlzFf6Q?t=1503)が参考になりました。

## Erlangノードを起動

- やり方は[nerves_pack - Erlang distribution]ドキュメントに説明されています。
- ここでは2つのラズパイを用意しました。それぞれのホスト名は`nerves-mn00.local`と`nerves-mn02.local`です。
- ホスト名の代わりにIPアドレスを使用することも可能です。
- 共通の[Erlang magic cookie]を使用することが重要です。

ターミナルを開き、ラズパイA（nerves-mn00.local）にSSHします。

```elixir
❯ ssh nerves-mn00.local

# OS上でepmdを起動
iex> System.cmd("epmd", ["-daemon"])

# Erlangノードを起動
iex> Node.start(:"nerves@nerves-mn00.local")

# クッキーをセット
iex(nerves@nerves-mn00.local)> Node.set_cookie(:securecookie)
```

新たにターミナルを開き、ラズパイB（nerves-mn02.local）にSSHします。

```elixir
❯ ssh nerves-mn02.local

# OS上でepmdを起動
iex> System.cmd("epmd", ["-daemon"])

# Erlangノードを起動
iex> Node.start(:"nerves@nerves-mn02.local")

# クッキーをセット
iex(nerves@nerves-mn02.local)> Node.set_cookie(:securecookie)
```

[nerves_pack - Erlang distribution]: https://hexdocs.pm/nerves_pack/readme.html#erlang-distribution
[Erlang magic cookie]: https://erlang.org/doc/reference_manual/distributed.html#security

## Erlangノードを接続

- `nerves-mn02.local`を`nerves-mn00.local`に接続します。

```elixir
iex(nerves@nerves-mn02.local)> Node.connect(:"nerves@nerves-mn00.local")
true

iex(nerves@nerves-mn02.local)> Node.list()
[:"nerves@nerves-mn00.local"]
```

## 別のノード（別のラズパイで）関数を実行

- `nerves-mn02.local`から`nerves-mn00.local`の中で関数を実行してみます。
- 例として[`Node.spawn/2`]を使って、[`Toolshed.Nerves.uname/0`]を実行して、デバイスの情報を確認してみます。

```elixir
Node.spawn(
  # 相手のノード名
  :"nerves@nerves-mn00.local",
  # 実行したい関数
  fn -> Toolshed.Nerves.uname() end
)
```

```elixir
iex(nerves@nerves-mn02.local)> Node.spawn(:"nerves@nerves-mn00.local", fn -> Toolshed.Nerves.uname() end)
Nerves nerves-mn00 hello_nerves 0.1.0 (240d82e1-1e6c-5800-0ef9-63cba9efc212) arm
#PID<51667.6840.0>
```

やった〜:tada::tada::tada:　
[分散Erlang][Distributed Erlang]を利用して別のラズパイで関数を実行することができました。

別のやり方として、[phoenix_pubsub]を利用して別のラズパイにメッセージを送りつけることも可能です。こちらのほうが扱いやすいかもしれません。

今回学んだ内容を[`kantan_cluster`]というElixirパッケージにまとめました。自分用のツールですが、もしよかったら遊んでみてください。

## 資料

- [Elixir Node](https://hexdocs.pm/elixir/1.13/Node.html)
- [Forming an Erlang cluster of Pi Zeros by underjord](https://youtu.be/ZdtAVlzFf6Q)
- [mdns_lite - DNS Bridge configuration](https://hexdocs.pm/mdns_lite/readme.html#dns-bridge-configuration)
- [nerves_pack - Erlang distribution](https://hexdocs.pm/nerves_pack/readme.html#erlang-distribution)
- [nerves_pack（vintage_net含む）を使ってNerves起動時に`Node.connect()`するようにした ](https://qiita.com/nishiuchikazuma/items/f68d2661959197d0765c) by @nishiuchikazuma
- [クラウドの外でエッジサーバを作るためのElixir技術スタック（＋立てた予定を仲間と実現できるようになる思考パターン） ](https://qiita.com/piacerex/items/5c6db095f4c567c36f69) by @piacerex
- [はじめてNerves(8) 単一ホストで動くシステムを複数ホストに分散する ](https://qiita.com/kikuyuta/items/4b8326d0584fe26083a6) by @kikuyuta

<!-- Links -->

[Nerves]: https://www.nerves-project.org/
[Distributed Erlang]: https://erlang.org/doc/reference_manual/distributed.html
[`Toolshed.Nerves.uname/0`]: https://hexdocs.pm/toolshed/Toolshed.Nerves-function-uname.html#uname/0
[`Node.spawn/2`]: https://hexdocs.pm/elixir/1.12/Node.html#spawn/2
[`kantan_cluster`]: https://github.com/mnishiguchi/kantan_cluster
[nerves_pack]: https://hex.pm/packages/nerves_pack
[phoenix_pubsub]: https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html