Edited at
SORACOM Day 15

SORACOM Funnel で CloudWatch に温度グラフを作る

More than 1 year has passed since last update.

この記事は SORACOM Advent Calendar 2016 の15日目です。


tl;dr


  1. RasPi に USB 温度計と SORACOM Air のドングルを差して


  2. SORACOM Funnel に送って

  3. Kinesis Streams からの

  4. Lambda からの

  5. CloudWatch Custom Metric でグラフ化するよ

cloudcraft - Oreno Architecture.png


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 を使うときに必要なのでグループを作って紐付けておきます。

Capture.PNG

登録したらデバイス側の設定を

あたりを参考にやっておきます。有線 LAN も Wi-Fi も切って、 SORACOM Air でインターネットに通信できることを確認します。


Kinesis Stream を作る

Amazon_Kinesis_Streams_Management_Console.png

設定項目もほとんどないのでさくっと作ります。もちろん Shard 数は 1 で充分です。


Funnel 用 IAM User を作る

IAM_Management_Console.png

たぶん kinesis:Put* だけを許可すれば問題ない気がしますが、今回は大雑把に AmazonKinesisFullAccess ポリシーを付加しました。


SORACOM に credentials を登録

SORACOM_ユーザーコンソール.png

セキュリティ > 認証情報ストア から認証情報を登録します。上で作った IAM User の credentials をコピペしましょう。


SORACOM Funnel のセットアップ

SORACOM_ユーザーコンソール.png

グループ > (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 を選択(好みの問題)。


temp2funnel.rb

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 からの導火線もまとめて設定でき、非常~~~に楽です。

こちらも言語はお好みで。


serverless.yml

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


handler.js

'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 はミリ秒なので、数値のまま PutMetricDataTimestamp に指定するとエラーになる(一方で、 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 です。そのうち紹介記事を書きます。

Screen_Shot_2016-12-14_at_4_36_55.png

コンソールでも問題なさそう。


ここでそうびしていくかい?

室内だけではあまりにデータが地味なので、一式を持って外を小一時間お散歩してみます。

Slack for iOS Upload.jpg

温度計は直差しだと RasPi 本体の発熱が影響するので、延長ケーブル経由で。モバイルバッテリーは Ingress エージェント御用達のリアルパワーキューブことコレ

結果がこちらです。

CloudWatch_Management_Console.png

温度計の品質の問題か、あまり反応性はよくなくて、なだらかに変化してますね。


まとめ


  • SORACOM Funnel を使うことで、 RasPi の中には AWS の認証情報を入れておく必要がありません。機器の数が多くなればなるほど管理の容易さ・セキュリティともに恩恵が大きくなっていくでしょう

  • 今回は CloudWatch にデータを送りましたが、一度 Kinesis に入れてしまえば後の取り回しは自由度が高いです