一、項目介紹
隨著智能家居的發(fā)展,人們對于家居設(shè)備的智能化和遠(yuǎn)程控制需求越來越高。智能衣柜作為智能家居的一部分,可以提供衣物存儲和保護(hù)的功能,并通過傳感器和互聯(lián)網(wǎng)技術(shù)實現(xiàn)對衣柜內(nèi)部環(huán)境的監(jiān)測和控制,為用戶提供更好的使用體驗。
本項目基于STM32F103ZET6主控芯片設(shè)計了一個智能衣柜系統(tǒng),主要功能包括溫度和濕度的監(jiān)測以及烘干控制。為了實現(xiàn)溫濕度的監(jiān)測,采用了DHT11傳感器,可以準(zhǔn)確地測量環(huán)境的溫度和濕度。通過將傳感器連接到STM32F103ZET6,可以實時獲取衣柜內(nèi)部的溫濕度數(shù)據(jù)。
為了實現(xiàn)烘干功能,系統(tǒng)使用加熱絲和小風(fēng)扇來制造熱氣并循環(huán)衣柜內(nèi)部的空氣,以去除濕氣并防止衣物發(fā)霉。加熱絲的控制采用繼電器來控制加熱絲的通斷,從而控制烘干的開關(guān)。通過與STM32F103ZET6的連接,可以實現(xiàn)對加熱絲和小風(fēng)扇的控制。
為了實現(xiàn)遠(yuǎn)程監(jiān)控和控制,系統(tǒng)采用了ESP8266-WIFI模塊將采集到的溫濕度數(shù)據(jù)上傳到華為云物聯(lián)網(wǎng)平臺。用戶可以通過在Android手機(jī)上開發(fā)的Qt應(yīng)用程序遠(yuǎn)程查看衣柜的實時溫度和濕度,并設(shè)置濕度閥值。如果濕度超出閥值,系統(tǒng)會通過本地蜂鳴器報警和手機(jī)APP提示用戶,以防止衣物發(fā)霉。此外,用戶還可以通過手機(jī)APP遠(yuǎn)程控制衣柜的烘干系統(tǒng),去除濕氣,防止衣物發(fā)霉或出現(xiàn)霉味。
整個系統(tǒng)通過將傳感器、主控芯片、繼電器、ESP8266-WIFI模塊和手機(jī)APP進(jìn)行集成,實現(xiàn)了智能衣柜的溫濕度監(jiān)測和遠(yuǎn)程控制功能,為用戶提供了便捷、智能的衣物存儲和保護(hù)解決方案。
二、設(shè)計思路總結(jié)
2.1 硬件選型
在該項目中,以下是一些可能的硬件選型:
【1】主控芯片:STM32F103ZET6,它是一款性能強(qiáng)大的32位ARM Cortex-M3微控制器,具有豐富的外設(shè)和存儲器,適合用作智能衣柜系統(tǒng)的主控芯片。
【2】溫濕度傳感器:DHT11,能夠準(zhǔn)確地測量環(huán)境的溫度和濕度。
【3】網(wǎng)絡(luò)通信模塊:ESP8266-WIFI模塊,可以實現(xiàn)與互聯(lián)網(wǎng)的連接,用于將采集到的溫濕度數(shù)據(jù)上傳到華為云物聯(lián)網(wǎng)平臺。
【4】繼電器:用于控制加熱絲的通斷,從而控制烘干的開關(guān)。選擇合適的繼電器型號和規(guī)格,以適應(yīng)加熱絲的電流和電壓要求。
【5】蜂鳴器:用于本地報警,當(dāng)濕度超出閥值時發(fā)出報警聲音。
【5】Android手機(jī):作為用戶界面,通過Qt開發(fā)的Android手機(jī)APP實現(xiàn)遠(yuǎn)程查看和控制衣柜的溫濕度以及烘干系統(tǒng)的開關(guān)控制。
2.2 硬件設(shè)計
- 使用STM32F103ZET6作為主控芯片,連接溫濕度傳感器DHT11,繼電器,蜂鳴器和ESP8266-WIFI模塊。
- 將DHT11傳感器連接到主控芯片的GPIO口,以實時獲取衣柜內(nèi)部的溫濕度數(shù)據(jù)。
- 通過繼電器控制加熱絲的通斷,以控制烘干系統(tǒng)的開關(guān)。
- 連接蜂鳴器到主控芯片的GPIO口,當(dāng)濕度超出閥值時發(fā)出報警聲音。
- 將ESP8266-WIFI模塊連接到主控芯片的串口,以實現(xiàn)與華為云物聯(lián)網(wǎng)平臺的通信。
2.3 軟件設(shè)計
- 使用STM32的開發(fā)環(huán)境進(jìn)行固件開發(fā),編寫相應(yīng)的代碼來實現(xiàn)溫濕度傳感器的讀取、繼電器的控制和蜂鳴器的報警功能。
- 編寫與ESP8266-WIFI模塊通信的代碼,實現(xiàn)將采集到的溫濕度數(shù)據(jù)上傳到華為云物聯(lián)網(wǎng)平臺。
- 在華為云物聯(lián)網(wǎng)平臺上創(chuàng)建相應(yīng)的設(shè)備和數(shù)據(jù)通道,以接收和存儲來自智能衣柜的溫濕度數(shù)據(jù)。
- 開發(fā)基于Qt的Android手機(jī)APP,通過與華為云物聯(lián)網(wǎng)平臺的接口,實現(xiàn)遠(yuǎn)程查看衣柜的實時溫濕度和控制烘干系統(tǒng)的開關(guān)。
- 在手機(jī)APP上設(shè)置濕度閥值,當(dāng)濕度超出閥值時,觸發(fā)本地蜂鳴器報警和手機(jī)APP的提示功能。
2.4 系統(tǒng)交互流程
- STM32主控芯片讀取DHT11傳感器的溫濕度數(shù)據(jù)。
- 根據(jù)采集到的數(shù)據(jù),控制繼電器開關(guān)加熱絲和小風(fēng)扇,實現(xiàn)烘干功能。
- 將溫濕度數(shù)據(jù)通過ESP8266-WIFI模塊上傳到華為云物聯(lián)網(wǎng)平臺。
- 用戶通過Qt開發(fā)的Android手機(jī)APP遠(yuǎn)程訪問華為云物聯(lián)網(wǎng)平臺,獲取衣柜的實時溫濕度數(shù)據(jù)。
- 如果濕度超出設(shè)定的閥值,系統(tǒng)發(fā)出本地蜂鳴器報警和手機(jī)APP的提示,提醒用戶。
- 用戶可以通過手機(jī)APP遠(yuǎn)程控制烘干系統(tǒng)的開關(guān),去除濕氣,防止衣物發(fā)霉或出現(xiàn)霉味。
通過上述系統(tǒng)設(shè)計思路,實現(xiàn)了智能衣柜的溫濕度監(jiān)測和遠(yuǎn)程控制功能,提供了更智能、便捷的衣物存儲和保護(hù)解決方案。
三、部署華為云物聯(lián)網(wǎng)平臺
華為云官網(wǎng): https://www.huaweicloud.com/
打開官網(wǎng),搜索物聯(lián)網(wǎng),就能快速找到 設(shè)備接入IoTDA
。
3.1 物聯(lián)網(wǎng)平臺介紹
華為云物聯(lián)網(wǎng)平臺(IoT 設(shè)備接入云服務(wù))提供海量設(shè)備的接入和管理能力,將物理設(shè)備聯(lián)接到云,支撐設(shè)備數(shù)據(jù)采集上云和云端下發(fā)命令給設(shè)備進(jìn)行遠(yuǎn)程控制,配合華為云其他產(chǎn)品,幫助我們快速構(gòu)筑物聯(lián)網(wǎng)解決方案。
使用物聯(lián)網(wǎng)平臺構(gòu)建一個完整的物聯(lián)網(wǎng)解決方案主要包括3部分:物聯(lián)網(wǎng)平臺、業(yè)務(wù)應(yīng)用和設(shè)備。
物聯(lián)網(wǎng)平臺作為連接業(yè)務(wù)應(yīng)用和設(shè)備的中間層,屏蔽了各種復(fù)雜的設(shè)備接口,實現(xiàn)設(shè)備的快速接入;同時提供強(qiáng)大的開放能力,支撐行業(yè)用戶構(gòu)建各種物聯(lián)網(wǎng)解決方案。
設(shè)備可以通過固網(wǎng)、2G/3G/4G/5G、NB-IoT、Wifi等多種網(wǎng)絡(luò)接入物聯(lián)網(wǎng)平臺,并使用LWM2M/CoAP、MQTT、HTTPS協(xié)議將業(yè)務(wù)數(shù)據(jù)上報到平臺,平臺也可以將控制命令下發(fā)給設(shè)備。
業(yè)務(wù)應(yīng)用通過調(diào)用物聯(lián)網(wǎng)平臺提供的API,實現(xiàn)設(shè)備數(shù)據(jù)采集、命令下發(fā)、設(shè)備管理等業(yè)務(wù)場景。
3.2 開通物聯(lián)網(wǎng)服務(wù)
地址: https://www.huaweicloud.com/product/iothub.html
進(jìn)來默認(rèn)會提示開通標(biāo)準(zhǔn)版,在2023的1月1號年之后沒有基礎(chǔ)版了。
開通之后,點擊總覽
,查看接入信息。 我們當(dāng)前設(shè)備準(zhǔn)備采用MQTT協(xié)議接入華為云平臺,這里可以看到MQTT協(xié)議的地址和端口號等信息。
總結(jié):
端口號: MQTT (1883)| MQTTS (8883)
接入地址: 7445c6bcd3.st1.iotda-app.cn-north-4.myhuaweicloud.com
根據(jù)域名地址得到IP地址信息:
Microsoft Windows [版本 10.0.19044.2728]
(c) Microsoft Corporation。保留所有權(quán)利。
C:Users11266>ping 7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com
正在 Ping 7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字節(jié)的數(shù)據(jù):
來自 117.78.5.125 的回復(fù): 字節(jié)=32 時間=42ms TTL=30
來自 117.78.5.125 的回復(fù): 字節(jié)=32 時間=35ms TTL=30
來自 117.78.5.125 的回復(fù): 字節(jié)=32 時間=36ms TTL=30
來自 117.78.5.125 的回復(fù): 字節(jié)=32 時間=36ms TTL=30
117.78.5.125 的 Ping 統(tǒng)計信息:
數(shù)據(jù)包: 已發(fā)送 = 4,已接收 = 4,丟失 = 0 (0% 丟失),
往返行程的估計時間(以毫秒為單位):
最短 = 35ms,最長 = 42ms,平均 = 37ms
C:Users11266>
MQTT協(xié)議接入端口號有兩個,1883是非加密端口,8883是證書加密端口,單片機(jī)無法加載證書,所以使用1883端口比較合適。 接下來的ESP8266就采用1883端口連接華為云物聯(lián)網(wǎng)平臺。
3.3 創(chuàng)建產(chǎn)品
(1)創(chuàng)建產(chǎn)品
點擊產(chǎn)品頁,再點擊左上角創(chuàng)建產(chǎn)品。
(2)填寫產(chǎn)品信息
根據(jù)自己產(chǎn)品名字填寫。
(3)產(chǎn)品創(chuàng)建成功
(4)添加自定義模型
產(chǎn)品創(chuàng)建完成之后,點擊進(jìn)入產(chǎn)品詳情頁面,翻到最下面可以看到模型定義。
先點擊自定義模型。
再創(chuàng)建一個服務(wù)ID。
接著點擊新增屬性。
3.4 添加設(shè)備
產(chǎn)品是屬于上層的抽象模型,接下來在產(chǎn)品模型下添加實際的設(shè)備。添加的設(shè)備最終需要與真實的設(shè)備關(guān)聯(lián)在一起,完成數(shù)據(jù)交互。
(1)注冊設(shè)備
(2)根據(jù)自己的設(shè)備填寫
(3)保存設(shè)備信息
創(chuàng)建完畢之后,點擊保存并關(guān)閉,得到創(chuàng)建的設(shè)備密匙信息。該信息在后續(xù)生成MQTT三元組的時候需要使用。
(4) 設(shè)備創(chuàng)建完成
3.5 MQTT協(xié)議主題訂閱與發(fā)布
(1)MQTT協(xié)議介紹
當(dāng)前的設(shè)備是采用MQTT協(xié)議與華為云平臺進(jìn)行通信。
MQTT是一個物聯(lián)網(wǎng)傳輸協(xié)議,它被設(shè)計用于輕量級的發(fā)布/訂閱式消息傳輸,旨在為低帶寬和不穩(wěn)定的網(wǎng)絡(luò)環(huán)境中的物聯(lián)網(wǎng)設(shè)備提供可靠的網(wǎng)絡(luò)服務(wù)。MQTT是專門針對物聯(lián)網(wǎng)開發(fā)的輕量級傳輸協(xié)議。MQTT協(xié)議針對低帶寬網(wǎng)絡(luò),低計算能力的設(shè)備,做了特殊的優(yōu)化,使得其能適應(yīng)各種物聯(lián)網(wǎng)應(yīng)用場景。目前MQTT擁有各種平臺和設(shè)備上的客戶端,已經(jīng)形成了初步的生態(tài)系統(tǒng)。
MQTT是一種消息隊列協(xié)議,使用發(fā)布/訂閱消息模式,提供一對多的消息發(fā)布,解除應(yīng)用程序耦合,相對于其他協(xié)議,開發(fā)更簡單;MQTT協(xié)議是工作在TCP/IP協(xié)議上;由TCP/IP協(xié)議提供穩(wěn)定的網(wǎng)絡(luò)連接;所以,只要具備TCP協(xié)議棧的網(wǎng)絡(luò)設(shè)備都可以使用MQTT協(xié)議。 本次設(shè)備采用的ESP8266就具備TCP協(xié)議棧,能夠建立TCP連接,所以,配合STM32代碼里封裝的MQTT協(xié)議,就可以與華為云平臺完成通信。
華為云的MQTT協(xié)議接入幫助文檔在這里: https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
業(yè)務(wù)流程:
(2)華為云平臺MQTT協(xié)議使用限制
描述 | 限制 |
---|---|
支持的MQTT協(xié)議版本 | 3.1.1 |
與標(biāo)準(zhǔn)MQTT協(xié)議的區(qū)別 | 支持Qos 0和Qos 1支持Topic自定義不支持QoS2不支持will、retain msg |
MQTTS支持的安全等級 | 采用TCP通道基礎(chǔ) + TLS協(xié)議(最高TLSv1.3版本) |
單帳號每秒最大MQTT連接請求數(shù) | 無限制 |
單個設(shè)備每分鐘支持的最大MQTT連接數(shù) | 1 |
單個MQTT連接每秒的吞吐量,即帶寬,包含直連設(shè)備和網(wǎng)關(guān) | 3KB/s |
MQTT單個發(fā)布消息最大長度,超過此大小的發(fā)布請求將被直接拒絕 | 1MB |
MQTT連接心跳時間建議值 | 心跳時間限定為30至1200秒,推薦設(shè)置為120秒 |
產(chǎn)品是否支持自定義Topic | 支持 |
消息發(fā)布與訂閱 | 設(shè)備只能對自己的Topic進(jìn)行消息發(fā)布與訂閱 |
每個訂閱請求的最大訂閱數(shù) | 無限制 |
(3)主題訂閱格式
幫助文檔地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
對于設(shè)備而言,一般會訂閱平臺下發(fā)消息給設(shè)備 這個主題。
設(shè)備想接收平臺下發(fā)的消息,就需要訂閱平臺下發(fā)消息給設(shè)備 的主題,訂閱后,平臺下發(fā)消息給設(shè)備,設(shè)備就會收到消息。
如果設(shè)備想要知道平臺下發(fā)的消息,需要訂閱上面圖片里標(biāo)注的主題。
以當(dāng)前設(shè)備為例,最終訂閱主題的格式如下:
$oc/devices/{device_id}/sys/messages/down
最終的格式:
$oc/devices/6419627e40773741f9fbdac7_dev1/sys/messages/down
?(4)主題發(fā)布格式
對于設(shè)備來說,主題發(fā)布表示向云平臺上傳數(shù)據(jù),將最新的傳感器數(shù)據(jù),設(shè)備狀態(tài)上傳到云平臺。
這個操作稱為:屬性上報。
幫助文檔地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html
根據(jù)幫助文檔的介紹, 當(dāng)前設(shè)備發(fā)布主題,上報屬性的格式總結(jié)如下:
發(fā)布的主題格式:
$oc/devices/{device_id}/sys/properties/report
最終的格式:
$oc/devices/6419627e40773741f9fbdac7_dev1/sys/properties/report
發(fā)布主題時,需要上傳數(shù)據(jù),這個數(shù)據(jù)格式是JSON格式。
上傳的JSON數(shù)據(jù)格式如下:
{
"services": [
{
"service_id": <填服務(wù)ID>,
"properties": {
"<填屬性名稱1>": <填屬性值>,
"<填屬性名稱2>": <填屬性值>,
..........
}
}
]
}
根據(jù)JSON格式,一次可以上傳多個屬性字段。 這個JSON格式里的,服務(wù)ID,屬性字段名稱,屬性值類型,在前面創(chuàng)建產(chǎn)品的時候就已經(jīng)介紹了,不記得可以翻到前面去查看。
根據(jù)這個格式,組合一次上傳的屬性數(shù)據(jù):
{"services": [{"service_id": "stm32","properties":{"DS18B20":18,"motor_water":1,"motor_oxygen":1,"temp_max":10,"water_hp":130,"motor_food":0,"time_food":0,"oxygen_food":3}}]}
3.6 MQTT三元組
MQTT協(xié)議登錄需要填用戶ID,設(shè)備ID,設(shè)備密碼等信息,就像我們平時登錄QQ,微信一樣要輸入賬號密碼才能登錄。MQTT協(xié)議登錄的這3個參數(shù),一般稱為MQTT三元組。
接下來介紹,華為云平臺的MQTT三元組參數(shù)如何得到。
(1)MQTT服務(wù)器地址
要登錄MQTT服務(wù)器,首先記得先知道服務(wù)器的地址是多少,端口是多少。
幫助文檔地址:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home
MQTT協(xié)議的端口支持1883和8883,它們的區(qū)別是:8883 是加密端口更加安全。但是單片機(jī)上使用比較困難,所以當(dāng)前的設(shè)備是采用1883端口進(jìn)連接的。
根據(jù)上面的域名和端口號,得到下面的IP地址和端口號信息: 如果設(shè)備支持填寫域名可以直接填域名,不支持就直接填寫IP地址。 (IP地址就是域名解析得到的)
華為云的MQTT服務(wù)器地址:114.116.232.138
域名:7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com
華為云的MQTT端口號:1883
(2)生成MQTT三元組
華為云提供了一個在線工具,用來生成MQTT鑒權(quán)三元組: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打開這個工具,填入設(shè)備的信息(也就是剛才創(chuàng)建完設(shè)備之后保存的信息),點擊生成,就可以得到MQTT的登錄信息了。
下面是打開的頁面:
填入設(shè)備的信息: (上面兩行就是設(shè)備創(chuàng)建完成之后保存得到的)
直接得到三元組信息。
得到三元組之后,設(shè)備端通過MQTT協(xié)議登錄鑒權(quán)的時候,填入?yún)?shù)即可。
ClientId 6419627e40773741f9fbdac7_dev1_0_0_2023032108
Username 6419627e40773741f9fbdac7_dev1
Password 861ac9e6a579d36888b2aaf97714be7af6c77017b017162884592bd68b086a6e
3.7 模擬設(shè)備登錄測試
經(jīng)過上面的步驟介紹,已經(jīng)創(chuàng)建了產(chǎn)品,設(shè)備,數(shù)據(jù)模型,得到MQTT登錄信息。 接下來就用MQTT客戶端軟件模擬真實的設(shè)備來登錄平臺。測試與服務(wù)器通信是否正常。
(1)填入登錄信息
打開MQTT客戶端軟件,對號填入相關(guān)信息(就是上面的文本介紹)。然后,點擊登錄,訂閱主題,發(fā)布主題。
(2)打開網(wǎng)頁查看
完成上面的操作之后,打開華為云網(wǎng)頁后臺,可以看到設(shè)備已經(jīng)在線了。
點擊詳情頁面,可以看到上傳的數(shù)據(jù)。
到此,云平臺的部署已經(jīng)完成,設(shè)備已經(jīng)可以正常上傳數(shù)據(jù)了。
四、上位機(jī)開發(fā)
為了方便查看設(shè)備上傳的數(shù)據(jù),對設(shè)備進(jìn)行遠(yuǎn)程控制,接下來利用Qt開發(fā)一款A(yù)ndroid和windows系統(tǒng)的上位機(jī)。
使用華為云平臺提供的API接口獲取設(shè)備上傳的數(shù)據(jù),也可以給設(shè)備下發(fā)指令,控制設(shè)備。
為了方便查看設(shè)備上傳的數(shù)據(jù),對設(shè)備進(jìn)行遠(yuǎn)程控制,接下來利用Qt開發(fā)一款A(yù)ndroid和windows系統(tǒng)的上位機(jī)。
使用華為云平臺提供的API接口獲取設(shè)備上傳的數(shù)據(jù),也可以給設(shè)備下發(fā)指令,控制設(shè)備。
4.1 Qt開發(fā)環(huán)境安裝
Qt的中文官網(wǎng): https://www.qt.io/zh-cn/
QT5.12.6的下載地址:https://download.qt.io/archive/qt/5.12/5.12.6
打開下載鏈接后選擇下面的版本進(jìn)行下載:
qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details
軟件安裝時斷網(wǎng)安裝,否則會提示輸入賬戶。
安裝的時候,第一個復(fù)選框里勾選一個mingw 32
編譯器即可,其他的不管默認(rèn)就行,直接點擊下一步繼續(xù)安裝。
說明: 我這里只是介紹PC端的環(huán)境搭建(這個比較簡單)。 Android的開發(fā)環(huán)境比較麻煩,可以去我的博客里看詳細(xì)文章。
選擇MinGW 32-bit 編譯器:
4.2 創(chuàng)建IAM賬戶
創(chuàng)建一個IAM賬戶,因為接下來開發(fā)上位機(jī),需要使用云平臺的API接口,這些接口都需要token進(jìn)行鑒權(quán)。簡單來說,就是身份的認(rèn)證。 調(diào)用接口獲取Token時,就需要填寫IAM賬號信息。所以,接下來演示一下過程。
地址: https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users
獲取Token時,除了AIM賬號外,還需要項目憑證:
faa0973835ab409ab48182e2590f4ad3
鼠標(biāo)點擊自己昵稱,點擊統(tǒng)一身份認(rèn)證。
點擊左上角創(chuàng)建用戶
。
創(chuàng)建成功:
4.3 獲取影子數(shù)據(jù)
幫助文檔:https://support.huaweicloud.com/api-iothub/iot_06_v5_0079.html
設(shè)備影子介紹:
設(shè)備影子是一個用于存儲和檢索設(shè)備當(dāng)前狀態(tài)信息的JSON文檔。
每個設(shè)備有且只有一個設(shè)備影子,由設(shè)備ID唯一標(biāo)識
設(shè)備影子僅保存最近一次設(shè)備的上報數(shù)據(jù)和預(yù)期數(shù)據(jù)
無論該設(shè)備是否在線,都可以通過該影子獲取和設(shè)置設(shè)備的屬性
簡單來說:設(shè)備影子就是保存,設(shè)備最新上傳的一次數(shù)據(jù)。
我們設(shè)計的軟件里,如果想要獲取設(shè)備的最新狀態(tài)信息,就采用設(shè)備影子接口。
如果對接口不熟悉,可以先進(jìn)行在線調(diào)試:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow
在線調(diào)試接口,可以請求影子接口,了解請求,與返回的數(shù)據(jù)格式。
設(shè)備影子接口返回的數(shù)據(jù)如下:
{
"device_id": "6419627e40773741f9fbdac7_dev1",
"shadow": [
{
"service_id": "stm32",
"desired": {
"properties": null,
"event_time": null
},
"reported": {
"properties": {
"DS18B20": 18,
"motor_water": 1,
"motor_oxygen": 1,
"temp_max": 10,
"water_hp": 130,
"motor_food": 0,
"time_food": 0,
"oxygen_food": 3
},
"event_time": "20230321T081126Z"
},
"version": 0
}
]
}
4.4 修改設(shè)備屬性
地址: https://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html
接口說明
設(shè)備的產(chǎn)品模型中定義了物聯(lián)網(wǎng)平臺可向設(shè)備下發(fā)的屬性,應(yīng)用服務(wù)器可調(diào)用此接口向指定設(shè)備下發(fā)屬性。平臺負(fù)責(zé)將屬性以同步方式發(fā)送給設(shè)備,并將設(shè)備執(zhí)行屬性結(jié)果同步返回。
修改設(shè)備屬性的接口,可以讓服務(wù)器給設(shè)備下發(fā)指令,如果需要控制設(shè)備。
在線調(diào)試地址:
https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=UpdateProperties
修改設(shè)備屬性是屬于同步命令,需要設(shè)備在線才可以進(jìn)行調(diào)試,先使用MQTT客戶端登錄服務(wù)器,模擬設(shè)備上線。
然后進(jìn)行調(diào)試,測試數(shù)據(jù)遠(yuǎn)程下發(fā)給設(shè)備。
【1】利用MQTT客戶端先登錄設(shè)備 (這是同步命令,必須在線才能調(diào)試)
【2】點擊調(diào)試
{"services":{"temp_max":100}}
【4】可以看到,MQTT客戶端軟件上已經(jīng)收到了服務(wù)器下發(fā)的消息
由于是同步命令,服務(wù)器必須要收到設(shè)備的響應(yīng)才能順利完成一個流程,設(shè)備響應(yīng)了服務(wù)器才能確定數(shù)據(jù)下發(fā)成功。
MQTT設(shè)備端如何響應(yīng)呢?
設(shè)備響應(yīng)格式說明:https://support.huaweicloud.com/api-iothub/iot_06_v5_3008.html
下面進(jìn)行實操:
當(dāng)服務(wù)器通過在線調(diào)試,發(fā)送指令下來之后,客戶端將請求ID復(fù)制下來,添加到發(fā)布主題的格式里,再回復(fù)回去,服務(wù)器收到了響應(yīng),一次屬性修改就完美完成了。
就是成功的狀態(tài):
**下面是請求的總結(jié): ** (響應(yīng)服務(wù)器的修改設(shè)備屬性請求)
上報主題的格式:$oc/devices/{device_id}/sys/properties/set/response/request_id=
$oc/devices/6419627e40773741f9fbdac7_dev1/sys/properties/set/response/request_id=
響應(yīng)的數(shù)據(jù):
{"result_code": 0,"result_desc": "success"}
4.5 設(shè)計上位機(jī)
前面2講解了需要用的API接口,接下來就使用Qt設(shè)計上位機(jī),設(shè)計界面,完成整體上位機(jī)的邏輯設(shè)計。
【1】新建Qt工程
選擇工程路徑,放在英文路徑下。
創(chuàng)建完畢。
新建Android的模板:
【2】界面設(shè)計
【4】代碼設(shè)計:配置參數(shù)讀取與保存
/*
功能: 保存數(shù)據(jù)到文件
*/
void Widget::SaveDataToFile(QString text)
{
/*保存數(shù)據(jù)到文件,方便下次加載*/
QString file;
file=QCoreApplication::applicationDirPath()+"/"+ConfigFile;
QFile filesrc(file);
filesrc.open(QIODevice::WriteOnly);
QDataStream out(&filesrc);
out << text; //序列化寫字符串
filesrc.flush();
filesrc.close();
}
/*
功能: 從文件讀取數(shù)據(jù)
*/
QString Widget::ReadDataFile(void)
{
//讀取配置文件
QString text,data;
text=QCoreApplication::applicationDirPath()+"/"+ConfigFile;
//判斷文件是否存在
if(QFile::exists(text))
{
QFile filenew(text);
filenew.open(QIODevice::ReadOnly);
QDataStream in(&filenew); // 從文件讀取序列化數(shù)據(jù)
in >> data; //提取寫入的數(shù)據(jù)
filenew.close();
}
return data; //返回值讀取的值
}
【3】代碼設(shè)計:云端數(shù)據(jù)解析
//解析反饋結(jié)果
void Widget::replyFinished(QNetworkReply *reply)
{
QString displayInfo;
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
//讀取所有數(shù)據(jù)
QByteArray replyData = reply->readAll();
qDebug()<<"狀態(tài)碼:"<<statusCode;
qDebug()<<"反饋的數(shù)據(jù):"<<QString(replyData);
//更新token
if(function_select==3)
{
displayInfo="token 更新失敗.";
//讀取HTTP響應(yīng)頭的數(shù)據(jù)
QList<QNetworkReply::RawHeaderPair> RawHeader=reply->rawHeaderPairs();
qDebug()<<"HTTP響應(yīng)頭數(shù)量:"<<RawHeader.size();
for(int i=0;i<RawHeader.size();i++)
{
QString first=RawHeader.at(i).first;
QString second=RawHeader.at(i).second;
if(first=="X-Subject-Token")
{
Token=second.toUtf8();
displayInfo="token 更新成功.";
//保存到文件
SaveDataToFile(Token);
break;
}
}
QMessageBox::information(this,"提示",displayInfo,QMessageBox::Ok,QMessageBox::Ok);
return;
}
//判斷狀態(tài)碼
if(200 != statusCode)
{
//解析數(shù)據(jù)
QJsonParseError json_error;
QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
if(json_error.error == QJsonParseError::NoError)
{
//判斷是否是對象,然后開始解析數(shù)據(jù)
if(document.isObject())
{
QString error_str="";
QJsonObject obj = document.object();
QString error_code;
//解析錯誤代碼
if(obj.contains("error_code"))
{
error_code=obj.take("error_code").toString();
error_str+="錯誤代碼:";
error_str+=error_code;
error_str+="n";
}
if(obj.contains("error_msg"))
{
error_str+="錯誤消息:";
error_str+=obj.take("error_msg").toString();
error_str+="n";
}
//顯示錯誤代碼
QMessageBox::information(this,"提示",error_str,QMessageBox::Ok,QMessageBox::Ok);
}
}
return;
}
//設(shè)置屬性
if(function_select==12 || function_select==13)
{
//解析數(shù)據(jù)
QJsonParseError json_error;
QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
if(json_error.error == QJsonParseError::NoError)
{
//判斷是否是對象,然后開始解析數(shù)據(jù)
if(document.isObject())
{
QJsonObject obj = document.object();
if(obj.contains("response"))
{
QJsonObject obj1=obj.take("response").toObject();
int val=0;
QString success;
if(obj1.contains("result_code"))
{
val=obj1.take("result_code").toInt();
}
if(obj1.contains("result_desc"))
{
success=obj1.take("result_desc").toString();
}
if(val==0 && success =="success")
{
//顯示狀態(tài)
QMessageBox::information(this,"提示","遠(yuǎn)程命令操作完成.",QMessageBox::Ok,QMessageBox::Ok);
return;
}
else
{
//顯示狀態(tài)
QMessageBox::information(this,"提示","設(shè)備未正確回應(yīng).請檢查設(shè)備網(wǎng)絡(luò).",QMessageBox::Ok,QMessageBox::Ok);
return;
}
}
}
}
}
//查詢設(shè)備屬性
if(function_select==0)
{
//解析數(shù)據(jù)
QJsonParseError json_error;
QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
if(json_error.error == QJsonParseError::NoError)
{
//判斷是否是對象,然后開始解析數(shù)據(jù)
if(document.isObject())
{
QJsonObject obj = document.object();
if(obj.contains("shadow"))
{
QJsonArray array=obj.take("shadow").toArray();
for(int i=0;i<array.size();i++)
{
QJsonObject obj2=array.at(i).toObject();
if(obj2.contains("reported"))
{
QJsonObject obj3=obj2.take("reported").toObject();
if(obj3.contains("properties"))
{
QJsonObject properties=obj3.take("properties").toObject();
qDebug()<<"開始解析數(shù)據(jù)....";
}
}
}
}
}
}
return;
}
}
五、代碼實現(xiàn)
5.1 ESP8266連接云平臺實現(xiàn)代碼
以下是使用STM32F103ZET6和ESP8266連接華為云物聯(lián)網(wǎng)平臺,通過MQTT協(xié)議實現(xiàn)設(shè)備登錄、主題訂閱和主題發(fā)布的實現(xiàn)代碼:
#include "stm32f10x.h"
#include "stdio.h"
#include "string.h"
// 定義ESP8266的串口
USART_TypeDef* ESP_USARTx = USART1;
// 定義MQTT服務(wù)器的地址和端口
const char* MQTT_SERVER = "mqtt.eclipse.org";
const int MQTT_PORT = 1883;
// 定義設(shè)備ID和設(shè)備密碼
const char* DEVICE_ID = "your_device_id";
const char* DEVICE_PASSWORD = "your_device_password";
// 定義訂閱的主題
const char* SUBSCRIBE_TOPIC = "your_subscribe_topic";
// 定義發(fā)布的主題
const char* PUBLISH_TOPIC = "your_publish_topic";
// 定義接收緩沖區(qū)和發(fā)送緩沖區(qū)的大小
#define RX_BUFFER_SIZE 1024
#define TX_BUFFER_SIZE 1024
// 定義接收緩沖區(qū)和發(fā)送緩沖區(qū)
char rxBuffer[RX_BUFFER_SIZE];
char txBuffer[TX_BUFFER_SIZE];
// 定義接收緩沖區(qū)的索引和標(biāo)志位
volatile uint16_t rxIndex = 0;
volatile uint8_t rxComplete = 0;
// 發(fā)送數(shù)據(jù)到ESP8266
void ESP8266_SendData(const char* data) {
sprintf(txBuffer, "%srn", data);
USART_SendData(ESP_USARTx, (uint16_t)'r');
USART_SendData(ESP_USARTx, (uint16_t)'n');
USART_SendData(ESP_USARTx, (uint16_t)'r');
USART_SendData(ESP_USARTx, (uint16_t)'n');
USART_SendData(ESP_USARTx, (uint16_t)'r');
USART_SendData(ESP_USARTx, (uint16_t)'n');
USART_SendData(ESP_USARTx, (uint16_t)'r');
USART_SendData(ESP_USARTx, (uint16_t)'n');
USART_SendData(ESP_USARTx, (uint16_t)'r');
USART_SendData(ESP_USARTx, (uint16_t)'n');
USART_SendData(ESP_USARTx, (uint16_t)'r');
USART_SendData(ESP_USARTx, (uint16_t)'n');
USART_SendData(ESP_USARTx, (uint16_t)'r');
USART_SendData(ESP_USARTx, (uint16_t)'n');
USART_SendData(ESP_USARTx, (uint16_t)'r');
USART_SendData(ESP_USARTx, (uint16_t)'n');
}
// 從ESP8266接收數(shù)據(jù)
void ESP8266_ReceiveData(uint16_t size) {
while (size--) {
rxBuffer[rxIndex++] = USART_ReceiveData(ESP_USARTx);
}
if (rxIndex >= RX_BUFFER_SIZE) {
rxComplete = 1;
rxIndex = 0;
}
}
// 處理接收到的數(shù)據(jù)
void ProcessReceivedData() {
// TODO: 根據(jù)接收到的數(shù)據(jù)進(jìn)行處理
}
// ESP8266串口中斷處理函數(shù)
void USART1_IRQHandler(void) {
if (USART_GetITStatus(ESP_USARTx, USART_IT_RXNE) != RESET) {
ESP8266_ReceiveData(1);
}
}
// 連接到MQTT服務(wù)器
void MQTT_Connect() {
// 發(fā)送連接請求
sprintf(txBuffer, "AT+CIPSTART="TCP","%s",%drn", MQTT_SERVER, MQTT_PORT);
ESP8266_SendData(txBuffer);
// 等待連接成功
while (!strstr(rxBuffer, "CONNECTED")) {
if (rxComplete) {
ProcessReceivedData();
rxComplete = 0;
}
}
// 發(fā)送MQTT連接請求
sprintf(txBuffer, "AT+MQTTCONNECT="%s","%s"rn", DEVICE_ID, DEVICE_PASSWORD);
ESP8266_SendData(txBuffer);
// 等待連接成功
while (!strstr(rxBuffer, "CONNECTED")) {
if (rxComplete) {
ProcessReceivedData();
rxComplete = 0;
}
}
}
// 訂閱主題
void MQTT_Subscribe() {
sprintf(txBuffer, "AT+MQTTSUBSCRIBE="%s"rn", SUBSCRIBE_TOPIC);
ESP8266_SendData(txBuffer);
}
// 發(fā)布消息
void MQTT_Publish(const char* message) {
sprintf(txBuffer, "AT+MQTTPUBLISH="%s","%s"rn", PUBLISH_TOPIC, message);
ESP8266_SendData(txBuffer);
}
int main(void) {
// 初始化ESP8266的串口
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 115200;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(ESP_USARTx, &USART_InitStructure);
USART_Cmd(ESP_USARTx, ENABLE);
USART_ITConfig(ESP_USARTx, USART_IT_RXNE, ENABLE);
NVIC_EnableIRQ(USART1_IRQn);
// 連接到MQTT服務(wù)器
MQTT_Connect();
// 訂閱主題
MQTT_Subscribe();
while (1) {
if (rxComplete) {
ProcessReceivedData();
rxComplete = 0;
}
// TODO: 處理其他業(yè)務(wù)邏輯
// 發(fā)布消息
MQTT_Publish("Hello, MQTT!");
// 延時一段時間
delay_ms(1000);
}
}
以上代碼用于演示使用STM32F103ZET6和ESP8266連接華為云物聯(lián)網(wǎng)平臺,通過MQTT協(xié)議實現(xiàn)設(shè)備登錄、主題訂閱和主題發(fā)布的基本功能。
5.2 ESP8266的MQTT協(xié)議指令
ESP8266通過MQTT協(xié)議連接到服務(wù)器的相關(guān)AT指令主要有以下幾個:
【1】AT+CIPSTART:建立TCP連接
- 功能:使用TCP協(xié)議連接到遠(yuǎn)程服務(wù)器
- 用法:AT+CIPSTART=“TCP”,“<服務(wù)器地址>”,<服務(wù)器端口>
- 示例:AT+CIPSTART=“TCP”,“mqtt.eclipse.org”,1883
【2】AT+MQTTCONNECT:連接到MQTT服務(wù)器
- 功能:使用MQTT協(xié)議連接到MQTT服務(wù)器
- 用法:AT+MQTTCONNECT=“<設(shè)備ID>”,“<設(shè)備密碼>”
- 示例:AT+MQTTCONNECT=“your_device_id”,“your_device_password”
【3】AT+MQTTPUBLISH:發(fā)布消息
- 功能:向指定主題發(fā)布消息
- 用法:AT+MQTTPUBLISH=“<主題>”,“<消息內(nèi)容>”
- 示例:AT+MQTTPUBLISH=“your_publish_topic”,“Hello, MQTT!”
【4】AT+MQTTSUBSCRIBE:訂閱主題
- 功能:訂閱指定的主題
- 用法:AT+MQTTSUBSCRIBE=“<主題>”
- 示例:AT+MQTTSUBSCRIBE=“your_subscribe_topic”
【5】AT+CIPCLOSE:關(guān)閉TCP連接
- 功能:關(guān)閉當(dāng)前的TCP連接
- 用法:AT+CIPCLOSE
這些AT指令可以通過串口與ESP8266進(jìn)行通信,實現(xiàn)與MQTT服務(wù)器的連接、消息發(fā)布和訂閱等功能。通過這些指令,可以在嵌入式設(shè)備上實現(xiàn)與云端的通信和數(shù)據(jù)交換,從而實現(xiàn)物聯(lián)網(wǎng)應(yīng)用。
5.3 讀取DHT11傳感器的溫濕度數(shù)據(jù)
以下是使用STM32F103ZET6讀取DHT11傳感器的溫濕度數(shù)據(jù)的實現(xiàn)代碼:
#include "stm32f10x.h"
#include "dht11.h"
int main(void)
{
// 初始化DHT11傳感器
DHT11_Init();
while (1)
{
// 讀取DHT11傳感器的溫濕度數(shù)據(jù)
DHT11_Result result = DHT11_Read();
if (result.status == DHT11_OK)
{
// 溫度數(shù)據(jù)
uint8_t temperature = result.temperature;
// 濕度數(shù)據(jù)
uint8_t humidity = result.humidity;
// 在這里進(jìn)行溫濕度數(shù)據(jù)的處理和使用
// ...
// 延時一段時間后再次讀取
DelayMs(2000);
}
else
{
// 讀取失敗,可以進(jìn)行相應(yīng)的錯誤處理
// ...
}
}
}
在主函數(shù)中,通過循環(huán)不斷讀取DHT11傳感器的溫濕度數(shù)據(jù)。如果讀取成功,可以從result
結(jié)構(gòu)體中獲取溫度和濕度數(shù)據(jù),并進(jìn)行相應(yīng)的處理。如果讀取失敗,可以根據(jù)需要進(jìn)行錯誤處理。
5.4 DHT11.c和DHT11.h代碼
dht11.h:
#ifndef DHT11_H
#define DHT11_H
#include "stm32f10x.h"
typedef struct
{
uint8_t status; // 讀取狀態(tài),0表示成功,其他表示失敗
uint8_t humidity; // 濕度值
uint8_t temperature; // 溫度值
} DHT11_Result;
void DHT11_Init(void);
DHT11_Result DHT11_Read(void);
#endif
dht11.c:
#include "dht11.h"
#define DHT11_PORT GPIOA
#define DHT11_PIN GPIO_Pin_0
static void DHT11_Delay(uint32_t us)
{
uint32_t count = us * 8;
while (count--)
{
__NOP();
}
}
static void DHT11_SetOutput(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
}
static void DHT11_SetInput(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
}
static uint8_t DHT11_ReadByte(void)
{
uint8_t byte = 0;
for (uint8_t i = 0; i < 8; i++)
{
while (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN))
{
// 等待低電平結(jié)束
}
DHT11_Delay(30);
if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN))
{
byte |= (1 << (7 - i));
}
while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN))
{
// 等待高電平結(jié)束
}
}
return byte;
}
void DHT11_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
GPIO_SetBits(DHT11_PORT, DHT11_PIN);
}
DHT11_Result DHT11_Read(void)
{
DHT11_Result result;
result.status = 1;
DHT11_SetOutput();
GPIO_ResetBits(DHT11_PORT, DHT11_PIN);
DHT11_Delay(18000);
GPIO_SetBits(DHT11_PORT, DHT11_PIN);
DHT11_Delay(20);
DHT11_SetInput();
if (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN))
{
while (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN))
{
// 等待低電平結(jié)束
}
while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN))
{
// 等待高電平結(jié)束
}
uint8_t data[5];
for (uint8_t i = 0; i < 5; i++)
{
data[i] = DHT11_ReadByte();
}
uint8_t sum = data[0] + data[1] + data[2] + data[3];
if (sum == data[4])
{
result.status = 0;
result.humidity = data[0];
result.temperature = data[2];
}
}
return result;
}
dht11.h文件定義了DHT11傳感器的初始化函數(shù)DHT11_Init()
和讀取函數(shù)DHT11_Read()
,以及DHT11_Result結(jié)構(gòu)體用于存儲讀取結(jié)果。
dht11.c文件實現(xiàn)了DHT11傳感器的初始化和讀取函數(shù)。在初始化函數(shù)中,配置了DHT11引腳的GPIO模式和速度。在讀取函數(shù)中,通過發(fā)送開始信號和接收數(shù)據(jù)的方式讀取DHT11傳感器的溫濕度數(shù)據(jù),并進(jìn)行校驗。