Routing Tree Web Toolkit "Roda" を動かしてみる

 先日開催された Railsdm はチケットが即完売してしまって残念ながら行けませんでした。

railsdm.github.io

 何か試してブログに書けそうなネタはないかなーとセッションの資料を見ていたら Roda という Web Framework が紹介されていました。

docs.google.com

 セッションでは Rails と組み合わせて使ってみるという内容でしたが、シンプルに Roda を動かしてみるサンプルはあまり多くなかったようなので、試してみました。

Roda とは

 詳しくは本家サイトや上記 Railsdm のセッション資料等をご覧いただければと思いますが、 Rails のようなフルスタックのフレームワークとは違い、ルーティング機能を提供するシンプルなフレームワークで、 Sinatra に近いイメージです。様々なプラグインが用意されていますので、必要に応じてプラグインを追加して機能追加していく形になります。ルーティングをツリー構造で定義できる点が Sinatra と違う点になります。

roda.jeremyevans.net

Roda のインストール

 Roda の GitHub リポジトリはこちらで、 gem のインストール方法やシンプルなアプリケーションのサンプルが紹介されています。

github.com

 Roda のインストールは gem をインストールするだけです。今回は Bundler を使ってインストールしてみますので、下記のように Gemfile を用意します。

# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

gem "roda"

 そして bundle install します。

$ bundle install
Fetching gem metadata from https://rubygems.org/...............
Resolving dependencies...
Using bundler 1.17.2
Using rack 2.0.6
Fetching roda 3.18.0
Installing roda 3.18.0
Bundle complete! 1 Gemfile dependency, 3 gems now installed.
Use `bundle info [gemname]` to see where a bundled gem is installed.

サンプルアプリケーション

 とりあえず GitHub で紹介されているサンプルアプリケーションを実装してみます。下記の内容で config.ru ファイルを用意します。

require "roda"

class App < Roda
  route do |r|
    # GET / request
    r.root do
      r.redirect "/hello"
    end

    # /hello branch
    r.on "hello" do
      # Set variable for all routes in /hello branch
      @greeting = 'Hello'

      # GET /hello/world request
      r.get "world" do
        "#{@greeting} world!"
      end

      # /hello request
      r.is do
        # GET /hello request
        r.get do
          "#{@greeting}!"
        end

        # POST /hello request
        r.post do
          puts "Someone said #{@greeting}!"
          r.redirect
        end
      end
    end
  end
end

run App.freeze.app

動作確認

 Roda は Rack ベースなので、動かすには rackup するだけです。今回は環境としては Cloud9 で EC2 インスタンス上で動作させています。

$ rackup
Puma starting in single mode...
* Version 3.12.0 (ruby 2.6.0-p0), codename: Llamas in Pajamas
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://localhost:9292
Use Ctrl-C to stop

 9292ポートでアプリケーションが実行されるので、 curl でリクエストを投げてみると、下記のようにレスポンスが返ります。

$ curl localhost:9292/hello
Hello!

 ログには下記のように出力されます。

127.0.0.1 - - [30/Mar/2019:12:55:59 +0000] "GET /hello HTTP/1.1" 200 6 0.0005

サンプルアプリの内容

 Roda ではリクエストのルーティングはルーティングツリーと呼ばれる構成で管理されています。 config.ru で定義した内容がルーティングツリーになりますので、その設定内容を簡単に説明します。

 まず r.root はルートパス / への GET リクエストのみマッチします。

# GET / request
r.root do
  r.redirect '/hello'
end

 curl では下記のようにリクエストを投げます。

$ curl localhost:9292/
127.0.0.1 - - [30/Mar/2019:13:09:27 +0000] "GET / HTTP/1.1" 302 - 0.0011

 /hello というリクエストについては下記のブロックで定義しています。

# /hello branch
r.on 'hello' do
 ・・・
end

 その中でさらに下層のパスへの設定も行います。 /hello/world への GET リクエストについては下記のように定義します。

r.get 'world' do
  "#{@greeting} world!"
end

 リクエストを投げると下記のように動作します。

$ curl localhost:9292/hello/world
Hello world!
127.0.0.1 - - [30/Mar/2019:13:22:26 +0000] "GET /hello/world HTTP/1.1" 200 12 0.0013

 /hello の下層のパスがないリクエストの場合は下記の設定がマッチします。

# /hello request
r.is do
 ・・・
end

 /hello に GET リクエストが投げられた場合の設定は下記の内容になります。

r.get do
  "#{@greeting}!"
end
$ curl localhost:9292/hello
Hello!
127.0.0.1 - - [30/Mar/2019:13:28:37 +0000] "GET /hello HTTP/1.1" 200 6 0.0014

 また、 POST リクエストの場合は下記のブロックの設定になります。

# POST /hello request
r.post do
  puts "Someone said #{@greeting}!"
  r.redirect
end
$ curl -X POST localhost:9292/hello
Someone said Hello!
127.0.0.1 - - [30/Mar/2019:13:30:27 +0000] "POST /hello HTTP/1.1" 302 - 0.0013

