精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于STM32設計的通信機房空調與新風系統聯動裝置

DS小龍哥-嵌入式技術 ? 來源:DS小龍哥-嵌入式技術 ? 作者:DS小龍哥-嵌入式技 ? 2024-10-29 14:01 ? 次閱讀

一、前言

本項目的資料已上傳到夸克網盤(包括軟件工具、傳感器代碼),需要的可以去下載https://pan.quark.cn/s/145a9b3f7f53

1.1 項目介紹

【1】項目開發背景

隨著信息技術的發展,數據中心通信機房作為信息處理的核心樞紐,在各行各業中扮演著越來越重要的角色。然而,由于設備密集且持續運行,機房內的溫度和濕度控制成為保證設備正常運行的關鍵因素之一。傳統的機房環境管理方式往往依賴于定期的人工巡檢和手動調節,這種方式不僅效率低下,而且容易出現管理疏漏,導致設備過熱或濕度過高,進而影響設備的穩定性和使用壽命。

針對這一現狀,本項目提出了一種基于STM32微控制器的通信機房空調與新風系統聯動裝置的設計方案。通過集成環境溫濕度傳感器、風扇控制模塊以及4G通信模塊,實現了對機房環境的實時監測和智能調控。特別是在數據傳輸方面,借助華為云IOT物聯網云平臺的強大功能,不僅可以遠程監控機房內的環境變化,還能及時調整空調和新風系統的運行策略,確保機房環境始終處于最佳狀態。同時,考慮到實際應用中的便捷性,本裝置還提供了本地控制功能,用戶可以通過直觀的OLED顯示屏了解當前環境狀態,并通過簡單的按鍵操作來調整設置,增強了用戶體驗。

通過這樣的設計,本項目不僅解決了傳統機房管理中存在的問題,還極大地提高了機房環境管理的智能化水平,為機房管理人員提供了一個高效、可靠、易于操作的解決方案,進一步保障了通信設備的穩定運行,降低了運維成本。

image-20240904161823706

【2】設計實現的功能

(1)實時采集環境參數:系統配備了DHT11溫濕度傳感器,能夠連續不斷地獲取機房內的環境溫度和濕度數據。

(2)風扇通風散熱控制:通過繼電器模塊控制兩個5V直流電機小風扇的啟停,以達到通風散熱的效果,確保機房內部溫度維持在安全范圍內。

(3)數據遠程上傳:利用合宙Air724UG 4G模塊,將環境監測數據上傳至華為云IOT物聯網云平臺,實現數據的云端存儲與管理。

(4)遠程監控與控制:用戶可以通過Windows客戶端應用程序實時查看機房內的溫度、濕度、風扇狀態等信息,并能遠程設置溫度濕度閾值,選擇運行模式(自動/手動),以及遠程控制風扇的開關。

(5)本地控制功能:在沒有網絡連接的情況下,用戶依然可以通過設備上的按鍵來控制風扇的開關狀態,以及切換設備的運行模式。

(6)OLED顯示屏信息展示:裝置上安裝了一個0.96寸的OLED顯示屏,用于顯示兩頁信息:第一頁顯示環境溫度、濕度、溫度和濕度閾值;第二頁顯示設備運行模式和風扇的開關狀態。

【3】項目硬件模塊組成

(1)主控模塊:采用STM32F103RCT6微控制器作為核心處理器,負責處理各種傳感器數據、控制輸出信號以及與外部設備的通信。

(2)環境監測模塊:使用DHT11溫濕度傳感器來實時采集機房內的溫度和濕度數據,為后續的環境控制提供依據。

(3)顯示模塊:配置了一塊0.96寸的OLED顯示屏,通過SPI協議接口與主控芯片相連,用于顯示當前環境的溫度、濕度、設定閾值以及設備的運行狀態。

(4)風扇控制模塊:包括兩個5V直流電機的小風扇,通過繼電器控制風扇的啟動和停止,實現通風散熱功能。

(5)通信模塊:采用合宙Air724UG 4G模塊,支持全網通4G網絡,用于將采集到的數據上傳至華為云IOT物聯網云平臺,同時也支持接收來自云端的控制指令。

(6)人機交互模塊:包括若干個按鍵,允許用戶進行本地控制,如設置溫度濕度閾值、切換設備運行模式以及控制風扇的開關。

