Private LoRa Gateway経由でnodeのデータをAWS IoTにpublish③ E220でESP32 LoRaデバイス構築

## この記事について

[前回構築したPrivate LoRa GW](https://qiita.com/Saito5656/items/99e5646408f256accd8c)に接続するLoRa DeviceをESP32ボードとLoRaモジュール評価ボードで構築します。下図の色付き部分です。

* edge node用ESP32ボードにLoRaモジュール評価ボードを接続しPrivate LoRaデバイス化
* edge node用ESP32を仮想エッジデバイスと見立てて監視対象の数値をLoRa GWにpub

させます。

<br>

<img width= 700 src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565396/7d49d381-2d99-1f3a-6eb7-352eea5783e8.png>

<br>

## デバイス

### LoRaモジュール評価ボード  

[LoRaモジュール評価ボード（E220-900T22S（JP）-EV1）](https://dragon-torch.tech/cat-components/rf-modules/e220-900t22s-jp-ev1/)
[README.md・サンプルコード](https://dragon-torch.tech/wp-content/uploads/2023/12/esp32_e220900t22s_jp_lib_202312.zip)

### ESP32ボード
あまり使用例がないボードですがAmazonで入手可能な[ACEIRMC D1 Mini NodeMCU ESP32 ESP-WROOM-32 IoT 開発ボード](https://www.amazon.co.jp/gp/product/B08PNWB81Z/ref=ppx_yo_dt_b_search_asin_title?ie=UTF8&psc=1)を使いました。

当時参照したDocのURLが探しても見つからない為、見つけたら後追いでリンクを貼ります。

本稿の話とは絡まないですが、[たまねぎブログさん](https://tamanegi.digick.jp/computer-embedded/mcuboa/miniesp32/#toc24)、[行き当たりばったり電子工作さん](https://jnz-tech.hatenablog.com/entry/2023/10/01/230926)が比較的最近丁寧なレポートをまとめておられますので利用検討の際は助けになるかと思います。

## Arduino  

前回まで2回のPythonによる構築時に設定したsetting.iniの内容をデータとしてアップロードするためにファイルアップローダーpluginが必要になります。

記事執筆時点(2024.07.07)でもArduino IDE 2.X系でファイルアップローダーが使えなかったので1.x系を使用します。

ボードについては[ESP32 DEV MODULE 3.x系のバグは解消していない](https://zenn.dev/nyazuki/articles/ebe56234d81e95)ようなので2.0.x系の利用を推奨します(というか修正できなかった)。

**IDE**：1.8.18  
**ボード**：ESP32 DEV MODULE 2.0.17
**追加ライブラリ**：M5Core2 0.1.7
**[ファイルアップローダーplugin](https://microcontrollerslab.com/install-esp32-filesystem-uploader-in-arduino-ide-spiffs/)**：ESP32FS-1.1

## サンプルスケッチ

### 構成  
[ダウンロードファイル](https://dragon-torch.tech/wp-content/uploads/2023/12/esp32_e220900t22s_jp_lib_202312.zip)の内訳です。

* README：esp32_e220900t22s_jp_lib/docs/README.md  
* スケッチsample code：esp32_e220900t22s_jp_lib/example/lora_multi_task/lora_multi_task.ino  
* ライブラリー：esp32_e220900t22s_jp_lib/src  
* iniファイル：esp32_e220900t22s_jp_lib/docs/e220900t22s_jp_lora_config.ini  

### スケッチ名変更

今回スケッチ名をE220_D1miniESP32_aws-2としてlora_multi_task.inoのファイル名を変更します。

iniファイルを格納するsub directoryをdataとするとスケッチdirectoryの構成は以下のようになります。

```sh
E220_D1miniESP32_aws-2
 ┣ data
 ┃ ┗ e220900t22s_jp_lora_config.ini
 ┣ E220_D1miniESP32_aws-2.ino
 ┣ esp32_e220900t22s_jp_lib.cpp
 ┗ esp32_e220900t22s_jp_lib.h
```

## スケッチ

スケッチ名：E220_D1miniESP32_aws-2のプロジェクトを書き込んだESP32ボードで[構築したPrivate LoRa GW](https://qiita.com/Saito5656/items/99e5646408f256accd8c) とLoRa通信するためにサンプルファイルを一部変更します。

### iniファイル  
#### own_address
Pi GWでは0、Pi LoRa Deviceは1にしたので2にします。

#### wor_cycle  
Pi GWに合わせて3000にします。

#### encryption_key
Pi GWに合わせて1234にします。

#### target_address
ffffにします。

```sh:/E220_D1miniESP32_aws-2/e220900t22s_jp_lora_config.ini
own_address=2
baud_rate=9600
bw=125
sf=9
subpacket_size=200
transmitting_power=13
own_channel=0
wor_cycle=3000
encryption_key=1234
target_address=ffff
target_channel=0
```

 ### lib.cpp

主にiniファイルの変更箇所を変更します。出力も追加します。
 
```cpp:/E220_D1miniESP32_aws-2/esp32_e220900t22s_jp_lib.cpp(修正後)
.
.
void CLoRa::SetDefaultConfigValue(struct LoRaConfigItem_t &config) {
  const LoRaConfigItem_t default_config = {
      0x0000, // own_address 0
      0b011, // baud_rate 9600 bps
      0b10000, // air_data_rate SF:9 BW:125
      0b00, // subpacket_size 200
      0b0, // rssi_ambient_noise_flag 無効
      0b0, // transmission_pause_flag 有効
      0b01, // transmitting_power 13 dBm
      0x00, // own_channel 0
      0b1, // rssi_byte_flag 有効
      0b1, // transmission_method_type 固定送信モード
      0b0, // lbt_flag 有効
      0b101, // wor_cycle 3000 msに変更
      0x0000, // encryption_key 0
      0x0000, // target_address 0
      0x00}; // target_channel 0

  config = default_config;
}
.
.
int CLoRa::OpenConfigFile(const char *filename,
                          struct LoRaConfigItem_t &config) {
  int ret = 0;

  // フラッシュメモリのファイルを開く
  File file = SPIFFS.open(filename, FILE_READ);
  if (!file) {
    SerialMon.printf("file open failed.\n");
    return 1;
  }

  while (file.available()) {
    String key = file.readStringUntil('=');
    String val = file.readStringUntil('\n');
//追加
    SerialMon.printf("%s", key.c_str());
    SerialMon.printf("%s", val.c_str());

    key.toLowerCase();
    // 設定値を内部変数に読み取る
    ReadConfigValue(key.c_str(), val.c_str());
  }

  // 設定値をセットする
  SetConfigValue(config);
//追加
  SerialMon.printf("Sat default values.\n");

  return ret;
}
.
.

  // wor_cycle
  switch (wor_cycle_val) {
  case 500:
    config.wor_cycle = 0b000;
    break;
  case 1000:
    config.wor_cycle = 0b001;
    break;
  case 1500:
    config.wor_cycle = 0b010;
    break;
  case 2000:
    config.wor_cycle = 0b011;
    break;
//3000を追加
  case 3000:
    config.wor_cycle = 0b101;
    break;
  default:
    err = 1;
    SerialMon.printf("wor_cycle invalid value.\n");
    SerialMon.printf("default wor_cycle value is used.\n");
    break;
  }
.
.
```

### lib.h

ボードのピンアサインを変更します。

LoRaモジュール評価ボード（E220-900T22S（JP）-EV1）のピンをこれらのport番号のピンと接続します。

この5種類以外にESP32とE220ボードのVCC(5V)とGNDも接続します。
 
```cpp:/E220_D1miniESP32_aws-2/esp32_e220900t22s_jp_lib.h(修正後)
.
.
// E220-900T22S(JP)へのピンアサイン
#define LoRa_ModeSettingPin_M0 23
#define LoRa_ModeSettingPin_M1 19
#define LoRa_RxPin 16
#define LoRa_TxPin 17
#define LoRa_AUXPin 18
.
,
```


### E220_D1miniESP32_aws-2.ino

主な変更はLoRaSendTask()のロジックです。
サンプルコードではconsole入力をpubしますが今回は0～1000の乱数を発生させて10秒毎にpubします。

実使用場面を想定してwifiも無効にしておきます。

識別子は、own_addressを2にしたのでtopicのサフィックスとなるdevice_noも2にします。

修正したコードは以下です。
 
```cpp:/E220_D1miniESP32_aws-2/E220_D1miniESP32_aws-2.ino(修正後)
#include "esp32_e220900t22s_jp_lib.h"
#include <Arduino.h>
#include <FS.h>
#include <SD.h>
#include <SPI.h>
//参照追加
#include <WiFi.h>


CLoRa lora;
struct LoRaConfigItem_t config;
struct RecvFrameE220900T22SJP_t data;

/** prototype declaration **/
void LoRaRecvTask(void *pvParameters);
void LoRaSendTask(void *pvParameters);
void ReadDataFromConsole(char *msg, int max_msg_len);

void setup() {
  // put your setup code here, to run once:
  SerialMon.begin(9600);
  delay(1000); // SerialMon init wait
  SerialMon.println();
  //wifi無効化
  WiFi.disconnect(true);

  // LoRa設定値の読み込み
  if (lora.LoadConfigSetting(CONFIG_FILENAME, config)) {
    SerialMon.printf("Loading Configfile failed. The default value is set.\n");
  } else {
    SerialMon.printf("Loading Configfile succeeded.\n");
  }

  // E220-900T22S(JP)へのLoRa初期設定
  if (lora.InitLoRaModule(config)) {
    SerialMon.printf("init error\n");
    return;
  }

  // ノーマルモード(M0=0,M1=0)へ移行する
  lora.SwitchToNormalMode();

  // マルチタスク
  xTaskCreateUniversal(LoRaRecvTask, "LoRaRecvTask", 8192, NULL, 1, NULL,
                       APP_CPU_NUM);
  xTaskCreateUniversal(LoRaSendTask, "LoRaSendTask", 8192, NULL, 1, NULL,
                       APP_CPU_NUM);
}

void loop() {
}

void LoRaRecvTask(void *pvParameters) {
  while (1) {
    if (lora.RecieveFrame(&data) == 0) {
      SerialMon.printf("recv data:\n");
      for (int i = 0; i < data.recv_data_len; i++) {
        SerialMon.printf("%c", data.recv_data[i]);
      }
      SerialMon.printf("\n");
      SerialMon.printf("hex dump:\n");
      for (int i = 0; i < data.recv_data_len; i++) {
        SerialMon.printf("%02x ", data.recv_data[i]);
      }
      SerialMon.printf("\n");
      SerialMon.printf("RSSI: %d dBm\n", data.rssi);
      SerialMon.printf("\n");

      SerialMon.flush();
    }

    delay(1);
  }
}

void LoRaSendTask(void *pvParameters) {
  while (1) {
    //ランダムな整数を生成してmsgとして出力
    int count = random(0, 1001);
    char msg[200] = {0};

    snprintf(msg, sizeof(msg), "%08d", count);
    // device No.2をmsg文字列に追加
    strcat(msg, "\n2\n");

    if (lora.SendFrame(config, (uint8_t *)msg, strlen(msg)) == 0) {
      SerialMon.printf("send succeeded.\n");
      SerialMon.printf("\n");
    } else {
      SerialMon.printf("send failed.\n");
      SerialMon.printf("\n");
    }

    SerialMon.flush();
    delay(10000);
  }
}

void ReadDataFromConsole(char *msg, int max_msg_len) {
  int len = 0;
  char *start_p = msg;

  while (len < max_msg_len) {
    if (SerialMon.available() > 0) {
      char incoming_byte = SerialMon.read();
      if (incoming_byte == 0x00 || incoming_byte > 0x7F)
        continue;
      *(start_p + len) = incoming_byte;
      // 最短で3文字(1文字 + CR LF)
      if (incoming_byte == 0x0a && len >= 2 && (*(start_p + len - 1)) == 0x0d) {
        break;
      }
      len++;
    }
    delay(1);
  }
  // msgからCR LFを削除
  len = strlen(msg);
  for (int i = 0; i < len; i++) {
    if (msg[i] == 0x0D || msg[i] == 0x0A) {
      msg[i] = '\0';
    }
  }
}
```

### iniファイル書き込み

ツール > ESP32 Sketch Data Upload を実行すると/dataに格納したiniファイルがuploadされます。

### スケッチ書込み

通常の「マイコンボードに書き込む」を実行します。

## 実行結果 

通常のArduino sketchと同様に書き込みが正常終了した時点で起動しています。

シリアルモニタでも出力を確認できます。

<img width= 700 src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565396/5c02351e-092b-3e7f-b09c-2fc379e7cff3.png>

Private LoRa GWのgateway.pyを起動させるとsubします。

```sh:gateway.py
user@privateLoRaAP1:~ $ python main.py /dev/ttyS0 --rssi
serial port:
/dev/ttyS0
receive waiting...
payload: b''
Connected
topic:  privateLoRaAP1/LoRaDev2
count :  00000897
{'Timestamp': 1720220384, 'count': 897}
topic:  privateLoRaAP1/LoRaDev2
count :  00000309
{'Timestamp': 1720220394, 'count': 309}
```

ラズパイ LoRa Deviceのsend.pyも実行します。
GW brokerで乱数と8888がsubされているのが確認できます。

```sh:gateway.py
.
.
.
{'Timestamp': 1720220485, 'count': 863}
topic:  privateLoRaAP1/LoRaDev1
count :  8888
{'Timestamp': 1720220489, 'count': 8888}
topic:  privateLoRaAP1/LoRaDev2
count :  00000068
{'Timestamp': 1720220495, 'count': 68}
topic:  privateLoRaAP1/LoRaDev1
count :  8888
{'Timestamp': 1720220499, 'count': 8888}
topic:  privateLoRaAP1/LoRaDev2
count :  00000453
{'Timestamp': 1720220505, 'count': 453}
.
.
.
```

AWS IoT CoreのMQTTクライアントでもdevice_no：1(topic：privateLoRaAP1/LoRaDev1)とdevice_no：2(topic：privateLoRaAP1/LoRaDev2)からsub出来ているのが確認できました。

<img width= 700 src = https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565396/c56fd2b2-6261-be03-8940-ad58446a3e2e.png>