まとめ

 今回はとりあえずサンプルをそのまま動かしただけでしたが、公式サイトには他にも様々な Matcher の書き方が紹介されています。 Rails を使っていると基本的な技術要素というよりは "Railsの使い方" に視点が行きがちだと思いますが、こういったコア機能のみ提供しているシンプルなフレームワークをベースにアプリケーションを作っていくとなると、 Rack や各個別の技術要素を改めて意識するきっかけになりそうで良いですね。

M5Stack用非接触温度センサユニットを試す

 最近は M5Stack のセンサーユニットをいろいろ試している感じになってますが、今回は非接触温度センサを試してみました。何かアイディアがあるわけではないのですが、いろいろと応用範囲が広そうかなと思っています。

www.switch-science.com

f:id:akanuma-hiroaki:20190323150151j:plain

M5Stack への接続

 ユニットの裏面を見ると、 PORT.A.I2C ということで、 M5Stack の Grove コネクタの A ポートに接続すれば良いということがわかります。

f:id:akanuma-hiroaki:20190323145931j:plain

 なので付属の Grove ケーブルを使って M5Stack 本体左側の Grove コネクタに接続します。接続はこれだけで OK です。

f:id:akanuma-hiroaki:20190323150021j:plain

UIFlow でのサンプル実装

 UIFlow でサンプルを実装してみます。まずはユニットを追加するために、エミュレータ下の ボタンをクリックします。

f:id:akanuma-hiroaki:20190323145344p:plain

 ユニットのリストから、 NCIR を選択し、ポート選択のプルダウンからは A を選択して OK をクリックします。

f:id:akanuma-hiroaki:20190323145453p:plain

 すると UIFlow に NCIR ユニットが追加されます。

f:id:akanuma-hiroaki:20190323145540p:plain

 Units メニューには NCIR メニューが追加され、センサーからの値を読み取るためのブロックが使用可能になります。

f:id:akanuma-hiroaki:20190323145630p:plain

 このブロックを使ってサンプルを実装してみます。やってることはシンプルで、1秒ごとにセンサーの値を画面に表示するだけです。

f:id:akanuma-hiroaki:20190323145718p:plain

動作確認

 これを実機で動作させると、下記写真のようにセンサーで取得した温度情報が画面に表示されます。

f:id:akanuma-hiroaki:20190323145818j:plain

 試しにコーヒーの温度を測ってみると下記のような感じになりました。

f:id:akanuma-hiroaki:20190323145846j:plain:w400

動体検知の動作確認

 先ほどのサンプルの待ち時間を 1秒間 から 0.1秒間 に変更すると、センサー前を人が横切った時などに検知できるようになります。動作確認の様子は下記の通りで、人の身体がセンサー前を横切った時に検知温度が高くなります。

まとめ

 温度情報を取得するところまでは、センサーの接続から含めて超お手軽にできてしまいました。温度情報をそのまま使うだけでなく、動体検知もできるので、使用用途を考えてみたいと思います。

M5Stack の Color Sensor を UIFlow で試す

 M5Stack の拡張ユニットには色々なものがありますが、なんとなく面白そうだったので物体の色情報を取得できる Color Sensor を試してみました。ただ、先に結論を言っておくと、センサーの値を読み出すまではすごく簡単にできたのですが、そのまま値を使っても対象の色を正しく再現するところまではできませんでした。ひとまずやったことを一通り描いてみたいと思います。

www.switch-science.com

 公式のドキュメントはこちらにあります。

docs.m5stack.com

ユニット接続

 Color Sensor も他の拡張ユニットと同様に Grove コネクタで接続できますので、 M5Stack 本体左側の Grove コネクタに接続します。

f:id:akanuma-hiroaki:20190316210620j:plain

 センサー裏面にはピンアウトが記載されています。また、 PORT. A. I2C という記載もあります。私が持っている M5Stack Gray は Grove コネクタは一つだけで、 A PORT のみなので、 PORT A(I2C)用の拡張ユニットが接続できます。

f:id:akanuma-hiroaki:20190316210700j:plain

センサーの値を読む

 Color Sensor は UIFlow も対応しているので、今回は UIFlow で手軽に試してみます。ユニットを追加するために、エミュレータ下の ボタンをクリックします。

f:id:akanuma-hiroaki:20190316204437p:plain

 ユニットのリストの中から COLOR を選択し、プルダウンからは A PORT して OK をクリックします。

f:id:akanuma-hiroaki:20190316204634p:plain

 すると Color Sensor ユニットが UI Flow に追加されます。

f:id:akanuma-hiroaki:20190316204822p:plain

 Units メニューには Color メニューが追加され、 Color Sensor 用のブロックが使用できるようになります。現状確認できたのは RGB それぞれの値を読み取るブロックです。

f:id:akanuma-hiroaki:20190316204930p:plain

 上記のブロックを使用して簡単なサンプルを作成してみます。1秒ごとに Color Sensor の RGB 情報を読み出し、画面に表示します。

f:id:akanuma-hiroaki:20190316205132p:plain

