基于OpenHarmony Native Api框架实现控制LED灯亮灭

系统教程10个月前发布 whoami
43 0 0

基于OpenHarmony Native Api框架实现控制LED灯亮灭

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

一、样例介绍

基于OpenHarmony NAPI框架实现北向应用端控制南向设备端扩展板指定GPIO口对应LED灯的亮灭。

二、开发环境

  1. 搭载OpenHarmony-3.1-release版本的Unionpi Tiger开发板
  2. DevEco Studio 3.0.0.991
  3. 40PIN测试小板
  4. Ubuntu20.04虚拟机
  5. USB_Burning_Tool烧录工具

环境搭建就不详细介绍了,社区也可以搜得到,这里给出笔者参考的几篇资料。

[1]​​OpenHarmony在Amlogic A311D芯片平台的快速开发上手指南-开源基础软件社区-51CTO.COM​​。

[2]​​docs/UnionpiTiger_helloworld · OpenHarmony-SIG/knowledge_demo_temp – 码云 – 开源中国 (gitee.com)​​。

[3]​​unionpi_tiger/README_zh.md · OpenHarmony/device_board_unionman – Gitee.com​​。

三、OpenHarmony系统技术架构

OpenHarmony整体遵从分层设计,从下向上依次为:内核层、系统服务层、框架层和应用层。系统功能按照“系统 > 子系统 > 组件”逐级展开,在多设备部署场景下,支持根据实际需求裁剪某些非必要的子系统或组件。子系统是一个逻辑概念,它具体由对应的组件构成。

基于OpenHarmony Native Api框架实现控制LED灯亮灭

四、NAPI框架简介

NAPI(Native API)是 OpenHarmony 标准系统的一种JS API实现机制,适合封装IO、CPU密集型、OS底层等能力并对外暴露JS接口,实现JS与C/C++代码互相访问

基于OpenHarmony Native Api框架实现控制LED灯亮灭

五、实现步骤

1、创建NAPI扩展库

新增子系统um_a311d

在OpenHarmony源码目录下任意位置创建一个目录um_a311d作为子系统的目录(子系统可创建在OpenHarmony源码目录任意位置)。子系统目录下创建ohos.build文件,构建时会先读取此文件。把新增的子系统配置到build/subsystem_config.json。

"um_a311d": {
"path": "vendor/unionman/a311d/sample/napi/gpioled/um_a311d",
"name": "um_a311d"
},

新增um_a311d组件

在子系统目录下创建一个子组件目录um_a311d。

修改子系统根目录下的ohos.build文件,添加组件配置。

{
"subsystem": "um_a311d",
"parts": {
"um_a311d": {
"variants": [
"phone"
],
"module_list": [
"//vendor/unionman/a311d/sample/napi/gpioled/um_a311d/um_a311d/um_gpio:um_a311d"
]
}
}
}

新增扩展动态库

在组件目录下创建一个子目录um_gpio,作为 NAPI扩展库的代码目录。

在um_gpio目录下创建代码文件gpio.cpp。

在um_gpio目录下创建BUILD.gn文件,编写构建配置。

import("//build/ohos.gni")

ohos_shared_library("um_a311d") {
include_dirs = [
"//foundation/ace/napi/interfaces/kits"
]
sources = [
"gpio.cpp",
]

deps = [
"//foundation/ace/napi:ace_napi",
]
relative_install_dir = "module"
subsystem_name = "um_a311d"
part_name = "um_a311d"
}

将组件添加到产品定义中

产品定义文件存放在productdefine/common/products 目录,文件名就是产品名称。 如:a311d开发板的产品定义文件为productdefine/common/products/a311d.json

按格式:“subsystemA:partA“:{}在产品定义中添加组件。

基于OpenHarmony Native Api框架实现控制LED灯亮灭

2、NAPI接口开发

模块注册

(1)添加NAPI框架头文件,引入框架提供的方法。

#include "napi/native_api.h"
#include "napi/native_node_api.h"

(2)定义模块。

(3)注册模块,加载动态库时自动调用注册。

/*
* 模块定义
*/
static napi_module um_gpioModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerUm_GpioApis,
.nm_modname = "um_gpio", //模块名
.nm_priv = ((void *) 0),
.reserved = {0},
};
/*
* 注册模块
*/
extern "C" __attribute__((constructor)) void RegisterUm_GpioModule(void) {
napi_module_register(&um_gpioModule); //接口注册函数
}

