M5Stack用SIM7080G搭載CAT-M/NB-IoT+GNSSユニットとIoT Connect Mobile Type Sを使ったLTE-MでのHTTP通信

# はじめに
IoT Connect Mobile Type S というNTTコミュニケーションズが提供している通信回線と M5Stack用SIM7080G搭載CAT-M/NB-IoT+GNSSユニット を使って通信環境を整えたいと思います。

M5Stack用SIM7080G搭載CAT-M/NB-IoT+GNSSユニット で IoT Connect Mobile Type S のSIMが動作するかどうかの検証は以下の記事が参考になります。ソースコード自体は共通部分も多いので、必要に応じて、両方を参照すると良いと思います。

https://qiita.com/yukimatsu/items/1ad1b505ea63006d0647

<br>

**M5Stack用 SIM7080G搭載 CAT-M/NB-IoT+GNSSユニット（アンテナつき）**

https://www.switch-science.com/products/8191


# はまったところ
### M5Stack Core2を使用したことでのコンパイルエラー

M5Stack用SIM7080G搭載CAT-M/NB-IoT+GNSSユニット を使う直接的な原因ではないですが、M5Stack Core2を使用しており、HTTP通信を行う為にライブラリは以下を使用しました。

```
#include <M5Core2.h>
#include <TinyGsmClient.h>
#include <ArduinoHttpClient.h>
```
<br>

ArduinoHttpClientライブラリ は、TinyGSMライブラリと組み合わせてHTTP通信を行っている人が多いようですが、今回はこの組み合わせを試したところ、以下のエラーが発生しました。

```
.pio/libdeps/m5stack-core2/M5Core2/src/M5Display.cpp: In member function 'void M5Display::drawPngUrl(const char*, uint16_t, uint16_t, uint16_t, uint16_t, uint16_t, uint16_t, double, uint8_t)':
.pio/libdeps/m5stack-core2/M5Core2/src/M5Display.cpp:563:3: error: 'HTTPClient' was not declared in this scope
   HTTPClient http;
   ^~~~~~~~~~
.pio/libdeps/m5stack-core2/M5Core2/src/M5Display.cpp:563:3: note: suggested alternative: 'HttpClient'
   HTTPClient http;
   ^~~~~~~~~~
   HttpClient
.pio/libdeps/m5stack-core2/M5Core2/src/M5Display.cpp:565:7: error: 'WiFi' was not declared in this scope
```
<br>

エラー内容を確認して、少し整理すると以下のようなことが発生しているようです。
* M5Display.cpp の drawPngUrlメソッド内で 'HTTPClient' が定義されていない
* 'HTTPClient'  の代わりに 'HttpClient' を提案します
* HttpClientクラスにはWiFiメソッド が定義されていない

これは、M5Stack環境で使用されている'HTTPClient' と 'ArduinoHttpClient' のクラスがそれぞれ内部で、**HTTPClient** と **HttpClient** を使用していて、それが解決できていないのだろうと推測できます。




# ライブラリ修正による対応方法
### M5Core2用の M5Display.cpp のソースコードを修正
コンパイルエラーが発生する箇所は、M5Core2用のソースコードの M5Display::drawPngUrlメソッド のみであり、機能が限定的だった為、コメントアウトすることにしました（559～631行目あたり。2023.10.07現在）

コメントアウトするメソッドは以下になります。