動作確認

 実際に動作させてみると、下記のように Color Sensor から読み取った RGB の値が画面に表示されます。

f:id:akanuma-hiroaki:20190316205429j:plain

 動作中は色判別のための白色 LED が点灯します。

f:id:akanuma-hiroaki:20190316210726j:plain

 そして実際に読んだ値が正しいのかの確認にためにも、読み取った RGB の値で図形の色を変えてみるサンプルを下記のように作ってみたのですが、結論から言うと実際の色は正しく反映されませんでした。

f:id:akanuma-hiroaki:20190316220940p:plain

 例えば下記写真の実行例だと、対象のものは濃い黄色なのですが、読み取った RGB をそのまま使用すると茶色の表示になってしまいました。下記数字をそのまま合成すると茶色になるというのは正しいようなので、読み取った値が間違っているのか、そのまま使うのではなく何か加工が必要なのかもしれません。

f:id:akanuma-hiroaki:20190316221017j:plain

 ただ、下記オフィシャル動画を見るとそのまま使えているように見えるので、違う原因があるのかもしれません。

docs.m5stack.com

まとめ

 今回はとりあえずセンサーの値を読み出すまではできましたが、正しい色を再現するところまではできなかったので、今後何かわかれば正しく再現できるように改善してみたいと思います。正しく再現できれば何か面白ことができそうな気はしているものの、特にまだアイディアは思いついていないので、何か役に立つもののアイディアも考えてみたいと思います。

AWS Cloud9 で既存の EC2 インスタンスに接続する

 以前の記事でEC2インスタンスも同時に自動的に作成するやり方で Cloud9 の環境を作成してみましたが、仕事等で使うケースを考えると、既存のインスタンスに接続するケースもありそうだったので、試しに EC2 インスタンスを事前に別途作成して、そこに接続するやり方で Cloud9 の環境を作成してみました。

 手順はこちらで紹介されているので、この手順に沿ってやってみました。 EC2 インスタンスを作成するリージョンは Cloud9 と同様にシンガポールリージョンにしています。

docs.aws.amazon.com

VPC作成

 まずは VPC を作成します。 VPC コンソールから VPC ウィザードの起動 をクリックします。

f:id:akanuma-hiroaki:20190309215356p:plain

 VPC 内からインターネットに接続できる必要があるので、種別としては 1個のパブリックサブネットを持つ VPC を選択して 選択 ボタンをクリックします。

f:id:akanuma-hiroaki:20190309215501p:plain

 VPC の設定は基本的にデフォルトで OK なので、 VPC 名だけ任意のものを設定し、 VPC の作成 をクリックします。

f:id:akanuma-hiroaki:20190309215614p:plain

 VPC が作成されたら、 VPC コンソールからその VPC を選択し、 Description タブの Network ACL の ID のリンクをクリックします。

f:id:akanuma-hiroaki:20190309215853p:plain

 インバウンドルールを設定するために、 Inbound Rules タブの Edit inbound rules をクリックします。

f:id:akanuma-hiroaki:20190309220004p:plain

 インバウンドルールでは ssh の接続元を Cloud9 に限定するのですが、設定すべき CIDR は ip-ranges.json というファイルに記載されているので、下記のように確認することができます。今回はシンガポールリージョンなので、 region が ap-southeast-1 になっている2項目を使用します。

$ wget https://ip-ranges.amazonaws.com/ip-ranges.json
$ jq '.prefixes[] | select(.service=="CLOUD9")' < ip-ranges.json
{                              
  "ip_prefix": "13.250.186.128/27",
  "region": "ap-southeast-1",
  "service": "CLOUD9"
}                                 
{                               
  "ip_prefix": "13.250.186.160/27",
  "region": "ap-southeast-1",
  "service": "CLOUD9"
}            
〜〜〜以下略〜〜〜

 上記 CIDR をアクセス元として設定した ssh のルール2つと、Port Range で接続を許可する設定1つの合計3つのルールを設定します。最後に Save ボタンをクリックして設定を保存します。

f:id:akanuma-hiroaki:20190309220132p:plain

EC2 インスタンス作成

 続いて実際に接続する EC2 インスタンスを作成します。 EC2 コンソールから インスタンスの作成 をクリックします。

f:id:akanuma-hiroaki:20190309220344p:plain

 AMI は Amazon Linux 2 を選択します。

f:id:akanuma-hiroaki:20190309220640p:plain

 インスタンスタイプは、今回はお試しということで t2.micro を選択します。

f:id:akanuma-hiroaki:20190309220759p:plain

 インスタンスの詳細設定では、ネットワークとして先程作成した VPC を選択します。サブネットもその VPC に属する一つが選択されます。その他はデフォルト設定でOKなので、 次の手順: ストレージの追加 をクリックします。

f:id:akanuma-hiroaki:20190309221009p:plain

 今回はストレージはデフォルトのままで良いので、そのまま 次の手順: タグの追加 をクリックします。

f:id:akanuma-hiroaki:20190309221104p:plain

 今回は特にタグも追加しないので、そのまま 次の手順: セキュリティグループの設定 をクリックします。

