これは 😺TECHSCORE Advent Calendar 2018😺 の24日目の記事です。
また、1 日目の おうちの二酸化炭素濃度を計ってみる の続きとなります。
おそとでおうちの二酸化炭素を見る
前回の記事で、小さなディスプレイ上で二酸化炭素濃度を見られるようになりました。
しかし、こうなるとログを取って変化を見たくなるところです。
そこで、ログを取りつつ、外出先で直近の変化を見られるようにします。
なお、外出先で見ることに意味はありません。
作戦
まず、ログはグラフを簡単に見られる AWS の Cloudwatch のカスタムメトリクスを使います。
これで、二酸化炭素濃度のログを残せます。
しかし、これだけでは、AWS にいちいちログインしなければ見られません。
そこで、S3 にグラフ画像を定期的に生成してやることにします。
この S3 の設定で、一般公開状態にしてしまえば、外出先でも見られるということです。
まずメトリクスに値を投入する
先のデバイスは、シリアルポートから常に二酸化炭素濃度と気温を出力していました。
それを読み取り、AWS の API を呼ぶスクリプトをサービスとして常駐させることにします。
単純なシェルスクリプトで、AWS CLI で API を叩いています。
Ruby などでライブラリから API を叩くという手もありますが、こういう簡単なものであれば AWS CLI + シェルスクリプトのほうが面倒が少なくて良いですね。
ほぼ自明なものなのですが、参考までにスクリプトを以下に示しておきます。
ppm.service (systemd 用サービス定義ファイル)
1 2 3 4 5 6 7 8 9 10 |
[Unit] Description=PPM [Service] Type=simple ExecStart=/home/app/app/ppm/reporter [Install] WantedBy=multi-user.target |
サービスとして動かすシェルスクリプト
picocom でデバイスにシリアル接続して値を読んでいます。
デバイスの出力頻度に合せていると、お金がかかりますので、適当に間隔をあけるようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#!/bin/bash dev="${1:-/dev/ttyUSB0}" last=0 while read -r line do now="$( date '+%s' )" d="$(( now - last ))" if [ "$d" -ge 180 ] then date ppm="${line% *}" temp="${line#* }" aws --region ap-northeast-1 cloudwatch put-metric-data --namespace myhome --metric-name ppm --value "$ppm" aws --region ap-northeast-1 cloudwatch put-metric-data --namespace myhome --metric-name temp --value "$temp" last="$now" fi done < <(picocom --quiet -b 115200 "$dev") |
Lambda 関数を用意する
これは、Python で書きます。
グラフの定義を JSON で指定し、メトリクスの画像を返してくれる便利 API を呼んでいます。
その画像をそのまま S3 に書き出しています。
ちなみに、レスポンスに意味はありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
import json import boto3 import datetime TEMP_JSON = ''' { "view": "timeSeries", "stacked": false, "metrics": [ [ "myhome", "temp" ] ], "yAxis": { "left": { "min": 5, "max": 35, "showUnits": false } }, "period": 300, "width": 800, "height": 250, "start": "-PT12H", "end": "P0D", "timezone": "+0900" }''' PPM_JSON = ''' { "view": "timeSeries", "stacked": false, "metrics": [ [ "myhome", "ppm" ] ], "yAxis": { "left": { "min": 400, "max": 1500 } }, "annotations": { "horizontal": [ { "value": 1000, "fill": "above" } ] }, "period": 300, "width": 800, "height": 250, "start": "-PT12H", "end": "P0D", "timezone": "+0900" } ''' def lambda_handler(event, context): def upload(key_name, widget): image = cloudwatch.get_metric_widget_image(MetricWidget = widget) obj = s3.Object(bucket_name, key_name) r = obj.put(Body = image['MetricWidgetImage'], ContentType = 'image/png') def get_value(name): now = datetime.datetime.utcnow() r = cloudwatch.get_metric_statistics( Period = 300, StartTime = now - datetime.timedelta(seconds = 600), EndTime = now, Namespace = 'myhome', MetricName = name, Statistics=['Average']) return r['Datapoints'][-1]['Average'] bucket_name = 'ky.example.com' s3 = boto3.resource('s3') cloudwatch = boto3.client('cloudwatch', region_name = 'ap-northeast-1') upload('ppm.png', PPM_JSON) upload('temp.png', TEMP_JSON) ppm = get_value('ppm') temp = get_value('temp') obj = s3.Object(bucket_name, 'values.json') r = obj.put(Body = json.dumps({'ppm': ppm, 'temp': temp}), ContentType = 'application/javascript') return { "statusCode": 200, "body": 'OK' } |
なお、TEMP_JSON
や PPM_JSON
は自分で書くのは面倒です。
CloudWatch のコンソールで実際にグラフを作ってみて、ソース
タブでイメージAPI
を選び、表示されたものをコピペすると楽です。
↓のような画面があるはずです。
定期的に書きだす
CloudWatch Events で 10 分毎に先程の Lambda 関数を実行します。
また、書き出した画像を埋めこんだ index.html も用意してやります。
こちらは勿論静的なものでかまわないので、適当に書いて同じ S3 のバケットに放りこみます。
完成!
これで、外出先から S3 経由で(ほぼ)最新のグラフが見られます。
↓が実際のグラフです。うっかりドアを閉めていたので濃度が高くなっていますね…。
セキュリティ
誰でも見られるような状態で URL を教えてしまうと、家に人がいないことがなんとなく分かってしまうかもしれません。
たまに激しい運動をして二酸化炭素を生成する生物が居ると良いかもしれません。