Nervesで起動時に`Node.connect()`するようにした


これまでNervesで頑張っていたのは、Nerves起動時にNode.connect()したかったからなのでした。ようやくできるようになりました\(^o^)/

これは「Nervesのネットワーク設定をmix firmwareする時に指定できるようにした」の続編になります。

更新情報

こちらの記事のほうが新しいので興味があればぜひ。

やりたいこと

Nerves起動時にOSXにNode.connect()して、接続できるようにしたい。

環境

Nervesプロジェクト名

  • Exineris001

ハードウェア

  • Mac OSX
  • NervesをRaspberryPi 3で稼働

ネットワーク

  • Mac OSX

    • 有線LAN→メンテナンス用、固定IPアドレス(192.168.5.100/24)
    • 無線LAN→インターネット接続用、DHCP(本記事では192.168.46.6)
  • ラズパイ

    • 有線LAN(eth0)→メンテナンス用、固定IPアドレス(192.168.5/24の範囲)
    • 無線LAN(wlan0)→インターネット接続用、DHCP(192.168.46/24から自動割当)
    • 192.168.5.240(default)は後述

こんな風に書きました

mix.exs

お約束でdefp depsに必要なライブラリを記載して、def releaseにあらかじめ決めておきたいCookieを記載しました。

  • defp depsに必要なライブラリを記載
  • def releaseにあらかじめ決めておきたいCookieを記載
mix.exs
defp deps do
  [
    ()
    {:nerves_network, "~> 0.5", targets: @all_targets},
    {:nerves_firmware_ssh, "~> 0.3", targets: @all_targets},
    {:ex_string_util, "~> 0.1.0"}
  ]
end

def release do
[
  cookie: "comecomeeverybody"    # セットしたいCookieを設定
]
end

config/target.exs

:nerves_init_gadgetやIPアドレス等のネットワークの設定はconfig/network.exsにまとめて記載するようにしました。SSHの鍵の登録を別途する場合はここで登録します。
(参考) NervesのSSH環境を整えた 〜upload.shを使ってファームウェア更新も〜

  • :nerves_init_gadgetをコメントアウト
  • import_config("network.exs")を記載して、IPアドレス等のネットワーク設定を別ファイルに分離
config/target.exs
# config :nerves_init_gadget,
#   ifname: "usb0",
#   address_method: :dhcpd,
#   mdns_domain: "nerves.local",
#   node_name: node_name,
#   node_host: :mdns_domain

import_config("network.exs")

config/network.exs

ここで無線(wlan0)と有線(eth0)のネットワーク設定をおこないます。
:nerves_config_gadgetに記載しているnode_name@node_hostという名前でNodeが起動します。

config/network.exs
import Config

settings = [
  networks: [
    [ssid: "SSIDを記載", psk: "PSKを記載", key_mgmt: :"WPA-PSK", priority: 0,
      ipv4_address_method: :dhcp,
      nameservers: ["8.8.8.8", "8.8.4.4"]
    ]
  ]
]

config :nerves_network, default: [
  wlan0: settings,
  eth0: [
    ipv4_address_method: :static,
    ipv4_address: "192.168.5.55",
    ipv4_subnet_mask: "255.255.255.0"
  ]
]

config :nerves_init_gadget,
  ifname: "wlan0",
  address_method: :dhcp,
  node_name: "exineris001",
  node_host: :ip

lib/exineris001/application.ex

アプリケーション起動時の引数として、OSXのノード名@ホスト名([email protected])を渡すようにしました。
ホスト名といいつつIPアドレスを記載しています。ここ、DNSサーバたてて解決できるかどうかが次の課題かなぁと思ってるところです。

lib/exineris001/application.ex
def children(_target) do
  {Exineris001.Bootexineris, ["[email protected]"]}    # OSXのWiFiのIPアドレス
end

lib/exineris001/bootexineris.ex

Nervesとしては以下の順で処理がすすむのですべての項目がクリア(今回は:true(trueでも同じ))にならないとNode.connect()しないようにしました。

  1. wlan0インターフェース作成 → is_wlan0_ready()で確認
  2. WiFi設定を有効化後、DHCPでIPアドレス取得 → is_ipv4_ready()で確認
  3. nodeの設定 → is_nodeconn_ready()で確認

ExStringUtil.is_ip!()はIPアドレスのチェックがかなりかなり甘いので注意して使うことが必要です。

lib/exineris001/bootexineris.ex
defmodule Exineris001.Bootexineris do

  use GenServer
  require Logger

  def start_link(node \\ []) do
    GenServer.start_link(__MODULE__, node, name: __MODULE__)
  end

  def init(node) do
    conn = do_nodeconn(node)
    {:ok, conn}
  end

  def do_nodeconn(node) do
    nodeconn_status = get_nodeconn_status()
    case nodeconn_status do
      [:true, :true, :true] ->
        conn = Node.connect(:"#{node}")
        case conn do
          :true  -> Logger.info("===== conn => #{conn} =====")
          _      -> do_nodeconn(node)
        end
      [_, _, _] ->
        do_nodeconn(node)
    end
  end

  def get_nodeconn_status() do
    [is_nodeconn_ready(), is_ipv4_ready(), is_wlan0_ready()]
  end

  def is_nodeconn_ready() do
    node = Node.self()
    case node do
      :nonode@nohost -> :false
      _              -> :true
    end
  end

  def is_ipv4_ready() do
    case Nerves.NetworkInterface.settings("wlan0") do
      {:ok, list} -> Map.get(list, :ipv4_address) |> ExStringUtil.is_ip!()   # return: true or false
      {_, _}      -> :false
    end
  end

  def is_wlan0_ready() do
    wlan0 = Nerves.NetworkInterface.interfaces() |> Enum.find(& &1 == "wlan0")
    case wlan0 do
      "wlan0" -> :true
      _       -> :false
    end
  end

end

実行→確認

準備として、OSXのターミナルから名前とCookieを指定してプロセスを起動しておきます。
Nerves起動前はNode.list()しても空のリストが返ってきますが、Nervesを起動するとConnectしているNodeのリストが返ってきます。

  • OSXのターミナルから名前とCookieを指定してプロセスを起動
  • Nerves起動前はNode.list()しても空のリストとなる
  • Nerves起動後は接続先のリストが返ってくる
bash(OSX)
$ mix deps.get
$ mix firmware
$ ./upload.sh 192.168.5.55

$ iex --name [email protected] --cookie comecomeeverybody
iex([email protected])> Node.list()
[]

<<< ここでNerves起動 >>>

iex([email protected])> Node.list()
[:"[email protected]"]
iex(exineris001@192.168.46.6)> Node.list()
[:"[email protected]"]

まとめ

Nerves起動時にNode.connect()できるようになりました!
感想:ながかったー

足跡

@kikuyuta さんが はじめてNerves(0) ElixirによるIoTフレームワークNervesがとにかく動くようになるためのリンク集 に色々とまとめてくれてるのでこちらもどうぞ。