(基于OpenHarmony Native Api架構實作控制LED燈亮滅)
一、樣例介紹
基于OpenHarmony NAPI架構實作北向應用端控制南向裝置端擴充闆指定GPIO口對應LED燈的亮滅
二、開發環境
- 搭載OpenHarmony-3.1-release版本的Unionpi Tiger開發闆
- DevEco Studio 3.0.0.991
- 40PIN測試小闆
- Ubuntu20.04虛拟機
- 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整體遵從分層設計,從下向上依次為:核心層、系統服務層、架構層和應用層。系統功能按照“系統 > 子系統 > 元件”逐級展開,在多裝置部署場景下,支援根據實際需求裁剪某些非必要的子系統或元件。子系統是一個邏輯概念,它具體由對應的元件構成。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI2YDOfhGLwIDOfdHLlpXazVmcvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5CM3MzNhlDZmZmN1YzMhBjZ4ETM3cDOwIWZkVjN5QzNxIjMxcTNi9CX4AjMyAjMvw1cldWYtl2Lc12bj5yb0NWM14ycvlnbv1mchhWLsR2Lc9CX6MHc0RHaiojIsJye.png)
四、NAPI架構簡介
NAPI(Native API)是 OpenHarmony 标準系統的一種JS API實作機制,适合封裝IO、CPU密集型、OS底層等能力并對外暴露JS接口,實作JS與C/C++代碼互相通路。
五、實作步驟
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“:{}在産品定義中添加元件。
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表示轉換成功,其他值失敗。
異步工作項工作時序圖
定義異步工作項上下文資料
根據實際場景需要定義異步工作項上下文資料結構,用于在主線程方法、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")指定的動态庫名一緻。
使用DevEco Studio建立标準應用App,并引入子產品:
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
選擇自動簽名
使用Micro USB資料線連接配接PC與開發闆OTG口并接通電源後點選Run即可
六、建構與燒錄
進入源碼根目錄,執行如下指令進行版本編譯
./build.sh --product-name a311d –ccache
編譯完成後,效果如圖所示:
編譯完成後需要,進行對鏡像進行打包,然後進行燒寫。
執行以下指令固件打包
./device/unionpi/build/packer-unionpi.sh
固件打包完成,生成路徑為編譯根目錄下的out/a311d/packages/phone/images/OpenHarmony.img。
打開燒錄工具,使用Micro USB資料線連接配接PC與開發闆OTG口并接通電源,導入燒錄包後開始燒錄即可(可關閉校驗IMG)
系統燒錄後,如系統版本未變,可直接使用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
将其添加到環境變量,顯示以下結果即可
更多資料請參考:OpenAtom OpenHarmony
七、示範效果
示範效果請移步到gitee倉庫
示範視訊
八、參考資料
标準裝置應用開發—Native Api-開源基礎軟體社群-51CTO.COM
九、項目位址
unionpi_tiger/sample/napi/gpioled · Haoc_小源同學/vendor_unionman - 碼雲 - 開源中國 (gitee.com)
【本文正在參加物聯網有獎征文活動】,活動連結:https://ost.51cto.com/posts/14758;