LTE-M Button で M5Stack Avatar とメールに「今から帰るよ」通知を送る

 この記事は「SORACOM LTE-M Button powered by AWS Advent Calendar 2018」の 12月4日(火)の記事になります。

 LTE-M Button で何を作ろうかと考えたのですが、今年から娘が中学生になり、徒歩30分ぐらいかかる学校に通い始めたので、ボタンを押したら今から帰るよメールが送られるようにしたら便利かなと考えました。ですがそれだけだと他にも多くの方がすでにやられているので、今回はさらに M5Stack と連携して Avatar にも通知が表示されるようにしてみました。

f:id:akanuma-hiroaki:20181204210313p:plain

動作の様子

 今回はボタンの3種類の操作に合わせて下記のようにメールとアバターで通知します。日本語表示は面倒だったので今回は英語表示のみです。

  • シングル: I've Arrived At The Venue.(目的地に着いたよ)

  • ダブル:I'm Going Home.(今から帰るよ)

  • ロング:I Left My Key At Home.(家に鍵忘れた)

 上記の内容がメールでも送られつつ、アバターが喋ってる風にスクロール表示します。アバターの動作の様子は下記動画をご覧ください。動画だと吹き出し部分が白いだけのように見えてしまっていますが、実際は上記のテキストがスクロール表示されています。

 メールは下記のような感じで送られてきます。一応子どもの名前はマスクしてあります。

f:id:akanuma-hiroaki:20181204125253p:plain:w400

f:id:akanuma-hiroaki:20181204125312p:plain:w400

f:id:akanuma-hiroaki:20181204125329p:plain:w400

システム構成

 かなり適当な図ですが、今回関連するコンポーネントの構成は下記のような形になります。

 LTE-M Button をクリックすると AWS IoT 1-Click を経由して Lambda 関数にリクエストが渡されます。 Lambda 関数では AWS IoT の Device Shadow をアップデートします。それによって AWS IoT Rule が発火して Amazon SNS にメール送信リクエストが投げられ、登録してあるアドレスにメールが送信されます。

 M5Stack は SORACOM Air を挿した SIM ルータに Wi-Fi 接続しておき、 SORACOM Beam 経由で AWS IoT の delta トピックに MQTT で Subscribe しておきます。 LTE-M Button 側の処理で Device Shadow が更新されると delta トピックにメッセージが Publish されるので、それに応じて M5Stack 側でアバターの表示の変更等を行うという構成です。

 本当は M5Stack を直接 MQTTS で AWS IoT に Subscribe したかったのですが、現在の MicroPython の実装だと X.509 の証明書を使用した TSL 認証に対応していないようで、色々試したのですが接続できませんでした。AWS 署名バージョン 4 での REST API も試したのですが、成功ステータス(200)は返って来るものの、本文を取得しようとするとうまくいかないという感じで、最終的には SORACOM Beam 経由でなんとか接続したという形です。

f:id:akanuma-hiroaki:20181204204404p:plain

Lambda 実装

 LTE-M Button の AWS IoT への登録は今回は割愛します。以前記事にも書いてますのでよろしければご参照ください。

blog.akanumahiroaki.com

 今回 Lambda 関数は Python3.7 ランタイムで下記のように作成しています。

f:id:akanuma-hiroaki:20181204124126p:plain

 LTE-M Button が押された時に送られてくるリクエストは下記のような json になります。下記はシングルクリックの例で、クリックの種類によって deviceEvent.buttonClicked.clickType の値が SINGLE DOUBLE LONG と変化します。

{
    "deviceInfo": {
        "deviceId": "7MF6JKC6XXXXXXXX",
        "type": "button",
        "remainingLife": 94.19284,
        "attributes": {
            "projectRegion": "ap-northeast-1",
            "projectName": "sendGoHomeNotice",
            "placementName": "XXXXXX",
            "deviceTemplateName": "SendGoHomeNotice"
        }
    },
    "deviceEvent": {
        "buttonClicked": {
            "clickType": "SINGLE",
            "reportedTime": "2018-11-25T13:15:55.696Z"
        }
    },
    "placementInfo": {
        "projectName": "sendGoHomeNotice",
        "placementName": "XXXXXX",
        "attributes": {
            "name": "XXXXXX"
        },
        "devices": {
            "SendGoHomeNotice": "7MF6JKC6XXXXXXXX"
        }
    }
}

 今回 Lambda 関数でやることは、クリック種別に応じて AWS IoT の Device Shadow のステータスを変更することです。 Lambda から Device Shadow を変更するには IoTDataPlane API の update_thing_shadow() メソッドを使用します。ドキュメントは下記リンク先にあります。

IoTDataPlane — Boto 3 Docs 1.9.57 documentation

 Lambda の実装の全体は下記のようになります。 LTE-M Button はレスポンスは受け取らないので適当な文字列を返しています。また、 Device Shadow のステータスには desired と reported がありますが、今回は desired を変更しています。

import json
import logging

import boto3

logger = logging.getLogger()
logger.setLevel(logging.INFO)

iot_client = boto3.client('iot-data')

def lambda_handler(event, context):
    logger.info('Received event: {}'.format(json.dumps(event)))
    name = event['placementInfo']['attributes']['name']
    click_type = event['deviceEvent']['buttonClicked']['clickType']
    
    if click_type == 'SINGLE':
        status = "I've Arrived At The Venue."
    elif click_type == 'DOUBLE':
        status = "I'm Going Home."
    elif click_type == 'LONG':
        status = 'I Left My Key At Home.'
    else:
        status = 'Unknown'
    
    shadow = { 'state': { 'desired': { 'status': status }}}
    
    response = iot_client.update_thing_shadow(
        thingName = 'GoHomeNoticeButton{}'.format(name),
        payload = json.dumps(shadow)
    )
    
    return {
        'statusCode': 200,
        'body': "{}: {}".format(name, click_type)
    }

 Device Shadow の初期状態は今回は手を抜いてコンソールから下記のように設定しておきます。

