一、元服务E-Bike简介
E-Bike是一款基于HarmonyOS开发的元服务,以万能卡片的形式给骑行提供便捷服务,主要功能包括:
- 车辆状态信息获取:用户可在元服务内连接电动自行车(真机和自行车自备),查看车辆位置、剩余电量、续航里程以及累计骑行里程。
- 包括响铃找车功能:按钮可触发车辆鸣响,便于快速确认车辆具体位置(真机和自行车自备)。
- 用户可通过右上角按钮添加2x2或2x4卡片,在桌面可直接查看车辆状态信息。
鸿蒙元服务开发实例:桌面卡片上的电动自行车助手E-Bike-开源基础软件社区
二、环境搭建
首先需要完成HarmonyOS开发环境搭建。E-Bike是元服务,且为端云一体化开发模式,新建工程可可参照如图步骤进行(注意该模式下APP为Stage模型)。
鸿蒙元服务开发实例:桌面卡片上的电动自行车助手E-Bike-开源基础软件社区
鸿蒙元服务开发实例:桌面卡片上的电动自行车助手E-Bike-开源基础软件社区
软件要求
- DevEco Studio版本:DevEco Studio 3.1 Release及以上版本。
- HarmonyOS SDK版本:API version 9及以上版本。
硬件要求
- 设备类型:华为手机或运行在DevEco Studio上的华为手机设备模拟器。
- HarmonyOS系统:3.1.0 Developer Release及以上版本。
- 电动自行车(获取真实车辆数据,车辆为作者自制)
环境搭建
安装DevEco Studio,详情请参考下载和安装软件。
设置DevEco Studio开发环境,DevEco Studio开发环境需要依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
- 如果可以直接访问Internet,只需进行下载HarmonyOS SDK操作。
- 如果网络不能直接访问Internet,需要通过代理服务器才可以访问,请参考配置开发环境。
开发者可以参考以下链接,完成设备调试的相关配置:使用真机进行调试使用模拟器进行调试
三、代码结构解读
本篇教程只对E-Bike实现的核心代码进行讲解,对于完整代码,会在源码下载或gitee中提供。主要的程序框架如下:
entry\src\main\ets
│ ├─common–通用常量和数据
│ ├─entryability – EntryAbility.ts 程序入口
│ ├─entryformability–EntryFormAbility.ts卡片入口
│ ├─pages—Index.ts 应用主页
│ ├─services
│ ├─widget
│ │ └─pages—2x2 ArkTS卡片
│ └─widget24
│ └─pages—2x4卡片
└─resources —资源文件目录
四、应用主页面UI和功能开发
1、主页面UI
新建工程后,在entry\src\main\ets\pages\Index.ts文件中已有一个模板案例,我们需要删除其中的代码,然后构建自己的页面。具体实现方法是:
- 删除build() { }中的代码。
- 使用Column、Flex、Row容器和Button、Image、Text组件构建E-Bike布局。
- 在UI中加入逻辑判断具体要显示的UI组件。如响铃找车的Image组件内容由用户的点击状态决定,点击响铃找车则Image切换为响铃状态,反之亦然。
具体代码实现框架如下:
@Entry @Component struct Index {
build() { Column({space:10})
{
// 背景图
Image($r("app.media.Ebike"))
···
Flex()
{
// 响铃找车
Column()
{
if(this.display_flag==1)
{
Image($r("app.media.ic_ring_on_filled"))
.height("55%")
.objectFit(ImageFit.Contain)
.interpolation(ImageInterpolation.High)
.onClick(() => { this.display_flag +=1; if(this.display_flag>2)
{
this.display_flag =1;
}
})
}
if(this.display_flag==2)
{
Image($r("app.media.ic_ring_off_filled"))
.height("55%")
.objectFit(ImageFit.Contain)
.interpolation(ImageInterpolation.High)
.onClick(() => { this.display_flag+=1; if(this.display_flag>2)
{
this.display_flag =1;
}
})
}
Text("响铃找车")
···
}
// 获取定位
Column()
{
Image($r("app.media.ic_statusbar_gps"))
····
})
Text(this.bike_location)
····
}
····
}.width("95%").height("10%")
// 电量 设置
Flex({ direction: FlexDirection.Row,justifyContent: FlexAlign.SpaceBetween,})
{
// 电量
Column() { Row()
{
Image($r("app.media.ic_power"))
···
// 电量值
Text(this.bike_power.toString() + '%')
···
}
Text("剩余电量")
···
}
···
// 设置
Column() { Image($r("app.media.ic_public_settings_filled"))
···
Text("车辆设置")
···
}···
}
// 骑行数据
Flex({ direction: FlexDirection.Column})
{
Row()
{
Text(" 累计骑行")
··· Blank()
Text("预计续航 ")
···
}
}
}
}
}
2、主页功能开发
主页主要实现的功能包括:连接车辆、获取需要展示的数据(车辆电量、位置、里程数据等)、将数据持久化便于页面展示。
车辆与E-Bike通过Socket TCP协议方式连接。鉴于此部分依赖硬件,这里主要介绍E-Bike应用层如何开发 Socket通信。实现如下:
Index.ets:
// 1. 首先要引入模块,创建TCPSocket对象
import socket from '@ohos.net.socket'
// 创建一个TCPSocket连接,返回一个TCPSocket对象。
let tcp = socket.constructTCPSocketInstance();
tcpInit() {
// 2. 绑定IP地址和端口。
let bindAddress = { address: '192.168.xx.xx',
port: 1234, // 绑定端口,如1234
family: 1
};
tcp.bind(bindAddress, err => { if (err) {
console.log('bind fail'); return;
}
console.log('bind success');
}
// 3.其次是订阅TCPSocket相关的订阅事件,如on message,有数据传入
tcp.on('connect', () => {
this.tcp_status = '连接ok'
prompt.showToast({message:this.tcp_status})
});
tcp.on('message', value => {
this.message_recv = this.resolveArrayBuffer(value.message)
prompt.showToast({message:this.message_recv})
});
tcp.on('close', () => {
prompt.showToast({message:"tcp close"})
});
}
// 4.连接到指定的IP地址和端口。 tcpConnect() {
tcp.getState().then((data) => {
if (data.isClose) { this.tcpInit()
}
// 连接
tcp.connect(
{address: { address: this.GetSetIP, port: this.localAddr.port, family: 1}, timeout: 2000}
).then(() => {
prompt.showToast({message:" tcp connect successful"})
}).catch((error) => { prompt.showToast({message:"tcp connect failed"})
})
})
}
// 5.发送数据
tcpSend() {
tcp.getState().then((data) =>
{ if (data.isConnected) {
// 发送消息
tcp.send(
{ data: this.message_send, }
).then(() => {
prompt.showToast({message:"send message successful"})
}).catch((error) => { prompt.showToast({message:"send failed"})
})
} else {
prompt.showToast({message:"tcp not connect"})
}
})
}
// 6. TCP连接操作界面通过设置按钮控制,点击设置按钮即可弹出,可输入车辆IP地址,确认连接
if(this.show_setting==1)
{
Flex({ direction: FlexDirection.Row})
{
Row()
{
Text('车辆IP')
TextInput({ placeholder: '192.168.43.164'})
.onChange((value: string) => { this.InputIP = value
})
Button('连接').onClick(()=>
{
this.GetSetIP=this.InputIP;
this.tcpInit()
})
}
}
}
// 7.点击组件,实现发送指令,并获取对应数据,这里以电量为例:
Image($r("app.media.ic_power"))
.height("80%")
.objectFit(ImageFit.Contain)
.onClick(() => {
// this.bike_power = 99;
this.message_send = MSG_CMD.GET_BIKE_POWER this.tcpSend()
this.bike_power = this.message_recv;
})
/ 8.连接使用完毕后,主动关闭。取消相关事件的订阅。
setTimeout(() => {
tcp.close((err) => { console.log('close socket.')
});
tcp.off('message');
tcp.off('connect');
tcp.off('close');
}, 30 * 1000);
五、卡片开发
1、卡片UI开发
开发卡片的目的是将自行车的数据展示在桌面上,让用户一目了然。用默认模板创建工程时,自动创建了一张卡片,在form_config.json文件改动配置为自动刷新,支持2x2.
//form_config.json
"colorMode": "auto", "isDefault": true, "updateEnabled": false, "scheduledUpdateTime": "10:30",
"updateDuration": 1,
"defaultDimension": "2*2", "supportDimensions": [
"2*2"
]
为展示电量信息,且布局不同,由此需要创建一张2x4的卡片。操作如下:在main目录下,点击鼠标右键 > New > Service Widget。
然后选择Hello World卡片模板,点击Next。
填写卡片名字(如Widget24Card)、开发语言(ArkTS和JS可选)、支持卡片规格(Support dimension 2x4)、关联表单(Ability name)点击Finish完成创建。修改配置为自动刷新,支持2x4。
创建完卡片后,在ets文件目录下显示卡片目录,然后开发者使用ArkTS开发卡片页面。效果如图所示:
鸿蒙元服务开发实例:桌面卡片上的电动自行车助手E-Bike-开源基础软件社区
为两张卡片开发UI,resource资源共用。使用Flex容器开发卡片,保证不同屏幕大小的显示效果。同时为组件绑定事件,用户可以主动获取数据,具体UI布局代码不再赘述,实现2x2和2x4效果如下
鸿蒙元服务开发实例:桌面卡片上的电动自行车助手E-Bike-开源基础软件社区
2、卡片数据刷新
通过message事件刷新卡片内容,在卡片页面中可以通过postCardAction接口触发message事件拉起FormExtensionAbility,然后由FormExtensionAbility刷新卡片内容。下面是这种刷新方式更新电量的简单示例。在卡片页面通过注册电量图标Image组件的onClick点击事件回调,并在回调中调用postCardAction接口触发事件至FormExtensionAbility。
// Widget24Card.ets:
let storage = new LocalStorage();
@Entry(storage) @Component
struct WidgetCard24 {
···
@LocalStorageProp('bike_power') bike_power: number = 50;
···
build() {
Row({space:5}) {
// 背景图 电量
Column()
{
Row()
{
Image($r("app.media.ic_power"))
···
.onClick(() => { postCardAction(this, {
'action': 'message',
'params': { 'bike_power': 55
}
});
})
// 电量值
Text(`${this.bike_power}`+'%')//this.bike_power.toString()+'%')
···
}
···
}
}
}
// EntryFormAbility.ets:
import formBindingData from '@ohos.app.form.formBindingData';
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility'; import formProvider from '@ohos.app.form.formProvider';
export default class EntryFormAbility extends FormExtensionAbility {
find_bike: string = "app.media.ic_ring_off" bike_power: number = 55.6
display_flag : number = 1
bike_location: string = "长安街1号"
bike_distance: number = 520
bike_duration: number = 479
my_font_size : number = 12
formData = {
'title': this.find_bike, 'bike_power': this.bike_power,
'bike_distance':this.bike_distance, 'bike_duration':this.bike_duration,
'bike_location':this.bike_location,
'detail': 'Detail Update Success.', // 和卡片布局中对应
}
onFormEvent(formId, message) {
console.info(`FormAbility onEvent, formId = ${formId}, message:
${JSON.stringify(message)}`);
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));
})
}
...
}
实现效果如下图:
鸿蒙元服务开发实例:桌面卡片上的电动自行车助手E-Bike-开源基础软件社区
参考链接:元服务官网