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

tl;dr

  1. RasPi に USB 温度計とSORACOM Air のドングルを差して
  2. SORACOM Funnel に送って
  3. Kinesis Streams からの
  4. Lambda からの
  5. CloudWatch Custom Metric でグラフ化するよ

96b54071-5c40-f910-40a9-79ee9947b37f

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

b0e70a99-d34a-b25d-b29e-16f5f6364252

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

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

Kinesis Stream を作る

b23a91be-f386-bb93-0984-0929008dde58

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

Funnel 用 IAM User を作る

20d7b1bd-1197-a41e-1f94-379d877575d7

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

SORACOM に credentials を登録

68392274-7026-667f-8ed2-a21e6b073121

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

SORACOM Funnel のセットアップ

e2f6507e-2418-4a88-0b39-e3828fc732e2

グループ > (SIM を登録した時に作ったグループ) から Funnel を有効化します。

リソースタイプはAmazon Kinesis Streams 、リソース URLはhttps://kinesis..amazonaws.com/(これは 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 です。そのうち紹介記事を書きます。

c18bee7a-3271-29a1-246e-813201a5113e

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

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

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

aa9b2985-60a7-54e1-fce0-b94d3b5109c2

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

結果がこちらです。

bdc8d843-f6e6-fd5b-7e26-3c73fc3a1b54

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

まとめ

  • SORACOM Funnel を使うことで、 RasPi の中には AWS の認証情報を入れておく必要がありません。機器の数が多くなればなるほど管理の容易さ・セキュリティともに恩恵が大きくなっていくでしょう
  • 今回は CloudWatch にデータを送りましたが、一度 Kinesis に入れてしまえば後の取り回しは自由度が高いです

元記事はこちら

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