添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

本文將示範使用Arduino Uno控制板搭載乙太網路擴展板,藉由Nick O’Leary先生開發的MQTT前端程式庫,叫做 PubSubClient ,從Arduino發送MQTT主題訊息給Mosquitto伺服器。

PubSubClient程式庫 相容於下列擴展板(shield)和控制板,完整說明請參閱此程式庫的網頁說明。

  • Arduino Ethernet
  • Arduino Ethernet Shield
  • Arduino YUN
  • Arduino WiFi Shield
  • Sparkfun WiFly Shield
  • TI CC3000 WiFi
  • Intel Galileo/Edison
  • ESP8266
  • 此程式庫有一些功能上的限制:

  • 只能發布QoS 0訊息 ,但可以訂閱QoS 0或QoS 1的主題。
  • 最大訊息長度(含標頭)預設為128位元組 ,可透過PubSubClient.h裡的MQTT_MAX_PACKET_SIZE常數值調整。
  • keepalive(保持連線) 簡隔時間預設為15秒,可透過PubSubClient.h裡的MQTT_KEEPALIVE常數值調整。
  • 用戶端預設採用MQTT 3.1.1標準 ,如果你的MQTT伺服器不支援(Mosquitto有支援),可將PubSubClient.h裡的MQTT_VERSION值改成3.1。
  • 安裝PubSubClient程式庫

    Arduino IDE(整合開發工具)從1.6.2版開始,支援從「 程式庫管理員(Library Manager) 」新增與更新程式庫的功能。選擇Arduino IDE裡的「 草稿碼→匯入程式庫→管理程式庫 」,開啟「程式庫管理員」。在搜尋欄位輸入關鍵字“mqtt”,可找到許多相關程式庫,請安裝PubSubClient。

    如果你使用的是舊版的IDE,需要手動下載安裝程式庫,請到 PubSubClient專案網頁下載 .zip壓縮格式檔,或者直接 按此連結下載

    下載之後,將它解壓縮存入「 文件\Arduino\libraries 」路徑。

    PubSubClient程式庫提供的函式指令介紹

    本節介紹稍後將使用的PubSubClient程式庫函式,完整的指令請參閱 官方API文件 ,請先略讀本節再閱讀下一節的程式碼。

    MQTT的相關指令都要透過PubSubClient物件操作,因此MQTT程式最重要的一步是 建立PubSubClient物件 ,程式指令如下:

    PubSubClient 物件名稱(網路用戶端物件)

    由於MQTT協定基於TCP/IP,因此網路層要透過其他程式庫實作。 採用W5100乙太網路卡的場合,使用官方Ethernet程式庫建立TCP/IP連線 ,因此這裡的「網路用戶端」指的是 EthernetClinet類型 的物件。基於乙太網路卡,建立PubSubClient物件的敘述如下:

    底下是本文使用的函式指令:

    setServer(MQTT伺服器, 埠號) :指定欲連接的MQTT伺服器的IP位址或網域名稱,以及埠號。

    connect(用戶端ID) :連線到MQTT伺服器,並傳入 自訂的唯一識別碼

    每個MQTT用戶端都需要一個唯一的識別碼(Client ID,以下稱「用戶端ID」) ,MQTT伺服器透過用戶端ID來識別用戶並且紀錄個別用戶的狀態,像是訂閱的主題和通訊品質設定。根據 MQTT 3.1.1規格書 Client Identifier單元的說明, 用戶端ID的長度為1~23個字元,並且只允許數字和英文字母。 但實際的狀況視伺服器和用戶端使用的軟體而定,例如,HiveMQ公司的MQTT伺服器軟體允許用戶端ID最大長度為65535字元(參閱該公司的 這篇文件 說明),而MQTTLens軟體自動產生的用戶端ID長度則是32個字元(參閱下圖)。話說回來,寫程式的時候還是盡量遵循規格書訂定的規範,以減少相容性的問題。

    connected() :檢查用戶端 是否和伺服器連線 ,傳回true代表仍處於連線狀態;false代表已斷線。

    publish(主題, 內容) :發布主題和內容,「主題」與「內容」參數值都是 字元陣列 類型。此函數會傳回一個布林值,true代表發布成功,false代表不成功,可能是斷線或者訊息內容太長。

    loop() 程式應該要定期呼叫loop()函式 ,以便和伺服器保持連線並且處理接收到的訊息。loop()函式會傳回一個布林值,true代表仍與伺服器相連;false代表與伺服器斷線。

    Arudino Uno搭載乙太網路擴展板發布MQTT主題訊息

    本單元將以Arduino充當MQTT發布者,每隔5秒發布一則隨機溫度和濕度值(JSON格式)的“home/yard/DHT11”主題。

    實驗材料:

  • Arduino Uno控制板 × 1
  • 採用W5100晶片的乙太網路擴展板 × 1
  • 實驗程式:

    本實驗程式修改自PubSubClient程式庫內建的mqtt_basic範例,底下是程式的處理流程以及相關PubSubClient指令:

    此程式基於Ethernet程式庫,所以寫過Arduino HTTP伺服器或前端程式的讀者應該會感到熟悉,這是主程式部份:

    #include <SPI.h> #include <Ethernet.h> #include <PubSubClient.h> // 設定MAC(實體)位址 const byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED}; // 設定用戶端和伺服器的IP位址,請自行修改成你的設備的IP位址。 const IPAddress ip(192, 168, 1, 25); const IPAddress server(192, 168, 1, 19); // 設定用戶端ID const char clientID[] = "yard001"; // 設定主題名稱 const char topic[] = "home/yard/DHT11"; // 儲存訊息的字串變數 String msgStr = ""; // 儲存字元陣列格式的訊息字串(參閱下文說明) char json[25]; EthernetClient ethClient; // 建立乙太網路前端物件 PubSubClient client(ethClient); // 基於乙太網路物件,建立MQTT前端物件 void setup(){ Serial.begin(9600); // 設定MQTT代理人的網址和埠號 client.setServer(server, 1883); Ethernet.begin(mac, ip); // 留點時間給乙太網路卡進行初始化 delay(1500); void loop(){ // 確認用戶端是否已連上伺服器 if (!client.connected()) { // 若沒有連上,則執行此自訂函式。 reconnect(); // 更新用戶端狀態 client.loop(); // 建立MQTT訊息(JSON格式的字串) msgStr = msgStr + "{\"temp\":" + (19 + random(10)) + ",\"humid\":" + 20 + "}"; // 把String字串轉換成字元陣列格式 msgStr.toCharArray(json, 25); // 發布MQTT主題與訊息 client.publish(topic, json); // 清空MQTT訊息內容 msgStr = ""; delay(5000);

    底下連結MQTT伺服器的reconnect()自訂函式。

    void reconnect() { // 若目前沒有和伺服器相連,則反覆執行直到連結成功… while (!client.connected()) { // 指定用戶端ID並連結MQTT伺服器 if (client.connect(clientID)) { // 若連結成功,在序列埠監控視窗顯示「已連線」。 Serial.println("connected"); } else { // 若連線不成功,則顯示錯誤訊息 Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" try again in 5 seconds"); // 等候5秒,再重新嘗試連線。 delay(5000);

    按此下載本單元的範例程式碼(.ZIP壓縮格式)

    實驗結果:

    我們將 使用MQTTLens程式訂閱home/yard/DHT11主題 ,如果你之前沒有透過它訂閱過這個主題,請先訂閱,然後再把上面的程式上傳到Arduino控制板。每隔5秒,MQTTLens將顯示從Mosquitto伺服器轉送過來的訂閱訊息。

    使用String資料類型動態建立字串並轉換成字元陣列

    補充說明一下建立MQTT訊息(JSON格式的字串)的敘述。這部份採用String資料類型動態建立字串,子字串和數字之間用“+”運算子相連。 連接字串的最前面要加上這個String類型變數(msgStr),否則編譯過程將引發資料類型錯誤。 由於publish()函式的參數值為字元陣列格式,因此程式要先透過String物件的toCharArray()方法,把String類型的字串轉換成字元陣列格式。

    筆者將字元陣列的長度(元素數量)設定成25,足夠此範例的JSON字串使用,如有需要,請自行調整json陣列變數的長度。

  • MQTT教學(五):「保留」發布訊息以及QoS品質設定
  • MQTT教學(四):使用MQTTLens訂閱與發布MQTT訊息
  • MQTT教學(一):認識MQTT
  • HiveMQ公司的 MQTT Essentials系列文件
  • Category: 教學文件 硬體與DIY Tagged:

    由Arduino 發佈 MQTTLens訂閱,MQTTLens可收到訊息
    但由MQTTLens發佈 Arduino訂閱,Arduino無法收到訊息
    由Arduino發佈 Arduino自己訂閱,Arduino可收到訊息

    可以確定Arduino收發正常,但為什麼由MQTTLens發佈訊息Arduino收不到?

    可否麻煩您解惑,並告之解決方法,感謝!

    EthernetClient ethClient; // 建立乙太網路前端物件 PubSubClient client(ethClient); // 基於乙太網路物件,建立MQTT前端物件

    不知為何你要連結兩個server,不過,你也可以寫個伺服器端程式,從一個server傳遞訊息給另一個server。

    thanks,
    jeffrey

    老師您好,請問在樹莓派上安裝了mqtt伺服器 並使用arduino uno +esp8266做連線一開始有回傳數值到樹莓派終端介面,但運作一陣子後就無回傳數值了是與keepalive 有關係嗎?

    MQTT是為了窄頻寬、網路通訊品質不佳的環境而設計的協定,前後端斷線是很正常的情況。

    如果你需要前後端始終保持連線並即時通訊,可使用WebSocket技術,像Node.js有個socket.io模組,可容易達成這項需求。

    thanks,
    jeffrey

    老師您好,我使用arduino uno +esp8266做連線一開始有回傳數值到AWS雲端BROKER,但運作一陣子後(大概12hr~18hr)就無回傳數值了是與keepalive 有關係嗎?
    或者會有其他原因嗎,非常需要您的幫助,謝謝~~!

    請查看AWS伺服器的log,了解斷線的原因。

    問題應該不是出在keepAlive設定,因為pubsubclient程式預設每15秒會向MQTT broker發出ping訊息來維持連線狀態(參閱 API文件的Configuration Options 裡的MQTT_KEEPALIVE常數)。

    你也可以查看pubsubclient的state()方法的傳回值對照錯誤訊息。

    thanks,
    jeffrey

    老師您好,想請問一下我看了「MQTT教學(二)」和「MQTT教學(四)」的介紹,還是搞不懂為甚麼出現failed, rc=-2,可以請老師解答嗎。
    另外想問,如果照著您的步驟做,一開始的:
    // 設定MAC(實體)位址
    const byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED};
    是要從哪裡查看自己的實體位址呢
    // 設定用戶端和伺服器的IP位址,請自行修改成你的設備的IP位址。
    const IPAddress ip(192,168,1,25);
    const IPAddress server(172,168,1,19);
    IP指的是利用CMD–>ipconfig後查到的本機地址
    那server呢?

    老師您好:
    請問mqtt server 的 ip是怎麼決定或設置的? 之前試過localhost and 127.0.0.1都能正常連接,若單純只有伺服器的話,如果從外部像是esp8266進行mqtt 連線,我總是失敗,ipv4位址也不例外,連伺服器都架不成。請教一下關於ip and server 問題,謝謝

    老師您好,

    最近在開發ESP32連上AWS IOT,如果是以WIFI進行連線,與AWS IOT的連線狀況都很穩定。
    但如果是由SIM7020提供網路訊號的話,與AWS IOT的連線狀況就會變得非常不穩定,大約30-40分鐘就會斷線,重新連線一次,不過因為我們的需求需要ESP32時時刻刻在監聽的狀態,所以這麼頻繁的斷線會影響到正常運行。

    所以不曉得可否請教老師遇到此類問題,可從何下手改善呢?
    非常謝謝!

    Ethernet.begin(mac); // 以DHCP方式連線 Serial.print(Ethernet.localIP()); // 顯示分配到的IP位址 void loop() {

    最新回應

    三星S22 Ultra手機(三):DIY 58mm相機濾鏡保護殼 三星S22 Ultra手機(二):DIY USB Type-C轉3.5mm耳機DAC音頻轉接線 三星S22 Ultra手機(一):替換藍牙耳機的電池 MQTT教學(十一):上傳資料到ThingSpeak MQTT伺服器的Arduino與MicroPython程式 替MSI電競筆電散熱風扇上潤滑油 搭載Wi-Fi與藍牙通訊晶片的Raspberry Pi Pico W微控制板(二) 搭載Wi-Fi與藍牙通訊晶片的Raspberry Pi Pico W微控制板(一) 更換飛利浦電動刮鬍刀的鎳氫充電電池 安裝macOS版的CH340 USB轉TTL序列通訊IC驅動程式 Arduino IDE 2.0(三):編輯器的新功能介紹

    文章分類