使用DECLARE_NAPI_FUNCTION(“js函数名”, c++实现函数名)定义接口函数、DECLARE_NAPI_PROPERTY、 DECLARE_NAPI_STATIC_PROPERTY等定义属性,再通过napi_define_properties赋给exports对象,最后返回exports对象。

/*
* 注册接口
*/
static napi_value registerUm_GpioApis(napi_env env, napi_value exports) {
napi_value gpioValHigh = gpioValHigh;
napi_value gpioValLow = nullptr;
napi_create_int32(env, UM_GPIO_HIGH_LEVE, &gpioValHigh);
napi_create_int32(env, UM_GPIO_LOW_LEVE, &gpioValLow);
napi_property_descriptor desc[] = {
DECLARE_NAPI_FUNCTION("setLedStatusWithCallback", setLedStatusWithCallback),
DECLARE_NAPI_FUNCTION("setLedStatusWithPromise", setLedStatusWithPromise),
DECLARE_NAPI_FUNCTION("setLedStatus", setLedStatus),
DECLARE_NAPI_STATIC_PROPERTY("LED_ON", gpioValHigh),
DECLARE_NAPI_STATIC_PROPERTY("LED_OFF", gpioValLow),
};
NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
return exports;
}

实现原理

NAPI支持Callback、 Promise 二种异步模型:

• Callback:任务结果以参数的形式提供给用户注册的回调函数;代码逻辑复杂,可读性差,回调地狱。

• Promise:ES6提供的一种异步编程解决方案,比传统的解决方案——回调函数更加优雅。可使得异步执行可以 按照同步的流表示出来,避免了层层嵌套的回调函数,代码更易读。

NAPI框架中,异步接口实现基于napi_create_async_work() 函数创建的异步工作项。

napi_status napi_create_async_work(napi_env env,

napi_value async_resource,

napi_value async_resource_name,

napi_async_execute_callback execute,

napi_async_complete_callback complete,

void* data, napi_async_work* result);

参数说明:

[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供。

[in] async_resource: 可选项,关联async_hooks。

[in] async_resource_name: 异步资源标识符,主要用于async_hooks API暴露断言诊断信息。

[in] execute: 异步工作项的处理函数,当工作项被调度到后在worker线程中执行,用于处理业务逻辑,并得到结果。

[in] complete: execute参数指定的函数执行完成或取消后,触发执行该函数,将结果返回给JS。此函数在EventLoop中执行。

[in] data: 异步工作项上下文数据(Addon),用于在主线程接口实现方法、execute、complete之间传递数据。

[out] result: napi_async_work*指针,返回创建的异步工作项对象。

返回值:返回napi_ok表示转换成功,其他值失败。

异步工作项工作时序图

基于OpenHarmony Native Api框架实现控制LED灯亮灭

定义异步工作项上下文数据

根据实际场景需要定义异步工作项上下文数据结构,用于在主线程方法、execute、complete之间传递数据,一般包含异步工作项对象、napi_deferred对象、回调函数、参数数组、返回结果等。

struct LedAddOnData {
napi_async_work asyncWork = nullptr; //异步工作项
napi_deferred deferred = nullptr; //用于Promise的resolve、reject处理
napi_ref callback = nullptr; //回调函数
int args[2] = {0}; //2个输入参数
int result = 0; //业务逻辑处理结果(返回值)
};

异步接口–Callback实现

主线程方法接收参数,初始化异步工作项上下文数据,创建异步工作项,并将其加到调度队列。最后方法返回空值。

static napi_value setLedStatusWithCallback(napi_env env, napi_callback_info info) {
//获取3个参数,值的类型是js类型(napi_value)
size_t argc = 3;
napi_value args[3];
napi_value thisArg = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));
//异步工作项上下文用户数据,传递到异步工作项的execute、complete中传递数据
auto addonData = new LedAddOnData{
.asyncWork = nullptr,
};
//将接收到的参数传入用户自定义的上下文数据
NAPI_CALL(env, napi_get_value_int32(env, args[0], &addonData->args[0]));
NAPI_CALL(env, napi_get_value_int32(env, args[1], &addonData->args[1]));
NAPI_CALL(env, napi_create_reference(env, args[2], 1, &addonData->callback));
//创建async work,创建成功后通过最后一个参数接受async work的handle
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "setLedStatusWithCallback", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, executeCB, completeCBForCallback, (void *) addonData,
&addonData->asyncWork);
//将刚创建的async work加到队列,由底层去调度执行
napi_queue_async_work(env, addonData->asyncWork);
//原生方法返回空对象
napi_value result = 0;
NAPI_CALL(env, napi_get_null(env, &result));
return result;
}