f:id:akanuma-hiroaki:20181204152859p:plain

AWS IoT ルール

 AWS IoT では Device Shadow のステータスが変更された時に Amazon SNS からメールが送信されるようにルールを設定します。 Device Shadow のステータスの desired と reported に差分が発生すると、 MQTT の $aws/things/<THING_NAME>/shadow/update/delta トピックに差分が publish されるので、そこからステータス情報を抜き出してメール送信用文言を生成します。

f:id:akanuma-hiroaki:20181204123912p:plain

 アクションの内容は下記のようにしています。詳細説明は割愛しますが Amazon SNS で作成したトピックには受信したいメールアドレスを Subscribe してアドレスの確認まで済ませておきます。

f:id:akanuma-hiroaki:20181204124035p:plain

SORACOM Beam 設定

 今回は M5Stack と AWS IoT の接続は SORACOM Beam を介して行いますので、 SORACOM User Console から該当の SIM グループで SORACOM Beam の設定を行っておきます。詳細は下記サイトで紹介されていますのでご参照ください。

dev.soracom.io

 設定が終わると下記のように MQTT 接続のエンドポイントが作成されます。

f:id:akanuma-hiroaki:20181204172744p:plain

M5Stack ファームウェア実装

 ここまででボタンを押したらメールが送信されるところまではできたので、最後に M5Stack 側の実装を行います。 M5Stack でのアバター表示には、私が以前作成した MicroPython 版の M5StackAvatar を使用します。使い方等は下記記事で紹介していますのでよろしければご参照ください。

blog.akanumahiroaki.com

 実装としては下記のようになります。やっていることとしては、 MQTT のクライアントインスタンス生成時に MQTT サーバへの接続時、切断時、データ取得時のコールバックメソッドを指定して delta トピックに Subscribe し、データ取得時のコールバックメソッドの中で Device Shadow のステータスの内容によってアバターの表示を切り替えているというものになります。 M5Stack のボタンがどれか押されたら、 Device Shadow のステータスとアバターの表示を元に戻しています。 SORACOM Beam を介しているので MQTT 接続は TSL 版ではなく通常の MQTT 接続になっています。ちなみに私のケースでは MQTT クライアントのインスタンス生成時のオプションで cleansession = True を指定しないと接続時にエラーになってしまいました。

from m5stack import buttonA, buttonB, buttonC
from m5stack_avatar import M5StackAvatar

import machine
import network
import time
import ujson

MQTT_SERVER = 'beam.soracom.io'
THING_NAME  = 'GoHomeNoticeButtonXXXXXX'
UPDATE_TOPIC = '$aws/things/{}/shadow/update'.format(THING_NAME)
DELTA_TOPIC = '$aws/things/{}/shadow/update/delta'.format(THING_NAME)
CLIENT_NAME = 'GoHomeAvatar'

avatar = M5StackAvatar()
avatar.start()

def connect_callback(task):
    print('MQTT Server [{}] Connected'.format(task))

def disconnect_callback(task):
    print('MQTT Server [{}] Disconnected'.format(task))

def data_callback(data):
    status = ujson.loads(data[2])['state']['status']
    print('Delta Status: {}'.format(status))

    if status == 'I Left My Key At Home.':
        avatar.pale_on()
    else:
        avatar.exclamation_on()
        
    avatar.speak(status)

def reset_avatar():
    shadow = { 'state': { 'desired': { 'status': "I'm Staying." }}}
    mqtt.publish(UPDATE_TOPIC, ujson.dumps(shadow))
    avatar.exclamation_off()
    avatar.pale_off()

mqtt = network.mqtt(
    CLIENT_NAME,
    MQTT_SERVER,
    autoreconnect   = True,
    cleansession    = True,
    connected_cb    = connect_callback,
    disconnected_cb = disconnect_callback,
    data_cb         = data_callback
)

mqtt.start()
tmo = 0
while mqtt.status()[0] != 2:
    time.sleep_ms(100)
    tmo += 1
    if tmo > 80:
        print("Not connected")
        break
    
print("Subscribing to the delta topic.")
mqtt.subscribe(DELTA_TOPIC)

while True:
    if buttonA.isPressed() or buttonB.isPressed() or buttonC.isPressed():
        reset_avatar()
    time.sleep_ms(10)

 これを M5Stack にフラッシュすれば一通りの実装は終了なので、 LTE-M Button をクリックすると冒頭の動画のように、 M5Stack のアバターとメールで通知が行われます。

まとめ

 今回 M5Stack を AWS IoT に接続するまでにかなり苦戦しましたが、 LTE-M Button から Lambda 関数をコールして Device Shadow を更新するまではあまり苦労せずにいけてしまいました。 Wi-Fi 接続と違って外でもネットワークの心配をする必要なく使えるというのもやはりとても良いですね。 SORACOM Beam のおかげで M5Stack を AWS IoT に接続することもできましたし、 ソラコムさんのサービスはうまく活用していきたいですね。

 かなり説明を省略してしまってるところも多いので、もし気になる点等ありましたらコメント欄などでご質問ください。

 ちなみに LTE-M Button 特化でないソラコムさんのアドベントカレンダーはこちらです。

SORACOM Advent Calendar 2018