Nature Remo+AWS+LINE Notifyで緩やかな自宅監視(3)
ここまでの流れ
Nature Remo+AWS+LINE Notifyで緩やかな自宅監視(1)
Nature Remo が検知した自宅の照度を Nature Remo Cloud API を使ってLambda関数「NatureRemo_Save」により取得し、DynamoDB「NatureRemo_Records」テーブルに保存する機能を実装した。
Nature Remo+AWS+LINE Notifyで緩やかな自宅監視(2)
EventBridgeトリガーを追加してLambda関数を毎分実行して照度データをテーブルに蓄積し、前回の照度から変化があった場合はLINE Notifyでスマホに通知する機能を実装した。
今回の記事では照度の変化をグラフで表示する機能を実装してみる。
概要
まずはLambda関数「NatureRemo_View」を新規作成し、DynamoDB「NatureRemo_Records」に蓄積された1日分の照度データをJSON形式で取得できるようにする。
次にAPI GatewayでAPIを新規作成し、所定URLへアクセスするとLambda関数から得たJSON形式の照度データをHTMLにそのままベタにはめ込んで返す。グラフの描画は、HTMLに<script>タグで埋め込んだChart.jsを使用する。
本来のAPI Gatewayの役割としては扱いやすいJSONデータを返すだけにして、別に作成した静的なHTML内のクライアントサイドJavaScriptからAPI Gatewayへリクエストする、という構成の方が正しいような気がするけど、今回は手っ取り早く試すためにAPI GatewayでHTMLコンテンツそのものを返すようにする。
API Gatewayの構成は下図の通り。バックエンドにLambda関数を統合して、URLのクエリ文字列「dayback=N」によりN日前の照度データを取得し、HTMLにはめ込んでレスポンスする。
Lambda関数 NatureRemo_View
この後のAPI Gatewayで登場するが、URLのクエリ文字列daybackの値はマッピングにより引数event.daybackとしてLambda関数に渡される。デフォルト値はdayback=0すなわち本日分とする。
取得したい範囲のunixtimeを定数since, untilに格納して、QueryCommandを使ってテーブルからタイムスタンプと照度データの配列を取得し、「.N」から得た文字列を数値に変換する。
import { DynamoDBClient, QueryCommand } from "@aws-sdk/client-dynamodb";
const db = new DynamoDBClient({ region: "ap-northeast-1" });
process.env.TZ = 'Asia/Tokyo';
export const handler = async (event) => {
// 何日前分を取得するかを指定。デフォルトは0=本日。
const dayback = event.dayback || 0;
// 本日0時0分のunixtime
const today = Math.floor(new Date(new Date().toLocaleDateString()) / 1000);
const since = today - 60*60*24 * dayback;
const until = since + 60*60*24;
const queryCmd = new QueryCommand({
TableName: "NatureRemo_Records",
KeyConditionExpression: "#dv = :dv AND #ts BETWEEN :ts1 AND :ts2",
ExpressionAttributeNames: {
"#dv": "device",
"#ts": "timestamp"
},
ExpressionAttributeValues: {
":dv" : { S: "Remo" },
":ts1": { N: since.toString() },
":ts2": { N: until.toString() }
},
ScanIndexForward: true // timestamp昇順で並び替え
});
let ret = await db.send(queryCmd);
ret = ret.Items.map(item => {
return {
timestamp: parseInt(item.timestamp.N, 10),
il : parseInt(item.il.N, 10)
};
})
return ret;
};
API Gateway
2023年10月よりAWSのAPI Gatewayコンソール画面がリニューアルして使いやすくなったようだ。
まずは「APIを作成 > REST API > 構築」と進み、下記の内容でAPIを作成する。
- 新しい API
- API 名: NatureRemo_View-API
「リソースを作成」ボタンをクリックし、リソース「パス /」配下に新たなリソースを作成する。API Gatewayにおけるリソースとは、APIのエンドポイントとなるURLといったところか。
- プロキシリソース: オフ
- リソースパス: /
- リソース名: graph
- CORS: オフ
リソース「/graph」に対してGETメソッドのAPIを作成し、Lambda関数「NatureRemo_View」と統合させる。なお、API GatewayからLambda関数を呼び出すための許可(ロールの設定)はメソッド作成時に自動でやってくれるようだ。
- メソッドタイプ: GET
- 統合タイプ: Lambda関数
- Lambdaプロキシ統合: オフ
- Lambda関数: arn:...function:NatureRemo_View
メソッドの設定
「/graph - GET - メソッドの実行」から「メソッドリクエスト」以下4つのタブで順番に設定していく。
メソッドリクエスト
URLクエリ文字列パラメータに下記を追加する。
- 名前: dayback
- 必須: オフ
- キャッシュ: オフ
統合リクエスト
マッピングテンプレートを下記の通り設定する。
- コンテンツタイプ: application/json
- テンプレート本文:
{
"dayback": "$input.params('dayback')"
}
統合レスポンス
マッピングテンプレートを下記の通り設定する。
- コンテンツタイプ: text/html
- テンプレート本文:
#set($inputRoot = $input.path('$'))
<!DOCTYPE html>
<html lang="ja">
<head>
<title>NatureRemo 照度変化</title>
<meta charset="UTF-8" />
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div>
<canvas id="myChart"></canvas>
</div>
<script>
const input = $inputRoot;
const labels = input.map(item => new Date(item.timestamp * 1000).toTimeString().substr(0, 5));
const ilData = input.map(item => item.il);
const ctx = document.getElementById("myChart");
var chart = new Chart(ctx, {
type: "line",
data: {
labels: labels,
datasets: [
{ label: "照度", data: ilData }
]
}
});
</script>
</body>
</html>
メソッドレスポンス
レスポンスの詳細を下記の通り設定する。
- コンテンツタイプ: text/html
- モデル: Empty
APIのデプロイと実行
メソッドの設定の「テスト」タブで、クエリ文字列を変化させながら実行して問題なさそうだったら、APIをデプロイする。デプロイ時に選択するステージは「default」とする。
「API Gateway > API > NatureRemo_View-API > ステージ」でリソースのツリーから「default > / > /graph > GET」と辿り、「URLを呼び出す」をクリックしてURLをクリップボードにコピーする。
ブラウザのタブを開いてURL「https://***.execute-api.ap-northeast-1.amazonaws.com/default/graph」を貼り付けて開いてみる。
お見事、照度の変化がグラフで表示された!
補足
API Gatewayのクエリ文字列daybackを変化させて、過去(1〜3日前)の照度変化も見られるようにしたい。取り急ぎ、統合レスポンスのマッピングテンプレートに下記のコードを追記して、グラフをクリックすることで過去を遡れるようにする。
ctx.addEventListener("click", event => {
let db = /dayback=(\d+)$/.test(window.location.search) ? parseInt(RegExp.$1, 10) : 0;
window.location.search = "?dayback=" + ++db;
}, false);
また、API Gatewayの本来の役割としてはHTMLコンテンツ生成ではなくJSONデータを返すことだと思うので、もしも別の静的HTMLからクライアントサイドJavaScriptによりAPI Gatewayを呼び出すのであれば、リソースの詳細から「CORSを有効にする」にして、GETメソッドのリクエストに対して「Access-Control-Allow-Methods」レスポンスヘッダーを設定する必要がある。
さいごに
ここまでの一連の記事は、5年ほど前にAWS(Node.js 12)で構築した機能をNode.js 20.xで再構築しつつ執筆した。あらためて、IoTデバイスとクラウドの連携やサーバレスな開発の面白さを感じた。
Nature Remo 3のセンサーで取得できる情報は、照度・温度・湿度の3つ(+人感も?)があるので、これらの情報を系列とした折れ線グラフを表示させるといった活用もできる。
Discussion