🕌

Nerves上で動作するLivebookをデバイスとローカルマシンとで同期する

5 min read

課題

Nerves Livebookをデバイス上で動かして、livebookでコードを書いたとします。その結果をリポジトリ管理しようと思っても、livebookのファイル(.livemdファイル)はデバイス上にあるので、そのままではうまくいきません。デバイスから手元のマシンになんらかの方法で持ってくる必要があります。

解決策

そこで、Nerves Livebookのデフォルト設定でサポートされているSFTP経由で、デバイス上のファイルを手元のマシンに取得するようにしましょう。

(本記事ではMacで検証しています。他のOSについては適宜読み替えてください)

Nerves Livebookを準備する

まずはNerves Livebookを、ドキュメントに従って動かせるようにしましょう。

下記のコマンドでデバイスにSSHできるようになればOKです。

$ ssh livebook@nerves.local

livebookを置くリポジトリを準備する

自分で作ったlivebookを管理するGitリポジトリを作っておきましょう。ディレクトリ構成は、こんな感じになりました。

  • Nerves Livebookのクローン先: /Users/antipop/src/github.com/fhunleth/nerves_livebook
  • 自分のLivebookを置くローカルリポジトリ: /Users/antipop/src/github.com/kentaro/my_nerves_livebooks

以下、適宜ご自身の環境に読み替えてください。

センサーデータを扱う準備をする

せっかくNerves Livebookを使うので、何かセンサーデータを取得してみたいですよね。そこで、CO2濃度を取得できるMH-Z19というセンサーをElixirから扱えるkentaro/mh_z19_exを使ってみることにしましょう。

Raspberry Piへのセンサーの接続については解説しません。mh-z19 · PyPIに図解されていますので、ご覧ください。

以下のようにして依存モジュールを追加します。

diff --git a/mix.exs b/mix.exs
index 316355a..9151cbd 100644
--- a/mix.exs
+++ b/mix.exs
@@ -54,7 +54,7 @@ defmodule NervesLivebook.MixProject do
       {:kino, "~> 0.2.1"},
 
       # Dependencies for all targets except :host
-      {:circuits_uart, "~> 1.3", targets: @all_targets},
+      {:circuits_uart, "~> 1.3", targets: @all_targets, override: true},
       {:circuits_gpio, "~> 0.4", targets: @all_targets},
       {:circuits_i2c, "~> 0.3", targets: @all_targets},
       {:circuits_spi, "~> 0.1", targets: @all_targets},
@@ -74,7 +74,10 @@ defmodule NervesLivebook.MixProject do
       {:nerves_system_bbb, "~> 2.10", runtime: false, targets: :bbb},
       {:nerves_system_osd32mp1, "~> 0.6", runtime: false, targets: :osd32mp1},
       {:nerves_system_x86_64, "~> 1.15", runtime: false, targets: :x86_64},
-      {:nerves_system_npi_imx6ull, "~> 0.2", runtime: false, targets: :npi_imx6ull}
+      {:nerves_system_npi_imx6ull, "~> 0.2", runtime: false, targets: :npi_imx6ull},
+
+      {:mh_z19, "~> 0.1.0"}
     ]
   end

上記でcircuits_uartの設定をいじっている箇所は、mh_z19の依存とバッティングしてエラーになったので上書きするようにしました。

SDカードをデバイスから取り出してパソコンにセットした上で、ファームウェアをビルドしてSDカードに焼きます。

$ mix deps.get
$ mix firmware.burn

SDカードをデバイスに差し戻して起動しましょう。

livebookを作成する

デバイスが無事起動したら、 http://nerves.local/ からアクセスできるはずです。

右上のNew livebookというボタンから新規作成します。作成したページで、以下のようにMH-Z19からデータを読み出すコードを動かしてみました。

無事に動きました!

作成したlivebookは、忘れずにファイルに保存しておきましょう。

デバイスから手元にファイルを取得する

さて、上記で作成したファイルはデバイス上にあります。自分で作ったlivebookを管理するGitリポジトリにファイルをコミットするには、デバイス上の自分が作成したlivebookが置かれているディレクトリに含まれる.livemdファイルを手元のMacに全部持ってくる必要があります。ここではSFTPを使って取得するようにしましょう。

以下のような内容でsync-my-livebooksとしてファイルを作っておきます。

cd /data/livebooks/notebook
get *

livebookを管理したいリポジトリから、以下のコマンドを実行します。パスワードの入力を求められるので、表示されている通りnervesを入力しましょう。

$ sftp livebook@nerves.local < sync-my-livebooks
Nerves Livebook
https://github.com/fhunleth/nerves_livebook

ssh livebook@nerves.local # Use password "nerves"

Password:

そうすると、sync-my-livebooksの内容がSFTPコマンドとして実行され、デバイス上のlivebookのファイルを手元に取得できます。あとは、取得したファイルをいつも通りgit commitしてやればよいでしょう。

補足

SCPではだめなのか

Nervesは、デフォルトでシェルをiex(ElixirのREPL)に置き換えています。また、SSH経由で送信された内容をデフォルトではElixirのコードとして実行します。そのため、普通にはSCPが使えません。SFTPを使うのがおすすめです。

NervesのSSHの設定についてはNervesSSH — nerves_ssh v0.2.2をご覧ください。

SSHFSではだめなのか

Embedded Elixirによるといけるっぽいのですが、試してみたところうまくマウントできませんでした。SSHFSがいけるなら自動的に同期できるので、その方がよいのですが。

デバイスにSSHできない場合

この検証ではRaspberry Pi 3(rpi3)を使っているのですが、デフォルトの設定ではイーサネット経由でのSSHができませんでした。そこで、rpi3用の設定ファイルを以下のように書き換えて、自前でファームウェアをビルドしてデバイスに適用しました。

diff --git a/config/rpi3.exs b/config/rpi3.exs
index c3049d8..d2fbc47 100644
--- a/config/rpi3.exs
+++ b/config/rpi3.exs
@@ -5,6 +5,6 @@ import Config
 config :vintage_net,
   regulatory_domain: "US",
   config: [
-    {"eth0", %{type: VintageNetEthernet, ipv4: %{method: :dhcp}}},
+    {"eth0", %{type: VintageNetDirect}},
     {"wlan0", %{type: VintageNetWiFi}}
   ]

これで、イーサネット経由でSSHできるようになりました。