worker线程执行业务逻辑计算,将结果存入上下文数据。

//业务逻辑处理函数,由worker线程池调度执行。
static void executeCB(napi_env env, void *data) {
LedAddOnData *addOnData = (LedAddOnData *) data;
int s32GpioNum = addOnData->args[0];
int bExport = UM_GPIO_EXPORTED;
int direction = UM_GPIO_DIRECTION_OUT;
int s32GetValue = -1;
UM_GPIO_IsExport(s32GpioNum, &s32GetValue);
if (s32GetValue == UM_GPIO_NOT_EXPORT) {
UM_GPIO_Export(s32GpioNum, bExport);
}
UM_GPIO_SetDirection(s32GpioNum, direction);
addOnData->result = UM_GPIO_SetValue(s32GpioNum, addOnData->args[1]);
}

执行Complete函数,从上下文数据中获取结果,将结果转换为JS类型,调用回调函数返回结果给调用方。

//业务逻辑处理完成回调函数,在业务逻辑处理函数执行完成或取消后触发,由EventLoop线程中执行。
static void completeCBForCallback(napi_env env, napi_status status, void *data) {
LedAddOnData *addOnData = (LedAddOnData *) data;
napi_value callback = nullptr;
napi_get_reference_value(env, addOnData->callback, &callback);
napi_value undefined = nullptr;
napi_get_undefined(env, &undefined);
napi_value result = nullptr;
napi_create_int32(env, addOnData->result, &result);
//执行回调函数
napi_value returnVal = nullptr;
napi_call_function(env, undefined, callback, 1, &result, &returnVal);
//删除napi_ref对象
if (addOnData->callback != nullptr) {
napi_delete_reference(env, addOnData->callback);
}
//删除异步工作项
napi_delete_async_work(env, addOnData->asyncWork);
delete addOnData;
}

异步接口– Promise实现

NAPI框架提供napi_create_promise()函数用于创建Promise,返回promise、deferred 二个对象, promise用于主线程方法返回, deferred对象用于resolve、reject处理。

napi_status napi_create_promise(napi_env env。

napi_deferred* deferred。

napi_value* promise)。

参数说明:

[in] env: 传入接口调用者的环境,包含js引擎等,由框架提供,默认情况下直接传入即可。

[out] deferred: 返回接收刚创建的deferred对象,关联Promise对象,后面用于napi_resolve_deferred() 或 napi_reject_deferred() 更新状态,返回数据。

[out] promise: 关联上面deferred对象的JS Promise对象,用于主线程方法返回。

返回值:返回napi_ok表示转换成功,其他值失败。

主线程方法接收参数,创建Promise,初始化异步工作项上下文数据,创建异步工作项,并将其加到调度队列。最后返回Promise对象。

static napi_value setLedStatusWithPromise(napi_env env, napi_callback_info info) {
//获取2个参数,值的类型是js类型(napi_value)
size_t argc = 2;
napi_value args[2];
napi_value thisArg = nullptr;
NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisArg, nullptr));
//创建promise
napi_value promise = nullptr;
napi_deferred deferred = nullptr;
NAPI_CALL(env, napi_create_promise(env, &deferred, &promise));
//异步工作项上下文用户数据,传递到异步工作项的execute、complete之间传递数据
auto addonData = new LedAddOnData{
.asyncWork = nullptr,
.deferred = deferred,
};
//将被收到的参数传入
NAPI_CALL(env, napi_get_value_int32(env, args[0], &addonData->args[0]));
NAPI_CALL(env, napi_get_value_int32(env, args[1], &addonData->args[1]));
//创建async work,创建成功后通过最后一个参数(addonData->asyncWork)返回asyncwork的handle
napi_value resourceName = nullptr;
napi_create_string_utf8(env, "setLedStatusWithPromise", NAPI_AUTO_LENGTH, &resourceName);
napi_create_async_work(env, nullptr, resourceName, executeCB, completeCBForPromise, (void *) addonData,
&addonData->asyncWork);
//将刚创建的async work加到队列,由底层去调度执行
napi_queue_async_work(env, addonData->asyncWork);
//返回promise
return promise;
}