(7)電源模塊:系統使用一個5V 2A的穩壓模塊來提供穩定的電力供應,確保各個模塊的正常工作。

這些硬件模塊相互協作,共同構成了一個完整的智能通信機房空調與新風系統聯動裝置,能夠實現環境參數的實時監測、數據的遠程傳輸與控制、以及本地的人機交互等功能。

【4】需求總結

項目名稱: 基于STM32設計的通信機房空調與新風系統聯動裝置

支持的功能:
(1) 能夠實時采集環境溫度與濕度。
(2) 能夠控制2個風扇進行通風扇熱。
(3) 設備端的數據通過4G網絡上傳到華為云IOT物聯網云平臺。
(4) 能夠通過Windows大屏遠程查看設備上傳的溫度、濕度,風扇開關狀態,設備運行的模式。
(5) 能夠通過Windows大屏遠程設置溫度和濕度閥值,設置運行模式(自動模式和手動模式),以及控制風扇的開啟和關閉。
(6) 本地設備也可以通過按鍵控制風扇的開關,以及切換設備運行模式。
(7) 本地設備帶了一個0.96寸的OLED顯示屏,一共顯示2個頁面。 第一個頁面顯示: 可以顯示環境的溫度、環境的濕度、溫度閥值、濕度閥值。 第二個頁面顯示: 設備運行模式、風扇的開關狀態。

硬件選型:
(1)主控芯片選擇STM32F103RCT6
(2)OLED顯示屏選擇SPI協議接口的0.96寸OLED顯示屏。
(3)4G模塊采用合宙的Air724UG 4G模塊,支持全網通4G網絡。
(4)環境溫濕度檢測采用DHT11模塊。
(5)風扇采用5V直流電機的小風扇,通過繼電器控制風扇的開和關。
(6)系統電源采用5V 2A的穩壓模塊進行供電,提供穩定電源。

1.2 設計思路

本項目的設計思路主要圍繞提高通信機房環境管理的智能化和自動化水平展開。鑒于機房內設備眾多且運行負荷大的特點,環境溫濕度的實時監測顯得尤為重要。選擇了DHT11溫濕度傳感器來持續采集環境數據,確保能夠及時反映機房內的溫度和濕度變化情況。

為了有效控制機房內的溫度,設計中引入了風扇控制模塊。通過繼電器模塊來驅動5V直流電機的小風扇,可以根據采集到的溫度數據自動或手動控制風扇的開關,以此來調節機房內的溫度,防止設備因過熱而損壞。考慮到遠程監控的需求,選用了合宙Air724UG 4G模塊,通過4G網絡將環境數據上傳至華為云IOT物聯網云平臺,使得管理者能夠在任何地點通過Windows客戶端實時查看機房狀態,并作出相應的調整。

在本地控制方面,為了便于現場工作人員的操作,設計中加入了按鍵控制功能。通過簡單的按鍵輸入,可以實現風扇開關的控制以及設備運行模式的切換。為了使用戶能夠直觀地了解當前機房的狀態,項目中還配備了一塊0.96寸的OLED顯示屏,用以顯示環境參數、設置閾值及設備運行模式等重要信息。

系統整體采用STM32F103RCT6作為主控芯片,該芯片具備足夠的性能來處理傳感器數據、執行控制邏輯以及管理與其他模塊之間的通信。為了保證系統的穩定運行,采用了5V 2A的穩壓電源模塊來提供可靠的電力供應。

本項目的設計思路是在充分理解通信機房環境管理需求的基礎上,結合現代物聯網技術和智能控制手段,構建一個集數據采集、分析處理、遠程監控與本地控制于一體的智能化管理系統,以提高機房環境管理的效率和可靠性。

1.3 系統功能總結

功能類別描述
環境監測實時采集機房內的溫度和濕度數據,使用DHT11模塊。
風扇控制通過繼電器模塊控制兩個5V直流風扇的啟停,實現通風散熱。
數據傳輸利用合宙Air724UG 4G模塊將環境數據上傳至華為云IOT物聯網云平臺。
遠程監控通過Windows客戶端遠程查看溫度、濕度、風扇狀態及設備運行模式。
遠程設置允許用戶遠程設置溫度和濕度閾值,切換運行模式(自動/手動),控制風扇開關。
本地控制提供按鍵輸入功能,實現風扇開關控制及設備運行模式切換。
顯示信息OLED顯示屏顯示環境參數、設置閾值、設備運行模式及風扇狀態。
電源管理采用5V 2A穩壓電源模塊,確保系統穩定運行。

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是有點不適應界面的。

