Wio LTE M1/NB1(BG96) で本当にパケ死せずにデータを受け取る（MQTT編）

## これまでのあらすじ

[Wio LTE M1/NB1\(BG96\) でパケ死せずにデータを受け取る \- Qiita](https://qiita.com/kkoiwai/items/332938e597fb36974fd4)
[「ボタンを押したらパトランプ点灯してブザーを鳴らす」を、すべてLTE\-M回線で実現してみた。 \- Qiita](https://qiita.com/kkoiwai/items/e05f11f26fa3b28d42ba)

の2部作で、SORACOM LTE-M Buttonを押したら、パトライトを点灯させる仕組みを作りました。
<blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><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> を押したことを Wio LTE M1/NB1で受信して、パトランプとブザーを鳴らしてみた。音量注意。<a href="https://twitter.com/hashtag/SORACOM?src=hash&amp;ref_src=twsrc%5Etfw">#SORACOM</a> <a href="https://twitter.com/hashtag/AWS?src=hash&amp;ref_src=twsrc%5Etfw">#AWS</a> <a href="https://t.co/4UMEiCktyH">pic.twitter.com/4UMEiCktyH</a></p>&mdash; Kosuke Koiwai (@kocko) <a href="https://twitter.com/kocko/status/1122421384976539648?ref_src=twsrc%5Etfw">April 28, 2019</a></blockquote>


しかしながら、このときは、UDPで数秒おきにポーリングする仕組みとしましたが、それでもLTE-Mの通信料が高額（[**1MBあたり 500円超！**](https://qiita.com/kkoiwai/items/332938e597fb36974fd4#soracom-air-plan-km1-%E3%81%AF%E3%83%91%E3%82%B1%E6%AD%BB%E3%81%99%E3%82%8B)）で、1ヶ月つけっぱなしにすると、2，3万円に到達する試算でした。

なので、さらなる通信量の削減を目指し、MQTTを使ってみることにしました。

<img width="729" alt="スクリーンショット 2019-05-19 2.10.51.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/44d0d188-9f61-46c6-8c32-19c9caa5f9da.png">

流れはこんな感じです。

* LTE-M Buttonを押す
* AWS 1-Clickを経由して、Lambdaを起動
* Lambdaで、AWS-IoTのMQTTサーバにデータをPublish
* MQTTサーバとWio-LTEとの間をSORACOM Beamでつなぐ
* Wio-LTEでパトライトを光らせる


## AWS-IoTの設定

MQTTサーバは、AWS-IoTを使うことにします。
まず、IoT Coreに接続できるようにするため、証明書の設定をします。

AWS-IoTの設定画面から、安全性（Serucityの訳語？）→ 証明書 → 作成 を押します。
<img width="1237" alt="スクリーンショット 2019-05-19 2.32.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/f2124cfb-a48f-ea77-6b29-a313aa5dd145.png">
1-Click 証明書の作成を押します。
<img width="904" alt="スクリーンショット 2019-05-19 2.34.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/4b8450ee-d96e-1637-1d07-338818bc811f.png">

証明書と、プライベートキーが必要になります。ダウンロードしておきます。後ほどSORACOM Beamの設定で使います。

<img width="888" alt="スクリーンショット 2019-05-18 15.34.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/73a32f08-9ba3-f8da-2fdb-bc3984e1022e.png">

次に、MQTTサーバにアクセスさせるためのポリシーを作ります。
安全性 → ポリシー → 作成を押します。

<img width="1274" alt="スクリーンショット 2019-05-18 22.32.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/accb0465-5ae2-f1cd-352f-bc0d9ca7cdbd.png">

適当な名前をつけます。
アクション、リソースは、とりあえずすべて許可にします。
（あとでちゃんと、権限を絞っておきましょう）

<img width="918" alt="スクリーンショット 2019-05-18 22.33.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/be444afe-dbc8-52b4-7b5b-856c4d0d5940.png">

証明書の設定にもどり、作成したポリシーをアタッチします。

<img width="916" alt="スクリーンショット 2019-05-18 22.38.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/7504cd75-cc81-a895-5dd0-5cbef85da4d5.png">

<img width="956" alt="スクリーンショット 2019-05-18 22.38.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/82e3f1ca-acbb-c7f7-f356-475632ee8a9b.png">

最後に、接続先のサーバ名（エンドポイント）をメモしておきます。後ほどSORACOM Beamの設定で使います。

<img width="1096" alt="スクリーンショット 2019-05-18 22.56.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/fbaae927-0666-0db5-3b72-7e2d37c8f92b.png">

## SORACOM Beamの設定

ユーザコンソール上で、SIMグループを作成します。
グループの設定で、BEAMの設定 → MQTTエントリポイントを追加します。

<img width="959" alt="スクリーンショット 2019-05-18 22.53.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/c579d097-616e-884f-c1a5-d25d79fc1314.png">

転送先の設定をします。ホスト名に、先ほどコピーしておいたAWSのエントリポイントをペーストします。
<img width="609" alt="スクリーンショット 2019-05-18 22.57.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/8ab582bd-f897-194c-353c-ee21454be61e.png">

証明書の利用をONにして、認証情報を追加します。
<img width="607" alt="スクリーンショット 2019-05-18 22.57.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/dfa5f337-2c13-80a0-4553-4966e1395768.png">

先ほどダウンロードしたAWS-IoTの証明書と秘密鍵をテキストエディタで開いて、中身をコピペします。

<img width="623" alt="スクリーンショット 2019-05-18 22.55.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/5f46dc5c-1a1b-c576-14c0-4067c2078588.png">


## Lambdaの編集

LTE-M Buttonを押したらLambdaを呼ぶ設定については、こちらの公式記事を参考にしてください。
[SORACOM LTE\-M Button powered by AWS をクリックしてSlackに通知する \| Getting Started with SORACOM LTE\-M Button \| SORACOM Developers](https://dev.soracom.io/jp/start/aws_button_slack/) 

設定が終わったら、Lambdaのコードを、下記のようにして、ボタンを押されたらMQTTサーバにpublishするようにします。
[前回](https://qiita.com/kkoiwai/items/332938e597fb36974fd4#lambda%E3%81%AE%E4%BD%9C%E6%88%90)同様、なるべくデータ量を減らすため、MQTTにはJSONではなく、0, 1, 2 の数字1つだけを送ることにします。
エンドポイントには、先ほどSORACOM Beamで設定したものと同じものを設定します。
### コード

```js

const https = require('https');
var AWS = require("aws-sdk");

exports.handler = async(event) => {
    console.log('Received event:', JSON.stringify(event, null, 2));
    var putItem = {};

    switch (event.deviceEvent.buttonClicked.clickType) {
        case 'SINGLE':
            buttonState = 1;
            break;
        case 'DOUBLE':
            buttonState = 2;
            break;
        default:
            buttonState = 0;
            break;
    }

    var iotdata = new AWS.IotData({
        endpoint: 'XXXXXXXXXXXXXX.iot.ap-northeast-1.amazonaws.com',
        region: 'ap-northeast-1'
    });
    var params = {
        topic: "IoTButtonStatus",
        payload: ''+buttonState,  // convert to String
        qos: 0
    };
    await iotdata.publish(params).promise();
};
```

### ロールの追加

AWS-IoTのMQTTサーバにLambdaから繋げるための権限設定をします。
Lambdaの編集画面の下の、実行ロールの設定から、現在のロールを表示します。別の画面でロールが開きます。
<img width="808" alt="スクリーンショット 2019-05-19 2.46.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/b4b6dd25-ce28-6cf8-8c53-c15fcf025f9b.png">
ポリシーのアタッチをクリックします。
<img width="946" alt="スクリーンショット 2019-05-19 1.16.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/e0bc9fc5-a962-6b81-beb7-92d814a70ab0.png">
ポリシーの作成をクリックします。
<img width="1059" alt="スクリーンショット 2019-05-19 21.03.33.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/b7acf4fa-5b7e-e2a6-95f8-51cde496abd9.png">
サービスをIoTにして、
<img width="1099" alt="スクリーンショット 2019-05-19 21.05.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/b70f9c48-3756-9fd4-e73d-e7442e3c53b0.png">
アクションをすべてにして、
<img width="1104" alt="スクリーンショット 2019-05-19 21.05.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/265fa09d-6907-9cbd-78bd-1ef3e3f328b8.png">
リソースをすべてにして、
<img width="1098" alt="スクリーンショット 2019-05-19 21.05.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/86944ec1-ddd5-c459-85c4-39b6c9d8314b.png">
名前をつけて、ポリシーの作成を押します。
（もちろん、あとで適切な権限に絞っておきましょう）
<img width="1103" alt="スクリーンショット 2019-05-19 21.06.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/7822213e-58a3-f65e-d728-2da490da5302.png">

そうやって作ったポリシーを、アタッチして終了です。
<img width="1163" alt="スクリーンショット 2019-05-19 1.18.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/2700941b-c220-3b37-1bff-40cdef24172d.png">

### テスト

AWS-IoT コンソールのMQTTクライアント機能で、データがちゃんと飛ぶことを見てみます。
その際、ペイロード表示を、JSONフォーマットではなく、文字列表示に設定します。そうしないと、```0``` を送ったとき、空欄になってしまいます。
<img width="1012" alt="スクリーンショット 2019-05-21 22.55.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/522fcaee-7f87-0ac2-9674-7a8fe379f6c8.png">

SORACOM LTE-M Buttonを押してみたり、Lambdaのテスト機能を使って、データを送ってみます。
<img width="795" alt="スクリーンショット 2019-05-21 22.56.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/5d29885d-39d5-4d35-7942-f82ea4850816.png">




## Arduinoのコード

ライブラリのサンプルコードにちょっと付け加えただけです。

いずれかの方法で、MQTTのライブラリをダウンロードする必要があります。
下記をダウンロードして、Arduinoのライブラリフォルダに解凍しておくか、
[GitHub \- SeeedJP/pubsubclient: A client library for the Arduino Ethernet Shield that provides support for MQTT\.](https://github.com/SeeedJP/pubsubclient)

Arduinoのツール→ライブラリを管理　から、PubSubClientを探してインストールしておきます。
<img width="912" alt="スクリーンショット 2019-05-19 21.26.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/fb5d5346-f10b-0065-839c-9db39c0fa99f.png">



```c

#include <WioCellLibforArduino.h>
#include <WioCellularClient.h>
#include <PubSubClient.h>		// https://github.com/SeeedJP/pubsubclient
#include <stdio.h>

#define APN               "soracom.io"
#define USERNAME          "sora"
#define PASSWORD          "sora"

#define MQTT_SERVER_HOST  "beam.soracom.io"
#define MQTT_SERVER_PORT  (1883)

#define ID                "WioCell"
#define IN_TOPIC          "IoTButtonStatus"


#define RED_PIN      (WIO_D19)
#define YELLOW_PIN      (WIO_D20)
#define BUZZER_PIN      (WIO_D38)


WioCellular Wio;
WioCellularClient WioClient(&Wio);
PubSubClient MqttClient;

void callback(char* topic, byte* payload, unsigned int length) {
  SerialUSB.print("Subscribe:");
  for (int i = 0; i < (int)length; i++) SerialUSB.print((char)payload[i]);
  SerialUSB.println("");

  char v = int(payload[0]);
  
  if (v == '0'){
    digitalWrite(YELLOW_PIN, LOW);
    digitalWrite(RED_PIN, LOW);
    digitalWrite(BUZZER_PIN, LOW);
  }else if(v == '1'){
    digitalWrite(YELLOW_PIN, HIGH);
    digitalWrite(RED_PIN, LOW);
    digitalWrite(BUZZER_PIN, LOW);
  }else if(v == '2'){
    digitalWrite(YELLOW_PIN, LOW);
    digitalWrite(RED_PIN, HIGH);
    digitalWrite(BUZZER_PIN, HIGH);
  }

  
}

void setup() {
  delay(200);
  
  pinMode(RED_PIN, OUTPUT);
  pinMode(YELLOW_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);


  SerialUSB.begin(115200);
  SerialUSB.println("");
  SerialUSB.println("--- START ---------------------------------------------------");
  
  SerialUSB.println("### I/O Initialize.");
  Wio.Init();
  
  SerialUSB.println("### Power supply ON.");
  Wio.PowerSupplyCellular(true);
  delay(500);

  SerialUSB.println("### Turn on or reset.");
#ifdef ARDUINO_WIO_LTE_M1NB1_BG96
  Wio.SetAccessTechnology(WioCellular::ACCESS_TECHNOLOGY_LTE_M1);
  Wio.SetSelectNetwork(WioCellular::SELECT_NETWORK_MODE_MANUAL_IMSI);
#endif
  if (!Wio.TurnOnOrReset()) {
    SerialUSB.println("### ERROR! ###");
    return;
  }

  SerialUSB.println("### Connecting to \"" APN "\".");
  if (!Wio.Activate(APN, USERNAME, PASSWORD)) {
    SerialUSB.println("### ERROR! ###");
    return;
  }

  SerialUSB.println("### Connecting to MQTT server \"" MQTT_SERVER_HOST "\"");
  MqttClient.setServer(MQTT_SERVER_HOST, MQTT_SERVER_PORT);
  MqttClient.setCallback(callback);
  MqttClient.setClient(WioClient);
  if (!MqttClient.connect(ID)) {
    SerialUSB.println("### ERROR! ###");
    return;
  }
  MqttClient.subscribe(IN_TOPIC);

  SerialUSB.println("### Setup completed.");
}

void loop() {
  MqttClient.loop();
  delay(500);
}

```


### Keep Alive間隔を延ばす
PubSubClientのデフォルトKeepAlive間隔は15秒ですが、AWS-IoTは、30秒から1200秒までに対応しています。

[ライブラリのReadme](https://github.com/SeeedJP/pubsubclient#limitations)によれば、```PubSubClient.h```の中で、KeepAlive設定値を変えられるとのことだったので変えます。ちょっと余裕を持って900秒にしてみました。（デフォルトは1200秒とのことなので、普通に1200秒にしておけばいいと思います）

[AWS サービスの制限 \- アマゾン ウェブ サービス](https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws_service_limits.html)
<img width="896" alt="スクリーンショット 2019-05-19 21.30.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/24cd709d-8b51-0459-60ea-e9ce6c552ec2.png">


```PubSubClient.h
// MQTT_KEEPALIVE : keepAlive interval in Seconds
#ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 900
#endif
```

## 試してみる

<blockquote class="twitter-tweet" data-conversation="none" data-lang="en"><p lang="ja" dir="ltr">UDPでポーリングしていたときと比べて、反応が早くなった。<a href="https://twitter.com/hashtag/soracom?src=hash&amp;ref_src=twsrc%5Etfw">#soracom</a> <a href="https://twitter.com/hashtag/wiolte?src=hash&amp;ref_src=twsrc%5Etfw">#wiolte</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://t.co/cNkuuFDVym">pic.twitter.com/cNkuuFDVym</a></p>&mdash; Kosuke Koiwai (@kocko) <a href="https://twitter.com/kocko/status/1130840994021175296?ref_src=twsrc%5Etfw">May 21, 2019</a></blockquote>



## 実際の通信量は

ここまで終わったので、実際に動かして見ます。

<img width="899" alt="スクリーンショット 2019-05-19 2.48.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135431/6e491913-3d51-9fa0-7916-71a0f09014de.png">

電源を入れた直後は、5KBぐらいの通信が発生しますが、一度つないでしまえば、ボタンを押さなければ、1時間で1.2KBレベルまで減らすことができました。
つまり、0.6円 * 24時間 * 30日 = 432円 で運用できます。
ついにパケ死から逃れることができました！！
でも、まだ通常のLTEプランを使った方がきっと安いですね。。。