想了解更多關于開源的內容,請訪問:
本站 鴻蒙開發者社區
項目名稱
丸騎行,一款幫你管理電動車的輕便APP。
如今電動車\自行車保有量巨大,停車點混亂、擁堵現象導致用車找車困難,為了給人們更好的騎行體驗,領航員1號團隊基于OpenHarmony開發了丸騎行方案。用戶可體驗遠程實時查看車輛電量、位置,遠程開關鎖、響鈴找車、續航估算等功能。
- 作品標題:丸騎行
- 軟件分類:生活類APP
- 應用領域:交通工具-電動車
- 開放源碼許可證:
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
- 文件說明
IotDevice:設備開發源碼
app/: OPenHrmony 應用
bin/ 設備開發固件
hap/ 應用Hap包
涉及的OH技術特性:
ArkUI、服務卡片、應用內web、分布式KvStore數據持久化、socket網絡通信。
1、運行條件
開發環境準備:
- 應用開發:
DevEco Studio版本:DevEco Studio 3.1.1 Release及以上版本。
OpenHarmony SDK版本:API 9, OpenHarmony 3.2 Release
- 設備開發:
- 主控芯片:RISC-V架構,Hi3861,適用于上海海思 HiSpark T1、潤和 HiHope Pegasus、小熊派 BearPI Nano。
本文使用Hihope Pegasus、BearPi Nano派驗證通過。 - OpenHarmony 版本:https://gitee.com/HiSpark/hi3861_hdu_iot_application。
- Windows環境搭建:hi3861_hdu_iot_application基于Hi3861V100和OpenHarmony. 開發指南文檔地址: /doc/物聯網設計及應用實驗指導手冊.pdf。
2、運行說明
- 操作一:準備前文所述應用開發環境,下載hap包到本地,正確編譯后上傳到DAYU800開發板;
- 操作二:給DAYU800開發板連接上可以訪問互聯網的熱點
- 操作三:【僅連接硬件需要】使用Hiburn工具下載bin文件到Hi3861開發板,使用串口工具查看當前Hi3861的IP地址
- 操作四:刷新設備狀態
- 運行APP后,右滑進入地圖頁面,等待幾秒(看網絡情況),地圖刷新出來后頁面自動刷新,看到頁面獲取到定位數據后,已同步保存到數據庫。(若GPS未開啟請點擊控件開啟定位功能。)
- 進入首頁頁面,查看對應的電量、位置等數據,因為數據寫入是異步的,若未及時刷新可點擊刷新數據控件獲取最新數據。
- 操作五: 桌面服務卡片
- 在桌面長按應用圖標,選擇添加卡片。
- 在卡片上可查看數據,當前實現了點擊對應控件或者定時刷新固定的數據,暫未與數據庫同步。
3、測試說明
演示視頻鏈接:領航員1號-智騎行
- 關于連接硬件:
- 運行APP,點擊首頁的臨時車輛,輸入Hi3861的IP地址,然后點擊wifi按鈕控件連接設備,若失敗重啟應用或者檢查IP是否正確
- 點擊開鎖、響鈴找車按鈕,硬件會做出相應動作。【需要硬件配和,具體效果看演示視頻】
- Hi3861設備默認連接的wifi信息如下
#define CONFIG_WIFI_SSID “r1” // 要連接的WiFi 熱點賬號
#define CONFIG_WIFI_PWD “88888889” // 要連接的WiFi 熱點password
#define CONFIG_CLIENT_PORT 8888 // 要連接的服務器端口
- 關于獲取定位信息
定位需要GPS模塊,為了評委測試方便,保證評審期間硬件設備24h不關機,每次運行APP時,每1s至少可獲取1次上報數據。由于地圖開放平臺限額地址逆編碼5000次/日,故在H5中默認限制逆編碼10次/運行,若不想頻繁啟動APP,可修改文件src/main/resources/rawfile/index.html如下四段的定義:
<script type="text/javascript">
var publish_topic="PilotWeb";
....
var getAddressCount = 4990 // 每次逆編碼數值+1,到5000停止地址逆編碼
....
</script>
4、技術架構
(1)APP功能框架
(2)UX/UI設計
從功能需求,設計應用交互。APP包含四個頁面,其中三個主要交互頁面在一個Tabs組件中,可點擊底部的導航bar或者左右滑動切換展示的內容,通過點擊TabContent(0)頁面中定位控件觸發Navigator導航到屏地圖頁面(WebPage.ets)。
APP主要頁面布局(文中不展示具體頁面布局代碼,主要講解UI信息與交互):
build() {
Column(){
Tabs({ barPosition: BarPosition.End, controller: this.controller }) {
TabContent() {...}
.tabBar(this.TabBuilder(0,'首頁'))
TabContent() {...}
.tabBar(this.TabBuilder(1,'地圖'))
TabContent() {...}
.tabBar(this.TabBuilder(2,'我的'))
}
.vertical(false)
.barHeight(100)
.onChange((index: number) => {
this.currentIndex = index
})
.width('100%')
.height('100%')
}
.height('100%')
.backgroundImage($r('app.media.background_lite'))
.backgroundImageSize({ width: '100%', height: '100%' })
}
為了便于區分當前的Tabs的TabContent,自定義一個Tab bar,選中時顯示不同圖標與文字效果。
@Builder TabBuilder(index: number ,name:string) {
Column() {
Image(this.currentIndex === index ? $r("app.media.bar_on") : $r("app.media.bar_off"))
.width(50)
.height(50)
.margin({ bottom: 8 })
.objectFit(ImageFit.Contain)
Text(name)
.fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor)
.fontSize(this.my_font_size)
.lineHeight(this.my_font_size+2)
}.width('100%')
}
應用首頁(Index.ets)
啟動應用后進入TabContent(0)設備主頁,這里展示用戶常用的功能和信息。
- UI信息:顯示實時電量(同步數據庫)、開關鎖、響鈴找車等控件。
- UX交互:
- 開關鎖控件,點擊觸發this.tcpSend()、this.webController.runJavaScript(‘RingOff()’)或者this.webController.runJavaScript(‘RingOn()’)函數(具體實現后文講解),設備在線時可實現消息通信(實測見第三章-功能演示);
- 響鈴找車控件,點擊觸發this.tcpSend()、this.webController.runJavaScript(‘RingOn()’)、this.webController.runJavaScript(‘RingOff()’)函數
- wifi連接控件,顯示設備近場連接狀態(socket),點擊觸發執行this.tcpConnect() 或者this.tcpSend()函數,實現近場通信。
- 位置信息控件,點擊觸發Navigator導航到大屏地圖頁面,查看可視化定位數據
- 累計騎行、預計續航控件展示里程數據,數據根據數據庫中的電量來估算。
- 點擊刷新數據控件,獲取數據庫中最新的數據,并展示到對應控件。
- 臨時車輛控件,用于連接臨時車輛,支持TCP Socket通信的車輛都可以連接。點擊時觸發執行this.dialogController.open(),打開自定義的對話框,設置目標IP,隨連隨用,IP不做持久化存儲。
地圖頁面(Index.ets)
點擊底部bar或者再右滑動到TabContent(1)可切換到車輛可視化定位頁面,包含帶標簽的地圖和定位開關。
- UI信息:可視化車輛位置、定位開關。
- UX交互:
- 點擊開啟定位按鈕,觸發執行訂閱位置信息函數this.webController.runJavaScript(‘subscribeGPS()’)
- 點擊關閉定位按鈕,觸發執行訂閱位置信息函數this.webController.runJavaScript(‘unsubscribeGPS()’)
- 文本顯示:車輛實時地址詳情
用戶個人頁面(Index.ets)
點擊底部bar或者再右滑動到TabContent(2)可切換到用戶設置頁面,可查看、設置車輛的基本信息。
- UI信息:車輛擁有者信息、車輛固定IP、序列號、固件版本號。
- UX交互:IP輸入控件,可輸入車輛IP并支持持久化保存;
大屏地圖頁面(WebPage.ets)
通過點擊TabContent(0)頁面中定位控件觸發Navigator導航到大屏地圖頁面(WebPage.ets),該頁面會加載本地web,完成地圖加載、設備上報數據的獲取。
- UI信息:車輛的地理位置,每1min自動刷新。
- UX交互:支持縮放地圖;
服務卡片
- UI信息:車輛的地理位置,每1min自動刷新。
- UX交互:當前只實現了點擊對應控件或者定時刷新固定的數據,暫未與數據庫同步。
#星計劃# 丸騎行-OpenHarmony騎行助手-鴻蒙開發者社區
(3)各功能實現
數據管理與通信連接
數據管理
為方便使用和管理數據,使用KvStore進行管理,在src/main/ets/model/KvStoreModel.ts創建了數據模板,提供KvStoreModel.createKvStore() KvStoreModel.get() KvStoreModel.put() 接口用于創建獲取數據庫數據。
例如在Index.ets中,頁面加載時先初始化。
import common from '@ohos.app.ability.common'
import { KvStoreModel } from '../model/KvStoreModel'
let kvStoreModel: KvStoreModel = new KvStoreModel()
aboutToAppear() {
let context = getContext(this) as common.UIAbilityContext
// 獲取數據庫對象
kvStoreModel.createKvStore(globalThis.context,(value)=>{
console.info('KVStore:kvStoreModel.createKvStore Callback'+value)
})
...
}
后續根據業務需求進行存取,如在點擊刷數據按鈕時,獲取傳入的數據。
// 手動刷新數據(位置+電量+續航估算)
Column() {
...
Text("刷新數據")
.fontSize(this.my_font_size)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.fontColor("black")
.maxLines(this.MAX_LINES)
.height("40%")
}.width('48%').height("100%")
.backgroundColor("#FFFFFF")
.borderRadius(15)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.onClick(()=>
{
kvStoreModel.get(Const.PILOT_POWER_KEY,(value)=>
{
this.bike_power = value
})
kvStoreModel.get(Const.PILOT_LOCATION_KEY,(value)=>
{
this.bike_location = value
})
}
在src/main/ets/common/Constant.ts定義了常用的KEY。
// KvStore存放車輛電量
static readonly PILOT_POWER_KEY: string = 'PILOT_POWER';
// KvStore存放車輛位置
static readonly PILOT_LOCATION_KEY:string = 'PILOT_LOCATION';
// KvStore存放車輛固定IP
static readonly PILOT_IP_KEY:string= 'PILOT_IP';
// KvStore存放車輛滿電續航,默認520+999 = 1519Km,用戶可根據經驗設定
static readonly PILOT_MAX_DURATION_KEY:string= 'PILOT_DURATION';
通信連接
智騎行APP支持TCP Socket、MQTT通信,用于與硬件交互數據。
Socket通信:近場連接車輛,目前可獲取車輛電量。
具體實現流程:
創建一個TCPSocket對象-->提供連接-發送-接收的接口-->根據設定的IP地址連接目標-->發送/接收數據
import socket from '@ohos.net.socket'
// 創建一個TCPSocket連接,返回一個TCPSocket對象。
let tcp = socket.constructTCPSocketInstance();
tcpInit() {
// 訂閱TCPSocket相關的訂閱事件
tcp.on('message', value => {
console.log("tcp on message")
let buffer = value.message
let dataView = new DataView(buffer)
let str = ""
for (let i = 0; i < dataView.byteLength; ++i) {
str += String.fromCharCode(dataView.getUint8(i))
}
//接收到車輛一幀數據
this.recv_rider_msg = str
//刷新電量
this.bike_power = this.recv_rider_msg.substring(19,21).toString()
console.log("tcp on connect received:" + str)
// 電量做持久化保存
kvStoreModel.put(Const.PILOT_POWER_KEY,this.bike_power)
});
}
tcpSend() {
tcp.getState().then((data) => {
if (data.isConnected) {
//發送消息
tcp.send(
{ data: this.message_send, }
).then(() => {
promptAction.showToast({message:"send message successful"})
}).catch((error) => {
promptAction.showToast({message:"send failed"})
})
} else {
promptAction.showToast({message:"tcp not connect"})
}
})
}
這里需要說明, 用戶可在我的頁面設定設備IP地址,可以持久化保存.若需要臨時連接一臺公共車輛或者調試時可以點擊臨時用車進行連接. 臨時用車通過自定義的Dialog連接,IP地址通過Link變量獲取到。
@CustomDialog
struct CustomDialogSetIP{
@State inputValue: string = ''
@Link InputIP: string // 獲取的IP
controller: CustomDialogController
cancel: () => void
confirm: () => void
....
TextInput({ placeholder: '不做存儲,隨連隨用192.168.43.164'}).width('85%').height('70%').fontSize(30)
.placeholderColor("rgb(0,0,225)")
.placeholderFont({ size: 16, weight: 100, family: 'cursive', style: FontStyle.Italic })
.onChange((value: string) => {
this.inputValue = value
})
....
}
**MQTT通信:**遠程連接車輛,獲取電量-位置信息。
實現流程:
①用戶ArkUI頁面,消息通信<-->②APP本地web頁面,發布或者訂閱消息<-->③云端服務器<-->④設備發布或者訂閱消息; // 數據通道是雙向的
用戶ArkUI頁面,消息通信<–>②APP本地web頁面的消息通信。
首先,在啟動app時,要在Index.ets的aboutToAppear()中創建一個和H5頁面通信的消息通道,實現如下:
// 注冊與H5通信的通道接口與回調
try {
// 1、創建兩個消息端口。
this.ports = this.webController.createWebMessagePorts();
// 2、在應用側的消息端口(如端口1)上注冊回調事件。
this.ports[1].onMessageEvent((result: web_view.WebMessage) => {
let msg = 'Got msg from HTML:';
if (typeof(result) === 'string') {
console.info(`received string message from html5, string is: ${result}`);
msg = result;
} else if (typeof(result) === 'object') {
if (result instanceof ArrayBuffer) {
console.info(`received arraybuffer from html5, length is: ${result.byteLength}`);
msg = msg + 'lenght is ' + result.byteLength;
} else {
console.info('not support');
}
} else {
console.info('not support');
}
this.receivedFromHtml = msg;
console.info('Callback when the first button is clicked')
kvStoreModel.put('APP','Pilot')
kvStoreModel.put(Const.PILOT_POWER_KEY,this.receivedFromHtml.substring(0,2)) //電量
kvStoreModel.put(Const.PILOT_LOCATION_KEY,this.receivedFromHtml.substring(2,8)) //位置
this.bike_power = this.receivedFromHtml.substring(0,2)
this.bike_location = this.receivedFromHtml.substring(2,8)
kvStoreModel.get(Const.PILOT_POWER_KEY,(value)=>
{
this.bike_power = value
})
kvStoreModel.get(Const.PILOT_LOCATION_KEY,(value)=>
{
this.bike_location = value
})
})
// 3、將另一個消息端口(如端口0)發送到HTML側,由HTML側保存并使用。
this.webController.postMessage('__init_port__', [this.ports[0]], '*');
} catch (error) {
console.error(`ErrorCode: ${error.code}, Message: ${error.message}`);
}
其次,需要在本地H5 src/main/resources/rawfile/index.html 中創建一個用于接收的監聽端口,具體實現如下:
// 頁面
var h5Port;
var output = document.querySelector('.output');
window.addEventListener('message', function (event) {
if (event.data === '__init_port__') {
if (event.ports[0] !== null) {
h5Port = event.ports[0]; // 1. 保存從ets側發送過來的端口
h5Port.onmessage = function (event) {
// 2. 接收ets側發送過來的消息.
var msg = 'Got message from ets:';
var result = event.data;
if (typeof(result) === 'string') {
console.info(`received string message from html5, string is: ${result}`);
msg = result;
} else if (typeof(result) === 'object') {
if (result instanceof ArrayBuffer) {
console.info(`received arraybuffer from html5, length is: ${result.byteLength}`);
msg = msg + 'lenght is ' + result.byteLength;
} else {
console.info('not support');
}
} else {
console.info('not support');
}
// this.PositionName = msg.toString();
// document.getElementById("getMsg").innerText = msg;
send(msg.toString(),"PilotWeb"); //將收到的數據通過mqtt發送到設備。ets可直接調用H5函數,該接口備用
}
}
}
})
也可以直接調用H5的runJavaScript,通過H5中的函數接口發送數據到MQTT服務器. 如響鈴找車按鈕,使用this.webController.runJavaScript()即可調用H5中的RingOff()函數.比消息發送更便捷。
// 響鈴找車
Column()
{
if(this.ring_icon_flag)
{
Image($r("app.media.ic_ring_on_filled"))
.onClick(() => {
...
// 調用H5函數,發送關閉響鈴的mqtt數據到設備
this.webController.runJavaScript('RingOff()');
})
}
}
APP本地web頁面,發布或者訂閱消息<–>③云端服務器。
在本地H5中直接實現一個MQTT實例,實現數據的交互
const options={
connectTimeout:4000,
keepalice:20,
clean:true,
clientId:'mqttjsks',
username:'hellokun',
password:'123456',
}
const client=mqtt.connect('ws://MQTT服務器的ip地址:8083/mqtt',options)
client.on('reconnect', (error) => {
// document.getElementById("status").innerText = '正在重連';
console.log('正在重連:', error)
})
client.on('error',(error)=>{
// document.getElementById("status").innerText='Faild';
console.log('connect faild:',error)
})
發送數據到設備:
前面提到響鈴找車按鈕觸發 this.webController.runJavaScript(‘RingOff()’);在H5中具體實現如下:
// 車輛關鈴
function RingOff()
{
this.send('ring_off',"PilotWeb"); // 通過MQTT發送數據
}
接收來自設備端的數據:
通信方向與發送時相反,本地的H5可以通過與ets建立的消息通道,直接發送數據到用戶頁面,這個通道也可以用來接收H5發送回來的數據.
// 使用h5Port往ets側發送消息.
function PostMsgToEts(data) {
console.info('H5 to Ets data:'+data);
if (h5Port) {
h5Port.postMessage(data);
} else {
console.error('h5Port is null, Please initialize first');
}
}
// 調用接口發送數據到ets用戶頁面,便于存儲和展示
this.PostMsgToEts(PilotPower.toString()+PositionName); // 電量+位置
可視化定位
通過MQTT服務器獲取到車輛的GPS坐標,接下來使用高德地圖開放平臺的JS API進行地圖標點和逆編碼,實現用戶在地圖上查看車輛的具體位置信息.
只需要使用Web組件,即可加載H5頁面到用戶頁面中,
// Web component loading H5.
Web({ src: $rawfile('index.html'), controller: this.webController })
在地圖頁面中,開關/定位功能是直接調用MQTT的訂閱/取消訂閱接口。
//訂閱話題
function subscribe() {
if (client.connected) {
client.subscribe(this.subscribe_topic);
// document.getElementById("status").innerText = '開始訂閱';
}
}
//取消訂閱話題
function unsubscribe() {
if (client.connected) {
client.unsubscribe(this.subscribe_topic, (error) => {
console.log(error || '取消訂閱')
// document.getElementById("status").innerText = '取消訂閱';
})
}
}
開關鎖/響鈴找車
通信連接一節講解的通信接口可實現點擊開關鎖控件,觸發x下列函數,設備在線時可實現消息通信(實測見演示視頻)。
this.tcpSend()
this.webController.runJavaScript('RingOff()')
this.webController.runJavaScript('RingOn()')
this.webController.runJavaScript('Lock()')
this.webController.runJavaScript('UnLock()')
里程數據
里程數據根據電量和用戶設定的滿電續航數據計算.獲取到電量數據后,自動計算,計算方式為:
剩余電量/總電量 = 預計續航/用戶設定最大續航
this.max_duration = value
// 使用電量預估續航,公式: 剩余電量/總電量 = 預計續航/用戶設定最大續航 數值取整
this.bike_duration = (parseInt(this.max_duration)*parseInt(this.bike_power)/100).toFixed(0)
// 累計騎行 = 最大續航-預計續航
this.bike_distance = (parseInt(this.max_duration) - parseInt(this.bike_duration)).toFixed(0)
桌面服務卡片
創建一張2*4尺寸的桌面服務卡片,目前可展示里程數據和電量信息.支持定時30min自動刷新或者用戶觸發控件刷新數據. 刷新的數據目前還未與數據庫同步。
服務卡片刷新數據的方式有如圖所示幾種,智騎行APP中使用了message 和call刷新數據,使用router拉起應用。
卡片使用message與FormExtensionAbility交互介紹: 在服務卡片的獲取定位控件中,添加點擊事件,發起postCardAction message。
// 獲取定位
Column() {
Image($r("app.media.ic_statusbar_gps"))
...
Text(this.location)
...
}
.onClick(() => {
console.info('KVStore postCardAction(this')
postCardAction(this, {
'action': 'message',
'params': {
'msgTest': 'messageEvent'
}
});
})
}
在FormExtensionAbility的onFormEvent生命周期中調用updateForm接口刷新卡片。
onUpdateForm(formId) {
// 每30min自動刷新一次
let formData = {
'power': 'power', // 和卡片布局中-電量對應
'location': 'location', // 和卡片布局中-位置對應
'distance': 'distance', // 和卡片布局中-里程對應
'duration': 'duration', // 和卡片布局中-預計續航對應
'beep': 'beep.', // 和卡片布局中-響鈴找車對應
'lock': 'lock', // 和卡片布局中-開鎖對應
};
let formInfo = formBindingData.createFormBindingData(formData)
formProvider.updateForm(formId, formInfo).then((data) => {
console.info('FormAbility updateForm success.' + JSON.stringify(data));
}).catch((error) => {
console.error('FormAbility updateForm failed: ' + JSON.stringify(error));
})
}
在卡片頁面通過注冊里程數據的onClick點擊事件回調,并在回調中調用postCardAction接口觸發router事件至EntryAbility。
// 里程數據
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceAround }) {
Column() .width('47%').height("100%")
...
.onClick(() => {
....
postCardAction(this, {
'action': 'router',
'abilityName': 'EntryAbility', // 只能跳轉到當前應用下的UIAbility
'params': {
'detail': 'RouterFromCard'
}
});
})
在卡片頁面通過注冊車輛圖標的onClick點擊事件回調,并在回調中調用postCardAction接口觸發call事件至UIAbility。
// 卡片中車輛圖標
.onClick(()=>
{
postCardAction(this, {
'action': 'call',
'bundleName': 'com.example.obike',
'abilityName': 'EntryAbility', // 只能拉起當前應用下的UIAbility
'params': {
'method': 'funA',
'formId': this.formId,
'detail': 'CallFromCard'
}
});
})
車輛硬件開發
基于OpenHarmony開發電動車的控制系統,主控芯片為Hi3861。近距離時可通過TCP連接APP,遠程可通過連接GPS+4G模塊實現通信。
具體實現:Hi3861通過串口發送指令到GPS+4G模塊獲取定位信息;通過ADC采集電池電量;通過4G模塊發送到云服務器;結合前文所述通信連接,APP從應用內web端口獲取數據。
Hi3861主要任務代碼如下:
while (1) {
memset_s(recvbuf, sizeof(recvbuf), 0, sizeof(recvbuf));
if ((ret = recv(new_fd, recvbuf, sizeof(recvbuf), 0)) == -1) {
printf("recv error \r\n");
}
printf("recv :%s\r\n", recvbuf);
if(!strncmp(recvbuf,UNLOCK,6))
{
IoTGpioSetOutputVal(LockCtr_GPIO, 0);
sleep(TASK_DELAY_1S);
IoTGpioSetOutputVal(LockCtr_GPIO, 1);
}
if(!strncmp(recvbuf,LOCK,5))
{
IoTGpioSetOutputVal(LockCtr_GPIO, 1);
sleep(TASK_DELAY_1S);
IoTGpioSetOutputVal(LockCtr_GPIO, 0);
}
if(!strncmp(recvbuf,RING_ON,7))
{
IoTGpioSetOutputVal(LockCtr_GPIO, 1);
}
if(!strncmp(recvbuf,RING_OFF,8))
{
IoTGpioSetOutputVal(LockCtr_GPIO, 0);
}
// sleep(TASK_DELAY_1S);
osDelay(10); // 100ms
if ((ret = send(new_fd, buf, strlen(buf) + 1, 0)) == -1) {
perror("send : ");
}
// sleep(TASK_DELAY_1S);
}
5、展望
**作品商業價值:**萬物互聯時代,電動車智能化是趨勢,團隊基于OpenHarmony開發的智騎行方案,擁有服務卡片、定位、找車等功能,成本低易用性強。
作品進一步優化計劃:
- B12版本:實現服務卡片數據庫同步;實現BLE通信,無需網絡,近場自動連接;實現"人離車鎖,人來車開"功能;連接真實電動車;
- B241版本:實現導航功能、軌跡回放、截圖分享、歷史數據查看
- B242版本: 支持圓屏幕lite設備
想了解更多關于開源的內容,請訪問:
本站 鴻蒙開發者社區