一、前言
項目設(shè)計里用到的全部工具軟件和源碼工程,都可以網(wǎng)盤里下載。
https://pan.quark.cn/s/145a9b3f7f53
1.1 項目介紹
【1】項目開發(fā)背景
隨著現(xiàn)代農(nóng)業(yè)技術(shù)的發(fā)展,智能化管理在農(nóng)業(yè)生產(chǎn)中扮演著越來越重要的角色。特別是在養(yǎng)殖業(yè)中,環(huán)境條件的好壞直接關(guān)系到養(yǎng)殖動物的生長狀態(tài)和健康水平。因此,建立一個能夠?qū)崟r監(jiān)控并調(diào)節(jié)養(yǎng)殖環(huán)境的系統(tǒng)顯得尤為重要。這樣的系統(tǒng)不僅可以提高養(yǎng)殖效率,減少人工成本,還可以通過精準控制環(huán)境參數(shù)來提升動物福利,進而保證產(chǎn)品質(zhì)量。
基于此背景,本項目設(shè)計并實現(xiàn)一套基于STM32微控制器的養(yǎng)殖場環(huán)境監(jiān)測系統(tǒng)。該系統(tǒng)將集成多種傳感器用于采集環(huán)境數(shù)據(jù),如溫度、濕度和光照強度等關(guān)鍵參數(shù)。通過本地OLED顯示屏,工作人員可以實時查看這些數(shù)據(jù),確保養(yǎng)殖環(huán)境處于最佳狀態(tài)。同時,考慮到現(xiàn)代養(yǎng)殖業(yè)對于遠程管理和自動化的需求,系統(tǒng)還將配備ESP8266 WiFi模塊,通過MQTT協(xié)議與華為云物聯(lián)網(wǎng)平臺進行通訊。這樣一來,借助于在Windows平臺上設(shè)計的上位機軟件以及Android移動應(yīng)用,管理者可以從任何地點遠程監(jiān)控環(huán)境狀況,調(diào)整預(yù)設(shè)閾值,并且根據(jù)實際情況啟動相應(yīng)的設(shè)備,比如控制通風(fēng)風(fēng)扇、加濕器或照明燈具。
此外,系統(tǒng)的硬件選型也經(jīng)過了深思熟慮。為了確保系統(tǒng)的穩(wěn)定性和可靠性,采用了成熟可靠的傳感器和技術(shù)方案,如DHT11溫濕度傳感器、BH1750光敏傳感器以及通過繼電器控制的5V風(fēng)扇和加濕器模塊。供電方面選擇了常見的USB線-5V供電方式,既方便又節(jié)能。整個系統(tǒng)的開發(fā)不僅能夠滿足現(xiàn)代化養(yǎng)殖業(yè)的實際需求,同時也為未來進一步的技術(shù)升級提供了堅實的基礎(chǔ)。
【2】設(shè)計實現(xiàn)的功能
(1)實時環(huán)境監(jiān)測
系統(tǒng)配備了DHT11溫濕度傳感器和BH1750光敏傳感器,能夠持續(xù)監(jiān)測養(yǎng)殖區(qū)域內(nèi)的溫度、濕度以及光照強度等關(guān)鍵環(huán)境參數(shù)。這些數(shù)據(jù)的實時收集有助于及時發(fā)現(xiàn)環(huán)境變化,從而迅速采取措施調(diào)整環(huán)境條件。
(2)本地數(shù)據(jù)顯示
通過OLED顯示屏,系統(tǒng)能夠在本地即時顯示所監(jiān)測到的各項環(huán)境參數(shù)。這使得工作人員無需依賴外部設(shè)備即可了解當(dāng)前的環(huán)境狀況,便于快速響應(yīng)環(huán)境變化。
(3)遠程監(jiān)控與管理
系統(tǒng)內(nèi)置了ESP8266 WiFi模塊,允許它通過MQTT協(xié)議與華為云物聯(lián)網(wǎng)平臺建立連接。借助于該平臺,用戶可以利用Windows上位機軟件或Android移動應(yīng)用程序遠程訪問環(huán)境監(jiān)測數(shù)據(jù),實現(xiàn)了跨越地理限制的環(huán)境監(jiān)控。此外,用戶還可以通過上位機軟件或移動應(yīng)用設(shè)置環(huán)境參數(shù)的閾值,并接收超出閾值范圍的告警通知。
(4)自動化控制
當(dāng)環(huán)境監(jiān)測數(shù)據(jù)超出預(yù)定的安全范圍時,系統(tǒng)能夠自動啟動相應(yīng)的設(shè)備進行環(huán)境調(diào)節(jié)。例如,當(dāng)濕度超過預(yù)設(shè)值時,系統(tǒng)會自動開啟通風(fēng)風(fēng)扇來降低濕度;當(dāng)濕度低于設(shè)定值,則啟動加濕器增加空氣濕度;若光照不足,則自動開啟燈光補充光源。這樣,即使在無人值守的情況下,也能確保環(huán)境條件保持在適宜范圍內(nèi)。
(5)設(shè)備控制接口
系統(tǒng)設(shè)計了繼電器控制接口來操作5V風(fēng)扇和加濕器模塊,確保了設(shè)備的安全啟停。通過這種方式,不僅簡化了硬件設(shè)計,而且提高了系統(tǒng)的可靠性和使用壽命。
【3】項目硬件模塊組成
【1】STM32微控制器 :作為整個系統(tǒng)的主控單元,負責(zé)協(xié)調(diào)各個傳感器的數(shù)據(jù)采集、處理邏輯運算以及控制指令的發(fā)送。
【2】DHT11溫濕度傳感器 :用于實時監(jiān)測養(yǎng)殖環(huán)境中的溫度和濕度,并將數(shù)據(jù)傳送給STM32進行處理。
【3】BH1750光敏傳感器 :用于檢測養(yǎng)殖環(huán)境內(nèi)的光照強度,為系統(tǒng)提供光照數(shù)據(jù),以便做出相應(yīng)的照明控制決策。
【4】OLED顯示屏 :用于在本地顯示傳感器采集到的環(huán)境數(shù)據(jù),包括溫度、濕度和光照強度等信息。
【5】ESP8266 WiFi模塊 :使系統(tǒng)能夠接入無線網(wǎng)絡(luò),并通過MQTT協(xié)議與華為云物聯(lián)網(wǎng)平臺通信,實現(xiàn)遠程數(shù)據(jù)傳輸。
【6】繼電器模塊 :用于控制5V風(fēng)扇和加濕器的開關(guān),確保設(shè)備根據(jù)環(huán)境參數(shù)的變化自動啟停。
【7】5V小風(fēng)扇 :當(dāng)濕度超標時,通過繼電器模塊啟動,以幫助降低環(huán)境濕度。
【8】5V加濕器模塊 :當(dāng)濕度低于設(shè)定值時,由繼電器模塊控制開啟,以增加環(huán)境濕度。
【9】LED照明系統(tǒng) :當(dāng)光照強度低于閾值時,自動開啟以補充光照,確保養(yǎng)殖環(huán)境的光照條件符合要求。
【10】USB供電模塊 :系統(tǒng)采用USB線5V供電方式,為整個系統(tǒng)提供穩(wěn)定的電力供應(yīng),確保所有組件正常工作。
【4】需求總結(jié)
基于STM32設(shè)計的養(yǎng)殖場環(huán)境監(jiān)測系統(tǒng)
支持的功能如下:
(1)支持對養(yǎng)殖環(huán)境中的溫度、濕度、光照強度實時監(jiān)測;
(2)本地通過0LED顯示屏實時顯示檢測數(shù)據(jù)。
(3)能夠通過WiFi模塊通過MQTT協(xié)議連接華為云物聯(lián)網(wǎng)平臺,通過Qt設(shè)計Windows上位機和Android手機APP,能遠程查看環(huán)境參數(shù),設(shè)置閥值參數(shù),控制通風(fēng)風(fēng)扇。
(4)當(dāng)濕度超過閥值,通過風(fēng)扇實現(xiàn)送風(fēng);當(dāng)濕度低于值,利用加濕模塊實現(xiàn)加濕;當(dāng)光照強度低于閾值,打開燈;
硬件選型:
(1)WiFi模塊采用ESP8266-WIFI模塊
(2)光敏模塊:采用BH1750光敏傳感器
(3)風(fēng)扇模塊:采用5V小風(fēng)扇,通過繼電器控制開關(guān)。
(4)加濕器模塊:采用5V加濕器模塊,通過繼電器控制開關(guān)。
(5)溫濕度傳感器:采用DHT11
(6)供電電源:采用USB線-5V供電。
1.2 設(shè)計思路
設(shè)計思路的核心在于構(gòu)建一個高效且易于維護的智能環(huán)境監(jiān)測系統(tǒng),通過自動化的手段來優(yōu)化養(yǎng)殖環(huán)境的管理。
第一步,選擇STM32作為主控芯片是因為其高性能與低功耗特性,能夠很好地支持多任務(wù)處理,同時保證了系統(tǒng)的穩(wěn)定運行。此外,STM32豐富的外設(shè)接口也方便了與其他傳感器和執(zhí)行機構(gòu)的連接,為后續(xù)的功能擴展提供了便利。
針對環(huán)境參數(shù)的監(jiān)測,項目選用了DHT11溫濕度傳感器和BH1750光敏傳感器。DHT11因其高性價比和簡單的接口協(xié)議被廣泛應(yīng)用于各種環(huán)境監(jiān)測場景,而BH1750則以其高精度測量能力和良好的穩(wěn)定性成為了光照強度檢測的理想選擇。這兩類傳感器的數(shù)據(jù)采集結(jié)果將通過STM32的ADC模塊進行讀取,并經(jīng)由主控芯片內(nèi)部的邏輯判斷后,決定是否需要啟動環(huán)境調(diào)節(jié)裝置。
為了便于現(xiàn)場人員隨時了解環(huán)境狀況,設(shè)計中加入了OLED顯示屏,用于直觀展示傳感器采集的數(shù)據(jù)。OLED屏幕的優(yōu)勢在于其清晰度高且視角寬廣,即便是在光線較暗的環(huán)境中也能清晰地顯示信息。
考慮到遠程監(jiān)控的重要性,系統(tǒng)引入了ESP8266 WiFi模塊。通過WiFi模塊,系統(tǒng)可以連接至互聯(lián)網(wǎng),并利用MQTT協(xié)議與華為云物聯(lián)網(wǎng)平臺進行數(shù)據(jù)交換。MQTT協(xié)議因其輕量級和高效性非常適合于物聯(lián)網(wǎng)應(yīng)用場景,能夠有效支持大量的并發(fā)連接,確保數(shù)據(jù)的實時傳輸。借助云端服務(wù),用戶可以通過上位機軟件或智能手機應(yīng)用程序?qū)崟r查看環(huán)境參數(shù),并根據(jù)需要調(diào)整設(shè)置。
在執(zhí)行機構(gòu)的選擇上,系統(tǒng)采用了5V小風(fēng)扇和加濕器模塊,并通過繼電器進行控制。這種設(shè)計不僅簡化了電路結(jié)構(gòu),還增強了系統(tǒng)的安全性和可靠性。當(dāng)環(huán)境濕度或光照不符合預(yù)設(shè)標準時,系統(tǒng)會自動啟動相應(yīng)的設(shè)備來進行調(diào)節(jié),確保環(huán)境始終處于最佳狀態(tài)。
在供電方案上選擇了USB線5V供電,這不僅簡化了電源設(shè)計,降低了系統(tǒng)的復(fù)雜性,還使得系統(tǒng)更加節(jié)能環(huán)保。整體設(shè)計思路強調(diào)了系統(tǒng)集成度與自動化程度,力求通過最小的硬件配置實現(xiàn)最有效的環(huán)境管理。
1.3 系統(tǒng)功能總結(jié)
功能類別 | 具體功能描述 |
---|---|
環(huán)境監(jiān)測 | - 實時監(jiān)測養(yǎng)殖環(huán)境中的溫度、濕度及光照強度。- 數(shù)據(jù)通過DHT11溫濕度傳感器和BH1750光敏傳感器采集。 |
本地數(shù)據(jù)顯示 | - 利用OLED顯示屏實時顯示各項環(huán)境參數(shù)。 |
遠程監(jiān)控 | - 通過ESP8266 WiFi模塊連接互聯(lián)網(wǎng)。- 使用MQTT協(xié)議與華為云物聯(lián)網(wǎng)平臺進行數(shù)據(jù)交互。 |
遠程管理 | - 在Windows上位機軟件或Android應(yīng)用程序上查看環(huán)境參數(shù)。- 設(shè)置環(huán)境參數(shù)的閾值。- 接收超出閾值的告警信息。 |
自動化控制 | - 當(dāng)濕度超過設(shè)定閾值時,自動啟動風(fēng)扇降低濕度。- 當(dāng)濕度低于設(shè)定閾值時,啟動加濕器增加濕度。- 當(dāng)光照強度低于設(shè)定閾值時,自動開啟燈光補充光照。 |
設(shè)備控制接口 | - 通過繼電器模塊控制5V風(fēng)扇和加濕器模塊的開關(guān)。 |
供電系統(tǒng) | - 采用USB線5V供電,確保系統(tǒng)的穩(wěn)定運行。 |
1.4 開發(fā)工具的選擇
【1】設(shè)備端開發(fā)
STM32的編程語言選擇C語言,C語言執(zhí)行效率高,大學(xué)里主學(xué)的C語言,C語言編譯出來的可執(zhí)行文件最接近于機器碼,匯編語言執(zhí)行效率最高,但是匯編的移植性比較差,目前在一些操作系統(tǒng)內(nèi)核里還有一些低配的單片機使用的較多,平常的單片機編程還是以C語言為主。C語言的執(zhí)行效率僅次于匯編,語法理解簡單、代碼通用性強,也支持跨平臺,在嵌入式底層、單片機編程里用的非常多,當(dāng)前的設(shè)計就是采用C語言開發(fā)。
開發(fā)工具選擇Keil,keil是一家世界領(lǐng)先的嵌入式微控制器軟件開發(fā)商,在2015年,keil被ARM公司收購。因為當(dāng)前芯片選擇的是STM32F103系列,STMF103是屬于ARM公司的芯片構(gòu)架、Cortex-M3內(nèi)核系列的芯片,所以使用Kile來開發(fā)STM32是有先天優(yōu)勢的,而keil在各大高校使用的也非常多,很多教科書里都是以keil來教學(xué),開發(fā)51單片機、STM32單片機等等。目前作為MCU芯片開發(fā)的軟件也不只是keil一家獨大,IAR在MCU微處理器開發(fā)領(lǐng)域里也使用的非常多,IAR擴展性更強,也支持STM32開發(fā),也支持其他芯片,比如:CC2530,51單片機的開發(fā)。從軟件的使用上來講,IAR比keil更加簡潔,功能相對少一些。如果之前使用過keil,而且使用頻率較多,已經(jīng)習(xí)慣再使用IAR是有點不適應(yīng)界面的。
【2】上位機開發(fā)
上位機的開發(fā)選擇Qt框架,編程語言采用C++;Qt是一個1991年由Qt Company開發(fā)的跨平臺C++圖形用戶界面應(yīng)用程序開發(fā)框架。它既可以開發(fā)GUI程序,也可用于開發(fā)非GUI程序,比如控制臺工具和服務(wù)器。Qt是面向?qū)ο蟮目蚣?,使用特殊的代碼生成擴展(稱為元對象編譯器(Meta Object Compiler, moc))以及一些宏,Qt很容易擴展,并且允許真正地組件編程。Qt能輕松創(chuàng)建具有原生C++性能的連接設(shè)備、用戶界面(UI)和應(yīng)用程序。它功能強大且結(jié)構(gòu)緊湊,擁有直觀的工具和庫。
二、部署華為云物聯(lián)網(wǎng)平臺
**華為云官網(wǎng): **[https://www.huaweicloud.com/]
**打開官網(wǎng),搜索物聯(lián)網(wǎng),就能快速找到 **設(shè)備接入IoTDA
。
2.1 物聯(lián)網(wǎng)平臺介紹
華為云物聯(lián)網(wǎng)平臺(IoT 設(shè)備接入云服務(wù))提供海量設(shè)備的接入和管理能力,將物理設(shè)備聯(lián)接到云,支撐設(shè)備數(shù)據(jù)采集上云和云端下發(fā)命令給設(shè)備進行遠程控制,配合華為云其他產(chǎn)品,幫助我們快速構(gòu)筑物聯(lián)網(wǎng)解決方案。
使用物聯(lián)網(wǎng)平臺構(gòu)建一個完整的物聯(lián)網(wǎng)解決方案主要包括3部分:物聯(lián)網(wǎng)平臺、業(yè)務(wù)應(yīng)用和設(shè)備。
物聯(lián)網(wǎng)平臺作為連接業(yè)務(wù)應(yīng)用和設(shè)備的中間層,屏蔽了各種復(fù)雜的設(shè)備接口,實現(xiàn)設(shè)備的快速接入;同時提供強大的開放能力,支撐行業(yè)用戶構(gòu)建各種物聯(lián)網(wǎng)解決方案。
設(shè)備可以通過固網(wǎng)、2G/3G/4G/5G、NB-IoT、Wifi等多種網(wǎng)絡(luò)接入物聯(lián)網(wǎng)平臺,并使用LWM2M/CoAP、MQTT、HTTPS協(xié)議將業(yè)務(wù)數(shù)據(jù)上報到平臺,平臺也可以將控制命令下發(fā)給設(shè)備。
業(yè)務(wù)應(yīng)用通過調(diào)用物聯(lián)網(wǎng)平臺提供的API,實現(xiàn)設(shè)備數(shù)據(jù)采集、命令下發(fā)、設(shè)備管理等業(yè)務(wù)場景。
2.2 開通物聯(lián)網(wǎng)服務(wù)
**地址: **[https://www.huaweicloud.com/product/iothub.html]
點擊立即創(chuàng)建
。
正在創(chuàng)建標準版實例,需要等待片刻。
創(chuàng)建完成之后,點擊實例名稱。 可以看到標準版實例的設(shè)備接入端口和地址。
在上面也能看到 免費單元的限制。
開通之后,點擊總覽
,也能查看接入信息。 我們當(dāng)前設(shè)備準備采用MQTT協(xié)議接入華為云平臺,這里可以看到MQTT協(xié)議的地址和端口號等信息。
總結(jié):
端口號: MQTT (1883)| MQTTS (8883)
接入地址:ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
**根據(jù)域名地址得到IP地址信息: **
打開Windows電腦的命令行控制臺終端,使用ping
命令。ping
一下即可。
Microsoft Windows [版本 10.0.19045.4170]
(c) Microsoft Corporation。保留所有權(quán)利。
C:Users11266 >ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
正在 Ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字節(jié)的數(shù)據(jù):
來自 117.78.5.125 的回復(fù): 字節(jié)=32 時間=35ms TTL=93
來自 117.78.5.125 的回復(fù): 字節(jié)=32 時間=36ms TTL=93
來自 117.78.5.125 的回復(fù): 字節(jié)=32 時間=36ms TTL=93
來自 117.78.5.125 的回復(fù): 字節(jié)=32 時間=39ms TTL=93
117.78.5.125 的 Ping 統(tǒng)計信息:
數(shù)據(jù)包: 已發(fā)送 = 4,已接收 = 4,丟失 = 0 (0% 丟失),
往返行程的估計時間(以毫秒為單位):
最短 = 35ms,最長 = 39ms,平均 = 36ms
C:Users11266 >
MQTT協(xié)議接入端口號有兩個,1883是非加密端口,8883是證書加密端口,單片機無法加載證書,所以使用1883端口比較合適。 接下來的ESP8266就采用1883端口連接華為云物聯(lián)網(wǎng)平臺。
2.3 創(chuàng)建產(chǎn)品
(1)創(chuàng)建產(chǎn)品
(2)填寫產(chǎn)品信息
根據(jù)自己產(chǎn)品名字填寫,下面的設(shè)備類型選擇自定義類型。
(3)產(chǎn)品創(chuàng)建成功
創(chuàng)建完成之后點擊查看詳情。
(4)添加自定義模型
產(chǎn)品創(chuàng)建完成之后,點擊進入產(chǎn)品詳情頁面,翻到最下面可以看到模型定義。
模型簡單來說: 就是存放設(shè)備上傳到云平臺的數(shù)據(jù)。
你可以根據(jù)自己的產(chǎn)品進行創(chuàng)建。
比如:
煙霧可以叫 MQ2
溫度可以叫 Temperature
濕度可以叫 humidity
火焰可以叫 flame
其他的傳感器自己用單詞簡寫命名即可。 這就是你的單片機設(shè)備端上傳到服務(wù)器的數(shù)據(jù)名字。
先點擊自定義模型。
再創(chuàng)建一個服務(wù)ID。
接著點擊新增屬性。
2.4 添加設(shè)備
產(chǎn)品是屬于上層的抽象模型,接下來在產(chǎn)品模型下添加實際的設(shè)備。添加的設(shè)備最終需要與真實的設(shè)備關(guān)聯(lián)在一起,完成數(shù)據(jù)交互。
(1)注冊設(shè)備
(2)根據(jù)自己的設(shè)備填寫
(3)保存設(shè)備信息
創(chuàng)建完畢之后,點擊保存并關(guān)閉,得到創(chuàng)建的設(shè)備密匙信息。該信息在后續(xù)生成MQTT三元組的時候需要使用。
(4)設(shè)備創(chuàng)建完成
(5)設(shè)備詳情
2.5 MQTT協(xié)議主題訂閱與發(fā)布
(1)MQTT協(xié)議介紹
當(dāng)前的設(shè)備是采用MQTT協(xié)議與華為云平臺進行通信。
MQTT是一個物聯(lián)網(wǎng)傳輸協(xié)議,它被設(shè)計用于輕量級的發(fā)布/訂閱式消息傳輸,旨在為低帶寬和不穩(wěn)定的網(wǎng)絡(luò)環(huán)境中的物聯(lián)網(wǎng)設(shè)備提供可靠的網(wǎng)絡(luò)服務(wù)。MQTT是專門針對物聯(lián)網(wǎng)開發(fā)的輕量級傳輸協(xié)議。MQTT協(xié)議針對低帶寬網(wǎng)絡(luò),低計算能力的設(shè)備,做了特殊的優(yōu)化,使得其能適應(yīng)各種物聯(lián)網(wǎng)應(yīng)用場景。目前MQTT擁有各種平臺和設(shè)備上的客戶端,已經(jīng)形成了初步的生態(tài)系統(tǒng)。
MQTT是一種消息隊列協(xié)議,使用發(fā)布/訂閱消息模式,提供一對多的消息發(fā)布,解除應(yīng)用程序耦合,相對于其他協(xié)議,開發(fā)更簡單;MQTT協(xié)議是工作在TCP/IP協(xié)議上;由TCP/IP協(xié)議提供穩(wěn)定的網(wǎng)絡(luò)連接;所以,只要具備TCP協(xié)議棧的網(wǎng)絡(luò)設(shè)備都可以使用MQTT協(xié)議。 本次設(shè)備采用的ESP8266就具備TCP協(xié)議棧,能夠建立TCP連接,所以,配合STM32代碼里封裝的MQTT協(xié)議,就可以與華為云平臺完成通信。
**華為云的MQTT協(xié)議接入幫助文檔在這里: **https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
業(yè)務(wù)流程:
(2)華為云平臺MQTT協(xié)議使用限制
描述 | 限制 |
---|---|
支持的MQTT協(xié)議版本 | 3.1.1 |
與標準MQTT協(xié)議的區(qū)別 | 支持Qos 0和Qos 1支持Topic自定義不支持QoS2不支持will、retain msg |
MQTTS支持的安全等級 | 采用TCP通道基礎(chǔ) + TLS協(xié)議(最高TLSv1.3版本) |
單帳號每秒最大MQTT連接請求數(shù) | 無限制 |
單個設(shè)備每分鐘支持的最大MQTT連接數(shù) | 1 |
單個MQTT連接每秒的吞吐量,即帶寬,包含直連設(shè)備和網(wǎng)關(guān) | 3KB/s |
MQTT單個發(fā)布消息最大長度,超過此大小的發(fā)布請求將被直接拒絕 | 1MB |
MQTT連接心跳時間建議值 | 心跳時間限定為30至1200秒,推薦設(shè)置為120秒 |
產(chǎn)品是否支持自定義Topic | 支持 |
消息發(fā)布與訂閱 | 設(shè)備只能對自己的Topic進行消息發(fā)布與訂閱 |
每個訂閱請求的最大訂閱數(shù) | 無限制 |
(3)主題訂閱格式
幫助文檔地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
對于設(shè)備而言,一般會訂閱平臺下發(fā)消息給設(shè)備 這個主題。
設(shè)備想接收平臺下發(fā)的消息,就需要訂閱平臺下發(fā)消息給設(shè)備 的主題,訂閱后,平臺下發(fā)消息給設(shè)備,設(shè)備就會收到消息。
如果設(shè)備想要知道平臺下發(fā)的消息,需要訂閱上面圖片里標注的主題。
以當(dāng)前設(shè)備為例,最終訂閱主題的格式如下:
$oc/devices/{device_id}/sys/messages/down
最終的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down
(4)主題發(fā)布格式
對于設(shè)備來說,主題發(fā)布表示向云平臺上傳數(shù)據(jù),將最新的傳感器數(shù)據(jù),設(shè)備狀態(tài)上傳到云平臺。
這個操作稱為:屬性上報。
幫助文檔地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html
根據(jù)幫助文檔的介紹, 當(dāng)前設(shè)備發(fā)布主題,上報屬性的格式總結(jié)如下:
發(fā)布的主題格式:
$oc/devices/{device_id}/sys/properties/report
最終的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report
發(fā)布主題時,需要上傳數(shù)據(jù),這個數(shù)據(jù)格式是JSON格式。
上傳的JSON數(shù)據(jù)格式如下:
{
"services": [
{
"service_id": < 填服務(wù)ID >,
"properties": {
"< 填屬性名稱1 >": < 填屬性值 >,
"< 填屬性名稱2 >": < 填屬性值 >,
..........
}
}
]
}
根據(jù)JSON格式,一次可以上傳多個屬性字段。 這個JSON格式里的,服務(wù)ID,屬性字段名稱,屬性值類型,在前面創(chuàng)建產(chǎn)品的時候就已經(jīng)介紹了,不記得可以翻到前面去查看。
根據(jù)這個格式,組合一次上傳的屬性數(shù)據(jù):
{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}
2.6 MQTT三元組
**MQTT協(xié)議登錄需要填用戶ID,設(shè)備ID,設(shè)備密碼等信息,就像我們平時登錄QQ,微信一樣要輸入賬號密碼才能登錄。MQTT協(xié)議登錄的這3個參數(shù),一般稱為MQTT三元組。 **
接下來介紹,華為云平臺的MQTT三元組參數(shù)如何得到。
(1)MQTT服務(wù)器地址
要登錄MQTT服務(wù)器,首先記得先知道服務(wù)器的地址是多少,端口是多少。
幫助文檔地址:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home
MQTT協(xié)議的端口支持1883和8883,它們的區(qū)別是:8883 是加密端口更加安全。但是單片機上使用比較困難,所以當(dāng)前的設(shè)備是采用1883端口進連接的。
根據(jù)上面的域名和端口號,得到下面的IP地址和端口號信息: 如果設(shè)備支持填寫域名可以直接填域名,不支持就直接填寫IP地址。 (IP地址就是域名解析得到的)
華為云的MQTT服務(wù)器地址:117.78.5.125
華為云的MQTT端口號:1883
如何得到IP地址?如何域名轉(zhuǎn)IP? 打開Windows的命令行輸入以下命令。
ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
(2)生成MQTT三元組
**華為云提供了一個在線工具,用來生成MQTT鑒權(quán)三元組: **https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打開這個工具,填入設(shè)備的信息(也就是剛才創(chuàng)建完設(shè)備之后保存的信息),點擊生成,就可以得到MQTT的登錄信息了。
下面是打開的頁面:
填入設(shè)備的信息: (上面兩行就是設(shè)備創(chuàng)建完成之后保存得到的)
直接得到三元組信息。
得到三元組之后,設(shè)備端通過MQTT協(xié)議登錄鑒權(quán)的時候,填入?yún)?shù)即可。
ClientId 663cb18871d845632a0912e7_dev1_0_0_2024050911
Username 663cb18871d845632a0912e7_dev1
Password 71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237
2.7 模擬設(shè)備登錄測試
**經(jīng)過上面的步驟介紹,已經(jīng)創(chuàng)建了產(chǎn)品,設(shè)備,數(shù)據(jù)模型,得到MQTT登錄信息。 接下來就用MQTT客戶端軟件模擬真實的設(shè)備來登錄平臺。測試與服務(wù)器通信是否正常。 **
(1)填入登錄信息
打開MQTT客戶端軟件,對號填入相關(guān)信息(就是上面的文本介紹)。然后,點擊登錄,訂閱主題,發(fā)布主題。
(2)打開網(wǎng)頁查看
完成上面的操作之后,打開華為云網(wǎng)頁后臺,可以看到設(shè)備已經(jīng)在線了。
點擊詳情頁面,可以看到上傳的數(shù)據(jù):
到此,云平臺的部署已經(jīng)完成,設(shè)備已經(jīng)可以正常上傳數(shù)據(jù)了。
(3)MQTT登錄測試參數(shù)總結(jié)
MQTT服務(wù)器: 117.78.5.125
MQTT端口號: 183
//物聯(lián)網(wǎng)服務(wù)器的設(shè)備信息
#define MQTT_ClientID "663cb18871d845632a0912e7_dev1_0_0_2024050911"
#define MQTT_UserName "663cb18871d845632a0912e7_dev1"
#define MQTT_PassWord "71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237"
//訂閱與發(fā)布的主題
#define SET_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down" //訂閱
#define POST_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report" //發(fā)布
發(fā)布的數(shù)據(jù):
{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}
2.8 創(chuàng)建IAM賬戶
創(chuàng)建一個IAM賬戶,因為接下來開發(fā)上位機,需要使用云平臺的API接口,這些接口都需要token進行鑒權(quán)。簡單來說,就是身份的認證。 調(diào)用接口獲取Token時,就需要填寫IAM賬號信息。所以,接下來演示一下過程。
**地址: **https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users
**【1】獲取項目憑證 ** 點擊左上角用戶名,選擇下拉菜單里的我的憑證
項目憑證:
28add376c01e4a61ac8b621c714bf459
【2】創(chuàng)建IAM用戶
鼠標放在左上角頭像上,在下拉菜單里選擇統(tǒng)一身份認證
。
點擊左上角創(chuàng)建用戶
。
創(chuàng)建成功:
【3】創(chuàng)建完成
用戶信息如下:
主用戶名 l19504562721
IAM用戶 ds_abc
密碼 DS12345678
2.9 獲取影子數(shù)據(jù)
幫助文檔:https://support.huaweicloud.com/api-iothub/iot_06_v5_0079.html
設(shè)備影子介紹:
設(shè)備影子是一個用于存儲和檢索設(shè)備當(dāng)前狀態(tài)信息的JSON文檔。
每個設(shè)備有且只有一個設(shè)備影子,由設(shè)備ID唯一標識
設(shè)備影子僅保存最近一次設(shè)備的上報數(shù)據(jù)和預(yù)期數(shù)據(jù)
無論該設(shè)備是否在線,都可以通過該影子獲取和設(shè)置設(shè)備的屬性
簡單來說:設(shè)備影子就是保存,設(shè)備最新上傳的一次數(shù)據(jù)。
我們設(shè)計的軟件里,如果想要獲取設(shè)備的最新狀態(tài)信息,就采用設(shè)備影子接口。
如果對接口不熟悉,可以先進行在線調(diào)試:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow
在線調(diào)試接口,可以請求影子接口,了解請求,與返回的數(shù)據(jù)格式。
調(diào)試完成看右下角的響應(yīng)體,就是返回的影子數(shù)據(jù)。
設(shè)備影子接口返回的數(shù)據(jù)如下:
{
"device_id": "663cb18871d845632a0912e7_dev1",
"shadow": [
{
"service_id": "stm32",
"desired": {
"properties": null,
"event_time": null
},
"reported": {
"properties": {
"DHT11_T": 18,
"DHT11_H": 90,
"BH1750": 38,
"MQ135": 70
},
"event_time": "20240509T113448Z"
},
"version": 3
}
]
}
調(diào)試成功之后,可以得到訪問影子數(shù)據(jù)的真實鏈接,接下來的代碼開發(fā)中,就采用Qt寫代碼訪問此鏈接,獲取影子數(shù)據(jù),完成上位機開發(fā)。
鏈接如下:
https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c714bf459/devices/663cb18871d845632a0912e7_dev1/shadow
三、上位機開發(fā)
為了方便查看設(shè)備上傳的數(shù)據(jù),接下來利用Qt開發(fā)一款A(yù)ndroid手機APP 和 Windows上位機。
使用華為云平臺提供的API接口獲取設(shè)備上傳的數(shù)據(jù),進行可視化顯示,以及遠程控制設(shè)備。
3.1 Qt開發(fā)環(huán)境安裝
**Qt的中文官網(wǎng): **https://www.qt.io/zh-cn/
QT5.12.6的下載地址:https://download.qt.io/archive/qt/5.12/5.12.6
或者去網(wǎng)盤里下載:https://pan.quark.cn/s/145a9b3f7f53
打開下載鏈接后選擇下面的版本進行下載:
qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details
軟件安裝時斷網(wǎng)安裝,否則會提示輸入賬戶。
安裝的時候,第一個復(fù)選框里勾選一個mingw 32
編譯器即可,其他的不管默認就行,直接點擊下一步繼續(xù)安裝。
選擇MinGW 32-bit 編譯器: (一定要看清楚了)
說明: 我這里只是介紹PC端,也就是Windows系統(tǒng)下的Qt環(huán)境搭建。 Android的開發(fā)環(huán)境比較麻煩,如果想學(xué)習(xí)Android開發(fā),想編譯Android程序的APP,需要自己去搭建Android環(huán)境。
也可以看下面這篇文章,不過這個文章是在Qt開發(fā)專欄里付費的,需要訂閱專欄才可以看。 如果不想付費看,也可以自行找其他教程,自己搭建好必須的環(huán)境就行了
Android環(huán)境搭建的博客鏈接:https://blog.csdn.net/xiaolong1126626497/article/details/117254453
3.2 新建上位機工程
前面2講解了需要用的API接口,接下來就使用Qt設(shè)計上位機,設(shè)計界面,完成整體上位機的邏輯設(shè)計。
【1】新建工程
【2】設(shè)置項目的名稱。
【3】選擇編譯系統(tǒng)
【4】選擇默認繼承的類
【5】選擇編譯器
【6】點擊完成
【7】工程創(chuàng)建完成
3.3 設(shè)計UI界面與工程配置
【1】打開UI文件
打開默認的界面如下:
【2】開始設(shè)計界面
根據(jù)自己需求設(shè)計界面。
3.5 編譯Windows上位機
點擊軟件左下角的綠色三角形按鈕進行編譯運行。
編譯之后的效果:
3.6 配置Android環(huán)境
如果想編譯Android手機APP,必須要先自己配置好自己的Android環(huán)境。(搭建環(huán)境的過程可以自行百度搜索學(xué)習(xí))
然后才可以進行下面的步驟。
【1】選擇Android編譯器
【2】創(chuàng)建Android配置文件
創(chuàng)建完成。
【3】配置Android圖標與名稱
【3】編譯Android上位機
Qt本身是跨平臺的,直接選擇Android的編譯器,就可以將程序編譯到Android平臺。
然后點擊構(gòu)建。
成功之后,在目錄下可以看到生成的apk
文件,也就是Android手機的安裝包,電腦端使用QQ
發(fā)送給手機QQ,手機登錄QQ接收,就能直接安裝。
生成的apk
的目錄在哪里呢? 編譯完成之后,在控制臺會輸出APK文件的路徑。
知道目錄在哪里之后,在Windows的文件資源管理器里,找到路徑,具體看下圖,找到生成的apk文件。
D:/linux-share-dir/QT/build-app_Huawei_Eco_tracking-Android_for_arm64_v8a_Clang_Qt_5_12_6_for_Android_ARM64_v8a-Release/android-build//build/outputs/apk/debug/android-build-debug.apk
四、STM32代碼開發(fā)
4.1 MQTT協(xié)議設(shè)計
#include "MQTTClient.h"
#include < string.h >
#include < stdlib.h >
#if !defined(_WINDOWS)
#include < sys/time.h >
#include < sys/socket.h >
#include < unistd.h >
#include < errno.h >
#else
#include < winsock2.h >
#include < ws2tcpip.h >
#define MAXHOSTNAMELEN 256
#define EAGAIN WSAEWOULDBLOCK
#define EINTR WSAEINTR
#define EINPROGRESS WSAEINPROGRESS
#define EWOULDBLOCK WSAEWOULDBLOCK
#define ENOTCONN WSAENOTCONN
#define ECONNRESET WSAECONNRESET
#endif
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
char* topics[] = {"TopicA", "TopicA/B", "Topic/C", "TopicA/C", "/TopicA"};
char* wildtopics[] = {"TopicA/+", "+/C", "#", "/#", "/+", "+/+", "TopicA/#"};
char* nosubscribe_topics[] = {"nosubscribe",};
struct Options
{
char* connection; /**< connection to system under test. */
char* clientid1;
char* clientid2;
char* username;
char* password;
int verbose;
int MQTTVersion;
int iterations;
int run_dollar_topics_test;
int run_subscribe_failure_test;
} options =
{
"tcp://localhost:1883",
"myclientid",
"myclientid2",
NULL,
NULL,
0,
MQTTVERSION_3_1_1,
1,
0,
0,
};
void usage(void)
{
printf("options:n connection, clientid1, clientid2, username, password, MQTTversion, iterations, verbosen");
exit(EXIT_FAILURE);
}
void getopts(int argc, char** argv)
{
int count = 1;
while (count < argc)
{
if (strcmp(argv[count], "--dollar_topics_test") == 0 || strcmp(argv[count], "--$") == 0)
{
options.run_dollar_topics_test = 1;
printf("Running $ topics testn");
}
else if (strcmp(argv[count], "--subscribe_failure_test") == 0 || strcmp(argv[count], "-s") == 0)
{
options.run_subscribe_failure_test = 1;
printf("Running subscribe failure testn");
}
else if (strcmp(argv[count], "--connection") == 0)
{
if (++count < argc)
{
options.connection = argv[count];
printf("Setting connection to %sn", options.connection);
}
else
usage();
}
else if (strcmp(argv[count], "--clientid1") == 0)
{
if (++count < argc)
{
options.clientid1 = argv[count];
printf("Setting clientid1 to %sn", options.clientid1);
}
else
usage();
}
else if (strcmp(argv[count], "--clientid2") == 0)
{
if (++count < argc)
{
options.clientid2 = argv[count];
printf("Setting clientid2 to %sn", options.clientid2);
}
else
usage();
}
else if (strcmp(argv[count], "--username") == 0)
{
if (++count < argc)
{
options.username = argv[count];
printf("Setting username to %sn", options.username);
}
else
usage();
}
else if (strcmp(argv[count], "--password") == 0)
{
if (++count < argc)
{
options.password = argv[count];
printf("Setting password to %sn", options.password);
}
else
usage();
}
else if (strcmp(argv[count], "--MQTTversion") == 0)
{
if (++count < argc)
{
options.MQTTVersion = atoi(argv[count]);
printf("Setting MQTT version to %dn", options.MQTTVersion);
}
else
usage();
}
else if (strcmp(argv[count], "--iterations") == 0)
{
if (++count < argc)
{
options.iterations = atoi(argv[count]);
printf("Setting iterations to %dn", options.iterations);
}
else
usage();
}
else if (strcmp(argv[count], "--verbose") == 0)
{
options.verbose = 1;
printf("nSetting verbose onn");
}
count++;
}
}
#if defined(_WIN32) || defined(_WINDOWS)
#define msleep Sleep
#define START_TIME_TYPE DWORD
static DWORD start_time = 0;
START_TIME_TYPE start_clock(void)
{
return GetTickCount();
}
#elif defined(AIX)
#define mqsleep sleep
#define START_TIME_TYPE struct timespec
START_TIME_TYPE start_clock(void)
{
static struct timespec start;
clock_gettime(CLOCK_REALTIME, &start);
return start;
}
#else
#define msleep(A) usleep(A*1000)
#define START_TIME_TYPE struct timeval
/* TODO - unused - remove? static struct timeval start_time; */
START_TIME_TYPE start_clock(void)
{
struct timeval start_time;
gettimeofday(&start_time, NULL);
return start_time;
}
#endif
#define LOGA_DEBUG 0
#define LOGA_INFO 1
#include < stdarg.h >
#include < time.h >
#include < sys/timeb.h >
void MyLog(int LOGA_level, char* format, ...)
{
static char msg_buf[256];
va_list args;
#if defined(_WIN32) || defined(_WINDOWS)
struct timeb ts;
#else
struct timeval ts;
#endif
struct tm timeinfo;
if (LOGA_level == LOGA_DEBUG && options.verbose == 0)
return;
#if defined(_WIN32) || defined(_WINDOWS)
ftime(&ts);
localtime_s(&timeinfo, &ts.time);
#else
gettimeofday(&ts, NULL);
localtime_r(&ts.tv_sec, &timeinfo);
#endif
strftime(msg_buf, 80, "%Y%m%d %H%M%S", &timeinfo);
#if defined(_WIN32) || defined(_WINDOWS)
sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm);
#else
sprintf(&msg_buf[strlen(msg_buf)], ".%.3lu ", ts.tv_usec / 1000L);
#endif
va_start(args, format);
vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args);
va_end(args);
printf("%sn", msg_buf);
fflush(stdout);
}
int tests = 0;
int failures = 0;
void myassert(char* filename, int lineno, char* description, int value, char* format, ...)
{
++tests;
if (!value)
{
int count;
va_list args;
++failures;
printf("Assertion failed, file %s, line %d, description: %sn", filename, lineno, description);
va_start(args, format);
count = vprintf(format, args);
va_end(args);
if (count)
printf("n");
//cur_output += sprintf(cur_output, "< failure type="%s" >file %s, line %d < /failure >n",
// description, filename, lineno);
}
else
MyLog(LOGA_DEBUG, "Assertion succeeded, file %s, line %d, description: %s", filename, lineno, description);
}
#define assert(a, b, c, d) myassert(__FILE__, __LINE__, a, b, c, d)
#define assert1(a, b, c, d, e) myassert(__FILE__, __LINE__, a, b, c, d, e)
typedef struct
{
char* topicName;
int topicLen;
MQTTClient_message* m;
} messageStruct;
messageStruct messagesArrived[1000];
int messageCount = 0;
int messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* m)
{
messagesArrived[messageCount].topicName = topicName;
messagesArrived[messageCount].topicLen = topicLen;
messagesArrived[messageCount++].m = m;
MyLog(LOGA_DEBUG, "Callback: %d message received on topic %s is %.*s.",
messageCount, topicName, m- >payloadlen, (char*)(m- >payload));
return 1;
}
void clearMessages(void)
{
int i;
for (i = 0; i < messageCount; ++i)
{
MQTTClient_free(messagesArrived[i].topicName);
MQTTClient_freeMessage(&messagesArrived[i].m);
}
messageCount = 0;
}
void cleanup(void)
{
// clean all client state
char* clientids[] = {options.clientid1, options.clientid2};
int i, rc;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
MyLog(LOGA_INFO, "Cleaning up");
opts.keepAliveInterval = 20;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
for (i = 0; i < 2; ++i)
{
rc = MQTTClient_create(&aclient, options.connection, clientids[i], MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&aclient);
}
// clean retained messages
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(aclient, "#", 0);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(2000); // wait for all retained messages to arrive
rc = MQTTClient_unsubscribe(aclient, "#");
assert("Good rc from unsubscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
for (i = 0; i < messageCount; ++i)
{
if (messagesArrived[i].m- >retained)
{
MyLog(LOGA_INFO, "Deleting retained message for topic %s", (char*)messagesArrived[i].topicName);
rc = MQTTClient_publish(aclient, messagesArrived[i].topicName, 0, "", 0, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
}
}
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&aclient);
clearMessages();
MyLog(LOGA_INFO, "Finished cleaning up");
}
int basic_test(void)
{
int i, rc;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
MyLog(LOGA_INFO, "Starting basic test");
tests = failures = 0;
opts.keepAliveInterval = 20;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(aclient, topics[0], 0);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[0], 5, "qos 0", 0, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[0], 5, "qos 1", 1, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[0], 5, "qos 2", 2, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(1000);
rc = MQTTClient_disconnect(aclient, 10000);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
assert("3 Messages received", messageCount == 3, "messageCount was %d", messageCount);
clearMessages();
/*opts.MQTTVersion = MQTTVERSION_3_1;
rc = MQTTClient_connect(aclient, &opts); // should fail - wrong protocol version
assert("Bad rc from connect", rc == MQTTCLIENT_FAILURE, "rc was %d", rc);*/
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Basic test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int offline_message_queueing_test(void)
{
int i, rc;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
MQTTClient bclient;
MyLog(LOGA_INFO, "Offline message queueing test");
tests = failures = 0;
opts.keepAliveInterval = 20;
opts.cleansession = 0;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
opts.cleansession = 1;
rc = MQTTClient_connect(bclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(bclient, topics[1], 5, "qos 0", 0, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(bclient, topics[2], 5, "qos 1", 1, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(bclient, topics[3], 5, "qos 2", 2, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(2000);
rc = MQTTClient_disconnect(bclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&bclient);
opts.cleansession = 0;
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(1000); // receive the queued messages
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&aclient);
assert("2 or 3 messages received", messageCount == 3 || messageCount == 2, "messageCount was %d", messageCount);
MyLog(LOGA_INFO, "This server %s queueing QoS 0 messages for offline clients", (messageCount == 3) ? "is" : "is not");
clearMessages();
MyLog(LOGA_INFO, "Offline message queueing test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int retained_message_test(void)
{
int i, rc;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
MyLog(LOGA_INFO, "Retained message test");
tests = failures = 0;
opts.keepAliveInterval = 20;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
assert("0 messages received", messageCount == 0, "messageCount was %d", messageCount);
// set retained messages
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[1], 5, "qos 0", 0, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[2], 5, "qos 1", 1, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[3], 5, "qos 2", 2, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(1000);
rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(2000);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
assert("3 messages received", messageCount == 3, "messageCount was %d", messageCount);
for (i = 0; i < messageCount; ++i)
{
assert("messages should be retained", messagesArrived[i].m- >retained, "retained was %d",
messagesArrived[i].m- >retained);
MQTTClient_free(messagesArrived[i].topicName);
MQTTClient_freeMessage(&messagesArrived[i].m);
}
messageCount = 0;
// clear retained messages
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[1], 0, "", 0, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[2], 0, "", 1, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[3], 0, "", 2, 1, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(200); // wait for QoS 2 exchange to be completed
rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(200);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
assert("0 messages received", messageCount == 0, "messageCount was %d", messageCount);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Retained message test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
#define SOCKET_ERROR -1
int test6_socket_error(char* aString, int sock)
{
#if defined(_WIN32)
int errno;
#endif
#if defined(_WIN32)
errno = WSAGetLastError();
#endif
if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS && errno != EWOULDBLOCK)
{
if (strcmp(aString, "shutdown") != 0 || (errno != ENOTCONN && errno != ECONNRESET))
printf("Socket error %d in %s for socket %d", errno, aString, sock);
}
return errno;
}
int test6_socket_close(int socket)
{
int rc;
#if defined(_WIN32)
if (shutdown(socket, SD_BOTH) == SOCKET_ERROR)
test6_socket_error("shutdown", socket);
if ((rc = closesocket(socket)) == SOCKET_ERROR)
test6_socket_error("close", socket);
#else
if (shutdown(socket, SHUT_RDWR) == SOCKET_ERROR)
test6_socket_error("shutdown", socket);
if ((rc = close(socket)) == SOCKET_ERROR)
test6_socket_error("close", socket);
#endif
return rc;
}
typedef struct
{
int socket;
time_t lastContact;
#if defined(OPENSSL)
SSL* ssl;
SSL_CTX* ctx;
#endif
} networkHandles;
typedef struct
{
char* clientID; /**< the string id of the client */
char* username; /**< MQTT v3.1 user name */
char* password; /**< MQTT v3.1 password */
unsigned int cleansession : 1; /**< MQTT clean session flag */
unsigned int connected : 1; /**< whether it is currently connected */
unsigned int good : 1; /**< if we have an error on the socket we turn this off */
unsigned int ping_outstanding : 1;
int connect_state : 4;
networkHandles net;
/* ... */
} Clients;
typedef struct
{
char* serverURI;
Clients* c;
MQTTClient_connectionLost* cl;
MQTTClient_messageArrived* ma;
MQTTClient_deliveryComplete* dc;
void* context;
int connect_sem;
int rc; /* getsockopt return code in connect */
int connack_sem;
int suback_sem;
int unsuback_sem;
void* pack;
} MQTTClients;
int will_message_test(void)
{
int i, rc, count = 0;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer;
MQTTClient aclient, bclient;
MyLog(LOGA_INFO, "Will message test");
tests = failures = 0;
opts.keepAliveInterval = 2;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
opts.will = &wopts;
opts.will- >message = "client not disconnected";
opts.will- >qos = 1;
opts.will- >retained = 0;
opts.will- >topicName = topics[2];
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_setCallbacks(bclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
opts.keepAliveInterval = 20;
opts.will = NULL;
rc = MQTTClient_connect(bclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(bclient, topics[2], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(100);
test6_socket_close(((MQTTClients*)aclient)- >c- >net.socket);
while (messageCount == 0 && ++count < 10)
msleep(1000);
rc = MQTTClient_disconnect(bclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&bclient);
assert("will message received", messageCount == 1, "messageCount was %d", messageCount);
rc = MQTTClient_disconnect(aclient, 100);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Will message test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int overlapping_subscriptions_test(void)
{
/* overlapping subscriptions. When there is more than one matching subscription for the same client for a topic,
the server may send back one message with the highest QoS of any matching subscription, or one message for
each subscription with a matching QoS. */
int i, rc;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
char* topicList[] = {wildtopics[6], wildtopics[0]};
int qosList[] = {2, 1};
MyLog(LOGA_INFO, "Starting overlapping subscriptions test");
clearMessages();
tests = failures = 0;
opts.keepAliveInterval = 20;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribeMany(aclient, 2, topicList, qosList);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[3], strlen("overlapping topic filters") + 1,
"overlapping topic filters", 2, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(1000);
assert("1 or 2 messages received", messageCount == 1 || messageCount == 2, "messageCount was %d", messageCount);
if (messageCount == 1)
{
MyLog(LOGA_INFO, "This server is publishing one message for all matching overlapping subscriptions, not one for each.");
assert("QoS should be 2", messagesArrived[0].m- >qos == 2, "QoS was %d", messagesArrived[0].m- >qos);
}
else
{
MyLog(LOGA_INFO, "This server is publishing one message per each matching overlapping subscription.");
assert1("QoSs should be 1 and 2",
(messagesArrived[0].m- >qos == 2 && messagesArrived[1].m- >qos == 1) ||
(messagesArrived[0].m- >qos == 1 && messagesArrived[1].m- >qos == 2),
"QoSs were %d %d", messagesArrived[0].m- >qos, messagesArrived[1].m- >qos);
}
rc = MQTTClient_disconnect(aclient, 100);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Overlapping subscription test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int keepalive_test(void)
{
/* keepalive processing. We should be kicked off by the server if we don't send or receive any data, and don't send
any pings either. */
int i, rc, count = 0;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer;
MQTTClient aclient, bclient;
MyLog(LOGA_INFO, "Starting keepalive test");
tests = failures = 0;
clearMessages();
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
opts.will = &wopts;
opts.will- >message = "keepalive expiry";
opts.will- >qos = 1;
opts.will- >retained = 0;
opts.will- >topicName = topics[4];
opts.keepAliveInterval = 20;
rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_setCallbacks(bclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(bclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(bclient, topics[4], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
opts.keepAliveInterval = 2;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
while (messageCount == 0 && ++count < 20)
msleep(1000);
rc = MQTTClient_disconnect(bclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
assert("Should have will message", messageCount == 1, "messageCount was %d", messageCount);
rc = MQTTClient_disconnect(aclient, 100);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Keepalive test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int redelivery_on_reconnect_test(void)
{
/* redelivery on reconnect. When a QoS 1 or 2 exchange has not been completed, the server should retry the
appropriate MQTT packets */
int i, rc, count = 0;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
MyLog(LOGA_INFO, "Starting redelivery on reconnect test");
tests = failures = 0;
clearMessages();
opts.keepAliveInterval = 0;
opts.cleansession = 0;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(aclient, wildtopics[6], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_yield();
// no background processing because no callback has been set
rc = MQTTClient_publish(aclient, topics[1], 6, "qos 1", 2, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, topics[3], 6, "qos 2", 2, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_disconnect(aclient, 0);
assert("No messages should have been received yet", messageCount == 0, "messageCount was %d", messageCount);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
while (messageCount < 2 && ++count < 5)
msleep(1000);
assert("Should have 2 messages", messageCount == 2, "messageCount was %d", messageCount);
rc = MQTTClient_disconnect(aclient, 100);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Redelivery on reconnect test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int zero_length_clientid_test(void)
{
int i, rc, count = 0;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
MyLog(LOGA_INFO, "Starting zero length clientid test");
tests = failures = 0;
clearMessages();
opts.keepAliveInterval = 0;
opts.cleansession = 0;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, "", MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("rc 2 from connect", rc == 2, "rc was %d", rc); // this should always fail
opts.cleansession = 1;
rc = MQTTClient_connect(aclient, &opts);
assert("Connack rc should be 0 or 2", rc == MQTTCLIENT_SUCCESS || rc == 2, "rc was %d", rc);
MyLog(LOGA_INFO, "This server %s support zero length clientids", (rc == 2) ? "does not" : "does");
if (rc == MQTTCLIENT_SUCCESS)
rc = MQTTClient_disconnect(aclient, 100);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Zero length clientid test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int dollar_topics_test(void)
{
/* $ topics. The specification says that a topic filter which starts with a wildcard does not match topic names that
begin with a $. Publishing to a topic which starts with a $ may not be allowed on some servers (which is entirely valid),
so this test will not work and should be omitted in that case.
*/
int i, rc, count = 0;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
char dollartopic[20];
MyLog(LOGA_INFO, "Starting $ topics test");
sprintf(dollartopic, "$%s", topics[1]);
clearMessages();
opts.keepAliveInterval = 5;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(1000); // wait for any retained messages, hopefully
clearMessages();
rc = MQTTClient_publish(aclient, topics[1], 20, "not sent to dollar topic", 1, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_publish(aclient, dollartopic, 20, "sent to dollar topic", 1, 0, NULL);
assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
msleep(1000);
assert("Should have 1 message", messageCount == 1, "messageCount was %d", messageCount);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "$ topics test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int subscribe_failure_test(void)
{
/* Subscribe failure. A new feature of MQTT 3.1.1 is the ability to send back negative reponses to subscribe
requests. One way of doing this is to subscribe to a topic which is not allowed to be subscribed to.
*/
int i, rc, count = 0;
MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
MQTTClient aclient;
int subqos = 2;
MyLog(LOGA_INFO, "Starting subscribe failure test");
clearMessages();
opts.keepAliveInterval = 5;
opts.cleansession = 1;
opts.username = options.username;
opts.password = options.password;
opts.MQTTVersion = options.MQTTVersion;
rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
assert("good rc from create", rc == MQTTCLIENT_SUCCESS, "rc was %dn", rc);
rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_connect(aclient, &opts);
assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
rc = MQTTClient_subscribeMany(aclient, 1, &nosubscribe_topics[0], &subqos);
assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
assert("0x80 rc from subscribe", subqos == 0x80, "subqos was %d", subqos);
rc = MQTTClient_disconnect(aclient, 100);
assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
MQTTClient_destroy(&aclient);
MyLog(LOGA_INFO, "Subscribe failure test %s", (failures == 0) ? "succeeded" : "failed");
return failures;
}
int main(int argc, char** argv)
{
int i;
int all_failures = 0;
getopts(argc, argv);
for (i = 0; i < options.iterations; ++i)
{
cleanup();
all_failures += basic_test() +
offline_message_queueing_test() +
retained_message_test() +
will_message_test() +
overlapping_subscriptions_test() +
keepalive_test() +
redelivery_on_reconnect_test() +
zero_length_clientid_test();
if (options.run_dollar_topics_test)
all_failures += dollar_topics_test();
if (options.run_subscribe_failure_test)
all_failures += subscribe_failure_test();
}
MyLog(LOGA_INFO, "Test suite %s", (all_failures == 0) ? "succeeded" : "failed");
}
4.2 DHT11溫濕度
#include "dht11.h"
#include "delay.h"
//復(fù)位DHT11
void DHT11_Rst(void)
{
DHT11_IO_OUT(); //SET OUTPUT
DHT11_DQ_OUT=0; //拉低DQ
delay_ms(20); //拉低至少18ms
DHT11_DQ_OUT=1; //DQ=1
delay_us(30); //主機拉高20~40us
}
//等待DHT11的回應(yīng)
//返回1:未檢測到DHT11的存在
//返回0:存在
u8 DHT11_Check(void)
{
u8 retry=0;
DHT11_IO_IN();//SET INPUT
while (DHT11_DQ_IN&&retry< 100)//DHT11會拉低40~80us
{
retry++;
delay_us(1);
};
if(retry >=100)return 1;
else retry=0;
while (!DHT11_DQ_IN&&retry< 100)//DHT11拉低后會再次拉高40~80us
{
retry++;
delay_us(1);
};
if(retry >=100)return 1;
return 0;
}
//從DHT11讀取一個位
//返回值:1/0
u8 DHT11_Read_Bit(void)
{
u8 retry=0;
while(DHT11_DQ_IN&&retry< 100)//等待變?yōu)榈碗娖?/span>
{
retry++;
delay_us(1);
}
retry=0;
while(!DHT11_DQ_IN&&retry< 100)//等待變高電平
{
retry++;
delay_us(1);
}
delay_us(40);//等待40us
if(DHT11_DQ_IN)return 1;
else return 0;
}
//從DHT11讀取一個字節(jié)
//返回值:讀到的數(shù)據(jù)
u8 DHT11_Read_Byte(void)
{
u8 i,dat;
dat=0;
for (i=0;i< 8;i++)
{
dat< <=1;
dat|=DHT11_Read_Bit();
}
return dat;
}
//從DHT11讀取一次數(shù)據(jù)
//temp:溫度值(范圍:0~50°)
//humi:濕度值(范圍:20%~90%)
//返回值:0,正常;1,讀取失敗
u8 DHT11_Read_Data(u8 *temp,u8 *humi)
{
u8 buf[5];
u8 i;
DHT11_Rst();
if(DHT11_Check()==0)
{
for(i=0;i< 5;i++)//讀取40位數(shù)據(jù)
{
buf[i]=DHT11_Read_Byte();
}
if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
{
*humi=buf[0];
*temp=buf[2];
}
}else return 1;
return 0;
}
//初始化DHT11的IO口 DQ 同時檢測DHT11的存在
//返回1:不存在
//返回0:存在
u8 DHT11_Init(void)
{
RCC- >APB2ENR|=1< 8; //使能PORTG口時鐘
GPIOG- >CRH&=0XFFFF0FFF;//PORTG.11 推挽輸出
GPIOG- >CRH|=0X00003000;
GPIOG- >ODR|=1< 11; //輸出1
DHT11_Rst();
return DHT11_Check();
}
4.3 光敏傳感器
#include "adc.h"
#include "delay.h"
//初始化ADC1
//這里我們僅以規(guī)則通道為例
//我們默認僅開啟通道1
void Adc_Init(void)
{
//先初始化IO口
RCC- >APB2ENR|=1< 2; //使能PORTA口時鐘
GPIOA- >CRL&=0XFFFFFF0F;//PA1 anolog輸入
RCC- >APB2ENR|=1< 9; //ADC1時鐘使能
RCC- >APB2RSTR|=1< 9; //ADC1復(fù)位
RCC- >APB2RSTR&=~(1< 9);//復(fù)位結(jié)束
RCC- >CFGR&=~(3< 14); //分頻因子清零
//SYSCLK/DIV2=12M ADC時鐘設(shè)置為12M,ADC最大時鐘不能超過14M!
//否則將導(dǎo)致ADC準確度下降!
RCC- >CFGR|=2< 14;
ADC1- >CR1&=0XF0FFFF; //工作模式清零
ADC1- >CR1|=0< 16; //獨立工作模式
ADC1- >CR1&=~(1< 8); //非掃描模式
ADC1- >CR2&=~(1< 1); //單次轉(zhuǎn)換模式
ADC1- >CR2&=~(7< 17);
ADC1- >CR2|=7< 17; //軟件控制轉(zhuǎn)換
ADC1- >CR2|=1< 20; //使用用外部觸發(fā)(SWSTART)!!! 必須使用一個事件來觸發(fā)
ADC1- >CR2&=~(1< 11); //右對齊
ADC1- >CR2|=1< 23; //使能溫度傳感器
ADC1- >SQR1&=~(0XF< 20);
ADC1- >SQR1|=0< 20; //1個轉(zhuǎn)換在規(guī)則序列中 也就是只轉(zhuǎn)換規(guī)則序列1
//設(shè)置通道1的采樣時間
ADC1- >SMPR2&=~(3*1); //通道1采樣時間清空
ADC1- >SMPR2|=7< (3*1); //通道1 239.5周期,提高采樣時間可以提高精確度
ADC1- >SMPR1&=~(7< 3*6);//清除通道16原來的設(shè)置
ADC1- >SMPR1|=7< (3*6); //通道16 239.5周期,提高采樣時間可以提高精確度
ADC1- >CR2|=1< 0; //開啟AD轉(zhuǎn)換器
ADC1- >CR2|=1< 3; //使能復(fù)位校準
while(ADC1- >CR2&1< 3); //等待校準結(jié)束
//該位由軟件設(shè)置并由硬件清除。在校準寄存器被初始化后該位將被清除。
ADC1- >CR2|=1< 2; //開啟AD校準
while(ADC1- >CR2&1< 2); //等待校準結(jié)束
//該位由軟件設(shè)置以開始校準,并在校準結(jié)束時由硬件清除
}
//獲得ADC1某個通道的值
//ch:通道值 0~16
//返回值:轉(zhuǎn)換結(jié)果
u16 Get_Adc(u8 ch)
{
//設(shè)置轉(zhuǎn)換序列
ADC1- >SQR3&=0XFFFFFFE0;//規(guī)則序列1 通道ch
ADC1- >SQR3|=ch;
ADC1- >CR2|=1< 22; //啟動規(guī)則轉(zhuǎn)換通道
while(!(ADC1- >SR&1< 1));//等待轉(zhuǎn)換結(jié)束
return ADC1- >DR; //返回adc值
}
//獲取通道ch的轉(zhuǎn)換值,取times次,然后平均
//ch:通道編號
//times:獲取次數(shù)
//返回值:通道ch的times次轉(zhuǎn)換結(jié)果平均值
u16 Get_Adc_Average(u8 ch,u8 times)
{
u32 temp_val=0;
u8 t;
for(t=0;t< times;t++)
{
temp_val+=Get_Adc(ch);
delay_ms(5);
}
return temp_val/times;
}
//得到溫度值
//返回值:溫度值(擴大了100倍,單位:℃.)
short Get_Temprate(void)
{
u32 adcx;
short result;
double temperate;
adcx=Get_Adc_Average(ADC_CH_TEMP,20); //讀取通道16,20次取平均
temperate=(float)adcx*(3.3/4096); //電壓值
temperate=(1.43-temperate)/0.0043+25; //轉(zhuǎn)換為溫度值
result=temperate*=100; //擴大100倍.
return result;
}
//初始化ADC3
//這里我們僅以規(guī)則通道為例
//我們默認僅開啟通道6
void Adc3_Init(void)
{
RCC- >APB2ENR|=1< 15; //ADC3時鐘使能
RCC- >APB2RSTR|=1< 15; //ADC復(fù)位
RCC- >APB2RSTR&=~(1< 15);//復(fù)位結(jié)束
RCC- >CFGR&=~(3< 14); //分頻因子清零
//SYSCLK/DIV2=12M ADC時鐘設(shè)置為12M,ADC最大時鐘不能超過14M!
//否則將導(dǎo)致ADC準確度下降!
RCC- >CFGR|=2< 14;
ADC3- >CR1&=0XF0FFFF; //工作模式清零
ADC3- >CR1|=0< 16; //獨立工作模式
ADC3- >CR1&=~(1< 8); //非掃描模式
ADC3- >CR2&=~(1< 1); //單次轉(zhuǎn)換模式
ADC3- >CR2&=~(7< 17);
ADC3- >CR2|=7< 17; //軟件控制轉(zhuǎn)換
ADC3- >CR2|=1< 20; //使用用外部觸發(fā)(SWSTART)!!! 必須使用一個事件來觸發(fā)
ADC3- >CR2&=~(1< 11); //右對齊
ADC3- >SQR1&=~(0XF< 20);
ADC3- >SQR1|=0< 20; //1個轉(zhuǎn)換在規(guī)則序列中 也就是只轉(zhuǎn)換規(guī)則序列1
//設(shè)置通道1的采樣時間
ADC3- >SMPR2&=~(7< (3*6));//通道6采樣時間清空
ADC3- >SMPR2|=7< (3*6); //通道6 239.5個周期,提高采樣時間可以提高精確度
ADC3- >CR2|=1< 0; //開啟AD轉(zhuǎn)換器
ADC3- >CR2|=1< 3; //使能復(fù)位校準
while(ADC1- >CR2&1< 3); //等待校準結(jié)束
//該位由軟件設(shè)置并由硬件清除。在校準寄存器被初始化后該位將被清除。
ADC3- >CR2|=1< 2; //開啟AD校準
while(ADC3- >CR2&1< 2); //等待校準結(jié)束
//該位由軟件設(shè)置以開始校準,并在校準結(jié)束時由硬件清除
}
//獲得ADC3某個通道的值
//ch:通道值 0~16
//返回值:轉(zhuǎn)換結(jié)果
u16 Get_Adc3(u8 ch)
{
//設(shè)置轉(zhuǎn)換序列
ADC3- >SQR3&=0XFFFFFFE0;//規(guī)則序列1 通道ch
ADC3- >SQR3|=ch;
ADC3- >CR2|=1< 22; //啟動規(guī)則轉(zhuǎn)換通道
while(!(ADC3- >SR&1< 1));//等待轉(zhuǎn)換結(jié)束
return ADC3- >DR; //返回adc值
}
五、總結(jié)
本項目致力于開發(fā)一款基于STM32微控制器的智能養(yǎng)殖場環(huán)境監(jiān)測系統(tǒng)。該系統(tǒng)集成了DHT11溫濕度傳感器、BH1750光敏傳感器等,用于實時采集養(yǎng)殖環(huán)境中的溫度、濕度及光照強度數(shù)據(jù),并通過OLED顯示屏提供本地數(shù)據(jù)顯示功能。為了實現(xiàn)遠程監(jiān)控與管理,系統(tǒng)還配備了ESP8266 WiFi模塊,利用MQTT協(xié)議與華為云物聯(lián)網(wǎng)平臺連接,使得用戶可以通過定制的Windows上位機軟件或Android應(yīng)用程序隨時隨地訪問環(huán)境數(shù)據(jù),設(shè)定報警閾值,并遠程控制通風(fēng)風(fēng)扇、加濕器和燈光等設(shè)備,以確保養(yǎng)殖環(huán)境維持在最優(yōu)狀態(tài)。系統(tǒng)采用USB線5V供電,確保了運行的便捷性和能耗的有效控制。
總體而言,這一環(huán)境監(jiān)測系統(tǒng)通過智能化手段提升養(yǎng)殖場的管理水平,降低運營成本,同時提高動物的生長質(zhì)量和健康水平。
審核編輯 黃宇
-
微控制器
+關(guān)注
關(guān)注
48文章
7574瀏覽量
151707 -
STM32
+關(guān)注
關(guān)注
2270文章
10914瀏覽量
356711 -
監(jiān)測系統(tǒng)
+關(guān)注
關(guān)注
8文章
2739瀏覽量
81396 -
IOT
+關(guān)注
關(guān)注
187文章
4218瀏覽量
197153 -
華為云
+關(guān)注
關(guān)注
3文章
2653瀏覽量
17496
發(fā)布評論請先 登錄
相關(guān)推薦
評論