GPIO服务开发流程说明

1.1 开发流程

2.2.1 创建目录结构

foundation下新增mhbase目录,手动创建如下目录:

/foundation/mhbase

├── gpio

│   ├── etc       # 服务启动配置目录----配置服务启动相关信息

│   ├── frameworks   # 框架实现存在目录

│       │── napi   # NAPI接口目录

│       └── proxy   # 客户端代理目录

│   ├── interfaces   # 接口目录

│   ├── sa_profile   # sa服务配置目录----配置服务的动态库信息

│   ├── services     # 组件服务端目录

│   ├── utils       # 工具类目录

```

 

2.2.2 构建代码流程

 

 

1.2 业务逻辑

GPIO服务框架为应用提供控制GPIO引脚的功能,能够对其进行读取和写入。通过IPC(进程间通信)机制实现应用与服务的跨进程通讯。应用作为请求方(Client)可获取GPIO服务(Server)提供的代理 (Proxy),并通过此代理读写数据来实现进程间的数据通信。

2. 实现步骤

2.1 在矿鸿系统上创建标准服务

3.1.1 定义接口类(Igpio)

接口类继承IRemoteBroker,定义描述符、业务函数和消息码。

路径:gpio/interfaces/innerkits/native/proxy/include/i_gpio.h

3.1.2 实现服务提供端(GpioServiceStub)

该类是和IPC框架相关的实现,需要继承自IRemoteStub<Igpio>。Stub端作为接收请求的一端,需重写OnRemoteRequest方法用于接收客户端调用。

3.1.3 实现服务请求端(GpioServiceProxy)

该类是Proxy端实现,继承自IRemoteProxy<Igpio>,调用SendRequest接口向Stub端发送请求,对外暴露服务端提供的能力。

3.1.4 注册及启动服务

SA需要将自己的GpioService实例通过AddSystemAbility接口注册到SystemAbilityManager。

GpioService继承于SystemAbility和GpioServiceStub,注册方法如下:

namespace {

auto ms = DelayedSpSingleton<GpioService>::GetInstance();

const bool G_REGISTER_RESULT = SystemAbility::MakeAndRegisterAbility(ms.GetRefPtr());

}

 

SA获取到实例后会调用GpioService中的OnStart()方法,进行发布

Publish(DelayedSpSingleton<GpioService>::GetInstance())

 

3.1.5 获取服务

通过SystemAbilityManager的GetSystemAbility方法可获取到对应SA的代理IRemoteObject,参考GpioServiceClient类中方法Connect()。

GpioServiceClient中的内部类GpioDeathRecipient继承于IRemoteObject::DeathRecipient,可以订阅RemoteObject的死亡通知,当收到死亡通知时,会调用内部类中的onRemoteDie方法。

3.1.6 编写Gn文件生成 libgpioservice.z.so

矿鸿系统中大多以编写Gn文件的方式生成动态库文件,gpio服务的代码也不例外。如下是Gn文件的简单介绍,具体代码可参考gpio/services/BUILD.gn

import("//build/ohos.gni") #引用的gni路径

import("//foundation/mhbase/gpio/gpio.gni")

 

config("gpio_service_config") { #配置模块

  include_dirs = [ #动态库里要用到的头文件 ]

}

ohos_shared_library("gpio_service") { #生成的成动态库名称

  sources = [ 

    "src/gpio_service.cpp",

    "src/gpio_service_stub.cpp",

"src/gpio_manager.cpp", #包含的cpp文件

]

  configs = [ ":gpio_service_config" ] #包含配置模块

  deps = [ #依赖的gpio子系统内的动态库

    "${gpio_frameworks_path}/proxy:gpio_proxy",

  ]

  external_deps = [ #依赖gpio子系统外的动态库

    "hiviewdfx_hilog_native:libhilog", #hilog动态库

    "ipc:ipc_core", #ipc动态库,用于跨进程通讯

    "utils_base:utils",  #常用工具类动态库

    "safwk:system_ability_fwk", #系统服务管理子系统

    "samgr_standard:samgr_proxy", #samgr模块动态库

  ]

  subsystem_name = " mhbase" #模块名称

  part_name = "${gpio_part_name}" #子系统名称,定义在gpio/gpio.gni

}

 

 

3.1.7 SA服务加载 libgpioservice.z.so

配置XML文件,路径:gpio/sa_profile/8120.xml,该文件配置了动态库的各种信息,SA服务会解析XML,同时将动态库加载到自身进程。注意GN文件需要按如下方式

import("//foundation/mhabse/gpio/gpio.gni")

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

 

ohos_sa_profile("gpio_sa_profile") {

  sources = [ "8120.xml" ]

  part_name = "${gpio_part_name}"

}

 

 

3.1.8 服务启动的相关配置

每次开机,init阶段会读取配置gpioservice.cfg,启动gpioservice。即拉起服务动态库。路径:gpio/etc/gpioservice.cfg

{

    "jobs" : [{

            "name" : "init",

            "cmds" : []

        },{

            "name" : "post-fs",

            "cmds" : [

                "start gpioservice"

            ]

        }

    ],

    "services" : [

        {

            "name" : "gpioservice",

            "path" : ["/system/bin/sa_main", "/system/profile/gpioservice.xml"],

            "uid" : "root",

            "gid" : ["system", "shell", "uhid"],

            "caps" : ["DAC_READ_SEARCH", "DAC_OVERRIDE", "CHOWN"],

            "start-mode" : "condition",

            "secon" : "u:r:gpioservice:s0"

        }

    ]

}

 

注意:gpioservice.cfg文件要安装在init路径下,所以gn文件这样写:(可参考gpio/etc/BUILD.gn)

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

import("//foundation/mhbase/gpio/gpio.gni")

 

ohos_prebuilt_etc("gpioservice.rc") {

  source = "gpioservice.cfg"

  relative_install_dir = "init"

  part_name = "gpioservice"

  subsystem_name = " mhbase "

}

 

 

2.2 服务启动配置

3.2.1 在编译构建主目录下,增加mhbase模块信息,及子系统信息

配置文件路径:build/subsystem_config.json   

"mhbase": {

        "path": "foundation/mhbase",

        "name": " mhbase"

    }

 

 

3.2.2  HiLog模块增加配置子系统的hilog信息(矿鸿代码中未找到文件,未配置不影响系统启动)

HiLog 是 OpenHarmony 日志系统,提供给系统框架、服务、以及应用打印日志,记录用户操作、系统运行状态等。如果你的代码中需要用到系统级Hilog功能,则需要引用到该模块。在编译lib库时需要增加如下相关依赖:(可参考gpio/frameworks/proxy/BUILD.gn)

external_deps = [

    "hiviewdfx_hilog_native:libhilog",

  ]

 

 

配置文件路径:base/hiviewdfx/hilog/services/hilogd/log_domains.cpp

{0xD005000, "GpioService"},

 

 

3.2.3 samgr模块配置服务属性

samgr组件是OpenHarmony的核心组件,提供OpenHarmony系统服务启动、注册、查询等功能。如果你的代码中涉及开发服务相关功能,则需要引用到该模块。在编译lib库时需要增加如下相关依赖:(可参考gpio/frameworks/proxy/BUILD.gn)

external_deps = [

"safwk:system_ability_fwk",

" samgr_standard:samgr_proxy "

  ]

 

在该模块下配置服务名称及ID: GPIO_SERVICE_SA_ID =  8120。配置文件路径:foundation/systemabilitymgr/samgr/interfaces/innerkits/samgr_proxy/include/system_ability_definition.h

GPIO_SERVICE_SA_ID                                = 8120,

{ GPIO_SERVICE_SA_ID, "GpioService"},

 

3.2.4

修改配置文件:productdefine/commom/products/rk3568.json

"mhservice:gpioservice": {}

 

3.2.5 gpio目录下编写bundle.json文件

代码在编译时,会根据subsystem_config.json的映射(3.2.1中的配置项),扫描foundation/mhbase此目录下的所有bundle.json文件,找到子系统名为gpioservice的bundle.json(可以有多个),即参与编译的bundle.json文件为foundation/mhbase/gpioservice/bundle.json。

bundle.json中需要包含子系统中参与的编译的所有模块。

"deps": {  

    "components": [  #编译模块依赖的其他子系统

        "hiviewdfx_hilog_native",

        "ipc",

        "safwk",

        "samgr",

        "appexecfwk_standard",

        "napi"

    ],

    "third_party": [  #编译的模块的三方库依赖

        "quickjs",

        "node",

        "libuv"

    ]

},

"build": {

    "sub_component": [   #需要编译的模块

        "//foundation/mhbase/gpio/frameworks/napi:gpio_napi",

        "//foundation/ mhbase /gpio/frameworks/proxy:gpio_proxy",

        "//foundation/ mhbase /gpio/sa_profile:gpio_sa_profile",

        "//foundation/ mhbase /gpio/services:gpio_service",

        "//foundation/ mhbase /gpio/etc:gpioservice.rc"

    ],

    "inner_kits": [],

    "test": []

}

 

 

2.3 NAPI开发

NAPI适合封装IO、CPU密集型、OS底层等能力并对外暴露JS接口,通过NAPI可以实现JS与C/C++代码互相访问。可以通过NAPI接口构建例如网络通信、串口访问、多媒体解码、传感器数据收集等模块。

我们需要按照NAPI框架的机制要求,实现注册相关动作,告诉系统这个lib库的名称,提供了哪些native方法,以及它们对应的js接口名称是什么。

具体代码可参考: gpio/frameworks/napi/src/js_gpio_napi.cpp

 

3.3.1 注册NAPI模块

使用__attribute__((constructor))标记函数,napi_module_register函数注册模块

extern "C" __attribute__((constructor)) void RegisterModule()

{

    napi_module_register(&gpioModule);

}

 

 

3.3.2 创建NAPI结构体

创建一个NAPI模块属性说明的结构体,重点需要关注的属性nm_register_func和nm_modname。前者需要实现Init函数,声明导出的API,后者是API的包名,供JS调用。

static napi_module gpioModule = {

    .nm_version = 1,

    .nm_flags = 0,

    .nm_filename = nullptr,

    .nm_register_func = Init,

    .nm_modname = "gpio_napi",

    .nm_priv = reinterpret_cast<void*>(0),

    .reserved = {0},

};

 

 

3.3.3 声明要导出的API

setStatus为导出函数的名称,供JS调用。

napi_value Init(napi_env env, napi_value exports)

{

    GPIO_LOGI("Init enter");

    napi_property_descriptor desc[] = {

        DECLARE_NAPI_FUNCTION("setStatus", SetGpioStatus),

    };

    CHKRP(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc), "napi_define_properties");

    return exports;

}

 

 

3.3.4 导出函数的具体实现

static napi_value SetGpioStatus(napi_env env, napi_callback_info info)

{

    GPIO_LOGI("SetGpioStatus enter");

    napi_value thisVar = nullptr;

    size_t argc = 0;

    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, nullptr, &thisVar, nullptr)); //获取JS传参个数

    napi_value args[2] = { 0 }; // 2: args

    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thisVar, nullptr)); //根据参数个数,获取参数

    NAPI_ASSERT(env, argc == 2, "Wrong number of arguments"); //判断参数个数是不是2个

    napi_valuetype valuetype = napi_undefined;

    NAPI_CALL(env, napi_typeof(env, args[0], &valuetype)); //获取第一个参数的类型

    NAPI_ASSERT(env, valuetype == napi_number, "Wrong argument info. Object expected."); //判断第一个参数的类型是不是数字

    int32_t gpio = -1, gpioStatus = -1;

    napi_get_value_int32(env, args[0], &gpio);

    napi_get_value_int32(env, args[1], &gpioStatus);

 //将参数由napi_value转换为int32_t类型

int32_t res = GpioServiceClient::GetInstance().SetGpioStatus(gpio, gpioStatus); //调用client的方法

    napi_value result = nullptr;

    NAPI_CALL(env, napi_create_int32(env, res, &result)); //返回值传递给JS

    return result;

}

 

 

3.3.5 生成napi lib库文件

编写GN文件生成动态库文件,注意:NAPI库文件需要安装在module路径下,否则JS可能无法调用。

ohos_shared_library("gpio_napi") {

  #具体内容查看文件 gpio/frameworks/napi/BUILD.gn

  subsystem_name = "mhbase" #模块名称

  part_name = "${gpio_part_name}" #子系统名称

  relative_install_dir = "module"

#安装到/system/lib/module下,NAPI库必须安装到/system/lib/module这个目录

}

 

 

 

2.4 应用开发

 

3.4.1 开发工具

DevEco Studio 3.0.0.993

3.4.2 开发测试代码

// @ts-nocheck

import gpio from '@ohos.gpio_napi';
@Entry
@Component
struct Index {
  @State flag: boolean = true

  build() {
    Row() {
      Column() {
        Button('手电筒')
          .height(70)
          .width('40%')
          .fontSize(16)
          .onClick(() => {
            console.log("Gpio 0 start ");
            if (this.flag) {
              var res1 = gpio.setStatus(1, 1);
            } else {
              var res1 = gpio.setStatus(1, 0);
            }
            console.log("Gpio gpio_napi.SetStatus " + res1);
            this.flag = !this.flag
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

 

 3.4.2 运行hap包

#安装HAP包

hdc_std -t 150100424a544434520369874e908800 install

C:\......\default\entry-default-signed.hap

#卸载HAP包

hdc_std -t 150100424a544434520369874e908800 install  C:\......\default\entry-default-signed.hap

 

3. 注意事项

本文档仅提供开发指导,已在MHBase_std 3.1-Release分支上验证成功。具体代码请参考//foundation/mhbase/gpio

 

ps -ef | grep gpio 查看服务命令

cat /sys/kernel/debug/gpio  查看gpio占用

hilog | grep Gpio  查看Gpio log信息