ESP32でMQTTブローカーサーバーを動かす その2 ローカルWebDav保存

概要

前回はMQTTブローカーサーバーとして最低限の動作確認をしてみました。今回は実用的な構成で利用できるかを検討したいと思います。

ローカル保存とWebDav化

たんなるMQTTブローカーサーバーであればESP32を利用する必要がないので、ローカルに保存してみたいと思います。ブローカーサーバーではなく、たんなるMQTTを受信してローカルに保存するサーバーです。

上記のコードを使って実験してみたいと思います。SPIFFS領域に保存をして、それをWebDavでアクセスできる構成にします。

#include <WiFi.h>
#include <SPIFFS.h>
#include <ESPWebDAV.h>
#include<sMQTTBroker.h>

ESPWebDAV dav;
WiFiServer tcp(80);

#define QUEUE_LENGTH 100
QueueHandle_t xQueueRec;

struct RecData {
  char data[1024];
};

void taskRec(void *pvParameters) {
  struct tm timeInfoOld = {};
  struct tm timeInfo;
  File file;

  while (1) {
    if (getLocalTime(&timeInfo)) {
      if (timeInfo.tm_min != timeInfoOld.tm_min || timeInfo.tm_hour != timeInfoOld.tm_hour) {
        Serial.print("Local Time  : ");
        Serial.printf("%04d-%02d-%02d ", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday);
        Serial.printf("%02d:%02d:%02d\n", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
        timeInfoOld = timeInfo;
        char filename[24];
        if (file) {
          file.flush();
          file.close();
        }
        snprintf(filename, 24, "/%04d-%02d-%02d_%02d%02d.txt", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min);
        file = SPIFFS.open(filename, "w");
      }
    }
    RecData recData;
    if (xQueueReceive(xQueueRec, &recData, 0) == pdTRUE) {
      file.println(recData.data);
    }

    dav.handleClient();
    delay(1);
  }
}

class MyBroker: public sMQTTBroker {
  public:
    bool onConnect(sMQTTClient *client, const std::string &username, const std::string &password) {
      Serial.printf("onConnect clientId=%s username=%s\n", client->getClientId().c_str(), username.c_str());
      return true;
    };

    void onRemove(sMQTTClient *client) {
      Serial.printf("onRemove clientId=%s\n", client->getClientId().c_str());
    };

    void onPublish(sMQTTClient *client, const std::string &topic, const std::string &payload) {
      struct tm timeInfo;
      String recText = "";
      char tempStr[100];
      RecData recData = {};

      getLocalTime(&timeInfo);
      sprintf(tempStr, "%04d-%02d-%02d %02d:%02d:%02d", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);

      recText = tempStr;
      recText += "\t";
      recText += client->getClientId().c_str();
      recText += "\t";
      recText += topic.c_str();
      recText += "\t";
      recText += payload.c_str();

      strncpy(recData.data, recText.c_str(), sizeof(recData.data) - 1);
      xQueueSend(xQueueRec, &recData, 0);

      Serial.printf("onPublish clientId=%s topic=%s payload=%s\n", client->getClientId().c_str(), topic.c_str(), payload.c_str());
    }

    bool onEvent(sMQTTEvent *event) {
      switch (event->Type()) {
        case NewClient_sMQTTEventType:
          {
            sMQTTNewClientEvent *e = (sMQTTNewClientEvent*)event;
            e->Login();
            e->Password();
          }
          break;

        case LostConnect_sMQTTEventType:
          WiFi.reconnect();
          break;
      }

      return true;
    }
};

MyBroker broker;

void setup() {
  Serial.begin(115200);
  WiFi.begin();
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();
  Serial.print("IP Address  : ");
  Serial.println(WiFi.localIP());
  Serial.print("WebDav      : file://");
  Serial.print(WiFi.localIP());
  Serial.println("/DavWWWRoot");

  // SPIFFS begin
  if (!SPIFFS.begin()) {
    // SPIFFS is unformatted
    Serial.print("SPIFFS Format ... (please wait)");
    delay(100);
    SPIFFS.format();
    Serial.println("Down");
    ESP.restart();
  }

  // NTP begin
  configTime(9 * 3600, 0, "pool.ntp.org");
  struct tm timeInfo;
  if (getLocalTime(&timeInfo)) {
    Serial.print("Local Time  : ");
    Serial.printf("%04d-%02d-%02d ", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday);
    Serial.printf("%02d:%02d:%02d\n", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
  }

  const unsigned short mqttPort = 1883;
  broker.init(mqttPort);

  // WebDav begin
  tcp.begin();
  dav.begin(&tcp, &SPIFFS);

  // Rec
  xQueueRec = xQueueCreate(QUEUE_LENGTH, sizeof(RecData));
  xTaskCreateUniversal(
    taskRec,        // タスク関数
    "taskRec",      // タスク名(あまり意味はない)
    8192,           // スタックサイズ
    NULL,           // 引数
    1,              // 優先度(loopが2で大きい方が高い)
    NULL,           // タスクハンドル
    APP_CPU_NUM     // 実行するCPU(PRO_CPU_NUM or APP_CPU_NUM)
  );
}

void loop() {
  broker.update();
}

何点か気をつけるポイントがあります。保存をする場合には結構時間がかかります。そのため受信した場所でそのまま保存をすると処理落ちの原因となります。今回はキューを利用して受信をしたら100個まではメモリ上に保存をしておき、別タスクでゆっくり保存をするようにしました。

受信部分

    void onPublish(sMQTTClient *client, const std::string &topic, const std::string &payload) {
      struct tm timeInfo;
      String recText = "";
      char tempStr[100];
      RecData recData = {};

      getLocalTime(&timeInfo);
      sprintf(tempStr, "%04d-%02d-%02d %02d:%02d:%02d", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);

      recText = tempStr;
      recText += "\t";
      recText += client->getClientId().c_str();
      recText += "\t";
      recText += topic.c_str();
      recText += "\t";
      recText += payload.c_str();

      strncpy(recData.data, recText.c_str(), sizeof(recData.data) - 1);
      xQueueSend(xQueueRec, &recData, 0);

      Serial.printf("onPublish clientId=%s topic=%s payload=%s\n", client->getClientId().c_str(), topic.c_str(), payload.c_str());
    }

このMQTTライブラリはイベント駆動形なのできれいに実装できていいですね。受信したときにタブ区切りの文字列にしてからxQueueSendでキューに保存しています。

保存部分

void taskRec(void *pvParameters) {
  struct tm timeInfoOld = {};
  struct tm timeInfo;
  File file;

  while (1) {
    if (getLocalTime(&timeInfo)) {
      if (timeInfo.tm_min != timeInfoOld.tm_min || timeInfo.tm_hour != timeInfoOld.tm_hour) {
        Serial.print("Local Time  : ");
        Serial.printf("%04d-%02d-%02d ", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday);
        Serial.printf("%02d:%02d:%02d\n", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
        timeInfoOld = timeInfo;
        char filename[24];
        if (file) {
          file.flush();
          file.close();
        }
        snprintf(filename, 24, "/%04d-%02d-%02d_%02d%02d.txt", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min);
        file = SPIFFS.open(filename, "w");
      }
    }
    RecData recData;
    if (xQueueReceive(xQueueRec, &recData, 0) == pdTRUE) {
      file.println(recData.data);
    }

    dav.handleClient();
    delay(1);
  }
}

ここでいろいろ処理をしてしまっています。まず毎分別ファイルに保存しているので、時間のチェックをして、分が進んだたら新しいファイルに切り替えています。

保存する部分はキューから受信して、そのままファイルに書き出しているだけです。ただ、本当はキューがなくなるまでループしたほうがいいのですが、1件受信したら次のループまで待ちます。

dav.handleClient()でWebDavの処理をしています。この関数は定期的に呼ばれないとWebDavがおかしくなるのですが、優先順位を下げるためにここにとりあえず置いています。

ここのタスクは結構問題があって、delay(1)があるので1ループで1ミリ秒以上かかります。つまり受信が大量に来た場合にキューが満杯になることがあります。

SPIFFS初期化

  // SPIFFS begin
  if (!SPIFFS.begin()) {
    // SPIFFS is unformatted
    Serial.print("SPIFFS Format ... (please wait)");
    delay(100);
    SPIFFS.format();
    Serial.println("Down");
    ESP.restart();
  }

地味に重要なのが上記のコードです。SPIFFSを初期化して、エラーになった場合にはフォーマットをする必要があります。初回実行時にはフォーマットが必要で結構時間がかかります。

また、デフォルトの設定ですとSPIFFSは1.5MBぐらいしか容量がありませんのでNo OTAなどを選択して容量を増やしてあげたほうがいいかもしれません。

UIFlowから実験

こんな感じのブロックで実験しました。いろいろ実験したのですが、クライアントIDは毎回ランダムぐらいのほうがわかりやすかったです。もちろん1台しかなければ決め打ちで問題ありませんが、データの区切りとか確認するのには違うほうが楽です。

あとは、送信と受信のタイムラグがでますので、送信時のタイムスタンプをデータに含めるのは有効でした。

送信間隔がおかしい?

どうもUIFlowから送信するとおかしいです。1秒インターバルで送信しても、2分で119個ぐらいしか受信していません。若干時間精度がずれている気がします。

0.1秒間隔で送信するとどんどん処理が重くなっていき、秒数件ぐらいしか送信できない状態になりました。

Pythonからの送信

結局Pythonから送信するテストをしてみました。適当なサイトで送信例を調べて1秒から徐々に送付頻度をあげていって確認しました。

間隔頻度状況
1秒間隔1件/秒問題なし
0.1秒間隔10件/秒問題なし
0.01秒間隔100件/秒たまにデータ欠損あり

上記のような結果でした。0.01秒間隔が惜しかったですが、微妙にデータ欠損がありました。それ以上の頻度であれば受信が遅れることはあるのですが、データ欠損はありません。

2022-11-05 21:47:38	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:36	61
2022-11-05 21:47:38	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:36	62
2022-11-05 21:47:38	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:36	63
2022-11-05 21:47:38	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:36	64
2022-11-05 21:47:38	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:37	65
2022-11-05 21:47:38	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:37	66
2022-11-05 21:47:38	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:37	67
2022-11-05 21:47:38	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:37	68
2022-11-05 21:47:38	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:37	69
2022-11-05 21:47:39	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:37	70
2022-11-05 21:47:39	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:37	71
2022-11-05 21:47:39	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:37	72
2022-11-05 21:47:39	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:37	73
2022-11-05 21:47:39	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:38	74
2022-11-05 21:47:39	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:38	75
2022-11-05 21:47:39	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:38	76
2022-11-05 21:47:39	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:38	77
2022-11-05 21:47:40	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:38	78
2022-11-05 21:47:40	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:38	79
2022-11-05 21:47:40	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:38	80
2022-11-05 21:47:40	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:38	81
2022-11-05 21:47:40	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:38	82
2022-11-05 21:47:40	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:39	83
2022-11-05 21:47:40	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:39	84
2022-11-05 21:47:40	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:39	85
2022-11-05 21:47:40	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:39	86
2022-11-05 21:47:40	UIFlow	UIFlowTimer	2022-11-05 Sat 21:47:39	87

UIFlowから投げた例ですが、一番左の時間が受信した時間、右側の時間が送信時間で、一番右がデータ欠落確認用の連番です。

送受信で2から3秒程度の遅延が発生しています。Wi-Fiの環境等によってさらに3秒ぐらい受信がなくなってから一斉に受信するときもあります。データ更新頻度によりますが、秒単位で確認が必要な場合には送信時間をデータに含めたほうが良さそうです。

15秒間隔とかある程度時間間隔が開いている場合には受信時間でも構わないと思います。

WebDav

IP Address  : 192.168.1.12
WebDav      : file://192.168.1.12/DavWWWRoot

起動時にIPアドレスと共に、WebDavのアドレスも表示させていますのでこのアドレスでアクセスしてみてください。Windowsの場合には接続しているネットワークの種類がプライベートネットワークになっていないとWebDavでのファイルアクセスはできないと思います。

処理の優先順位を下げているので若干重いですが、エクスプローラーなどでESP32の内部に保存したファイル一覧が開けると思います。

注意点としては、最新のファイルは書き込み途中ですので開かないでください。1分前のファイルであれば開いても問題ありません。そしてエディタなどで直接開くよりは、手元にコピーしてから開いたほうが安全です。

ファイル転送はFTPなどもESP32で実装可能ですがWebDavで動かすのでアプリが必要ないので楽だと思います。

ツール追加

#include <WiFi.h>
#include <SPIFFS.h>
#include <ESPWebDAV.h>
#include<sMQTTBroker.h>

ESPWebDAV dav;
WiFiServer tcp(80);

#define QUEUE_LENGTH 100
QueueHandle_t xQueueRec;

bool dispDebug = false;

struct RecData {
  char data[1024];
};

void taskRec(void *pvParameters) {
  struct tm timeInfoOld = {};
  struct tm timeInfo;
  File file;

  while (1) {
    if (getLocalTime(&timeInfo)) {
      if (timeInfo.tm_min != timeInfoOld.tm_min || timeInfo.tm_hour != timeInfoOld.tm_hour) {
        if (dispDebug) {
          Serial.print("Local Time  : ");
          Serial.printf("%04d-%02d-%02d ", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday);
          Serial.printf("%02d:%02d:%02d\n", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
        }
        timeInfoOld = timeInfo;
        char filename[24];
        if (file) {
          file.flush();
          file.close();
        }
        snprintf(filename, 24, "/%04d-%02d-%02d_%02d%02d.txt", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min);
        file = SPIFFS.open(filename, "w");
      }
    }
    RecData recData;
    if (xQueueReceive(xQueueRec, &recData, 0) == pdTRUE) {
      file.println(recData.data);
    }

    dav.handleClient();
    delay(1);
  }
}

class MyBroker: public sMQTTBroker {
  public:
    bool onConnect(sMQTTClient *client, const std::string &username, const std::string &password) {
      if (dispDebug) {
        Serial.printf("onConnect clientId=%s username=%s\n", client->getClientId().c_str(), username.c_str());
      }
      return true;
    };

    void onRemove(sMQTTClient *client) {
      if (dispDebug) {
        Serial.printf("onRemove clientId=%s\n", client->getClientId().c_str());
      }
    };

    void onPublish(sMQTTClient *client, const std::string &topic, const std::string &payload) {
      struct tm timeInfo;
      String recText = "";
      char tempStr[100];
      RecData recData = {};

      getLocalTime(&timeInfo);
      sprintf(tempStr, "%04d-%02d-%02d %02d:%02d:%02d", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday, timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);

      recText = tempStr;
      recText += "\t";
      recText += client->getClientId().c_str();
      recText += "\t";
      recText += topic.c_str();
      recText += "\t";
      recText += payload.c_str();

      strncpy(recData.data, recText.c_str(), sizeof(recData.data) - 1);
      xQueueSend(xQueueRec, &recData, 0);

      if (dispDebug) {
        Serial.printf("onPublish clientId=%s topic=%s payload=%s\n", client->getClientId().c_str(), topic.c_str(), payload.c_str());
      }
    }

    bool onEvent(sMQTTEvent *event) {
      switch (event->Type()) {
        case NewClient_sMQTTEventType:
          {
            sMQTTNewClientEvent *e = (sMQTTNewClientEvent*)event;
            e->Login();
            e->Password();
          }
          break;

        case LostConnect_sMQTTEventType:
          WiFi.reconnect();
          break;
      }

      return true;
    }
};

MyBroker broker;

void taskTool(void *pvParameters);

void setup() {
  Serial.begin(115200);

  // Tool Task
  xTaskCreateUniversal(
    taskTool,       // タスク関数
    "taskTool",     // タスク名(あまり意味はない)
    8192,           // スタックサイズ
    NULL,           // 引数
    1,              // 優先度(loopが2で大きい方が高い)
    NULL,           // タスクハンドル
    PRO_CPU_NUM     // 実行するCPU(PRO_CPU_NUM or APP_CPU_NUM)
  );

  WiFi.begin();
  while (WiFi.status() != WL_CONNECTED) {
    Serial.println("WiFi connecting. SET [WIFI SSID KEY] command");
    delay(5000);
  }
  Serial.println();
  Serial.print("IP Address  : ");
  Serial.println(WiFi.localIP());
  Serial.print("WebDav      : file://");
  Serial.print(WiFi.localIP());
  Serial.println("/DavWWWRoot");

  // SPIFFS begin
  if (!SPIFFS.begin()) {
    // SPIFFS is unformatted
    Serial.print("SPIFFS Format ... (please wait)");
    delay(100);
    SPIFFS.format();
    Serial.println("Down");
    ESP.restart();
  }

  // NTP begin
  configTime(9 * 3600, 0, "pool.ntp.org");
  struct tm timeInfo;
  if (getLocalTime(&timeInfo)) {
    Serial.print("Local Time  : ");
    Serial.printf("%04d-%02d-%02d ", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday);
    Serial.printf("%02d:%02d:%02d\n", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
  }

  const unsigned short mqttPort = 1883;
  broker.init(mqttPort);

  // WebDav begin
  tcp.begin();
  dav.begin(&tcp, &SPIFFS);

  // Rec
  xQueueRec = xQueueCreate(QUEUE_LENGTH, sizeof(RecData));
  xTaskCreateUniversal(
    taskRec,        // タスク関数
    "taskRec",      // タスク名(あまり意味はない)
    8192,           // スタックサイズ
    NULL,           // 引数
    1,              // 優先度(loopが2で大きい方が高い)
    NULL,           // タスクハンドル
    APP_CPU_NUM     // 実行するCPU(PRO_CPU_NUM or APP_CPU_NUM)
  );
}

void loop() {
  broker.update();
}

void taskTool(void *pvParameters) {
  while (1) {
    while (Serial.available()) {
      char input[256];
      String command = Serial.readStringUntil('\n');
      command.trim();
      strncpy(input, command.c_str(), sizeof(input) - 1);
      char* command0 = strtok(input, " ");
      command = command0;
      command0 = strtok(NULL, " ");
      String command2 = command0;
      command0 = strtok(NULL, " ");
      String command3 = command0;
      command0 = strtok(NULL, " ");
      String command4 = command0;

      if (command == "") {
        // Skip
      } else if (command == "DISP") {
        dispDebug = true;
      } else if (command == "NODISP") {
        dispDebug = false;
      } else if (command == "RESET") {
        ESP.restart();
      } else if (command == "INFO") {
        uint8_t mac[6];
        esp_read_mac(mac, ESP_MAC_WIFI_STA);
        Serial.printf("Mac Address : %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
        Serial.printf("IP Address  : ");
        Serial.println(WiFi.localIP());
        struct tm timeInfo;
        if (getLocalTime(&timeInfo)) {
          Serial.print("Local Time  : ");
          Serial.printf("%04d-%02d-%02d ", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday);
          Serial.printf("%02d:%02d:%02d\n", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
        }
      } else if (command == "MEM") {
        Serial.printf("esp_get_free_heap_size()                              : %6d\n", esp_get_free_heap_size() );
        Serial.printf("esp_get_minimum_free_heap_size()                      : %6d\n", esp_get_minimum_free_heap_size() );
      } else if (command == "WIFI") {
        WiFi.begin(command2.c_str(), command3.c_str());
        Serial.println(" Set WiFi.begin()");
      } else {
        Serial.println(" ?                                 : This print");
        Serial.println(" INFO                              : Print Info");
        Serial.println(" MEM                               : Memory Info");
        Serial.println(" WIFI [SSID] [KEY]                 : Connect Wi-Fi(default Last SSID & Key)");
        Serial.println(" RESET                             : Reset ESP32");
        Serial.println(" DISP                              : Display Debug Print");
        Serial.println(" NODISP                            : No Display Debug Print");
      }
      Serial.println(">");
    }
    delay(1);
  }
}

Wi-Fiアクセスポイントとかの設定をシリアルからできるようにかんたんなツールを仕込みました。

ツール起動

void setup() {
  Serial.begin(115200);

  // Tool Task
  xTaskCreateUniversal(
    taskTool,       // タスク関数
    "taskTool",     // タスク名(あまり意味はない)
    8192,           // スタックサイズ
    NULL,           // 引数
    1,              // 優先度(loopが2で大きい方が高い)
    NULL,           // タスクハンドル
    PRO_CPU_NUM     // 実行するCPU(PRO_CPU_NUM or APP_CPU_NUM)
  );

起動した直後にツール用のタスクを起動します。

ツールタスク

void taskTool(void *pvParameters) {
  while (1) {
    while (Serial.available()) {
      char input[256];
      String command = Serial.readStringUntil('\n');
      command.trim();
      strncpy(input, command.c_str(), sizeof(input) - 1);
      char* command0 = strtok(input, " ");
      command = command0;
      command0 = strtok(NULL, " ");
      String command2 = command0;
      command0 = strtok(NULL, " ");
      String command3 = command0;
      command0 = strtok(NULL, " ");
      String command4 = command0;

      if (command == "") {
        // Skip
      } else if (command == "DISP") {
        dispDebug = true;
      } else if (command == "NODISP") {
        dispDebug = false;
      } else if (command == "RESET") {
        ESP.restart();
      } else if (command == "INFO") {
        uint8_t mac[6];
        esp_read_mac(mac, ESP_MAC_WIFI_STA);
        Serial.printf("Mac Address : %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
        Serial.printf("IP Address  : ");
        Serial.println(WiFi.localIP());
        struct tm timeInfo;
        if (getLocalTime(&timeInfo)) {
          Serial.print("Local Time  : ");
          Serial.printf("%04d-%02d-%02d ", timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday);
          Serial.printf("%02d:%02d:%02d\n", timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
        }
      } else if (command == "MEM") {
        Serial.printf("esp_get_free_heap_size()                              : %6d\n", esp_get_free_heap_size() );
        Serial.printf("esp_get_minimum_free_heap_size()                      : %6d\n", esp_get_minimum_free_heap_size() );
      } else if (command == "WIFI") {
        WiFi.begin(command2.c_str(), command3.c_str());
        Serial.println(" Set WiFi.begin()");
      } else {
        Serial.println(" ?                                 : This print");
        Serial.println(" INFO                              : Print Info");
        Serial.println(" MEM                               : Memory Info");
        Serial.println(" WIFI [SSID] [KEY]                 : Connect Wi-Fi(default Last SSID & Key)");
        Serial.println(" RESET                             : Reset ESP32");
        Serial.println(" DISP                              : Display Debug Print");
        Serial.println(" NODISP                            : No Display Debug Print");
      }
      Serial.println(">");
    }
    delay(1);
  }
}

とりあえずはWi-Fiアクセスポイントの設定が重要なのですが、他にも少し仕込んでいます。デフォルトは出力をオフにして、DISPコマンドを使うとログを表示するように変更してあります。

      if (dispDebug) {
        Serial.printf("onPublish clientId=%s topic=%s payload=%s\n", client->getClientId().c_str(), topic.c_str(), payload.c_str());
      }

単純に全部のデバッグ出力にif文を追加しています。

あと地味にRESETコマンドとかも便利です。

Wi-Fi接続部分

  WiFi.begin();
  while (WiFi.status() != WL_CONNECTED) {
    Serial.println("WiFi connecting. SET [WIFI SSID KEY] command");
    delay(5000);
  }

別タスクでシリアルからWIFIコマンドを受け取ってSSIDとKEYを設定しているので、setupの中では無限ループで構いません。接続されるとそれ以降の処理に進みますが、未接続だと無限ループでシリアルからの入力しか受け付けません。

まとめ

おもったよりESP32でMQTTブローカーサーバーが実用的に運用できる可能性がわかりました。今回作ったものは最適化していないのでいまいちなところはありますが、秒10件ぐらいであればトラブルなく動きそうな気がします。

個人的には今回のは試作ですのでそのまま使うのはなしだと思いますが、目的に合わせて手を入れやすい最低限のベースにはなると思います。以下懸念点を上げておきます。

  • 各タスクの動くCPUと優先順位の調整をしていない
  • 毎分ファイルができるので、データがない場合には削除などが必要かフォルダ分け
  • 書き込み中のファイルにWebDavからアクセスすると以降そのファイルに書き込みできなくなる
  • キューから書き込み時に1個書き込んで1ms Delayなのは微妙

たぶんこのシリーズはここで終わりの予定です。実際にMQTTを使った仕組みなどを作りたくなったときに参考にしつつ、チューニングする気がします。

コメント