Nature Remo Cloud API + Lambdaで温度を記録して折れ線グラフを表示する自分ち専用のAndroidアプリを作った

Nature Remo Cloud API + Lambdaで温度を記録して折れ線グラフを表示する自分ち専用のAndroidアプリを作りました。
# 経緯

 - 我が家ではNature Remoというスマートリモコンを使っており、Alexaやスマホアプリ経由でいろんな家電を操作できるようにしている
 - そのNature Remoでは[Nature Remo Cloud API](https://swagger.nature.global/)というものが提供されており、HTTPで簡単に情報が取れるらしい
 - 最近エアコン暖房がついててもたいしてあったかくない気がする
 - Nature Remoに搭載されている温度センサーの値と、エアコンの稼働状況をAPIから定期的に取得し、保存して見える化すればエアコンが本当に調子が悪いのかわかるのでは？
 - 単純に室温の変化が見えたら面白そう
 - [気象庁ホームページのリニューアルでアメダスのデータも簡単に取れるようになったらしい](https://mindtech.jp/?p=1754)のでついでに外気温も取得してみよう


# つくったもの
![Untitled Diagram (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536687/7b08643c-21a3-0270-4ac1-dfa58078a6f3.jpeg)

### AWS Lambdaで動くバッチ（RemoBatch）
 - Cloud Watch Eventsで10分おきに動作させる
 - Nature Remoから室内気温、エアコンがついているorいない、動作モード（冷房か暖房か）、エアコンの設定温度を取得
 - 同時に気象庁HPのアメダスデータから外気温を取得
 - それらをDynamoDBに保存


### AWS Lambda + API Gatewayによるデータ取得API（GetRemoInfo）
 - DynamoDBに保存したデータを取得するためのAPI
 - リクエストでいつからいつまでのデータを取得したいか指定し、JSONでその期間内のデータを返す

### 折れ線グラフを表示するAndroidアプリ（RemoTemperatureViewer）
 - 上記GetRemoInfoAPIを叩き、取得したJSONから折れ線グラフを表示する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536687/0cf206a3-1191-ffb9-22b6-b85f4d0ae8a2.png)


# RemoBatch
コード：https://github.com/nishinsoba/RemoBatch
年末年始に作成。
とりあえずすぐ動くものが欲しかったので、業務で触ったことがあるC#でコーディングしました。
基本的にはひたすら外部のAPIを叩き、レスポンスのJSONを解析して、最後にDynamoDBに詰めているだけなので難しいことはしていないです。
Nature Remo Cloud APIを叩く際に必要となるトークンはLambdaの環境変数としています。

年末年始に作成してしばらく放置していたので、その間にDynamoDBにデータがたまっていったのですが、途中でNature Remo Cloud API側で仕様変更があったらしく（？）途中からエアコンを使用しているorしていないが取得できなくなっていたので修正しました。

参考ページ
 - [Nature Remo Cloud API](https://swagger.nature.global/)
 - [Nature Remoの公式APIの使い方](https://qiita.com/sohsatoh/items/b710ab3fa05e77ab2b0a)
 - [新しい気象庁サイトからJSONデータが取得できる件](https://mindtech.jp/?p=1754)
 - [AWS公式 .NETドキュメントモデル](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DotNetSDKMidLevel.html) DynamoDBへの書き込みにドキュメントモデルを使ってみました。

### 気象庁アメダスデータの取得
外気温を知るために気象庁のアメダスデータを使用しています。（我が家は神奈川県某所にあるので、神奈川県内のアメダス地点のデータを使っています）

https://www.jma.go.jp/bosai/amedas/data/map/yyyyMMddHHmm00.json
(yyyyMMddHHmmは任意）でデータを取得できるようですが、10分おきに更新されているようなので、mmは10で割り切れる数である必要があります。
つまり、
https://www.jma.go.jp/bosai/amedas/data/map/20220222222000.json (2022/2/22 22:20)ならばOKですが、
https://www.jma.go.jp/bosai/amedas/data/map/20220222222200.json (2022/2/22 22:22)だとNot Foundとなります。

また、現在時刻から近すぎるとこれまたNot Foundになるようなので、RemoBatchでは現在時刻より15分以上前のデータを取得するようにしています。（そのため外気温のデータはリアルタイムではないですが、目安として使えればいいやということで）


```csharp

//現在時刻(JST)
var jstZoneInfo = TZConvert.GetTimeZoneInfo("Tokyo Standard Time");
DateTime jst = TimeZoneInfo.ConvertTime(DateTimeOffset.Now, jstZoneInfo).DateTime;
string jstStr = jst.ToString("yyyy/MM/dd HH:mm:ss");

//アメダスデータ
//現在気温の取得には15分程度ラグがあるので、15分前の気温を取得することにする
var before15Minutesjst = jst.AddMinutes(-15);
var jstAmedasStr = before15Minutesjst.ToString("yyyyMMddHH");
var jstAmedasStrMinute = before15Minutesjst.ToString("mm").Substring(0, 1);

var amedasUrl = "https://www.jma.go.jp/bosai/amedas/data/map/" + jstAmedasStr + jstAmedasStrMinute + "000.json";

```
力技ではある。

```csharp

var amedasRequest = new HttpRequestMessage(HttpMethod.Get, amedasUrl);
var amedasResponse = httpClient.SendAsync(amedasRequest);
var amedasJson = amedasResponse.Result.Content.ReadAsStringAsync().Result;
var amedasData = JObject.Parse(@amedasJson);
//アメダス 海老名観測点のデータを抽出
ebinaAmedasData = amedasData.SelectToken("$..46091").ToObject<Amedas>();
```

我が家に一番近そうな海老名観測点のデータを取り出しています。
海老名 = 46091という番号は、[こちら](https://www.jma.go.jp/bosai/amedas/const/amedastable.json)から取得するか、[こちら（PDF）](https://www.jma.go.jp/jma/kishou/know/amedas/ame_master.pdf)の資料の「観測所番号」欄をチェックすることで確認可能です。


# DynamoDB
こんなかんじです。
プライマリーキーを日付にしてしまったのが若干使いづらくて後悔はしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536687/649ee31e-730b-dfb0-9ae0-38b270219912.png)



# GetRemoInfo
コード：https://github.com/nishinsoba/GetRemoInfo
DynamoDBの中身を検索してJSONで返すAPIです。バッチと同じくC#で書きました。
APIキーを設定しています（我が家のエアコン稼働状況とかが見れてしまうので！）

requestにクエリパラメータで取得したい期間fromとto（どちらもyyyyMMddHHmmss）を指定してGETすると、from ~ toの間の日時のレコードを検索して返却します。

DynamoDBからの読み込みには、Batchの方はドキュメントモデルを使ったのでこちらでは[オブジェクト永続性モデル](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/DotNetSDKHighLevel.html)を使ってみました。

レスポンスにそこそこ時間がかかるのが改善したいポイントです。
 - 費用が高くならないようにプロビジョンドキャパシティーユニットを低めに設定している
 - そもそもScanに時間がかかる（DB設計の問題？）
 - Lambdaのコールドスタートにも時間がかかる
のが原因かなと思っています。

以下はレスポンスの一例です。
取得期間中の外気温と室温の平均値も返すようにしてみました。

```json:response
{
    "result_message": "SUCCESS",
    "remo_data": [
        {
            "datetime": "2022/02/22 10:10:15",
            "is_using_aircon": 0,
            "outdoor_temperature": 7.3,
            "room_temperature": 17,
            "aircon_mode": "warm",
            "aircon_temperature": 20
        },
        {
            "datetime": "2022/02/22 10:20:15",
            "is_using_aircon": 0,
            "outdoor_temperature": 7.6,
            "room_temperature": 17,
            "aircon_mode": "warm",
            "aircon_temperature": 20
        }
    ],
    "start_date_time": "2022/02/22 10:10:15",
    "end_date_time": "2022/02/22 10:20:15",
    "average_room_temperature": "17",
    "average_outdoor_temperature": "7.449999999999999"
}

```

# RemoTemperatureViewer
コード：https://github.com/nishinsoba/RemoTemperatureViewer

上記GetRemoInfo APIで取得したJSONデータを折れ線グラフで表示するAndroidアプリです。
別にAndroidでなくてもよいのですが、最近触れていないので復習と、MVVM + LiveDataの勉強がしたかったのでAndroidにしました。


 - StartFragmentでユーザにfrom（データ取得期間の開始）とto（取得期間の終わり）を入力してもらう
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536687/975036ef-8296-42ab-6d04-54266b1a0b9a.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536687/e7d55eb4-3404-a887-dd0a-5234d8f1d984.png)

 - FirstFragmentで入力された日付・時間情報をRemoTemperatureViewModelが保持
 - OKボタンが押されたらRemoTemperatureViewModelがAPIを叩く
 - APIからレスポンスが帰ってきたら、折れ線グラフ描画の準備をする
 - GraphFragmentが折れ線グラフを表示する
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536687/979677ba-09b0-9fc6-678d-1205fa9e93d3.png)
 - グラフを表示は横画面にも対応 （※エアコンを使用していないときは緑の線が0℃になる）
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536687/86191a48-b0a4-a513-b592-bddd921d2b32.png)



というのが基本的な流れになります。

### 工夫したところ⓵
誰からでもAPIを叩けてしまうと困るので、GetRemoInfoにAPIキーを設定しています。そのためこのアプリから叩くときもAPIキーが必要となるので、初回起動時にユーザに入力してもらうようにしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536687/a547bd58-2001-a9a2-949c-9e1cdd697c1b.png)
内部的にはSharedPreferencesとして入力してもらったトークンを保存しています。

```kotlin:FirstFragment

    /**
     * GetRemoInfoを叩くためのトークンを持っているかチェックし、持っていなければ入力ダイアログを表示する
     */
    private fun checkTokenExist(): String?{
        val sharedPref = requireActivity().getSharedPreferences("remo_viewer_setting",Context.MODE_PRIVATE)
        var token = sharedPref.getString("token",null)
        if (token == null){
            // ダイアログを二重表示しない
            if( dialog != null && dialog!!.isShowing() ) {
                return null
            }
            // トークン入力ダイアログを表示する
            val myEdit = EditText(activity)
            dialog = AlertDialog.Builder(requireActivity())
                .setTitle("トークンを入力してください")
                .setView(myEdit)
                .setPositiveButton("OK", DialogInterface.OnClickListener { _, _ ->
                    // OKボタン押したときの処理
                    val inputText = myEdit.getText().toString()
                    if (!inputText.isEmpty()) {
                        sharedPref.edit().putString("token", inputText).apply()
                        token = inputText
                    }
                })
                .show()
        }
        return token
    }

```
<br><br>



### 工夫したところ②
また、最初の画面で日付と時刻を入力するのがめんどくさいので、よく使われるであろう「直近24時間」と「直近1週間」の設定を一発でできるボタンを設置しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536687/2707f113-9c91-6528-7619-d70f10b3d8f4.png)




### 感想
せっかくMVVMで作ってみたのにFragment内も結構ごちゃごちゃしてしまったのが今後直したい…という感じです。
折れ線グラフの表示は[こちらの記事](https://qiita.com/c60evaporator/items/14e63d22d860b73e6f22)が大変わかりやすく、必要な情報もすべて網羅されていたので大変助かりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536687/f37a1cee-1ab5-5d94-76fa-5edc67385501.png)
外気温と室温の連動がわかるような、わからないような…
料理をする夜の時間帯が室温のピークだということがわかります。



# 結果
 - 初めて個人でがっつり何かを作ったので楽しかった
 - AndroidのMVVM,LiveDataが理解できた(と思う)
 - 結局エアコンの暖房の効きが悪いどうかはわからない
 - Qiitaに初投稿してみてドキドキです