项目概述
可能有些同学已经接触过了标准系统上的软总线应用开发,但是你玩过轻量系统上的软总线应用吗?现在它来了。我们利用 OpenAtom OpenHarmony 3.1 Release(以下简称“OpenHarmony”)版本的轻量系统软总线能力,将智能燃气检测设备和智能窗户通风设备组成一个轻量级分布式网络,实现设备之间的相互控制。
原理图如下:
当家中的燃气告警时,无需任何操作,直接控制窗户通风系统中的电机工作。
开发说明
从上面的视频中可以看到,相关案例设备的应用界面都可以分成外设交互页面和软总线操作页面两大块。在下面的系统框图中可以看到相关页面和其依赖的相关轻量系统能力。例如外设交互界面通过自定义 JSI 接口与设备硬件打交道,软总线操作界面则通过轻量系统的设备管理能力、软总线能力来实现设备间的发现、认证、传输等软总线操作。
在下面的内容中我们将以智能燃气告警器设备为例,将相关开发过程分成 JS 应用端开发、自定义 JSI 实现、开发板端代码三部分来说明:
JS 应用开发
燃气告警器 JS 应用是基于 3.1 release 版本,并结合方舟开发框架(ArkUI)、分布式组网等特性,使用 JS 语言开发的一款分布式安全厨房应用。为了体现了 OpenHarmony 轻量级分布式特性,不仅需要考虑页面该如何设计、应用怎样同外设交互,还需要考虑两个轻量级设备间如何进行设备认证,设备间如何进行通讯等问题。
所以在相关应用中设计了操作页面,报警页面以及设备认证页面。其中首页是设置燃气浓度阈值,以及显示当前燃气浓度;报警页面是当首页检测到当前燃气浓度达到或高于我们设置的阈值时,会从首页跳转到报警界面;设备认证页面则是对两台设备进行分布式组网。
代码结构如下图:
外设交互页面开发
相关界面如上图所示,我们将首页页面分成三个部分:顶部标签、燃气浓度显示、设置告警阈值。接下来是具体的解析内容:
1)顶部标签解析
顶部标签中除了相关文本界面,主要是顶部两个按钮,分别用来显示当前 Wi-Fi 连接状态以及跳转到设备认证页面。
GetKey() {
com.get({ // 获取wifi状态
key: 'storage_key',
success: (data)=> {
let res = JSON.parse(data)
if (res.wifi) {
if(res.wifi == 'connected') {
this.isWifi = true;
} else {
this.isWifi = false;
}
}
.....
},
});
},
changePage(operation) {
router.replace({
uri:"pages/dm/dm" // 跳转到设备认证页面
});
}
复制代码
2)燃气浓度显示
主要是获取当前燃气浓度,并实时刷新到相关界面上。
onInit() {
setTimeout(()=>{
setInterval(()=>this.GetKey(),500) // 每500ms 获取一次
},3000);
},
GetKey() {
com.get({
key: 'storage_key',
success: (data)=> {
let res = JSON.parse(data)
if (res.CurrentGasCONC) {
this.currentValue = res.CurrentGasCONC;
this.progressPercent = ((this.currentValue ) /300) * 100
if(this.currentValue >this.PresetValue && !this.isChange){
this.isChange = true;
router.replace({
uri:"pages/warn/warn" // 燃气数值超标后自动跳转告警页面
});
}
}
},
......
});
},
复制代码
3)设置报警阈值
首页界面中对于报警阈值的处理,主要包含减小预设阈值和增大预设阈值,都是通过调用相关 SetKey 操作完成的。
reduceProgress(){ //减小预设阈值
if (this.PresetValue <= 0) {
this.PresetValue = 0
}else{
this.PresetValue = parseInt(this.PresetValue) - 20
}
this.isChange = false;
this.setProgress = ((this.PresetValue ) /300) * 100
this.SetKey( 'GasThreshold', this.PresetValue );
},
addProgress(){ //增大预设阈值
if (this.PresetValue >= 300) {
this.PresetValue = 300
}else{
this.PresetValue = parseInt(this.PresetValue) + 20
}
this.isChange = false;
this.setProgress = ((this.PresetValue ) /300) * 100
this.SetKey( 'GasThreshold', this.PresetValue );
},
SetKey(key1, value1) {
com.set({
key: key1 + '',
value: value1 + '',
// success or failed 状态打印
});
},
复制代码
设备认证页面开发
相关界面如上图所示,我们将设备认证步骤分成四个步骤:发现设备、发起认证、允许认证、输入 PIN 码。接下来是具体的解析内容:
1)发现设备解析
设备认证因设备状态不同显示对应的 UI,上图显示的 UI 对应设备状态”status = start“。
startDevice(){
this.subscribeId = Math.floor(Math.random() * 10000 + 1000)
var info = {
"subscribeId": this.subscribeId, // 特定随机的
"mode": 0xAA, // 设置主动发现模式,除此之外还有被动模式DISCOVER_MODE_PASSIVE
"medium": 0, // 自动选择发现介质,目前用的是coap
"freq": 2, // 发送发现消息的频率,目前用的是HIGH 还有LOW/MID/SUPER_HIGH
"isSameAccount": false, // 取消同一账号下才能发现的限制
"isWakeRemote": false, // 目前轻量系统没有睡眠模式,所以不用睡眠唤醒功能
"capability": 0 // 目前使用DDMP
devicemanager.startDeviceDiscovery(info); // 开始设备发现
},
复制代码
2)发起认证解析
AuthenticateDevice(){ // 发起认证
let extraInfo = {
targetPkgName: 'test',
appName: "Newname",
appDescription: "testAPP",
business: '0',
displayOwner: 0
};
let AuthParam = {
authType: 1, // 以PIN 码方式进行认证校验
appIcon:null,
appThumbnail:null,
extraInfo: extraInfo
};
let _this = this;
devicemanager.authenticateDevice(this.statusInfo, AuthParam, {
// 省略了相关success 和fail 回调处理,完整代码见参考链接
},
复制代码
3)允许认证解析
当设备状态”status = join-pin“,允许相关认证动作并且显示相关 PIN 码。
joinAuthOk() {
this.joinPin() //切换显示PIN码界面
this.initStatue() //获取PIN码并显示
devicemanager.setUserOperation(0)
},
initStatue() {
this.log('initStatue')
const data = devicemanager.getAuthenticationParam()
// 参数值转换为 JSON 字符串写入data
this.log('getAuthenticationParam:' + JSON.stringify(data))
// Authentication type, 1 for pin code.
// ode ==1,pin码
if (data && data.authType == 1) {
// 完整代码见参考链接
}
},
复制代码
4)输入 PIN 码解析
当设备状态"status = main-pin",进到相关 PIN 码输入、校验界面。
mainInputPin(s) { // 输入六位数字
if (this.pinNumb == 6) return
if (this.pinNumb < 6) {
this.pin[this.pinNumb] = s
++this.pinNumb
}
if (this.pinNumb == 6) {
console.log("verifyAuthInfo ok")
this.verifyAuthInfo(this.pin.join('')) // PIN码校验
}
},
复制代码
自定义 JSI 原理和实现
JSI 是 OpenHarmony 轻量和小型系统的一种 JS API 实现机制,适合封装 IO、CPU 密集型、OS 底层等能力给到 JS 应用调用,通过 JSI 可以实现 JS 与 C/C++代码互相访问。与 OpenHarmony 轻量级系统中涉及到的 audio、device、sensor 等需要与硬件打交道的 JSI 模块类似,CommunicationKit 和 DeviceManager 模块同样首先要加入到相关的配置文件中。ace_lite_engine 通过 JSI::SetModuleAPI 将 JS 应用中使用的关键字映射成 C++函数。具体操作如下:foundation/ace/ace_engine_lite/frameworks/module_manager/ohos_module_config.h 中的 OHOS_MODULES 新增如下字段:
{"CommunicationKit", InitNativeApiCommunicationKit},
{"devicemanager", InitDeviceManagerModule},
复制代码
加载自定义模块
如上所示,在 JS 应用外设控制界面中数据读取和命令下发时,引入了 CommunicationKit 模块,现在我们就看一下相关具体内容:
InitNativeApiCommunicationKit 函数相关内容如下:
vendor/team_x/common/communicationkit/native_utils/src/nativeapi_communication_kit.cpp
void InitNativeApiCommunicationKit(JSIValue exports) {
JSI::SetModuleAPI(exports, "get", NativeapiCommunicationKit::Get); // 与JS应用中的关键字一致
JSI::SetModuleAPI(exports, "set", NativeapiCommunicationKit::Set);
}
复制代码
相关 C++实现 NativeapiCommunicationKit::Get 和 Set 的方式类似,我们参考轻量系统源码中其他模块的实现,使用 ExecuteAsyncWork 函数。根据给定的参数创建一个异步工作,并将其分派给主应用程序任务处理程序。其获取燃气浓度逻辑在 ExecuteGet 实现,设置轻量系统燃气告警阈值由 ExecuteSet 实现。函数 args 参数用来接收 JS 端传过来的参数(JSIValue 数组),argsNum 表示该数组长度。
ExecuteAsyncWork(thisVal, args, argsNum, ExecuteGet, false);
ExecuteAsyncWork(thisVal, args, argsNum, ExecuteSet, false);
复制代码
加载设备管理模块
与自定义模块类似,JS 应用使用软总线接口时,需要在应用执行前加载设备管理模块。设备管理模块也是 OpenHarmony 系统中的重要组成部分,我们在标准系统中是通过 NAPI 的方式来加载相关模块,而在我们轻量系统中,设备管理模块是通过 JSI 的方式来加载的。首先看到 InitDeviceManagerModule 函数相关内容如下:
foundation/distributedhardware/devicemanager/interfaces/kits/js_mini/src/native_devicemanager_js.cpp
void InitDeviceManagerModule(JSIValue exports) {
JSI::SetModuleAPI(exports, "createDeviceManager", DeviceManagerModule::CreateDeviceManager);
......
JSI::SetModuleAPI(exports, "startDeviceDiscovery", DeviceManagerModule::StartDeviceDiscoverSync);
JSI::SetModuleAPI(exports, "stopDeviceDiscovery", DeviceManagerModule::StopDeviceDiscoverSync);
JSI::SetModuleAPI(exports, "authenticateDevice", DeviceManagerModule::AuthenticateDevice);
JSI::SetModuleAPI(exports, "verifyAuthInfo", DeviceManagerModule::VerifyAuthInfo);
JSI::SetModuleAPI(exports, "setUserOperation", DeviceManagerModule::SetUserOperationSync);
JSI::SetModuleAPI(exports, "getAuthenticationParam", DeviceManagerModule::GetAuthenticationParamSync);
......
}
复制代码
开发板端代码开发说明
如下图所示,我们从轻量系统软总线设备的系统启动流程出发,来分析软总线应用执行的相关要点。第一步:首先初始化软总线 server;第二步:注册与软总线相关的服务,例如 PRC、设备管理 DeviceManager 服务;第三步:JS engine 加载 DeviceManager 接口声明;第四步:具体的 RPC 通信操作流程。
初始化软总线服务
轻量系统设备启动的启动过程中,会注册相关初始化软总线服务线程。该线程中的主要内容就是调用 InitSoftBusServer 函数。该函数会初始化与软总线相关的配置、发现、认证等相关操作。
软总线相关服务注册
服务是 OpenHarmony 系统中的一个重要概念,不同的功能模块,不同线程/进程之间的调用接口,都统一抽象成了服务。利用服务机制,操作系统、驱动框架等提供的能力都能被包装成服务提供给到应用调用。下面给大家具体介绍软总线相关的服务注册细节和要点。
SAMGR:作为中介者,管理 Provider 提供的能力,同时帮助 Consumer 发现 Provider 的能力。Provider:服务的提供者,为系统提供能力(对外接口)。
PRC 服务注册
RPC:(Remote Procedure Call)用于跨设备跨进程间的通信,在轻量系统软总线应用中,我们利用 RPC 能力实现了安全厨房项目中两个设备的关联控制,RPC 服务注册的具体内容如下:
1.创建相关的静态服务对象;
static MiniService g_miniService = {
.GetName = GetName, // 相关服务名为mini_sa_rpc
.Initialize = Initialize,
.MessageHandle = MessageHandle,
.GetTaskConfig = GetTaskConfig,
SERVER_IPROXY_IMPL_BEGIN,
.Invoke = FeatureInvoke, // 对外提供Invoke方法,供RPC相关client端程序调用
IPROXY_END,
};
复制代码
2.注册相关的服务和缺省对象;
SAMGR_GetInstance()->RegisterService((Service *)&g_miniService);
SAMGR_GetInstance()->RegisterDefaultFeatureApi(MINI_SERVICE, GET_IUNKNOWN(g_miniService));
复制代码
设备管理服务注册
如果没注册设备管理服务,那么相关软总线能力也就无从谈起。在轻量系统启动时,执行相关设备管理服务初始化动作,具体内容如下:
1)创建相关的静态服务对象;
static DeviceManagerSamgrService service = {
.GetName = GetName, // 相关服务名为dev_mgr_svc
.Initialize = Initialize,
.MessageHandle = MessageHandle,
.GetTaskConfig = GetTaskConfig,
};
复制代码
2)注册相关的服务和缺省对象;
SAMGR_GetInstance()->RegisterService((Service *)&service)) ;
SAMGR_GetInstance()->RegisterDefaultFeatureApi(DEVICE_MANAGER_SERVICE_NAME,GET_IUNKNOWN(service));
复制代码
3)执行相关初始化动作。DeviceManager 服务的初始化函数将完成相关状态回调注册和传输通道初始化,然后执行相关 Publish 动作,做好接收发现消息的准备。
设备间 RPC 通信
在标准系统应用开发中,我们可以通过分布式数据库和启动远程 Ability 的方式实现设备之间的通信,而在轻量系统中则通过 RPC 的方法来实现。在前面的内容中我们讲了服务的概念,下面是拓展的相关原理图:
Consumer:服务的消费者,调用服务提供的功能(对外接口)。
在完成软总线组网后,如果检测到燃气度数超标后,按如下步骤即可实现对智能通风设备的控制:
1.获取软总线网络中的相关节点信息;
GetAllNodeDeviceInfo("com.ohos.devicemanagerui", &nodeInfo, &infoNum);
复制代码
2.获取远程节点发布的 mini_sa_rpc 服务中对应的 IUnknown 方法;
IUnknown *miniDefApi = SAMGR_GetInstance()->GetRemoteDefaultFeatureApi(nodeInfo[0]->networkId, "mini_sa_rpc");
复制代码
3.查询服务所发布的相关能力,获取指向具体 API 接口的指针 miniInterface;
miniDefApi->QueryInterface(miniDefApi, 0, (void **) &miniInterface);
复制代码
4.调用相关 mini_sa_rpc 对外提供的 Invoke 能力;
miniInterface->Invoke(miniInterface, 1, &reply, NULL, NULL);
复制代码
操作体验
提前准备好安全厨房场景中的智能窗户通风设备和智能燃气告警设备,并完成相关的编译和应用安装动作;
提前准备好正常工作的无线路由设备(请保证预设热点名称:test_wifi 密码:12345678;是否能连接互联网均可)
将燃气检测设备和窗户通风设备上电,确认两个设备应用启动正常和操作正常;
按如下步骤将通风设备、燃气检测设备组成一个软总线网络:● 分别点击两个设备应用界面右上角的软总线配置图标,进入软总线配置界面;● 点击智能燃气检测设备应用发现图标,间隔 3S 后点击发起认证图标;● 点击智能通风设备软总线配置界面下的允许认证图标,正常情况下会显示一个 6 位数的 PIN 码;● 点击智能燃气检测设备应用输入 PIN 码按钮,进入数字键盘输入 PIN 码;● 分别点击两个应用软总线配置图标左上角的返回按键,进入设备控制界面。
设置燃气检测设备的阈值低于实际读取的燃气数值,燃气检测应用进入警报界面的同时会控制电机工作,自动通风换气,保证家居的安全。待到实际燃气数值低于设置的阈值时,则关闭电机。
参考链接
本项目中涉及到的参考资料和相关文档路径如下:欧智通 BES2600WM 开发板快速上手学习路径:https://growing.openharmony.cn/mainPlay/learnPathMaps?id=17
轻量系统应用开发软总线视频课程:https://www.bilibili.com/video/BV1BS4y1A7ry/?vd_source=fa133082ba4f0aaa5d2dae4f0a981ab3
设备管理模块文档:https://gitee.com/openharmony/device_manager/blob/master/README_zh.md
智能燃气检测系统样例:https://growing.openharmony.cn/mainPlay/detail?sampleId=3935
智能窗户通风系统样例:https://growing.openharmony.cn/mainPlay/detail?sampleId=3936
总结
从本文中可以看到与标准系统一样应用都是调用设备管理模块提供的相关接口来实现的软总线发现、认证等功能,但是不同的地方在于标准系统使用了预制的 DeviceManager_UI.hap 来显示 PIN 码、输入 PIN 码。而轻量系统软总线应用中,相关 PIN 码显示、PIN 码输入需要自己调用相关接口。
与标准系统软总线应用相比,目前轻量系统软总线应用只实现了轻量系统设备之前数据流转功能,轻量系统分布式拉起、分布式数据库等功能待后续更新迭代。下一步还将研究如何利用软总线来连接轻量系统和标准系统,敬请大家期待。
丰富多样的 OpenHarmony 开发样例离不开广大合作伙伴和开发者的贡献,如果你也想把自己开发的样例分享出来,欢迎提交到 OpenHarmony 知识体系 SIG 仓库。
共建开发样例参考https://gitee.com/openharmony-sig/knowledge/blob/master/docs/co-construct_demos/README_zh.md
评论