SORACOM LTE-M Button と AWS IoT Core の ThingShadow で「呼び出しシステム」

# 展示会で困る事

それは展示会場で担当者が不在だった時ですよね。呼び出したいけどどうすれば？という問題を「[あのボタン](https://blog.soracom.jp/blog/2018/11/01/shipping-lte-m-button/)」で解決しようとした試みです。

## こんな感じ

[JAWS FESTA 2018 OSAKA](https://jft2018.jaws-ug.jp/) でお披露目しました。
こんな感じのSPA (Single Page Application) です。

あのボタン「SORACOM LTE-M Button」を押すと、担当者に Slack のメンションが飛びます。
また、担当者が「OK ...」と Slack 上でメッセージ送信すると「現場急行中！」となる仕組みです。

![web.png](https://qiita-image-store.s3.amazonaws.com/0/22754/1192db5c-bd59-0801-53c1-79532126bc42.png)

名前のとなりに状況が表示されてますね。

# 実装

考え方ですが、担当者を "モノ" として扱い、そのステータス (= Shadow) を MQTT over Websocket でブラウザに表示するという仕組みです。

<img src="https://docs.google.com/drawings/d/e/2PACX-1vSOoQxCFbYVt4VvA-fwSXrG05mzpEX_2NwQ86IgNlakdb5v4j3nKpB8frPydggjX1BEaDPSLbt27fXU/pub?w=1126&amp;h=522" alt="jaws-festa-2018 / architecture">

各担当者は、このように「モノ」扱いです。もちろんソラコムはそういう会社じゃないですよ！便宜上、やむを得ず、しかたなく、断腸の思いでこのような実装になってるだけです！

![aws-iot-core-things.png](https://qiita-image-store.s3.amazonaws.com/0/22754/983ccba1-dd57-edd7-db03-c1505684f672.png)

Shadow はこの通りです。

![shadow.png](https://qiita-image-store.s3.amazonaws.com/0/22754/f9571466-c6f4-479d-d98d-71b8be4ba58b.png)

## アーキテクチャ

大きく2系統あります。

### １系統目: SORACOM LTE-M Button から Slack へ POST

[SORACOM LTE-M Button (= AWS IoT 1-Click) から起動される関数(python3.6)](https://gist.github.com/ma2shita/e522875dbd7a794780adb34b172b892f)

Slack の Incoming Webhook を利用しています。
メンションを飛ばす方法は [SlackのIncoming Webhooksでメンションを飛ばす方法](https://qiita.com/ryo-yamaoka/items/7677ee4486cf395ce9bc) に助けていただきました。

同時に Shadow を「呼び出し中... `(state=calling)`」に update します。これは Websocket を通じてブラウザにリアルタイムでpushされます。

### ２系統目: Slack から AWS IoT Core の Shadow を更新

[Slack から API Gateway を経由して AWS IoT Core の ThingShadow を更新する関数](https://gist.github.com/ma2shita/4c49cf93034ce2b43c46068b3f475a28)

Slack の Outgoing Webhook を利用しています。
トリガーキーワードに "ok" を指定し、担当者は `ok ...` と入れてもらうようにしました。
受けは API Gateway です。Slack からは `Content-Type:application/x-www-form-urlencoded` で送信されるので、本来は API Gateway 上で JSON 化したかったのですが、私には難しかったので Lambda proxy としてそのまま Lambda 関数に引き渡し、その関数内(Python3.6)上で `urllib.parse.parse_qs(event['body'])` としてパースする軟弱実装です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/22754/1107cae3-d735-39fd-b6ca-c0d7e07a54dc.png)

その後 Shadow を「現場急行中！ `(state=running)`」に update します。先ほど同様、ブラウザにリアルタイム push されます。

### 共通してやってること

[一定時間経過したら `state` を `idle` に更新する関数(python3.6)](https://gist.github.com/ma2shita/f4973cc64e28fd24234450139c7fa8f6)

Shadow が「現場急行中！」や「呼び出し中...」のままだと格好悪いので、一定期間で「呼び出しＯＫ！ `(state=idle)`」に戻してあげたかったので、Lambda 関数を作っています。

各 Lambda 関数から呼び出される形のコードです。呼び出し方は後述の TIPS で紹介します。
実装は `time.sleep(sec)` を使いました。
→ [11/7] Amazon SQS を使ったプロセス間通信という方法を教えていただきました。今度はそれでやってみよう。

#### Lambda 関数から Lambda 関数を呼ぶ

Amazon SQS を使った Lambda 関数間の通信の方が良さそうですが、こういう方法もあるよという話です。

```python
import boto3

f = boto3.client("lambda")
f.invoke(FunctionName=...,
  InvocationType='Event',
  Payload=json.dumps({'key1': 'value1'})
)
```

InvocationType については [AWS LambdaからLambda呼んでハマった話。 / Qiita](https://qiita.com/shinichi-takahashi/items/04287757c5944818ffe5) が参考になりました。
結論 `'Event'` が非同期呼び出し `'RequestResponse'` が同期呼び出しです。

### MQTT over Websocket による ThingShadow のリアルタイム表示

[AWS IoT Core の ThingShadow を MQTT Subscribe して Push ベースで状態を表示する HTML](https://gist.github.com/ma2shita/4a43d39a8d61f6be8f1163050498e498)

![web.png](https://qiita-image-store.s3.amazonaws.com/0/22754/1192db5c-bd59-0801-53c1-79532126bc42.png)

この画面は [Vue.js](https://jp.vuejs.org/index.html) と [UIKit](https://getuikit.com/) です。

AWS IoT Core の ThingShadow へのアクセスは AWS Cognito の "認証されていない ID に対してアクセスを有効にする" (Unauth) を利用しています。

* AWS Cognito のリージョンは AWS IoT Core のリージョンと合わせておきましょう。
* Unauth のロールへ "AWSIoTDataAccess" ポリシーを割り当てましょう。

AWS IoT Device SDK から Websocket で接続する方法がわからなかったので、とりあえず [Paho](https://www.eclipse.org/paho/) の MQTT over Websocket で `$aws/things/...` を Subscribe するようにしています。 

#### AWS IoT Core に対する `ping`

Websocket を使った SPA アプリケーションの起動時に AWS IoT Core のトピックに対して ping を送るようにしました。
これは Shadow である `{"state":{"reported":{...}}}` を読み出すのに、ともかく更新が発生する ping を _publish_ をすることで、`state` 全体が _subscribe_ に振ってくるようにしたものです。

これをやらないと、SPA アプリ起動時に各モノの状態がわからないという状態になります。
が、今から思えば AWS IoT Device SDK の `awsIot.thingShadow#get()` を使えばよかったんですよね。

# コードを公開します。

* Lambda 関数
    * [SORACOM LTE-M Button (= AWS IoT 1-Click) から起動される関数(python3.6)](https://gist.github.com/ma2shita/e522875dbd7a794780adb34b172b892f)
    * [Slack から API Gateway を経由して AWS IoT Core の ThingShadow を更新する関数](https://gist.github.com/ma2shita/4c49cf93034ce2b43c46068b3f475a28)
    * [一定時間経過したら `state` を `idle` に更新する関数(python3.6)](https://gist.github.com/ma2shita/f4973cc64e28fd24234450139c7fa8f6)
* Web ページ (SPA; Single Page Application)
    * [AWS IoT Core の ThingShadow を MQTT Subscribe して Push ベースで状態を表示する HTML](https://gist.github.com/ma2shita/4a43d39a8d61f6be8f1163050498e498)

# 改善ポイント

* 呼び出し不可 (do not disturb) モードを作っても良かったかな

# あとがき

~~さっきまで作ってた。~~
~~そのうちコードは公開します。~~

やっと全部書いたよ。

EoT