SORACOM LTE-M Buttonでお母さんに親孝行しようと思った話(元気やでボタン)

#とある休日に、何を思ったか
- SORACOMのボタン(俗に言う「あのボタン」)が発売されるねんなぁ
- 何作ろうかなぁ
- そりゃ作るのはできるけどさ、せっかく作るなら使うもんがいいなぁ

## そんな時、どこからともなくお母さんからLINEが
<img src="https://qiita-image-store.s3.amazonaws.com/0/307347/3f8cd91d-d3a4-5fc2-119d-c74ade74d217.jpeg" width=50%>

- それに対し、息子はこう返す。　  
<img src="https://qiita-image-store.s3.amazonaws.com/0/307347/769d5054-d60c-f81f-98b4-2d5392466171.jpeg" width=50%>
- そういえば、そろそろ**お母さんの誕生日**やねんなぁ…

# 息子の悩み
実家の大阪、自分の住む関東。
**元気やけど、それを言うのはなんか気恥ずかしい。**
**でもお母さんから来る「元気か？」「元気やろ？」はちょっと嬉しい。**
お母さんに心配はかけたくない。

# そうだ、これだ
**いつでもどこでもお母さんに「元気やで」って言えればメッチャ良くね？**
…お母さんの誕生日に贈ることにしました。あのボタン。

<img src="https://qiita-image-store.s3.amazonaws.com/0/307347/d013c312-8882-b153-641c-8bf17bf1b0c5.jpeg" width=２０％>

# どんなものを作ろうと思ったか
- **ボタンを押すと、LINEに通知が届く**
- ボタンの役割によって届くメッセージが異なる
- どうせだったら汎用的に使えるものが欲しい
- **名付けて**

