はじめに
備忘録です。
ElixirのIoTフレームワークであるNervesにてようやくLチカしました。
2年前のALGYANハンズオンの内容1を途中まで追っかけただけです。
環境
- 母艦機: antiX21(debian GNUlinux11/bullseye) on sony VAIO VGN-FW72JGB / 4GB
- target機: RaspberryPI-0 WH
- Erlang: 25.0.4
- Elixir: 1.14.0-otp-25
コマンド操作はすべて母艦機上で行います。
asdf のインストール
erlangとelixirのバージョン管理ツールにasdfを使います。
asdfの公式サイトのgetting startedに従います。
$ sudo apt install curl git
$ git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.10.2
私はbash
+git
のケースなので、公式サイトの指示に従って、~/.bashrc
を編集し、bashを再度立ち上げます。
:
. $HOME/.asdf/asdf.sh
. $HOME/.asdf/completions/asdf.bash
Erlang / Elixir のインストール
nishiuchiさんの記事に従って、以下を進めていきます。
Erlang/Elixirとも最新バージョンのインストールでいいと思います。
【オンライン】豪華プレゼント付!Elixir/Nerves(ナーブス)体験ハンズオン!
# プラグイン
$ asdf plugin-add erlang https://github.com/asdf-vm/asdf-erlang.git
$ asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git
# erlang/elixirインストール
$ asdf install erlang 25.0.4
$ asdf install elixir 1.14.0-otp-25
# 使用するバージョンを指定
$ asdf global erlang 25.0.4
$ asdf global elixir 1.14.0-otp-25
erlangのバージョンは公式サイトで調べます。
asdf install
でエラーが出る場合は、エラー内容に従って、インストールします。
:
Building Erlang/OTP 25.0.2 (asdf_25.0.2), please wait...
WARNING: It appears that a required development package 'libssl-dev' is not installed.
WARNING: It appears that a required development package 'make' is not installed.
WARNING: It appears that a required development package 'automake' is not installed.
WARNING: It appears that a required development package 'autoconf' is not installed.
WARNING: It appears that a required development package 'libncurses5-dev' is not installed.
WARNING: It appears that a required development package 'gcc' is not installed.
:
$ sudo apt install libssl-dev make automake autoconf libncurses5-dev gcc
Nervesのインストール
公式のhexdocページの内容に基づいて進めます。
$ sudo apt install build-essential automake autoconf git squashfs-tools ssh-askpass pkg-config curl
fwup
のインストール
fwup
はmicroSDカードにfirmwareを書き込むツールです。
公式ページに従って、debパッケージをブラウザ上でダウンロードしてから、以下のコマンドでインストールします。
# download fwup.deb
$ sudo dpkg -i ~/Downloads/fwup_1.9.0_amd64.deb
引き続きNervesのインストールを続けます。
$ sudo apt install libssl-dev libncurses5-dev bc m4 unzip cmake python
$ mix local.hex
$ mix local.rebar
$ mix archive.install hex nerves_bootstrap
Nervesのインストールはこれで終了です。
Nervesのプロジェクトの作成
公式ページにかかれている通りに新規プロジェクトを作成します。
mix nerves.new {プロジェクト名}
で作成できます。
$ mix nerves.new Hellonerves
$ cd Hello_nerves
wifiの設定
vintage.net
を使います。
config/target.exs
に設定します。
wifiの設定は"wlan0"の箇所をデフォルトの状態から書き換えます。
config :vintage_net,
regulatory_domain: "US",
config: [
{"usb0", %{type: VintageNetDirect}},
{"eth0",
%{
type: VintageNetEthernet,
ipv4: %{method: :dhcp}
}},
{"wlan0",
%{type: VintageNetWiFi,
vintage_net_wifi: %{
networks: [
%{ key_mgmt: :wpa_psk,
ssid: System.get_env("MY_NERVES_SSID"),
psk: System.get_env("MY_NERVES_PSK")
}
]
},
ipv4: %{method: :dhcp}
}}
]
wifiのssid, psk を環境変数に設定してから渡します。各自の状況に合わせて設定します。
ソースコードをgithubなどの公の場所におく場合に、パスワード類をコードに記載することを避けるためです。
環境変数名は他の変数とかぶらない限り、コードと一致していれば何でもいいです。
$ export MY_NERVES_SSID=*****
$ export MY_NERVES_PSK=+++++
ssh鍵の作成
ssh鍵を作成し、公開鍵のファイル名をコードに記載します。
$ ssh-keygen -t ed25519 -f ~/.ssh/id_nerves_key -N ""
:
# ~/.ssh下に id_nerves_key(秘密鍵)と id_nerves_key.pub(公開鍵)が作成される。
作成されたssh公開鍵をconfig/target.exs
のkeys=[...
の箇所に、他の例に従って記載します。
keys =
[
Path.join([System.user_home!(), ".ssh", "id_nerves_key.pub"]), # <-- 追加
Path.join([System.user_home!(), ".ssh", "id_rsa.pub"]),
:
]
target
環境変数の設定
私の場合はtarget機にrpi0
を使いますので、以下の設定にします。
他のtarget機器を使用する場合はこちらに従って設定します。
$ export MIX_TARGET=rpi0
mix deps.get
, mix firmware
コンパイルしていきます。
mix deps.get
はdepsに依存するライブラリの取得をします。
先ほど環境変数を設定したシェルと同じシェルで実施する必要があります。
違うシェルでは環境変数が反映されません。
$ mix deps.get # ライブラリの読み込みなど
$ mix firmware # コンパイル
microSDカードの準備
microSDカードのリードライターを接続する前後でlsblk -a | grep -v loop
コマンドの結果の差異と、microSDカードの容量の情報から、microSDカードのデバイスファイル名を確認しておきます。恐らく/dev/sdb
とか/dev/sdc
などになると思います。私の場合は、/dev/sdd
でした。
間違えると母艦機のシステムを壊してしまう可能性があるので、十分注意します。
$ lsblk -a | grep -v loop
mix burn
microSDカードへの書き込みです。
$ mix burn
前項で確認したデバイスファイル名を聞かれるので、合っていることを確認してenterで進めます。
自分のlinux上でのログインパスワードを聞かれるので、入力します。
パスワードが合っていれば、すぐにmicroSDカードへの書き込みが始まり、5秒間くらいで終了します。
RaspberryPIの起動
microSDカードをリーダーライターから抜き、RaspberryPI0に挿入します。
microUSBポートに電源ケーブルを接続して、5V電源を供給します。rpi0なら1Aくらいの供給能力があれば十分かと思います。電源を供給すると電源の緑色LEDの2度点滅を繰り返します。
母艦機からのssh接続
RaspberryPIを起動してから約30秒間してから、pingでアクセス可否を確認します。
$ ping -c 3 nerves.local
RaspberryPI起動からの時間が短いと、名前解決エラーが出ます。さらに10秒間くらい待ってから再度確認してください。
PING nerves.local (***.***.***.***) 56(84) byes of data.
(ここでしばらく止まって)
--- nerves.local ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time ****ms
pingから3回とも応答があり、packet lossが0であればアクセス成功です。
PING nerves.local (***.***.***.***) 56(84) byes of data.
64 bytes from nerves-**** (***.***.***.***): icmp_seq=1 ttl=64 time=8.87 ms
64 bytes from nerves-**** (***.***.***.***): icmp_seq=2 ttl=64 time=9.91 ms
64 bytes from nerves-**** (***.***.***.***): icmp_seq=3 ttl=64 time=21.3 ms
--- nerves.local ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 8.871/13.356/21.291/5.626 ms
私の場合はnerves.local
では名前解決できませんでした。あるあるのようです。23
$ ping -c 3 nerves.local
ping: nerves.local: Name or service not known
なので、別のパソコンからルーターにログインして、ホスト名が "nerves-****" というものを探しました。
****は英数4桁のようです。
次にssh接続します。
$ ssh nerves-****
するとパスワードを延々と聞かれ続ける状態になりました。
SSH server
Enter password for "user"
password:
高瀬先生4の記事を参考にして、以下のように~/.ssh/config
に設定を追加すると、うまく接続できるようになりました。5
あとのmix upload
するときもnerves.local
としてアクセスするので、nerves.local
でアクセスできるようにしておくと後々楽だと思います。6
Host nerves.local
Hostname nerves-****
StrictHostKeyChecking no
IdentityFile ~/.ssh/id_nerves_key
ssh接続が成功すれば、nervesのロゴが出てきてiexのプロンプト待機になります。
$ ssh nerves.local
Interactive Elixir (1.14.0) - press Ctrl+C to exit (type h() ENTER for help)
████▄▄ ▐███
█▌ ▀▀██▄▄ ▐█
█▌ ▄▄ ▀▀ ▐█ N E R V E S
█▌ ▀▀██▄▄ ▐█
███▌ ▀▀████
hellonerves 0.1.0 (.....................................) arm rpi0
Uptime : 1 minutes and 12 seconds
Clock : 2022-09-23 06:56:09 UTC
Firmware : Valid (B) Applications : 36 started
Memory usage : 41 MB (13%) Part usage : 0 MB (0%)
Hostname : nerves-**** Load average : 0.34 0.13 0.04
wlan0 : **.**.**.**/**, **:**:**:**:**:**:**:**/**, **::**:**:**:**/**
usb0 : **.**.**.**/**
Nerves CLI help: https://hexdocs.pm/nerves/using-the-cli.html
Toolshed imported. Run h(Toolshed) for more info.
iex(1) >
ssh接続に失敗する場合は、sshの鍵の作成に失敗している可能性があるので、そこからやり直す必要があります。
元々準備されているHellonerves.hello()関数を呼び出して、worldアトムが返ることを確認します。
iex> Hellonerves.hello()
:world
exit
と入力して、一旦ssh接続を終了してシェルに戻ります。
iex> exit
Connection to nerves.local closed.
$
コードを書き換えてmix upload
何でもいいのですが、Hellonerves.hello()
関数をちょこっと書き換えます。
defmodule Hellonerves do
def hello do
"Hello Nerves!"
end
end
mix firmware
でコンパイルします。
以降でmix deps.get
, mix firmware
, mix upload
を実行するときは、必ず環境変数(MIX_TARGET, wifiのssid/psk)が設定されている必要があります。
$ mix firmware
microSDカードをRaspberryPIに挿したまま、mix upload
コマンドでファームウェアを書き換えます。
mix upload
の場合はmix burn
に比べて書き込みスピードが少し遅いようです。
$ mix upload
mix upload
から再び約30秒間待ってからssh接続して、Hellonerves.hello()
コマンドを実行して、
書き換えたところが反映されていることを確認します。
$ ssh nerves.local
:
iex> Hellonerves.hello()
"Hello Nerves!"
iex> exit
Connection to nerves.local closed.
$
Lチカとスイッチ状態検出
参考資料1.のALGYANハンズオン基本編1.にあるデバイスを利用します。
添付写真のように、LEDはD16に、プッシュスイッチはD5に接続されています。
circuits.GPIOのライブラリを使います。
mix.exs
内のdepsに以下を追加します。
defp deps do
[
:
{ .....}, # <-- 前行の最後にコンマも入れてください
{:circuits_gpio, "~> 0.4"} # <-- 追加
]
end
mix deps.get
でcircuits_gpio
のライブラリを取得してきます。
mix firmware
, mix.upload
を実行し、再度ssh接続します。
$ mix deps.get
$ mix firmware
$ mix upload
$ ssh nerves.local
:
iex>
LEDは16番に接続されています。
GPIOのwriteコマンドの引数でLEDのon/offが制御できることを確認します。
iex> {:ok, gpio} = Circuits.GPIIO.open(16, :output)
iex> Circuits.GPIO.write(gpio, 1) # LED点灯
iex> Circuits.GPIO.write(gpio, 0) # LED消灯
プッシュスイッチもやってみます。5番に接続しています。
iex> {:ok, gpio} = Circuits.GPIO.open(5, :input)
iex> Circuits.GPIO.read(gpio) # ボタンを押しながら実行すると1が返る
1
iex> Circuits.GPIO.read(gpio) # ボタンを押さずに実行すると0が返る
0
iex> exit
$
プッシュスイッチを押したときにLED点灯
ハンズオンの資料に基づいてやってみます。
以下のコードを追加します。
ファイル名はlib/
下にあれば何でもOKです。
defmodule NervesjpBasis.ButtonServer do
use GenServer
require Logger
alias Circuits.GPIO
@button 5
@led 16
def start_link(_) do
GenServer.start_link(__MODULE__, [])
end
def init(_) do
{:ok, button} = GPIO.open(@button, :input, pull_mode: :pullup)
GPIO.set_interrupts(button, :both)
{:ok, led} = GPIO.open(@led, :output)
{:ok, %{button: button, led: led}}
end
def handle_info({:circuits_gpio, @button, _timestamp, value}, state) do
Logger.debug("Button is now #{value}")
GPIO.write(state.led, value)
{:noreply, state}
end
end
mix.deps.get
, mix.firmware
, mix upload
, ssh nerves.local
を順に実行します。
iex> NervesjpBases.ButtonServer.start_link([])
{:ok, #PID<****.0>}
iex>
この状態でプッシュスイッチを押している間だけLEDが点灯することを確認します。
UART経由でiexコンソールの表示
NishiguchiさんのElixir/Nerves UARTでシリアルコンソール接続をマネしてやってみました。
UART-TTL変換ケーブルの入手
私は以下を入手しました。
Prolific PL2303使用品
単なるUSBケーブルの一端をばらしたモノかと錯覚しましたが、USB接続(typeA形状)のコネクタ部にICを含む回路が内蔵されているようです。
私の環境はlinuxだったため?か、デバイスドライバのインストールは不要でした。デバイスファイル名は/dev/ttyUSB0
として認識されました。ls /dev/tty*
で確認。
ケーブルの接続
前述のNishiguchiさんのqiita記事のリンクにある記事の内容に従って、変換ケーブルの3本をラズパイのGPIOピンに接続します。赤線(5V)のみ非接続です。
エミュレータのインストール
私はscreen
を使用しました。
$ sudo apt install screen
エミュレータの起動
$ screen /dev/ttyUSB0 115200
画面が真っ黒のままの場合は、enterを何度が押すとプロンプトが出現します。
文字化けする場合はボーレートの設定(上記コマンド中の115200の箇所)を入れてみるといいと思います。
エミュレータ端末を切断するときは、ctrl-a + k
を押すと、kill screen?という確認が入るので、yを押下して終了すると、bashプロンプトに戻ります。
ctrl-a + k
の前にexit
を入力してしまうと、UART接続のターミナルが終了してしまうようで、再度screenコマンドを叩いても端末表示はされなくなりました。
NervesではUARTのデバイスファイルは、rpi0ではデフォルトで/dev/ttyAMA0
と設定されるようです。その後screenコマンド実行後にNerves側で/dev/ttyAMA0
-->/dev/ttyUSB0
に自動接続されるのかな?と思っています。
参考資料
- ALGYAN Elixir/Nerves(ナーブス)体験ハンズオン!
- ALGYAN x Seeed x NervesJPハンズオン!に向けた開発環境の準備方法(高瀬先生の記事)
- NervesのSSH環境を整えた 〜upload.shを使ってファームウェア更新も〜
- nerves_pack(vintage_net含む)を使ってNervesのネットワーク設定をした〜SSHログインまで〜
-
Nerves講義ノート・始めてみる回の、3(6)通信状態の確認の項を参照。 ↩
-
高瀬先生の記事:ElixirでIoT#4.3:Nervesアプリ開発時のよくあるトラブルをシューティング nerves.localが見つからない ↩
-
高瀬先生の記事:ElixirでIoT#4.3:Nervesアプリ開発時のよくあるトラブルをシューティング ssh接続できない ↩
-
ssh接続時にパスワードを延々と聞かれるのは、ssh接続時の秘密鍵がどれかわからないためと思われます。ssh接続時の秘密鍵を明示すればうまくいくのかと。 ↩
-
~/.ssh/configへの設定記載がなくても、
mix firmware
,mix upload
コマンドにて、mix firmware nerves-****
,mix upload nerves-****
などと記述して実行できるようです。 ↩