SPRESENSEでSORACOM Beam経由してAWS IoTにpubする

## SORACOMでAWS IoTに接続する。

基本的な接続手順は下記を参照
- [AWS IoT と接続する](https://users.soracom.io/ja-jp/docs/beam/aws-iot-console/)
    - ステップ 2: SORACOM Beam を有効にして MQTT エントリポイントを設定する
     まで設定できればOK

## SRACOM Beam経由で接続する
SPRESENSEのサンプルコードのLteGnssTracker.inoを元にし改変していく
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483299/8f79aac3-76d3-d772-821e-fb8f4f48faf0.png width=320>

## 変更内容
　MQTTSからMQTT（非TLS通信）に変更するには、以下のような調整が必要です。
 主にセキュリティ証明書を利用した設定をコメントアウトし、通常のMQTT通信に切り替えることがポイントです。

### 1. SORACOM LTE設定の確認
APN、ユーザー名、パスワードの設定が正しいことを再確認してください。

```bash:変更後
#define APP_LTE_APN "soracom.io"
#define APP_LTE_USER_NAME "sora"
#define APP_LTE_PASSWORD "sora"
```

### 2. LTETLSClient を LTEClient に変更
LTETLSClient は TLS 通信を使いますが、MQTTでは非TLSのクライアントを使います。
LTEClientに置き換えます。

```bash:変更前
LTETLSClient client;
```
```bash:変更後
LTEClient client;
```

### 3. 証明書設定部分をコメントアウト
証明書関連のコードがTLSに必要なため、これをすべてコメントアウトします。

```bash:変更後（コメントアウト）

// #define ROOTCA_FILE "CERTS/AmazonRootCA1.pem"  
// #define CERT_FILE   "CERTS/1fd3b233ee-certificate.pem.crt" 
// #define KEY_FILE    "CERTS/1fd3b233ee-private.pem.key"

・
・
・

// File rootCertsFile = theSD.open(ROOTCA_FILE, FILE_READ);
// client.setCACert(rootCertsFile, rootCertsFile.available());
// rootCertsFile.close();

// File certsFile = theSD.open(CERT_FILE, FILE_READ);
// client.setCertificate(certsFile, certsFile.available());
// certsFile.close();

// File priKeyFile = theSD.open(KEY_FILE, FILE_READ);
// client.setPrivateKey(priKeyFile, priKeyFile.available());
// priKeyFile.close();
```

### 4. ポート番号の修正
MQTTS (TLS) ではポート番号が 8883 ですが、
MQTT (非TLS) の場合はデフォルトで 1883 を使います。

```bash:変更前
#define BROKER_PORT 8883  // TLS用のポート
```
```bash:変更後
#define BROKER_PORT        1883
```
## 変更後のコード

<details><summary>LteGnssTracker.inoの改変後</summary>
<div>

```bash:
// libraries
#include <RTC.h>
#include <SDHCI.h>
#include <GNSS.h>
#include <LTE.h>
#include <ArduinoMqttClient.h>
#include "gnss_nmea.h"

// APN name
#define APP_LTE_APN "soracom.io" // replace your APN
#define APP_LTE_USER_NAME "sora" // replace with your username
#define APP_LTE_PASSWORD  "sora" // 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
#define APP_LTE_RAT (LTE_NET_RAT_CATM) // RAT : LTE-M (LTE Cat-M1)
// MQTT broker
#define BROKER_NAME        "your-MQTT-broker" // replace with your broker
#define BROKER_PORT        1883               // port 8883 is the default for MQTT over TLS.
#define ROOTCA_FILE "path/to/cafile"   // Define the path to a file containing CA
                                       // certificates that are trusted.
#define CERT_FILE   "path/to/certfile" // Define the path to a file containing certificate
                                       // for this client, if required by the server.
#define KEY_FILE    "path/to/keyfile"  // Define the path to a file containing private key
                                       // for this client, if required by the server.

// MQTT topic
#define MQTT_TOPIC         "spresense/gnss_tracker" // replace with your topic

// MQTT publish interval settings
#define PUBLISH_INTERVAL_SEC   1   // MQTT publish interval in sec
#define MAX_NUMBER_OF_PUBLISH  60  // Maximum number of publish
#define RET_SUCCESS            1   // Return value meaning success
#define RET_FAILURE            0   // Return value meaning failure
#define CONNECT_RETRY          5   // Number of retries when connection to the broker fails

LTE lteAccess;
LTEClient client;
MqttClient mqttClient(client);
SDClass theSD;
SpGnss Gnss;

int numOfPubs = 0;
unsigned long lastPubSec = 0;
char broker[] = BROKER_NAME;
int port = BROKER_PORT;
char topic[]  = MQTT_TOPIC;

void printClock(RtcTime &rtc)
{
  printf("%04d/%02d/%02d %02d:%02d:%02d\n",
         rtc.year(), rtc.month(), rtc.day(),
         rtc.hour(), rtc.minute(), rtc.second());
}

String readFromSerial() {
  /* Read String from serial monitor */
  String str;
  int  read_byte = 0;
  while (true) {
    if (Serial.available() > 0) {
      read_byte = Serial.read();
      if (read_byte == '\n' || read_byte == '\r') {
        Serial.println("");
        break;
      }
      Serial.print((char)read_byte);
      str += (char)read_byte;
    }
  }
  return str;
}

void readApnInformation(char apn[], LTENetworkAuthType *authtype,
                       char user_name[], char password[]) {
  /* Set APN parameter to arguments from readFromSerial() */

  String read_buf;

  while (strlen(apn) == 0) {
    Serial.print("Enter Access Point Name:");
    readFromSerial().toCharArray(apn, LTE_NET_APN_MAXLEN);
  }

  while (true) {
    Serial.print("Enter APN authentication type(CHAP, PAP, NONE):");
    read_buf = readFromSerial();
    if (read_buf.equals("NONE") == true) {
      *authtype = LTE_NET_AUTHTYPE_NONE;
    } else if (read_buf.equals("PAP") == true) {
      *authtype = LTE_NET_AUTHTYPE_PAP;
    } else if (read_buf.equals("CHAP") == true) {
      *authtype = LTE_NET_AUTHTYPE_CHAP;
    } else {
      /* No match authtype */
      Serial.println("No match authtype. type at CHAP, PAP, NONE.");
      continue;
    }
    break;
  }

  if (*authtype != LTE_NET_AUTHTYPE_NONE) {
    while (strlen(user_name)== 0) {
      Serial.print("Enter username:");
      readFromSerial().toCharArray(user_name, LTE_NET_USER_MAXLEN);
    }
    while (strlen(password) == 0) {
      Serial.print("Enter password:");
      readFromSerial().toCharArray(password, LTE_NET_PASSWORD_MAXLEN);
    }
  }

  return;
}

/* Attach to the LTE network */

void doAttach()
{
  char apn[LTE_NET_APN_MAXLEN] = APP_LTE_APN;
  LTENetworkAuthType authtype = APP_LTE_AUTH_TYPE;
  char user_name[LTE_NET_USER_MAXLEN] = APP_LTE_USER_NAME;
  char password[LTE_NET_PASSWORD_MAXLEN] = APP_LTE_PASSWORD;

  /* Set if Access Point Name is empty */
  if (strlen(APP_LTE_APN) == 0) {
    Serial.println("This sketch doesn't have a APN information.");
    readApnInformation(apn, &authtype, user_name, password);
  }
  Serial.println("=========== APN information ===========");
  Serial.print("Access Point Name  : ");
  Serial.println(apn);
  Serial.print("Authentication Type: ");
  Serial.println(authtype == LTE_NET_AUTHTYPE_CHAP ? "CHAP" :
                 authtype == LTE_NET_AUTHTYPE_NONE ? "NONE" : "PAP");
  if (authtype != LTE_NET_AUTHTYPE_NONE) {
    Serial.print("User Name          : ");
    Serial.println(user_name);
    Serial.print("Password           : ");
    Serial.println(password);
  }

  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,
                         apn,
                         user_name,
                         password,
                         authtype,
                         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);
  }
}

void connectMqttBroker(char *_broker, int _port)
{
  int i;

  for (i = 0; i < CONNECT_RETRY; i++) {
    Serial.print("Attempting to connect to the MQTT broker: ");
    Serial.println(_broker);

    if (mqttClient.connect(_broker, _port)) {
      break;
    }
    Serial.print("MQTT connection failed! Error code = ");
    Serial.println(mqttClient.connectError());
    sleep(1);
  }

  if (i >= CONNECT_RETRY) {
    Serial.println("Exceeded maximum number of retries to connect to the broker, application terminated.");

    // do nothing forevermore:
    for (;;)
      sleep(1);
  }

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

int mqttPublish(char *_topic, const String &_data)
{
  // Publish to broker
  Serial.print("Sending message to topic: ");
  Serial.println(_topic);
  Serial.print("Publish: ");
  Serial.println(_data);

  if (mqttClient.beginMessage(_topic) == 0) {
    Serial.println("mqttClient.beginMessage failed!");
    mqttClient.stop();
    return RET_FAILURE;
  }

  if (mqttClient.print(_data) == 0) {
    Serial.println("mqttClient.print failed!");
    mqttClient.stop();
    return RET_FAILURE;
  }

  if (mqttClient.endMessage() == 0) {
    Serial.println("mqttClient.endMessage failed!");
    mqttClient.stop();
    return RET_FAILURE;
  }

  Serial.println("MQTT Publish succeeded!");

  return RET_SUCCESS;
}

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 GNSS tracker via LTE.");

  /* Initialize SD */
  while (!theSD.begin()) {
    ; /* wait until SD card is mounted. */
  }

  /* Connect LTE network */
  doAttach();

  int result;

  /* Activate GNSS device */
  result = Gnss.begin();
  assert(result == 0);

  /* Start positioning */
  result = Gnss.start();
  assert(result == 0);
  Serial.println("Gnss setup OK");

  // 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) {

      /* If the following logs occur frequently, one of the following might be a cause:
       * - Reject from LTE network
       */
      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.");

  // Set local time (not UTC) obtained from the network to RTC.
  RTC.begin();
  unsigned long currentTime;
  while(0 == (currentTime = lteAccess.getTime())) {
    sleep(1);
  }
  RtcTime rtc(currentTime);
  printClock(rtc);
  RTC.setTime(rtc);

  // Set certifications via a file on the SD card before connecting to the MQTT broker
  //  File rootCertsFile = theSD.open(ROOTCA_FILE, FILE_READ);
  //  client.setCACert(rootCertsFile, rootCertsFile.available());
  //  rootCertsFile.close();
  //
  //  File certsFile = theSD.open(CERT_FILE, FILE_READ);
  //  client.setCertificate(certsFile, certsFile.available());
  //  certsFile.close();
  //
  //  File priKeyFile = theSD.open(KEY_FILE, FILE_READ);
  //  client.setPrivateKey(priKeyFile, priKeyFile.available());
  //  priKeyFile.close();

  // connect to the MQTT broker
  connectMqttBroker(broker, port);
}

void loop()
{
  /* Check update. */
  if (Gnss.waitUpdate(-1)) {
    /* Get navData. */
    SpNavData navData;
    Gnss.getNavData(&navData);

    bool posFix = ((navData.posDataExist) && (navData.posFixMode != FixInvalid));
    if (posFix) {
      Serial.println("Position is fixed.");
      String nmeaString = getNmeaGga(&navData);
      if (strlen(nmeaString.c_str()) != 0) {
        unsigned long currentTime = lteAccess.getTime();
        if (currentTime >= lastPubSec + PUBLISH_INTERVAL_SEC) {
          if (!mqttClient.connected()) {
            // reconnect to the MQTT broker
            connectMqttBroker(broker, port);
          }

          // Publish to broker
          if (mqttPublish(topic, nmeaString) == RET_SUCCESS) {
            lastPubSec = currentTime;
            numOfPubs++;
          }
        }
      }
    } else {
      Serial.println("Position is not fixed.");
    }
  }

  if (numOfPubs >= MAX_NUMBER_OF_PUBLISH) {
    Serial.println("Publish end");
    // do nothing forevermore:
    for (;;)
      sleep(1);
  }
}
```
</div></details>

以上

# 参考
- [AWS IoT Device SDK PythonをベースにしてSORACOM Beam経由でAWS IoT CoreへMQTT接続できるか認確](https://qiita.com/TakashiKOYANAGAWA/items/98ec7d5ed6c1d4688428)