![fudechar_jibyt.png](https://qiita-image-store.s3.amazonaws.com/0/307347/69155ae9-50d3-ee36-3f88-b7e7f8c3dd7d.png)

#仕組み
こんな感じです。
1. AWS IoT 1-Clickで、ボタンにLambdaの関数を割り当て
2. ボタンの属性に、それぞれのクリックに対応するメッセージを仕込む
3. ボタンの押された種別に応じて、LambdaからLINE Notifyにメッセージを発行
<img src="https://qiita-image-store.s3.amazonaws.com/0/307347/6702202d-928a-d113-21f6-6887e5cbf282.png">

#AWS IoT 1-Clickの設定について
SORACOMのMaxさんが分かりやすく書いてくれてます。
[SORACOM LTE-M Button powered by AWS を用いた開発 TIPS - SORACOM Blog](https://blog.soracom.jp/blog/2018/11/07/tips-of-soracom-lte-m-button/)

これを基にして、今回のプロジェクトで設定した内容を図解したので参考になれば。
<img src="https://qiita-image-store.s3.amazonaws.com/0/307347/63885b37-b970-4a2d-7fdd-2207101ac46d.png">

#Lambda側の設定
##の前に、気をつけたいこと
- **Lambdaのプロジェクトを作るときに、Python3.6を使う**  
（いや別になんでもいいけど笑）
 - 参考：[【API Gateway】AWS Lambda統合のPythonでHello, world - Qiita](https://qiita.com/baikichiz/items/2de7c4c0dcf9b051037a)
- **LambdaのPythonにはデフォルトでrequestsモジュールが入っていない**
 - 一旦ローカルにpipでモジュールをダウンロード
 - そこに下記のlambda_function.pyと一緒にzipで固めてアップロードする必要がある
 - 参考：[【AWS】Lambdaでpipしたいと思ったときにすべきこと - Qiita](https://qiita.com/Hironsan/items/0eb5578f3321c72637b4)
- **zipでアップロードしたらさっきまでLambdaのWebで書いてたコードは上書きされるぞ**
 - これで泣いたぞ、1回

##コードで工夫した点
- 例外処理ちゃんと書いて、CloudWatchで出力されるログに、エラーが出るようにした
- LINE Notifyのトークンが設定されていれば、そのログをLINE側に通知するようにした
- LINE Notifyのトークンをコード内に設定できるようにした（環境変数にしたら良かった…）

##実際のコード
messageのところは、適宜書き換えて好きなフォーマットで出力してください。

```python:lambda_function.py
# -*- coding:utf-8 -*-

import json
import requests
import datetime as dt

# LINE NotifyのURLとデフォルトの通知先トークン(これよりもボタン側に設定したトークンが優先される)
LINE_NOTIFY_URL = "https://notify-api.line.me/api/notify"
LINE_NOTIFY_TOKEN = ""

# UTCとの時差(時間)
UTC = 9


def lambda_handler(event, context):
    global LINE_NOTIFY_TOKEN
    print("Received event: " + json.dumps(event))

    # デバイス情報(デバッグ用)
    deviceInfo = event['deviceInfo']
    deviceId = deviceInfo['deviceId']
    deviceAttributes = deviceInfo['attributes']

    # プロジェクト名、プレイスメント名(デバッグ用)
    projectName = deviceAttributes['projectName']
    placementName = deviceAttributes['placementName']

    # クリック種別とクリック時間の取得
    buttonClicked = event['deviceEvent']['buttonClicked']
    clickType = buttonClicked['clickType']
    reportedTime = buttonClicked['reportedTime']

    # AWSデフォルト表記：2018-11-09T12:37:10.614Z
    input_datetime_format = '%Y-%m-%dT%H:%M:%S.%fZ'
    # 出力表記：2018/11/09 12:37:10
    output_datetime_format = '%Y/%m/%d %H:%M:%S'

    # datetimeオブジェクト作成
    datetime_obj = dt.datetime.strptime(reportedTime, input_datetime_format)
    # 時差補正
    td = dt.timedelta(hours=UTC)
    # 日時フォーマット整形
    reportedTime = (datetime_obj + td).strftime(output_datetime_format)

    # 属性情報の取得
    attributes = event['placementInfo']['attributes']

    try:
        # トークンが設定されていれば
        if 'LINE_NOTIFY_TOKEN' in attributes:
            LINE_NOTIFY_TOKEN = attributes['LINE_NOTIFY_TOKEN']
        elif LINE_NOTIFY_TOKEN == "":
            raise AttributeError("LINE Notifyのトークンが見つかりません。")

        # ボタン名が設定されていれば
        if 'BUTTON_NAME' in attributes:
            BUTTON_NAME = attributes['BUTTON_NAME']
        else:
            raise AttributeError("ボタン名が見つかりません。")

        # クリック種別に応じた処理
        if clickType == "SINGLE":
            if 'SINGLE_MESSAGE' in attributes:
                message = attributes['SINGLE_MESSAGE']
            else:
                raise AttributeError("シングルクリック時のメッセージが定義されていません。")

        elif clickType == "DOUBLE":
            if 'DOUBLE_MESSAGE' in attributes:
                message = attributes['DOUBLE_MESSAGE']
            else:
                raise AttributeError("ダブルクリック時のメッセージが定義されていません。")

        elif clickType == "LONG":
            if 'LONG_MESSAGE' in attributes:
                message = attributes['LONG_MESSAGE']
            else:
                raise AttributeError("ロングクリック時のメッセージが定義されていません。")

        # メッセージ生成
        message = "\n"+BUTTON_NAME+"が「"+message+"」と言っています。\n" \
            + "時刻:"+reportedTime
            # + "時刻:"+reportedTime+"\n" \
            # + "ボタン名:"+placementName+"\n" \
            # + "シリアル:"+deviceId

        # LINE送信処理
        send_to_line(message)

    except AttributeError as e:
        error_message = "\nプロジェクト名["+projectName+"]の端末["+placementName+"("+deviceId+")]でエラーが発生しました："+e.args[0]+"\n" \
            + "時刻:"+reportedTime

        print(error_message)

        # LINE Notifyのトークンが設定されていれば、エラーメッセージを通知
        if 'LINE_NOTIFY_TOKEN' in attributes:
            send_to_line(error_message)


def send_to_line(message):
    headers = {"Authorization": "Bearer " + LINE_NOTIFY_TOKEN}
    payload = {"message":  message}

    r = requests.post(LINE_NOTIFY_URL, headers=headers, params=payload)
    print(r.text)
```

##テストイベント用のJSON
**これをLambdaのテストイベントの箇所に貼り付けてテストしたら、LINEに通知されます。**
(トークンだけ書き換えてね)

```json:test_event.json
{
    "deviceInfo": {
        "deviceId": "1AB2CDEFGHI3456",
        "type": "button",
        "remainingLife": 98.4,
        "attributes": {
            "projectRegion": "ap-northeast-1",
            "projectName": "howareyou_mom",
            "placementName": "button_01",
            "deviceTemplateName": "notifyToLINE"
        }
    },
    "deviceEvent": {
        "buttonClicked": {
            "clickType": "SINGLE",
            "reportedTime": "2018-11-09T16:29:40.474Z"
        }
    },
    "placementInfo": {
        "projectName": "howareyou_mom",
        "placementName": "button_01",
        "attributes": {
            "SINGLE_MESSAGE": "元気です。",
            "DOUBLE_MESSAGE": "元気してる？",
            "LINE_NOTIFY_TOKEN": "(LINE Notifyトークン)",
            "LONG_MESSAGE2": "連絡ちょうだい！",
            "BUTTON_NAME": "息子"
        },
        "devices": {
            "notifyToLINE": "1AB2CDEFGHI3456"
        }
    }
}
```

#実際の画面
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">できたー！オカンがもうすぐ誕生日やから、このボタン渡して親孝行するんだ！日本初の「オカンにソラコムボタン渡した息子」になりたい…！<br><br>AWS IoT 1-Click側の設定だけでLambdaには手を加えなくていい親切設計です。メッチャ汎用的。(近日Qiitaに書きます)<a href="https://twitter.com/hashtag/SORACOM?src=hash&amp;ref_src=twsrc%5Etfw">#SORACOM</a> <a href="https://twitter.com/hashtag/%E3%81%82%E3%81%AE%E3%83%9C%E3%82%BF%E3%83%B3?src=hash&amp;ref_src=twsrc%5Etfw">#あのボタン</a> <a href="https://twitter.com/hashtag/AWS?src=hash&amp;ref_src=twsrc%5Etfw">#AWS</a> <a href="https://twitter.com/hashtag/IoT?src=hash&amp;ref_src=twsrc%5Etfw">#IoT</a> <a href="https://t.co/lqdj3xm0Uj">pic.twitter.com/lqdj3xm0Uj</a></p>&mdash; itox(dev) (@itoxdev) <a href="https://twitter.com/itoxdev/status/1060934506851401728?ref_src=twsrc%5Etfw">2018年11月9日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

**やりとりのイメージ**
<img src="https://qiita-image-store.s3.amazonaws.com/0/307347/cd7be3ca-3a19-6f0f-1d3a-31d1bc855ec6.png" width="375">

**エラー出力**
<img src="https://qiita-image-store.s3.amazonaws.com/0/307347/77a3af94-edde-b1e4-7e4e-cfce1923e22d.jpeg" width="299">

#作ってみた感想
- **実は今回AWSというかLambda初めて触ったけどメッチャ面白い**
 - アカウント作って1週間くらい(いや、ずっとやりたいとは思ってたのよ)
- **お母さんに早く渡したい**
 - お母さんと電話して頭出しは済んでる
 - お母さん「**それよりあんた、誕生日プレゼントにひこにゃんのLINEスタンプ欲しいわ**」 
 - **まさかのスタンプおねだりかよ**
- **え、VSCodeメッチャ便利…（これまでメモ帳で頑張ってた）**
- **PythonいいよPython、モジュール使いやすいぜ**
- **よく考えたら「元気やでボタン」やのに本文中は「元気です」とか言ってるごめん**
- **IoTって技術ありきじゃなくて、アイデア勝負やんな、やっぱ**
- **IoTはきっと人を幸せにできる**

#今後考えてること
- DynamoDBに入れたいなぁ
- もうちょい可視化したいなぁ
- LINEじゃなくてAlexaに出力しても面白いなぁ
- Wio + モータードライバ or リレーとか使って、元気ならぬいぐるみ動かしても面白いなぁ

以上