NEW!Mosquitto で MQTT 双方向通信 (5)IoT デバイス ソフトウエアコード

MQTT Mosquitto ブラウザ Web ソケット WebSocket IoTデバイス 監視・制御 SSL/TLS

背景

MQTT プロトコルは IoT 機器の監視・制御に最適な通信方式です。 Mosquitto や EMQX社 から無料で利用できる MQTT Broker が提供されていて手軽に利用できます。 しかし予期せぬサーバーの停止により IoT 機器の監視・制御に不都合をきたしたり、相乗りサーバーのためどうしてもセキュリティに対する不安も残ります。

これらの課題を解決するために VPS サーバーにセキュリティ対策も行った MQTT Broker をインストールして IoT システムを構成しました。 特に Web ブラウザと MQTT Broker 間の MQTT over WebSockets with TLS に多くの試行錯誤をともなったので整理して記録を残します。

ゴール

図 1 Web ブラウザ IoT デバイス 間 MQTT 通信

・VPS サーバーに MQTT Broker インストール
・TLS(SSL) によるセキュリティ対応
・IoT デバイス (C++) と MQTT Broker 間は MQTT over TLS (mqtts://) 通信
・Web ブラウザ (HTML, Java Script, CSS) と MQTT Broker 間 MQTT over WebSockets with TLS (wss://) 通信

本章では IoT デバイスの C++ コードを作成して MQTT Broker との間で MQTT over TLS (mqtts://) 通信を行います。

前提条件

・Mosquitto (バージョン 2.0.11) インストール済み
・VPSサーバー Linux OS Debian (バージョン 6.1.153-1) 
・IoT デバイス ESP32-DevKitC
・開発プラットフォーム Visual Studio Code (バージョン:1.105.0)
・拡張機能 PlatformIO IDE for VSCode (バージョン 3.3.4)
・開発言語 Linux , C++, HTML, Java Script, CSS
・通信プロトコル MQTT over TLS (mqtts://), MQTT over WebSockets with TLS (wss://)
・Windows 11 (バージョン Pro 24H2)

プロジェクト作成

(1)Visual Studio Code を立ち上げます。

(2)アリさんマーク > Create New Project
  Project Wizard が開くので次の項目を入力します。
   Nmae:    MQTT_VPS_CPP (任意の名前を入力します)
   Board:    Espressif ESP32 Dev Module (プルダウンメニューから選択します)
   Framework: Arudino
  Finish をクリックすると Platformio.ini の内容が表示されます。

(3)アリさんマーク > Libraries > Registryタブ
  検索窓に pubsubclient と入力して検索します。
 「PubSubClient by Nick O’Leary」 > Add to Project
  platformio.ini に lib_deps = knolleary/PubSubClient@^2.8 が自動的に追加されます。

(4)platformio.ini に次の 2 行を追加入力します。
   upload_port = COM4(ESP32 が接続されている PC のポート番号に変更します。)
   monitor_speed = 115200

; 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
lib_deps = knolleary/PubSubClient@^2.8
upload_port = COM4
monitor_speed = 115200

図 2 platformio.ini

C++ コード 全文

図 3 に C++ コード全文を示します。右上隅にカーソルを合わせると一括コピーできます。

// MQTT_VPS_CPP
// MQTT over TLS

#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <WiFiClientSecure.h>

//端子定義
#define state_pin19 19      //入力端子
#define output_on_pin21 21  //出力端子
#define output_off_pin2 2   //出力端子

//時刻表示用
#define JST     3600* 9
struct tm timeInfo; //時刻を格納するオブジェクト
char s[20];         //文字格納用

//状態変化検出用 
bool state_pin = false;
bool state_pin_pre = false;

//ループ実行周期設定用
unsigned long old = 0;       // 前回実行時刻を初期化
unsigned long interval = 10; // 実行周期を設定 10ms

// WiFi 認証情報
const char *ssid = "wifi_ssid";
const char *password = "wifi_password";

// MQTT Broker settings
const char *mqtt_broker = "domain-name";
const char *mqtt_topic = "MQTT topic";
const char *mqtt_username = "username";
const char *mqtt_password = "password";
const int mqtt_port = 8883;
bool option_retain = false;

// WiFi and MQTT client initialization
WiFiClientSecure esp_client;
PubSubClient mqtt_client(esp_client);

// Letsencript で生成した CA 証明書 ISRG_Root_X1.pem
const char* ca_cert= \
"-----BEGIN CERTIFICATE-----\n" \
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" \
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" \
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" \
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" \
"MTCCA
"h77ct
"0TM8u
"A5/TR
"T8KOE
"B5T0Y
"B5iPN
"KBds0
"OlFuh
"jh8BC
"qHyGO
"rU7m2
"HRMBA
"hkiG9
"ubhzE
"3BebY
"NFtY2
"ORAzI
"TkXWS
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" \
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" \
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" \
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" \
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=" \
"-----END CERTIFICATE-----\n";

//---------------- Function Declarations プロトタイプ宣言 -------------------------------------//
std::string get_time_info();
void connectToWiFi();
void connectToMQTT();
void mqttCallback(char *topic, byte *payload, unsigned int length);

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
//+                                   setup                                                     +//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//

void setup() {
    //端子設定
    pinMode(state_pin19, INPUT_PULLDOWN);
    pinMode(output_on_pin21, OUTPUT);
    pinMode(output_off_pin2, OUTPUT);
    digitalWrite(output_on_pin21, LOW);
    digitalWrite(output_off_pin2, LOW);
    //シリアル通信開始
    Serial.begin(115200);
    //ターミナルにメッセージ出力
    Serial.print("\nESP32 ⇔ MQTT Broker 間通信");
    Serial.print("\n通信プロトコル: MQTT over TLS");
    //WiFi 接続の関数呼び出し
    connectToWiFi();
    //NTPの設定
    configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");
    //MQTT通信用設定
    esp_client.setCACert(ca_cert); // Set Root CA certificate
    mqtt_client.setServer(mqtt_broker, mqtt_port);
    mqtt_client.setKeepAlive(60);
    mqtt_client.setCallback(mqttCallback);
    //MQTT接続する関数呼び出し
    connectToMQTT();
}
//++++++++++++++++++++++++++++++++++ ここまで setup  +++++++++++++++++++++++++++++++++++++++++//

//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
//+                                   loop                                                  +//
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//

void loop(){
    unsigned long curr = millis(); // 現在時刻を取得
    if ((curr - old) >= interval) {
        // do periodic tasks // interval = 10ms 毎に実行する     
        //19pin の状態を state_pin に代入する
        state_pin = digitalRead(state_pin19);
        //state_pin が変化した場合 にパブリッシュする
        if (state_pin != state_pin_pre){
            //文字列 str_state に端子が HIGH か LOW_ かの状態を設定する
            std::string str_state;
            if(digitalRead(state_pin19)==HIGH){
                str_state="HIGH";
            }else{
                str_state ="LOW_";
            }
            //現在時刻と端子の状態から publish する文字列を生成する     
            std::string buf1 = get_time_info();
            buf1 ="{\"date\":\"" + buf1 + "\",\"client\":\"ESP32__\",\"state\":\"" + str_state + "\",\"control\":\"NULL_\"}";
            //publish message upon connection
            mqtt_client.publish(mqtt_topic, buf1.c_str(), option_retain);
            Serial.print("\npublish selected at loop\n");
            Serial.print(buf1.c_str());
            Serial.println("\nEnd_of_Publish_+++++++++++++++++++++++++++++++++++++++++++++++++\n");
            state_pin_pre = state_pin;
        }
        mqtt_client.loop(); 
        old = curr;                  // 前回実行時刻を現在時刻で更新
    }
}
//+++++++++++++++++++++++++++++++++++ ここまで loop ++++++++++++++++++++++++++++++++++++++++++++//

//---------------- ここから  -----------------------------------------------------------//
 //現在時刻を取得して char s[] に設定する
std::string get_time_info(){
    //tmオブジェクトのtimeInfoに現在時刻を入れ込む
    getLocalTime(&timeInfo);
    //人間が読める形式に変換
    sprintf(s, "%04d-%02d-%02dT%02d:%02d:%02d.000",
    timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday,
    timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
    delay(100);
    //文字列に変換
    std::string buf10 = s;
    return buf10;
}

//---------------- ここから WiFi 接続 -----------------------------------------------------------//

void connectToWiFi() {
    WiFi.begin(ssid, password);
    Serial.print("\nConnecting to WiFi\n");
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }
    Serial.println("\nConnected to WiFi");
    
    //モニターにローカル IPアドレスを表示する
    Serial.println("WiFi connected.");
    Serial.print("  *IP address: ");
    Serial.println(WiFi.localIP());
}

//---------------- ここから MQTT 接続 ------------------------------------------------------------//

void connectToMQTT() {
    //MAC uint_8t*
    while (!mqtt_client.connected()) {
        // ESP32 の macAddress からユニークな client_id を生成
        std::string bufx = std::string(WiFi.macAddress().begin(), WiFi.macAddress().end());
        std::string client_id = "esp32-client-" + bufx ;
        Serial.printf("Connecting to MQTT Broker as %s...\n", client_id.c_str());
        if (mqtt_client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
            Serial.println("Connected to MQTT broker");
            mqtt_client.subscribe(mqtt_topic);
            Serial.println("\nsubscribe started at setup");     
        } else {
            Serial.print("Failed to connect to MQTT broker, rc=");
            Serial.print(mqtt_client.state());
            Serial.println(" Retrying in 5 seconds.");
            delay(5000);
        }
    }
}

//---------------- ここから MQTT コールバック ----------------------------------------------------//

void mqttCallback(char *topic, byte *payload, unsigned int length) {
    Serial.print("Message received on topic: ");
    Serial.println(topic);
    std::string buf2 = "" ; 

    //payload 全体を取得してターミナルに出力
    for (unsigned int i = 0; i < length; i++) {
        Serial.print((char) payload[i]);
        buf2= buf2+ (char)payload[i];
    }
    
    //payload から control 値を抽出
    std::string buf3 = "" ; 
    for (unsigned int i = 79; i < 83; i++) {
        buf3= buf3+ (char)payload[i];
    }
    //Serial.print("\nbuf3=");
    //Serial.print(buf3.c_str());

    //control 値に応じて出力端子を制御
    if (buf3 =="ON__"){
        digitalWrite(output_on_pin21, HIGH);
        digitalWrite(output_off_pin2, LOW);
          Serial.print("\nON___pin21=HIGH");
    }else if(buf3=="OFF_"){
        digitalWrite(output_on_pin21, LOW);
        digitalWrite(output_off_pin2, HIGH);
          Serial.print("\nOFF__pin2=HIGH");
    }
    Serial.println("\nEnd_of_mqttCallback---------------------------------------------\n");
}
//---------------- ここまで MQTT コールバック -----------------------------------------------------//

図 3 C++ コード全文

main.cpp にデフォルトで存在するコードはすべて削除します。
C++ コード全文をコピー/ペーストします。
以下の変更を行います。
   ・32行目 domain-name: Mosquitto インストール時のドメイン名を設定します。
   ・33行目 MQTT topic: Mosquitto インストール時に合わせて MQTT topic を設定します。
   ・34行目 username: Mosquitto インストール時の username を設定します。
   ・35行目 password: Mosquitto インストール時の password を設定します。
   ・45~75行目 CA 証明書 ISRG_Root_X1.pem を notepad で編集します。
     -全ての行頭にダブルクオーテーション ” を付加します。
     -各行末に \n” \ を付加します。
     -74行目末尾には ” \ を付加します。
     -75行目末尾には \n” を付加します。
   編集したものを45~75行目と置き換えます。

以上で C++ コードが完成しました。
ビルドして ESP32 へアップロードすれば動作確認できます。

詳細解説

6 行目 MQTT通信を行うためのライブラリ PubSubClient を include します。
#include <PubSubClient.h>

10 行目 IO19 を入力端子に割り当てます。
11 行目 IO21 を出力端子に割り当てます。(ブラウザの ON ボタンに応答)
12 行目 IO2 を出力端子に割り当てます。(ブラウザの OFF ボタンに応答)
#define state_pin19 19      //入力端子
#define output_on_pin21 21  //出力端子
#define output_off_pin2 2   //出力端子

20~21行目
 IO19 入力端子の状態変化を検出するために変数 state_pin と state_pin_pre を設定します。
bool state_pin = false;
bool state_pin_pre = false;

24行目 前回実行時刻を格納する 4 バイト変数 old を定義して初期化します。
25行目 IO19 入力端子の変化や MQTT ブローカに対する応答を考慮してループの周期を 10ms に設定します。
unsigned long old = 0;       // 前回実行時刻を初期化
unsigned long interval = 10; // 実行周期を設定 10ms

28 ~ 29行目 Wi-Fi 環境に合わせて wifi_ssid と wifi_password を設定します。
const char *ssid = "wifi_ssid";
const char *password = "wifi_password";

MQTT ブローカーに接続するためのパラメータを設定します。
32行目 domain-name: Mosquitto インストール時のドメイン名を設定します。
33行目 MQTT topic: Mosquitto インストール時に合わせて MQTT topic を設定します。
34行目 username: Mosquitto インストール時の username を設定します。
35行目 password: Mosquitto インストール時の password を設定します。
36行目 mqtt_port = 8883: MQTT over TLS ( mqtts:// )に対応するポート番号です。
37行目 option_retain = false: MQTT ブローカーによるデータの保持はしません。

const char *mqtt_broker = "domain-name";
const char *mqtt_topic = "MQTT topic";
const char *mqtt_username = "username";
const char *mqtt_password = "password";
const int mqtt_port = 8883;
bool option_retain = false;

40行目 HTTPS 接続の確立、SSL 証明書の検証、暗号化通信を行うためライブラリ
    WiFiClientSecure を使ってクライアントインスタンス esp_client を生成します。
41行目 MQTT 通信のためのライブラリ PubSubClient を使用して esp_client と紐づけて
    TCP 通信上で MQTT 通信を行うためのインスタンス mqtt_client を生成します。
WiFiClientSecure esp_client;
PubSubClient mqtt_client(esp_client);

45~75行目
Letsencript で生成した CA 証明書 ISRG_Root_X1.pem を notepad で編集します。
   -全ての行頭にダブルクオーテーション ” を付加します。
   -各行末に \n” \ を付加します。
   -74行目末尾には ” \ を付加します。
   -75行目末尾には \n” を付加します。
編集したものを45~75行目と置き換えます。

// Letsencript で生成した CA 証明書 ISRG_Root_X1.pem
const char* ca_cert= \
"-----BEGIN CERTIFICATE-----\n" \
"MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" \
"TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" \
"cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" \
"WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" \
"ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" \
"MTCCA
"h77ct
"0TM8u
"A5/TR
"T8KOE
"B5T0Y
"B5iPN
"KBds0
"OlFuh
"jh8BC
"qHyGO
"rU7m2
"HRMBA
"hkiG9
"ubhzE
"3BebY
"NFtY2
"ORAzI
"TkXWS
"jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" \
"oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" \
"4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" \
"mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" \
"emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=" \
"-----END CERTIFICATE-----\n";

使用する関数をプロトタイプ宣言しておきます。
78行目 get_time_info() 現在時刻を取得する関数です。
79行目 connectToWiFi() Wi-Fi接続関連の関数です。
80行目 connectToMQTT() MQTT接続関連の関数です。
81行目 mqttCallback(char *topic, byte *payload, unsigned int length)
    topic を引数、payload と length が戻り値の MQTT コールバック関数です。
//---------------- Function Declarations プロトタイプ宣言 -------------------------------------//
std::string get_time_info();
void connectToWiFi();
void connectToMQTT();
void mqttCallback(char *topic, byte *payload, unsigned int length);

setup() 関数

100 行目 Wi-Fi 接続関連を関数化して connectToWiFi() に定義したものを呼び出します。
connectToWiFi();

104行目 インスタンス化された esp_client にルート証明書を設定します。

以下は41行目でインスタンス化した mqtt_client を使って設定を行います。
105行目 ドメイン名 mqtt_broker と ポート番号 mqtt_port をサーバーに設定します。
106行目 KeepAlive を60秒に設定します。  ブローカーはこの値の1.5倍の時間内に
    クライアントがデータを受信しない場合は接続を切断します。
107行目 コールバック時に呼び出す関数として mqttCallback (後述)を設定します。
109行目 MQTT接続関連を関数化(後述)したものを呼び出します。
//MQTT通信用設定
esp_client.setCACert(ca_cert); // Set Root CA certificate
mqtt_client.setServer(mqtt_broker, mqtt_port);
mqtt_client.setKeepAlive(60);
mqtt_client.setCallback(mqttCallback);
//MQTT接続する関数呼び出し
connectToMQTT();

loop() 関数

timer割込みや Tickerを使ってもよかったのですが泥臭い方法で一定周期の処理を行います。

118行目 プログラムの実行開始からの経過時間をミリ秒単位で取得して curr に代入します。
     curr は 4 バイト符号なしロング整数型とします。
119行目 経過時間 curr と前回値 old との差分が interval 以上になると if 文内を実行します。
122行目 IO19 入力端子の状態(論理値)を変数 state_pin に代入します。
124行目 state_pin の値が state_pin_pre (前回の state_pin の値)と異なる、
     すなわち値が変化した場合に if 文内を実行します。
    unsigned long curr = millis(); // 現在時刻を取得
    if ((curr - old) >= interval) {
        // do periodic tasks // interval = 10ms 毎に実行する     
        //19pin の状態を state_pin に代入する
        state_pin = digitalRead(state_pin19);
        //state_pin が変化した場合 にパブリッシュする
        if (state_pin != state_pin_pre){

126~131行目 IO19 入力端子が HIGH の場合文字列変数 str_state =”HIGH” に、
       IO19 入力端子が LOW の場合 str_state =”LOW_”に設定します。
133行目 現在時刻を文字列変数 buf1 に設定します。
134行目 定型文字列、buf1 および str_state を組み合わせて buf1 として代入します。
136行目 publish(キュー)するための関数にインスタンス mqtt_client を設定します。
    引数は mqtt_topic と buf1 、option_retain を設定します。
137~139行目 publish する文字列をターミナルにも出力します。
140行目 state_pin_pre (前回の値) に state_pin (現在の値) を代入して更新します。
            std::string str_state;
            if(digitalRead(state_pin19)==HIGH){
                str_state="HIGH";
            }else{
                str_state ="LOW_";
            }
            //現在時刻と端子の状態から publish する文字列を生成する     
            std::string buf1 = get_time_info();
            buf1 ="{\"date\":\"" + buf1 + "\",\"client\":\"ESP32__\",\"state\":\"" + str_state + "\",\"control\":\"NULL\"}";
            //publish message upon connection
            mqtt_client.publish(mqtt_topic, buf1.c_str(), option_retain);
            Serial.print("\npublish selected at loop\n");
            Serial.print(buf1.c_str());
            Serial.println("\nEnd_of_Publish_+++++++++++++++++++++++++++++++++++++++++++++++++\n");
            state_pin_pre = state_pin;

142行目 mqtt_client.loop() この関数を呼び出すことにより次の動作が行われます。
     ・ブローカーからメッセージが届いていればコールバック関数を呼び出します。
     ・publish() でキューに入れたメッセージ ( 136行目) を実際に送信します。
     ・keep_alive の時間を監視して定期的に PINREQ 送信を行います。
143行目 変数 old に現在の経過時間 curr を代入して次のループ ( 119行目)で使用します。
   mqtt_client.loop(); 
   old = curr;                  // 前回実行時刻を現在時刻で更新

150~161行目 関数 std::string get_time_info() を定義しています。
152行目   ポインタ &timeinfo の構造体に現在時刻を格納します。
154~156行目 文字配列 s[] に yyyy-mm-ddThh:mm:ss.000 の形式で格納します。
       ブラウザ側でミリ秒まで扱うので 000 で埋めて桁を揃えます。
       フォーマットは ISO8601 に準じています。
 //現在時刻を取得して char s[] に設定する
std::string get_time_info(){
    //tmオブジェクトのtimeInfoに現在時刻を入れ込む
    getLocalTime(&timeInfo);
    //人間が読める形式に変換
    sprintf(s, "%04d-%02d-%02dT%02d:%02d:%02d.000",
    timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday,
    timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
    delay(100);
    //文字列に変換
    std::string buf10 = s;
    return buf10;
}

182~200行目 MQTT 接続関連の関数として connectToMQTT() を定義しています。
184行目 MQTT 接続が確立されるまで { } を実行します。
186行目 Wi-Fi 接続の MACアドレスを取得して 文字列変数 bufx に代入します。
187行目 定型文字列と bufx を組合わせてユニーク文字列として client_id に代入します。
188行目 ターミナルにメッセージと生成した client_id を出力します。
189行目 MQTT ブローカへの接続が確立されたら mqtt_client.connect( ) は trueを返します。     引数には client_id.c_str()、 mqtt_username および mqtt_password を使用します。

MQTTブローカーへの接続が成功した場合、
190行目 ターミナルにメッセージを出力
191行目 mqtt_topic で指定された情報のリッスンを開始します。
    メッセージを受信したらコールバック関数が呼び出されます。

MQTTブローカーへの接続が失敗した場合、
194行目~196行目 ターミナルにメッセージとエラーコード mqtt_client.state() を出力します。
197行目 5秒待ってから 184行目に戻り、接続されるまでリトライを繰り返します。
void connectToMQTT() {
    //MAC uint_8t*
    while (!mqtt_client.connected()) {
        // ESP32 の macAddress からユニークな client_id を生成
        std::string bufx = std::string(WiFi.macAddress().begin(), WiFi.macAddress().end());
        std::string client_id = "esp32-client-" + bufx ;
        Serial.printf("Connecting to MQTT Broker as %s...\n", client_id.c_str());
        if (mqtt_client.connect(client_id.c_str(), mqtt_username, mqtt_password)) {
            Serial.println("Connected to MQTT broker");
            mqtt_client.subscribe(mqtt_topic);
            Serial.println("\nsubscribe started at setup");     
        } else {
            Serial.print("Failed to connect to MQTT broker, rc=");
            Serial.print(mqtt_client.state());
            Serial.println(" Retrying in 5 seconds.");
            delay(5000);
        }
    }
}

204行目 メッセージを受信したらこの関数が呼び出されます。
    引数として topic を設定して、戻り値として payloard と length が得られます。
205~206行目 メッセージを受信するのに使った topic の内容をターミナルに出力します。
210~211行目 受信したメッセージをターミナルに出力します。( 212行目は未使用です。)
void mqttCallback(char *topic, byte *payload, unsigned int length) {
    Serial.print("Message received on topic: ");
    Serial.println(topic);
    std::string buf2 = "" ; 

    //payload 全体を取得してターミナルに出力
    for (unsigned int i = 0; i < length; i++) {
        Serial.print((char) payload[i]);
        buf2= buf2+ (char)payload[i];
    }

216行目   文字列変数 buf3 を定義します。
217~219行目 payload の 80 番目から 83 番目までの 4 文字を抽出して buf3 に代入します。        buf3 はブラウザが publish した “ON__” か “OFF_”
       またはエコーバックした初期値 “NULL” の値をとります。
    //payload から control 値を抽出
    std::string buf3 = "" ; 
    for (unsigned int i = 79; i < 83; i++) {
        buf3= buf3+ (char)payload[i];
    }

224~226行目 buf3 が “ON__” の場合、IO21 に HIGH、IO2 に LOW を出力します。
229~231行目 buf3 が “OFF_” の場合、IO21 に LOW、IO2 に HIGH を出力します。
233行目 ターミナルにメッセージを出力してコールバック関数を終了します。
    if (buf3 =="ON__"){
        digitalWrite(output_on_pin21, HIGH);
        digitalWrite(output_off_pin2, LOW);
          Serial.print("\nON___pin21=HIGH");
    }else if(buf3=="OFF_"){
        digitalWrite(output_on_pin21, LOW);
        digitalWrite(output_off_pin2, HIGH);
          Serial.print("\nOFF__pin2=HIGH");
    }
    Serial.println("\nEnd_of_mqttCallback---------------------------------------------\n");

リンク

Mosquitto で MQTT 双方向通信 
(1)Mosquitto を VPS にインストール
(2)Web ブラウザと IoT 間 動作エミュレート
(3)Web ブラウザ ソフトウエアコード
(4)IoT デバイスハードウエア
(5)IoT デバイス ソフトウエアコード
(6)システム全体動作確認

(YI)

コメント

error: Content is protected !!
タイトルとURLをコピーしました