この記事は SORACOM Advent Calendar 2016 の15日目です。
tl;dr
- RasPi に USB 温度計と SORACOM Air のドングルを差して
- SORACOM Funnel に送って
- Kinesis Streams からの
- Lambda からの
- CloudWatch Custom Metric でグラフ化するよ
RasPi の準備
実は全く触ったことがなかったので、お手軽そうな Raspberry Pi3 Model B ボード&ケースセット (Element14版, Clear) を購入しました。色は SORACOM ネタということで青。kawaii。
Raspbian を dd
で MicroSD に焼いて差し、 HDMI でテレビにつないで起動、 SSH の設定あたりまでをさくっと完了しました。 Web を検索すると山ほど先達がいて、ありがたいことこの上なし。
温度計を使えるようにする
家の温度計ってみようか、と思って1年半くらい前に買った(そしてちょっとだけ触って放置してた) USB温度計! USB thermometer-528018 の存在を思い出したので発掘。
初老丸 a.k.a. かっぱ先輩の記事を参考に temper
をインストールしました。
pi@raspberrypi:~ $ sudo temper
12-Dec-2016 20:11,27.310661
いちおう気温が取れてるようです。
SORACOM Air のセットアップ
SORACOM から発売されている 3G USBドングル AK-020 SORACOMスターターキットを購入しました。
まずは届いた SIM を SORACOM Console で登録して、のちほど Funnel を使うときに必要なのでグループを作って紐付けておきます。
登録したらデバイス側の設定を
- 各種デバイスで SORACOM Air 使用する | Getting Started | SORACOM Developers
- AK-020 を Raspbian で使う(とりあえず版) - Qiita
- (Raspbian) AK-020で3Gモデム自動接続(毎回のejectとmodprobeなし) - Qiita
あたりを参考にやっておきます。有線 LAN も Wi-Fi も切って、 SORACOM Air でインターネットに通信できることを確認します。
Kinesis Stream を作る
設定項目もほとんどないのでさくっと作ります。もちろん Shard 数は 1
で充分です。
Funnel 用 IAM User を作る
たぶん kinesis:Put*
だけを許可すれば問題ない気がしますが、今回は大雑把に AmazonKinesisFullAccess
ポリシーを付加しました。
SORACOM に credentials を登録
セキュリティ
> 認証情報ストア
から認証情報を登録します。上で作った IAM User の credentials をコピペしましょう。
SORACOM Funnel のセットアップ
グループ
> (SIM を登録した時に作ったグループ)
から Funnel を有効化します。
リソースタイプは Amazon Kinesis Streams
、リソース URL は https://kinesis.<Stream を作ったリージョン>.amazonaws.com/<Stream の名前>
(これは AWS コンソールで Stream を見ても表示されておらず、ちょっとわかりづらい)、認証情報は上で登録した認証情報、送信するデータの形式は JSON
にします。
SORACOM Funnel の Kinesis Firehose アダプターを使用してクラウドにデータを収集する(コンソール版) | Getting Started | SORACOM Developers を参考に、 SORACOM Air 接続済みの RasPi から動作確認をします。
$ curl -vX POST http://funnel.soracom.io -d '{"message":"Hello SORACOM Funnel via HTTP!"}' -H 'Content-Type: application/json'
Content-Type
はちゃんと指定しないと怒られます。 HTTP 20x
が返ってくればおそらく成功です。
温度計のデータを Funnel に投げ続けるようにする
言語はなんでもよいですが、ここでは Ruby を選択(好みの問題)。
require "time"
require "json"
require "net/http"
result = %x(sudo temper).split(",")
time = Time.parse(result.first)
temperature = result.last.to_f
payload = JSON.generate(time: time, temperature: temperature)
Net::HTTP.start("funnel.soracom.io", 80) do |http|
http.post "/", payload, "Content-Type" => "application/json"
end
これを RasPi に入れて、 cron で1分おきに叩くようにしておきます。
* * * * * /usr/bin/ruby /home/pi/temp2funnel.rb
Lambda Function をデプロイする
Serverless Framework を使いました。簡単なコマンドで Function のみならず、追加の IAM 権限や Kinesis からの導火線もまとめて設定でき、非常~~~に楽です。
こちらも言語はお好みで。
service: orenotempmeter
provider:
name: aws
runtime: nodejs4.3
region: ap-northeast-1
iamRoleStatements:
- Effect: Allow
Action:
- "cloudwatch:PutMetricData"
Resource:
- "*"
functions:
kinesis2cw:
handler: handler.default
events:
- stream:
arn: arn:aws:kinesis:ap-northeast-1:123412341234:stream/soratemp # さっき作った Stream の ARN
batchSize: 5
startingPosition: LATEST
enabled: true
memorySize: 128
'use strict';
const AWS = require('aws-sdk');
const cloudwatch = new AWS.CloudWatch();
module.exports.default = (event, context, callback) => {
const decodedRecords = event.Records.map(record => {
const buffer = new Buffer(record.kinesis.data, 'base64');
const dataJSON = buffer.toString();
return JSON.parse(dataJSON);
});
cloudwatch.putMetricData({
Namespace: 'OrenoTempMeter',
MetricData: decodedRecords.map(record => {
return {
MetricName: 'Temperature',
Value: record.payloads.temperature,
Timestamp: new Date(record.timestamp),
Dimensions: [
{
Name: 'IMSI',
Value: record.imsi,
},
{
Name: 'OperatorId',
Value: record.operatorId,
},
],
}
}),
}).promise().then(putMetricDataResult => {
console.log(JSON.stringify(putMetricDataResult));
callback(null, {putMetricDataResult});
return null;
}).catch(err => {
console.log(JSON.stringify(err));
callback({err}, null);
return null;
});
};
ポイントとしては
- Kinesis Streams で流れてくるデータ本体は
base64
でエンコードされているので、これを復号する必要がある - データには、ユーザーが送信した元データ以外に Funnel が付加したメタデータが含まれている
- Funnel が付加したメタデータの
timestamp
はミリ秒なので、数値のままPutMetricData
のTimestamp
に指定するとエラーになる(一方で、 JavaScript のnew Date()
には1000を掛けなくてもそのまま入力できる)
今回は CloudWatch の Dimension として、 Funnel が付加したメタデータの IMSI
(SIM のユニークな ID) と OperatorId
(SORACOM のユーザー ID ?)を追加しました。今回は1台しか RasPi がありませんが、もし複数のデバイスで同様にデータを収集したとしても、複数の EC2 インスタンスから特定インスタンスのメトリックを見るのと全く同じ感覚で使えることでしょう。
CloudWatch の確認
$ metrin --region ap-northeast-1 --namespace OrenoTempMeter --metric-name Temperature --statistic Average --dimension IMSI:123451234512345 --dimension OperatorId:OP1234567890 debug
Params: {
Dimensions: [{
Name: "IMSI",
Value: "123451234512345"
},{
Name: "OperatorId",
Value: "OP1234567890"
}],
EndTime: 2016-12-14 04:15:15 +0900 JST,
MetricName: "Temperature",
Namespace: "OrenoTempMeter",
Period: 60,
StartTime: 2016-12-14 04:10:15 +0900 JST,
Statistics: ["Average"]
}
Response: {
Datapoints: [
{
Average: 33.482864,
Timestamp: 2016-12-13 19:13:00 +0000 UTC,
Unit: "None"
},
{
Average: 33.482864,
Timestamp: 2016-12-13 19:12:00 +0000 UTC,
Unit: "None"
},
{
Average: 33.482864,
Timestamp: 2016-12-13 19:11:00 +0000 UTC,
Unit: "None"
},
{
Average: 33.482864,
Timestamp: 2016-12-13 19:10:00 +0000 UTC,
Unit: "None"
},
{
Average: 33.354275,
Timestamp: 2016-12-13 19:14:00 +0000 UTC,
Unit: "None"
}
],
Label: "Temperature"
}
取れているようですね。 y13i/metrin は最近作った CloudWatch のミニマルな CLI です。そのうち紹介記事を書きます。
コンソールでも問題なさそう。
ここでそうびしていくかい?
室内だけではあまりにデータが地味なので、一式を持って外を小一時間お散歩してみます。
温度計は直差しだと RasPi 本体の発熱が影響するので、延長ケーブル経由で。モバイルバッテリーは Ingress エージェント御用達のリアルパワーキューブことコレ。
結果がこちらです。
温度計の品質の問題か、あまり反応性はよくなくて、なだらかに変化してますね。
まとめ
- SORACOM Funnel を使うことで、 RasPi の中には AWS の認証情報を入れておく必要がありません。機器の数が多くなればなるほど管理の容易さ・セキュリティともに恩恵が大きくなっていくでしょう
- 今回は CloudWatch にデータを送りましたが、一度 Kinesis に入れてしまえば後の取り回しは自由度が高いです