f:id:akanuma-hiroaki:20190309221156p:plain

 セキュリティグループの割り当てでは 既存のセキュリティグループを選択する を選択し、VPC 内に作成されているセキュリティグループを選択して 確認と作成 をクリックします。

f:id:akanuma-hiroaki:20190309221342p:plain

 設定内容の確認画面が表示されますので、間違いがなければ 起動 ボタンをクリックします。

f:id:akanuma-hiroaki:20190309221513p:plain

 インスタンスに接続する際のキーペアを新たに作るか、既存のものを使用するかの選択フォームが表示されます。今回は 新しいキーペアの作成 を選択し、キーペア名を設定して キーペアのダウンロード をクリックし、プライベートキーファイルをダウンロードします。ダウンロードすると インスタンスの作成 ボタンがアクティブになりますので、クリックしてインスタンスを作成します。

f:id:akanuma-hiroaki:20190309221730p:plain

EC2 の環境設定

 冒頭で紹介したページでも説明されている通り、 Cloud9 で接続するにはいくつかの要件を満たす必要がありますので、インスタンスに接続して設定を行います。 VPC の作成中にインバウンドルールの設定で ssh の接続元を Cloud9 に限定しましたが、設定時にローカルからも接続できるように一時的に設定を追加します。設定が完了したら追加した設定は削除しておきます。

f:id:akanuma-hiroaki:20190309222144p:plain

 また、詳細な説明は割愛しますが、パブリックIPアドレスが必要になりますので Elastic IP も割り当てておきます。

 接続できるようになったら ssh でインスタンスに接続します。

$ ssh -i "cloud9.pem" ec2-user@ec2-52-XXX-XXX-XXX.ap-southeast-1.compute.amazonaws.com

 Cloud9 で接続するには Python 2.7 が必要になります。これはデフォルトでインストールされています。

$ python -V
Python 2.7.14

 次に node.js も必要になります。これはデフォルトではインストールされていませんので、下記手順でインストールします。

$ sudo yum -y update 
$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash
$ . ~/.bashrc
$ nvm install node
$ node --version
v11.11.0

 また、 Cloud9 で接続するディレクトリの権限を設定します。ホームディレクトリを対象とする場合には下記のように権限を設定します。

$ ls -ld ~
drwx------ 5 ec2-user ec2-user 122 Mar  9 11:47 /home/ec2-user
$ 
$ sudo chmod u=rwx,g=rx,o=rx ~
$ 
$ ls -ld ~
drwxr-xr-x 5 ec2-user ec2-user 122 Mar  9 11:47 /home/ec2-user

 最後に AWS Cloud9 インストーラを実行します。 gcc が必要なので yum で Development Tools をインストールしてから、 curl でインストーラをダウンロードして実行します。

$ sudo yum -y groupinstall "Development Tools"
$ curl -L https://raw.githubusercontent.com/c9/install/master/install.sh | bash

Cloud9 の環境作成

 それでは Cloud9 の環境を作成します。 Cloud9 のコンソールから Create environment をクリックします。

f:id:akanuma-hiroaki:20190309222332p:plain

 環境名と説明を任意に入力し、 Next step をクリックします。

f:id:akanuma-hiroaki:20190309222454p:plain

 Environment type で Connect and run in remote server (SSH) を選択します。User には ec2-user、Host には先程までに作成した EC2 インスタンスのパブリックIPを設定します。 Copy key to clipboard をクリックすると Cloud9 から EC2 インスタンスに接続するためのパブリックキーがコピーされますので、インスタンス上で authorized_keys に設定します。設定できたら Next step をクリックします。

f:id:akanuma-hiroaki:20190309222729p:plain

 ちなみに authorized_keys への登録は vi などで下記のようにファイルを編集してペーストします。

$ vi ~/.ssh/authorized_keys

 設定内容の確認画面が表示されますので、問題なければ Create environment をクリックします。

f:id:akanuma-hiroaki:20190309222850p:plain

 Cloud9 の環境が作成され、追加でインストールが必要なもののリストが表示されますので、全て選択された状態のまま Next をクリックします。

f:id:akanuma-hiroaki:20190309223009p:plain

 コンポーネントのインストールが始まりますので完了するまで待ちます。ちなみに私は上記の画面から進まなくなってしまったので一度キャンセルしましたが、再度 Cloud9 環境に接続した際にインストールが行われ、無事に完了しました。

f:id:akanuma-hiroaki:20190309223100p:plain

 インストールが完了すると Cloud9 の IDE が表示され、使用できるようになります。

f:id:akanuma-hiroaki:20190309223144p:plain

まとめ

 Cloud9 で接続するためにはサーバ側がネットワーク設定やソフトウェアのインストールなどの要件を満たす必要があるのでちょっと煩雑ですが、一度設定を作ってしまって AMI として保存しておけば、誰かが同じ環境を使う場合にも AMI からインスタンスを作成すれば Cloud9 側の環境だけ新たに作れば良いので、導入時の手間が削減できそうですね。

