東京から大阪にあるSpresenseを制御しよう! by MQTT via LTE 

#初めに

MQTTの勉強とLTE使ってM2Mっぽいものを見せてくれよというリクエストから、**Spresense** のM2Mを**LTE**経由で**MQTT**の**pub/sub**を使ってやってみようと作ってみました。このコードではM2Mって感じじゃないのですがベースのプログラムとして使えるかな～と思って言います。

具体的には、例えば、東京側にある**Spresense**の **Keypad**で、**１～４**の数字を入力すると、それに合わせて大阪にある**Spresense**の**LED０～３**が点灯するというものです。
※東京、大阪はイメージです。

**Keypad**も使うので、今回は、**ArduinoIDE**を使って実現しようとしました。
実は、これだと動きがもっさりしています。これを解消する記事は、別途書こうと思っていますが、それほど反応速度がいらないようなものには、非常に簡単にできるのでまずはここまでということで。

#用意したもの

[**SPRESENSEメインボード[CXD5602PWBMAIN1]**
https://www.switch-science.com/catalog/3900/](https://www.switch-science.com/catalog/3900/)
<img src="https://d2air1d4eqhwg2.cloudfront.net/images/3900/500x500/42ccd153-3e89-4f7c-921c-775bc7de996d.jpg" width="200">

[**SPRESENSE LTE拡張ボード [CXD5602PWBLM1J]**
https://www.switch-science.com/catalog/5999/](https://www.switch-science.com/catalog/5999/
)
<img src="https://d2air1d4eqhwg2.cloudfront.net/images/5999/500x500/8af29fbe-3fc8-4aea-921b-401e23213226.png" width="200">

[**IIJmioプリペイドパック**
https://s.iijmio.jp/prepaid/](https://s.iijmio.jp/prepaid/)

https://s.iijmio.jp/prepaid/


と、Mainボード、LTE、SIMをすべて2セットづつ。


それに、Qwiicのコネクタとキーパッド。
[**SPRESENSE用Qwiic接続基板**
https://www.switch-science.com/catalog/6318/](https://www.switch-science.com/catalog/6318/)
<img src="https://d2air1d4eqhwg2.cloudfront.net/images/6318/500x500/d1e4f607-c416-42a2-9950-0a158e342f7b.jpg" width="200">

[**Qwiic - キーパッド（12ボタン）**
https://www.switch-science.com/catalog/5535/](https://www.switch-science.com/catalog/5535/)
<img src="https://d2air1d4eqhwg2.cloudfront.net/images/5535/500x500/7f028162-2b8a-4504-b0b6-adcc949ee388.jpg" width="200">

そうです。使いまわしです。苦笑い…。

#ブログ（元ネタ）
この記事は、以下のブログに過去に投稿したもののまとめです。

[LTEボードとMQTTを使ってM2Mを体感する その1
http://spresense.livedoor.blog/archives/28134913.html](http://spresense.livedoor.blog/archives/28134913.html)

[LTEボードとMQTTを使ってM2Mを体感する その2
http://spresense.livedoor.blog/archives/28174660.html](http://spresense.livedoor.blog/archives/28174660.html)


#サンプルプログラム
**Spresense ArduinoIDE** の **LTE** のサンプルの中に、**MQTT** で通信するサンプルがあります。

[5.6. AWSサーバにGNSS位置情報をPublishする
https://developer.sony.com/develop/spresense/docs/arduino_tutorials_ja.html#_aws%E3%82%B5%E3%83%BC%E3%83%90%E3%81%ABgnss%E4%BD%8D%E7%BD%AE%E6%83%85%E5%A0%B1%E3%82%92publish%E3%81%99%E3%82%8B](https://developer.sony.com/develop/spresense/docs/arduino_tutorials_ja.html#_aws%E3%82%B5%E3%83%BC%E3%83%90%E3%81%ABgnss%E4%BD%8D%E7%BD%AE%E6%83%85%E5%A0%B1%E3%82%92publish%E3%81%99%E3%82%8B)

これを見ることで、**LTE** を使った **MQTT通信** のサンプルが作れます。

#LTEを使う共通部分

LTEを使う上での共通定義部分は以下になります。

```c
#include <LTE.h>
#include <ArduinoMqttClient.h>

// APN name
#define APP_LTE_APN "iijmio.jp" // replace your APN

/* APN authentication settings
 * Ignore these parameters when setting LTE_NET_AUTHTYPE_NONE.
 */
#define APP_LTE_USER_NAME "mio@iij"     // replace with your username
#define APP_LTE_PASSWORD  "iij" // replace with your password

// APN IP type
#define APP_LTE_IP_TYPE (LTE_NET_IPTYPE_V4V6) // IP : IPv4v6

// APN authentication type
#define APP_LTE_AUTH_TYPE (LTE_NET_AUTHTYPE_CHAP) // Authentication : CHAP

/* RAT to use
 * Refer to the cellular carriers information
 * to find out which RAT your SIM supports.
 * The RAT set on the modem can be checked with LTEModemVerification::getRAT().
 */

#define APP_LTE_RAT (LTE_NET_RAT_CATM) // RAT : LTE-M (LTE Cat-M1)

LTE lteAccess;
LTEClient client;

```

当然ですが、**IIJ MIO** 用のサンプルです。
異なる **SIM** を使う人は変更してください。

また、サンプルコードにある **doAttach()** は、そのまま使えるので使います。

```c
void doAttach()
{
  while (true) {

    /* Power on the modem and Enable the radio function. */

    if (lteAccess.begin() != LTE_SEARCHING) {
      Serial.println("Could not transition to LTE_SEARCHING.");
      Serial.println("Please check the status of the LTE board.");
      for (;;) {
        sleep(1);
      }
    }

    /* The connection process to the APN will start.
     * If the synchronous parameter is false,
     * the return value will be returned when the connection process is started.
     */
    if (lteAccess.attach(APP_LTE_RAT,
                         APP_LTE_APN,
                         APP_LTE_USER_NAME,
                         APP_LTE_PASSWORD,
                         APP_LTE_AUTH_TYPE,
                         APP_LTE_IP_TYPE,
                         false) == LTE_CONNECTING) {
      Serial.println("Attempting to connect to network.");
      break;
    }

    /* If the following logs occur frequently, one of the following might be a cause:
     * - APN settings are incorrect
     * - SIM is not inserted correctly
     * - If you have specified LTE_NET_RAT_NBIOT for APP_LTE_RAT,
     *   your LTE board may not support it.
     */
    Serial.println("An error has occurred. Shutdown and retry the network attach preparation process after 1 second.");
    lteAccess.shutdown();
    sleep(1);
  }
}
```

#MQTT通信を使う共通部分

MQTTで通信する上での共通定義部分は以下になります。
今回は、コードの単純化と応答速度を鑑みて、特に認証もセキュア通信もしませんでした。

```c

// MQTT broker
#define BROKER_NAME "test.mosquitto.org"
#define BROKER_PORT 1883

// MQTT topic
#define MQTT_TOPIC "spresense/mqtt"

MqttClient mqttClient(client);

char broker[] = BROKER_NAME;
int port = BROKER_PORT;
char topic[]  = MQTT_TOPIC;

```

で、ここにMQTTの **クライアントID** が必要です。
これは、punlisher/subscriberごとに代わるので、publisher側は、

```c:m2m_pub.ino

// MQTT ID
#define MQTT_ID "spresense_pub"

```

と定義し、subscriber側は、

```c:m2m_sub.ino

// MQTT ID
#define MQTT_ID "spresense_sub"

```
と定義します。

また、今回は **Broker** には、[**mosquitoの公開サーバ**](http://test.mosquitto.org) を使いました。

mosquitoはこちら。

https://mosquitto.org/

今回は、この辺の説明は飛ばします。

これで、LTE経由でのMQTT通信の準備ができました。


#Publisher側のコード

Publisher側は、Keypadをつかうので、includeとインスタンス定義に以下を追加します。


```c:m2m_pub.ino
#include <Wire.h>
#include "SparkFun_Qwiic_Keypad_Arduino_Library.h"

KEYPAD keypad1; //Create instance of this object

```

**setup()** は以下になります。

```c:m2m_pub.ino
void setup()
{
  // Open serial communications and wait for port to open
  Serial.begin(115200);
  while (!Serial) {
      ; // wait for serial port to connect. Needed for native USB port only
  }

  Serial.println("Starting .");

  if (keypad1.begin() == false)  {
    Serial.println("Keypad does not appear to be connected. Please check wiring. Freezing...");
    while (1);
  }
  Serial.println(keypad1.getVersion());

  /* Connect LTE network */
  doAttach();

  // Wait for the modem to connect to the LTE network.
  Serial.println("Waiting for successful attach.");
  LTEModemStatus modemStatus = lteAccess.getStatus();

  while(LTE_READY != modemStatus) {
    if (LTE_ERROR == modemStatus) {

      Serial.println("An error has occurred. Shutdown and retry the network attach process after 1 second.");
      lteAccess.shutdown();
      sleep(1);
      doAttach();
    }
    sleep(1);
    modemStatus = lteAccess.getStatus();
  }

  Serial.println("attach succeeded.");

  Serial.print("Attempting to connect to the MQTT broker: ");
  Serial.println(broker);

  mqttClient.setId(MQTT_ID);

  while (!mqttClient.connect(broker, port)) {
    Serial.print("MQTT connection failed! Error code = ");
    Serial.println(mqttClient.connectError());
    sleep(1);
  }

  Serial.println("You're connected to the MQTT broker!");

  ledOn(LED0);
  ledOn(LED1);
  ledOn(LED2);
  ledOn(LED3);
}

```

データを送りユーティリティー関数として、**sendData** を用意します。

```c:m2m_pub.ino

void sendData(String testString)
{
    mqttClient.beginMessage(topic);
    mqttClient.print(testString);
    mqttClient.endMessage();
}

```

これで、**MQTT** の **publish** ができるようになったので、**loop()** の中で、**keypad** の1から4の入力を検出して、それに合わせた **数字** を **MQTT** で送るようにします。

```c:m2m_pub.ino
void loop()
{
  keypad1.updateFIFO();  // necessary for keypad to pull button from stack to readable register
  char button = keypad1.getButton();

  if (button > 0) {
     switch (button) {
        case '1':
          sendData(String(1));
          break;
        case '2':
          sendData(String(2));
          break;
        case '3':
          sendData(String(3));
          break;
        case '4':
          sendData(String(4));
          break;
          
        default:
          break;
      }
    }
}
```

これで、**keypad** の入力の数字に合わせて数字を送ることができました。

#Subscriber側のコード

Publisher側は、受け取った文字に合わせてLEDをつけるだけなので、特に追加のincludeとインスタンス定義はありません。

**setup()** は以下になります。

```c:m2m_sub.ino
void setup()
{ // Open serial communications and wait for port to open
  Serial.begin(115200);
  while (!Serial) {
      ; // wait for serial port to connect. Needed for native USB port only
  }

  Serial.println("Starting .");

  /* Connect LTE network */
  doAttach();

  // Wait for the modem to connect to the LTE network.
  Serial.println("Waiting for successful attach.");
  LTEModemStatus modemStatus = lteAccess.getStatus();

  while(LTE_READY != modemStatus) {
    if (LTE_ERROR == modemStatus) {

      Serial.println("An error has occurred. Shutdown and retry the network attach process after 1 second.");
      lteAccess.shutdown();
      sleep(1);
      doAttach();
    }

  Serial.println("attach succeeded.");

  Serial.print("Attempting to connect to the MQTT broker: ");
  Serial.println(broker);

  mqttClient.setId(MQTT_ID);

  while (!mqttClient.connect(broker, port)) {
    Serial.print("MQTT connection failed! Error code = ");
    Serial.println(mqttClient.connectError());
    sleep(1);
  }

  mqttClient.subscribe(topic);

  Serial.println("You're connected to the MQTT broker!");
}
```
これで、**MQTT** の **subscribe** ができるようになったので、**loop()** の中で、受け取った文字に合わせて **LED** を点灯するようにします。

```c:m2m_sub.ino
void loop()
{
  int messageSize = mqttClient.parseMessage();
  if (messageSize) {
    while (mqttClient.available()) {
     switch (mqttClient.read()) {
        case '1':
          ledOn(LED0);
          ledOff(LED1);
          ledOff(LED2);
          ledOff(LED3);
          break;
        case '2':
          ledOff(LED0);
          ledOn(LED1);
          ledOff(LED2);
          ledOff(LED3);
          break;
        case '3':
          ledOff(LED0);
          ledOff(LED1);
          ledOn(LED2);
          ledOff(LED3);
          break;
        case '4':
          ledOff(LED0);
          ledOff(LED1);
          ledOff(LED2);
          ledOn(LED3);
          break;

        default:
          break;
      }
    }
    Serial.println();
  }
}
```

これで **リモート** にある **Spresense** の **キーパッド** 入力に合わせて **LED**を点灯させることができました。

#動作結果

動かすとこんな感じになります。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/Spresense?src=hash&amp;ref_src=twsrc%5Etfw">#Spresense</a> <a href="https://twitter.com/hashtag/LTE?src=hash&amp;ref_src=twsrc%5Etfw">#LTE</a> で、<a href="https://twitter.com/hashtag/MQTT?src=hash&amp;ref_src=twsrc%5Etfw">#MQTT</a> 使って、<a href="https://twitter.com/hashtag/M2M?src=hash&amp;ref_src=twsrc%5Etfw">#M2M</a> してみた。<a href="https://twitter.com/hashtag/KeyPad?src=hash&amp;ref_src=twsrc%5Etfw">#KeyPad</a> で押した数字の <a href="https://twitter.com/hashtag/LED?src=hash&amp;ref_src=twsrc%5Etfw">#LED</a> が点いた！<br>これで、東京から大阪のSpresenseが操作できるね。<a href="https://t.co/g9hQalUcD6">https://t.co/g9hQalUcD6</a> <a href="https://t.co/3lgouwfItQ">pic.twitter.com/3lgouwfItQ</a></p>&mdash; 「SPRESENSEに興味あり」 (@spresense) <a href="https://twitter.com/spresense/status/1464162579694059525?ref_src=twsrc%5Etfw">November 26, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

冒頭にも書いたとおり、かなり動きがもっさりしますが、これは、**Arduino** の MQTTライブラリで**subscribe** することが原因のようです。SDKで動かせば早くなります。

**機会** があればこちらも書きま～す。