```cpp：M5Display.cpp
void M5Display::drawPngUrl(const char *url, uint16_t x, uint16_t y,
                           uint16_t maxWidth, uint16_t maxHeight, uint16_t offX,
                           uint16_t offY, double scale,
                           uint8_t alphaThreshold) {
  HTTPClient http;

  if (WiFi.status() != WL_CONNECTED) {
    log_e("Not connected");
    return;
  }

  http.begin(url);

  int httpCode = http.GET();
  if (httpCode != HTTP_CODE_OK) {
    log_e("HTTP ERROR: %d\n", httpCode);
    http.end();
    return;
  }

  WiFiClient *stream = http.getStreamPtr();

  pngle_t *pngle = pngle_new();

  png_file_decoder_t png;

  if (!maxWidth) {
    maxWidth = width() - x;
  }
  if (!maxHeight) {
    maxHeight = height() - y;
  }

  png.x = x;
  png.y = y;
  png.maxWidth = maxWidth;
  png.maxHeight = maxHeight;
  png.offX = offX;
  png.offY = offY;
  png.scale = scale;
  png.alphaThreshold = alphaThreshold;
  png.tft = this;

  pngle_set_user_data(pngle, &png);
  pngle_set_draw_callback(pngle, pngle_draw_callback);

  // Feed data to pngle
  uint8_t buf[1024];
  int remain = 0;
  int len;
  while (http.connected()) {
    size_t size = stream->available();
    if (!size) {
      delay(1);
      continue;
    }

    if (size > sizeof(buf) - remain) size = sizeof(buf) - remain;
    if ((len = stream->readBytes(buf + remain, size)) > 0) {
      int fed = pngle_feed(pngle, buf, remain + len);
      if (fed < 0) {
        log_e("[pngle error] %s", pngle_error(pngle));
        break;
      }

      remain = remain + len - fed;
      if (remain > 0) memmove(buf, buf + fed, remain);
    }
  }

  pngle_destroy(pngle);
  http.end();
}
```

# ソースコード（HTTP通信）


* M5Stack Core2
* M5Stack用SIM7080G搭載CAT-M/NB-IoT+GNSSユニット
* IoT Connect Mobile Type S

上記の組み合わせで、HTTP通信の動作確認をしたソースコードがこちらです。
<br>

```cpp:main.cpp
// SIM7080用のモデムモジュール
#define TINY_GSM_MODEM_SIM7080
#define SerialAT Serial2
#define PIN_RX 33
#define PIN_TX 32

#include <M5Core2.h>
#include <TinyGsmClient.h>
#include <ArduinoHttpClient.h>

// APN設定
const char APN[] = "mobiledata.ntt.com";
const char LTE_USER[] = "";
const char LTE_PASS[] = "";

// Server details
const char server[]   = "httpbin.org";
const char resource[] = "/ip";
const int  port       = 80;

TinyGsm modem(SerialAT);
TinyGsmClient client(modem);
HttpClient http(client, server, port);

// モデムの初期化
void initSim(){
    // シリアル通信の開始
    SerialAT.begin(115200, SERIAL_8N1, PIN_RX, PIN_TX);
    delay(6000);

    // モデムの初期化
    Serial.println("Initializing modem...");
    if(!modem.init())
    {
        Serial.println("Failed to restart modem.");
    }

    // モデムの情報を表示
    String modemInfo = modem.getModemInfo();
    Serial.println("modem.getModemInfo()");
    Serial.println(modemInfo);

    // SIMカードのチェック
    Serial.println("Checking SIM card...");
    if (!modem.getSimStatus()) {
        Serial.println("SIM card not inserted.");
        return;
    }

    // ネットワークモードと優先モードの設定
    const int NETWORK_MODE = 2;   // 2: Automatic, 13: GSM only, 38: LTE only, 51: GSM and LTE only
    const int PREFERRED_MODE = 1; // 1: CAT-M only, 2: NB-IoT only, 3: CAT-M and NB-IoT
    bool result;
    
    do {
        result = modem.setNetworkMode(NETWORK_MODE);
        Serial.println("Setting network mode to " + String(NETWORK_MODE) + ": " + (result ? "OK" : "NG"));
        delay(500);
    } while (!result);

    do {
        result = modem.setPreferredMode(PREFERRED_MODE);
        Serial.println("Setting preferred mode to " + String(PREFERRED_MODE) + ": " + (result ? "OK" : "NG"));
        delay(500);
    } while (!result);

    // ネットワークへの接続待ち
    Serial.println("Waiting for network...");
    if (!modem.waitForNetwork()) {
        Serial.println("Failed to connect to network.");
        return;
    }

    // ネットワークへの接続状態を表示
    Serial.println("Network connected: " + String(modem.isNetworkConnected()));

    // APNへの接続
    Serial.println("Connecting to APN: " + String(APN));
    if (!modem.gprsConnect(APN)) {
        Serial.println("Failed to connect to APN.");
        return;
    }

    // GPRSへの接続状態を表示
    Serial.println("GPRS connected: " + String(modem.isGprsConnected()));

    // SIMカード、IMEI、オペレーター、ローカルIP、信号強度などの情報を表示
    Serial.println("SIM CCID: " + modem.getSimCCID());
    Serial.println("IMEI: " + modem.getIMEI());
    Serial.println("Operator: " + modem.getOperator());
    Serial.println("Local IP: " + modem.localIP().toString());
    Serial.println("Signal quality: " + String(modem.getSignalQuality()));

}

void setup(){

    // M5Stackの初期化
    M5.begin(true, true, true, false);
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setTextColor(WHITE);
    M5.Lcd.setTextSize(2);

    // ネットワークの初期化
    initSim();
}

void loop(){

    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);

    // HTTPリクエストの送信
    Serial.println("Performing HTTP GET request... ");
    int err = http.get(resource);
    if (err != 0) {
      Serial.println("failed to connect");
      delay(1000);
      return;
    }

    int status = http.responseStatusCode();
    Serial.print("Response status code: ");
    Serial.println(status);
    if (!status) {
      delay(10000);
      return;
    }

    Serial.print("Response Headers:");
    while (http.headerAvailable()) {
      String headerName = http.readHeaderName();
      String headerValue = http.readHeaderValue();
      Serial.println("    " + headerName + " : " + headerValue);
    }

    int length = http.contentLength();
    if (length >= 0) {
      Serial.print("Content length is: ");
      Serial.println(length);
    }
    if (http.isResponseChunked()) {
      Serial.println("The response is chunked");
    }
    String body = http.responseBody();
    Serial.print("Response:");
    Serial.println(body);


    // HTTPレスポンスの表示
    M5.Lcd.println("HTTP code: " + String(status));
    M5.Lcd.println("Response: " + String(body));

    // HTTPクライアントの終了
    http.stop();

    // 次のリクエストまで300秒間待機
    delay(300000);
    M5.update();
}
```
<br>