f:id:akanuma-hiroaki:20190309233114j:plain:w400

M5Stack NeoPixel互換 LEDテープでLチカ

 先日の記事で使った距離センサを買った時に、面白そうだったのでついでに買ってみた LED テープを試してみたいと思います。

www.switch-science.com

 ドキュメントはこちらに用意されています。

docs.m5stack.com

LEDテープ接続

 まずは LED テープを M5Stack に接続します。 LED テープの両端は Grove コネクタになっているので、 Grove ケーブルで M5Stack に接続します。 LED テープは複数のテープを Grove ケーブルで連結していくことができるようになっていますが、 Input と Output の向きが決まっていますので、裏面を見て方向を確認し、 Input 側を M5Stack と接続します。今回は1本しか接続していませんが、複数接続する場合は1本目の Output が2本目の Input に接続されるようにします。

f:id:akanuma-hiroaki:20190302220746j:plain

 今回使っている LED テープは50cmのものなので、接続すると下記のような感じになります。ちなみに Grove ケーブルは付属していませんので、別途用意する必要があります。

f:id:akanuma-hiroaki:20190302220818j:plain

ユニット追加

 それでは LED を操作してみたいと思いますが、距離センサの時と同様に UI Flow で動かしてみたいと思いますので、まずは LED テープのユニットを追加します。エミュレータの左下の + ボタンをクリックします。

f:id:akanuma-hiroaki:20190302214851p:plain

 UI Flow が対応しているユニットのリストが表示されますので、 NEOPIXEL を選択します。 Port は A で、count には LED の数を設定します。50cm のテープには 72 個の LED があったので、72 を設定しています。設定したら OK をクリックします。

f:id:akanuma-hiroaki:20190302215005p:plain

 設定されるとエミュレータの左下に、追加されたユニットということで neopixel0 が表示されます。

f:id:akanuma-hiroaki:20190302215110p:plain

LED 操作

 ユニットが追加されると Units メニューに Neopixel が表示されるようになり、 LED を操作するための様々なブロックが使用できるようになります。

f:id:akanuma-hiroaki:20190302215212p:plain

 まずは LED 全体の色を1秒おきに切り替えるサンプルです。下記のようにブロックを配置します。

f:id:akanuma-hiroaki:20190302215302p:plain

 実際に動かしてみた時の様子はこちらです。この例では特に明るさは設定していませんが、デフォルトで結構明るく点灯するようになっています。

 もう一つのサンプルとして LED の明るさを徐々に変化させてみます。0 から 100 の間で明るさを増減させています。

f:id:akanuma-hiroaki:20190302215347p:plain

 実際の様子はこちらです。

まとめ

 今回使用した LED テープは 50cm のものですが、他に 10cm, 20cm, 100cm, 200cm のバリエーションがあり、さらに連結もしていけます。テープという名の通り裏側の紙を剥がせばどこかに貼り付けることも簡単にできますので、デジタルサイネージ的なものやインジケーター的なものも結構簡単に作れてしまいそうです。

 距離センサ等の他のユニットと組み合わせて使えば簡単にプロトタイピングできそうですが、 M5Stack Core や Gray では Grove コネクタが一つしかついていないので、 Grove コネクタを複数備えた他の M5Stack が欲しいところです。

SORACOM Lagoon の Free プランで Harvest のデータをグラフ表示

 前回の記事では if-up2019 で発表された Unified Endpoint のことを書きましたが、他にもいくつか発表があり、そのうちの一つとして SORACOM Lagoon に Freeプランと Proプランが追加されたという発表がありました。

blog.soracom.jp

 今までは月額で980円ということで、興味はあったものの試していなかったのですが、 Freeプランでは一人で使ってみる分には無料で使えるので、改めて使ってみました。

SORACOM Lagoon の利用開始設定

 Lagoon を使うには利用設定が必要になります。ユーザコンソールの共通メニューから ダッシュボード作成・共有 をクリックします。

f:id:akanuma-hiroaki:20190223123230p:plain

 Lagoon の利用開始設定の画面が表示されますので、 SORACOM Lagoon の利用を開始する ボタンをクリックします。

f:id:akanuma-hiroaki:20190223123507p:plain

 料金プランの選択ダイアログが表示されますので、 Freeプラン を選択して 続行する をクリックします。

f:id:akanuma-hiroaki:20190223123648p:plain

 Lagoon を利用するためのパスワードを設定します。アカウントには現在ユーザコンソールにログインしているメールアドレスが使われ、そのメールアドレスとここで設定するパスワードで Lagoon を利用できるようになります。パスワードを入力したら 利用開始 をクリックします。

f:id:akanuma-hiroaki:20190223123822p:plain

 すると Lagoon のユーザが追加されますので、 SORACOM Lagoon console にアクセス をクリックします。

f:id:akanuma-hiroaki:20190223124058p:plain

 Lagoon へのログインフォームが表示されますので、先ほど設定した時のメールアドレスとパスワードを入力して Lagoon にログインします。

f:id:akanuma-hiroaki:20190223124208p:plain

