家に着いたらリマインドしてもらう

今回は、Androidスマートフォンで、家に着いたら通知でリマインドしてくれるようにします。
例えば、会社にいるときに、次会社来るときに家から忘れずに持ってこようとして、忘れてしまうことがありますよね。
その時は覚えていても、家に着くとうっかり気づかずに次の日に普通に会社に行ってしまったりします。
そこで、会社にいるときに、家に着いたら教えてほしいメモを登録しておき、家に着いたらスマホ通知で知らせてくれるようにします。
私はスマホの通知をXiaomiのスマートウォッチにしているので、ぶるってくれるの気づけます。

家に着いたことを認識するのに、Pingを使いました。
LINEビーコンを使う方法もありますが、もう少し正確に判別したかったため、採用しませんでした。

ソースコードもろもろは以下にあります。

https://github.com/poruruba/ReminderNotification

## Androidに通知する

Androidの通知をするには、サーバを立ち上げておく必要があります。また、ネイティブアプリケーションを作成し、Androidスマートフォンにインストールしておく必要があります。
逆にそれができれば、いつでも好きな時にAndroidの画面をOffにした状態でも、通知を受け取ることができます。

通知にはいくつかの方法がありますが、以下の2種類を示しておきます。

①自作の通知アプリを実装する。

詳細はこちらを参照ください。以下の投稿でセットアップされていることを前提としています。

