1. 開發環境
硬件:Hi3516DV300開發板
軟件:OpenHarmony3.0系統
工具:DevEco Studio 3.0
2. 功能簡介
OpenHarmony3.0采用了方舟開發框架arkUI,支持了基于TS擴展的聲明式開發范式eTS,本文使用ets開發語言,構造一個應用程序,實現通過上層HAP控制底層LED燈的亮與滅。
3. 實現原理
如果在Android上實現,需要通過java調用jni實現對底層的訪問。但是在OpenHarmony上,HAP采用ets語言開發,沒有發現嵌入到HAP當中的類JNI語言,但是系統也提供了一個訪問底層的機制,叫做NAPI,不過這部分是在系統層實現的,不隨HAP一起發布。我們想要實現控制LED燈的功能,是在NAPI部分通過C語言實現的,然后編譯為xxx.z.so動態庫,它向上層提供了一個控制接口。綠色LED燈對應GPIO2_3,計算出編號:2*8+3=19,所以直接控制gpio19下的value值就可以控制LED燈亮滅了。
4. 具體實現
整個功能的實現分為了上層HAP應用開發和底層.z.so庫的開發兩部分。
4.1 應用的開發
1.在HUAWEI DevEco Studio中,創建一個 [Standard]Empty Ability 。

開發語言選擇 “eTS”,可以注意到API Version僅支持7,說明是在OpenHarmony3中新支持的,也僅在OpenHarmony3中支持,這些功能實際上都是測試版本,穩定了之后就會在HarmonyOS中使用了,但目前還沒有發布。

工程創建完畢后,我直接在pages目錄結構下右擊新建了一個ets page,取名led。

我們頁面的樣式、布局和控制全都在led.ets這個文件里了,不再像js分為css、hml和js三個文件。
Led.ets 文件內容
- import led from '@ohos.led'
- @Entry
- @Component
- struct Led {
- @State private imgpath: string = 'app.media.ledoff'
- @State private isShow: boolean= false
- build() {
- Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
- Text('LED 燈控制')
- .fontSize(25)
- .fontWeight(FontWeight.Bold)
- .margin({bottom: 30})
- Image($r('app.media.ledoff'))
- .objectFit(ImageFit.Contain)
- .width(150)
- .height(150)
- .visibility(this.isShow ? Visibility.None : Visibility.Visible)
- Image($r('app.media.ledon'))
- .objectFit(ImageFit.Contain)
- .width(150)
- .height(150)
- .visibility(this.isShow ? Visibility.Visible : Visibility.None)
- Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.SpaceEvenly }) {
- Button('關閉', { type: ButtonType.Capsule, stateEffect: true }).backgroundColor(0x317aff).width(150).height(50).backgroundColor("#aaaaaa")
- .onClick((event: ClickEvent) => {
- this.isShow = false
- led.switchLed(19, 0);
- })
- Button('打開', { type: ButtonType.Capsule, stateEffect: true }).backgroundColor(0x317aff).width(150).height(50)
- .onClick((event: ClickEvent) => {
- this.isShow = true
- led.switchLed(19, 1);
- })
- }.width("100%")
- .margin({ top: 50 })
- }
- .width('100%')
- .height('100%')
- .padding(10)
- }
- }
放了兩個圖片,表示LED燈狀態的,放在了代碼的entry\src\main\resources\phone\media目錄。

導入的ohos.led 庫,是自己添加的NAPI層的動態庫,后面會繼續介紹。
在config.json文件中,記得把led.ets放到js部分pages數組的第一位,因為它是要顯示的首頁面。
代碼中使用了兩個Button組件,一個打開,一個關閉,因為在ets中還沒有類似js中的switch的組件,頁面中有兩個image組件,分別顯示打開和關閉的圖像,通過設置visibility屬性來切換狀態,本來想通過動態設置image的源來改變圖像內容,但沒找到有效的方法,應該是支持的,只是自己還沒了解怎么用。
編譯前記得一定要設置簽名,否則編譯出來的程序無法安裝。

最后可以編譯程序了,

生成的最終HAP在 build\outputs\hap\debug\phone\entry-debug-standard-ark-signed.hap
4.2 動態庫的開發
動態庫需要在OpenHarmony源碼中添加和編譯,本文使用的是OpenHarmony3.0源碼,在foundation/ace/napi/sample目錄下,復制一份native_module_demo,重命名為native_module_led,里面的文件也相應的修改名字,注意文件里調用也相應的修改成正確的名字,否則編譯會報錯。

