[esp32] LoRaでduplex通信

# 使っているもの

https://www.aliexpress.com/item/10000151020525.html

https://github.com/LilyGO/TTGO-LORA32-V2.0/tree/LilyGO-868-V1.0

![pin_layout.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16757/ace796e9-fa25-a84c-a23f-8dae91a7e768.jpeg)


# 問題

https://github.com/sandeepmistry/arduino-LoRa/blob/01bf044b769a2ab276b59fa8cb24e587b24fc787/examples/LoRaDuplex/LoRaDuplex.ino

https://github.com/sandeepmistry/arduino-LoRa/tree/01bf044b769a2ab276b59fa8cb24e587b24fc787/examples/LoRaDuplexCallback

https://www.youtube.com/watch?v=tO1hYr6hNa4

色々LoRaでduplexをやってるサンプルがあるがイマイチ全体像の説明がないので、パット見理解できない。

全くの無知識からなのでLoRaがデフォルトで送受信同時に出来るのかどうかさえ分からない。

# 調べた結果

送受信は同時にはできないが（送信中は受信状態が勝手に切れるっぽい - 多分そうしないと自分の送信内容を受信しちゃう？）、RTOSのタスクを使ってほぼ切れ間なく送受信はできる。

RTOSはESP32に元々入ってる機能で、マルチスレッド（マルチタスク）が出来る（通常だとloopの中をぐるぐる回すしかないのでフラグで制御したりする必要がありカオス）。

上のサンプルコードリンクでは送信元、送信先を`0xAA`とかで指定していて、最初は「送受信を同時にやると自分が送ってる内容を自分で拾うことがあるのか？」と訳が分からなくなっていたが、要は一対多を想定した場合、複数の中の特定のデバイスだけにメッセージを送りたい場合には送信元、送信先をマーキングとして持っておけば良いよねっていう発想だと思う。
自分が送信している内容を自分で受信することはないので、一対一であれば誰がどこに送っているのかは言う必要がない。

# サンプルコード

LoRaデバイス2台に下のコードをアップロードするとお互いに送受信し合う。

```
[D][main.cpp:67] setup(): lora: ok
[D][main.cpp:79] setup(): ready
[D][main.cpp:85] loop(): heart beat..
[D][main.cpp:42] sendTask(): duration: 8051
[D][main.cpp:30] onTxDone(): yes
[D][main.cpp:23] onReceive(): packetSize: 5
[D][main.cpp:51] receiveTask(): text: 59832
[D][main.cpp:42] sendTask(): duration: 11052
[D][main.cpp:30] onTxDone(): yes
[D][main.cpp:23] onReceive(): packetSize: 5
[D][main.cpp:51] receiveTask(): text: 63087
[D][main.cpp:42] sendTask(): duration: 14052
[D][main.cpp:30] onTxDone(): yes
[D][main.cpp:85] loop(): heart beat..
```

`platformio.ini`で[`build_flags = -DCORE_DEBUG_LEVEL=5`という風に指定][1]しておくと[`ESP_LOG*`が使えて便利][2]。

1. LoRaをセットアップ
1. onReceive / onTxDone を指定
1. タスクを開始
1. 直後にreceiveTaskはとりあえずsuspendして眠らせておく
1. onReceiveきっかけでreceiveTaskは再開される
1. sendTaskは3秒おきに実行するが、多分`LoRa.beginPacket()`をした時点で受信待機状態が切れる
1. [`LoRa.endPacket(true)`でasync実行にする][3]ので、処理待ちをせずに走る
1. Tx（送信）完了後`onTxDone`が呼ばれるので、そのタイミングで再度受信待機状態にする
1. を繰り返す

マイクロ秒単位でデータが飛び交うような環境でなければ、受信時の排他処理は要らないのかなと思う。
普通は`packetSize`で`LoRa.read()`を回すのかと思うけど、一対一で3秒に一回程度の送信なので問題はないと思う。

```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:ttgo-lora32-v1]
platform = espressif32
board = ttgo-lora32-v1
framework = arduino
upload_port = /dev/cu.wchusbserial52870239861
monitor_speed = 115200
build_flags = -DCORE_DEBUG_LEVEL=5
lib_deps = 
	sandeepmistry/LoRa@^0.8.0
```

```cpp:main.cpp
#include <Arduino.h>
#include <LoRa.h>

#if CONFIG_FREERTOS_UNICORE
static const BaseType_t appCpu = 0;
#else
static const BaseType_t appCpu = 1;
#endif
// SPI経由でデータを取る場合？
// SPI.begin(SCK,MISO,MOSI,SS);
// と合わせて使う？
// #define SCK 5   // GPIO5  -- SX1278's SCK
// #define MISO 19 // GPIO19 -- SX1278's MISnO
// #define MOSI 27 // GPIO27 -- SX1278's MOSI
#define SS 18  // GPIO18 -- SX1278's CS
#define RST 14 // GPIO14 -- SX1278's RESET
#define DI0 26 // GPIO26 -- SX1278's IRQ(Interrupt Request)
#define BAND 915E6

TaskHandle_t receiveTaskHandle;

void onReceive(int packetSize) {
  ESP_LOGD("onReceive", "packetSize: %d", packetSize);
  vTaskResume(receiveTaskHandle);
}

void onTxDone() {
  // LoRa.endPacket(true); async = true / blocking = false
  // endPacketでtrue = asyncとしてやらないとonTxDoneは呼ばれない
  ESP_LOGD("onTxDone", "yes");
  LoRa.receive();
}

void sendTask(void *parameter) {
  while (true) {
    vTaskDelay(3000 / portTICK_PERIOD_MS);

    auto duration = millis();
    LoRa.beginPacket();
    LoRa.print(String(duration));
    LoRa.endPacket(true);
    ESP_LOGD("sendTask", "duration: %d", duration);
  }
}
void receiveTask(void *parameter) {
  while (true) {
    String text = "";
    while (LoRa.available()) {
      text += (char)LoRa.read();
    }
    ESP_LOGD("receiveTask", "text: %s", text);

    text = "";
    vTaskSuspend(NULL);
  }
}

void setup() {
  delay(5000);
  LoRa.setPins(SS, RST, DI0);

  if (!LoRa.begin(BAND)) {
    ESP_LOGD("setup", "Starting LoRa failed!");
    while (1)
      ;
  }
  ESP_LOGD("setup", "lora: ok");

  LoRa.onReceive(onReceive);
  LoRa.onTxDone(onTxDone);

  xTaskCreatePinnedToCore(sendTask, "sendTask", 2048, NULL, 0, NULL, appCpu);
  xTaskCreatePinnedToCore(receiveTask, "receiveTask", 2048, NULL, 1,
                          &receiveTaskHandle, appCpu);
  while (!receiveTaskHandle)
    ;
  vTaskSuspend(receiveTaskHandle);

  // とりあえず一発目の受信待ち
  // 3秒後の送信で受信待機が切れて、その後またonTxDoneで待機状態になるが...  
  LoRa.receive();

  ESP_LOGD("setup", "ready");
}

void loop() {
  // 原因はよくわからないが、loop内でログを表示してないとRTOSのtask内での
  // ログ表示がされない...
  ESP_LOGD("loop", "heart beat..");
  delay(10000);
}
```

[1]: https://docs.platformio.org/en/latest/platforms/espressif32.html#debug-level
[2]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/log.html#how-to-use-this-library
[3]: https://github.com/sandeepmistry/arduino-LoRa/blob/01bf044b769a2ab276b59fa8cb24e587b24fc787/API.md#end-packet