[スマートウォッチをリマインダ代わりにバイブレーションさせる](https://qiita.com/poruruba/items/f172dd99cf497761ce18)

②LINE Notifyを使う

LINEを使っている場合は、LINE Notifyを使ってLINE通知として通知することができます。
一番大きいのはネイティブアプリを実装する必要がないことです。

詳細はこちらを参照ください。

[ESP32からLINEメッセージとSlackメッセージを送信する](https://qiita.com/poruruba/items/cda9f09ff64ed1a1a8be)

## Androidの通知をスマートウォッチでバイブレーションする

スマートウォッチとして、Xiaomi Smart Band 3を使っています。
他のスマートウォッチでも同様の機能を提供しているとか思います。

どのスマートウォッチも、スマートフォンの着信を知らせてくれる機能がついていると思います。それは、各スマートウォッチに対応したAndroidアプリケーションが仲介しているためで、同じように通知もスマートウォッチで知らせてくれます。

Xiaomiの場合は、アプリ通知 という名前で機能が提供されていました。
設定の仕方は、Androidですでに通知を受けているアプリのうち、スマートウォッチでも通知を知らせてほしいアプリケーションを選択します。

## 帰宅を検知する

帰宅の検知には、Ping を使いました。
家に帰ると家に設置したWiFiルータに接続することが多いと思います。
WiFiルータに接続するとIPアドレスが割り当てられ、そのIPアドレスにPingが反応することで、帰宅したことを検知するわけです。
だいたいのWiFiルータは、同じMACアドレスが接続してきたら同じIPアドレスを返すことがほとんどかと思いますし、WiFiルータによっては、MACアドレスに対してIPアドレスを固定する機能もよくついています。

Node.jsからPingを呼び出すのに、以下のnpmモジュールを使わせていただきました。

https://github.com/danielzzz/node-ping

```js
const ping = require('ping');

ping.sys.probe(target, async (isAlive) =>{
    console.log("host:" + target + " is " + (isAlive ? "alive" : "dead"));
    if( !isAlive )
        return;
```

targetのところに、IPアドレスやホスト名を入れて呼び出すと、isAliveのところに結果が返ってきます。

## リマインドメモを管理する

リマインドして欲しいメモは、SQLite3のファイルに保管するようにしました。
データベースを使う方法もありますが、個人用ですしファイルのほうが手っ取り早いためです。

スキーマは以下の通りです。

```
id TEXT PRIMARY KEY,
topic TEXT,
title TEXT,
body TEXT,
updated_at INTEGER,
finished_at INTEGER,
sent_at INTEGER,
alive_at INTEGER
```

idが、メモをユニークに識別するための文字列です。UUIDを入れるようにしています。
topicは、検出したいスマホのIPアドレスまたはホスト名です。
titleとbodyが、リマインドしたいメモのタイトルと内容です。

上記をSQLite3に登録することで、あとはサーバ側で、topicに示すスマートフォンの存在をPingで確認できたらtitleとbodyの内容をスマートフォンに通知します。

updated_at は、内容を最後に変更した日時です。msecのunixtimeです。
finished_at は、通知を受け取った人が、もう通知は不要と設定した日時です。msecのunixtimeです。
sent_at は、通知を行った日時です。msecのunixtimeです。
alive_at は、最後にPingでスマホの存在を確認できた日時です。msecのunixtimeです。

## スマートフォンの検出と通知の発行

スマートフォンを検出するために、1分ごとにPingを送信するようにしました。
以下の設定にあります。

```json:cron.json
[
  {
    "handler": "cron_handler",
    "enable": true,
    "schedule": "0 * * * * *"
  }
]
```

この設定により、毎分0秒に、cron_handlerを呼び出します。以下の部分です。

```js
exports.cron_handler = async (event, context, callback) => {
```

スマートフォンの検出は以上の通りなのですが、一時的にPingが応答できなくなるなどで、頻繁に通知されても困るので、最後にPingで存在を確認してから、ABSENT_INTERVALの間はまだ検出している状態としました。

```js
const ABSENT_INTERVAL = 1000 * 60 * 60;
```

以下は、スマートフォンの存在を検出した時の処理の抜粋です。

```js
    if( !row.sent_at || now > (row.alive_at + ABSENT_INTERVAL)){
        try{
            // 通知を発行
            var result = await send_message(DEFAULT_CLIENT_ID, row.title, row.body, now);
            console.log(result);

            // send_atとalive_atを現在日時に更新
            db.all(`UPDATE '${REMINDER_TABLE_NAME}' SET sent_at = ?, alive_at = ? WHERE id = ?`, [now, now, row.id], (err) => {
                if( err ){
                    console.error(err);
                    return;
                }
            });
        }catch(error){
            console.error(error);
        }
    }else{
        try{
            // alive_atのみを現在日時に更新
            db.all(`UPDATE '${REMINDER_TABLE_NAME}' SET alive_at = ? WHERE id = ?`, [now, row.id], (err) => {
                if( err ){
                    console.error(err);
                    return;
                }
            });
        }catch(error){
            console.error(error);
        }

    }
```

## リマインド設定の管理コンソール

SPAで作成しました。
WebAPIとして以下を用意しました。すべて、JSON（POST）です。
以下のいずれも、呼び出しにはAPIキーを必要とするようにしています。

- /reminder-add-item
リマインドを登録します。immediateをtrueにして呼び出すことで、Pingによるスマートフォンの存在を確認せずに、強制的に一度通知を送ります。

- /reminder-remove-item
登録済みのリマインドを削除します。

- /reminder-list-item
登録済みのリマインドのリストを取得します。
取得の際に、完了済みのみにするかすべてを取得するか選択できるようにしています。

- /reminder-update-item
登録済みのリマインドの内容を変更します。
titleとbodyの変更に加えて、完了状態を解除したり、immediateをtrueにして強制的に一度送信するようなことも可能にしています。

ちなみに、通知は別のWebAPIとして用意したため、内部でそのWebAPIを呼び出すようにしています。その際に以下のnpmモジュールを使っています。

https://github.com/node-fetch/node-fetch/tree/v2.7.0

モジュール形式は使いたくなかったので、v2系のv2.7.0を採用しています。

## PWA化

いつでもリマインドを登録しやすいように、PWA化してアプリケーションのように扱えるようにしました。
以下を参考にしています。

[PWAを試してみよう](https://qiita.com/poruruba/items/77cbb6448d804e6e996b)

以上