主要修改的文件有,
foundation/ace/napi/sample/native_module_led/BUILD.gn
foundation/ace/napi/sample/native_module_led/native_module_led.cpp
foundation/ace/napi/BUILD.gn
目錄native_module_led下BUILD.gn文件:
- import("http://build/ohos.gni")
- ohos_shared_library("led") {
- include_dirs = [
- "http://third_party/node/src",
- "http://foundation/ace/napi/interfaces/kits",
- ]
- sources = [
- "led_javascript_class.cpp",
- "native_module_led.cpp",
- ]
- deps = [ "http://foundation/ace/napi:ace_napi" ]
- relative_install_dir = "module"
- external_deps = [ "hiviewdfx_hilog_native:libhilog" ]
- subsystem_name = "ace"
- part_name = "napi"
- }
目錄native_module_led下native_module_led.cpp文件修改部分摘要:
包含的頭文件和宏定義,
-
#include
// standard library 標準庫函數頭文件 -
#include
// standard input output 標準輸入輸出函數 -
#include
// 定義了擴展的整數類型和宏 -
#include
// POSIX 系統 API 訪問功能的頭文件 -
#include
// unix標準中通用的頭文件 define O_WRONLY and O_RDONLY -
// #include
- #define GPIO_DIR_IN (char*)"in"
- #define GPIO_DIR_OUT (char*)"out"
- #define GPIO_VAL_LOW 0
- #define GPIO_VAL_HIGH 1
添加函數SwitchLed的具體實現,
- static napi_value SwitchLed(napi_env env, napi_callback_info info)
- {
- HILOG_INFO("hey, SwitchLed - 0");
- size_t requireArgc = 2;
- size_t argc = 2;
- napi_value args[2] = { nullptr };
- NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
- NAPI_ASSERT(env, argc >= requireArgc, "Wrong number of arguments");
- napi_valuetype valuetype0;
- NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0));
- napi_valuetype valuetype1;
- NAPI_CALL(env, napi_typeof(env, args[1], &valuetype1));
- NAPI_ASSERT(env, valuetype0 == napi_number && valuetype1 == napi_number, "Wrong argument type. Numbers expected.");
- uint32_t gpio;
- NAPI_CALL(env, napi_get_value_uint32(env, args[0], &gpio));
- uint32_t val;
- NAPI_CALL(env, napi_get_value_uint32(env, args[1], &val));
- char direction[100] = {0};
- sprintf(direction,"echo out > /sys/class/gpio/gpio%d/direction", gpio);
- system(direction);
- char value[100] = {0};
- sprintf(value,"echo %d > /sys/class/gpio/gpio%d/value", val, gpio);
- system(value);
- napi_value sum;
- NAPI_CALL(env, napi_create_double(env, 1.0f, &sum));
- return sum;
- }
初始化部分,
- EXTERN_C_START
- /*
- * function for module exports
- */
- static napi_value Init(napi_env env, napi_value exports)
- {
- /*
- * Properties define
- */
- napi_property_descriptor desc[] = {
- DECLARE_NAPI_FUNCTION("add", Add),
- DECLARE_NAPI_FUNCTION("minus", Minus),
- DECLARE_NAPI_FUNCTION("switchLed", SwitchLed),
- DECLARE_NAPI_FUNCTION("TestPromise", TestPromise),
- DECLARE_NAPI_FUNCTION("TestPromiseOrAsyncCallback", TestPromiseOrAsyncCallback),
- };
- NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
- DemoJavascriptClassInit(env, exports);
- return exports;
- }
- EXTERN_C_END
模塊定義及注冊,
- /*
- * Module define
- */
- static napi_module ledModule = {
- .nm_version = 1,
- .nm_flags = 0,
- .nm_filename = nullptr,
- .nm_register_func = Init,
- .nm_modname = "led",
- .nm_priv = ((void*)0),
- .reserved = { 0 },
- };
- /*
- * Module register function
- */
- extern "C" __attribute__((constructor)) void RegisterModule(void)
- {
- napi_module_register(&ledModule);
- }
目錄napi下BUILD.gn文件,
- group("napi_packages_test") {
- testonly = true
- deps = [
- "sample/native_module_demo:demo",
- "sample/native_module_netserver:netserver",
- "sample/native_module_storage:storage",
- "test/unittest:unittest",
- "sample/native_module_led:led",
- ]
- if (is_standard_system) {
- deps += [ "sample/native_module_ability:ability" ]
- }
- }
最后在源碼根目錄下執行編譯命令,
- $./build.sh --product-name Hi3516DV300 --build-target make_test
生成的文件為:
- out/ohos-arm-release/ace/napi/libled.z.so
5. 系統設置
需要授予應用訪問gpio下export文件的權限,
device/hisilicon/hi3516dv300/build/rootfs/init.Hi3516DV300.cfg
- "name" : "boot",
- "cmds" : [
- "write /sys/class/gpio/export 19",
- "chmod 777 /sys/class/gpio/gpio19/direction",
- "chmod 777 /sys/class/gpio/gpio19/value",
6. 系統部署
6.1 拷貝動態庫
生成的.z.so動態庫已經拷貝到PC上E:\libled.z.so
PC串口控制臺:
- #mount -o remount,rw /
PC命令窗口cmd:
- E:>hdc_std file send E:\libled.z.so /system/lib/module/
PC串口控制臺:
- #chmod 666 /system/lib/module/libled.z.so
6.2 安裝應用
PC命令窗口cmd:
- E:>hdc_std install E:\Projects\HarmonyProject\MyLed\build\outputs\hap\debug\phone\entry-debug-standard-ark-signed.hap
7. 應用測試
點擊打開按鈕,LED圖標變綠,同時LED燈亮,

點擊關閉按鈕,LED圖標變灰,同時LED燈滅。

8. 動圖展示

原文鏈接:https://harmonyos.51cto.com