本文將示範使用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); } } }
實驗結果:
我們將使用MQTTLens程式訂閱home/yard/DHT11主題,如果你之前沒有透過它訂閱過這個主題,請先訂閱,然後再把上面的程式上傳到Arduino控制板。每隔5秒,MQTTLens將顯示從Mosquitto伺服器轉送過來的訂閱訊息。
使用String資料類型動態建立字串並轉換成字元陣列
補充說明一下建立MQTT訊息(JSON格式的字串)的敘述。這部份採用String資料類型動態建立字串,子字串和數字之間用“+”運算子相連。連接字串的最前面要加上這個String類型變數(msgStr),否則編譯過程將引發資料類型錯誤。由於publish()函式的參數值為字元陣列格式,因此程式要先透過String物件的toCharArray()方法,把String類型的字串轉換成字元陣列格式。
筆者將字元陣列的長度(元素數量)設定成25,足夠此範例的JSON字串使用,如有需要,請自行調整json陣列變數的長度。
感謝分享!!
想問https://swf.com.tw/images/books/IoT/MQTT/mqttLens_2.png 這張圖片的Client Id的功能是?
請參閱上文說明。
阿阿看漏看了 抱歉 感謝老師詳盡的說明
經實驗得到以下結果
由Arduino 發佈 MQTTLens訂閱,MQTTLens可收到訊息
但由MQTTLens發佈 Arduino訂閱,Arduino無法收到訊息
由Arduino發佈 Arduino自己訂閱,Arduino可收到訊息
可以確定Arduino收發正常,但為什麼由MQTTLens發佈訊息Arduino收不到?
可否麻煩您解惑,並告之解決方法,感謝!
我測試使用MQTTLens發布和訂閱MQTT訊息都沒問題,話說回來,MQTTLens只是個測試工具,既然你的Arduino和伺服器程式都能運作無誤,那就沒問題啦~
thanks,
jeffrey
你好,我是来自大陆的电子爱好者,请教下你博客中的插图和流程图是使用什么软件编辑制作,感觉做的好精美好有创意,谢谢,你的博客内容十分给力。
感谢您的鼓励~我是用Adobe Flash(现已改名Animate) 软件,搭配鼠标(偶尔用第一代微软Surface Pro平板的Wacom笔)徒手绘制的。
thanks,
jeffrey
感謝教學!
想請問大大如果我要用mqtt一次連兩個不同的server並分別傳直,這樣可行嗎?
我自己實驗好久一直失敗,只能傳出第一組server,要進入server2時就卡住。
謝謝
請問這兩個物件有分別建立兩組嗎?
不知為何你要連結兩個server,不過,你也可以寫個伺服器端程式,從一個server傳遞訊息給另一個server。
thanks,
jeffrey
請問broker ip 是自己電腦ip位址?
對,伺服器軟體的IP位址就是安裝電腦的IP位址。
thanks,
jeffrey
請問reconnect是?為甚麼我無法編譯出來…
請閱讀上文說明。
上面三個資料庫裡面有包含reconnect??
請問老師SERVER IP是IPv4嗎? CLIENT IP是連接以太/WIFI 的IP嗎? 因為我出現了failed, rc=-2,不知道是哪邊溝通不良…
請先閱讀並實作「MQTT教學(二)」和「MQTT教學(四)」,會比較有完整的概念。
thanks,
jeffrey
請問老師,如果我要把乙太改成WIFI,請問我該怎麼改?
請參考「MQTT教學(九)」
thanks,
jeffrey
老師您好,請問在樹莓派上安裝了mqtt伺服器 並使用arduino uno +esp8266做連線一開始有回傳數值到樹莓派終端介面,但運作一陣子後就無回傳數值了是與keepalive 有關係嗎?
應該不是,因為MQTT用戶端不需要一直保持連線狀態,請測試上傳資料到ThingSpeak MQTT伺服器看看。
thanks,
jeffrey
老師您好,請問如果keep a live 如果是18hr會斷開連線,請問我該如何讓18hr後esp8266 to raspberry pi連線持續運作呢?
請問老師如果我需要用戶端保持連線,有甚麼方式可以實現呢?
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呢?
MAC位址只要不跟區域網路的其他設備衝突即可,例如,用你的手機的MAC位址+1。
伺服器IP位址就是你的電腦的IP位址。
thanks,
jeffrey
請問一下,arduino載入pubsubclient時會顯示strnlen was not decleared in the scope,請問這該如何解決? 謝謝
我剛剛用Arduino Uno編譯本文的範例檔,並沒有問題。請問你用那一款開發板?
thanks,
jeffrey
想請問mymqtt怎麼設定 謝謝
老師您好:
請問mqtt server 的 ip是怎麼決定或設置的? 之前試過localhost and 127.0.0.1都能正常連接,若單純只有伺服器的話,如果從外部像是esp8266進行mqtt 連線,我總是失敗,ipv4位址也不例外,連伺服器都架不成。請教一下關於ip and server 問題,謝謝
您好,請參閱這一則回應。
thanks,
jeffrey
請問PubSubClient程式庫可以與Qtmqtt整合起來使用嗎?
可以
老師您好,
最近在開發ESP32連上AWS IOT,如果是以WIFI進行連線,與AWS IOT的連線狀況都很穩定。
但如果是由SIM7020提供網路訊號的話,與AWS IOT的連線狀況就會變得非常不穩定,大約30-40分鐘就會斷線,重新連線一次,不過因為我們的需求需要ESP32時時刻刻在監聽的狀態,所以這麼頻繁的斷線會影響到正常運行。
所以不曉得可否請教老師遇到此類問題,可從何下手改善呢?
非常謝謝!
拍謝,沒用過GSM通訊模組,建議先用簡單的程式單獨測試GSM模組聯網的穩定性,也請留意GSM模組的供電是否充足。
每隔一段時間(3-5分鐘 或 更短時間) 發送Mqtt連線狀態偵測
如果斷線 就重新建立連線
想請問老師Client ip要如何取得?
MQTT Broker = server
那老師定義的ip 位址是客戶端的ip囉?
你是指Arduino分配到的IP位址嗎?假設你採用乙太網路連線,程式像這樣:
老師您好:
因工作需求,原本我有使用ESP32 + DHT22溫濕度計使用MQTT上傳到Homeassistant,並放置外點機房。
但因為無線訊號有時候會突然斷線,有時機器放置在外地無法遠端重開。
因此最近想嘗試使用ESP32 + w5500(乙太網路) + DHT22溫濕度計,建置在麵包板上,
但程式的撰寫上無法很完整的相容… 因此有許多不太懂的地方想跟您請教。
如果您方便的話,我願意支付學費給您,請您協助我啟動這套建置。
另外看完教學文後有個小問題,
請問在
『// 設定MAC(實體)位址
const byte mac[] = {0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED};』
這個部分,Mac地址是我要先確認W5500 MAC位址後,再填入其中還是這部分可以不用修改他呢?
ESP32晶片有內建TCP/IP層,所以乙太網路不需要用W5500晶片,而是用LAN8720(廣告一下,《超圖解ESP32深度實作》第一章有講),也有現成的,結合ESP32和LAN8720的開發板,商品關鍵字是”WT32-ETH01″。
至於MAC位址,理論上,網卡模組上面應該會貼一張註明MAC位址的貼紙,不過,絕大多數廠商都不願意支付權利金,所以你只要自行設定一個跟區域網路內的其他設備不同的MAC位址值即可。
esp32 有 WatchDog功能 可以在特定條件下 硬體從新開機
希望對此有幫助