Lagoon で Harvest のデータを表示する

 Lagoon では SORACOM Harvest に収集したデータを簡単に表示することができますので試してみます。 Lagoon にログインすると下記のように Home Dashboard が表示されますが、最初は何もありません。ダッシュボードを追加するために、まずは左上の Home をクリックします。

f:id:akanuma-hiroaki:20190223124611p:plain

 Home 画面で 新しいダッシュボード をクリックします。

f:id:akanuma-hiroaki:20190223124807p:plain

 ダッシュボードには様々なパネルを追加することができます。今回はグラフ表示のために Graph を選択します。

f:id:akanuma-hiroaki:20190223124907p:plain

 パネルが追加されたら Panel Title をクリックして表示されるメニューから 編集 をクリックします。

f:id:akanuma-hiroaki:20190223161915p:plain

 Lagoon のデータソースには Harvest 以外にも Grafana が選択できます。 default だと Harvest のデータが使用されますので、今回は default のままにします。

f:id:akanuma-hiroaki:20190223162046p:plain

 今回は Harvest に前回の記事で Unified Endpoint を試した時のサンプルデータが入っていますので、そのうちの一つの項目である humidity が対象として表示されています。今回はもう一つの項目である temperature も表示しますので、 クエリを追加 をクリックして対象データを追加します。

f:id:akanuma-hiroaki:20190223204319p:plain

 追加したデータの方では temperature を選択して、下記のように2つのデータを対象とします。

f:id:akanuma-hiroaki:20190223204538p:plain

 対象データを設定すると、デフォルトでは直近6時間分のデータが表示されるのですが、今回のデータを登録したのは一週間前なので、対象期間を変更してみます。 Last 6 hours と表示されているところをクリックすると、対象期間が選択できますので、今回は 先週の今日 をクリックします。

f:id:akanuma-hiroaki:20190223204714p:plain

 すると一週間前の1日分のデータが表示されます。データは1日のうちの一部だけなので、グラフでデータが表示されている部分をドラッグして選択します。

f:id:akanuma-hiroaki:20190223204837p:plain

 すると選択した部分が拡大して表示されます。対象期間を簡単に選択できるので便利ですね。

f:id:akanuma-hiroaki:20190223205021p:plain

 グラフを見やすくする為に少し設定を変更してみます。 全般 タブを選択してタイトルと説明文を設定します。

f:id:akanuma-hiroaki:20190223164422p:plain

 次に タブで各軸のラベルを設定します。

f:id:akanuma-hiroaki:20190223171913p:plain

 ここまででひとまずの設定ができたので、設定画面からダッシュボードの名前を設定して保存しておきます。

f:id:akanuma-hiroaki:20190223172845p:plain

ダッシュボードの共有(Makerプラン以上)

 ちなみに Lagoon では他の Lagoon ユーザにダッシュボードを共有することができます。画面上部の 共有 ボタンをクリックします。

f:id:akanuma-hiroaki:20190223173426p:plain

 共有用のリンクが表示されますので、このリンクを他のユーザに共有することでダッシュボードにアクセスしてもらうことができます。

f:id:akanuma-hiroaki:20190223173827p:plain

 ただし、その為には Lagoon のユーザを追加する必要がありますが、 Freeプランでは登録できるユーザ数は一人(自分)だけなので、ユーザを追加しようとしても下記のようにエラーが表示されて追加できません。複数ユーザで使用する場合には Maker 以上のプランにする必要があります。

f:id:akanuma-hiroaki:20190223174110p:plain

アラートを追加する

 Lagoon ではデータの内容によってアラートを出すことが可能です。まずはアラートの通知方法を設定するために、チャンネルを作成します。左のメニューから 通知チャンネル を選択します。

f:id:akanuma-hiroaki:20190223194956p:plain

 チャンネルリストの画面が表示されますが、最初はまだチャンネルがないので、表示されている チャンネルを追加 ボタンをクリックします。

f:id:akanuma-hiroaki:20190223195138p:plain

 チャンネルの設定フォームが表示されます。通知方法としては slack や webhook など様々なものが選択可能です。今回は Email を選択します。対象のメールアドレスはセミコロンで区切って複数指定することが可能です。 送信テスト をクリックするとテストメールが送信されます。正しく設定できたら 保存 をクリックします。

f:id:akanuma-hiroaki:20190223195323p:plain

 作成されたチャンネルはリスト画面に表示されます。

f:id:akanuma-hiroaki:20190223195456p:plain

 次にダッシュボードの編集画面の アラート タブから アラートを作成 ボタンをクリックします。

f:id:akanuma-hiroaki:20190223195649p:plain

 アラートの設定画面からアラートの条件を設定します。 query では対象のデータ項目と対象の時間を設定できます。今回は温度を対象にするので、 B を対象として、5分間のデータを対象にします。条件としては平均値、最小値など様々な方法が指定できます。今回は最大値( max() )を使用して、5分間の最大値が23を超えた場合にアラートを出します。