worker线程执行业务逻辑计算,将结果存入上下文数据。

//业务逻辑处理函数,由worker线程池调度执行。
static void executeCB(napi_env env, void *data) {
LedAddOnData *addOnData = (LedAddOnData *) data;
int s32GpioNum = addOnData->args[0];
int bExport = UM_GPIO_EXPORTED;
int direction = UM_GPIO_DIRECTION_OUT;
int s32GetValue = -1;
UM_GPIO_IsExport(s32GpioNum, &s32GetValue);
if (s32GetValue == UM_GPIO_NOT_EXPORT) {
UM_GPIO_Export(s32GpioNum, bExport);
}
UM_GPIO_SetDirection(s32GpioNum, direction);
addOnData->result = UM_GPIO_SetValue(s32GpioNum, addOnData->args[1]);
}

执行Complete函数,从上下文数据中获取结果,将结果转换为JS类型,调用 napi_resolve_deferred、 napi_reject_deferred更新状态、返回结果给调用方。

static void completeCBForPromise(napi_env env, napi_status status, void *data) {
LedAddOnData *addOnData = (LedAddOnData *) data;
napi_value result = nullptr;
napi_create_int32(env, addOnData->result, &result);
napi_resolve_deferred(env, addOnData->deferred, result);
//删除napi_ref对象
if (addOnData->callback != nullptr) {
napi_delete_reference(env, addOnData->callback);
}
//删除异步工作项
napi_delete_async_work(env, addOnData->asyncWork);
delete addOnData;
}

修改device/unionman/a311d/system/cfg/init.A311D.cfg 文件,在boot的cmds中添加相关命令。

"write /sys/class/gpio/export 380",
"write /sys/class/gpio/export 381",
"write /sys/class/gpio/export 382",
"write /sys/class/gpio/export 383",
"write /sys/class/gpio/export 384",
"write /sys/class/gpio/export 385",
"write /sys/class/gpio/export 386",
"write /sys/class/gpio/export 387",
"write /sys/class/gpio/export 388",
"write /sys/class/gpio/export 389",
"chmod 666 /sys/class/gpio/gpio380/direction",
"chmod 666 /sys/class/gpio/gpio381/direction",
"chmod 666 /sys/class/gpio/gpio382/direction",
"chmod 666 /sys/class/gpio/gpio383/direction",
"chmod 666 /sys/class/gpio/gpio384/direction",
"chmod 666 /sys/class/gpio/gpio385/direction",
"chmod 666 /sys/class/gpio/gpio386/direction",
"chmod 666 /sys/class/gpio/gpio387/direction",
"chmod 666 /sys/class/gpio/gpio388/direction",
"chmod 666 /sys/class/gpio/gpio389/direction",
"chmod 666 /sys/class/gpio/gpio380/value",
"chmod 666 /sys/class/gpio/gpio381/value",
"chmod 666 /sys/class/gpio/gpio382/value",
"chmod 666 /sys/class/gpio/gpio383/value",
"chmod 666 /sys/class/gpio/gpio384/value",
"chmod 666 /sys/class/gpio/gpio385/value",
"chmod 666 /sys/class/gpio/gpio386/value",
"chmod 666 /sys/class/gpio/gpio387/value",
"chmod 666 /sys/class/gpio/gpio388/value",
"chmod 666 /sys/class/gpio/gpio389/value"

3、NAPI接口应用

编写定义.d.ts文件

编写接口定义@ohos.nameX.d.ts文件,放到OpenHarmony SDK目录ets${js_version}\api目录下。使用SDK 8 则${js_version}为3.1.6.6,SDK 7则为3.0.0.0。

注意@ohos.nameX必须和NAPI模块的BUILD.gn文件中ohos_shared_library(“nameX”)指定的动态库名一致。

