第15週- IoT MQTT

tags: WiFi IoT 遠距控制 MQTT

在了解各類 IoT 網路架構以及 ESP32 WiFi 網路伺服器管理資源後,學習 MQTT 如何破除不同網域的限制,進行遠端遙控。


MQTT 背景簡介

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

發明及演進:

  • 1999 年由 IBM 的 Andy Stanford-Clark 博士和 Arcom(已更名為 Eurotech)的 Arlen Nipper 博士發明的通訊協定。
  • 方便分佈廣闊、大量的石油管線感測器,透過昻貴的人造衛星通訊,傳遞輕量、可靠的資料,便於降低電力損耗和網路擁塞,達到低頻寬、低硬體的需求。
  • 2011 年 11 月,捐贈給 Eclipse 基金會,目前已演變成 Open Source Code。
  • 2019 年 MQTT 提出 v5 版本。(目前市面上有兩個版本,v3.1.1、v.5)

基礎功能架構:

  • 使用 TCP/IP
  1. (為何不用 UDP?)
  • Publish/Scriber messaing transport (「發佈訂閱」機制)
  1. 透過 broker 執行一對多通訊 (MQTT Broker 就是 Server)
  2. Client 可以向 broker 的主動發佈 (當 publisher) 及訂閱 (當 subscriber) 內容
    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →
  3. topic message 的最大容量為 256MB
  • QoS (傳送的服務品質等級)
  1. 代表的是發送與接收訊息的品質,可設定 0 ~ 2

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

  1. 若 topic 沒人訂閱,則 payload 內容被丟棄

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

  • Topic (會談主題)
  1. Topic 命名原則:

a) 是由 utf-8 編碼組成,如同 Http 的 URL 的概念,但以 "/" 進行分階層

b) 不可以使用 "$"、"#"、"+"、或空隔(space)

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

  1. 利用特殊符號訂閱 Topic :

a) ”#”: 代表的垂直的概念,指的是該階層以下的全部Topic都訂閱:
ex: myhome/groundfloor/#

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

b) ”+”: 代表水平的概念,使用該符號的階層所處的階層可以替換成任何字元:
ex: myhome/groundfloor/+/temperature

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

