ESP32でMQTTブローカーサーバーを動かす その1 基礎実験

概要

UIFlowなどを触っていると外部に通信をしたくなりますが、手軽なMQTTサーバーが少なく気軽に利用することができません。そこでESP32自体でMQTTブローカーを動かすことができるライブラリがあったので試してみました

MQTTとは?

メッセージをやり取りするためのプロトコルで、非常に軽いのが特徴です。IoT系などで小さいデータをやり取りするときに便利なプロトコルになります。

MQTTはサーバーとなるMQTTブローカーにメッセージを送信すると、適切な相手に配信してくれる機能があります。チャットサーバーのような動きが可能で、同じ部屋にいる人にそのメッセージが転送されるような使い方が可能です。

MQTTブローカー

MQTTのメッセージを受け取ったり、配信したりするサーバーです。Raspberry Piで構築するのが楽なのですが、品不足でなかなか購入できないので今回ESP32で動かしてみました。

MQTTブローカーはメッセージを配信する機能の他に、保存する機能もあります。これはMQTTサーバーによるのですが、保存できる量はサーバーによって異なります。自前サーバーなどでオープンソースのMosquittoを動かした場合などはほぼ保存上限がありませんが、AWSやAzureなどは利用量に応じた従量課金や、HiveMQなどのように容量やメッセージ数上限がある月額サービスもあります。

今回ESP32で動かすブローカーサーバーは保存をしないタイプです。通信相手が接続していない場合には届かないことになります。

MQTTクライアント(デバイス)

MQTTクライアントや、MQTTデバイスと呼ばれるものはMQTTブローカーサーバーに接続をしている端末になります。送信だけするデバイス、受信だけするデバイス、送受信の両方を行うデバイスがいます。

パブリッシュ

MQTTブローカーにメッセージを送信する行為をパブリッシュと呼びます。送信時にはメッセージ本文の他に宛先であるトピックを指定します。

トピック

チャットサーバーの部屋のようなものです。/(スラッシュ)で区切られた階層構造になっており、どの部屋に送信するのかを指定します。

サブスクライブ

サブスクと呼ばれる購読です。MQTTクライアントはメッセージを受信したいトピックを指定して購読をします。購読している場所にメッセージがパブリッシュされた場合には、そのメッセージがブローカーサーバーより配信される動きになります。

サブスクライブはワイルドカードの#が利用でき「room/#」などと指定することで「room/123」などに送信したものを購読可能です。「#」と指定するとすべてのメッセージが購読できます。

ESP32側のスケッチ例

今回利用したのが上記のsMQTTBrokerになります。Arduinoのライブラリマネージャーにも登録されているので、かんたんに実行可能です。

Wi-Fiアクセスポイントの設定

#include <WiFi.h>

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

  WiFi.begin("SSID", "KEY");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }

  Serial.println();

  Serial.println("WiFi Connected.");

  Serial.printf("IP Address  : ");
  Serial.println(WiFi.localIP());
}

void loop() {
}

利用するESP32ボードにWi-Fiアクセスポイントの情報を保存しておきます。今回はM5StickCを利用しましたが、どんなボードでも構いません。事前保存は必須ではないですが個別のスケッチにWi-Fiアクセスポイントの情報は書かないほうがおすすめです。

ESP32ブローカーサーバー

#include<sMQTTBroker.h>

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) {
      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) { // Wait for the Wi-Fi to connect
    delay(1000);
  }
  Serial.println("Connection established!");
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP());

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

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

sMQTTBrokerのスケッチ例にあるadvancebrokerを少し変更していますが、メッセージを追加したのと整形と、不要なところ削除したぐらいです。

#include"sMQTTBroker.h"

sMQTTBroker broker;

void setup() {
  Serial.begin(115200);
  WiFi.begin();
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
  }
  Serial.println("Connection established!");
  Serial.print("IP address:\t");
  Serial.println(WiFi.localIP());

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

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

単にブローカーで動かすだけであれば上記の最小構成でも問題ありません。

Windows側で動作確認

今回は上記のMQTT XというMQTTクライアントツールを使わせてもらいました。

こんな感じでIPアドレスとMQTTバージョンを指定すれば接続できると思います。MQTTバージョンはデフォルトでは5.0なのですが、3.1か3.1.1に変更しないと接続できませんでした。

そのあとにサブスクリプションを追加します。全部確認する場合には「#」で構わないと思います。複数追加可能ですので、いろいろ変えながら動作を確認してみてください。

UIFlowでMQTTクライアントからパブリッシュしてみる

上記のコードでテストしてみました。タイマーを使って1秒(1000ミリ)間隔で現在の時間を送信しています。IPアドレスだけESP32ブローカーサーバーのものを入力すれば送信できます。

受信結果分析

1秒間隔で送信した場合、手元で少し試したところ0.51秒から1.41秒間隔までのばらつきがありました。とりあえず2秒以上のばらつきはないので最小1秒間隔であれば大丈夫そうでした。0.5秒間隔にしたらタイムアウトが発生して、ぐだぐだになったのでこれぐらいが限界だと思います。

5秒間隔にしてみました。こちらのほうが分布がきれいですね。とはいえ3.98秒から6.96秒間隔で受信していたので2秒弱遅れることがあるようでした。これはWi-Fi環境によって変わってくると思います。とはいえ、1秒間隔だとぎりぎりそうで、5秒以上の間隔であればそれなりに安定して通信ができそうな雰囲気です。

まとめ

秒間1メッセージぐらいであればなんとか動くことがわかりました。このままだと応用がしにくいと思いますので、もう少し実用的な構成を考えていきたいと思います。

コメント