天天看點

#物聯網征文# 基于OpenHarmony Native Api架構實作控制LED燈亮滅

(基于OpenHarmony Native Api架構實作控制LED燈亮滅)

一、樣例介紹

基于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

九、項目位址

unionpi_tiger/sample/napi/gpioled · Haoc_小源同學/vendor_unionman - 碼雲 - 開源中國 (gitee.com)

【本文正在參加物聯網有獎征文活動】,活動連結:https://ost.51cto.com/posts/14758;

繼續閱讀