f:id:akanuma-hiroaki:20190223200136p:plain

 通知の送り先としては先ほど作成したチャンネルを指定します。メールの文面は自由に設定できます。

f:id:akanuma-hiroaki:20190223200334p:plain

 指定した閾値を超えると下記のようなメールが送られてきます。

f:id:akanuma-hiroaki:20190223200416p:plain

まとめ

 センサー等で集めたデータを可視化するのは自分でやると結構な手間がかかる部分ですが、 Lagoon を使うことで Harvest に入っているデータであれば画面から設定するだけで簡単に可視化することができました。今回 Freeプランができたことで、開発者が自分で使うぐらいであれば無料で使うことができるので、デバイスで取得したデータは json で Unified Endpoint を経由して Harvest に投げ込んでおいて Lagoon で可視化するという、サーバサイドの一連の処理を無料で簡単に用意することができるようになりました。ますますプロトタイピングのハードルが下がりましたね。

Unified Endpoint で SORACOM の複数サービスにデータを送る

 先日 2/14 にソラコムさんの if-up2019 IoT Technology Conference に参加してきました。

if-up2019.soracom.jp

 ソラコムさんはイベントに合わせていつも新発表をされますが、今回の新発表の一つとして Unified Endpoint が発表されました。

blog.soracom.jp

 こちらは独立した新サービスではなく新機能ということで、すでに Public Beta として使えるようになっていたので早速試してみました。

機能概要

 Unified Endpoint を簡単に説明すると、 SORACOM の複数のサービスへのデータ送信を一つのURLでまとめて受け取れるようにしたものです。 SORACOM では今までに Beam, Funnel, Harvest といったサービスが提供されていて、クラウドサービスとのデータ連携や可視化が便利にできるようになっていましたが、送信先URLはそれぞれ違っています。なので後から送信先サービスを変更する場合、例えば Beam で自前のサーバに送っていたけど Funnel で AWS IoT に連携するようにする、といった場合にはデバイス上の実装に含まれているURL指定を変更する必要がありました。

 これが Unified Endpoint を利用することにより、デバイスの実装では常に Unified Endpoint の URL にデータを送っていれば、ユーザーコンソールの設定だけで送信先サービスを変更できるようになります。もちろん送信内容はあらかじめ汎用性を考慮しておく必要はありますが、設置済みデバイスの実装に手を入れずに送信先を変更できるというのはとても便利なのではないかと思います。

設定

 今回は Raspberry Pi を SORACOM Air でセルラーネットワークに接続し、温度と湿度のデータを JSON で SORACOM Harvest と SORACOM Funnel 経由で AWS IoT に送信してみます。 AWS IoT に接続するにはデバイスの認証が必要ですが、今回の認証は以前下記の記事で試した SORACOM Krypton での認証を使用します。

blog.akanumahiroaki.com

 それではそれぞれのサービスの設定を行なっていきます。まず Unified Endpoint は有効にするための設定は特に必要なく、自動的に使用されます。レスポンスの形式を変更する場合はユーザコンソールのSimグループの設定画面から設定変更を行います。今回はデフォルトの Auto 設定のまま使用してみます。

f:id:akanuma-hiroaki:20190216154948p:plain

 Harvest の設定は ON/OFF の切り替えのみですので、ユーザコンソールから ON にします。

f:id:akanuma-hiroaki:20190216155126p:plain

 Funnel から AWS IoT への転送設定は、 Funnel の利用を ON にして、転送先サービスで AWS IoT を選択します。転送先 URL には AWS IoT のエンドポイントに MQTT のトピックス名を組み合わせて指定し、認証情報は Krypton で設定した認証情報を指定します。

f:id:akanuma-hiroaki:20190216160208p:plain

 これで設定は一通り完了です。データ送信については今回は擬似的に温度と湿度データを生成して送信する Ruby スクリプトを実装してデータを送信してみます。送信先の URL には Unified Endpoint の URL を指定し、30秒ごとにデータを送信してみます。

require 'bundler/setup'
require 'httpclient'
require 'logger'

UNIFIED_ENDPOINT = 'http://uni.soracom.io'
LOG_FILE = 'env_monitor.log'

class ENV_MONITOR
  def initialize(interval)
    @interval = interval
    @http_client = HTTPClient.new
    @logger = Logger.new(LOG_FILE)
    @temperature_range = 20.0..25.0
    @humidity_range = 40..60
  end

  def execute
    loop do
      temperature = Random.rand(@temperature_range)
      humidity = Random.rand(@humidity_range)
      payload = '{"temperature": "%.1f", "humidity": "%.1f"}' % [temperature, humidity]
      res = @http_client.post(UNIFIED_ENDPOINT, payload, 'Content-Type' => 'application/json')
      @logger.info("PAYLOAD: #{payload} / Response: #{res.status}")
      @logger.info(res.body)

      sleep(@interval)
    end
  end
end

if __FILE__ == $PROGRAM_NAME
  monitor = ENV_MONITOR.new(30)
  monitor.execute
end

