[IoT] esp32 + SIM7600でネット通信

# 問題

esp32を使ってSIMカード経由でネット通信がしたい

# 解決

- [SIM7600のモジュールを買う][1] （T-PCIE 4MB 9102、T-PCIE-SIM7600JC-H）
- ATコマンドを理解する

## T-PCIE 4MB 9102



esp32のベースモジュール

![](https://ae01.alicdn.com/kf/H95d71910311f43b09dae057fcdc25f46l/LILYGO-TTGO-T-PCIE-ESP32-WROVER-B-AXP192-Chip-WIFI-Bluetooth-Nano-Card-SIM-Series-Composable.jpg_640x640.jpg)


## T-PCIE-SIM7600JC-H

![](https://ae01.alicdn.com/kf/H17f9ce7d80234540b926796d0a4cfaf2A/LILYGO-TTGO-T-PCIE-ESP32-WROVER-B-AXP192-Chip-WIFI-Bluetooth-Nano-Card-SIM-Series-Composable.jpg_640x640.jpg)


SIMカードを刺す通信モジュール
ベースモジュールに差し込んでビスで留める

## ATコマンド

[製造元][2]のページよりユーザー登録して、ログインして、これらのPDFをダウンロードする

- SIM7500_SIM7600 Series_AT Command Manual_V3.00
- SIM7500_SIM7600_Series_HTTP(S)_Application Note_V3.00
- SIM7600_Series_PCIE_Hardware_Design_V1.03

### 参考

https://bearmini.hatenablog.com/entry/at-command1

https://youtu.be/g1nxe_J3DVI

## サンプルコード（VS Codeでplatform.ioを使っている）

どうも巷にあるサンプルコードではOCN ONEモバイルには素直に繋がらなかった

`AT+COPS?`で通信業者を探してから、`AT+COPS=`でちゃんとしてやらないとどうもネットワークを検索するばかりで繋がらなかった
一度つなげてしまえば、その後は巷にあるサンプルでも動き出す（というか多分設定がSIMに保存される？のでネットワークがすぐに見つかってる感じ？）


### AT+COPS=で通信業者を自前で設定するコード

まず初回はこれでIPアドレスが取れるまでは確認しておく
[ATコマンドの流れはここを参考にした][3]

**upload_port = /dev/cu.wchusbserial537D0225881**は自分のものに変える

```ini:platformio.ini
; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
upload_port = /dev/cu.wchusbserial537D0225881
monitor_speed = 115200
build_flags = -DCORE_DEBUG_LEVEL=5
lib_deps = 
	vshymanskyy/TinyGSM@^0.11.5
	vshymanskyy/StreamDebugger@^1.0.1
```

```cpp:main.cpp
#define SerialMon Serial
#define SerialAT Serial1

static const char *TAG = "SIM LTE";
const char *apn = "ocn.ne.jp";
const char *gprsUser = "mobileid@ocn";
const char *gprsPass = "mobile";

#define uS_TO_S_FACTOR 1000000ULL
#define TIME_TO_SLEEP 60

#define PIN_TX 27
#define PIN_RX 26
#define UART_BAUD 115200
#define PWR_PIN 4
#define LED_PIN 12
#define POWER_PIN 25
#define IND_PIN 36

// https://github.com/vshymanskyy/TinyGSM#diagnostics-sketch
// デバッグログを出す（TinyGsmClient内に記載）
#define TINY_GSM_DEBUG SerialMon
// SIM7600指定（TinyGsmClient内に記載）
#define TINY_GSM_MODEM_SIM7600
// その上で読み込む
#include <TinyGsmClient.h>

// デバッグ設定
#include <StreamDebugger.h>
StreamDebugger debugger(SerialAT, SerialMon);
TinyGsm modem(debugger);

void setup() {
  // デバッグログ用
  SerialMon.begin(115200);

  // 基本モジュールからの電源供給
  pinMode(POWER_PIN, OUTPUT);
  digitalWrite(POWER_PIN, HIGH);
  delay(1000);

  // この操作はここの事だと思う
  // SIM7600_Series_PCIE_Hardware_Design_V1.03.pdf
  // 3.2 PERST#
  // The active low level time impulse on PERST# pin to reset module
  pinMode(PWR_PIN, OUTPUT);
  digitalWrite(PWR_PIN, HIGH);
  delay(500);
  digitalWrite(PWR_PIN, LOW);

  // モデムにATcommandを送る用
  SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX);
  // 起動までにちょっとまっておかないとATコマンドが受け付けられない
  delay(5000);

  if (!modem.init()) {
    ESP_LOGD(TAG, "初期化失敗");
    return;
  }

  //////////////////////////////////////////////
  // ネットワーク、とりあえず全部ON（よく分かってない）
  //////////////////////////////////////////////
  // network
  modem.sendAT(GF("+CREG=1"));
  ESP_LOGD(TAG, "+CREG=1 %d", modem.waitResponse());

  // GPRS network
  modem.sendAT(GF("+CGREG=1"));
  ESP_LOGD(TAG, "+CGREG=1 %d", modem.waitResponse());

  // LTE network
  // https://atmarkit.itmedia.co.jp/ait/articles/1001/13/news105.html
  // LTEネットワークは、「進化したパケットシステム」という意味で「EPS（Evolved
  // Packet System）」とも呼ばれ
  modem.sendAT(GF("+CEREG=1"));
  ESP_LOGD(TAG, "+CEREG=1 %d", modem.waitResponse());

  //////////////////////////////////////////////
  // 通信業者指定
  //////////////////////////////////////////////
  // 検索（時間がかかる）
  modem.sendAT(GF("+COPS=?"));
  ESP_LOGD(TAG, "+COPS=? %d", modem.waitResponse(20000L, GF("+COPS:")));

  // 指定（ドコモ）
  modem.sendAT(GF("+COPS=1,2,\"44010\""));
  ESP_LOGD(TAG, "+COPS=1,2,\"44010\" %d", modem.waitResponse());

  // 設定状況確認
  modem.sendAT(GF("+COPS?"));
  ESP_LOGD(TAG, "+COPS? %d", modem.waitResponse());

  // APN接続
  modem.sendAT(GF("+CGAUTH=1,2,\""), gprsPass, GF("\",\""), gprsUser, '"');
  modem.waitResponse();
  // 設定状況確認
  modem.sendAT(GF("+CGAUTH?"));
  ESP_LOGD(TAG, "+CGAUTH? %d", modem.waitResponse(GF("+CGAUTH:")));

  ////////////////////////////////////////
  // TinyGsmClientSIM7600.h
  // bool gprsConnectImpl をそのままコピーした
  modem.sendAT(GF("+CGDCONT=1,\"IP\",\""), apn, '"', ",\"0.0.0.0\",0,0");
  modem.waitResponse();

  modem.sendAT(GF("+CIPMODE=0"));
  modem.waitResponse();

  modem.sendAT(GF("+CIPSENDMODE=0"));
  modem.waitResponse();

  modem.sendAT(GF("+CIPCCFG=10,0,0,0,1,0,75000"));
  if (modem.waitResponse() != 1) {
    return;
  }

  modem.sendAT(GF("+CIPTIMEOUT="), 75000, ',', 15000, ',', 15000);
  modem.waitResponse();

  modem.sendAT(GF("+NETOPEN"));
  if (modem.waitResponse(75000L, GF(GSM_NL "+NETOPEN: 0")) != 1) {
    return;
  }
  // TinyGsmClientSIM7600.h
  // bool gprsConnectImpl
  ////////////////////////////////////////

  ESP_LOGD(TAG, "GPRS status: %s",
           modem.isGprsConnected() ? "接続中 ..." : "接続していない");

  IPAddress local = modem.localIP();
  ESP_LOGD(TAG, "Local IP: %s", local.toString().c_str());

  ESP_LOGD(TAG, "Signal Status: %d", modem.getRegistrationStatus());
}

void loop() {
  ESP_LOGD(TAG, "...");
  delay(1000);
}
```

### 上記で接続確認後は以下だけで動く

1点問題なのは`TinyGsmClientSIM7600.h`の`gprsConnectImpl`内`+CGAUTH`ATコマンドのパラメータ順序が多分間違っていて、

1. password
1. user

という順序に`TinyGsmClientSIM7600.h`を書き換えないといけないのかなと思うが、今思うと他の人がそんな事をせずに素直に動いているのは不思議。

![](https://i.gyazo.com/9399b27cfb442030991e60d51acf95ca.png)



[セルラーモデムの試食][4]というページを読んでみると、APNの情報はSIMに保存しているのか？とも思うがよく分かってない。

> アクセスポイント情報の登録
単にSIMカードをセットしただけでは動作しません。新しいSIMカードを使うときは、まず最初にアクセスポイント関するデータ(APN、ユーザ名、パスワード、認証方式)を設定する必要があるのです。

```cpp:main.cpp
#include <Arduino.h>
#define SerialMon Serial
#define SerialAT Serial1

#define PIN_TX 27
#define PIN_RX 26
#define UART_BAUD 115200
#define PWR_PIN 4
#define POWER_PIN 25

// #define DUMP_AT_COMMANDS
#define TINY_GSM_DEBUG SerialMon
#define TINY_GSM_MODEM_SIM7600
#include <TinyGsmClient.h>

#define APN "ocn.ne.jp"
#define APN_USER "mobileid@ocn"
#define APN_PASS "mobile"

#ifdef DUMP_AT_COMMANDS
#include <StreamDebugger.h>
StreamDebugger debugger(SerialAT, SerialMon);
Mobile::Mobile() : modem(debugger){};
#else
Mobile::Mobile() : modem(SerialAT){};
#endif

void setup() {
  // 基本モジュールからの電源供給
  pinMode(POWER_PIN, OUTPUT);
  digitalWrite(POWER_PIN, HIGH);
  delay(1000);

  // この操作はここの事だと思う
  // SIM7600_Series_PCIE_Hardware_Design_V1.03.pdf
  // 3.2 PERST#
  // The active low level time impulse on PERST# pin to reset module
  pinMode(PWR_PIN, OUTPUT);
  digitalWrite(PWR_PIN, HIGH);
  delay(500);
  digitalWrite(PWR_PIN, LOW);

  SerialAT.begin(UART_BAUD, SERIAL_8N1, PIN_RX, PIN_TX);
  // 起動までにちょっと待っておかないとATコマンドが受け付けられない
  delay(5000);

  // SIMにPINの設定があればinitに渡せる
  if (!modem.init()) {
    // AT→OKが出なかった
    // 上のdelayが短いとかが考えられる
    ESP_LOGE(MOBILE_TAG, "初期化失敗");
    return;
  }

  // SIM7500_SIM7600 Series_AT Command Manual_V3.00.pdf
  // 4.2.11 AT+CNMP Preferred mode selection
  if (!modem.setNetworkMode(2)) {
    ESP_LOGE(MOBILE_TAG, "ネットワーク選択設定失敗");
    return;
  }

  if (!modem.waitForNetwork()) {
    ESP_LOGE(MOBILE_TAG, "ネットワーク接続失敗");
    return;
  }

  //////////////////////////////////////////////////////////////////
  // TinyGsmClientSIM7600.h
  // https://i.gyazo.com/9399b27cfb442030991e60d51acf95ca.png
  //////////////////////////////////////////////////////////////////
  // sendAT(GF("+CGAUTH=1,2,\""), user, GF("\",\""), pwd, '"');
  //                               ↓                 ↓
  // sendAT(GF("+CGAUTH=1,2,\""), pwd, GF("\",\""), user, '"');
  //////////////////////////////////////////////////////////////////
  //   /*
  //    * GPRS functions
  //    */
  // protected:
  //   bool gprsConnectImpl(const char *apn, const char *user = NULL,
  //                        const char *pwd = NULL) {
  //     gprsDisconnect(); // Make sure we're not connected first

  //     // Define the PDP context

  //     // The CGDCONT commands set up the "external" PDP context

  //     // Set the external authentication
  //     if (user && strlen(user) > 0) {
  //       sendAT(GF("+CGAUTH=1,2,\""), pwd, GF("\",\""), user, '"');
  //       waitResponse();
  //     }

  // APNに接続
  if (!modem.gprsConnect(APN, APN_USER, APN_PASS)) {
    ESP_LOGE(MOBILE_TAG, "APN接続失敗");
    return;
  }

  for (auto c = 0; c < 20; c++) {
    delay(1000);
    const auto quality = modem.getSignalQuality();
    ESP_LOGI(MOBILE_TAG, "受信感度: %d", quality);

    if (quality < 99) {
      ESP_LOGI(MOBILE_TAG, "IPアドレス: %s",
               modem.localIP().toString().c_str());
      return;
    }
  }

}

void loop() {
  ESP_LOGD(TAG, "...");
  delay(1000);
}
```


[1]: https://www.aliexpress.com/item/4001142716386.html
[2]: https://www.simcom.com/product/SIM7600X-PCIE.html
[3]: https://support.emnify.com/hc/en-us/articles/4401884056466-AT-Commands-Introduction-commands-for-a-proper-network-connection-disconnection-and-device-configuration-examples#h_01F798HQ417Z0KX601EEPV0TKT
[4]: http://elm-chan.org/junk/cell/tobyl2.html#brd