image-20221210225339928

【2】上位機開發

上位機的開發選擇Qt框架,編程語言采用C++;Qt是一個1991年由Qt Company開發的跨平臺C++圖形用戶界面應用程序開發框架。它既可以開發GUI程序,也可用于開發非GUI程序,比如控制臺工具和服務器。Qt是面向對象的框架,使用特殊的代碼生成擴展(稱為元對象編譯器(Meta Object Compiler, moc))以及一些宏,Qt很容易擴展,并且允許真正地組件編程。Qt能輕松創建具有原生C++性能的連接設備、用戶界面(UI)和應用程序。它功能強大且結構緊湊,擁有直觀的工具和庫。

image-20230218001243591

image-20230218001219105

二、部署華為云物聯網平臺

**華為云官網: **https://www.huaweicloud.com/

**打開官網,搜索物聯網,就能快速找到 **設備接入IoTDA

image-20221204193824815

2.1 物聯網平臺介紹

華為云物聯網平臺(IoT 設備接入云服務)提供海量設備的接入和管理能力,將物理設備聯接到云,支撐設備數據采集上云和云端下發命令給設備進行遠程控制,配合華為云其他產品,幫助我們快速構筑物聯網解決方案。

使用物聯網平臺構建一個完整的物聯網解決方案主要包括3部分:物聯網平臺、業務應用和設備。

物聯網平臺作為連接業務應用和設備的中間層,屏蔽了各種復雜的設備接口,實現設備的快速接入;同時提供強大的開放能力,支撐行業用戶構建各種物聯網解決方案。

設備可以通過固網、2G/3G/4G/5GNB-IoT、Wifi等多種網絡接入物聯網平臺,并使用LWM2M/CoAP、MQTT、HTTPS協議將業務數據上報到平臺,平臺也可以將控制命令下發給設備。

業務應用通過調用物聯網平臺提供的API,實現設備數據采集、命令下發、設備管理等業務場景。

img

2.2 開通物聯網服務

**地址: **https://www.huaweicloud.com/product/iothub.html

image-20221204194233414

點擊立即創建

image-20240117134653452

正在創建標準版實例,需要等待片刻。

image-20240117134729401

創建完成之后,點擊實例名稱。 可以看到標準版實例的設備接入端口和地址。

image-20240425180759670

在上面也能看到 免費單元的限制。

image-20240425180817704

開通之后,點擊總覽,也能查看接入信息。 我們當前設備準備采用MQTT協議接入華為云平臺,這里可以看到MQTT協議的地址和端口號等信息。

image-20240425180845461

總結:

端口號:   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)創建產品

image-20230109164412041

(2)填寫產品信息

根據自己產品名字填寫,下面的設備類型選擇自定義類型。

image-20240612094809689

(3)產品創建成功

image-20240612095148945

創建完成之后點擊查看詳情。

image-20240612095134263

(4)添加自定義模型

產品創建完成之后,點擊進入產品詳情頁面,翻到最下面可以看到模型定義。

模型簡單來說: 就是存放設備上傳到云平臺的數據。

你可以根據自己的產品進行創建。

比如:

煙霧可以叫  MQ2
溫度可以叫  Temperature
濕度可以叫  humidity
火焰可以叫  flame
其他的傳感器自己用單詞簡寫命名即可。 這就是你的單片機設備端上傳到服務器的數據名字。

先點擊自定義模型。

image-20240612095517900

再創建一個服務ID。

image-20240612095542749

接著點擊新增屬性。

image-20240612095648815

image-20240612095711898

2.4 添加設備

產品是屬于上層的抽象模型,接下來在產品模型下添加實際的設備。添加的設備最終需要與真實的設備關聯在一起,完成數據交互。

(1)注冊設備

image-20240425181935561

(2)根據自己的設備填寫

image-20240612100115167

(3)保存設備信息

創建完畢之后,點擊保存并關閉,得到創建的設備密匙信息。該信息在后續生成MQTT三元組的時候需要使用。

