LoginSignup
13
3

More than 3 years have passed since last update.

作って学ぶPhoenix、IoTサーバー

Last updated at Posted at 2019-12-14

これはfukuoka.ex Elixir/Phoenix Advent Calendar 2019の15日の記事です。
昨日は@a_utsukiさんの「「プログラミングElixir」の次にやること」でした。

今回は「作って学ぶNerves、BBBでCO2計測」の

取得した値をPhoenixへPOSTしWEBブラウザで見られるようにできたら、
WEBからIoT端末までElixirで統一できてかっちょいいですね。

を実現できたので紹介します。:sunny:

結果

先に結果を示します。

Peek 2019-12-13 21-56.gif

見た目は地味で残念ですが、:sweat_smile:

  • Current Measurementの直下に表示される値が5秒毎に更新されています。
  • この値はNervesで計測したCO2の計測時刻(UTC)とその値(ppm)です。
  • そして描画更新周期はCO2の計測周期です。←ココポイント

以下では、このシステムの構成とその処理を紹介します。

構成と処理

構成

image.png

Phoenixサーバーに2つのクライアントがアクセスします。
一つは「作って学ぶNerves、BBBでCO2計測」で作ったNerves、もう一つはWebブラウザです。

Phoenix<-->Browser間の処理

  1. WebブラウザはPhoenixのデータ参照ページにアクセスします。
  2. Webブラウザは取得したjsファイルを実行し、WebSocketでPhoenixに接続をかけ、channelにjoinします。
    このchannelには「"new_measurement"が来たら画面の値(時間とCO2濃度)を書き変える」イベントを登録しています。

PhoenixのChannelについては@nikuさんの「Phoenix Framework - Channel 日本語翻訳」が把握しやすいです。

Phoenix<-->Nerves間の処理

  1. Nervesクライアントは計測したCO2データを5秒毎にPhoenixのJSON APIにPOSTします。
  2. PhoenixはPOSTされたデータを永続データとしてPostgresに格納します。
    また、同時にchannelに対し、"new_measurement"をbroadcastします。

このbroadcastを受け、Browserに登録したイベントがキックされ画面更新が起きます。
このため、この画面更新は即応性があります。

これはWebSocketを使ったサーバープッシュ、通知機能等で用いられる技術です。

作っている中でPhoenixにはサーバープッシュに2つの方法があることが分かりました。

寄り道:Phoenixのサーバープッシュ

  • chatのような場合は、Phoenix.Channel.broadcastを使う
  • 今回の場合は、Phoenix.Endpoint.broadcastを使う

chatのような場合

PhoenixのChannelsのドキュメントにあるchatの実装は、

  1. Browser側は投稿ボタンのクリックに合わせてchannel.push("new_msg", {body: chatInput.value})し、
  2. Phoenix側はhandle_in("new_msg", %{"body" => body}, socket)でBrowserのメッセージを受けて、
    Phoenix.Channel.broadcastを実行します。
def handle_in("new_msg", %{"body" => body}, socket) do
  broadcast!(socket, "new_msg", %{body: body}) # Phoenix.Channel.broadcast! を使っている
  {:noreply, socket}
end

handle_in関数はsocketに流れて来るeventとmessageを受けます。
このときhandle_in関数の引数にはsocketが渡ってくるため、broadcast先はそのsocketとして知ることができます。

今回の場合

今回の場合は「Phoenix<-->Nerves間の処理」で説明したとおり、データをDBに格納した直後にbroadcastします。
このとき、Phoenix.Channel.broadcastを使おうとすると、引数に渡すsocketに何を用いればよいか分かりません。
※ここでハマりました。

この場合に使えるのが、Phoenix.Endpoint.broadcastです。
@kanmoさんの「Phoenix ChannelのAPIについて」が参考になりました。)

Phoenix.Endpoint.broadcastはトピックを指定して、broadcastすることができます。

measurement_controller.ex
def create(conn, %{"data" => measurement}=params) do
  with {:ok, %Measurements.Data{} = datum} <- Measurements.create_data(measurement) do
    EasyIotServerWeb.Endpoint.broadcast!( # Phoenix.Endpoint.broadcast! を使っている
      "topic:subtopic",
      "new_measurement",
      %{
        time: datum.utc_datetime_usec,
        value: datum.value,
      }
    )

    conn
    |> put_status(:ok)
    |> render("show.json", datum: datum)
  end
end

このbroadcastによって、サーバープッシュでブラウザの画面更新を行うことができました。

さらなるアイデア

Phoenix<-->Browser間はchannelによって、計測に対して即応性のある画面更新ができるようになりました。

では、Phoenix<-->Nerves間でchannelを用いて、即応性のある動作をさせることはできないかと思いました。
イメージはブラウザに表示されたボタンの押下により、即応性の高い制御をすることです。

もうすでにチャレンジしているリポジトリ、「nerves_remote_led」がありました。

ということで組み込んで試してみました。

test.gif

イェイ:v:

突然の謝罪

nerves_remote_ledが使っているライブラリはphoenix_channel_clientで、今回はそれを使いました。
phoenix_channel_clientは現在はretiredで、PhoenixClientとしてメンテンスされているようです。

このLED制御の取り組みは、
CO2計測の画面更新のネタだけだと「内容薄いな」と思い、無理やりねじ込んだので、
ちゃんと確認せずに古いphoenix_channel_clientで作ってしまいました。詳細がないのもそのためです。
:bow::bow::bow::bow::bow:

使う場合はPhoenixClientを使うよう気をつけてください。

まとめ

  • ElixirのNervesとPhoenixを使い、下から上までElixirで書けるIoTシステムを作ってみました。
  • WebSocket(channel)を使うことで即応性のあるUIと制御が作れることを確認しました。

かけ足でPhoenixを学んでいるので、おかしい点はご指摘いただけると幸いです。

今回は以上です。では、また!

「いいね」よろしくお願いします。:wink:

明日は@zacky1972さんの「どうやら Erlang をコンパイルした時のCコンパイラによって Elixir の性能がかなり異なるようだ」です。

後記

Railsを知らないからなのか、Webフレームワークに慣れていないからなのか、
Phoenixの構成を理解するのに大分時間を費やしました。:sweat_smile:

英語が辛いのですが、構成の理解に「Programming Phoenix」はとても良かったです。
Phoenixはほぼこれで勉強しました。
構成の理解だけでなく、そこどうするの?という「かゆいところに手が届く」点も良かったです。

今回の取り組みでPhoenixが分かってきたのでいろいろ作ってみます。:muscle:

13
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
3