深圳幻海软件技术有限公司 欢迎您!

OpenHarmony3.0上采用ets开发HAP控制LED灯

2023-02-28

想了解更多内容,请访问:51CTO和华为官方合作共建的鸿蒙技术社区https://harmonyos.51cto.com1.开发环境硬件:Hi3516DV300开发板软件:OpenHarmony3.0系统工具:DevEcoStudio3.02.功能简介OpenHarmony3.0采用了方舟开发框架a

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

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) 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.

放了两个图片,表示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("//build/ohos.gni"
 
ohos_shared_library("led") { 
  include_dirs = [ 
    "//third_party/node/src"
    "//foundation/ace/napi/interfaces/kits"
  ] 
 
  sources = [ 
    "led_javascript_class.cpp"
    "native_module_led.cpp"
  ] 
 
  deps = [ "//foundation/ace/napi:ace_napi" ] 
 
  relative_install_dir = "module" 
   
  external_deps = [ "hiviewdfx_hilog_native:libhilog" ] 
   
  subsystem_name = "ace" 
  part_name = "napi" 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

 目录native_module_led下native_module_led.cpp文件修改部分摘要:

包含的头文件和宏定义,

#include <stdlib.h>     // standard library 标准库函数头文件 
#include <stdio.h>      // standard input output 标准输入输出函数 
#include <stdint.h>     // 定义了扩展的整数类型和宏 
 
#include <unistd.h>     // POSIX 系统 API 访问功能的头文件 
#include <fcntl.h>      // unix标准中通用的头文件 define O_WRONLY and O_RDONLY   
 
// #include <string.h> 
#define GPIO_DIR_IN (char*)"in" 
#define GPIO_DIR_OUT (char*)"out" 
#define GPIO_VAL_LOW 0 
#define GPIO_VAL_HIGH 1 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

 添加函数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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

初始化部分,

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 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

 模块定义及注册,

/* 
 * 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); 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

 目录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" ] 
  } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

 最后在源码根目录下执行编译命令,

$./build.sh --product-name Hi3516DV300 --build-target make_test 
  • 1.

生成的文件为:

out/ohos-arm-release/ace/napi/libled.z.so 
  • 1.

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"
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

6. 系统部署

6.1 拷贝动态库

生成的.z.so动态库已经拷贝到PC上E:\libled.z.so

PC串口控制台:

#mount -o remount,rw / 
  • 1.

PC命令窗口cmd:

E:>hdc_std file send E:\libled.z.so /system/lib/module/ 
  • 1.

PC串口控制台:

#chmod 666 /system/lib/module/libled.z.so 
  • 1.

6.2 安装应用

PC命令窗口cmd:

E:>hdc_std install E:\Projects\HarmonyProject\MyLed\build\outputs\hap\debug\phone\entry-debug-standard-ark-signed.hap 
  • 1.

7. 应用测试

点击打开按钮,LED图标变绿,同时LED灯亮,


点击关闭按钮,LED图标变灰,同时LED灯灭。


8. 动图展示

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com