通信テストに使用したサイトは以下になります。
httpbin.orgとは、HTTPリクエストとレスポンスのサービスです。HTTPリクエストを送ると、指定したルールに従ってリクエストの内容を返してくれます。HTTPライブラリのテストやデバッグに便利です。

https://httpbin.org/



# HTTP通信の動作確認

上記のソースコードでは、ターミナルにログが出力され、以下のようになります。
レスポンスで、グローバルIPアドレス が返ってくることを確認できています。
これで、HTTP通信が行えることが確認できました。
```
Performing HTTP GET request... 
Response status code: 200
Response Headers:    Date : Sat, 07 Oct 2023 10:01:34 GMT
    Content-Type : application/json
    Content-Length : 33
    Connection : close
    Server : gunicorn/19.9.0
    Access-Control-Allow-Origin : *
    Access-Control-Allow-Credentials : true
Content length is: 33
Response:{
  "origin": "xxx.xxx.xxx.xxx" // ここは、xxxに変更しました
}
```

# 参考
インターネット上を検索すると、以下のような情報も出てきました。
使用しているデバイスは違いますが、近い現象な気がします。

https://qiita.com/ma2shita/items/97bf1a0c3158b848019a

ただし、自分の場合は、main.cpp や ライブラリのソースコードに
```
#include "HTTPClient.h"
```
を明記しても、コンパイルエラーは治らなかったので、上記の対策をしました。


# まとめ
これで、IoT Connect Mobile Type S と M5Stack用SIM7080G搭載CAT-M/NB-IoT+GNSSユニット で通信動作を確認することができました。
こちらの情報を参考に、送信先をクラウドに変更などすれば、WiFi環境が整っていない環境でのクラウド連携などが簡単になると思います。