[注意:myhome/#/temperature 是不符合規定的]

  • Persistent Session (持續會談主題)
  1. 當 publisher 對 MQTT 連線斷掉時,Topic 還會自動保留。
  2. 重新連線後 Topic 還存在。
  • Retained Messages (保留主題訊息)
  1. 發送訊息後會將訊息保持在 Topic 上,使的新的加入者也可以獲取最新的息。
  2. 若在沒有設定的情況下,新加入的 subscriber 不會收到上一個已發送過的訊息。
  • Last Will & testament (lwt) (遺囑)
  1. 當 publisher 斷線的時候,可指定 lwt 的 Topic,與想要傳送的訊息。

調用 PubSubClient 程式庫

接入服務語法指令

動作 WiFi WiFiMulti WiFi Client MQTT Client
宣告物件 WiFiMulti wifiMulti WiFiClient client PubSubClient MQTTClient(client)
模式宣告 WiFi.mode(WIFI_?) WiFi.mode(WIFI_?)
增加清單 wifiMulti.addAP("SSID_1", "password_1")
連線伺服器 client.connect(ip, port) MQTTClient.setServer(MQTTServer, MQTTPort)
連線 WiFi.begin(ssid,password) wifiMulti.run() MQTTClient.connect(id, MQTTUser, MQTTPassword)
檢查狀態 WiFi.status()==WL_CONNECTED wifiMulti.run()==WL_CONNECTED MQTTClient.connect()
關閉(斷線) WiFi.mode(WIFI_OFF) WiFi.mode(WIFI_OFF) client.stop(); MQTTClient.disconnect()

ESP32 mqtt 實作:

實作一:

啟動 ESP32 藍牙功能

  1. 依課本 p.208 的硬體需求
  2. 修改課本 p.210 程式中的 mqttserver 和 topic,並上傳

hint:

  1. 先安裝 library Nick O'Leary 的 pubsubclient
  2. 程式先包含 library:
    #include <WiFi.h>
    #include <PubSubClient.h>
  3. broker (MQTTServer) 為 broker.emqx.io
    MQTTPort 為 1883
    MQTTUser 為 emqx
    MQTTPassword 為 public
  4. 設定 publish 的 topic
    設定 subscribe 的 topic
  5. 設定 publish 的間隔時間(15秒以內),並以 MQTTClient.loop() 更新訂閱狀態
  6. 建立 WiFiClient 物件

WiFiClient WifiClient;

  1. 基於 WiFiClient 物件,建立 MQTTClient 物件

PubSubClient MQTTClient(WifiClient);

  1. 連接 broker (MQTTServer):

MQTTClient.setServer(MQTTServer, MQTTPort);
MQTTClient.setCallback(MQTTCallback);

[當 subscribe 的 topic 有更新時,執行MQTTCallback 副程式]

  1. 訂閱 topic 為 MQTTClient.subscribe(主題)
    發佈 topic 資料為 MQTTClient.publish(主題, 字元)
#include <WiFi.h> #include <WiFiMulti.h> //多重連線 WiFiMulti wifiMulti; //宣告多重連線 #include <PubSubClient.h> //請先安裝PubSubClient程式庫 #include <SimpleDHT.h> // ------ 設定WiFi帳號密碼 ------ char ssid[] = "ssid_0"; //請改名 char password[] = "pw_0"; //請改名 char ssid1[] = "sside_1"; //請改名 char password1[] = "pw_0"; //請改名 //------ 設定DHT11腳位 ------ int pinDHT11 = 23;// SimpleDHT11 dht11(pinDHT11); // ------ 以下修改成你MQTT設定 ------ //char* MQTTServer = "mqtt.eclipseprojects.io";//免註冊MQTT伺服器 //int MQTTPort = 1883; //MQTT Port //char* MQTTUser = ""; //不須帳密 //char* MQTTPassword = ""; //不須帳密 char* MQTTServer = "broker.emqx.io";//免註冊MQTT伺服器 int MQTTPort = 1883; //MQTT Port char* MQTTUser = "emqx"; char* MQTTPassword = "public"; //推播主題1:推播溫度(記得改Topic) char* MQTTPubTopic1 = "YourTopic/class205/temp"; //推播主題2:推播濕度(記得改Topic) char* MQTTPubTopic2 = "YourTopic/class205/humi"; //訂閱主題1:改變LED燈號(記得改Topic) char* MQTTSubTopic1 = "YourTopic/class205/led"; long MQTTLastPublishTime;//此變數用來記錄推播時間 long MQTTPublishInterval = 10000;//每10秒推撥一次 WiFiClient WifiClient; // 建立 WiFiClient 物件 PubSubClient MQTTClient(WifiClient); // 基於 WiFiClient 物件,建立 MQTTClient 物件 void setup() { Serial.begin(115200); pinMode(15, OUTPUT); //綠色LED燈 //開始WiFiMulti連線 WifiMultiConnecte(); //開始MQTT連線 MQTTConnecte(); } void loop() { //如果WiFi連線中斷,則重啟WiFi連線 if (WiFi.status() != WL_CONNECTED) { WifiMultiConnecte(); } //如果MQTT連線中斷,則重啟MQTT連線 if (!MQTTClient.connected()) { MQTTConnecte(); } //如果距離上次傳輸已經超過10秒,則Publish溫溼度 if ((millis() - MQTTLastPublishTime) >= MQTTPublishInterval ) { //讀取溫濕度 byte temperature = 0; byte humidity = 0; ReadDHT(&temperature, &humidity); // ------ 將DHT11溫濕度發佈到MQTT主題 ------ MQTTClient.publish(MQTTPubTopic1, String(temperature).c_str()); MQTTClient.publish(MQTTPubTopic2, String(humidity).c_str()); Serial.println("溫溼度已發佈到MQTT Broker"); MQTTLastPublishTime = millis(); //更新最後傳輸時間 } MQTTClient.loop();//更新訂閱狀態 delay(50); } //自建函式,讀取DHT11溫濕度 void ReadDHT(byte * temperature, byte * humidity) { int err = SimpleDHTErrSuccess; if ((err = dht11.read(temperature, humidity, NULL)) != SimpleDHTErrSuccess) { Serial.print("讀取失敗,錯誤訊息="); Serial.print(SimpleDHTErrCode(err)); Serial.print(","); Serial.println(SimpleDHTErrDuration(err)); delay(1000); return; } Serial.print("DHT讀取成功:"); Serial.print((int)*temperature); Serial.print(" *C, "); Serial.print((int)*humidity); Serial.println(" H"); } /* //自建函式,開始WiFi連線 void WifiConnecte() { //開始WiFi連線 WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("WiFi連線成功"); Serial.print("IP Address:"); Serial.println(WiFi.localIP()); } */ //自建函式,開始WiFiMulti連線 void WifiMultiConnecte() { //開始WiFiMulti連線 wifiMulti.addAP(ssid, password); wifiMulti.addAP(ssid1, password1); while(wifiMulti.run() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("WiFiMulti連線成功"); Serial.print("IP Address: "); Serial.println(WiFi.localIP()); } //自建函式,開始MQTT連線 void MQTTConnecte() { MQTTClient.setServer(MQTTServer, MQTTPort); MQTTClient.setCallback(MQTTCallback); while (!MQTTClient.connected()) { //以亂數為ClietID String MQTTClientid = "esp32-" + String(random(1000000, 9999999)); if (MQTTClient.connect(MQTTClientid.c_str(), MQTTUser, MQTTPassword)) { //連結成功,顯示「已連線」。 Serial.println("MQTT已連線"); //訂閱SubTopic1主題 MQTTClient.subscribe(MQTTSubTopic1); } else { //若連線不成功,則顯示錯誤訊息,並重新連線 Serial.print("MQTT連線失敗,狀態碼="); Serial.println(MQTTClient.state()); Serial.println("十五秒後重新連線"); delay(15000); } } } //自建函式,接收到訂閱時 void MQTTCallback(char* topic, byte* payload, unsigned int length) { Serial.print(topic); Serial.print("訂閱通知:"); String payloadString; //將接收的payload轉成字串 //顯示訂閱內容 for (int i = 0; i < length; i++) { payloadString = payloadString + (char)payload[i]; } Serial.println(payloadString); //比對主題是否為訂閱主題1 if (strcmp(topic, MQTTSubTopic1) == 0) { Serial.println("改變燈號:" + payloadString); if (payloadString == "ON") { digitalWrite(15, HIGH); } if (payloadString == "OFF") { digitalWrite(15, LOW); } } }

觀察:當打開 App terminal 後,連續收到 "Hello World"。

手機 mqtt app 應用實作:

實作一:

ESP32 透過mqtt將溫濕度數值,向手機傳送

  1. 需一部 Android 手機,並下戴安裝 "MQTT Dash"
    依程式修改 mqttserver 連線 ,並訂閱 topic

hint:
在 App 上輸入:

  1. MQTTServer connection 為 broker.emqx.io
    port 為 1883
  2. subscribe YourTopic//temp 或 humi
    publish YourTopic//led

觀察:當打開 App terminal 後,連續收到溫濕度數值,並可以控制 LED 的啟閉。

實作二:

跨網域訂閱其他同學的 ESP32 topic

hint:
在 App 上輸入:

  1. subscribe 其他同學的 YourTopic//temp 或 humi
  2. publish 其他同學的 YourTopic//led

觀察:當打開 App terminal 後,收到其他同學 ESP32 的溫濕度數值連續收到溫濕度數值,並可以控制 LED 的啟閉。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
延伸
安裝不同的 MQTT 手機 APP,體驗不同的 UX (使用者經驗) 設計,比較設計的差異性。

  1. "MQTT Dashboard"
  2. "MQTT Dashboard"
  3. "mymqtt"

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
進階延伸
在MQTT中使用TLS可以保證信息的機密性和完整性,防止訊息洩露和篡改。

  1. #include <WiFiClientSecure.h>
    const char* ca_cert= *****\n;
  2. 利用 server 的 CA certificate 建立加密的 WiFi 連線
    // init wifi secure client
    WiFiClientSecure espClient;
    espClient.setCACert(ca_cert);

[參考]MQTT on ESP32: A Beginner's Guide

參考資料

1)IOT物聯網應用第十四章 – 尤濬哲(夜市小霸王) 編著
2)What is MQTT?
3)MQTT教學(五):「保留」發布訊息以及QoS品質設定
4)[深入淺出MQTT]: v3.1.1與v5 的差異
5)MQTT on ESP32: A Beginner's Guide