image-20240612100128061

(4)設備創建完成

image-20240612100147232

(5)設備詳情

image-20240612100202960

image-20240612100217236

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

img

業務流程:

img

(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

image-20221207153310037

對于設備而言,一般會訂閱平臺下發消息給設備 這個主題。

設備想接收平臺下發的消息,就需要訂閱平臺下發消息給設備 的主題,訂閱后,平臺下發消息給設備,設備就會收到消息。

如果設備想要知道平臺下發的消息,需要訂閱上面圖片里標注的主題。

以當前設備為例,最終訂閱主題的格式如下:
$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

image-20221207153637391

根據幫助文檔的介紹, 當前設備發布主題,上報屬性的格式總結如下:

發布的主題格式:
$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

image-20240509193207359

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

image-20240425182610048

(2)生成MQTT三元組

**華為云提供了一個在線工具,用來生成MQTT鑒權三元組: **https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/

打開這個工具,填入設備的信息(也就是剛才創建完設備之后保存的信息),點擊生成,就可以得到MQTT的登錄信息了。

下面是打開的頁面:

image-20240425183025893

填入設備的信息: (上面兩行就是設備創建完成之后保存得到的)

直接得到三元組信息。

image-20240509193310020

得到三元組之后,設備端通過MQTT協議登錄鑒權的時候,填入參數即可。

ClientId  663cb18871d845632a0912e7_dev1_0_0_2024050911
Username  663cb18871d845632a0912e7_dev1
Password  71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237

2.7 模擬設備登錄測試

**經過上面的步驟介紹,已經創建了產品,設備,數據模型,得到MQTT登錄信息。 接下來就用MQTT客戶端軟件模擬真實的設備來登錄平臺。測試與服務器通信是否正常。 **

(1)填入登錄信息

打開MQTT客戶端軟件,對號填入相關信息(就是上面的文本介紹)。然后,點擊登錄,訂閱主題,發布主題。

image-20240509193457358

(2)打開網頁查看

完成上面的操作之后,打開華為云網頁后臺,可以看到設備已經在線了。

image-20240612100508790

點擊詳情頁面,可以看到上傳的數據:

image-20240612100529581

到此,云平臺的部署已經完成,設備已經可以正常上傳數據了。

(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】獲取項目憑證 ** 點擊左上角用戶名,選擇下拉菜單里的我的憑證

image-20240509193646253

image-20240509193701262

項目憑證:

28add376c01e4a61ac8b621c714bf459

【2】創建IAM用戶

鼠標放在左上角頭像上,在下拉菜單里選擇統一身份認證

image-20240509193729078

點擊左上角創建用戶

image-20240509193744287

image-20240314153208692

image-20240314153228359

image-20240314153258229

創建成功:

image-20240314153315444

【3】創建完成

image-20240509193828289

用戶信息如下:

主用戶名  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

在線調試接口,可以請求影子接口,了解請求,與返回的數據格式。

調試完成看右下角的響應體,就是返回的影子數據。

image-20240509194152229

設備影子接口返回的數據如下:

{
 "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寫代碼訪問此鏈接,獲取影子數據,完成上位機開發。

image-20240509194214716

鏈接如下:

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/image-20221207160550486

image-20221207160606892

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編譯器即可,其他的不管默認就行,直接點擊下一步繼續安裝。

image-20221203151742653

選擇MinGW 32-bit 編譯器: (一定要看清楚了)

image-20221203151750344

說明: 我這里只是介紹PC端,也就是Windows系統下的Qt環境搭建。 Android的開發環境比較麻煩,如果想學習Android開發,想編譯Android程序的APP,需要自己去搭建Android環境。

也可以看下面這篇文章,不過這個文章是在Qt開發專欄里付費的,需要訂閱專欄才可以看。 如果不想付費看,也可以自行找其他教程,自己搭建好必須的環境就行了

Android環境搭建的博客鏈接:https://blog.csdn.net/xiaolong1126626497/article/details/117254453

3.2 新建上位機工程

前面2講解了需要用的API接口,接下來就使用Qt設計上位機,設計界面,完成整體上位機的邏輯設計。

【1】新建工程

image-20240117144052547

【2】設置項目的名稱。

image-20240509195711965

【3】選擇編譯系統

image-20240117144239681

【4】選擇默認繼承的類

image-20240117144302275

【5】選擇編譯器

image-20240314162137170

【6】點擊完成

image-20240117144354252

【7】工程創建完成

image-20230421094133333

3.3 設計UI界面與工程配置

【1】打開UI文件

image-20230421094815236

打開默認的界面如下:

image-20240425194845233

【2】開始設計界面

根據自己需求設計界面。

image-20240904160948734

3.5 編譯Windows上位機

點擊軟件左下角的綠色三角形按鈕進行編譯運行。

image-20240509202031739

編譯之后的效果:

image-20240903163952415

3.6 配置Android環境

如果想編譯Android手機APP,必須要先自己配置好自己的Android環境。(搭建環境的過程可以自行百度搜索學習)

然后才可以進行下面的步驟。

【1】選擇Android編譯器

image-20240425232651515

image-20240509202408776

【2】創建Android配置文件

image-20240117144604025

image-20240117144635052

image-20240117144652014

創建完成。

【3】配置Android圖標與名稱

image-20240612100947190

【3】編譯Android上位機

Qt本身是跨平臺的,直接選擇Android的編譯器,就可以將程序編譯到Android平臺。

然后點擊構建。

image-20240509202534407

成功之后,在目錄下可以看到生成的apk文件,也就是Android手機的安裝包,電腦端使用QQ發送給手機QQ,手機登錄QQ接收,就能直接安裝。

生成的apk的目錄在哪里呢? 編譯完成之后,在控制臺會輸出APK文件的路徑。

知道目錄在哪里之后,在Windows的文件資源管理器里,找到路徑,具體看下圖,找到生成的apk文件。

image-20240509202712295

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 DHT11溫濕度模塊驅動代碼

#include "dht11.h"
#include "delay.h"
//IO方向設置
#define DHT11_IO_IN()  {GPIOG- >CRH&=0XFFFF0FFF;GPIOG- >CRH|=8< 
#define DHT11_IO_OUT() {GPIOG- >CRH&=0XFFFF0FFF;GPIOG- >CRH|=3< 
////IO操作函數											   
#define	DHT11_DQ_OUT PGout(11) //數據端口	PG11 
#define	DHT11_DQ_IN  PGin(11)  //數據端口	PG11


u8 DHT11_Init(void);		//初始化DHT11
u8 DHT11_Read_Data(u8 *temp,u8 *humi);//讀取溫濕度
u8 DHT11_Read_Byte(void);	//讀出一個字節
u8 DHT11_Read_Bit(void);	//讀出一個位
u8 DHT11_Check(void);		//檢測是否存在DHT11
void DHT11_Rst(void);		//復位DHT11    


//復位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();
}

五、總結

本項目設計一種基于STM32微控制器的智能通信機房空調與新風系統聯動裝置,以提升通信機房的環境管理效率和自動化水平。該裝置能夠實時監測機房內的環境溫度和濕度,并根據預設閾值自動控制風扇進行散熱,從而保持機房內部環境的適宜條件。系統利用合宙Air724UG 4G模塊將采集到的數據上傳至華為云IOT物聯網云平臺,支持遠程監控及控制功能。通過Windows客戶端界面,管理員可以實時查看機房內環境參數,調整設定值,并控制風扇的工作狀態。此外,本地操作也得到了考慮,通過簡單的按鍵輸入即可完成風扇控制和工作模式的切換。為了方便現場人員查看當前狀態,設備還配備了一塊0.96寸的OLED顯示屏,用于顯示環境參數、設置值及運行狀態等信息。整個系統由一個5V 2A的穩壓電源模塊供電,確保了系統的穩定性和可靠性。此項目的實施有助于提高通信機房的智能化管理水平,減少人工干預,降低維護成本。

審核編輯 黃宇

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 微控制器
    +關注

    關注

    48

    文章

    7496

    瀏覽量

    151083
  • 物聯網
    +關注

    關注

    2904

    文章

    44304

    瀏覽量

    371456
  • STM32
    +關注

    關注

    2266

    文章

    10876

    瀏覽量

    354922
  • 溫濕度傳感器

    關注

    5

    文章

    575

    瀏覽量

    35670
收藏 人收藏

    評論

    相關推薦

    機房環境監控系統

    和生產。JCD-PSMS8.0機房監控系統是一個綜合利用計算機網絡技術、數據庫技術、通信技術、自動控制技術、新型傳感技術等構成的計算機網絡,提供的一種以計算機技術為基礎、基于集中管理監控模式的自動化
    發表于 09-29 16:31

    智能新風-未來發展的必然

    研發出基站智能新風系統,該系統充分利用自然環境資源,在氣溫較低的季節或者是低溫的夜間自動啟動新風系統,減少
    發表于 04-11 17:28

    基站新風系統 推進綠色基站進程

    系統,熱管,機房外墻涂抹隔熱層等等。從這些節能方式中的效果來看,基站智能新風系統屬于其中效率較高的一種方式,且普遍應用。  基站智能新風
    發表于 06-20 15:02

    【GoKit申請】通信機房環境控制及防盜監控系統

    申請理由:想用機智云GoKit開發套件做這個項目項目描述:希望通過機智云GoKit開發套件為通信機房研制一款 高效率的環境控制及防盜監控系統
    發表于 11-02 10:46

    通信機房電源及配套設計

    通信機房電源及配套設計一、前期準備及勘察階段1.1前期準備我們設計院將接下來的通信電源工程設計任務書下發的設計部門,設計部門經過確定后再發到項目組成員,項目組成員要詳細的琢磨任務書上寫明的每一句話
    發表于 09-10 07:55

    通信機房數據中心備用柴油發電機組的組成和特點是什么?

    通信機房數據中心備用柴油發電機組的組成和特點是什么?
    發表于 11-04 06:51

    現代防雷技術在變電站通信機房的應用

    現代防雷技術在變電站通信機房的應用:Application of the modern thunder-proof on communication house of transformer substation 摘要:弱電設備(通信設備)如何防感應雷是目前變電站
    發表于 11-01 09:51 ?25次下載

    通信電源在通信機房中的重要作用

    通信電源在整體通信機房中占據重要作用。面對
    發表于 11-16 11:55 ?2244次閱讀

    通信機房溫度檢測系統設計

    設計了一款智能化、性價比高的通信機房溫度的檢測系統。由于傳統的溫度檢測系統測溫點少、兼容性和擴展性差的缺點。文中設計主要運用主從分布式通信思想,設計了應用于測量
    發表于 10-17 16:56 ?41次下載
    <b class='flag-5'>通信機房</b>溫度檢測<b class='flag-5'>系統</b>設計

    通信電源在通信機房中的應用

    通信電源在整體通信機房中占據重要作用。面對電信拆分、人員重組等新的發展形勢,對電源維護管理工作提出了更新、更高地要求。
    發表于 10-18 12:27 ?1506次閱讀

    通信電源在通信機房中的重要性

    通信電源在整體通信機房中占據重要作用。面對電信拆分、人員重組等新的發展形勢,對電源維護管理工作提出了更新、更高地要求。通信電源的管理工作應根據技術發展、管理發展和
    發表于 12-28 10:54 ?1721次閱讀

    物聯網或成通信機房智能化的“關鍵先生”

    隨著物聯網大數據技術的興起,傳統的通信機房和基站也將迎來智能化改進和完善。通信機房和基站在通信系統中占有舉足輕重的地位,機房管理
    發表于 03-27 16:32 ?805次閱讀

    通信機房電源-48伏與48伏相同嗎

    早期的通信局供電系統有用-48V,現在已經由原來的-48V,60V,改為48V的供電系統了,所以現用的通信逆變器電源也統一改為48V。所以通信機房
    的頭像 發表于 02-06 11:45 ?2172次閱讀
    <b class='flag-5'>通信機房</b>電源-48伏與48伏相同嗎

    淺談通信機房的節能減排

    電子發燒友網站提供《淺談通信機房的節能減排.doc》資料免費下載
    發表于 11-10 16:24 ?1次下載
    淺談<b class='flag-5'>通信機房</b>的節能減排

    通信機房動環監控系統功能特點

    在信息化飛速發展的今天,通信機房的安全與穩定對于整個社會的運轉具有至關重要的意義。為了確保通信機房的高效、穩定運行,動環監控系統應運而生,成為了機房管理中不可或缺的一環。
    的頭像 發表于 06-11 16:37 ?388次閱讀