丸騎行-OpenHarmony騎行助手
想了解更多關(guān)于開源的內(nèi)容,請(qǐng)?jiān)L問:
項(xiàng)目名稱
丸騎行,一款幫你管理電動(dòng)車的輕便APP。
如今電動(dòng)車\自行車保有量巨大,停車點(diǎn)混亂、擁堵現(xiàn)象導(dǎo)致用車找車?yán)щy,為了給人們更好的騎行體驗(yàn),領(lǐng)航員1號(hào)團(tuán)隊(duì)基于OpenHarmony開發(fā)了丸騎行方案。用戶可體驗(yàn)遠(yuǎn)程實(shí)時(shí)查看車輛電量、位置,遠(yuǎn)程開關(guān)鎖、響鈴找車、續(xù)航估算等功能。
- 作品標(biāo)題:丸騎行
- 軟件分類:生活類APP
- 應(yīng)用領(lǐng)域:交通工具-電動(dòng)車
- 開放源碼許可證:
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
- 文件說明
IotDevice:設(shè)備開發(fā)源碼
app/: OPenHrmony 應(yīng)用
bin/ 設(shè)備開發(fā)固件
hap/ 應(yīng)用Hap包
涉及的OH技術(shù)特性:
ArkUI、服務(wù)卡片、應(yīng)用內(nèi)web、分布式KvStore數(shù)據(jù)持久化、socket網(wǎng)絡(luò)通信。
1、運(yùn)行條件
開發(fā)環(huán)境準(zhǔn)備:
- 應(yīng)用開發(fā):
DevEco Studio版本:DevEco Studio 3.1.1 Release及以上版本。
OpenHarmony SDK版本:API 9, OpenHarmony 3.2 Release
- 設(shè)備開發(fā):
- 主控芯片:RISC-V架構(gòu),Hi3861,適用于上海海思 HiSpark T1、潤(rùn)和 HiHope Pegasus、小熊派 BearPI Nano。
本文使用Hihope Pegasus、BearPi Nano派驗(yàn)證通過。 - OpenHarmony 版本: https://gitee.com/HiSpark/hi3861_hdu_iot_application。
- Windows環(huán)境搭建: hi3861_hdu_iot_application基于Hi3861V100和OpenHarmony. 開發(fā)指南文檔地址: /doc/物聯(lián)網(wǎng)設(shè)計(jì)及應(yīng)用實(shí)驗(yàn)指導(dǎo)手冊(cè).pdf。
2、運(yùn)行說明
- 操作一:準(zhǔn)備前文所述應(yīng)用開發(fā)環(huán)境,下載hap包到本地,正確編譯后上傳到DAYU800開發(fā)板;
- 操作二:給DAYU800開發(fā)板連接上可以訪問互聯(lián)網(wǎng)的熱點(diǎn)
- 操作三:【僅連接硬件需要】使用Hiburn工具下載bin文件到Hi3861開發(fā)板,使用串口工具查看當(dāng)前Hi3861的IP地址
- 操作四:刷新設(shè)備狀態(tài)
- 運(yùn)行APP后,右滑進(jìn)入地圖頁面,等待幾秒(看網(wǎng)絡(luò)情況),地圖刷新出來后頁面自動(dòng)刷新,看到頁面獲取到定位數(shù)據(jù)后,已同步保存到數(shù)據(jù)庫。(若GPS未開啟請(qǐng)點(diǎn)擊控件開啟定位功能。)
- 進(jìn)入首頁頁面,查看對(duì)應(yīng)的電量、位置等數(shù)據(jù),因?yàn)閿?shù)據(jù)寫入是異步的,若未及時(shí)刷新可點(diǎn)擊刷新數(shù)據(jù)控件獲取最新數(shù)據(jù)。
- 操作五: 桌面服務(wù)卡片
- 在桌面長(zhǎng)按應(yīng)用圖標(biāo),選擇添加卡片。
- 在卡片上可查看數(shù)據(jù),當(dāng)前實(shí)現(xiàn)了點(diǎn)擊對(duì)應(yīng)控件或者定時(shí)刷新固定的數(shù)據(jù),暫未與數(shù)據(jù)庫同步。
3、測(cè)試說明
演示視頻鏈接:領(lǐng)航員1號(hào)-智騎行
- 關(guān)于連接硬件:
- 運(yùn)行APP,點(diǎn)擊首頁的臨時(shí)車輛,輸入Hi3861的IP地址,然后點(diǎn)擊wifi按鈕控件連接設(shè)備,若失敗重啟應(yīng)用或者檢查IP是否正確
- 點(diǎn)擊開鎖、響鈴找車按鈕,硬件會(huì)做出相應(yīng)動(dòng)作?!拘枰布浜?,具體效果看演示視頻】
- Hi3861設(shè)備默認(rèn)連接的wifi信息如下
#define CONFIG_WIFI_SSID “r1” // 要連接的WiFi 熱點(diǎn)賬號(hào)
#define CONFIG_WIFI_PWD “88888889” // 要連接的WiFi 熱點(diǎn)password
#define CONFIG_CLIENT_PORT 8888 // 要連接的服務(wù)器端口
- 關(guān)于獲取定位信息
定位需要GPS模塊,為了評(píng)委測(cè)試方便,保證評(píng)審期間硬件設(shè)備24h不關(guān)機(jī),每次運(yùn)行APP時(shí),每1s至少可獲取1次上報(bào)數(shù)據(jù)。由于地圖開放平臺(tái)限額地址逆編碼5000次/日,故在H5中默認(rèn)限制逆編碼10次/運(yùn)行,若不想頻繁啟動(dòng)APP,可修改文件src/main/resources/rawfile/index.html如下四段的定義:
<script type="text/javascript">
var publish_topic="PilotWeb";
....
var getAddressCount = 4990 // 每次逆編碼數(shù)值+1,到5000停止地址逆編碼
....
</script>
4、技術(shù)架構(gòu)
(1)APP功能框架
(2)UX/UI設(shè)計(jì)
從功能需求,設(shè)計(jì)應(yīng)用交互。APP包含四個(gè)頁面,其中三個(gè)主要交互頁面在一個(gè)Tabs組件中,可點(diǎn)擊底部的導(dǎo)航bar或者左右滑動(dòng)切換展示的內(nèi)容,通過點(diǎn)擊TabContent(0)頁面中定位控件觸發(fā)Navigator導(dǎo)航到屏地圖頁面(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%' })
}
為了便于區(qū)分當(dāng)前的Tabs的TabContent,自定義一個(gè)Tab bar,選中時(shí)顯示不同圖標(biāo)與文字效果。
@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%')
}
應(yīng)用首頁(Index.ets)
啟動(dòng)應(yīng)用后進(jìn)入TabContent(0)設(shè)備主頁,這里展示用戶常用的功能和信息。
- UI信息:顯示實(shí)時(shí)電量(同步數(shù)據(jù)庫)、開關(guān)鎖、響鈴找車等控件。
- UX交互:
- 開關(guān)鎖控件,點(diǎn)擊觸發(fā)this.tcpSend()、this.webController.runJavaScript(‘RingOff()’)或者this.webController.runJavaScript(‘RingOn()’)函數(shù)(具體實(shí)現(xiàn)后文講解),設(shè)備在線時(shí)可實(shí)現(xiàn)消息通信(實(shí)測(cè)見第三章-功能演示);
- 響鈴找車控件,點(diǎn)擊觸發(fā)this.tcpSend()、this.webController.runJavaScript(‘RingOn()’)、this.webController.runJavaScript(‘RingOff()’)函數(shù)
- wifi連接控件,顯示設(shè)備近場(chǎng)連接狀態(tài)(socket),點(diǎn)擊觸發(fā)執(zhí)行this.tcpConnect() 或者this.tcpSend()函數(shù),實(shí)現(xiàn)近場(chǎng)通信。
- 位置信息控件,點(diǎn)擊觸發(fā)Navigator導(dǎo)航到大屏地圖頁面,查看可視化定位數(shù)據(jù)
- 累計(jì)騎行、預(yù)計(jì)續(xù)航控件展示里程數(shù)據(jù),數(shù)據(jù)根據(jù)數(shù)據(jù)庫中的電量來估算。
- 點(diǎn)擊刷新數(shù)據(jù)控件,獲取數(shù)據(jù)庫中最新的數(shù)據(jù),并展示到對(duì)應(yīng)控件。
- 臨時(shí)車輛控件,用于連接臨時(shí)車輛,支持TCP Socket通信的車輛都可以連接。點(diǎn)擊時(shí)觸發(fā)執(zhí)行this.dialogController.open(),打開自定義的對(duì)話框,設(shè)置目標(biāo)IP,隨連隨用,IP不做持久化存儲(chǔ)。
地圖頁面(Index.ets)
點(diǎn)擊底部bar或者再右滑動(dòng)到TabContent(1)可切換到車輛可視化定位頁面,包含帶標(biāo)簽的地圖和定位開關(guān)。
- UI信息:可視化車輛位置、定位開關(guān)。
- UX交互:
- 點(diǎn)擊開啟定位按鈕,觸發(fā)執(zhí)行訂閱位置信息函數(shù)this.webController.runJavaScript(‘subscribeGPS()’)
- 點(diǎn)擊關(guān)閉定位按鈕,觸發(fā)執(zhí)行訂閱位置信息函數(shù)this.webController.runJavaScript(‘unsubscribeGPS()’)
- 文本顯示:車輛實(shí)時(shí)地址詳情
用戶個(gè)人頁面(Index.ets)
點(diǎn)擊底部bar或者再右滑動(dòng)到TabContent(2)可切換到用戶設(shè)置頁面,可查看、設(shè)置車輛的基本信息。
- UI信息:車輛擁有者信息、車輛固定IP、序列號(hào)、固件版本號(hào)。
- UX交互:IP輸入控件,可輸入車輛IP并支持持久化保存;
大屏地圖頁面(WebPage.ets)
通過點(diǎn)擊TabContent(0)頁面中定位控件觸發(fā)Navigator導(dǎo)航到大屏地圖頁面(WebPage.ets),該頁面會(huì)加載本地web,完成地圖加載、設(shè)備上報(bào)數(shù)據(jù)的獲取。
- UI信息:車輛的地理位置,每1min自動(dòng)刷新。
- UX交互:支持縮放地圖;
服務(wù)卡片
- UI信息:車輛的地理位置,每1min自動(dòng)刷新。
- UX交互:當(dāng)前只實(shí)現(xiàn)了點(diǎn)擊對(duì)應(yīng)控件或者定時(shí)刷新固定的數(shù)據(jù),暫未與數(shù)據(jù)庫同步。
#星計(jì)劃# 丸騎行-OpenHarmony騎行助手-鴻蒙開發(fā)者社區(qū)
(3)各功能實(shí)現(xiàn)
數(shù)據(jù)管理與通信連接
數(shù)據(jù)管理
為方便使用和管理數(shù)據(jù),使用KvStore進(jìn)行管理,在src/main/ets/model/KvStoreModel.ts創(chuàng)建了數(shù)據(jù)模板,提供KvStoreModel.createKvStore() KvStoreModel.get() KvStoreModel.put() 接口用于創(chuàng)建獲取數(shù)據(jù)庫數(shù)據(jù)。
例如在Index.ets中,頁面加載時(shí)先初始化。
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
// 獲取數(shù)據(jù)庫對(duì)象
kvStoreModel.createKvStore(globalThis.context,(value)=>{
console.info('KVStore:kvStoreModel.createKvStore Callback'+value)
})
...
}
后續(xù)根據(jù)業(yè)務(wù)需求進(jìn)行存取,如在點(diǎn)擊刷數(shù)據(jù)按鈕時(shí),獲取傳入的數(shù)據(jù)。
// 手動(dòng)刷新數(shù)據(jù)(位置+電量+續(xù)航估算)
Column() {
...
Text("刷新數(shù)據(jù)")
.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存放車輛滿電續(xù)航,默認(rèn)520+999 = 1519Km,用戶可根據(jù)經(jīng)驗(yàn)設(shè)定
static readonly PILOT_MAX_DURATION_KEY:string= 'PILOT_DURATION';
通信連接
智騎行APP支持TCP Socket、MQTT通信,用于與硬件交互數(shù)據(jù)。
Socket通信: 近場(chǎng)連接車輛,目前可獲取車輛電量。
具體實(shí)現(xiàn)流程:
創(chuàng)建一個(gè)TCPSocket對(duì)象-->提供連接-發(fā)送-接收的接口-->根據(jù)設(shè)定的IP地址連接目標(biāo)-->發(fā)送/接收數(shù)據(jù)
import socket from '@ohos.net.socket'
// 創(chuàng)建一個(gè)TCPSocket連接,返回一個(gè)TCPSocket對(duì)象。
let tcp = socket.constructTCPSocketInstance();
tcpInit() {
// 訂閱TCPSocket相關(guān)的訂閱事件
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))
}
//接收到車輛一幀數(shù)據(jù)
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) {
//發(fā)送消息
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"})
}
})
}
這里需要說明, 用戶可在我的頁面設(shè)定設(shè)備IP地址,可以持久化保存.若需要臨時(shí)連接一臺(tái)公共車輛或者調(diào)試時(shí)可以點(diǎn)擊臨時(shí)用車進(jìn)行連接. 臨時(shí)用車通過自定義的Dialog連接,IP地址通過Link變量獲取到。
@CustomDialog
struct CustomDialogSetIP{
@State inputValue: string = ''
@Link InputIP: string // 獲取的IP
controller: CustomDialogController
cancel: () => void
confirm: () => void
....
TextInput({ placeholder: '不做存儲(chǔ),隨連隨用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通信:**遠(yuǎn)程連接車輛,獲取電量-位置信息。
實(shí)現(xiàn)流程:
①用戶ArkUI頁面,消息通信<-->②APP本地web頁面,發(fā)布或者訂閱消息<-->③云端服務(wù)器<-->④設(shè)備發(fā)布或者訂閱消息; // 數(shù)據(jù)通道是雙向的
用戶ArkUI頁面,消息通信<–>②APP本地web頁面的消息通信。
首先,在啟動(dòng)app時(shí),要在Index.ets的aboutToAppear()中創(chuàng)建一個(gè)和H5頁面通信的消息通道,實(shí)現(xiàn)如下:
// 注冊(cè)與H5通信的通道接口與回調(diào)
try {
// 1、創(chuàng)建兩個(gè)消息端口。
this.ports = this.webController.createWebMessagePorts();
// 2、在應(yīng)用側(cè)的消息端口(如端口1)上注冊(cè)回調(diào)事件。
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、將另一個(gè)消息端口(如端口0)發(fā)送到HTML側(cè),由HTML側(cè)保存并使用。
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 中創(chuàng)建一個(gè)用于接收的監(jiān)聽端口,具體實(shí)現(xiàn)如下:
// 頁面
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側(cè)發(fā)送過來的端口
h5Port.onmessage = function (event) {
// 2. 接收ets側(cè)發(fā)送過來的消息.
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"); //將收到的數(shù)據(jù)通過mqtt發(fā)送到設(shè)備。ets可直接調(diào)用H5函數(shù),該接口備用
}
}
}
})
也可以直接調(diào)用H5的runJavaScript,通過H5中的函數(shù)接口發(fā)送數(shù)據(jù)到MQTT服務(wù)器. 如響鈴找車按鈕,使用this.webController.runJavaScript()即可調(diào)用H5中的RingOff()函數(shù).比消息發(fā)送更便捷。
// 響鈴找車
Column()
{
if(this.ring_icon_flag)
{
Image($r("app.media.ic_ring_on_filled"))
.onClick(() => {
...
// 調(diào)用H5函數(shù),發(fā)送關(guān)閉響鈴的mqtt數(shù)據(jù)到設(shè)備
this.webController.runJavaScript('RingOff()');
})
}
}
APP本地web頁面,發(fā)布或者訂閱消息<–>③云端服務(wù)器。
在本地H5中直接實(shí)現(xiàn)一個(gè)MQTT實(shí)例,實(shí)現(xiàn)數(shù)據(jù)的交互
const options={
connectTimeout:4000,
keepalice:20,
clean:true,
clientId:'mqttjsks',
username:'hellokun',
password:'123456',
}
const client=mqtt.connect('ws://MQTT服務(wù)器的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)
})
發(fā)送數(shù)據(jù)到設(shè)備:
前面提到響鈴找車按鈕觸發(fā) this.webController.runJavaScript(‘RingOff()’);在H5中具體實(shí)現(xiàn)如下:
// 車輛關(guān)鈴
function RingOff()
{
this.send('ring_off',"PilotWeb"); // 通過MQTT發(fā)送數(shù)據(jù)
}
接收來自設(shè)備端的數(shù)據(jù):
通信方向與發(fā)送時(shí)相反,本地的H5可以通過與ets建立的消息通道,直接發(fā)送數(shù)據(jù)到用戶頁面,這個(gè)通道也可以用來接收H5發(fā)送回來的數(shù)據(jù).
// 使用h5Port往ets側(cè)發(fā)送消息.
function PostMsgToEts(data) {
console.info('H5 to Ets data:'+data);
if (h5Port) {
h5Port.postMessage(data);
} else {
console.error('h5Port is null, Please initialize first');
}
}
// 調(diào)用接口發(fā)送數(shù)據(jù)到ets用戶頁面,便于存儲(chǔ)和展示
this.PostMsgToEts(PilotPower.toString()+PositionName); // 電量+位置
可視化定位
通過MQTT服務(wù)器獲取到車輛的GPS坐標(biāo),接下來使用高德地圖開放平臺(tái)的JS API進(jìn)行地圖標(biāo)點(diǎn)和逆編碼,實(shí)現(xiàn)用戶在地圖上查看車輛的具體位置信息.
只需要使用Web組件,即可加載H5頁面到用戶頁面中,
// Web component loading H5.
Web({ src: $rawfile('index.html'), controller: this.webController })
在地圖頁面中,開關(guān)/定位功能是直接調(diào)用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 = '取消訂閱';
})
}
}
開關(guān)鎖/響鈴找車
通信連接一節(jié)講解的通信接口可實(shí)現(xiàn)點(diǎn)擊開關(guān)鎖控件,觸發(fā)x下列函數(shù),設(shè)備在線時(shí)可實(shí)現(xiàn)消息通信(實(shí)測(cè)見演示視頻)。
this.tcpSend()
this.webController.runJavaScript('RingOff()')
this.webController.runJavaScript('RingOn()')
this.webController.runJavaScript('Lock()')
this.webController.runJavaScript('UnLock()')
里程數(shù)據(jù)
里程數(shù)據(jù)根據(jù)電量和用戶設(shè)定的滿電續(xù)航數(shù)據(jù)計(jì)算.獲取到電量數(shù)據(jù)后,自動(dòng)計(jì)算,計(jì)算方式為:
剩余電量/總電量 = 預(yù)計(jì)續(xù)航/用戶設(shè)定最大續(xù)航
this.max_duration = value
// 使用電量預(yù)估續(xù)航,公式: 剩余電量/總電量 = 預(yù)計(jì)續(xù)航/用戶設(shè)定最大續(xù)航 數(shù)值取整
this.bike_duration = (parseInt(this.max_duration)*parseInt(this.bike_power)/100).toFixed(0)
// 累計(jì)騎行 = 最大續(xù)航-預(yù)計(jì)續(xù)航
this.bike_distance = (parseInt(this.max_duration) - parseInt(this.bike_duration)).toFixed(0)
桌面服務(wù)卡片
創(chuàng)建一張2*4尺寸的桌面服務(wù)卡片,目前可展示里程數(shù)據(jù)和電量信息.支持定時(shí)30min自動(dòng)刷新或者用戶觸發(fā)控件刷新數(shù)據(jù). 刷新的數(shù)據(jù)目前還未與數(shù)據(jù)庫同步。
服務(wù)卡片刷新數(shù)據(jù)的方式有如圖所示幾種,智騎行APP中使用了message 和call刷新數(shù)據(jù),使用router拉起應(yīng)用。
卡片使用message與FormExtensionAbility交互介紹: 在服務(wù)卡片的獲取定位控件中,添加點(diǎn)擊事件,發(fā)起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生命周期中調(diào)用updateForm接口刷新卡片。
onUpdateForm(formId) {
// 每30min自動(dòng)刷新一次
let formData = {
'power': 'power', // 和卡片布局中-電量對(duì)應(yīng)
'location': 'location', // 和卡片布局中-位置對(duì)應(yīng)
'distance': 'distance', // 和卡片布局中-里程對(duì)應(yīng)
'duration': 'duration', // 和卡片布局中-預(yù)計(jì)續(xù)航對(duì)應(yīng)
'beep': 'beep.', // 和卡片布局中-響鈴找車對(duì)應(yīng)
'lock': 'lock', // 和卡片布局中-開鎖對(duì)應(yīng)
};
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));
})
}
在卡片頁面通過注冊(cè)里程數(shù)據(jù)的onClick點(diǎn)擊事件回調(diào),并在回調(diào)中調(diào)用postCardAction接口觸發(fā)router事件至EntryAbility。
// 里程數(shù)據(jù)
Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceAround }) {
Column() .width('47%').height("100%")
...
.onClick(() => {
....
postCardAction(this, {
'action': 'router',
'abilityName': 'EntryAbility', // 只能跳轉(zhuǎn)到當(dāng)前應(yīng)用下的UIAbility
'params': {
'detail': 'RouterFromCard'
}
});
})
在卡片頁面通過注冊(cè)車輛圖標(biāo)的onClick點(diǎn)擊事件回調(diào),并在回調(diào)中調(diào)用postCardAction接口觸發(fā)call事件至UIAbility。
// 卡片中車輛圖標(biāo)
.onClick(()=>
{
postCardAction(this, {
'action': 'call',
'bundleName': 'com.example.obike',
'abilityName': 'EntryAbility', // 只能拉起當(dāng)前應(yīng)用下的UIAbility
'params': {
'method': 'funA',
'formId': this.formId,
'detail': 'CallFromCard'
}
});
})
車輛硬件開發(fā)
基于OpenHarmony開發(fā)電動(dòng)車的控制系統(tǒng),主控芯片為Hi3861。近距離時(shí)可通過TCP連接APP,遠(yuǎn)程可通過連接GPS+4G模塊實(shí)現(xiàn)通信。
具體實(shí)現(xiàn):Hi3861通過串口發(fā)送指令到GPS+4G模塊獲取定位信息;通過ADC采集電池電量;通過4G模塊發(fā)送到云服務(wù)器;結(jié)合前文所述通信連接,APP從應(yīng)用內(nèi)web端口獲取數(shù)據(jù)。
Hi3861主要任務(wù)代碼如下:
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、展望
**作品商業(yè)價(jià)值:**萬物互聯(lián)時(shí)代,電動(dòng)車智能化是趨勢(shì),團(tuán)隊(duì)基于OpenHarmony開發(fā)的智騎行方案,擁有服務(wù)卡片、定位、找車等功能,成本低易用性強(qiáng)。
作品進(jìn)一步優(yōu)化計(jì)劃:
- B12版本:實(shí)現(xiàn)服務(wù)卡片數(shù)據(jù)庫同步;實(shí)現(xiàn)BLE通信,無需網(wǎng)絡(luò),近場(chǎng)自動(dòng)連接;實(shí)現(xiàn)"人離車鎖,人來車開"功能;連接真實(shí)電動(dòng)車;
- B241版本:實(shí)現(xiàn)導(dǎo)航功能、軌跡回放、截圖分享、歷史數(shù)據(jù)查看
- B242版本: 支持圓屏幕lite設(shè)備