標籤:
Linkit 系列博文:
聯發科Linkit 7688 (一) 上手及在Mac下搭建OpenWrt交叉編譯環境,C語言編譯Hello,World
聯發科Linkit 7688 (二)GPIO基本操作與C語言編程
聯發科Linkit 7688 DUO(三): 通過 Arduino 控制外設和感應器
Linkit 7688 DUO(四): 接上各種Arduino感應器和模組——基礎篇
Linkit 7688 DUO(五) 接上各種Arduino感應器和模組—擴充篇
Linkit 7688 DUO(六) 加入MQTT物聯網協議
本篇介紹 將 MQTT物聯網協議載入到 Linkit 7688 DUO開發板上 ( 其中的源碼和方法也適用於所有的Linux系統、也適合於Windows系統)
MQTT是IBM開發的一種物聯網即時通訊協定,該協議支援所有平台,幾乎可以把所有連網物品、電腦APP、手機APP、網頁等串連起來,讓它們隨時相互交換資訊。
有了MQTT協議,Linkit 7688開發板就可以稱得上物聯網開發板了,可以實現很多情境,比如:手機遠端控制、網頁遠端控制、伺服器監測物體狀態等等。
一、MQTT協議簡介
1,MQTT是一種基於TCP的網路訊息協議,用於發送和接收訊息。
在一個MQTT網路中,有一個伺服器和多個用戶端。 每個用戶端以TCP Socket與伺服器串連,並保持為長串連。
用戶端可以是一個連網物體、電腦軟體、手機APP、甚至是網頁。如:
2, 訊息傳輸採取發布/訂閱(publish/subscribe)模式。
用戶端發行就緒(publish)訊息, 每個訊息由一個主題(topic)和一個訊息內容組成。比如:某溫度感應器發布一個訊息: topic = "sensor1/temperature", message="25“
用戶端可以訂閱(subscribe)一個或多個主題的訊息。 當網路中有人發布了這些主題的訊息,則用戶端將收到這條訊息。比如: 手機App訂閱了 “sensor1/temperature"訊息,則上述訊息將被收到, 手機App就知道sensor1的溫度值了。
由於用戶端與伺服器保持著Socket長串連,訊息將立即即時推送到用戶端,也就是說:PUSH到用戶端。
MQTT伺服器的主要作用是接收-轉寄:接收訊息、判斷哪些客訂閱了該主題的訊息,PUSH給相應用戶端。
這種機制,可以實現一對一發送訊息,也可以實現一對多發送訊息(群發)。
應用情境舉例:
情境1: 手機APP發布查溫度的訊息, 各個感應器裝置收到訊息後,發布溫度訊息。則手機就可以收集到各個感應器的溫度。
情境2: 當火警感應器裝置檢測到起火後,發布訊息。 則訂閱了該類型訊息的手機、電腦等均可立即收到火警。
3,MQTT的訊息包傳輸資料量很小(固定長度的頭部僅為 2 位元組),能充分降低網路流量,非常適合於低頻寬、不可靠串連、嵌入式裝置。
同時非常適合於手機等移動通訊環境,可以省流量、省電。因此,有人用MQTT作為手機PUSH使用。
4, 為保障訊息有效率到達,MQTT定義了三種訊息發布服務品質(Qos, Quality Of Service):
“至多一次”(At most once),開銷最小,訊息發布完全依賴底層 TCP/IP 網路,會發生訊息丟失或重複。這一層級可用於如下情況,環境感應器資料,丟失一次讀記錄無所謂,因為不久後還會有第二次發送。
“至少一次”(At least once),確保訊息到達,但訊息重複可能會發生。
“只有一次”(Exactly once),確保訊息到達一次。這一層級可用於如下情況,在計費系統中,訊息重複或丟失會導致不正確的結果。常用於精確控制。
每一條訊息都可以有自己的Qos定義。每一個客訂閱某主題時,也可以指定Qos.
5, MQTT協議由IBM公司於1999年提出,經過多年發展已比較成熟,目前版本是3.1.1。伺服器和用戶端均有多個開源實現,支援各類作業系統和開發語言。
二、適合嵌入式裝置的MQTT協議用戶端(C語言實現)
對於Linkit 7688開發板,我選取 “Eclipse Paho C” 的MQTT 用戶端開原始碼庫 。其首頁在這:https://www.eclipse.org/paho/clients/c/
這個程式碼程式庫是IBM公司提供的,Eclipse項目組維護的,可以稱為官方的。
我的使用方式是: 將它作為一個函數庫, 在Linkit 7688上編寫C語言程式,實現MQTT訊息收發。
這個庫有一點點複雜,需要對協議比較瞭解。如果你要完全搞懂它,需要先讀它的文檔(http://www.eclipse.org/paho/files/mqttdoc/Cclient/index.html)
我們一般總是喜歡把複雜的事情簡單化,因此,我對這個庫進行了封裝, 變成幾個函數, 不需要懂太多就可以用.
庫和常式在我的資源中下載: 其中mqtt目錄是Paho C所有的庫檔案( 原有的檔案都沒改,我增加了 mqtt_client.c , mqtt_client.h 兩個檔案),使用時將mqtt目錄複寫到你的專案檔夾中即可。
在使用前,需要有一個測試用的MQTT伺服器, 我用的是 IBM提供的測試伺服器: messagesight.demos.ibm.com, 連接埠是1883 (1883是MQTT的預設連接埠)
同時需要有一個測試用的MQTT用戶端作為對端,我用的是IBM提供的網頁版MQTT用戶端:http://m2m.demos.ibm.com/mqttclient/
1, 在Linkit 7688上編一個發送MQTT訊息的程式:
用Eclipse 建立一個交叉編譯項目 ( 開發環境搭建請見:聯發科Linkit 7688 (一) 上手及在Mac下搭建OpenWrt交叉編譯環境,C語言編譯Hello,World)
將mqtt目錄複寫到你的專案檔夾下。
建立一個 mqtt_publish.c檔案, 編寫主程式如下:
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include "mqtt/mqtt_client.h"//這個是我對mqtt_client封裝後的標頭檔int publish(int argc, char ** argv) {mqtt_client *m; //mqtt_client 對象指標int ret; //傳回值char *host = "messagesight.demos.ibm.com:1883";//測試伺服器char *topic = "test_topic"; //主題char *client_id = "clientid33883";//用戶端ID; 對測試伺服器,可以隨便寫char *username = NULL;//使用者名稱,用於驗證身份。對測試伺服器,無。char *password = NULL;//密碼,用於驗證身份。對測試伺服器,無。int Qos; //Quality of Service//create new mqtt client objectm = mqtt_new(host, MQTT_PORT, client_id); //建立對象,MQTT_PORT = 1883if ( m == NULL ) {printf("mqtt client create failure, return code = %d\n", errno);return 1;} else {printf("mqtt client created\n");}//connect to serverret = mqtt_connect(m, username, password); //串連伺服器if (ret != MQTT_SUCCESS ) {printf("mqtt client connect failure, return code = %d\n", ret);return 1;} else {printf("mqtt client connect\n");}//publish messageQos = QOS_EXACTLY_ONCE; //Qosret = mqtt_publish(m, topic, "hello from Linkit 7688", Qos);//發布訊息printf("mqtt client publish, return code = %d\n", ret);mqtt_disconnect(m); //disconnectmqtt_delete(m); //delete mqtt client objectreturn 0;}
主程式分為三個步驟:
1, 調用 mqtt_new()建立 用戶端對象
2, 調用 mqtt_connect() 串連伺服器
3, 調用 mqtt_publish() 發布訊息
用Eclipse編譯項目
如果出現錯誤提示:
undefined reference to `pthread_create’
undefined reference to `pthread_mutexattr_init’
這是因為沒有在編譯串連時包含pthread庫, 解決辦法: 需在GCC中添加 -lpthread 參數。
如果出現錯誤提示:
undefined reference to `dlclose’
undefined reference to `dlopen’
undefined reference to `dlsym
這是因為沒有在編譯串連時包含dl庫, 解決辦法: 需在GCC中添加 -ldl 參數
在eclipse中的操作是: 項目Property => C/C++ Build => Settings, 在"Tools Setting"頁,選Cross GCC linker, 在 "Linker flags"編譯框中填入: -lpthread -ldl
OK, 編譯成功了。
用scp將 mqtt_publish 程式上傳到 linkit 7688: scp mqtt_publish [email protected]:/root
先準備接收訊息的網頁用戶端: 用瀏覽器開啟 http://m2m.demos.ibm.com/mqttclient/
在Connect欄中, 點Connect.
在Subscribe欄中,將topic設為 test_topic, 按subscribe
OK, 網頁用戶端準備好接收主題(topic)為 test_topic的訊息了。
用SSH登入入Linkit 7688, 運行 mqtt_publish 程式。
運行結果, 顯示mqtt 建立、串連、發布訊息的過程, return code >=0 表示成功:
mqtt client created
mqtt client connect
mqtt client publish, return code = 1
這時,可以看到瀏覽器中的MQTT網頁用戶端收到了Linkit 7688發布的MQTT訊息
成功了: Linkit 7688發布MQTT訊息到伺服器, 網頁用戶端即時收到伺服器推送來的訊息。
2, 在Linkit 7688上編一個接收MQTT訊息的程式:
用Eclipse 建立一個交叉編譯項目 ( 開發環境搭建請見:聯發科Linkit 7688 (一) 上手及在Mac下搭建OpenWrt交叉編譯環境,C語言編譯Hello,World)
將mqtt目錄複寫到你的專案檔夾下。
建立一個 mqtt_subscribe.c檔案, 編寫主程式如下:
#include <stdio.h>#include <stdlib.h>#include <errno.h>#include <signal.h>#include "mqtt/mqtt_client.h"//這個是我對mqtt_client封裝後的標頭檔int running = 1;void stop_running(int sig){signal(SIGINT, NULL);running = 0;}int main(int argc, char ** argv) {mqtt_client *m; //mqtt_client 對象指標int ret; //傳回值char *host = "messagesight.demos.ibm.com:1883";//測試伺服器char *topic = "test_topic"; //主題char *client_id = "clientid33883";//用戶端ID; 對測試伺服器,可以隨便寫char *username = NULL;//使用者名稱,用於驗證身份。對測試伺服器,無。char *password = NULL;//密碼,用於驗證身份。對測試伺服器,無。int Qos; //Quality of Service//create new mqtt client objectm = mqtt_new(host, MQTT_PORT, client_id); //建立對象,MQTT_PORT = 1883if ( m == NULL ) {printf("mqtt client create failure, return code = %d\n", errno);return 1;} else {printf("mqtt client created\n");}//connect to serverret = mqtt_connect(m, username, password); //串連伺服器if (ret != MQTT_SUCCESS ) {printf("mqtt client connect failure, return code = %d\n", ret);return 1;} else {printf("mqtt client connect\n");}//subscribeQos = QOS_EXACTLY_ONCE;ret = mqtt_subscribe(m, topic, Qos);//訂閱訊息printf("mqtt client subscribe %s, return code = %d\n", topic, ret);signal(SIGINT, stop_running);signal(SIGTERM, stop_running);printf("wait for message of topic: %s ...\n", topic);//loop: waiting message, 迴圈while (running) {int timeout = 200;if ( mqtt_receive(m, timeout) == MQTT_SUCCESS ) { //recieve message,接收訊息printf("received Topic=%s, Message=%s\n", m->received_topic, m->received_message);}mqtt_sleep(200); //sleep a while}mqtt_disconnect(m); //disconnectprintf("mqtt client disconnect");mqtt_delete(m); //delete mqtt client objectreturn 0;}
主程式分為幾個步驟:
1, 調用 mqtt_new()建立 用戶端對象
2, 調用 mqtt_connect() 串連伺服器
3, 調用 mqtt_subscribe() 訂閱訊息
4, 進入迴圈:不斷用 mqtt_receive()檢測有否新訊息,如有,則列印出來。
用Eclipse編譯項目 (記得必須在GCC中加入串連選項: -lpthread -ldl )
OK, 編譯成功了。
用scp將 mqtt_subscribe 程式上傳到 linkit 7688: scp mqtt_subscribe [email protected]:/root
用SSH登入入Linkit 7688, 運行 mqtt_subscribe 程式。則此時出現:
mqtt client created
mqtt client connect
mqtt client subscribe, return code = 0
wait for message of topic: test_topic ...
顯示程式在等待訊息到來
準備發送訊息的網頁用戶端: 用瀏覽器開啟 http://m2m.demos.ibm.com/mqttclient/
在Connect欄中, 點Connect.
在Publish欄中,將topic設為 test_topic, 將Message設為 “Say hello to linkit 7688” 按publish, 則發出一條MQTT訊息。
這時, 看Linkit 7688 SSH用戶端,可以看到,Linkit7688立即收到了這條MQTT訊息
mqtt client created
mqtt client connect
mqtt client subscribe, return code = 0
wait for message of topic: test_topic ...
received Topic=test_topic, Message=Say hello to linkit 7688
成功了: Linkit 7688 即時收到了網頁用戶端發來的訊息。
mqtt_subscribe 程式是死迴圈,一直在接收訊息。 可按CTRL+C退出。
通用性: Eclipse Paho C 的 MQTT用戶端代碼,可以運行在Linux、Windows、Mac上。所以,上述的代碼可以用於Linux、Windows、Mac作業系統,編寫各種App。
對於Android手機, Eclipse Paho另提供了Java的程式碼程式庫。
Linkit 7688 DUO(六) 加入MQTT物聯網協議