一、前言
1.1 項(xiàng)目介紹
【1】項(xiàng)目功能介紹
隨著物聯(lián)網(wǎng)技術(shù)與移動(dòng)通信技術(shù)的快速發(fā)展,遠(yuǎn)程遙控設(shè)備在日常生活及工業(yè)應(yīng)用中的普及度日益提高。無(wú)論是家用掃地機(jī)器人實(shí)現(xiàn)自主導(dǎo)航清掃,還是目前抖音平臺(tái)上展示的實(shí)景互動(dòng)小車(chē)
等創(chuàng)新應(yīng)用,都體現(xiàn)了遠(yuǎn)程控制和實(shí)時(shí)視頻監(jiān)控技術(shù)對(duì)現(xiàn)代智能設(shè)備的重要性。
本項(xiàng)目設(shè)計(jì)并實(shí)現(xiàn)一款基于STM32微控制器的遠(yuǎn)程遙控安卓小車(chē)系統(tǒng)。該系統(tǒng)充分利用了淘汰下來(lái)的安卓舊手機(jī)作為車(chē)載信息處理單元,不僅實(shí)現(xiàn)了資源的有效再利用,還結(jié)合4G網(wǎng)絡(luò)技術(shù)以及先進(jìn)的流媒體服務(wù)和物聯(lián)網(wǎng)技術(shù),搭建起一套集遠(yuǎn)程操控、實(shí)時(shí)視頻音頻傳輸于一體的高效解決方案。
項(xiàng)目的小車(chē)搭載了STM32主控板以精確控制四個(gè)電機(jī)的動(dòng)作,通過(guò)L298N驅(qū)動(dòng)芯片確保了底座移動(dòng)的穩(wěn)定性和靈活性。同時(shí),小車(chē)的動(dòng)力源采用兩節(jié)18650鋰電池提供充足的電力支持。
車(chē)載的舊安卓手機(jī)通過(guò)USB線連接到STM32主控板上,接收并執(zhí)行來(lái)自遠(yuǎn)端手機(jī)APP的指令。這款由Qt開(kāi)發(fā)的Android APP能夠利用4G網(wǎng)絡(luò)實(shí)現(xiàn)實(shí)時(shí)在線,并通過(guò)攝像頭采集音視頻數(shù)據(jù),通過(guò)RTMP協(xié)議將這些數(shù)據(jù)推送到華為云ECS服務(wù)器上的NGINX流媒體服務(wù)器,從而實(shí)現(xiàn)高清流暢的遠(yuǎn)程視頻監(jiān)控。
為了實(shí)現(xiàn)雙向交互和低延遲控制,整個(gè)系統(tǒng)還借助MQTT協(xié)議連接至華為云IOT服務(wù)器。另一臺(tái)安裝了同樣由Qt開(kāi)發(fā)的Android手機(jī)APP的終端設(shè)備,可以通過(guò)該APP拉取小車(chē)端的實(shí)時(shí)音視頻流進(jìn)行播放,并通過(guò)方向鍵菜單實(shí)現(xiàn)對(duì)小車(chē)的精準(zhǔn)遠(yuǎn)程操控。這種設(shè)計(jì)不僅極大地拓展了傳統(tǒng)遙控小車(chē)的功能性與實(shí)用性,還為其他類似應(yīng)用場(chǎng)景提供了可借鑒的技術(shù)框架。
當(dāng)前設(shè)計(jì)的這種基于4G網(wǎng)絡(luò)設(shè)計(jì)的遠(yuǎn)程遙控巡檢小車(chē)
的技術(shù)應(yīng)用場(chǎng)景主要是: 安全防護(hù)、環(huán)境監(jiān)測(cè)、設(shè)備巡檢、物料搬運(yùn)、應(yīng)急救援這些地方。
(1)遠(yuǎn)程監(jiān)控:
- 可應(yīng)用于安全防護(hù)、環(huán)境監(jiān)測(cè)、農(nóng)業(yè)監(jiān)控等領(lǐng)域,例如森林防火、農(nóng)田灌溉管理、危險(xiǎn)區(qū)域偵查等,通過(guò)實(shí)時(shí)視頻和音頻傳輸,工作人員可以在遠(yuǎn)程位置對(duì)現(xiàn)場(chǎng)情況進(jìn)行實(shí)時(shí)了解和操控。
(2)工業(yè)巡檢:
- 在工廠、倉(cāng)庫(kù)或大型設(shè)施內(nèi)部署此類小車(chē),用于設(shè)備巡檢、物料搬運(yùn)或生產(chǎn)流程監(jiān)控,尤其適合那些人員不易到達(dá)或者存在安全隱患的地方。
(3)應(yīng)急救援:
- 在地震、火災(zāi)等災(zāi)害現(xiàn)場(chǎng),遠(yuǎn)程遙控小車(chē)可用于進(jìn)入倒塌建筑內(nèi)部搜尋生命跡象,或是攜帶傳感器測(cè)量有害氣體濃度等,為救援決策提供及時(shí)信息
下面是當(dāng)前小車(chē)整體技術(shù)框架:
小車(chē)的模型:
本次設(shè)計(jì)里放在小車(chē)終端上的Android手機(jī)采用的是小米4C,一款普通的Android手機(jī):
2015年上市的小米4C
。 從本身價(jià)值來(lái)講,現(xiàn)在在某魚(yú)
上差不多是200塊,本身就是一個(gè)完整的系統(tǒng)。性價(jià)比非常高。比去單獨(dú)買(mǎi)Linux開(kāi)發(fā)板進(jìn)行模型開(kāi)發(fā)實(shí)驗(yàn)來(lái)說(shuō),成本低低很多。 主流的Linux、Android開(kāi)發(fā)板價(jià)格都比較貴的。
開(kāi)發(fā)過(guò)程中,測(cè)試遙控效果:
【2】設(shè)計(jì)實(shí)現(xiàn)的功能
(1)STM32主控板功能:
- 控制4個(gè)電機(jī):STM32通過(guò)L298N驅(qū)動(dòng)芯片驅(qū)動(dòng)4個(gè)電機(jī),實(shí)現(xiàn)小車(chē)的前進(jìn)、后退、左轉(zhuǎn)、右轉(zhuǎn)等動(dòng)作。
- 數(shù)據(jù)通信:STM32通過(guò)USB接口與安卓手機(jī)通信,接收手機(jī)APP發(fā)送的控制指令,并將小車(chē)的狀態(tài)信息(如電量、速度、位置等)發(fā)送回手機(jī)。
- 電源管理:管理2節(jié)18650鋰電池的供電,確保電壓穩(wěn)定并監(jiān)控電池電量。
(2)安卓手機(jī)APP功能
- 控制指令下發(fā):手機(jī)APP通過(guò)USB接口向STM32發(fā)送控制指令,控制小車(chē)的動(dòng)作。
- 視頻和音頻流獲?。篈PP從手機(jī)攝像頭和麥克風(fēng)獲取視頻和音頻流,并進(jìn)行編碼處理。
- 流媒體推流:通過(guò)RTMP協(xié)議將編碼后的視頻和音頻流推送到華為云ECS服務(wù)器+NGINX搭建的RTMP流媒體服務(wù)器。
- MQTT連接:APP通過(guò)MQTT協(xié)議與華為云IOT服務(wù)器建立連接,實(shí)現(xiàn)雙向通信。
(3)華為云服務(wù)器功能:
- RTMP流媒體服務(wù):接收并轉(zhuǎn)發(fā)安卓手機(jī)APP推送的視頻和音頻流。
- MQTT服務(wù):作為MQTT消息代理,實(shí)現(xiàn)遠(yuǎn)程手機(jī)與STM32主控板之間的通信。
(4)遠(yuǎn)程Android手機(jī)APP功能:
- 實(shí)時(shí)視頻和音頻播放:從華為云ECS服務(wù)器拉取視頻和音頻流,并實(shí)時(shí)顯示在APP界面上。
- MQTT連接:與華為云IOT服務(wù)器建立連接,接收STM32主控板發(fā)送的小車(chē)狀態(tài)信息。
- 遠(yuǎn)程控制:提供方向鍵控制菜單,允許用戶遠(yuǎn)程控制小車(chē)前進(jìn)、后退、轉(zhuǎn)彎等動(dòng)作。
【3】項(xiàng)目硬件模塊組成
(1)電源模塊:
- 電池組:采用兩節(jié)18650鋰電池作為供電源,它們具有高能量密度、體積小的特點(diǎn),能夠?yàn)檎麄€(gè)系統(tǒng)提供穩(wěn)定的直流電能。
(2)主控模塊:
- STM32微控制器:這是整個(gè)小車(chē)的核心控制單元,負(fù)責(zé)處理所有的邏輯運(yùn)算和數(shù)據(jù)通信任務(wù)。通過(guò)編程實(shí)現(xiàn)對(duì)電機(jī)驅(qū)動(dòng)、USB通信、網(wǎng)絡(luò)連接等功能的控制。
(3)電機(jī)驅(qū)動(dòng)模塊:
- L298N驅(qū)動(dòng)芯片:用于驅(qū)動(dòng)底座上的四個(gè)電機(jī),L298N是一個(gè)高性能的H橋電機(jī)驅(qū)動(dòng)器,可以接收來(lái)自STM32的信號(hào),轉(zhuǎn)換為足夠驅(qū)動(dòng)電機(jī)工作的電流和電壓,并且支持電機(jī)正反轉(zhuǎn)及速度調(diào)節(jié)。
(4)移動(dòng)平臺(tái)模塊:
- 四個(gè)直流電機(jī):直接安裝在小車(chē)底座上,通過(guò)L298N驅(qū)動(dòng)進(jìn)行精確的速度和方向控制,以實(shí)現(xiàn)小車(chē)前進(jìn)、后退、左右轉(zhuǎn)彎等運(yùn)動(dòng)功能。
(5)通信模塊:
- USB接口:STM32主控板通過(guò)USB線與安卓手機(jī)物理連接,實(shí)現(xiàn)數(shù)據(jù)傳輸,接收來(lái)自手機(jī)APP的控制指令。
- 4G模組:集成在安卓手機(jī)內(nèi)部,插入SIM卡后可實(shí)現(xiàn)高速無(wú)線網(wǎng)絡(luò)連接,使小車(chē)能夠在遠(yuǎn)程環(huán)境下通過(guò)互聯(lián)網(wǎng)與其他設(shè)備通信。
(6)多媒體采集模塊:
- 安卓手機(jī)攝像頭:用于捕捉實(shí)時(shí)視頻和音頻信息,是小車(chē)端環(huán)境感知的關(guān)鍵組件。
(7)云服務(wù)交互模塊:
- 華為云ECS服務(wù)器+NGINX RTMP流媒體服務(wù)器:小車(chē)端將采集到的音視頻流推送到華為云服務(wù)器上,通過(guò)RTMP協(xié)議實(shí)現(xiàn)實(shí)時(shí)音視頻的低延遲傳輸和分發(fā)。
- 華為云IOT服務(wù)器:小車(chē)和遠(yuǎn)端控制手機(jī)均通過(guò)MQTT協(xié)議與之建立連接,實(shí)現(xiàn)遠(yuǎn)程數(shù)據(jù)交換和控制命令的下發(fā)。
【3】功能總結(jié)
(1)電機(jī)驅(qū)動(dòng)與控制:通過(guò)STM32微控制器和L298N驅(qū)動(dòng)芯片,實(shí)現(xiàn)對(duì)小車(chē)上四個(gè)電機(jī)的精確控制,包括前進(jìn)、后退、左轉(zhuǎn)、右轉(zhuǎn)等動(dòng)作,從而控制小車(chē)的移動(dòng)方向和速度。
(2)無(wú)線通信與數(shù)據(jù)傳輸:STM32與安卓手機(jī)之間通過(guò)USB接口建立通信,實(shí)現(xiàn)控制指令的下發(fā)和小車(chē)狀態(tài)信息的上傳。同時(shí),安卓手機(jī)通過(guò)4G網(wǎng)絡(luò)連接到華為云服務(wù)器,實(shí)現(xiàn)了遠(yuǎn)程控制命令的遠(yuǎn)程傳輸和視頻音頻流的推送。
(3)流媒體推流與播放:安卓手機(jī)APP能夠捕獲手機(jī)攝像頭和麥克風(fēng)的視頻和音頻流,通過(guò)RTMP協(xié)議推送到華為云服務(wù)器。另一臺(tái)安卓手機(jī)APP則從服務(wù)器拉取這些流,實(shí)現(xiàn)實(shí)時(shí)播放,從而允許用戶遠(yuǎn)程觀看小車(chē)的實(shí)時(shí)畫(huà)面和音頻。
(4)華為云服務(wù)器支持:華為云ECS服務(wù)器和NGINX搭建的RTMP流媒體服務(wù)器負(fù)責(zé)接收、轉(zhuǎn)發(fā)視頻和音頻流,確保流媒體的穩(wěn)定性和實(shí)時(shí)性。同時(shí),華為云IOT服務(wù)器通過(guò)MQTT協(xié)議提供消息代理服務(wù),實(shí)現(xiàn)遠(yuǎn)程手機(jī)與STM32之間的雙向通信。
(5)用戶界面與交互設(shè)計(jì):安卓手機(jī)APP提供了直觀的用戶界面,包括控制按鈕、狀態(tài)顯示、視頻播放器等,使用戶能夠方便地對(duì)小車(chē)進(jìn)行控制、觀看視頻、監(jiān)聽(tīng)音頻,以及監(jiān)控小車(chē)的狀態(tài)信息。
(6)遠(yuǎn)程控制:通過(guò)結(jié)合STM32的電機(jī)控制、華為云服務(wù)器的數(shù)據(jù)處理和傳輸,以及安卓手機(jī)的用戶界面和交互設(shè)計(jì),實(shí)現(xiàn)了從遠(yuǎn)程手機(jī)到小車(chē)的遠(yuǎn)程控制功能。用戶可以在遠(yuǎn)離小車(chē)的地點(diǎn),通過(guò)手機(jī)APP發(fā)出控制指令,實(shí)時(shí)觀察小車(chē)的動(dòng)作和周?chē)h(huán)境。
1.2 設(shè)計(jì)思路
1.3 系統(tǒng)功能總結(jié)
自主供電與移動(dòng)控制 | 采用2節(jié)18650鋰電池為小車(chē)提供電力供應(yīng);STM32微控制器結(jié)合L298N驅(qū)動(dòng)芯片,精準(zhǔn)控制4個(gè)電機(jī)動(dòng)作,實(shí)現(xiàn)前進(jìn)、后退、轉(zhuǎn)彎等移動(dòng)功能 |
---|---|
手機(jī)APP通信與指令傳輸 | STM32通過(guò)USB線與安卓手機(jī)連接,接收并解析來(lái)自手機(jī)APP的控制指令,實(shí)現(xiàn)人機(jī)交互和遠(yuǎn)程指令執(zhí)行 |
實(shí)時(shí)視頻音頻流傳輸 | 安卓手機(jī)利用4G網(wǎng)絡(luò)上網(wǎng),搭載Qt開(kāi)發(fā)的Android APP采集攝像頭視頻和音頻數(shù)據(jù),并通過(guò)RTMP協(xié)議將音視頻流推送到華為云ECS服務(wù)器+NGINX搭建的流媒體服務(wù)器 |
物聯(lián)網(wǎng)(IoT)連接與遠(yuǎn)程監(jiān)控 | 小車(chē)端及遠(yuǎn)端控制手機(jī)均通過(guò)MQTT協(xié)議連接華為云IOT服務(wù)器,實(shí)現(xiàn)車(chē)輛狀態(tài)信息實(shí)時(shí)上傳及遠(yuǎn)程音視頻流拉取顯示;遠(yuǎn)端手機(jī)APP提供方向鍵菜單以遠(yuǎn)程操控小車(chē) |
數(shù)據(jù)交互與低延遲控制 | 利用MQTT協(xié)議確保在4G網(wǎng)絡(luò)環(huán)境下高效、低延遲的數(shù)據(jù)交互,實(shí)現(xiàn)對(duì)小車(chē)的實(shí)時(shí)遠(yuǎn)程控制,提升整體系統(tǒng)的響應(yīng)速度和操作體驗(yàn) |
二、搭建視頻監(jiān)控流媒體服務(wù)器
2.1 購(gòu)買(mǎi)云服務(wù)器
如果之前沒(méi)有使用過(guò)華為云的ECS服務(wù)器,可以先申請(qǐng)?jiān)囉?個(gè)月,了解服務(wù)器的基本使用然后再購(gòu)買(mǎi),不得不說(shuō)提供這個(gè)試用服務(wù)還是非常不錯(cuò)。
產(chǎn)品試用領(lǐng)取地址: https://activity.huaweicloud.com/free_test/index.html
每天9:30開(kāi)搶,每天限量100份,這個(gè)頁(yè)面不僅有云服務(wù)器可以領(lǐng)取試用,還有云數(shù)據(jù)庫(kù)、沙盒等其他產(chǎn)品。
我這里領(lǐng)取了 2核4G S6云服務(wù)器
,這個(gè)服務(wù)器是配套華為自研25GE智能高速網(wǎng)卡,適用于網(wǎng)站和Web應(yīng)用等中輕載企業(yè)應(yīng)用。
選擇配置的時(shí)候發(fā)現(xiàn)S6規(guī)格的已經(jīng)沒(méi)有了,來(lái)晚了。
S6規(guī)格沒(méi)有了,就選擇了S3,2核,4GB的配置結(jié)算。
頁(yè)面向下翻,下面選擇系統(tǒng)預(yù)裝的系統(tǒng),我這里選擇ubuntu 20.04,安裝NGINX,來(lái)搭建流媒體服務(wù)器。
頁(yè)面繼續(xù)下翻,設(shè)置云服務(wù)器名稱,設(shè)置系統(tǒng)的root密碼。
接著就可以繼續(xù)去支付了。
購(gòu)買(mǎi)后等待一段時(shí)間,系統(tǒng)資源就即可分配完成。
2.2 登錄云服務(wù)器
云服務(wù)器的管理控制臺(tái)從這里進(jìn)入: https://www.huaweicloud.com/product/ecs.html
在官網(wǎng)主頁(yè),點(diǎn)擊產(chǎn)品,找到計(jì)算選項(xiàng),就可以看到彈性云服務(wù)器ECS,點(diǎn)擊進(jìn)去就可以看到管理控制臺(tái)的選項(xiàng)。
在彈性云服務(wù)器
的選項(xiàng)頁(yè)面可以看到剛才購(gòu)買(mǎi)的云服務(wù)器,如果點(diǎn)擊進(jìn)去提示該區(qū)域沒(méi)有可用的服務(wù)器,說(shuō)明區(qū)域選擇的不對(duì),在下面截圖紅色框框的位置可以看到可用的區(qū)域切換按鈕,切換之后就行了。
點(diǎn)擊服務(wù)器右邊的更多
,可以對(duì)服務(wù)器重裝系統(tǒng)、切換操作系統(tǒng)、關(guān)機(jī)、開(kāi)機(jī)、重啟、重置密碼等操作。
接下來(lái)先點(diǎn)擊登錄,了解一下登錄的流程,看看系統(tǒng)進(jìn)去的效果。
云服務(wù)器支持SSH協(xié)議遠(yuǎn)程登錄,可以在瀏覽器上直接使用CloudShell方式或者VNC方式登錄,如果本身你自己也是使用Linux系統(tǒng),可以在Linux系統(tǒng)里通過(guò)ssh命令直接登錄,如果是在windows下可以使用SecureCRT登錄。
其他登錄方式。
最方便的登錄方式,直接在控制臺(tái)使用VNC在瀏覽器里登錄,點(diǎn)擊立即登錄
。
根據(jù)提示輸入用戶名,密碼后,按下回車(chē)鍵即可登錄。
用戶名直接輸入root
,密碼就是剛才配置云服務(wù)器時(shí),填入的root密碼。
注意: Linux下輸入密碼默認(rèn)都是隱藏的,也就是在鍵盤(pán)上輸入密碼界面上是不會(huì)有反應(yīng)的,自己按照正確的密碼輸入即可。
用戶名、密碼輸入正確后,登錄成功。
可以點(diǎn)擊全屏模式,更好的操作。
2.3 使用CloudShell登錄云服務(wù)器
在頁(yè)面上直接點(diǎn)擊CloudShell登錄
按鈕。CloudShell方式比VNC方式方便、流暢多了,也支持命令的復(fù)制粘貼。
輸入密碼點(diǎn)擊連接。
登錄成功進(jìn)入終端。
2.4 使用SecureCRT登錄云服務(wù)器
上面演示了兩種登錄方式,都是直接在瀏覽器里登錄,這種兩種方式比較來(lái)看,VNC方式效率最低,CloudShell
相對(duì)來(lái)說(shuō)要方便很多。一般我自己正常開(kāi)發(fā)時(shí),都是使用第三方工具來(lái)登錄的,如果本身自己開(kāi)發(fā)環(huán)境的電腦就是Linux,MAC等,可以直接使用ssh命令登錄,這種方式操作流暢方便。如果在windows下,可以使用第三方軟件登錄。
我現(xiàn)在使用的系統(tǒng)是win10,在windows系統(tǒng)下有很多遠(yuǎn)程終端軟件支持SSH登錄到Linux云服務(wù)器,我當(dāng)前采用的使用SecureCRT 6.5
來(lái)作為登錄終端,登錄云服務(wù)器。
注意: SecureCRT 6.5
登錄高版本Linux系統(tǒng)會(huì)出現(xiàn)Key exchange failed
問(wèn)題,導(dǎo)致登錄失敗,比如: 登錄ubuntu 20.04 系統(tǒng)。 出現(xiàn)這種問(wèn)題需要對(duì)系統(tǒng)ssh配置文件進(jìn)行添加配置。
添加配置的流程:
命令行輸入:
vim /etc/ssh/sshd_config
在文件最后添加:
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1
保存退出。
重啟ssh服務(wù)
service ssh restart
如果不想這么麻煩的去修改配置文件,那么最簡(jiǎn)單的辦法就是: 切換操作系統(tǒng),換一個(gè)低版本的,比如:ubuntu18.04 即可。
在云服務(wù)器的控制臺(tái),找到自己的服務(wù)器,然后選擇切換操作系統(tǒng)。
根據(jù)界面上的引導(dǎo)流程,切換即可。更換新的系統(tǒng)需要1~4分鐘時(shí)間,稍微等待一下即可。
如果要使用遠(yuǎn)程SSH協(xié)議方式登錄云服務(wù)器,需要具備以下幾個(gè)前提條件。
[1]彈性云服務(wù)器狀態(tài)為“運(yùn)行中”。
[2]彈性云服務(wù)器已經(jīng)綁定彈性公網(wǎng)IP,綁定方式請(qǐng)參見(jiàn)綁定彈性公網(wǎng)IP。
[3]所在安全組入方向已開(kāi)放22端口,配置方式請(qǐng)參見(jiàn)配置安全組規(guī)則。
[4]使用的登錄工具(如PuTTY,SecureCRT`)與待登錄的彈性云服務(wù)器之間網(wǎng)絡(luò)連通。例如,默認(rèn)的22端口沒(méi)有被防火墻屏蔽。
但是這些配置不用擔(dān)心,在購(gòu)買(mǎi)服務(wù)器后,根據(jù)引導(dǎo)一套走完,上面的這些配置都已經(jīng)默認(rèn)配置好了,不用自己再去單獨(dú)配置。
系統(tǒng)切換成功后,打開(kāi)SecureCRT 6.5
軟件,進(jìn)行登錄。SecureCRT 6.5
整體而言還是挺好用的。
如果自己沒(méi)有``SecureCRT,自己下載一個(gè)即可。當(dāng)然,不一定非要使用
SecureCRT`,其他還有很多SSH遠(yuǎn)程登錄工具,喜歡哪個(gè)使用哪個(gè)。
下面介紹``SecureCRT `登錄的流程。
選擇新建會(huì)話。
選擇SSH2協(xié)議。
主機(jī)名就填服務(wù)器的公網(wǎng)IP地址,端口號(hào)默認(rèn)是22,用戶名填root。
自己云服務(wù)器的公網(wǎng)IP地址,在控制臺(tái)可以看到。
軟件點(diǎn)擊下一步之后,可以填充描述信息,備注這個(gè)鏈接是干什么的。
選擇剛才新建的協(xié)議端口點(diǎn)擊連接。
云服務(wù)器連接上之后,軟件界面會(huì)彈出對(duì)話框填充用戶名、密碼。
登錄成功的效果如下。
軟件可以配置一些選項(xiàng),讓界面符合Linux終端配色,可以修改字體大小、字體編碼等等。
配置后的效果。
2.5 安裝NFS服務(wù)器
為了方便向服務(wù)器上拷貝文件,可以采用NFS服務(wù)器、或者FTP服務(wù)器 這些方式。 我本地有一臺(tái)ubuntu 18.04 系統(tǒng)筆記本,我這里采用NFS方式拷貝文件上去。
(1)安裝NFS服務(wù)器
root@ecs-348470:~# sudo apt-get install nfs-kernel-server
(2)創(chuàng)建一個(gè)work目錄方便當(dāng)做共享目錄使用
root@ecs-348470:~# mkdir work
(3)編寫(xiě)NFS配置文件
NFS 服務(wù)的配置文件為/etc/exports,如果系統(tǒng)沒(méi)有默認(rèn)值,這個(gè)文件就不一定會(huì)存在,可以使用 vim 手動(dòng)建立,然后在文件里面寫(xiě)入配置內(nèi)容。
/home/work *(rw,no_root_squash,sync,no_subtree_check,insecure)
配置文件里參數(shù)的含義:
(4)NFS服務(wù)器相關(guān)指令
/etc/init.d/nfs-kernel-server start #啟動(dòng) NFS 服務(wù)
ufw disable #關(guān)閉防火墻
/etc/init.d/nfs-kernel-server restart #重啟NFS服務(wù)
exportfs -arv #共享路徑生效
(5)本地客戶機(jī)掛載服務(wù)器的目錄
wbyq@wbyq:~$ sudo mount -t nfs -o nolock 122.112.212.171:/home/work /home/wbyq/mnt/
(6)設(shè)置華為云服務(wù)器的安全策略
需要把華為云服務(wù)器的端口號(hào)開(kāi)放出來(lái),不然其他客戶端是無(wú)法訪問(wèn)服務(wù)器的。
我這里比較粗暴直接,直接將所有端口號(hào),所有IP地址都開(kāi)放出來(lái)了。
**(7)本地客戶機(jī)掛載服務(wù)器測(cè)試 **
掛載指令:
sudo mount -t nfs -o nolock 122.112.212.171:/home/work /home/wbyq/mnt/
2.6 安裝NGINX(配置RTMP服務(wù))
(1)下載編譯時(shí)需要依賴的一些工具
root@ecs-348470:~# sudo apt-get install build-essential libpcre3 libpcre3-dev libssl-dev
(2)下載NGINX編譯需要的軟件包
root@ecs-348470:~# mkdir nginx
root@ecs-348470:~# cd nginx/
root@ecs-348470:~# wget http://nginx.org/download/nginx-1.10.3.tar.gz
root@ecs-348470:~# wget http://zlib.net/zlib-1.2.11.tar.gz
root@ecs-348470:~# wget https://ftp.pcre.org/pub/pcre/pcre-8.40.tar.gz
root@ecs-348470:~# wget https://www.openssl.org/source/openssl-1.0.2k.tar.gz
root@ecs-348470:~# wget https://github.com/arut/nginx-rtmp-module/archive/master.zip
(3)下載后的文件全部解壓
root@ecs-348470:~# tar xvf openssl-1.0.2k.tar.gz
root@ecs-348470:~# tar xvf nginx-rtmp-module-master.tar.gz
root@ecs-348470:~# tar xvf nginx-1.8.1.tar.gz
root@ecs-348470:~# tar xvf pcre-8.40.tar.gz
root@ecs-348470:~# tar xvf zlib-1.2.11.tar.gz
(4)配置NGINX源碼,生成Makefile文件
root@ecs-348470:~# cd nginx-1.8.1/
root@ecs-348470:~# ./configure --prefix=/usr/local/nginx --with-debug --with-pcre=../pcre-8.40 --with-zlib=../zlib-1.2.11 --with-openssl=../openssl-1.0.2k --add-module=../nginx-rtmp-module-master
執(zhí)行完上一步之后,打開(kāi)objs/Makefile文件,找到-Werror選項(xiàng)并刪除。
(5)編譯并安裝NGINX
root@ecs-348470:~/nginx/nginx-1.8.1# make && make install
安裝之后NGINX的配置文件存放路徑:
/usr/local/nginx/nginx:主程序
(6)查看NGINX的版本號(hào)
root@ecs-348470:/usr/local/nginx/sbin# /usr/local/nginx/sbin/nginx -v
nginx version: nginx/1.8.1
(5)在配置文件里加入RTMP服務(wù)器的配置
root@ecs-348470:~# vim /usr/local/nginx/conf/nginx.conf
打開(kāi)文件后,在文件最后加入以下配置:
rtmp {
server {
listen 8888;
application live {
live on;
record all;
record_unique on;
record_path "./video"; #視頻緩存的路徑
record_suffix -%Y-%m-%d-%H_%M_%S.flv;
}
}
}
這樣配置之后,服務(wù)器收到RTMP流會(huì)在NGINX的當(dāng)前目錄下,創(chuàng)建一個(gè)video目錄用來(lái)緩存視頻。
客戶端向服務(wù)器推流之后,服務(wù)器就會(huì)緩存視頻到設(shè)置的目錄。
(5)檢查配置文件是否正確
root@ecs-348470:/usr/local/nginx/sbin# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
(6)NGINX常用的3個(gè)命令
sudo service nginx start
sudo service nginx stop
sudo service nginx restart
(7)啟動(dòng)NGINX服務(wù)器
sudo service nginx start
2.7 攝像頭推流音視頻到服務(wù)器
為了模擬攝像頭實(shí)時(shí)監(jiān)控推流,我這使用QT+FFMPEG編寫(xiě)了一個(gè)小軟件,在windows下運(yùn)行,推流本地筆記本電腦的數(shù)據(jù)到服務(wù)器。軟件運(yùn)行之后,將本地音頻、視頻編碼之后通過(guò)RTMP協(xié)議推流到NGINX服務(wù)器。
軟件運(yùn)行效果:
推流工具運(yùn)行過(guò)程中效果。
2.8 編寫(xiě)本地RTMP流播放器
在上面通過(guò)推流客戶端模擬攝像頭,已經(jīng)將本地的攝像頭數(shù)據(jù)實(shí)時(shí)推流到服務(wù)器了,那么還差一個(gè)播放器,為了方便能夠在任何有網(wǎng)的地方實(shí)時(shí)查看攝像頭的音頻和圖像,還需要編寫(xiě)一個(gè)RTMP流媒體播放器。
我這里的播放器內(nèi)核是采用libvlc開(kāi)發(fā)的,使用QT作為GUI框架,開(kāi)發(fā)了一個(gè)流媒體播放器,可以實(shí)時(shí)拉取服務(wù)器上的流數(shù)據(jù),并且還支持回放。因?yàn)榉?wù)器上的NGINX配置了自動(dòng)保存的參數(shù),可以將推上去的流按時(shí)間段保存下來(lái)。
輸入服務(wù)器地址之后就可以拉取流進(jìn)行播放。
點(diǎn)擊獲取回放列表,可以查看服務(wù)器上保存的歷史視頻回放播放。
三、華為云IOT服務(wù)器部署過(guò)程
在華為云IOT平臺(tái)上,需要進(jìn)行設(shè)備接入、數(shù)據(jù)模型定義、規(guī)則引擎配置和應(yīng)用開(kāi)發(fā)等四個(gè)核心模塊的開(kāi)發(fā)。其中,設(shè)備接入模塊包括設(shè)備注冊(cè)、獲取設(shè)備證書(shū)、建立連接等步驟,以保障設(shè)備與云平臺(tái)之間的安全通信;數(shù)據(jù)模型定義模塊需要根據(jù)實(shí)際需求定義相應(yīng)的數(shù)據(jù)模型,包括上傳數(shù)據(jù)格式、設(shè)備屬性和服務(wù)等。規(guī)則引擎配置模塊需要完成實(shí)時(shí)消息推送、遠(yuǎn)程控制和告警等功能。應(yīng)用開(kāi)發(fā)模塊則是將完整的智能井蓋系統(tǒng)進(jìn)行打包,為用戶提供統(tǒng)一的操作接口。
華為云官網(wǎng): https://www.huaweicloud.com/
打開(kāi)官網(wǎng),搜索物聯(lián)網(wǎng),就能快速找到 設(shè)備接入IoTDA
。
3.1 物聯(lián)網(wǎng)平臺(tái)介紹
華為云物聯(lián)網(wǎng)平臺(tái)(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)平臺(tái)構(gòu)建一個(gè)完整的物聯(lián)網(wǎng)解決方案主要包括3部分:物聯(lián)網(wǎng)平臺(tái)、業(yè)務(wù)應(yīng)用和設(shè)備。
物聯(lián)網(wǎng)平臺(tái)作為連接業(yè)務(wù)應(yīng)用和設(shè)備的中間層,屏蔽了各種復(fù)雜的設(shè)備接口,實(shí)現(xiàn)設(shè)備的快速接入;同時(shí)提供強(qiáng)大的開(kāi)放能力,支撐行業(yè)用戶構(gòu)建各種物聯(lián)網(wǎng)解決方案。
設(shè)備可以通過(guò)固網(wǎng)、2G/3G/4G/5G、NB-IoT、Wifi等多種網(wǎng)絡(luò)接入物聯(lián)網(wǎng)平臺(tái),并使用LWM2M/CoAP、MQTT、HTTPS協(xié)議將業(yè)務(wù)數(shù)據(jù)上報(bào)到平臺(tái),平臺(tái)也可以將控制命令下發(fā)給設(shè)備。
業(yè)務(wù)應(yīng)用通過(guò)調(diào)用物聯(lián)網(wǎng)平臺(tái)提供的API,實(shí)現(xiàn)設(shè)備數(shù)據(jù)采集、命令下發(fā)、設(shè)備管理等業(yè)務(wù)場(chǎng)景。
3.2 開(kāi)通物聯(lián)網(wǎng)服務(wù)
地址: https://www.huaweicloud.com/product/iothub.html
開(kāi)通標(biāo)準(zhǔn)版免費(fèi)單元。
開(kāi)通之后,點(diǎn)擊總覽
,查看接入信息。 我們當(dāng)前設(shè)備準(zhǔn)備采用MQTT協(xié)議接入華為云平臺(tái),這里可以看到MQTT協(xié)議的地址和端口號(hào)等信息。
總結(jié):
端口號(hào): MQTT (1883)| MQTTS (8883)
接入地址: a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com
根據(jù)域名地址得到IP地址信息:
Microsoft Windows [版本 10.0.19044.2846]
(c) Microsoft Corporation。保留所有權(quán)利。
C:Users11266>ping a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com
正在 Ping a3433ab133.iot-mqtts.cn-north-4.myhuaweicloud.com [121.36.42.100] 具有 32 字節(jié)的數(shù)據(jù):
來(lái)自 121.36.42.100 的回復(fù): 字節(jié)=32 時(shí)間=37ms TTL=31
來(lái)自 121.36.42.100 的回復(fù): 字節(jié)=32 時(shí)間=37ms TTL=31
來(lái)自 121.36.42.100 的回復(fù): 字節(jié)=32 時(shí)間=36ms TTL=31
來(lái)自 121.36.42.100 的回復(fù): 字節(jié)=32 時(shí)間=37ms TTL=31
121.36.42.100 的 Ping 統(tǒng)計(jì)信息:
數(shù)據(jù)包: 已發(fā)送 = 4,已接收 = 4,丟失 = 0 (0% 丟失),
往返行程的估計(jì)時(shí)間(以毫秒為單位):
最短 = 36ms,最長(zhǎng) = 37ms,平均 = 36ms
C:Users11266>
MQTT協(xié)議接入端口號(hào)有兩個(gè),1883是非加密端口,8883是證書(shū)加密端口,單片機(jī)無(wú)法加載證書(shū),所以使用1883端口比較合適。 接下來(lái)的ESP8266就采用1883端口連接華為云物聯(lián)網(wǎng)平臺(tái)。
3.3 創(chuàng)建產(chǎn)品
(1)創(chuàng)建產(chǎn)品
點(diǎn)擊右上角創(chuàng)建產(chǎn)品。
(2)填寫(xiě)產(chǎn)品信息
根據(jù)自己產(chǎn)品名字填寫(xiě),設(shè)備類型選擇自定義類型。
(3)添加自定義模型
產(chǎn)品創(chuàng)建完成之后,點(diǎn)擊進(jìn)入產(chǎn)品詳情頁(yè)面,翻到最下面可以看到模型定義。
模型簡(jiǎn)單來(lái)說(shuō): 就是存放設(shè)備上傳到云平臺(tái)的數(shù)據(jù)。比如:環(huán)境溫度、環(huán)境濕度、環(huán)境煙霧濃度、火焰檢測(cè)狀態(tài)圖等等,這些我們都可以單獨(dú)創(chuàng)建一個(gè)模型保存。
3.4 添加設(shè)備
產(chǎn)品是屬于上層的抽象模型,接下來(lái)在產(chǎn)品模型下添加實(shí)際的設(shè)備。添加的設(shè)備最終需要與真實(shí)的設(shè)備關(guān)聯(lián)在一起,完成數(shù)據(jù)交互。
(1)注冊(cè)設(shè)備
點(diǎn)擊右上角注冊(cè)設(shè)備。
(2)根據(jù)自己的設(shè)備填寫(xiě)
在彈出的對(duì)話框里填寫(xiě)自己設(shè)備的信息。根據(jù)自己設(shè)備詳細(xì)情況填寫(xiě)。
(3)保存設(shè)備信息
創(chuàng)建完畢之后,點(diǎn)擊保存并關(guān)閉,得到創(chuàng)建的設(shè)備密匙信息。該信息在后續(xù)生成MQTT三元組的時(shí)候需要使用。
比如我當(dāng)前設(shè)備的信息如下:
{
"device_id": "64000697352830580e48df07_dev1",
"secret": "12345678"
}
3.5 MQTT協(xié)議主題訂閱與發(fā)布
(1)MQTT協(xié)議介紹
當(dāng)前的設(shè)備是采用MQTT協(xié)議與華為云平臺(tái)進(jìn)行通信。
MQTT是一個(gè)物聯(lián)網(wǎng)傳輸協(xié)議,它被設(shè)計(jì)用于輕量級(jí)的發(fā)布/訂閱式消息傳輸,旨在為低帶寬和不穩(wěn)定的網(wǎng)絡(luò)環(huán)境中的物聯(lián)網(wǎng)設(shè)備提供可靠的網(wǎng)絡(luò)服務(wù)。MQTT是專門(mén)針對(duì)物聯(lián)網(wǎng)開(kāi)發(fā)的輕量級(jí)傳輸協(xié)議。MQTT協(xié)議針對(duì)低帶寬網(wǎng)絡(luò),低計(jì)算能力的設(shè)備,做了特殊的優(yōu)化,使得其能適應(yīng)各種物聯(lián)網(wǎng)應(yīng)用場(chǎng)景。目前MQTT擁有各種平臺(tái)和設(shè)備上的客戶端,已經(jīng)形成了初步的生態(tài)系統(tǒng)。
MQTT是一種消息隊(duì)列協(xié)議,使用發(fā)布/訂閱消息模式,提供一對(duì)多的消息發(fā)布,解除應(yīng)用程序耦合,相對(duì)于其他協(xié)議,開(kāi)發(fā)更簡(jiǎn)單;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é)議,就可以與華為云平臺(tái)完成通信。
華為云的MQTT協(xié)議接入幫助文檔在這里: https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
業(yè)務(wù)流程:
(2)華為云平臺(tái)MQTT協(xié)議使用限制
描述 | 限制 |
---|---|
支持的MQTT協(xié)議版本 | 3.1.1 |
與標(biāo)準(zhǔn)MQTT協(xié)議的區(qū)別 | 支持Qos 0和Qos 1支持Topic自定義不支持QoS2不支持will、retain msg |
MQTTS支持的安全等級(jí) | 采用TCP通道基礎(chǔ) + TLS協(xié)議(最高TLSv1.3版本) |
單帳號(hào)每秒最大MQTT連接請(qǐng)求數(shù) | 無(wú)限制 |
單個(gè)設(shè)備每分鐘支持的最大MQTT連接數(shù) | 1 |
單個(gè)MQTT連接每秒的吞吐量,即帶寬,包含直連設(shè)備和網(wǎng)關(guān) | 3KB/s |
MQTT單個(gè)發(fā)布消息最大長(zhǎng)度,超過(guò)此大小的發(fā)布請(qǐng)求將被直接拒絕 | 1MB |
MQTT連接心跳時(shí)間建議值 | 心跳時(shí)間限定為30至1200秒,推薦設(shè)置為120秒 |
產(chǎn)品是否支持自定義Topic | 支持 |
消息發(fā)布與訂閱 | 設(shè)備只能對(duì)自己的Topic進(jìn)行消息發(fā)布與訂閱 |
每個(gè)訂閱請(qǐng)求的最大訂閱數(shù) | 無(wú)限制 |
(3)主題訂閱格式
幫助文檔地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html
對(duì)于設(shè)備而言,一般會(huì)訂閱平臺(tái)下發(fā)消息給設(shè)備 這個(gè)主題。
設(shè)備想接收平臺(tái)下發(fā)的消息,就需要訂閱平臺(tái)下發(fā)消息給設(shè)備 的主題,訂閱后,平臺(tái)下發(fā)消息給設(shè)備,設(shè)備就會(huì)收到消息。
比如:我創(chuàng)建的設(shè)備信息如下
以當(dāng)前設(shè)備為例,最終訂閱主題的格式如下:
$oc/devices/{device_id}/sys/messages/down
最終的格式:
$oc/devices/64000697352830580e48df07_dev1/sys/messages/down
(4)主題發(fā)布格式
對(duì)于設(shè)備來(lái)說(shuō),主題發(fā)布表示向云平臺(tái)上傳數(shù)據(jù),將最新的傳感器數(shù)據(jù),設(shè)備狀態(tài)上傳到云平臺(tái)。
這個(gè)操作稱為:屬性上報(bào)。
幫助文檔地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html
根據(jù)幫助文檔的介紹, 當(dāng)前設(shè)備發(fā)布主題,上報(bào)屬性的格式總結(jié)如下:
發(fā)布的主題格式:
$oc/devices/{device_id}/sys/properties/report
最終的格式:
$oc/devices/64000697352830580e48df07_dev1/sys/properties/report
發(fā)布主題時(shí),需要上傳數(shù)據(jù),這個(gè)數(shù)據(jù)格式是JSON格式。
上傳的JSON數(shù)據(jù)格式如下:
{
"services": [
{
"service_id": <填服務(wù)ID>,
"properties": {
"<填屬性名稱1>": <填屬性值>,
"<填屬性名稱2>": <填屬性值>,
..........
}
}
]
}
根據(jù)JSON格式,一次可以上傳多個(gè)屬性字段。 這個(gè)JSON格式里的,服務(wù)ID,屬性字段名稱,屬性值類型,在前面創(chuàng)建產(chǎn)品的時(shí)候就已經(jīng)介紹了,不記得可以翻到前面去查看。
//Up, Down, Left, Right, Stop
根據(jù)這個(gè)格式,組合一次上傳的屬性數(shù)據(jù):
{"services": [{"service_id": "stm32","properties":{"Up":1,"Down":1,"Left":1,"Right":1,"Stop":1}}]}
3.6 MQTT三元組
MQTT協(xié)議登錄需要填用戶ID,設(shè)備ID,設(shè)備密碼等信息,就像我們平時(shí)登錄QQ,微信一樣要輸入賬號(hào)密碼才能登錄。MQTT協(xié)議登錄的這3個(gè)參數(shù),一般稱為MQTT三元組。
接下來(lái)介紹,華為云平臺(tái)的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ù)上面的域名和端口號(hào),得到下面的IP地址和端口號(hào)信息: 如果設(shè)備支持填寫(xiě)域名可以直接填域名,不支持就直接填寫(xiě)IP地址。 (IP地址就是域名解析得到的)
華為云的MQTT服務(wù)器地址:117.78.5.125
華為云的MQTT端口號(hào):1883
(2)生成MQTT三元組
華為云提供了一個(gè)在線工具,用來(lái)生成MQTT鑒權(quán)三元組: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打開(kāi)這個(gè)工具,填入設(shè)備的信息(也就是剛才創(chuàng)建完設(shè)備之后保存的信息),點(diǎn)擊生成,就可以得到MQTT的登錄信息了。
下面是打開(kāi)的頁(yè)面:
填入設(shè)備的信息: (上面兩行就是設(shè)備創(chuàng)建完成之后保存得到的)
直接得到三元組信息。
得到三元組之后,設(shè)備端通過(guò)MQTT協(xié)議登錄鑒權(quán)的時(shí)候,填入?yún)?shù)即可。
DeviceId 64000697352830580e48df07_dev1
DeviceSecret 12345678
ClientId 64000697352830580e48df07_dev1_0_0_2023030206
Username 64000697352830580e48df07_dev1
Password a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf449
3.7 模擬設(shè)備登錄測(cè)試
經(jīng)過(guò)上面的步驟介紹,已經(jīng)創(chuàng)建了產(chǎn)品,設(shè)備,數(shù)據(jù)模型,得到MQTT登錄信息。 接下來(lái)就用MQTT客戶端軟件模擬真實(shí)的設(shè)備來(lái)登錄平臺(tái)。測(cè)試與服務(wù)器通信是否正常。
(1)填入登錄信息
打開(kāi)MQTT客戶端軟件,對(duì)號(hào)填入相關(guān)信息(就是上面的文本介紹)。然后,點(diǎn)擊登錄,訂閱主題,發(fā)布主題。
(2)打開(kāi)網(wǎng)頁(yè)查看
完成上面的操作之后,打開(kāi)華為云網(wǎng)頁(yè)后臺(tái),可以看到設(shè)備已經(jīng)在線了。
到此,云平臺(tái)的部署已經(jīng)完成,設(shè)備已經(jīng)可以正常上傳數(shù)據(jù)了。
(3)MQTT登錄測(cè)試參數(shù)總結(jié)
IP地址:117.78.5.125
端口號(hào):1883
DeviceId 64000697352830580e48df07_dev1
DeviceSecret 12345678
ClientId 64000697352830580e48df07_dev1_0_0_2023030206
Username 64000697352830580e48df07_dev1
Password a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf449
訂閱主題:$oc/devices/64000697352830580e48df07_dev1/sys/messages/down
發(fā)布主題:$oc/devices/64000697352830580e48df07_dev1/sys/properties/report
發(fā)布的消息:{"services": [{"service_id": "stm32","properties":{"Up":1,"Down":1,"Left":1,"Right":1,"Stop":1}}]}
四、Android手機(jī)APP開(kāi)發(fā)
4.1 開(kāi)發(fā)環(huán)境介紹
在當(dāng)前項(xiàng)目中,用于遠(yuǎn)程遙控安卓小車(chē)的Android手機(jī)APP是基于Qt框架開(kāi)發(fā)的。Qt是一個(gè)功能強(qiáng)大且高度靈活的跨平臺(tái)應(yīng)用程序開(kāi)發(fā)框架,特別適合構(gòu)建具有豐富圖形用戶界面(GUI)的應(yīng)用程序,同時(shí)也支持開(kāi)發(fā)非GUI程序。在開(kāi)發(fā)這款遠(yuǎn)程遙控APP時(shí),Qt的優(yōu)勢(shì)在于其跨平臺(tái)性,使得同一份代碼可以輕松部署在不同操作系統(tǒng)平臺(tái)上,包括Android。
Android開(kāi)發(fā)必備的工具鏈包括:Java JDK 、Android SDK 、Android NDK。
NDK下載地址:https://dl.google.com/android/repository/android-ndk-r19c-linux-x86_64.zip
SDK下載: https://www.androiddevtools.cn/
JDK下載地址:https://www.oracle.com/java/technologies/javase-jdk8-downloads.html
4.2 ffmpeg介紹
說(shuō)起ffmpeg,只要是搞音視頻相關(guān)的開(kāi)發(fā)應(yīng)該都是聽(tīng)過(guò)的。FFmpeg提供了非常先進(jìn)的音頻/視頻編解碼庫(kù),并且支持跨平臺(tái)。
現(xiàn)在互聯(lián)網(wǎng)上ffmpeg相關(guān)的文章、教程也非常的多,ffmpeg本身主要是用來(lái)對(duì)視頻、音頻進(jìn)行解碼、編碼,對(duì)音視頻進(jìn)行處理。
其中主要是解碼和編碼。 解碼的應(yīng)用主要是視頻播放器制作、音樂(lè)播放器制作,解碼視頻文件得到視頻畫(huà)面再渲染顯示出來(lái)就是播放器的基本模型了。 編碼主要是用于視頻錄制保存,就是將攝像頭的畫(huà)面或者屏幕的畫(huà)面編碼后寫(xiě)入文件保存為視頻,比如:行車(chē)記錄儀錄制視頻,監(jiān)控?cái)z像頭錄制視頻等等。 當(dāng)然也可以編碼推流到服務(wù)器,現(xiàn)在的直播平臺(tái)、智能家居里的視頻監(jiān)控、智能安防攝像頭都是這樣的應(yīng)用。
在本項(xiàng)目里,通過(guò)ffmpeg技術(shù)將手機(jī)采集的視頻圖像編碼后,推流到搭建好的流媒體服務(wù)器,實(shí)現(xiàn)遠(yuǎn)程監(jiān)控。
4.3 Linux下編譯安裝ffmpeg
(1)安裝依賴項(xiàng):
sudo apt-get update
sudo apt-get install build-essential nasm yasm cmake libx264-dev libx265-dev libvpx-dev libfdk-aac-dev libmp3lame-dev libopus-dev libssl-dev
(2)下載FFmpeg源碼:
wget https://ffmpeg.org/releases/ffmpeg-4.2.2.tar.gz
tar -zxvf ffmpeg-x.y.z.tar.gz
cd ffmpeg-x.y.z
注意替換 4.2.2
為實(shí)際的版本號(hào)。
(3)配置編譯選項(xiàng):
./configure --enable-gpl --enable-libx264 --enable-libx265 --enable-libvpx --enable-libfdk-aac --enable-libmp3lame --enable-libopus --enable-nonfree
如果需要其他編碼器或功能,可以根據(jù)需要添加或修改配置選項(xiàng)。
(4)編譯和安裝:
make -j$(nproc)
sudo make install
-j$(nproc)
表示使用多個(gè)CPU核心進(jìn)行并行編譯,可以根據(jù)實(shí)際情況調(diào)整。
(5)完成后,可以通過(guò)運(yùn)行以下命令來(lái)驗(yàn)證FFmpeg是否正確安裝:
ffmpeg -version
這樣就完成了在Linux下編譯FFmpeg源碼的過(guò)程。
4.4 Qt攝像頭采集
下面代碼實(shí)現(xiàn),通過(guò)子線程采集攝像頭畫(huà)面,并通過(guò)信號(hào)槽機(jī)制將圖像傳遞給主線程顯示。
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QCamera>
#include <QCameraViewfinder>
#include <QCameraImageCapture>
#include <QThread>
class CameraThread;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_pushButton_start_clicked();
void on_pushButton_stop_clicked();
void onNewImageAvailable(const QImage &image);
private:
Ui::MainWindow *ui;
QCamera *camera;
QCameraViewfinder *viewfinder;
QCameraImageCapture *imageCapture;
CameraThread *cameraThread;
};
#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QThread>
class CameraThread : public QThread
{
Q_OBJECT
public:
explicit CameraThread(QObject *parent = nullptr);
signals:
void newImageAvailable(const QImage &image);
protected:
void run() override;
private:
QCamera *camera;
QCameraImageCapture *imageCapture;
};
CameraThread::CameraThread(QObject *parent) : QThread(parent)
{
camera = new QCamera(this);
imageCapture = new QCameraImageCapture(camera, this);
}
void CameraThread::run()
{
camera->setCaptureMode(QCamera::CaptureStillImage);
camera->start();
connect(imageCapture, &QCameraImageCapture::imageCaptured, this, [&](int id, const QImage &preview) {
emit newImageAvailable(preview);
});
exec();
}
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
camera = new QCamera(this);
viewfinder = new QCameraViewfinder(this);
imageCapture = new QCameraImageCapture(camera, this);
cameraThread = new CameraThread(this);
cameraThread->start();
connect(cameraThread, &CameraThread::newImageAvailable, this, &MainWindow::onNewImageAvailable);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_start_clicked()
{
camera->setViewfinder(viewfinder);
camera->start();
ui->verticalLayout->addWidget(viewfinder);
}
void MainWindow::on_pushButton_stop_clicked()
{
camera->stop();
ui->verticalLayout->removeWidget(viewfinder);
viewfinder->deleteLater();
}
void MainWindow::onNewImageAvailable(const QImage &image)
{
// 在這里處理接收到的圖像,例如將其顯示在 QLabel 上
ui->label_image->setPixmap(QPixmap::fromImage(image));
}
// main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
(1)CameraThread
類繼承自 QThread
,在子線程中負(fù)責(zé)采集攝像頭畫(huà)面。在 run()
方法中,首先創(chuàng)建了一個(gè) QCamera
對(duì)象和一個(gè) QCameraImageCapture
對(duì)象,然后設(shè)置攝像頭的捕獲模式為靜態(tài)圖像,啟動(dòng)攝像頭。通過(guò)連接 imageCapture
的 imageCaptured
信號(hào)到 lambda 函數(shù),當(dāng)捕獲到新圖像時(shí),將圖像通過(guò)自定義信號(hào) newImageAvailable
發(fā)送出去。
(2)MainWindow
類是程序的主窗口,其中包含了攝像頭的視圖控件、開(kāi)始按鈕、停止按鈕以及用于顯示圖像的標(biāo)簽。在構(gòu)造函數(shù)中,創(chuàng)建了攝像頭對(duì)象、視圖控件對(duì)象、圖像捕獲對(duì)象,并創(chuàng)建了一個(gè) CameraThread
對(duì)象作為子線程來(lái)處理攝像頭畫(huà)面的采集。
(3)當(dāng)用戶點(diǎn)擊開(kāi)始按鈕時(shí),調(diào)用 on_pushButton_start_clicked()
槽函數(shù),將攝像頭視圖控件添加到界面上并啟動(dòng)攝像頭,開(kāi)始顯示攝像頭畫(huà)面。
(4)當(dāng)用戶點(diǎn)擊停止按鈕時(shí),調(diào)用 on_pushButton_stop_clicked()
槽函數(shù),停止攝像頭捕獲并移除視圖控件。
(5)當(dāng)子線程采集到新的圖像時(shí),通過(guò) onNewImageAvailable()
槽函數(shù)接收到圖像,并在標(biāo)簽上顯示該圖像。
(6)main.cpp
文件是程序的入口,創(chuàng)建了 QApplication
對(duì)象和 MainWindow
對(duì)象,并執(zhí)行主事件循環(huán)。
4.6 ffmpeg視頻編碼推流代碼
使用Qt(C++)結(jié)合FFmpeg庫(kù)來(lái)采集攝像頭畫(huà)面,進(jìn)行編碼,并通過(guò)子線程將視頻推送到RTMP流媒體服務(wù)器。
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QThread>
extern "C" {
#include <libavformat/avformat.h>
#include <libavdevice/avdevice.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
}
class VideoCaptureThread;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_pushButton_start_clicked();
void on_pushButton_stop_clicked();
void onNewFrameAvailable(const QImage &frame);
private:
Ui::MainWindow *ui;
VideoCaptureThread *videoCaptureThread;
};
#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
class VideoCaptureThread : public QThread
{
Q_OBJECT
public:
explicit VideoCaptureThread(QObject *parent = nullptr);
~VideoCaptureThread();
protected:
void run() override;
signals:
void newFrameAvailable(const QImage &frame);
private:
AVFormatContext *formatContext;
AVCodecContext *codecContext;
AVStream *videoStream;
SwsContext *swsContext;
bool stop;
};
VideoCaptureThread::VideoCaptureThread(QObject *parent) : QThread(parent), stop(false)
{
avformat_network_init();
formatContext = avformat_alloc_context();
AVInputFormat *inputFormat = av_find_input_format("dshow");
avformat_open_input(&formatContext, "video=YourCameraDevice", inputFormat, NULL);
// 省略了初始化視頻編碼器的部分,需要根據(jù)實(shí)際情況添加
swsContext = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,
codecContext->width, codecContext->height, AV_PIX_FMT_RGB32,
SWS_BICUBIC, NULL, NULL, NULL);
}
VideoCaptureThread::~VideoCaptureThread()
{
stop = true;
wait();
sws_freeContext(swsContext);
avcodec_free_context(&codecContext);
avformat_close_input(&formatContext);
avformat_free_context(formatContext);
}
void VideoCaptureThread::run()
{
while (!stop) {
AVPacket packet;
av_init_packet(&packet);
if (av_read_frame(formatContext, &packet) >= 0) {
// 省略了視頻編碼的部分,需要根據(jù)實(shí)際情況添加
QImage frameImage(codecContext->width, codecContext->height, QImage::Format_RGB32);
sws_scale(swsContext, codecContext->coded_frame->data, codecContext->coded_frame->linesize,
0, codecContext->height, reinterpret_cast<uint8_t **>(frameImage.bits()), frameImage.bytesPerLine());
emit newFrameAvailable(frameImage);
}
av_packet_unref(&packet);
}
}
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
videoCaptureThread = new VideoCaptureThread(this);
connect(videoCaptureThread, &VideoCaptureThread::newFrameAvailable, this, &MainWindow::onNewFrameAvailable);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_start_clicked()
{
videoCaptureThread->start();
}
void MainWindow::on_pushButton_stop_clicked()
{
videoCaptureThread->quit();
}
void MainWindow::onNewFrameAvailable(const QImage &frame)
{
// 將QImage轉(zhuǎn)換為AVFrame
AVFrame *avFrame = av_frame_alloc();
avFrame->width = frame.width();
avFrame->height = frame.height();
avFrame->format = AV_PIX_FMT_RGB32;
av_frame_get_buffer(avFrame, 0);
for (int y = 0; y < frame.height(); ++y) {
memcpy(avFrame->data[0] + y * avFrame->linesize[0], frame.scanLine(y), frame.width() * 4);
}
// 編碼視頻幀
AVPacket packet;
av_init_packet(&packet);
int ret = avcodec_send_frame(codecContext, avFrame);
if (ret < 0) {
qDebug() << "Failed to send frame to encoder";
return;
}
while (ret >= 0) {
ret = avcodec_receive_packet(codecContext, &packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
qDebug() << "Error during encoding";
return;
}
// 推送到RTMP服務(wù)器
RTMP *rtmp = RTMP_Alloc();
RTMP_Init(rtmp);
if (!RTMP_SetupURL(rtmp, "rtmp://your_rtmp_server_url")) {
qDebug() << "Failed to set RTMP URL";
RTMP_Close(rtmp);
RTMP_Free(rtmp);
return;
}
RTMP_EnableWrite(rtmp);
if (!RTMP_Connect(rtmp, NULL)) {
qDebug() << "Failed to connect to RTMP server";
RTMP_Close(rtmp);
RTMP_Free(rtmp);
return;
}
if (!RTMP_ConnectStream(rtmp, 0)) {
qDebug() << "Failed to connect to RTMP stream";
RTMP_Close(rtmp);
RTMP_Free(rtmp);
return;
}
packet.stream_index = videoStream->index;
ret = RTMP_SendPacket(rtmp, reinterpret_cast<char*>(packet.data), packet.size, TRUE);
if (ret < 0) {
qDebug() << "Failed to send packet to RTMP server";
RTMP_Close(rtmp);
RTMP_Free(rtmp);
return;
}
av_packet_unref(&packet);
RTMP_Close(rtmp);
RTMP_Free(rtmp);
}
av_frame_free(&avFrame);
}
代碼中創(chuàng)建了一個(gè) VideoCaptureThread
類作為子線程,負(fù)責(zé)采集攝像頭畫(huà)面并進(jìn)行視頻編碼。在 run()
方法中,利用FFmpeg庫(kù)讀取攝像頭畫(huà)面,進(jìn)行視頻編碼,并通過(guò)自定義信號(hào) newFrameAvailable
發(fā)送每一幀圖像。主界面中的 MainWindow
類負(fù)責(zé)開(kāi)始和停止視頻采集線程,并處理接收到的視頻幀,可以在 onNewFrameAvailable()
槽函數(shù)中將視頻幀編碼為RTMP流并推送到服務(wù)器。將QImage
轉(zhuǎn)換為AVFrame
,使用avcodec_send_frame
和avcodec_receive_packet
函數(shù)對(duì)視頻幀進(jìn)行編碼。創(chuàng)建一個(gè)RTMP連接,并將編碼后的視頻包發(fā)送到RTMP服務(wù)器。
五、STM32端代碼設(shè)計(jì)
STM32端的代碼主要是控制小車(chē)的移動(dòng),代碼比較少。
5.1 STM32小車(chē)底座驅(qū)動(dòng)代碼
#include "stm32f10x.h"
#define MOTOR1_PIN1 GPIO_Pin_0
#define MOTOR1_PIN2 GPIO_Pin_1
#define MOTOR2_PIN1 GPIO_Pin_2
#define MOTOR2_PIN2 GPIO_Pin_3
#define MOTOR3_PIN1 GPIO_Pin_4
#define MOTOR3_PIN2 GPIO_Pin_5
#define MOTOR4_PIN1 GPIO_Pin_6
#define MOTOR4_PIN2 GPIO_Pin_7
void delay_ms(uint32_t ms) {
uint32_t i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 7200; j++);
}
void motor_init() {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = MOTOR1_PIN1 | MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN1 | MOTOR3_PIN2 | MOTOR4_PIN1 | MOTOR4_PIN2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void forward() {
GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2);
GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1);
}
void backward() {
GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1);
GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2);
}
void left() {
GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2);
GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1);
}
void right() {
GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1);
GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2);
}
int main(void) {
motor_init();
while (1) {
forward();
delay_ms(1000);
backward();
delay_ms(1000);
left();
delay_ms(1000);
right();
delay_ms(1000);
}
}
5.2 小車(chē)控制代碼
#include "stm32f10x.h"
#include <stdio.h>
#include <string.h>
#define MOTOR1_PIN1 GPIO_Pin_0
#define MOTOR1_PIN2 GPIO_Pin_1
#define MOTOR2_PIN1 GPIO_Pin_2
#define MOTOR2_PIN2 GPIO_Pin_3
#define MOTOR3_PIN1 GPIO_Pin_4
#define MOTOR3_PIN2 GPIO_Pin_5
#define MOTOR4_PIN1 GPIO_Pin_6
#define MOTOR4_PIN2 GPIO_Pin_7
void delay_ms(uint32_t ms) {
uint32_t i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 7200; j++);
}
void motor_init() {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = MOTOR1_PIN1 | MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN1 | MOTOR3_PIN2 | MOTOR4_PIN1 | MOTOR4_PIN2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void forward() {
GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2);
GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1);
}
void backward() {
GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN1);
GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN2);
}
void left() {
GPIO_ResetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2);
GPIO_SetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1);
}
void right() {
GPIO_ResetBits(GPIOA, MOTOR1_PIN1 | MOTOR2_PIN2 | MOTOR3_PIN2 | MOTOR4_PIN1);
GPIO_SetBits(GPIOA, MOTOR1_PIN2 | MOTOR2_PIN1 | MOTOR3_PIN1 | MOTOR4_PIN2);
}
void usart_init() {
USART_InitTypeDef USART_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
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(USART1, &USART_InitStructure);
USART_Cmd(USART1, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
void usart_send(USART_TypeDef* USARTx, uint8_t data) {
while (USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET);
USART_SendData(USARTx, data);
}
uint8_t usart_receive(USART_TypeDef* USARTx) {
while (USART_GetFlagStatus(USARTx, USART_FLAG_RXNE) == RESET);
return USART_ReceiveData(USARTx);
}
void usart_puts(USART_TypeDef* USARTx, char* str) {
while (*str) {
usart_send(USARTx, *str++);
}
}
void control(char* cmd) {
if (strcmp(cmd, "forward") == 0) {
forward();
usart_puts(USART1, "OKn");
} else if (strcmp(cmd, "backward") == 0) {
backward();
usart_puts(USART1, "OKn");
} else if (strcmp(cmd, "left") == 0) {
left();
usart_puts(USART1, "OKn");
} else if (strcmp(cmd, "right") == 0) {
right();
usart_puts(USART1, "OKn");
} else {
usart_puts(USART1, "Invalid commandn");
}
}
int main(void) {
motor_init();
usart_init();
while (1) {
char cmd[10];
memset(cmd, 0, sizeof(cmd));
int i = 0;
while (1) {
char c = usart_receive(USART1);
if (c == 'r' || c == 'n') {
break;
}
cmd[i++] = c;
}
control(cmd);
}
}
六、關(guān)于Android手機(jī)USB通信的問(wèn)題
在Qt中開(kāi)發(fā)Android手機(jī)APP并利用USB線進(jìn)行串口通信,需要啟用權(quán)限。
(1)添加權(quán)限:在AndroidManifest.xml文件中添加USB權(quán)限,并在Qt項(xiàng)目中的Android配置文件中聲明需要的權(quán)限。例如,在AndroidManifest.xml中添加以下代碼:
<uses-permission android:name="android.permission.USB_PERMISSION" />
(2)檢測(cè)USB連接:通過(guò)Qt的Android JNI接口(Java Native Interface)來(lái)檢測(cè)USB設(shè)備的插拔狀態(tài),并獲取USB設(shè)備的信息。
(3)打開(kāi)和關(guān)閉USB串口:使用Qt的QSerialPort類來(lái)打開(kāi)和關(guān)閉USB串口,并進(jìn)行數(shù)據(jù)的讀寫(xiě)操作??梢酝ㄟ^(guò)檢測(cè)到的USB設(shè)備路徑來(lái)打開(kāi)對(duì)應(yīng)的串口。
(4)處理串口數(shù)據(jù):接收到的串口數(shù)據(jù)可以通過(guò)信號(hào)槽機(jī)制或者其他方式傳遞給界面進(jìn)行顯示或進(jìn)一步處理。
下面是測(cè)試的代碼:
#include <QSerialPort>
#include <QSerialPortInfo>
void detectUsbDevices() {
QList<QSerialPortInfo> usbDevices = QSerialPortInfo::availablePorts();
foreach (const QSerialPortInfo &info, usbDevices) {
qDebug() << "USB Device Name: " << info.portName();
qDebug() << "Description: " << info.description();
}
}
void openUsbSerialPort(const QString &portName) {
QSerialPort serialPort;
serialPort.setPortName(portName);
serialPort.setBaudRate(QSerialPort::Baud9600);
if (serialPort.open(QIODevice::ReadWrite)) {
qDebug() << "USB Serial Port opened successfully!";
// Read or write data here
serialPort.close();
} else {
qDebug() << "Failed to open USB Serial Port!";
}
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
// Detect USB devices
detectUsbDevices();
// Open USB serial port
openUsbSerialPort("/dev/ttyUSB0"); // Replace with the actual port name
return app.exec();
}
七、總結(jié)
本文詳細(xì)介紹了一款創(chuàng)新且環(huán)保的基于4G網(wǎng)絡(luò)設(shè)計(jì)的遠(yuǎn)程遙控安卓小車(chē)系統(tǒng)的開(kāi)發(fā)與實(shí)現(xiàn)過(guò)程。該項(xiàng)目巧妙地將被淘汰的安卓舊手機(jī)升級(jí)轉(zhuǎn)化為車(chē)載信息處理單元,賦予其新的生命力,同時(shí)融入先進(jìn)的4G網(wǎng)絡(luò)技術(shù)、流媒體服務(wù)以及物聯(lián)網(wǎng)技術(shù),成功打造出一個(gè)集遠(yuǎn)程操控與實(shí)時(shí)音視頻傳輸功能于一身的高效率解決方案。
該智能小車(chē)以其獨(dú)特的設(shè)計(jì)思路和強(qiáng)大的功能特性,展現(xiàn)出廣泛的應(yīng)用潛力。無(wú)論是作為教育科研領(lǐng)域的實(shí)踐平臺(tái),還是在遠(yuǎn)程監(jiān)控、工業(yè)巡檢、應(yīng)急救援、無(wú)人駕駛技術(shù)驗(yàn)證,甚至智能家居與物流等方面,均能發(fā)揮重要作用,顯著提升了工作效率,降低了人力成本,并有效保障了作業(yè)的安全性。
該項(xiàng)目積極響應(yīng)可持續(xù)發(fā)展號(hào)召,通過(guò)資源循環(huán)利用,成功展示了科技如何助力環(huán)保,彰顯了技術(shù)創(chuàng)新的社會(huì)價(jià)值。展望未來(lái),隨著5G網(wǎng)絡(luò)技術(shù)的廣泛應(yīng)用,這款基于4G網(wǎng)絡(luò)的遠(yuǎn)程遙控安卓小車(chē)將進(jìn)一步優(yōu)化性能,拓展應(yīng)用場(chǎng)景,為社會(huì)各領(lǐng)域帶來(lái)更加智能化、便捷化的技術(shù)服務(wù)。