最近ラズパイに入門してさらなる有効活用にチャレンジしました。
何番煎じかわかりませんが、私が寝室として使っている和室の温度と湿度を計測して可視化してみます。
可視化のためのソリューションとして目をつけたのがAWS IoT CoreとAmazon TimeStreamです。
AWSのDBと言えばDynamoDBですが、時系列データを扱うにはベストプラクティスにもあるとおり一定単位(例えば日付)でテーブルを分割、その中でタイムスタンプを主キーとして構成するような使い方が望まれます。
個人でのデバイス利用でそこまで労力はかけたくないので、若干の出費はありますが時系列管理にもってこいのAmazon TimeStreamを試してみました。
また可視化(グラフ描画)ですが、最初はQuickSightを試しましたがTimeStreamとのうまい連携方法が分からず、AWS LambdaとChart.jsで書くことにしました。
環境
利用プラットフォーム
デバイス:ラズベリーパイ4 ModelB
温湿度センサ:DHT22
メッセージング:AWS IoT Core
データベース:Amazon TimeStream
可視化バックエンド:API Gateway、Lambda
可視化フロントエンド;Chart.js
構成図
各所で必要なプログラムはGitHubにあげています。
https://github.com/quotto/raspberrypi-and-aws_iot
Amazon TimeStreamの設定
TimeStreamの概要
はじめにラズパイから送信される温湿度データを保存するAmazon TimeStreamを設定していきます。
TimeStreamは名前のとおり時系列データをシンプルに扱えるデータベースです。**「アクセス頻度が高い直近のデータはインメモリに、一定期間が経ったら低コストの磁気ディスクに移す」**という作業も自動でやってくれます。
テーブル構成は従来のRDBに近く、DynamoDBではサポートされていないSQLが使えるのもポイントです。(ただし独自の関数もあるのでガイドを参照しましょう。)
TimeStreamでデータベースとテーブルを作る
TimeStreamを構成する要素は
- データベース
- テーブル
の2つです。これらを作っていきます。なお当記事執筆時点ではus-east-1、us-east-2、us-west-1、eu-west-1の4つのリージョンでのみ使えます。
マネジメントコンソールでTimeStreamを開き「CreateDatabase」からデータベースを作成します。
データベースの作成タイプを選択します。1から自分で作成する場合は**「Standard database」**を選択します。ちなみに「Sample database」を選択すると、IoTとDevOps用途での使用を想定した、サンプルテーブルが格納されたデータベースが作成されます。
TimeStreamはKMSによる暗号化が必須ですが、Keyを指定しなければ自動で作成されるので今回は特に指定しませんでした。
「CreateDatabase」を実行するとすぐにDBが作成されました。
**「Data retention」**ではテーブル上のデータをメモリに保管する期間と磁気ディスクから削除する期間を指定します。この指定によりアクセス頻度が高い最近のデータはメモリに、古いデータは磁気ディスクに自動で移し替えられコストとパフォーマンスを最適化できます。
ここまで指定したら「Create Table」を実行します。テーブルが作成されました。
AWS IoT Coreの設定
AWSのIoT系サービスのハブとして動くのがAWS IoT Coreです。今回はIoT Coreの
- デバイス(モノ)
- 証明書
- ポリシー
- ルール
を構成する必要があります。
セットアップ
一つずつ作ってもいいのですが、サクッと試す程度であればまとめて作ってしまったほうが楽です。
マネジメントコンソールのIoT Coreのトップページ左側の**「オンボード」→「開始方法」→「開始方法」をクリックします。次のページでも「開始方法」**をクリックします。(AWS IoT系サービスの日本語訳がちょっと変でわかりずらい……)
次の画面でこのIoT Coreに接続予定のデバイスのプラットフォームを選択します。この選択値は提供されるサンプルプログラムがわかるだけなのでで、何でもいいです。(今回は使いません)
ここまでくるとポリシーとクライアント証明書が生成されるので、クライアント証明書をダウンロードします。AWSのルート証明書、クライアント証明書、秘密鍵が格納されています。(この他にサンプルプログラムも含まれます)
これらをデバイスの認証に利用することで、デバイスがセキュアに外(IoT Core)のネットワークとつながることができます。
この後に接続テストの手順が表示されますが、今回は省略します。
ルールを作ってIoT CoreとTimeStreamをつなぐ
ここまででIoT Core上にモノ、証明書、ポリシーが出来上がりました。最後にルールを作成し、デバイスからIoT Coreで受信したメッセージ(トピック)をTimeStreamのテーブルに格納するように設定します。
IoT Coreの画面左側**「ルール」**から作成します。まずはこのルールの名前を入力します。
次にルールクエリステートメントを設定します。これはIoT CoreからTimeStreamに連携する対象データの条件をSQL形式で定義します。
この例ではtopic/raspberrypi
という名前のトピックにpublishされたメッセージがすべて格納されますが((トピック名の「topic/」は自動で付加されるため、実際にpublishで指定するトピック名は「raspberrypi」になる。))、例えばトピック内のデータ項目で条件付(where)したり、topic/#
という感じでトピック名にワイルドカードが使えたりします。
続いてのアクションでは、このルールに当てはまったデータの格納先を設定します。TimeStreamを選ぶと先ほど作ったDBとテーブルが表示されるのでそれを選択します。
TimeStreamにデータを格納するには**「ディメンション」と「タイムスタンプ」**を設定します。
ディメンションは各データのメタ情報のようなもので、例えばそのデータを送信したデバイスのIDやデバイスの地域を設定します。値は固定値の他にトピックの名前やペイロード中のデータを指定できます。
タイムスタンプは名前のとおり、このルールが起動した時刻情報です。単位はどこまでサポートされているのか分かりませんが今回はデフォルトのミリ秒にしておきます。
最後にロールの作成でTimeStreamへ書き込み権限のあるロールを作成します。(ロール名を入力するだけで自動で作成されます)
これでラズパイからトピックtopic/raspberrypi
にメッセージが配信されるとディメンションdeviceIDとタイムスタンプを付けてTimeStreamにデータが格納されるようになりました。
ラズパイから温湿度をIoT Coreへ送信する
実際のデータを取るため、温湿度センサーを付けたラズパイからIoT Coreに向けて温湿度データを送信します。
温湿度センサーの取り付け
ラズパイに温湿度センサーモジュールを取り付けます。今回購入したDSD TECH DHT22は接続用ケーブルが付いており、直接ラズパイのピンに差し込めば良いので手軽で簡単です。
こんな感じになりました。センサのデータはDATピンから受け取ります。ラズパイ側のGPIOは23番です。
Pythonプログラムで温湿度データを取り出す
DTH22からデータを取り出すにはいくつかのPythonライブラリがありますが、今回はCirculcuitPythonを使いました。
pythonライブラリと必要パッケージをインストールします。
pip3 install adafruit-circuitpython-dht
sudo apt install libgpiod2
実際のpythonプログラムからは、こんな感じで簡単に温度と湿度が取得できます。
import adafruit_dht
import board
#DHT22のDATを接続したGPIOピン番号を指定してセンサーを取得
dht_Device = adafruit_dht.DHT22(board.D23)
# 温度の取得(摂氏)
dht_Device.temperature
# 湿度の取得
dht_Device.humidity
IoT Coreに温湿度を送信する
最後に上記のプログラムで取得した温湿度データをIoT Coreに送信します。
pythonではAWS IoT関連のsdkが用意されているのでまずはライブラリをインストールします。
pip3 install awsiotsdk
IoT Coreへのメッセージ送信はAWSが公開しているサンプルプログラムが役に立つのでとりあえずこれをcloneします。
git https://github.com/aws/aws-iot-device-sdk-python-v2.git
IoT Coreの作成でダウンロードしたクライアント証明書、秘密鍵、ルート証明書をラズパイ上の適当なディレクトリに配置します。
scp -r ./download/certificate/* pi@raspberrypi.local:~/certs
サンプルプログラムをもとに引数を受け取ってトピックtopic/raspberrypi
にJSONメッセージを送信するプログラムを作成します。
ソース全部はGitHubを参照してもらうこととして、必要な値を引数に指定してスクリプトを実行します。
python publish.py --endpoint xxxx.xxxx.xxxx --topic raspberrypi --root-ca ~/cert/root-CA.pem --cert ~/cert/cert.pem --key ~/cert/private.key.pem --count 1
--endpoint
がメッセージのpublish先になりますが、これはIoT Coreのコンソールから確認することができます。
また注意点として、IoT Core上のルールやポリシーのトピック名はtopic/raspberrypi
になりますが、「topic/」部分はIoT Core上で自動で付与されるためSDKプログラムで指定するトピック名は「raspberrypi」になります。
正常に実行されると次のようなメッセージがIoT Coreに向かってpublishされます。
{"temp": 20.5, "humidity": 45.3, "timestamp": 1607230805}
なおtimestamp
をつけていますが前述のとおり、TimeStreamへデータを格納する際にタイムスタンは自動で付与されます。
TimeStreamのデータを確認する
Amazon TimeStreamのコンソールのクエリーから実際に格納されたデータをSQLで抽出できます。
TimeStreamには、ラズパイがpublishしたjsonペイロードの各項目が1レコード分のデータとしてmeasure_name(項目名)とmeasure_value(値)のペアで登録され、IoT Coreで設定したディメンションと**タイムスタンプ(time)**が全レコード共通の項目として設定されています。
クエリを発行してグラフを描いてみる
最後にTimeStreamに格納されたデータを取り出してグラフにしてみます。
今回はフロントエンドの静的ファイルをs3に格納し、その内部でバックエンドのapi-gateway→lambdaを叩く構成です。細かい部分は省略しますので全容はGitHubを参照してください。
TimeStreamからデータを抽出する(バックエンド)
lambdaの中でこのようなクエリを発行してTimeStreamから値を取り出しています。(${}はjavascript中の変数)
WITH temps AS (
select time,measure_value::double as temp from"raspberrypiDB"."temp_and_humidity_table"
where measure_name = 'temp' order by time
),humidities AS (
select measure_value::double as humidity,time from
"raspberrypiDB"."temp_and_humidity_table"
where measure_name = 'humidity' order by time
)
select to_iso8601(a.time) as time,a.temp,b.humidity from temps a
left outer join humidities b
on a.time=b.time where a.time >= from_iso8601_timestamp('${begin_dateISOString}')
and a.time <= from_iso8601_timestamp('${end_dateISOString}')
order by a. time
このSQLは指定した時間内のデータをtimestamp
で結合して、同時刻の温度と湿度を取り出します。
TimeStreamのSQLは独自の関数が用意されており、from_iso8601_timestamp(string)
はiso8601形式の文字列を受け取り、TimeStream上のtimestamp型に変換します。
Chart.jsでグラフ描画(フロントエンド)
フロントエンドのJavaScript内で、バックエンドから上記のSQL結果セットを受け取り、温度と湿度の2軸折線グラフを出力します。
データが飛び飛びなのはラズパイをつけたり切ったりしているからです。Chart.jsは初めてでグラフのバランスもちょっとおかしいですが、なんとなくそれっぽいものが作れました!!
DynamoDBはとっても手っ取り早くて簡単に使えますが、ちょっとした時系列データを扱いたい時や、慣れ親しんだSQLが使えないという難点もありました。
Amazon TimeStreamはそのへんをうまく保管してくれる機能が揃っています。
とはいえ、個人的にはディメンションの扱い方がピンときていなかったりするので、IoTに限らず、今後も積極的に使っていきたいと思います。