一、前言
項目設計里用到的全部工具軟件和源碼工程,都可以網盤里下載。
https://pan.quark.cn/s/145a9b3f7f53
1.1 項目介紹
【1】項目開發背景
隨著現代農業技術的發展,智能化管理在農業生產中扮演著越來越重要的角色。特別是在養殖業中,環境條件的好壞直接關系到養殖動物的生長狀態和健康水平。因此,建立一個能夠實時監控并調節養殖環境的系統顯得尤為重要。這樣的系統不僅可以提高養殖效率,減少人工成本,還可以通過精準控制環境參數來提升動物福利,進而保證產品質量。
基于此背景,本項目設計并實現一套基于STM32微控制器的養殖場環境監測系統。該系統將集成多種傳感器用于采集環境數據,如溫度、濕度和光照強度等關鍵參數。通過本地OLED顯示屏,工作人員可以實時查看這些數據,確保養殖環境處于最佳狀態。同時,考慮到現代養殖業對于遠程管理和自動化的需求,系統還將配備ESP8266 WiFi模塊,通過MQTT協議與華為云物聯網平臺進行通訊。這樣一來,借助于在Windows平臺上設計的上位機軟件以及Android移動應用,管理者可以從任何地點遠程監控環境狀況,調整預設閾值,并且根據實際情況啟動相應的設備,比如控制通風風扇、加濕器或照明燈具。
此外,系統的硬件選型也經過了深思熟慮。為了確保系統的穩定性和可靠性,采用了成熟可靠的傳感器和技術方案,如DHT11溫濕度傳感器、BH1750光敏傳感器以及通過繼電器控制的5V風扇和加濕器模塊。供電方面選擇了常見的USB線-5V供電方式,既方便又節能。整個系統的開發不僅能夠滿足現代化養殖業的實際需求,同時也為未來進一步的技術升級提供了堅實的基礎。
【2】設計實現的功能
(1)實時環境監測
系統配備了DHT11溫濕度傳感器和BH1750光敏傳感器,能夠持續監測養殖區域內的溫度、濕度以及光照強度等關鍵環境參數。這些數據的實時收集有助于及時發現環境變化,從而迅速采取措施調整環境條件。
(2)本地數據顯示
通過OLED顯示屏,系統能夠在本地即時顯示所監測到的各項環境參數。這使得工作人員無需依賴外部設備即可了解當前的環境狀況,便于快速響應環境變化。
(3)遠程監控與管理
系統內置了ESP8266 WiFi模塊,允許它通過MQTT協議與華為云物聯網平臺建立連接。借助于該平臺,用戶可以利用Windows上位機軟件或Android移動應用程序遠程訪問環境監測數據,實現了跨越地理限制的環境監控。此外,用戶還可以通過上位機軟件或移動應用設置環境參數的閾值,并接收超出閾值范圍的告警通知。
(4)自動化控制
當環境監測數據超出預定的安全范圍時,系統能夠自動啟動相應的設備進行環境調節。例如,當濕度超過預設值時,系統會自動開啟通風風扇來降低濕度;當濕度低于設定值,則啟動加濕器增加空氣濕度;若光照不足,則自動開啟燈光補充光源。這樣,即使在無人值守的情況下,也能確保環境條件保持在適宜范圍內。
(5)設備控制接口
系統設計了繼電器控制接口來操作5V風扇和加濕器模塊,確保了設備的安全啟停。通過這種方式,不僅簡化了硬件設計,而且提高了系統的可靠性和使用壽命。
【3】項目硬件模塊組成
【1】STM32微控制器 :作為整個系統的主控單元,負責協調各個傳感器的數據采集、處理邏輯運算以及控制指令的發送。
【2】DHT11溫濕度傳感器 :用于實時監測養殖環境中的溫度和濕度,并將數據傳送給STM32進行處理。
【3】BH1750光敏傳感器 :用于檢測養殖環境內的光照強度,為系統提供光照數據,以便做出相應的照明控制決策。
【4】OLED顯示屏 :用于在本地顯示傳感器采集到的環境數據,包括溫度、濕度和光照強度等信息。
【5】ESP8266 WiFi模塊 :使系統能夠接入無線網絡,并通過MQTT協議與華為云物聯網平臺通信,實現遠程數據傳輸。
【6】繼電器模塊 :用于控制5V風扇和加濕器的開關,確保設備根據環境參數的變化自動啟停。
【7】5V小風扇 :當濕度超標時,通過繼電器模塊啟動,以幫助降低環境濕度。
【8】5V加濕器模塊 :當濕度低于設定值時,由繼電器模塊控制開啟,以增加環境濕度。
【9】LED照明系統 :當光照強度低于閾值時,自動開啟以補充光照,確保養殖環境的光照條件符合要求。
【10】USB供電模塊 :系統采用USB線5V供電方式,為整個系統提供穩定的電力供應,確保所有組件正常工作。
【4】需求總結
基于STM32設計的養殖場環境監測系統
支持的功能如下:
(1)支持對養殖環境中的溫度、濕度、光照強度實時監測;
(2)本地通過0LED顯示屏實時顯示檢測數據。
(3)能夠通過WiFi模塊通過MQTT協議連接華為云物聯網平臺,通過Qt設計Windows上位機和Android手機APP,能遠程查看環境參數,設置閥值參數,控制通風風扇。
(4)當濕度超過閥值,通過風扇實現送風;當濕度低于值,利用加濕模塊實現加濕;當光照強度低于閾值,打開燈;
硬件選型:
(1)WiFi模塊采用ESP8266-WIFI模塊
(2)光敏模塊:采用BH1750光敏傳感器
(3)風扇模塊:采用5V小風扇,通過繼電器控制開關。
(4)加濕器模塊:采用5V加濕器模塊,通過繼電器控制開關。
(5)溫濕度傳感器:采用DHT11
(6)供電電源:采用USB線-5V供電。
1.2 設計思路
設計思路的核心在于構建一個高效且易于維護的智能環境監測系統,通過自動化的手段來優化養殖環境的管理。
第一步,選擇STM32作為主控芯片是因為其高性能與低功耗特性,能夠很好地支持多任務處理,同時保證了系統的穩定運行。此外,STM32豐富的外設接口也方便了與其他傳感器和執行機構的連接,為后續的功能擴展提供了便利。
針對環境參數的監測,項目選用了DHT11溫濕度傳感器和BH1750光敏傳感器。DHT11因其高性價比和簡單的接口協議被廣泛應用于各種環境監測場景,而BH1750則以其高精度測量能力和良好的穩定性成為了光照強度檢測的理想選擇。這兩類傳感器的數據采集結果將通過STM32的ADC模塊進行讀取,并經由主控芯片內部的邏輯判斷后,決定是否需要啟動環境調節裝置。
為了便于現場人員隨時了解環境狀況,設計中加入了OLED顯示屏,用于直觀展示傳感器采集的數據。OLED屏幕的優勢在于其清晰度高且視角寬廣,即便是在光線較暗的環境中也能清晰地顯示信息。
考慮到遠程監控的重要性,系統引入了ESP8266 WiFi模塊。通過WiFi模塊,系統可以連接至互聯網,并利用MQTT協議與華為云物聯網平臺進行數據交換。MQTT協議因其輕量級和高效性非常適合于物聯網應用場景,能夠有效支持大量的并發連接,確保數據的實時傳輸。借助云端服務,用戶可以通過上位機軟件或智能手機應用程序實時查看環境參數,并根據需要調整設置。
在執行機構的選擇上,系統采用了5V小風扇和加濕器模塊,并通過繼電器進行控制。這種設計不僅簡化了電路結構,還增強了系統的安全性和可靠性。當環境濕度或光照不符合預設標準時,系統會自動啟動相應的設備來進行調節,確保環境始終處于最佳狀態。
在供電方案上選擇了USB線5V供電,這不僅簡化了電源設計,降低了系統的復雜性,還使得系統更加節能環保。整體設計思路強調了系統集成度與自動化程度,力求通過最小的硬件配置實現最有效的環境管理。
1.3 系統功能總結
功能類別 | 具體功能描述 |
---|---|
環境監測 | - 實時監測養殖環境中的溫度、濕度及光照強度。- 數據通過DHT11溫濕度傳感器和BH1750光敏傳感器采集。 |
本地數據顯示 | - 利用OLED顯示屏實時顯示各項環境參數。 |
遠程監控 | - 通過ESP8266 WiFi模塊連接互聯網。- 使用MQTT協議與華為云物聯網平臺進行數據交互。 |
遠程管理 | - 在Windows上位機軟件或Android應用程序上查看環境參數。- 設置環境參數的閾值。- 接收超出閾值的告警信息。 |
自動化控制 | - 當濕度超過設定閾值時,自動啟動風扇降低濕度。- 當濕度低于設定閾值時,啟動加濕器增加濕度。- 當光照強度低于設定閾值時,自動開啟燈光補充光照。 |
設備控制接口 | - 通過繼電器模塊控制5V風扇和加濕器模塊的開關。 |
供電系統 | - 采用USB線5V供電,確保系統的穩定運行。 |
1.4 開發工具的選擇
【1】設備端開發
STM32的編程語言選擇C語言,C語言執行效率高,大學里主學的C語言,C語言編譯出來的可執行文件最接近于機器碼,匯編語言執行效率最高,但是匯編的移植性比較差,目前在一些操作系統內核里還有一些低配的單片機使用的較多,平常的單片機編程還是以C語言為主。C語言的執行效率僅次于匯編,語法理解簡單、代碼通用性強,也支持跨平臺,在嵌入式底層、單片機編程里用的非常多,當前的設計就是采用C語言開發。
開發工具選擇Keil,keil是一家世界領先的嵌入式微控制器軟件開發商,在2015年,keil被ARM公司收購。因為當前芯片選擇的是STM32F103系列,STMF103是屬于ARM公司的芯片構架、Cortex-M3內核系列的芯片,所以使用Kile來開發STM32是有先天優勢的,而keil在各大高校使用的也非常多,很多教科書里都是以keil來教學,開發51單片機、STM32單片機等等。目前作為MCU芯片開發的軟件也不只是keil一家獨大,IAR在MCU微處理器開發領域里也使用的非常多,IAR擴展性更強,也支持STM32開發,也支持其他芯片,比如:CC2530,51單片機的開發。從軟件的使用上來講,IAR比keil更加簡潔,功能相對少一些。如果之前使用過keil,而且使用頻率較多,已經習慣再使用IAR是有點不適應界面的。
【2】上位機開發
上位機的開發選擇Qt框架,編程語言采用C++;Qt是一個1991年由Qt Company開發的跨平臺C++圖形用戶界面應用程序開發框架。它既可以開發GUI程序,也可用于開發非GUI程序,比如控制臺工具和服務器。Qt是面向對象的框架,使用特殊的代碼生成擴展(稱為元對象編譯器(Meta Object Compiler, moc))以及一些宏,Qt很容易擴展,并且允許真正地組件編程。Qt能輕松創建具有原生C++性能的連接設備、用戶界面(UI)和應用程序。它功能強大且結構緊湊,擁有直觀的工具和庫。
二、部署華為云物聯網平臺
**華為云官網: **[https://www.huaweicloud.com/]
**打開官網,搜索物聯網,就能快速找到 **設備接入IoTDA
。
2.1 物聯網平臺介紹
華為云物聯網平臺(IoT 設備接入云服務)提供海量設備的接入和管理能力,將物理設備聯接到云,支撐設備數據采集上云和云端下發命令給設備進行遠程控制,配合華為云其他產品,幫助我們快速構筑物聯網解決方案。
使用物聯網平臺構建一個完整的物聯網解決方案主要包括3部分:物聯網平臺、業務應用和設備。
物聯網平臺作為連接業務應用和設備的中間層,屏蔽了各種復雜的設備接口,實現設備的快速接入;同時提供強大的開放能力,支撐行業用戶構建各種物聯網解決方案。
設備可以通過固網、2G/3G/4G/5G、NB-IoT、Wifi等多種網絡接入物聯網平臺,并使用LWM2M/CoAP、MQTT、HTTPS協議將業務數據上報到平臺,平臺也可以將控制命令下發給設備。
業務應用通過調用物聯網平臺提供的API,實現設備數據采集、命令下發、設備管理等業務場景。
2.2 開通物聯網服務
**地址: **[https://www.huaweicloud.com/product/iothub.html]
點擊立即創建
。
正在創建標準版實例,需要等待片刻。
創建完成之后,點擊實例名稱。 可以看到標準版實例的設備接入端口和地址。
在上面也能看到 免費單元的限制。
開通之后,點擊總覽
,也能查看接入信息。 我們當前設備準備采用MQTT協議接入華為云平臺,這里可以看到MQTT協議的地址和端口號等信息。
總結:
端口號: MQTT (1883)| MQTTS (8883)
接入地址:ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
**根據域名地址得到IP地址信息: **
打開Windows電腦的命令行控制臺終端,使用ping
命令。ping
一下即可。
Microsoft Windows [版本 10.0.19045.4170]
(c) Microsoft Corporation。保留所有權利。
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 字節的數據:
來自 117.78.5.125 的回復: 字節=32 時間=35ms TTL=93
來自 117.78.5.125 的回復: 字節=32 時間=36ms TTL=93
來自 117.78.5.125 的回復: 字節=32 時間=36ms TTL=93
來自 117.78.5.125 的回復: 字節=32 時間=39ms TTL=93
117.78.5.125 的 Ping 統計信息:
數據包: 已發送 = 4,已接收 = 4,丟失 = 0 (0% 丟失),
往返行程的估計時間(以毫秒為單位):
最短 = 35ms,最長 = 39ms,平均 = 36ms
C:Users11266 >
MQTT協議接入端口號有兩個,1883是非加密端口,8883是證書加密端口,單片機無法加載證書,所以使用1883端口比較合適。 接下來的ESP8266就采用1883端口連接華為云物聯網平臺。
2.3 創建產品
(1)創建產品
(2)填寫產品信息
根據自己產品名字填寫,下面的設備類型選擇自定義類型。
(3)產品創建成功
創建完成之后點擊查看詳情。
(4)添加自定義模型
產品創建完成之后,點擊進入產品詳情頁面,翻到最下面可以看到模型定義。
模型簡單來說: 就是存放設備上傳到云平臺的數據。
你可以根據自己的產品進行創建。
比如:
煙霧可以叫 MQ2
溫度可以叫 Temperature
濕度可以叫 humidity
火焰可以叫 flame
其他的傳感器自己用單詞簡寫命名即可。 這就是你的單片機設備端上傳到服務器的數據名字。
先點擊自定義模型。
再創建一個服務ID。
接著點擊新增屬性。
2.4 添加設備
產品是屬于上層的抽象模型,接下來在產品模型下添加實際的設備。添加的設備最終需要與真實的設備關聯在一起,完成數據交互。
(1)注冊設備
(2)根據自己的設備填寫
(3)保存設備信息
創建完畢之后,點擊保存并關閉,得到創建的設備密匙信息。該信息在后續生成MQTT三元組的時候需要使用。
(4)設備創建完成
(5)設備詳情
2.5 MQTT協議主題訂閱與發布
(1)MQTT協議介紹
當前的設備是采用MQTT協議與華為云平臺進行通信。
MQTT是一個物聯網傳輸協議,它被設計用于輕量級的發布/訂閱式消息傳輸,旨在為低帶寬和不穩定的網絡環境中的物聯網設備提供可靠的網絡服務。MQTT是專門針對物聯網開發的輕量級傳輸協議。MQTT協議針對低帶寬網絡,低計算能力的設備,做了特殊的優化,使得其能適應各種物聯網應用場景。目前MQTT擁有各種平臺和設備上的客戶端,已經形成了初步的生態系統。
MQTT是一種消息隊列協議,使用發布/訂閱消息模式,提供一對多的消息發布,解除應用程序耦合,相對于其他協議,開發更簡單;MQTT協議是工作在TCP/IP協議上;由TCP/IP協議提供穩定的網絡連接;所以,只要具備TCP協議棧的網絡設備都可以使用MQTT協議。 本次設備采用的ESP8266就具備TCP協議棧,能夠建立TCP連接,所以,配合STM32代碼里封裝的MQTT協議,就可以與華為云平臺完成通信。
**華為云的MQTT協議接入幫助文檔在這里: **https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
業務流程:
(2)華為云平臺MQTT協議使用限制
描述 | 限制 |
---|---|
支持的MQTT協議版本 | 3.1.1 |
與標準MQTT協議的區別 | 支持Qos 0和Qos 1支持Topic自定義不支持QoS2不支持will、retain msg |
MQTTS支持的安全等級 | 采用TCP通道基礎 + TLS協議(最高TLSv1.3版本) |
單帳號每秒最大MQTT連接請求數 | 無限制 |
單個設備每分鐘支持的最大MQTT連接數 | 1 |
單個MQTT連接每秒的吞吐量,即帶寬,包含直連設備和網關 | 3KB/s |
MQTT單個發布消息最大長度,超過此大小的發布請求將被直接拒絕 | 1MB |
MQTT連接心跳時間建議值 | 心跳時間限定為30至1200秒,推薦設置為120秒 |
產品是否支持自定義Topic | 支持 |
消息發布與訂閱 | 設備只能對自己的Topic進行消息發布與訂閱 |
每個訂閱請求的最大訂閱數 | 無限制 |
(3)主題訂閱格式
幫助文檔地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
對于設備而言,一般會訂閱平臺下發消息給設備 這個主題。
設備想接收平臺下發的消息,就需要訂閱平臺下發消息給設備 的主題,訂閱后,平臺下發消息給設備,設備就會收到消息。
如果設備想要知道平臺下發的消息,需要訂閱上面圖片里標注的主題。
以當前設備為例,最終訂閱主題的格式如下:
$oc/devices/{device_id}/sys/messages/down
最終的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down
(4)主題發布格式
對于設備來說,主題發布表示向云平臺上傳數據,將最新的傳感器數據,設備狀態上傳到云平臺。
這個操作稱為:屬性上報。
幫助文檔地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html
根據幫助文檔的介紹, 當前設備發布主題,上報屬性的格式總結如下:
發布的主題格式:
$oc/devices/{device_id}/sys/properties/report
最終的格式:
$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report
發布主題時,需要上傳數據,這個數據格式是JSON格式。
上傳的JSON數據格式如下:
{
"services": [
{
"service_id": < 填服務ID >,
"properties": {
"< 填屬性名稱1 >": < 填屬性值 >,
"< 填屬性名稱2 >": < 填屬性值 >,
..........
}
}
]
}
根據JSON格式,一次可以上傳多個屬性字段。 這個JSON格式里的,服務ID,屬性字段名稱,屬性值類型,在前面創建產品的時候就已經介紹了,不記得可以翻到前面去查看。
根據這個格式,組合一次上傳的屬性數據:
{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}
2.6 MQTT三元組
**MQTT協議登錄需要填用戶ID,設備ID,設備密碼等信息,就像我們平時登錄QQ,微信一樣要輸入賬號密碼才能登錄。MQTT協議登錄的這3個參數,一般稱為MQTT三元組。 **
接下來介紹,華為云平臺的MQTT三元組參數如何得到。
(1)MQTT服務器地址
要登錄MQTT服務器,首先記得先知道服務器的地址是多少,端口是多少。
幫助文檔地址:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home
MQTT協議的端口支持1883和8883,它們的區別是:8883 是加密端口更加安全。但是單片機上使用比較困難,所以當前的設備是采用1883端口進連接的。
根據上面的域名和端口號,得到下面的IP地址和端口號信息: 如果設備支持填寫域名可以直接填域名,不支持就直接填寫IP地址。 (IP地址就是域名解析得到的)
華為云的MQTT服務器地址:117.78.5.125
華為云的MQTT端口號:1883
如何得到IP地址?如何域名轉IP? 打開Windows的命令行輸入以下命令。
ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com
(2)生成MQTT三元組
**華為云提供了一個在線工具,用來生成MQTT鑒權三元組: **https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打開這個工具,填入設備的信息(也就是剛才創建完設備之后保存的信息),點擊生成,就可以得到MQTT的登錄信息了。
下面是打開的頁面:
填入設備的信息: (上面兩行就是設備創建完成之后保存得到的)
直接得到三元組信息。
得到三元組之后,設備端通過MQTT協議登錄鑒權的時候,填入參數即可。
ClientId 663cb18871d845632a0912e7_dev1_0_0_2024050911
Username 663cb18871d845632a0912e7_dev1
Password 71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237
2.7 模擬設備登錄測試
**經過上面的步驟介紹,已經創建了產品,設備,數據模型,得到MQTT登錄信息。 接下來就用MQTT客戶端軟件模擬真實的設備來登錄平臺。測試與服務器通信是否正常。 **
(1)填入登錄信息
打開MQTT客戶端軟件,對號填入相關信息(就是上面的文本介紹)。然后,點擊登錄,訂閱主題,發布主題。
(2)打開網頁查看
完成上面的操作之后,打開華為云網頁后臺,可以看到設備已經在線了。
點擊詳情頁面,可以看到上傳的數據:
到此,云平臺的部署已經完成,設備已經可以正常上傳數據了。
(3)MQTT登錄測試參數總結
MQTT服務器: 117.78.5.125
MQTT端口號: 183
//物聯網服務器的設備信息
#define MQTT_ClientID "663cb18871d845632a0912e7_dev1_0_0_2024050911"
#define MQTT_UserName "663cb18871d845632a0912e7_dev1"
#define MQTT_PassWord "71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237"
//訂閱與發布的主題
#define SET_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down" //訂閱
#define POST_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report" //發布
發布的數據:
{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}
2.8 創建IAM賬戶
創建一個IAM賬戶,因為接下來開發上位機,需要使用云平臺的API接口,這些接口都需要token進行鑒權。簡單來說,就是身份的認證。 調用接口獲取Token時,就需要填寫IAM賬號信息。所以,接下來演示一下過程。
**地址: **https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users
**【1】獲取項目憑證 ** 點擊左上角用戶名,選擇下拉菜單里的我的憑證
項目憑證:
28add376c01e4a61ac8b621c714bf459
【2】創建IAM用戶
鼠標放在左上角頭像上,在下拉菜單里選擇統一身份認證
。
點擊左上角創建用戶
。
創建成功:
【3】創建完成
用戶信息如下:
主用戶名 l19504562721
IAM用戶 ds_abc
密碼 DS12345678
2.9 獲取影子數據
幫助文檔:https://support.huaweicloud.com/api-iothub/iot_06_v5_0079.html
設備影子介紹:
設備影子是一個用于存儲和檢索設備當前狀態信息的JSON文檔。
每個設備有且只有一個設備影子,由設備ID唯一標識
設備影子僅保存最近一次設備的上報數據和預期數據
無論該設備是否在線,都可以通過該影子獲取和設置設備的屬性
簡單來說:設備影子就是保存,設備最新上傳的一次數據。
我們設計的軟件里,如果想要獲取設備的最新狀態信息,就采用設備影子接口。
如果對接口不熟悉,可以先進行在線調試:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow
在線調試接口,可以請求影子接口,了解請求,與返回的數據格式。
調試完成看右下角的響應體,就是返回的影子數據。
設備影子接口返回的數據如下:
{
"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
}
]
}
調試成功之后,可以得到訪問影子數據的真實鏈接,接下來的代碼開發中,就采用Qt寫代碼訪問此鏈接,獲取影子數據,完成上位機開發。
鏈接如下:
https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c714bf459/devices/663cb18871d845632a0912e7_dev1/shadow
三、上位機開發
為了方便查看設備上傳的數據,接下來利用Qt開發一款Android手機APP 和 Windows上位機。
使用華為云平臺提供的API接口獲取設備上傳的數據,進行可視化顯示,以及遠程控制設備。
3.1 Qt開發環境安裝
**Qt的中文官網: **https://www.qt.io/zh-cn/
QT5.12.6的下載地址:https://download.qt.io/archive/qt/5.12/5.12.6
或者去網盤里下載:https://pan.quark.cn/s/145a9b3f7f53
打開下載鏈接后選擇下面的版本進行下載:
qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details
軟件安裝時斷網安裝,否則會提示輸入賬戶。
安裝的時候,第一個復選框里勾選一個mingw 32
編譯器即可,其他的不管默認就行,直接點擊下一步繼續安裝。
選擇MinGW 32-bit 編譯器: (一定要看清楚了)
說明: 我這里只是介紹PC端,也就是Windows系統下的Qt環境搭建。 Android的開發環境比較麻煩,如果想學習Android開發,想編譯Android程序的APP,需要自己去搭建Android環境。
也可以看下面這篇文章,不過這個文章是在Qt開發專欄里付費的,需要訂閱專欄才可以看。 如果不想付費看,也可以自行找其他教程,自己搭建好必須的環境就行了
Android環境搭建的博客鏈接:https://blog.csdn.net/xiaolong1126626497/article/details/117254453
3.2 新建上位機工程
前面2講解了需要用的API接口,接下來就使用Qt設計上位機,設計界面,完成整體上位機的邏輯設計。
【1】新建工程
【2】設置項目的名稱。
【3】選擇編譯系統
【4】選擇默認繼承的類
【5】選擇編譯器
【6】點擊完成
【7】工程創建完成
3.3 設計UI界面與工程配置
【1】打開UI文件
打開默認的界面如下:
【2】開始設計界面
根據自己需求設計界面。
3.5 編譯Windows上位機
點擊軟件左下角的綠色三角形按鈕進行編譯運行。
編譯之后的效果:
3.6 配置Android環境
如果想編譯Android手機APP,必須要先自己配置好自己的Android環境。(搭建環境的過程可以自行百度搜索學習)
然后才可以進行下面的步驟。
【1】選擇Android編譯器
【2】創建Android配置文件
創建完成。
【3】配置Android圖標與名稱
【3】編譯Android上位機
Qt本身是跨平臺的,直接選擇Android的編譯器,就可以將程序編譯到Android平臺。
然后點擊構建。
成功之后,在目錄下可以看到生成的apk
文件,也就是Android手機的安裝包,電腦端使用QQ
發送給手機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代碼開發
4.1 MQTT協議設計
#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"
//復位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的回應
//返回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)//等待變為低電平
{
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讀取一個字節
//返回值:讀到的數據
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讀取一次數據
//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位數據
{
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
//這里我們僅以規則通道為例
//我們默認僅開啟通道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復位
RCC- >APB2RSTR&=~(1< 9);//復位結束
RCC- >CFGR&=~(3< 14); //分頻因子清零
//SYSCLK/DIV2=12M ADC時鐘設置為12M,ADC最大時鐘不能超過14M!
//否則將導致ADC準確度下降!
RCC- >CFGR|=2< 14;
ADC1- >CR1&=0XF0FFFF; //工作模式清零
ADC1- >CR1|=0< 16; //獨立工作模式
ADC1- >CR1&=~(1< 8); //非掃描模式
ADC1- >CR2&=~(1< 1); //單次轉換模式
ADC1- >CR2&=~(7< 17);
ADC1- >CR2|=7< 17; //軟件控制轉換
ADC1- >CR2|=1< 20; //使用用外部觸發(SWSTART)!!! 必須使用一個事件來觸發
ADC1- >CR2&=~(1< 11); //右對齊
ADC1- >CR2|=1< 23; //使能溫度傳感器
ADC1- >SQR1&=~(0XF< 20);
ADC1- >SQR1|=0< 20; //1個轉換在規則序列中 也就是只轉換規則序列1
//設置通道1的采樣時間
ADC1- >SMPR2&=~(3*1); //通道1采樣時間清空
ADC1- >SMPR2|=7< (3*1); //通道1 239.5周期,提高采樣時間可以提高精確度
ADC1- >SMPR1&=~(7< 3*6);//清除通道16原來的設置
ADC1- >SMPR1|=7< (3*6); //通道16 239.5周期,提高采樣時間可以提高精確度
ADC1- >CR2|=1< 0; //開啟AD轉換器
ADC1- >CR2|=1< 3; //使能復位校準
while(ADC1- >CR2&1< 3); //等待校準結束
//該位由軟件設置并由硬件清除。在校準寄存器被初始化后該位將被清除。
ADC1- >CR2|=1< 2; //開啟AD校準
while(ADC1- >CR2&1< 2); //等待校準結束
//該位由軟件設置以開始校準,并在校準結束時由硬件清除
}
//獲得ADC1某個通道的值
//ch:通道值 0~16
//返回值:轉換結果
u16 Get_Adc(u8 ch)
{
//設置轉換序列
ADC1- >SQR3&=0XFFFFFFE0;//規則序列1 通道ch
ADC1- >SQR3|=ch;
ADC1- >CR2|=1< 22; //啟動規則轉換通道
while(!(ADC1- >SR&1< 1));//等待轉換結束
return ADC1- >DR; //返回adc值
}
//獲取通道ch的轉換值,取times次,然后平均
//ch:通道編號
//times:獲取次數
//返回值:通道ch的times次轉換結果平均值
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; //轉換為溫度值
result=temperate*=100; //擴大100倍.
return result;
}
//初始化ADC3
//這里我們僅以規則通道為例
//我們默認僅開啟通道6
void Adc3_Init(void)
{
RCC- >APB2ENR|=1< 15; //ADC3時鐘使能
RCC- >APB2RSTR|=1< 15; //ADC復位
RCC- >APB2RSTR&=~(1< 15);//復位結束
RCC- >CFGR&=~(3< 14); //分頻因子清零
//SYSCLK/DIV2=12M ADC時鐘設置為12M,ADC最大時鐘不能超過14M!
//否則將導致ADC準確度下降!
RCC- >CFGR|=2< 14;
ADC3- >CR1&=0XF0FFFF; //工作模式清零
ADC3- >CR1|=0< 16; //獨立工作模式
ADC3- >CR1&=~(1< 8); //非掃描模式
ADC3- >CR2&=~(1< 1); //單次轉換模式
ADC3- >CR2&=~(7< 17);
ADC3- >CR2|=7< 17; //軟件控制轉換
ADC3- >CR2|=1< 20; //使用用外部觸發(SWSTART)!!! 必須使用一個事件來觸發
ADC3- >CR2&=~(1< 11); //右對齊
ADC3- >SQR1&=~(0XF< 20);
ADC3- >SQR1|=0< 20; //1個轉換在規則序列中 也就是只轉換規則序列1
//設置通道1的采樣時間
ADC3- >SMPR2&=~(7< (3*6));//通道6采樣時間清空
ADC3- >SMPR2|=7< (3*6); //通道6 239.5個周期,提高采樣時間可以提高精確度
ADC3- >CR2|=1< 0; //開啟AD轉換器
ADC3- >CR2|=1< 3; //使能復位校準
while(ADC1- >CR2&1< 3); //等待校準結束
//該位由軟件設置并由硬件清除。在校準寄存器被初始化后該位將被清除。
ADC3- >CR2|=1< 2; //開啟AD校準
while(ADC3- >CR2&1< 2); //等待校準結束
//該位由軟件設置以開始校準,并在校準結束時由硬件清除
}
//獲得ADC3某個通道的值
//ch:通道值 0~16
//返回值:轉換結果
u16 Get_Adc3(u8 ch)
{
//設置轉換序列
ADC3- >SQR3&=0XFFFFFFE0;//規則序列1 通道ch
ADC3- >SQR3|=ch;
ADC3- >CR2|=1< 22; //啟動規則轉換通道
while(!(ADC3- >SR&1< 1));//等待轉換結束
return ADC3- >DR; //返回adc值
}
五、總結
本項目致力于開發一款基于STM32微控制器的智能養殖場環境監測系統。該系統集成了DHT11溫濕度傳感器、BH1750光敏傳感器等,用于實時采集養殖環境中的溫度、濕度及光照強度數據,并通過OLED顯示屏提供本地數據顯示功能。為了實現遠程監控與管理,系統還配備了ESP8266 WiFi模塊,利用MQTT協議與華為云物聯網平臺連接,使得用戶可以通過定制的Windows上位機軟件或Android應用程序隨時隨地訪問環境數據,設定報警閾值,并遠程控制通風風扇、加濕器和燈光等設備,以確保養殖環境維持在最優狀態。系統采用USB線5V供電,確保了運行的便捷性和能耗的有效控制。
總體而言,這一環境監測系統通過智能化手段提升養殖場的管理水平,降低運營成本,同時提高動物的生長質量和健康水平。
審核編輯 黃宇
-
微控制器
+關注
關注
48文章
7496瀏覽量
151083 -
STM32
+關注
關注
2266文章
10876瀏覽量
354922 -
監測系統
+關注
關注
8文章
2682瀏覽量
81260 -
IOT
+關注
關注
186文章
4181瀏覽量
196270 -
華為云
+關注
關注
3文章
2435瀏覽量
17322
發布評論請先 登錄
相關推薦
評論