(この記事は「fukuoka.ex Elixir/Phoenix Advent Calendar 2018」の5日目です)
昨日の記事は @koga1020 さんの「Phoenix1.4 でのvue環境構築メモ(Part1: 単一ファイルコンポーネントを使えるようにするまで)」でした.
はじめに
こんにちは
fukuoka.exではIoT芸人をやっております.
福岡コミュニティに参加していながら実は京都在住なので,先日立ち上がったkyoto.exもばんばん盛り上げていきたいところです!!
さてこの記事では,もう半年も前の話しになってしまっていますが,「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」にて披露したIoT芸のネタについて,Advent Calendarらしく2018年の総決算!として今さらながら紹介しようかと思います.
# 連載記事が滞ってしまっていてすみません^^;
fukuoka.ex#11の発表スライドとGitHubリポジトリは下記のとおりです.
- SlideShare: ElixirでIoT 第2回「環境センシングとデータ表示をサクっと?やってみた」
- GitHub: https://github.com/takasehideki/fukuokaex11
ラズパイやIoTなお話しはこれまでの連載記事も合わせてご笑覧ください.
- Qiita記事: ElixirでIoT:連載目次
こんなの作りました
ラズパイとElixirを使ってリアルタイムな環境センシングをサクッとやってみました!
ライブデモこれです。手ぶれ酷くてすんません。
— TAKASE hideki (@TAKASEhideki) 2018年6月22日
温湿度センサと超音波センサの値をラズパイ3B&GrovePi+で取得して、LCDに表示させつつWebページにもリアルタイム表示!もちろんすべてElixir/Phoenix!! #fukuokaex pic.twitter.com/loGk3yZxzW
登壇直前までデモが現地でまともに動かなくって,よっしゃギリで動いた!とりまバックアップで撮っておくぞ!!ってな焦りが手ブレから伝われば幸いです^^;
静止画の概念図だとこんな感じです.
コレを作るまでに様々な要素技術を駆使して多くのTIPSを得られました.
ですが細かいトコロまで解説しているとキリがない,,,(そしてまとまりがつかない!)ので,この記事では各TIPSの紹介は概要レベルに留めます.
用意したモノ
まずは使ったモノを紹介します.
ハードウェア
- ボード:Raspberry Pi 3 Model B
- 使いやすさが自慢のIoT時代の風雲児です.とりあえず買ってみて動かしてみた方も多いかと思います.
- 低価格なのに64-bit CPUが4-coreも載っていたり無線LANが標準搭載だったりで,下手なラップトップより性能が高くなるなんてこともあります.
- Groveシールド:GrovePi+
- Groveとは,各種センサなどのIoTデバイスを画一パッケージ化したモジュール群です.
- 入出力ピンが4ピンのGroveコネクタで統一されており,簡単に付け替えできるのが特徴です.
- このシールドはラズパイに適合していて,様々なIoTシステムの開発をラピッドに試すことができます
- Grove:今回は下記のモジュールを使いました.
- Temperature&Humidity Sensor (DHT):DHT11というデジタル温度湿度センサを搭載したモジュールです.
- Ultrasonic Ranger:超音波によって対象物との距離を測定できるモジュールです.
- LCD RGB Backlight:16x2の文字をLCD表示できます.RGBに光るバックライト付き.
今回はラズパイ上でのホスト開発です.
キーボードやディスプレイはよしなにご用意ください.SSHログインできるなら不要です.
ソフトウェア
- カーネル:Raspbian Stretch with Desktop 4.9
- Elixirバージョン:Elixir 1.6.5 (compiled with OTP 20)
- Erlang/OTP 20 [erts-9.3] [source] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
- GrovePi+ライブラリ:こちらを参考にしてください.インストール不要だったかも.
- グラフ表示:Chart.js
- Phoenixページでのhtmlグラフ表示に使用しました.簡単にカスタマイズできて便利です.バージョンは2.1.4を使いました.
- Phoenixのhtmlページを整形している
lib/home_weather_phx_web/templetes/page/index.html.eex
にて使用しています.
なんかいろいろバージョン古くね!?ってのは,半年前ですしね,,,
それぞれ新しいものでも動くとは思います(誰か試して,,,
Elixir用のGroveライブラリは,下記を使用しました.
GrovePi+とGrovePi Zeroに対応しています.Groveモジュールの対応状況はHexDocsをご参照ください.また,このライブラリでは,Erlang VMのNIF経由でラズパイのGPIO,I2C,SPIの各種デバドラにアクセスできるelixir_aleが使われています.
動かしカタ
中身の説明に入る前に,デモの使い方を紹介します.
まずはハードウェアの準備としてラズパイのセットアップです.
GrovePi+をラズパイ3Bに刺して,下記の通りGroveモジュールを接続します.
Groveモジュール | GrovePi+接続先 |
---|---|
温湿度センサ | D7 |
超音波センサ | D4 |
LCD | I2C-1 |
次はソフトウェア側の操作です.
まぁひとまずcloneします.
$ git clone https://github.com/takasehideki/fukuokaex11
今回のアプリはhome_weather_phx
です.
$ cd home_weather_phx/
他にも開発したものはREADME.mdをご参照ください.いろいろ悪戦苦闘の跡が感じ取れるかと.
本日の日時をlib/home_weather_phx_web/templates/page/index.html.eex
の153行目のtoday
に定義してください.(自動化したかったんですけどねぇ^^;
window.onload = function () {
var csvData = csvToArray("dhtdata.csv");
var today = "2018-12-04 ";
...
};
あとはこのディレクトリにて,Phoenixサーバとアプリを起動するだけです.
$ MIX_ENV=dev mix phx.server
ブラウザからhttp://<IP>:4000/
にアクセスすれば,温湿度・超音波距離の取得値がグラフ表示されます.しかもリアルタイムに表示更新します!
ラズパイ内からlocalhostでも,同ネットワーク内のPCやスマホからIP直打ちでも閲覧可能です.
仕組みのナカミ
少しだけナカミを紹介します.
詳細は(また気が向いたら)連載の別記事にしたいと思います(たぶん).
mix.exs
まずはmix.exs
の中身です.
defmodule HomeWeatherPhx.Mixfile do
use Mix.Project
def project do
[
app: :home_weather_phx,
version: "0.0.1",
elixir: "~> 1.4",
elixirc_paths: elixirc_paths(Mix.env),
compilers: [:phoenix, :gettext] ++ Mix.compilers,
build_embedded: Mix.env() == :prod,
start_permanent: Mix.env == :prod,
deps: deps()
]
end
# Configuration for the OTP application.
#
# Type `mix help compile.app` for more information.
def application do
[
mod: {HomeWeatherPhx.Application, []},
extra_applications: [:logger, :runtime_tools, :timex]
]
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
# Specifies your project dependencies.
#
# Type `mix help deps` for examples and options.
defp deps do
[
{:phoenix, "~> 1.3.2"},
{:phoenix_pubsub, "~> 1.0"},
{:phoenix_html, "~> 2.10"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:gettext, "~> 0.11"},
{:cowboy, "~> 1.0"},
{:timex, "~> 3.1"},
{:grovepi, github: "adkron/grovepi", branch: "master"}
]
end
end
timex
は現在時刻の取得に,phoenix_live_reload
はブラウザのリアルタイム表示に使用しています.
今回はpriv/static/dhtdata.csv
に取得データを書き込んでいくこととして,このCSVファイルが更新されたらPhoenixも更新されるようにしました.dev.exs
のlive_reload対象にcsvを追加しています.
# Watch static and templates for browser reloading.
config :home_weather_phx, HomeWeatherPhxWeb.Endpoint,
live_reload: [
patterns: [
~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg|csv)$},
~r{priv/gettext/.*(po)$},
~r{lib/home_weather_phx_web/views/.*(ex)$},
~r{lib/home_weather_phx_web/templates/.*(eex)$}
]
]
GrovePiライブラリがなかなか手癖がありまして,ReleaseされているHexパッケージでは不具合がありました.このため,GitHubからmaster
ブランチを直接指定するようにしました.
{:grovepi, github: "adkron/grovepi", branch: "master"}
実はdeps
はこんな書き方もできるのです.
home_weather_phx.ex
アプリ本体の記述です.
defmodule HomeWeatherPhx do
@moduledoc false
use GenServer
use Timex
require Logger
defstruct [:dht]
alias GrovePi.{RGBLCD, DHT}
@us_pin 4 # Use port 4 for Ultrasonic
def start_link(pin) do
GenServer.start_link(__MODULE__, pin)
end
def init(dht_pin) do
state = %HomeWeatherPhx{dht: dht_pin}
{:ok, _pid} = GrovePi.Ultrasonic.start_link(@us_pin)
RGBLCD.initialize()
RGBLCD.set_text("Ready!")
# Create CSV file
File.write "priv/static/dhtdata.csv", ""
DHT.subscribe(dht_pin, :changed)
{:ok, state}
end
def handle_info({_pin, :changed, %{temp: temp, humidity: humidity}}, state) do
# Measure distance
distance = GrovePi.Ultrasonic.read_distance(@us_pin)
distance = if distance >= 200, do: 0, else: distance
# Get date
date = Timex.now("Asia/Tokyo")
|> Timex.format!( "%Y-%m-%d %H:%M:%S", :strftime )
temp = format_temp(temp)
humidity = format_humidity(humidity)
distance = format_distance(distance)
# Write data to CSV
File.write "priv/static/dhtdata.csv", "#{date},#{temp},#{humidity},#{distance}\n", [:append]
flash_rgb()
RGBLCD.set_text(temp)
RGBLCD.set_cursor(1, 0)
RGBLCD.write_text(humidity)
Logger.info temp <> " " <> humidity <> "" <> distance
{:noreply, state}
end
def handle_info(_message, state) do
{:noreply, state}
end
defp flash_rgb() do
RGBLCD.set_rgb(255, 0, 0)
Process.sleep(1000)
RGBLCD.set_color_white()
end
defp format_temp(temp) do
"Temp: #{Float.to_string(temp)} C"
end
defp format_humidity(humidity) do
"Humidity: #{Float.to_string(humidity)}%"
end
defp format_distance(distance) do
"distance: #{distance}cm"
end
end
温湿度センサGrovePi.DHT
のライブラリはGenServer
とSupervisor
で動作する設計になっています.
handle_info()
の第2引数が:changed
しか選べないのがちょっと厄介.要するに温湿度センサの値が変更したときしか動作しません.
同じくhandle_info()
内では,超音波センサの値を取得したのちに,出力用にデータをフォーマットしてCSVファイルpriv/static/dhtdata.csv
に追加書き込みします.同時にLCDにもデータを表示させています.
application.ex
の記述はこんな感じです.
defmodule HomeWeatherPhx.Application do
use Application
# RGB LCD Screen should use the IC2-1 port
@dht_pin 7 # Use port 7 for the DHT
@dht_poll_interval 10_000 # poll every 10 second
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Supervisor.Spec, warn: false
# Define workers and child supervisors to be supervised
children = [
# Start the endpoint when the application starts
supervisor(HomeWeatherPhxWeb.Endpoint, []),
# Start your own worker by calling: HomeWeatherPhx.Worker.start_link(arg1, arg2, arg3)
# worker(HomeWeatherPhx.Worker, [arg1, arg2, arg3]),
# Start the GrovePi sensor we want
worker(GrovePi.DHT, [@dht_pin, [poll_interval: @dht_poll_interval]]),
# Start the main app
worker(HomeWeatherPhx, [@dht_pin]),
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: HomeWeatherPhx.Supervisor]
Supervisor.start_link(children, opts)
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
def config_change(changed, _new, removed) do
HomeWeatherPhxWeb.Endpoint.config_change(changed, removed)
:ok
end
end
GrovePi.DHT
のworkerを定義しています.
おわりに
2018年はElixirに出会って,そして@piacere_exさんを始めとしたfukuoka.exな多くの仲間に出会えた素晴らしい年でした.このデモアプリも皆さんのお力添えが無ければ完成することはできませんでした.
そしてそして,このような出会いのきっかけを作っていただいた@zacky1972先生には特に感謝感激雨霰でございます!!
(この辺りだけでけっこうなボリュームになりそなので,また技術ポエムにまとめますかね^^;
とはいえ2018年に挙げたネタは,所詮は他人のフンドシを借りていたに過ぎません.研究屋さんとしては新しい技術や概念をドンドン打ち出していくつもりです.脳内には構想や計画がもうたっぷりとあるのであります><;
2019年もElixirでIoT芸に磨きを掛けて,研究を加速させていきまっす!!
明日の「fukuoka.ex Elixir/Phoenix Advent Calendar 2018」の担当は @kobatako さんの
「ソフトウェアルータでカオスエンジニアリング入門」です!