MQTT で動くようになった SwitchBot を様々なサービスやデバイスから動かす

[SwitchBot を MQTT で操作できる](https://qiita.com/ma2shita/items/44c2764af896b5afe187) ようになりました。

仕様的には `{"state":{"command":"METHOD"}}` という JSON を `/devices/SwitchBot1/commands/#` というトピックに Publish できれば良いので、様々なサービスやデバイスから SwitchBot が動かせるようになるわけです。

 そこで、以下の４つから SwitchBot が動かせるようにしたので、そちらの解説。

* [SORACOM LTE-M Button powered by AWS](https://soracom.jp/products/gadgets/aws_button/)
* IFTTT
    * [Button Widget](https://ifttt.com/do_button)
    * [Amazon Echo (というか Amazon Alexa)](https://ifttt.com/amazon_alexa)
* [ReButton](https://seeedjp.github.io/ReButton/)

![](https://docs.google.com/drawings/d/e/2PACX-1vQFGcA7RyTnJkNWwIl5agMg6dQDUWl9Q-EmbtAvHsXFG--hvNH_kEDH-AIsFDkzT9BFQ87fRDmExRfa/pub?w=1066&h=477)

## SORACOM LTE-M Button powered by AWS

SORACOM の LTE-M (省電力セルラー通信) を搭載したボタンです。

![SORACOM-LTE-M_Button_01a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/942a9c26-edf7-de20-a6da-74ac4d5a43e7.png)

使用方法は [SORACOM LTE-M Button powered by AWS をクリックしてSlackに通知する](https://dev.soracom.io/jp/start/aws_button_slack/) に記載されているため、今回は AWS Lambda から AWS IoT Core へ MQTT Publish する方法を掲載します。

### Lambda 関数

AWS IoT 1-Click から起動される Lambda 関数です。

* `region_name` は AWS IoT Core のリージョンを指定
* この Lambda 関数には `Allow iot:publish` ポリシーが必要
* Lambda 関数の環境変数は以下の通り
    * `THING_NAME`: トピック名に利用される (例: `SwitchBot1`)

※ ダブルクリックを `press` に割り当てています

```lambda_function.py
# python3
import json
import os

# Require policy: `iot:Publish`
import boto3
iot = boto3.client('iot-data', region_name='ap-northeast-1')
THING_NAME = os.environ['THING_NAME']
def publish_to_iot(device_id, click_type, thing_name):
    event_map = {
        'SINGLE': 'ignore',
        'DOUBLE': 'press',
        'LONG'  : 'ignore'
    }
    cmd = event_map[click_type]
    payload = {"state":{"command":cmd}}
    print(payload)
    topic = "/devices/{}/commands/{}".format(thing_name, device_id)
    print(topic)
    iot.publish(topic=topic, payload=json.dumps(payload, separators=(',', ':')))

def lambda_handler(event, context):
    print(event)
    try:
        device_id = event['placementInfo']['attributes']['device_id']
    except KeyError:
        device_id = event['deviceInfo']['deviceId']
    click_type = event['deviceEvent']['buttonClicked']['clickType']
    
    publish_to_iot(device_id, click_type, THING_NAME)
    
    return {'statusCode': 204}
```

### AWS IoT 1-Click

先の Lambda 関数に割り付けるだけです。

## Amazon API Gateway

ReButton および IFTTT においては Amazon API Gateway で受け取ってから Lambda 関数を実行して MQTT Publish を行うため、共通して利用できる Amazon API Gateway について解説します。

### Lambda 関数

Amazon API Gateway から起動される Lambda 関数です。

`{"_shared_key_":"XXXX": "source": "AAAA": "clickType": "BBB"}` という JSON を受け取るようになっています。

AWS IoT 1-Click 用の関数と同様に、以下を確認してください。

* `region_name` は AWS IoT Core のリージョンを指定
* この Lambda 関数には `Allow iot:publish` ポリシーが必要
* Lambda 関数の環境変数は以下の通り
    * `THING_NAME`: トピック名に利用される (例: `SwitchBot1`)

※ ロングクリックを `press` に割り当てています

加えて、以下が仕様として追加されています。

* Lambda 関数の環境変数は以下の通り
    * `SHARED_KEY`: 不用意に SwitchBot が起動しないようにするための共通鍵文字列
    * ※ そのほかの実装として [Amazon API Gateway のオーソライザーを利用](https://qiita.com/favolabo/items/369631ca7696fad9307a)すれば、ifブロックを排除できます

```lambda_function.py
# python3
import json
import os

# Require policy: `iot:Publish`
import boto3
iot = boto3.client('iot-data', region_name='ap-northeast-1')
THING_NAME = os.environ['THING_NAME']
def publish_to_iot(device_id, click_type, thing_name):
    event_map = {
        'SINGLE': 'ignore',
        'DOUBLE': 'ignore',
        'LONG'  : 'press'
    }
    cmd = event_map[click_type]
    payload = {"state":{"garage":cmd}}
    print(payload)
    topic = "/devices/{}/commands/{}".format(thing_name, device_id)
    print(topic)
    iot.publish(topic=topic, payload=json.dumps(payload, separators=(',', ':')))

def lambda_handler(event, context):
    print(event)
    obj = json.loads(event['body'])
    
    if obj['_shared_key_'] != os.environ['SHARED_KEY']:
        return {'statusCode': 403, 'body': json.dumps('Invalid _shared_key_')}
    
    device_id = obj['source']
    click_type = obj['clickType']

    publish_to_iot(device_id, click_type, THING_NAME)
    
    return {'statusCode': 204}
```

### Amazon API Gateway

統合リクエストで Lambda 関数を呼び出しすように指定するだけです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/005d203b-430a-7a52-7740-952f7d90ecd6.png)

これで、Amazon API Gateway に REST を投げれば AWS IoT Core に Publish できるようになりました。

## Button Widget by IFTTT

IFTTT には、スマホ上で押すことができる仮想的なボタンウィジットがあります。
これをつかって、SORACOM LTE-M Button powered by AWS と似たようなことを行ってみます。

* This = Button Widget
* That = Webhooks

Webhooks (Make a web request) の設定は以下の通りです。

* URL: Amazon API Gateway の URL
* Method: Amazon API Gateway で設定したメソッド
* Content-Type: *application/json*
* Body(例): `{"_shared_key_":"YOUR_KEY", "source": "IFTTTbuttonWidget", "clickType": "SINGLE"}`

```json
{"_shared_key_":"Amazon API Gateway から起動される Lambda 関数の環境変数 SHARED_KEY に指定した文字列",
"source": "任意の文字列",
"clickType": "SINGLE or DOUBLE or LONG"}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/10a65eb9-751c-2957-91d0-4b962cbb355c.png)

## Amazon Echo (Amazon Alexa) by IFTTT

IFTTT には Amazon Echo (Amazon Alexa)をトリガーにする方法もあります。

* This = Amazon Alexa > Say a specific phrase
* That = Webhooks

Say a specific phrase の設定は以下の通りです。

* What phrase?: `ボタンを押して`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/22754/ec0d71ef-abe1-c807-f58a-13064ce3e558.png)

Webhooks (Make a web request) の設定は、Button Widget の時と同じです。

#### Amazon Echo (Amazon Alexa) への入力

`アレクサ、トリガー、ボタンを押して`

という形で「トリガー」というキーワードを入れる必要があります。ちょっと微妙だけど、動くと感動します。

日本語も解釈してくれますが、漢字（の読み）が微妙な時がありますので、その辺はテストしてください。

## ReButton

ReButton は Seeed さんが販売している Wi-Fi ベースのボタン型デバイスです。
標準では Microsoft Azure の Azure IoT Hub (もしくは Azure IoT Central)につながるようになっています。
今回は Azure IoT Hub から Azure Functions を経由して Amazon API Gateway に送信してみます。

Azure IoT Hub や Azure Functions のライブラリセットアップまでは [ReButton を MicroPush Bot を押すリモートボタンにする](https://qiita.com/ma2shita/items/6c82aea07e9e612d1078) に記載してあるので見てください。

ここでは Amazon API Gateway に送信するコードを紹介します。

```index.js
const request = require('request');
const eventNameMap = {
    '1': 'SINGLE',
    '2': 'DOUBLE',
    '3': 'LONG'
};
const DEVICE_ID = "reButton1";
const WEBHOOK_URL = process.env['WEBHOOK_URL'];
const SHARED_KEY = process.env['SHARED_KEY'];

module.exports = function (context, IoTHubMessages) {
    IoTHubMessages.forEach(message => {
        context.log(message);
        const eventName = eventNameMap[message.actionNum];
        if (eventName) {
            const opts = {
                url: WEBHOOK_URL,
                headers: {'Content-Type': 'application/json'},
                json: {
                    _shared_key_: SHARED_KEY,
                    source: DEVICE_ID,
                    clickType: eventName
                }
            };
            request.post(opts, (error, response, body) => {
                context.log(response);
            });
        } else {
            context.log("Not found in eventNameMap");
        }
    });
    context.done();
};
```

## あとがき

一気に紹介したので若干の消化不良は否めませんがご容赦を。

今まで利用していた MicroBot Push ですが、遠隔操作するためには、ゲートウェイデバイスに "Prota OS" 、そしてクラウドには "Prota" を利用する必要がありました。これら二つに加えて MicroBot Push 自体も情報(特に API)が公開されていません。

そのため、ユーザーは **「代替手段が存在しない」というリスクを常に追う** ことになります。これはかつてのメインフレームや Web 1.0 を彷彿とさせる「閉じたビジネス」です。

こういった考え方は遅かれ早かれ詰む＝終わる事は、私たち IT 業界に生きるものの常識ではありますが、残念なことにモノづくりの方々はいまだに「閉じたビジネス」を追い続けています。
その証左として、現在発売されているデバイス利用時に強制利用させられる「クラウドと称しているオンラインサービス」のほとんどが、今回紹介した多様なデバイス／クラウドを許容しておらず、「クラウドと称しているオンラインサービス」が終わった瞬間に、ユーザーに不利益を与えることになります。

* [スマートロック「246Padlock」がサービス終了 -その後は開錠不可 / ASCII.jp, 4/28/2016](https://ascii.jp/elem/000/001/156/1156555/)
* [マウスコンピューター、スマートホーム製品の販売終了　サービス終了後は利用不可に / ITmedia NEWS, 7/22/2019](https://www.itmedia.co.jp/news/articles/1907/22/news095.html)

これからのデバイスは**開かれているべき**です。

例えば今回の SwitchBot のように BLE の I/F が一つの例となるでしょう。
SwitchBot 市場を魅力的だと感じたプレイヤーが SwitchBot のゲートウェイデバイス "SwitchBot Hub" の互換製品を作ることができますし、それによって SwitchBot 自体も売れることになります。
こういった開かれたビジネスは、例えば「[Alexa 搭載デバイス](https://www.amazon.co.jp/b?ie=UTF8&node=5364388051)」という形で実を結び始めています。

何度も言いますが、これからのデバイスは**開かれているべき**なのです。
**「あなたの "クラウド" が終わった時、ユーザーはどうすればいいのですか？」**

EoT