一、前言
1.1 項目介紹
已開源的全部工具軟件、源碼、教程文檔、視頻都已經上傳到網盤。https://pan.quark.cn/s/145a9b3f7f53
【1】項目開發背景
隨著現代農業技術的發展,智能農業逐漸成為提高農作物產量和品質的關鍵手段之一。傳統的農業生產方式依賴于人工經驗,這種方式不僅效率低下,而且難以應對氣候變化帶來的挑戰。特別是在水資源管理和光照控制方面,傳統方法往往無法提供精確的控制,這直接影響到了農作物的生長周期和最終產量。
在此背景下,基于現代物聯網技術的農作物生長管理系統應運而生。這類系統能夠通過各種傳感器實時監測農田環境,如土壤濕度、光照強度、空氣溫度和濕度等,并根據這些數據自動調整灌溉、照明等設施的工作狀態。這種智能化的管理不僅大大減輕了農民的勞動強度,而且通過優化資源利用,提高了農作物的生產效率。
本項目提出了一種基于STM32微控制器和NBIOT通信技術的農作物生長管理系統設計方案。通過將先進的微處理器技術與窄帶物聯網(NBIOT)結合,本系統能夠在無人干預的情況下,實現對農田環境的精確控制。系統還具備遠程監控和控制功能,用戶可以通過手機應用程序隨時查看農田的實時數據,并根據需要調整設備的工作模式。
隨著科技的進步和人們對食品安全重視程度的提高,智能農業管理系統的需求日益增長。本項目的開發不僅有助于推動農業向更加科學化、精準化的方向發展,同時也為實現可持續農業生產和保障糧食安全提供了技術支持。通過本系統的應用,可以預見未來的農業生產將更加高效、環保,同時也為農民帶來了更大的經濟效益和社會效益。
【2】設計實現的功能
(1)實時檢測土壤含水量,并根據預設的閾值自動判斷是否需要補水,進而實現自動化的灌溉控制。
(2)除了自動補水功能外,系統還提供了本地按鈕控制補水選項,同時支持通過遠程控制手段進行手動補水操作,增加了補水控制的靈活性。
(3)系統支持植物補光燈亮度的PWM自動調節,可以根據環境亮度的變化動態調整燈光亮度,以確保植物獲得最佳光照條件。
(4)環境溫度與濕度檢測功能也是系統的重要組成部分,當檢測到空氣溫度或濕度超出設定的安全范圍時,系統會觸發相應的警報機制。
(5)本地OLED屏幕顯示當前環境的各項參數,如溫度、濕度、土壤含水量以及光線照射強度,方便用戶隨時查看環境狀態。
(6)為了及時響應異常情況,系統配備了蜂鳴器報警功能。一旦發現溫度、濕度或土壤含水量不符合預設的健康范圍,系統會通過蜂鳴器發出聲音報警。這些閾值可以通過手機應用程序進行設定。
(7)為了避免由于溫度突變引起的誤操作,系統采用了適當的控制方法,在設定溫度發生大變動時延遲系統的反應調節時間,確保系統的穩定性和準確性。
(8)通過NBIOT-BC26模塊,整個設備可以連接至華為云IoT物聯網平臺,并采用MQTT協議上傳數據。基于此,設計了Android手機APP,以便用戶能夠遠程查看設備上傳的數據,并實現遠程控制設備的功能,如設置設備的溫度閾值等。
(9)采用Qt框架開發Android手機應用程序
【3】項目硬件模塊組成
(1)主控芯片:采用STM32F103RCT6作為核心處理器,負責處理系統中所有的邏輯運算和控制任務。
(2)環境光照強度檢測模塊:使用BH1750傳感器來測量環境光照強度,為補光燈的亮度調節提供數據支持。
(3)環境溫濕度檢測模塊:采用DHT11傳感器,用于監測環境中的溫度和濕度,確保農作物處于適宜的生長環境中。
(4)OLED顯示屏模塊:選用0.96寸的SPI協議OLED顯示屏,用于顯示當前環境參數,如溫度、濕度、土壤含水量及光線強度等信息。
(5)補光燈模塊:使用白色LED燈作為植物補光燈,通過PWM技術調節亮度,以適應不同環境下的光照需求。
(6)聲音報警模塊:采用蜂鳴器作為報警裝置,當環境參數超出預設閾值時,通過蜂鳴器發出聲音警報。
(7)土壤濕度檢測模塊:使用帶有ADC模擬量接口的土壤濕度檢測傳感器,用于實時監測土壤含水量,確保適時補水。
(8)網絡通信模塊:采用NBIOT-BC26模塊,實現設備與華為云IoT物聯網平臺之間的數據傳輸,通過MQTT協議上傳數據。
(9)補水控制模塊:使用繼電器驅動5V抽水電機,根據土壤濕度傳感器的反饋信息自動或手動控制補水過程。
(10)供電模塊:采用USB線5V供電方式,為整個系統提供穩定的電力供應,簡化了設備的安裝和使用流程。
【4】需求總結
項目名字: 基于STM32+NBIOT設計的農作物生長管理系統
實現功能:
1. 實時檢測土壤含水量,根據設置的閥值判斷實現自動補水
2. 本地可用按鈕控制補水、遠程手動控制補水
3. 支持植物補光燈亮度PWM自動調節,可根據環境亮度調節燈光亮度
4. 支持檢測環境溫度、濕度,當空氣溫度、濕度超過設定值范圍時會報警。
5. 本地0LED屏幕顯示當前環境溫度、濕度、土壤含水量、光線照射強度等參數。
6. 支持蜂鳴器報警提醒,當溫度、濕度、土壤含水量閥值不符合設定值時通過蜂鳴器發出聲音報警(閥值可通過手機APP設定)
7.采用合適的控制方法,當設定溫度發生大突變時,為了防止誤設置,延遲系統的反應調節時間。
8. 整個設備會通過NBIOT-BC26連接華為云IOT物聯網平臺,通過MQTT協議上傳數據到物聯網云平臺,再設計Android手機APP實現遠程顯示設備上傳的數據,同時可以遠程控制設備,設置設備溫度閥值等等。
9. 采用Qt(C++)設計Android手機APP,實現數據遠程監測顯示和遠程控制。
硬件選型:
1. 主控芯片選擇STM32F103RCT6
2. 環境光照強度檢測采用BH1750
3. 環境溫濕度檢測采用DHT11
4. OLED顯示屏采用0.96寸SPI協議顯示屏
5. 補光燈采用白色LED燈
6. 聲音報警采用蜂鳴器
7. 土壤濕度檢測采用ADC模擬量接口的土壤濕度檢測傳感器
8. 聯網采用NBIOT-BC26模塊
9. 植物補水采用繼電器驅動5V抽水電機抽水進行補水。
10.供電電源:采用USB線-5V供電。
1.2 設計思路
本項目的設計思路圍繞著實現一個高度自動化、智能化的農作物生長管理系統展開,通過現代電子技術與物聯網技術的融合,為農作物提供最佳的生長環境。首先,系統的核心是基于STM32F103RCT6微控制器,這是因為該芯片具備高性能、低功耗的特點,非常適合用于需要實時處理大量數據的應用場景。通過集成多種傳感器,系統能夠實時采集環境數據,如土壤濕度、光照強度、溫度和濕度等,為后續的決策和控制提供依據。
在環境監測方面,系統選用了BH1750光照強度傳感器和DHT11溫濕度傳感器。BH1750能夠精確測量光照強度,從而支持植物補光燈的PWM自動調節功能;而DHT11則用于監測空氣中的溫度和濕度,當這些參數超出預設的安全范圍時,系統會通過蜂鳴器報警,提醒用戶采取措施。系統配置了土壤濕度檢測傳感器,它可以實時檢測土壤的含水量,并根據設定的閾值自動控制補水,確保植物獲得充足的水分。
為了使用戶能夠直觀地了解環境狀態,系統配備了一塊0.96寸的OLED顯示屏,通過SPI協議與主控芯片相連,實時顯示各項環境參數。這樣,即使在沒有智能手機的情況下,用戶也能通過顯示屏掌握當前的環境狀況。
在聯網功能上,系統采用了NBIOT-BC26模塊,通過窄帶物聯網技術實現設備與華為云IoT物聯網平臺的連接。借助MQTT協議,系統能夠將采集到的數據上傳至云端,用戶可以通過Android手機APP實時查看數據,并遠程控制設備。為了進一步增強用戶體驗,項目還計劃使用Qt框架開發Android版的應用程序,以便于用戶更方便地進行遠程監控和管理。
在控制策略上,系統特別考慮到了溫度突變可能引發的誤操作問題。為此,設計了適當的延時控制機制,即當設定溫度發生較大變化時,系統不會立即作出反應,而是經過一段時間的延遲后再進行調節,從而避免了因溫度波動導致的誤動作。
在供電方面,系統選擇了簡單且可靠的USB線5V供電方式,不僅便于安裝和維護,也確保了系統的穩定性。通過這一系列的設計思路,本項目打造一個高效、可靠且易于使用的農作物生長管理系統,助力現代農業的智能化轉型。
1.3 系統功能總結
功能類別 | 描述 |
---|---|
實時土壤含水量檢測與自動補水 | 系統能夠實時檢測土壤含水量,并根據預設閾值自動判斷是否需要啟動補水機制。 |
本地與遠程補水控制 | 用戶可以通過本地按鈕手動控制補水,也可以通過遠程手段(如手機APP)進行手動補水控制。 |
補光燈亮度自動調節 | 根據環境亮度變化,系統能夠自動調節補光燈的亮度,以滿足植物生長所需的光照條件。 |
環境溫濕度檢測與報警 | 通過傳感器檢測環境溫度和濕度,當檢測到的數值超出設定范圍時,系統會觸發報警機制。 |
環境參數本地顯示 | OLED屏幕實時顯示當前環境的溫度、濕度、土壤含水量及光線照射強度等參數。 |
蜂鳴器聲音報警 | 當溫度、濕度或土壤含水量不符合預設的健康范圍時,系統通過蜂鳴器發出聲音報警。 |
溫度突變延遲控制 | 針對溫度突變的情況,系統采用了延遲反應機制,以防止因溫度快速變化而導致的誤操作。 |
數據上傳與遠程監控 | 通過NBIOT-BC26模塊將數據上傳至華為云IoT平臺,并通過MQTT協議傳輸數據,支持遠程監控。 |
遠程控制與設置 | 用戶可以通過設計的Android手機APP實現遠程控制設備,包括查看數據、控制設備和設置溫度閾值等功能。 |
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 OLED顯示屏驅動代碼
#include "oled.h"
#include "stdlib.h"
#include "oledfont.h"
#include "delay.h"
//OLED模式設置
//0: 4線串行模式 (模塊的BS1,BS2均接GND)
//1: 并行8080模式 (模塊的BS1,BS2均接VCC)
#define OLED_MODE 1
//---------------------------OLED端口定義--------------------------
#define OLED_CS PDout(6)
#define OLED_RST PGout(15)
#define OLED_RS PDout(3)
#define OLED_WR PGout(14)
#define OLED_RD PGout(13)
//PC0~7,作為數據線
#define DATAOUT(x) GPIOC- >ODR=(GPIOC- >ODR&0xff00)|(x&0x00FF); //輸出
//使用4線串行接口時使用
#define OLED_SCLK PCout(0)
#define OLED_SDIN PCout(1)
#define OLED_CMD 0 //寫命令
#define OLED_DATA 1 //寫數據
//OLED控制用函數
void OLED_WR_Byte(u8 dat,u8 cmd);
void OLED_Display_On(void);
void OLED_Display_Off(void);
void OLED_Refresh_Gram(void);
void OLED_Init(void);
void OLED_Clear(void);
void OLED_DrawPoint(u8 x,u8 y,u8 t);
void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot);
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size);
void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size);
//OLED的顯存
//存放格式如下.
//[0]0 1 2 3 ... 127
//[1]0 1 2 3 ... 127
//[2]0 1 2 3 ... 127
//[3]0 1 2 3 ... 127
//[4]0 1 2 3 ... 127
//[5]0 1 2 3 ... 127
//[6]0 1 2 3 ... 127
//[7]0 1 2 3 ... 127
u8 OLED_GRAM[128][8];
//更新顯存到LCD
void OLED_Refresh_Gram(void)
{
u8 i,n;
for(i=0;i< 8;i++)
{
OLED_WR_Byte (0xb0+i,OLED_CMD); //設置頁地址(0~7)
OLED_WR_Byte (0x00,OLED_CMD); //設置顯示位置—列低地址
OLED_WR_Byte (0x10,OLED_CMD); //設置顯示位置—列高地址
for(n=0;n< 128;n++)OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA);
}
}
#if OLED_MODE==1 //8080并口
//向SSD1306寫入一個字節。
//dat:要寫入的數據/命令
//cmd:數據/命令標志 0,表示命令;1,表示數據;
void OLED_WR_Byte(u8 dat,u8 cmd)
{
DATAOUT(dat);
OLED_RS=cmd;
OLED_CS=0;
OLED_WR=0;
OLED_WR=1;
OLED_CS=1;
OLED_RS=1;
}
#else
//向SSD1306寫入一個字節。
//dat:要寫入的數據/命令
//cmd:數據/命令標志 0,表示命令;1,表示數據;
void OLED_WR_Byte(u8 dat,u8 cmd)
{
u8 i;
OLED_RS=cmd; //寫命令
OLED_CS=0;
for(i=0;i< 8;i++)
{
OLED_SCLK=0;
if(dat&0x80)OLED_SDIN=1;
else OLED_SDIN=0;
OLED_SCLK=1;
dat< <=1;
}
OLED_CS=1;
OLED_RS=1;
}
#endif
//開啟OLED顯示
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON
OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON
}
//關閉OLED顯示
void OLED_Display_Off(void)
{
OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC命令
OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF
OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF
}
//清屏函數,清完屏,整個屏幕是黑色的!和沒點亮一樣!!!
void OLED_Clear(void)
{
u8 i,n;
for(i=0;i< 8;i++)for(n=0;n< 128;n++)OLED_GRAM[n][i]=0X00;
OLED_Refresh_Gram();//更新顯示
}
//畫點
//x:0~127
//y:0~63
//t:1 填充 0,清空
void OLED_DrawPoint(u8 x,u8 y,u8 t)
{
u8 pos,bx,temp=0;
if(x >127||y >63)return;//超出范圍了.
pos=7-y/8;
bx=y%8;
temp=1< (7-bx);
if(t)OLED_GRAM[x][pos]|=temp;
else OLED_GRAM[x][pos]&=~temp;
}
//x1,y1,x2,y2 填充區域的對角坐標
//確保x1<=x2;y1<=y2 0<=x1<=127 0<=y1<=63
//dot:0,清空;1,填充
void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot)
{
u8 x,y;
for(x=x1;x<=x2;x++)
{
for(y=y1;y<=y2;y++)OLED_DrawPoint(x,y,dot);
}
OLED_Refresh_Gram();//更新顯示
}
//在指定位置顯示一個字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白顯示;1,正常顯示
//size:選擇字體 12/16/24
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
{
u8 temp,t,t1;
u8 y0=y;
u8 csize=(size/8+((size%8)?1:0))*(size/2); //得到字體一個字符對應點陣集所占的字節數
chr=chr-' ';//得到偏移后的值
for(t=0;t< csize;t++)
{
if(size==12)temp=asc2_1206[chr][t]; //調用1206字體
else if(size==16)temp=asc2_1608[chr][t]; //調用1608字體
else if(size==24)temp=asc2_2412[chr][t]; //調用2412字體
else return; //沒有的字庫
for(t1=0;t1< 8;t1++)
{
if(temp&0x80)OLED_DrawPoint(x,y,mode);
else OLED_DrawPoint(x,y,!mode);
temp< <=1;
y++;
if((y-y0)==size)
{
y=y0;
x++;
break;
}
}
}
}
//m^n函數
u32 mypow(u8 m,u8 n)
{
u32 result=1;
while(n--)result*=m;
return result;
}
//顯示2個數字
//x,y :起點坐標
//len :數字的位數
//size:字體大小
//mode:模式 0,填充模式;1,疊加模式
//num:數值(0~4294967295);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size)
{
u8 t,temp;
u8 enshow=0;
for(t=0;t< len;t++)
{
temp=(num/mypow(10,len-t-1))%10;
if(enshow==0&&t< (len-1))
{
if(temp==0)
{
OLED_ShowChar(x+(size/2)*t,y,' ',size,1);
continue;
}else enshow=1;
}
OLED_ShowChar(x+(size/2)*t,y,temp+'0',size,1);
}
}
//顯示字符串
//x,y:起點坐標
//size:字體大小
//*p:字符串起始地址
void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size)
{
while((*p<='~')&&(*p >=' '))//判斷是不是非法字符!
{
if(x >(128-(size/2))){x=0;y+=size;}
if(y >(64-size)){y=x=0;OLED_Clear();}
OLED_ShowChar(x,y,*p,size,1);
x+=size/2;
p++;
}
}
//初始化SSD1306
void OLED_Init(void)
{
RCC- >APB2ENR|=1< 4; //使能PORTC時鐘
RCC- >APB2ENR|=1< 5; //使能PORTD時鐘
RCC- >APB2ENR|=1< 8; //使能PORTG時鐘
GPIOD- >CRL&=0XF0FF0FFF;//PD3,6 推挽輸出
GPIOD- >CRL|=0X03003000;
GPIOD- >ODR|=1< 3;
GPIOD- >ODR|=1< 6;
#if OLED_MODE==1 //8080并口模式
GPIOC- >CRL=0X33333333; //PC0~7 OUT
GPIOC- >ODR|=0X00FF;
GPIOG- >CRH&=0X000FFFFF; //PG13,14,15 OUT
GPIOG- >CRH|=0X33300000;
GPIOG- >ODR|=7< 13;
#else //4線SPI模式
GPIOC- >CRL&=0XFFFFFF00; //PC0,1 OUT
GPIOC- >CRL|=0X00000033;
GPIOC- >ODR|=3< 0;
GPIOG- >CRH&=0X0FFFFFFF; //RST
GPIOG- >CRH|=0X30000000;
GPIOG- >ODR|=1< 15;
#endif
OLED_CS=1;
OLED_RS=1;
OLED_RST=0;
delay_ms(100);
OLED_RST=1;
OLED_WR_Byte(0xAE,OLED_CMD); //關閉顯示
OLED_WR_Byte(0xD5,OLED_CMD); //設置時鐘分頻因子,震蕩頻率
OLED_WR_Byte(80,OLED_CMD); //[3:0],分頻因子;[7:4],震蕩頻率
OLED_WR_Byte(0xA8,OLED_CMD); //設置驅動路數
OLED_WR_Byte(0X3F,OLED_CMD); //默認0X3F(1/64)
OLED_WR_Byte(0xD3,OLED_CMD); //設置顯示偏移
OLED_WR_Byte(0X00,OLED_CMD); //默認為0
OLED_WR_Byte(0x40,OLED_CMD); //設置顯示開始行 [5:0],行數.
OLED_WR_Byte(0x8D,OLED_CMD); //電荷泵設置
OLED_WR_Byte(0x14,OLED_CMD); //bit2,開啟/關閉
OLED_WR_Byte(0x20,OLED_CMD); //設置內存地址模式
OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,頁地址模式;默認10;
OLED_WR_Byte(0xA1,OLED_CMD); //段重定義設置,bit0:0,0- >0;1,0- >127;
OLED_WR_Byte(0xC0,OLED_CMD); //設置COM掃描方向;bit3:0,普通模式;1,重定義模式 COM[N-1]- >COM0;N:驅動路數
OLED_WR_Byte(0xDA,OLED_CMD); //設置COM硬件引腳配置
OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置
OLED_WR_Byte(0x81,OLED_CMD); //對比度設置
OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默認0X7F (亮度設置,越大越亮)
OLED_WR_Byte(0xD9,OLED_CMD); //設置預充電周期
OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2;
OLED_WR_Byte(0xDB,OLED_CMD); //設置VCOMH 電壓倍率
OLED_WR_Byte(0x30,OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;
OLED_WR_Byte(0xA4,OLED_CMD); //全局顯示開啟;bit0:1,開啟;0,關閉;(白屏/黑屏)
OLED_WR_Byte(0xA6,OLED_CMD); //設置顯示方式;bit0:1,反相顯示;0,正常顯示
OLED_WR_Byte(0xAF,OLED_CMD); //開啟顯示
OLED_Clear();
}
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- >SQR1&=~(0XF< 20);
ADC1- >SQR1|=0< 20; //1個轉換在規則序列中 也就是只轉換規則序列1
//設置通道1的采樣時間
ADC1- >SMPR2&=~(3*1); //通道1采樣時間清空
ADC1- >SMPR2|=7< (3*1); //通道1 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;
}
4.4 定時器配置代碼
#include "timer.h"
#include "led.h"
//定時器3中斷服務程序
void TIM3_IRQHandler(void)
{
if(TIM3- >SR&0X0001)//溢出中斷
{
LED1=!LED1;
}
TIM3- >SR&=~(1< 0);//清除中斷標志位
}
//通用定時器3中斷初始化
//這里時鐘選擇為APB1的2倍,而APB1為36M
//arr:自動重裝值。
//psc:時鐘預分頻數
//這里使用的是定時器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
RCC- >APB1ENR|=1< 1; //TIM3時鐘使能
TIM3- >ARR=arr; //設定計數器自動重裝值//剛好1ms
TIM3- >PSC=psc; //預分頻器7200,得到10Khz的計數時鐘
TIM3- >DIER|=1< 0; //允許更新中斷
TIM3- >CR1|=0x01; //使能定時器3
MY_NVIC_Init(1,3,TIM3_IRQn,2);//搶占1,子優先級3,組2
}
//TIM3 PWM部分初始化
//PWM輸出初始化
//arr:自動重裝值
//psc:時鐘預分頻數
void TIM3_PWM_Init(u16 arr,u16 psc)
{
//此部分需手動修改IO口設置
RCC- >APB1ENR|=1< 1; //TIM3時鐘使能
RCC- >APB2ENR|=1< 3; //使能PORTB時鐘
GPIOB- >CRL&=0XFF0FFFFF; //PB5輸出
GPIOB- >CRL|=0X00B00000; //復用功能輸出
RCC- >APB2ENR|=1< 0; //開啟輔助時鐘
AFIO- >MAPR&=0XFFFFF3FF; //清除MAPR的[11:10]
AFIO- >MAPR|=1< 11; //部分重映像,TIM3_CH2- >PB5
TIM3- >ARR=arr; //設定計數器自動重裝值
TIM3- >PSC=psc; //預分頻器不分頻
TIM3- >CCMR1|=7< 12; //CH2 PWM2模式
TIM3- >CCMR1|=1< 11; //CH2預裝載使能
TIM3- >CCER|=1< 4; //OC2 輸出使能
TIM3- >CR1=0x0080; //ARPE使能
TIM3- >CR1|=0x01; //使能定時器3
}
//定時器5通道1輸入捕獲配置
//arr:自動重裝值
//psc:時鐘預分頻數
void TIM5_Cap_Init(u16 arr,u16 psc)
{
RCC- >APB1ENR|=1< 3; //TIM5 時鐘使能
RCC- >APB2ENR|=1< 2; //使能PORTA時鐘
GPIOA- >CRL&=0XFFFFFFF0; //PA0 清除之前設置
GPIOA- >CRL|=0X00000008; //PA0 輸入
GPIOA- >ODR|=0< 0; //PA0 下拉
TIM5- >ARR=arr; //設定計數器自動重裝值
TIM5- >PSC=psc; //預分頻器
TIM5- >CCMR1|=1< 0; //CC1S=01 選擇輸入端 IC1映射到TI1上
TIM5- >CCMR1|=0< 4; //IC1F=0000 配置輸入濾波器 不濾波
TIM5- >CCMR1|=0< 10; //IC2PS=00 配置輸入分頻,不分頻
TIM5- >CCER|=0< 1; //CC1P=0 上升沿捕獲
TIM5- >CCER|=1< 0; //CC1E=1 允許捕獲計數器的值到捕獲寄存器中
TIM5- >DIER|=1< 1; //允許捕獲中斷
TIM5- >DIER|=1< 0; //允許更新中斷
TIM5- >CR1|=0x01; //使能定時器2
MY_NVIC_Init(2,0,TIM5_IRQn,2);//搶占2,子優先級0,組2
}
//捕獲狀態
//[7]:0,沒有成功的捕獲;1,成功捕獲到一次.
//[6]:0,還沒捕獲到高電平;1,已經捕獲到高電平了.
//[5:0]:捕獲高電平后溢出的次數
u8 TIM5CH1_CAPTURE_STA=0; //輸入捕獲狀態
u16 TIM5CH1_CAPTURE_VAL; //輸入捕獲值
//定時器5中斷服務程序
void TIM5_IRQHandler(void)
{
u16 tsr;
tsr=TIM5- >SR;
if((TIM5CH1_CAPTURE_STA&0X80)==0)//還未成功捕獲
{
if(tsr&0X01)//溢出
{
if(TIM5CH1_CAPTURE_STA&0X40)//已經捕獲到高電平了
{
if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高電平太長了
{
TIM5CH1_CAPTURE_STA|=0X80;//標記成功捕獲了一次
TIM5CH1_CAPTURE_VAL=0XFFFF;
}else TIM5CH1_CAPTURE_STA++;
}
}
if(tsr&0x02)//捕獲1發生捕獲事件
{
if(TIM5CH1_CAPTURE_STA&0X40) //捕獲到一個下降沿
{
TIM5CH1_CAPTURE_STA|=0X80; //標記成功捕獲到一次高電平脈寬
TIM5CH1_CAPTURE_VAL=TIM5- >CCR1; //獲取當前的捕獲值.
TIM5- >CCER&=~(1< 1); //CC1P=0 設置為上升沿捕獲
}else //還未開始,第一次捕獲上升沿
{
TIM5CH1_CAPTURE_STA=0; //清空
TIM5CH1_CAPTURE_VAL=0;
TIM5CH1_CAPTURE_STA|=0X40; //標記捕獲到了上升沿
TIM5- >CNT=0; //計數器清空
TIM5- >CCER|=1< 1; //CC1P=1 設置為下降沿捕獲
}
}
}
TIM5- >SR=0;//清除中斷標志位
}
五、總結
本項目致力于構建一套基于STM32F103RCT6微控制器和NBIOT-BC26通信模塊的農作物生長管理系統。系統的核心功能在于實時監測農田環境中的關鍵參數,例如土壤含水量、環境光照強度、溫度與濕度,并據此實現自動化管理。具體而言,系統能夠依據預設的閾值自動控制補水,同時允許用戶通過本地按鈕或遠程控制的方式進行手動補水。此外,系統還能根據環境亮度的變化自動調節補光燈的亮度,以保證植物獲得適宜的光照條件。
為了保障作物生長環境的安全性,系統配置了環境溫濕度傳感器(DHT11),并在檢測到溫度或濕度超出預設范圍時觸發蜂鳴器報警。這些閾值可以通過手機應用程序進行設定,使得系統更加靈活易用。系統還配備了一塊0.96寸的OLED顯示屏,用于實時顯示環境溫度、濕度、土壤含水量以及光照強度等重要參數,使用戶能夠直觀地了解當前的環境狀況。
考慮到突然的溫度變化可能導致誤操作,系統采用了適當的控制策略,在設定溫度出現較大波動時延遲系統的反應調節時間,從而避免不必要的誤動作。整個設備通過NBIOT-BC26模塊連接至華為云IoT平臺,并采用MQTT協議上傳數據,使得用戶能夠借助微信小程序遠程監控設備狀態并進行控制,包括調整設備的溫度閾值等設置。
項目利用Qt框架開發了一個Android版的應用程序,進一步增強了系統的遠程監控與控制能力。該應用程序不僅可以顯示遠程監測數據,還可以讓用戶隨時隨地控制設備的各項功能。供電方面,系統選擇了簡便且易于獲取的USB線5V供電方案,方便用戶安裝與使用。
該項目通過集成先進的傳感器技術和物聯網應用,為現代化農業生產提供了一套高效、便捷且智能化的解決方案。
審核編輯 黃宇
-
物聯網
+關注
關注
2904文章
44304瀏覽量
371449 -
STM32
+關注
關注
2266文章
10876瀏覽量
354922 -
單片機
+關注
關注
0文章
207瀏覽量
16658
發布評論請先 登錄
相關推薦
評論