基于OpenHarmony Native Api框架实现控制LED灯亮灭

使用DevEco Studio创建标准应用App,并引入模块:

基于OpenHarmony Native Api框架实现控制LED灯亮灭

基于OpenHarmony Native Api框架实现控制LED灯亮灭

import um_gpio from '@ohos.um_a311d';

使用select组件实现选择指定GPIO口的功能。

<select @change="changeGpio" style="font-size : 30fp; weights : 400;">
<option value="380">
UM_GPIO_01
</option>
...
<option value="389">
UM_GPIO_10
</option>
</select>

当下拉选择新值时,触发change事件并调用changeGpio函数。

changeGpio(msg) {
this.pin = Number(msg.newValue)
}

使用switch组件,当开关状态切换时触发事件调用switchChange方法。

<switch @change="switchChange">
</switch>
switchChange(e) {
if (e.checked) {
this.addLedEffect()
} else {
this.removeLedEffect()
}
}

新增两个方法,封装开启、关闭指定的LED灯。

//开灯
addLedEffect() {
um_gpio.setLedStatus(this.pin, um_gpio.LED_ON).then((result) => {
if (result === 0) {
prompt.showToast({
message: "开灯"
})
} else {
prompt.showToast({
message: "开灯失败"
})
}
})
},
//关灯
removeLedEffect() {
um_gpio.setLedStatus(this.pin, um_gpio.LED_OFF).then((result) => {
if (result === 0) {
prompt.showToast({
message: "关灯"
})
} else {
prompt.showToast({
message: "关灯失败"
})
}
})
}

点击File->Project Structure。

基于OpenHarmony Native Api框架实现控制LED灯亮灭

选择自动签名。

基于OpenHarmony Native Api框架实现控制LED灯亮灭

使用Micro USB数据线连接PC与开发板OTG口并接通电源后点击Run即可。

基于OpenHarmony Native Api框架实现控制LED灯亮灭

六、构建与烧录

进入源码根目录,执行如下命令进行版本编译

./build.sh --product-name a311d –ccache

编译完成后,效果如图所示:

基于OpenHarmony Native Api框架实现控制LED灯亮灭

编译完成后需要,进行对镜像进行打包,然后进行烧写。

执行以下命令固件打包。

./device/unionpi/build/packer-unionpi.sh

固件打包完成,生成路径为编译根目录下的out/a311d/packages/phone/images/OpenHarmony.img。

基于OpenHarmony Native Api框架实现控制LED灯亮灭

打开烧录工具,使用Micro USB数据线连接PC与开发板OTG口并接通电源,导入烧录包后开始烧录即可(可关闭校验IMG)。

基于OpenHarmony Native Api框架实现控制LED灯亮灭

基于OpenHarmony Native Api框架实现控制LED灯亮灭

基于OpenHarmony Native Api框架实现控制LED灯亮灭

系统烧录后,如系统版本未变,可直接使用hdc_std工具将新构建的out/a311d/packages/phone/system/lib/module/libum_a311d.z.so文件复制替换开发板系统中的/system/lib/module/libum_a311d.z.so文件,提升验证效率。参考命令如下:

hdc_std shell mount -o remount,rw /          //重新挂载为已经挂载了的文件系统(以读写权限挂载)
hdc_std file send libum_a311d.z.so /system/lib/module/

使用hdc工具还有另一个好处就是调试过程中不需要将Micro USB数据线在开发板OTG口和DEBUG口来回切换,不管是烧录,串口调试还是应用安装都是连接OTG口就行。

hdc_std工具获取方式:

通过OpenHarmony sdk获取,hdc_std在sdk的toolchains目录下,例如笔者的hdc工具存放路径为:C:\Users\haoyuan.chen\AppData\Local\OpenHarmony\Sdk\toolchains\3.1.6.6。

基于OpenHarmony Native Api框架实现控制LED灯亮灭

将其添加到环境变量,显示以下结果即可。

基于OpenHarmony Native Api框架实现控制LED灯亮灭

更多资料请参考:​​OpenAtom OpenHarmony​​。

七、演示效果

演示效果请移步到gitee仓库。

​演示视频​

八、参考资料

​标准设备应用开发—Native Api-开源基础软件社区-51CTO.COM​​。

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​​。

© 版权声明

相关文章