動作確認

 それでは SORACOM Air でネットワーク接続した上でスクリプトを動かして動作を確認してみます。スクリプトを動かすと下記のようにログに出力されます。今回は Unified Endpoint のレスポンスの設定を Auto にしていますので、レスポンスの detail には Funnel と Harvest それぞれからの statusCode が含まれます。 Funnel からは 204、 Harvest からは 201 が返って来ていますので、それぞれへのデータ送信は成功しているようです。

I, [2019-02-16T06:34:37.185769 #23241]  INFO -- : PAYLOAD: {"temperature": "20.8", "humidity": "58.0"} / Response: 200
I, [2019-02-16T06:34:37.186107 #23241]  INFO -- : {"result":"ok","detail":{"SoracomFunnel":{"statusCode":204},"SoracomHarvest":{"statusCode":201}}}
I, [2019-02-16T06:35:09.495921 #23241]  INFO -- : PAYLOAD: {"temperature": "25.0", "humidity": "41.0"} / Response: 200
I, [2019-02-16T06:35:09.496257 #23241]  INFO -- : {"result":"ok","detail":{"SoracomFunnel":{"statusCode":204},"SoracomHarvest":{"statusCode":201}}}
I, [2019-02-16T06:35:43.716117 #23241]  INFO -- : PAYLOAD: {"temperature": "20.1", "humidity": "52.0"} / Response: 200
I, [2019-02-16T06:35:43.716453 #23241]  INFO -- : {"result":"ok","detail":{"SoracomFunnel":{"statusCode":204},"SoracomHarvest":{"statusCode":201}}}
I, [2019-02-16T06:36:15.356277 #23241]  INFO -- : PAYLOAD: {"temperature": "23.3", "humidity": "47.0"} / Response: 200
I, [2019-02-16T06:36:15.356612 #23241]  INFO -- : {"result":"ok","detail":{"SoracomFunnel":{"statusCode":204},"SoracomHarvest":{"statusCode":201}}}
I, [2019-02-16T06:36:49.526506 #23241]  INFO -- : PAYLOAD: {"temperature": "20.4", "humidity": "52.0"} / Response: 200
I, [2019-02-16T06:36:49.526851 #23241]  INFO -- : {"result":"ok","detail":{"SoracomFunnel":{"statusCode":204},"SoracomHarvest":{"statusCode":201}}}
I, [2019-02-16T06:37:21.146493 #23241]  INFO -- : PAYLOAD: {"temperature": "23.7", "humidity": "47.0"} / Response: 200
I, [2019-02-16T06:37:21.146824 #23241]  INFO -- : {"result":"ok","detail":{"SoracomFunnel":{"statusCode":204},"SoracomHarvest":{"statusCode":201}}}
I, [2019-02-16T06:37:58.956850 #23241]  INFO -- : PAYLOAD: {"temperature": "22.3", "humidity": "59.0"} / Response: 200
I, [2019-02-16T06:37:58.957189 #23241]  INFO -- : {"result":"ok","detail":{"SoracomFunnel":{"statusCode":204},"SoracomHarvest":{"statusCode":201}}}
I, [2019-02-16T06:38:30.626827 #23241]  INFO -- : PAYLOAD: {"temperature": "21.2", "humidity": "50.0"} / Response: 200
I, [2019-02-16T06:38:30.627169 #23241]  INFO -- : {"result":"ok","detail":{"SoracomFunnel":{"statusCode":204},"SoracomHarvest":{"statusCode":201}}}
I, [2019-02-16T06:39:03.247368 #23241]  INFO -- : PAYLOAD: {"temperature": "21.7", "humidity": "59.0"} / Response: 200
I, [2019-02-16T06:39:03.247702 #23241]  INFO -- : {"result":"ok","detail":{"SoracomFunnel":{"statusCode":204},"SoracomHarvest":{"statusCode":201}}}
I, [2019-02-16T06:39:34.887067 #23241]  INFO -- : PAYLOAD: {"temperature": "22.1", "humidity": "55.0"} / Response: 200
I, [2019-02-16T06:39:34.887410 #23241]  INFO -- : {"result":"ok","detail":{"SoracomFunnel":{"statusCode":204},"SoracomHarvest":{"statusCode":201}}}

 レスポンスのフォーマットについては下記公式ドキュメントに詳しく書かれていますのでご参照ください。

Unified Endpoint : 機能の説明 | 開発者ガイド | SORACOM Developers

 Harvest の画面でも送信されたデータが確認できます。

f:id:akanuma-hiroaki:20190216160003p:plain

まとめ

 Unified Endpoint から転送可能なのは Beam, Funnel, Harvest の3つですが、 Beam に転送できるということは実質的には何処へでも転送できるということになりますので、実装時にはとりあえず Unified Endpoint 宛に JSON でデータを送るようにしておけば、後からの変更はかなり柔軟にできるのではないでしょうか。認証でも Krypton 等を組み合わせることができますし、様々な処理をデバイスからクラウド側へオフロードできるようになって、どんどん便利になってきました。プロトタイプ段階からこういったサービスを活用してアーキテクチャを考えていくことが重要になってきますね。