itopen组织
1、提供OpenHarmony优雅实用的小工具
2、手把手适配riscv + qemu + linux的三方库移植
3、未来计划riscv + qemu + ohos的三方库移植 + 小程序开发
4、一切拥抱开源,拥抱国产化
一、环境准备工作
1.1 Ubuntu20.04环境配置
如果已经配置OpenHarmony的编译环境则不需要要配置
# root环境配置
git clone https://gitee.com/itopen/openharmony_env_init
cd openharmony_env_init
bash root_init.sh
# 个人用户环境配置
git config --global user.name "yourname"
git config --global user.email "your-email-address"
git config --global core.editor vim
git config --global credential.helper store
git config --global http.sslverify false
1.2 下载、安装DevEco Studio
dayu800使用的是Openharmony-3.2-Release分支,所对应的API版本为9,DevEco Studio目前最新版的5.x支持的API版本为10、11、12,并不支持9,所以需要下载3.1.1Release版本
下载完毕后安装,可以直接勾选修改“PATH”后重启,这样不用手动添加环境变量
1.3 初始化DevEco Studio环境
以管理员权限启动DevEco,然后create project,选择空项目即可,按照提示完成环境配置,之后根据UI提示等待环境配置完成。
有可能还需要配置SDK,在工具栏file>setting>SDK中配置。
二、下载、编译、烧录duyu800代码
2.1 下载duyu800代码
代码下载使用自动化下载工具下载,关于自动化下载工具使用参见itopen: 一键下载openharmony代码
# 下载dayu800代码,关于
git clone https://gitee.com/itopen/ohos_download.git
cd ohos_download
./ohos_download
# 选择1.1
2.2 编译dayu800代码
# 首次编译需要下载预处理文件
cd ~/Openharmony/dayu800-ohos
./build/prebuilts_download.sh
# 全量编译代码,添加--gn-args full_mini_debug=false这次参数会编译比较快,否则中间会有一段停留30分钟的时间
./build.sh --product-name dayu800 --gn-args full_mini_debug=false --ccache
2.3 镜像烧录
关于dayu800的烧录参考itopen: dayu800开发板使用说明
三、NAPI Demo介绍
3.1 napi demo代码处理
将itopen: napi_demo代码下载放置到dayu800-ohos代码的device/soc/thead/th1520/hardware目录下,然后在BUILD.gn中添加napi_demo模块
cd device/soc/thead/th1520/hardware
git clone https://gitee.com/itopen/napi_demo.git
vim BUILD.gn
# 添加napi_demo:napi_demo
group("hardware_group") {
deps = [
"bootanimation:bootanimation",
"isp8000:isp8000",
"camera:camera",
"hap:th1520_hap",
"napi_demo:napi_demo", # 第一个napi_demo表示napi_demo目录,第二个napi_demo表示napi_demo目录下BUILD.gn中的napi_demo模块
]
}
3.2 查看接口功能
关于NAPI有哪些接口请查看OpenHarmony NAPI接口介绍和使用
NAPI提供了提供了一系列接口函数,需要声明包含如下2个头文件中
#include "napi/native_api.h"
#include "napi/native_node_api.h"
该头文件在//foundation/arkui/napi/interfaces/kits/napi和//foundation/arkui/napi/interfaces/inner_api/napi之中
3.3 代码简单介绍
代码结构如下
.
├── BUILD.gn
├── CMakeLists.txt
├── include
│ ├── i_serialport_client.h
│ ├── log
│ │ └── serialport_log_wrapper.h
│ ├── serial_callback_base.h
│ └── serialport_types.h
├── serial_async_callback.cpp
├── serial_async_callback.h
├── serial_helper.cpp
├── serial_opt.cpp
├── serial_opt.h
├── types
│ └── libserialhelper
│ ├── package.json
│ └── serialhelper.d.ts
├── x_napi_tool.cpp
└── x_napi_tool.h
下面以一段打开串口的代码为例进行说明。
void OpenSerial_execute(XNapiTool *pxt, void *data)
{
OpenSerial_value_struct *vio = (OpenSerial_value_struct *)data;
vio->out = OpenSerial(vio->in0.c_str());
return;
}
void OpenSerial_complete(XNapiTool *pxt, void *data)
{
int32_t ret = -1;
OpenSerial_value_struct *vio = (OpenSerial_value_struct *)data;
napi_value result = nullptr;
result = NUMBER_C_2_JS(pxt, Int32, vio->out);
{
napi_value args[XNapiTool::ARGV_CNT] = {result, nullptr};
if(vio->out > 0)
ret = 0;
pxt->FinishAsync(ret, args);
}
delete vio;
}
napi_value OpenSerial_middle(napi_env env, napi_callback_info info)
{
XNapiTool *pxt = std::make_unique<XNapiTool>(env, info).release();
if (pxt->IsFailed()) {
napi_value err = pxt->GetError();
delete pxt;
return err;
}
struct OpenSerial_value_struct *vio = new OpenSerial_value_struct();
pxt->SwapJs2CUtf8(pxt->GetArgv(0), vio->in0);
napi_value result = pxt->StartAsync(OpenSerial_execute, vio, OpenSerial_complete,
pxt->GetArgc() == 2 ? pxt->GetArgv(1) : nullptr);
if (pxt->IsFailed()) {
result = pxt->GetError();
}
return result;
}
- 这段C++函数用于在node.js环境中通过NAPI异步打开串口
- 主要流程如下
- JavaScript调用:从JavaScript代码中调用 openSerial函数。
- 中间层函数:OpenSerial_middle函数被调用,准备参数并启动异步操作。
- 后台线程执行:OpenSerial_execute函数在后台线程中执行,执行实际的串口打开操作。
- 主线程回调:一旦后台操作完成,OpenSerial_complete函数在主线程中被调用,处理结果并回调到JavaScript。
3.4 创建类型声明文件
类型声明文件的命名方式为动态库名称.d.ts,参照以下编写
/*
* Copyright (C) 2021-2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {AsyncCallback, Callback} from "basic";
/**
* Provides methods related to serialport services.
*
* @since 7
* @syscap SystemCapability.Miscservices.SerialportService
*/
declare namespace serialHelper {
/**
* Set serial port options.
* @param dev Indicates the serial port dev.
* @param speeds baud rate.
* @param bits 7/8.
* @param events 'O'/'E'/'N'
* @param stops 1/2
*/
function setOptions(dev:string, speeds:number, bits:number, events:number, stops: number, callback: AsyncCallback<void>): void;
function setOptions(dev:string, speeds:number, bits:number, events:number, stops: number): Promise<void>;
/**
* Open serial port.
* @param dev Indicates the serial port dev.
*/
function openSerial(dev:string, callback: AsyncCallback<void>): void;
function openSerial(dev:string): Promise<void>;
/**
* Close serial port.
* @param dev Indicates the serial port dev.
*/
function closeSerial(dev:string, callback: AsyncCallback<void>): void;
function closeSerial(dev:string): Promise<void>;
/**
* tcflush serial port.
* @param dev Indicates the serial port dev.
* @param selector 0 in 1 out 2 in&out.
*/
function clearBuffer(dev:string, selector:number, callback: AsyncCallback<void>): void;
function clearBuffer(dev:string, selector:number): Promise<void>;
/**
* Send data to serial port.
* @param dev Indicates the serial port dev.
* @param data.
*/
function sendData(dev:string, data:Uint8Array, callback: AsyncCallback<void>): void;
function sendData(dev:string, data:Uint8Array): Promise<void>;
/**
* read data from serial port.
* @param dev Indicates the serial port dev.
* @param timeout
*/
function recvData(dev:string, timeout:number, callback: AsyncCallback<Uint8Array>): void;
function recvData(dev:string, timeout:number): Promise<Uint8Array>;
/**
* transmit Send and Read data
* @param dev Indicates the serial port dev.
* @param cmd Indicates the command.
* @param timeout
* @param callback Returns the Uint8Array
*/
function transmit(dev:string, cmd: Uint8Array, timeout: number, callback: AsyncCallback<Uint8Array>): void;
function transmit(dev:string, cmd: Uint8Array, timeout: number): Promise<Uint8Array>;
/**
* on/off serial data
* @param type Indicates the serial port dev.
* @param callback serial data
*/
function on(type: '/dev/ttyXRUSB0', callback: Callback<Uint8Array>): void;
function on(type: '/dev/ttyXRUSB1', callback: Callback<Uint8Array>): void;
function on(type: '/dev/ttyXRUSB2', callback: Callback<Uint8Array>): void;
function on(type: '/dev/ttyXRUSB3', callback: Callback<Uint8Array>): void;
function off(type: '/dev/ttyXRUSB0'): void;
function off(type: '/dev/ttyXRUSB1'): void;
function off(type: '/dev/ttyXRUSB2'): void;
function off(type: '/dev/ttyXRUSB3'): void;
/**
* Set GPIO Direction.
*
* @param portNo Gpio number.
* @param dirIn Is it an input port.
* @permission None
*/
function setGPIODirection(portNo:number, dirIn:boolean, callback: AsyncCallback<void>): void;
function setGPIODirection(portNo:number, dirIn:boolean): Promise<void>;
/**
* Set GPIO Value.
*
* @param portNo Gpio number.
* @param value Gpio value, 0 or 1.
* @permission None
*/
function setGPIOValue(portNo:number, value:number, callback: AsyncCallback<void>): void;
function setGPIOValue(portNo:number, value:number): Promise<void>;
/**
* Get GPIO Value.
*
* @param portNo Gpio number.
* @param callback Returns gpio value of portNo, 0 or 1.
* @permission None
*/
function getGPIOValue(portNo:number, callback: AsyncCallback<number>): void;
function getGPIOValue(portNo:number): Promise<number>;
}
export default serialHelper;
3.5 BUILD.gn文件介绍
import("//build/ohos.gni")
#ohos_shared_library()中的serialhelper决定了生成动态库的名称,增量编译阶段生成动态库libserialhelper.z.so
ohos_shared_library("serialhelper") {
#编译需要的源文件
sources = [
"serial_opt.cpp",
"serial_helper.cpp",
"x_napi_tool.cpp",
]
include_dirs = [
#根据增量编译阶段报错添加的头文件目录
"//third_party/node/src",
"./include",
"//base/hiviewdfx/hilog/interfaces/native/kits/include",
]
remove_configs = [
"//build/config/compiler:no_rtti",
"//build/config/compiler:no_exceptions",
]
#根据增量编译时clang编译器报警,添加的cflag
cflags = [
"-mno-relax",
"-fpermissive",
"-Wno-writable-strings",
"-Wno-error=pointer-to-int-cast",
"-Wno-error=void-pointer-to-int-cast",
"-Wno-error=conversion",
"-Wno-error=implicit-function-declaration",
"-Wno-error",
]
ldflags = [
"-lpthread",
"-ldl",
"-lrt",
]
#指定编译依赖hilog_ndk
deps = [
"//base/hiviewdfx/hilog/frameworks/hilog_ndk:hilog_ndk",
]
#指定编译依赖libhilog.z.so动态库
external_deps = [
"hilog_native:libhilog",
"napi:ace_napi",
]
defines = [ "MY_TEST_DEFINE" ]
#组件名称是prebuilt_hap
part_name = "prebuilt_hap"
#子系统名称是applications
subsystem_name = "applications"
}
ohos_executable("serialdebug") {
sources = [
"serial_opt.cpp",
]
include_dirs = [
"//third_party/node/src",
"./include",
"//base/hiviewdfx/hilog/interfaces/native/kits/include",
]
remove_configs = [
"//build/config/compiler:no_rtti",
"//build/config/compiler:no_exceptions",
]
cflags = [
"-mno-relax",
"-fpermissive",
"-Wno-writable-strings",
"-Wno-error=pointer-to-int-cast",
"-Wno-error=void-pointer-to-int-cast",
"-Wno-error=conversion",
"-Wno-error=implicit-function-declaration",
"-Wno-error",
]
ldflags = [
"-lpthread",
"-ldl",
"-lrt",
]
deps = [
"//base/hiviewdfx/hilog/frameworks/hilog_ndk:hilog_ndk",
]
external_deps = [
"hilog_native:libhilog",
"napi:ace_napi",
]
defines = [ "MY_TEST_DEFINE" ]
part_name = "prebuilt_hap"
subsystem_name = "applications"
}
group("napi_demo") {
deps = [
":serialhelper",
":serialdebug",
]
}
3.6 napi_demo编译
- 为了节省时间,可以先通过指定target的指令先查看是否编写错误 ./build.sh --product-name dayu800 --ccache --build-target=serialhelperlib或者直接./build.sh --product-name dayu800 --ccache进行全量编译
- 全量编译完成后,使用find -name 指令查找类似libserialhelper.z.so的文件,若找到则编译完成,将其发送到烧录了Openharmony3.2.2镜像的设备的/system/lib64/module目录中
./build.sh --product-name dayu800 --ccache --build-target=napi_demo
# 编译的libserialhelper.z.so和serialdebug位于./out/dayu800/thead_products/thead_products/目录中
四、测试NAPI接口功能
4.1 编写测试ets
打开DevEco Studio并创建一个空项目
在entry/src/main/ets/pages/Index.ets中编写typescript程序。
// 示例代码
// @ts-ignore
import testNapi from '@ohos.serialhelper'
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
fd: number = -1
devPath: string = '/dev/ttyS3'
async openChuankouFn() {
this.fd = await testNapi.openSerial(this.devPath);
console.log('daihai openChuankouFn this.fd: ' + this.fd)
}
async serialSet() {
let ret = await testNapi.setOptions(this.fd, 115200, 8, 0, 1);
console.log('daihai serialSet ret: ' + ret)
}
async SerialSend() {
const databuff = [0x61, 0xaa, 0x0a, 0x15, 0x00]; // send ABCDE
console.log('daihai SerialSend databuff len: ' + databuff.length)
let uint8Arr = new Uint8Array(databuff);
let ret = await testNapi.sendData(this.fd, uint8Arr);
console.log('daihai SerialSend ret: ' + ret)
// if (msg[9] == this.dwMap[this.index]) {
// this.warnFlag[this.index] = '2'
// }
}
async SerialRecv(timeout?: number) {
let revTestInfo = await testNapi.recvData(this.fd, timeout, 16);
console.log('daihai revTestInfo: ',JSON.stringify(revTestInfo))
//let revTestInfo = testNapi.SerialRecv(this.fd, timeout, 6);
// const message = revTestInfo?.recevedBuf?.toString()
// console.log('daihai revTestInfo.revTestInfo.recevedLen: ',revTestInfo.recevedLen)
// console.log('daihai revTestInfo.recevedBuf.toString(): ',revTestInfo.recevedBuf)
// console.log('daihai revTestInfo.recevedBuf.toString(): ',revTestInfo.recevedBuf.toString())
}
openChuankouFnCb() {
testNapi.openSerial(this.devPath, (err, ret) => {
console.log('daihai openChuankouFn this.fd: ' + this.fd)
})
}
serialSetCb() {
testNapi.setOptions(this.fd, 115200, 8, 0, 1, (err, ret) => {
console.log('daihai serialSet ret: ' + ret)
})
}
SerialSendCb() {
const databuff = [0x61, 0xaa, 0x0a, 0x15, 0x00]; // send ABCDE
console.log('daihai SerialSend databuff len: ' + databuff.length)
let uint8Arr = new Uint8Array(databuff);
testNapi.sendData(this.fd, uint8Arr, (err, ret) => {
console.log('daihai SerialSend ret: ' + ret)
})
// if (msg[9] == this.dwMap[this.index]) {
// this.warnFlag[this.index] = '2'
// }
}
SerialRecvCb(timeout?: number) {
testNapi.recvData(this.fd, timeout, 16, (err, revTestInfo) => {
console.log('daihai revTestInfo: ',JSON.stringify(revTestInfo))
})
}
build() {
Row() {
Column() {
Button('open')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.openChuankouFn()
})
Button('open cb')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.openChuankouFnCb()
}).margin({ top: 20 })
Button('serialSet')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.serialSet()
}).margin({ top: 20 })
Button('serialSet cb')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.serialSetCb()
}).margin({ top: 20 })
Button('SerialSend')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.SerialSend()
}).margin({ top: 20 })
Button('SerialSend cb')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.SerialSendCb()
}).margin({ top: 20 })
Button('SerialRecv')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.SerialRecv(3000)
}).margin({ top: 20 })
Button('SerialRecv cb')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.SerialRecvCb(3000)
}).margin({ top: 20 })
}
.width('100%')
}
.height('100%')
}
}
因为在编写接口时已经将d.ts文件加入so库中,所以需要加入// @ts-ignore防止编译失败
编写完成后点击右上角头像进行登陆,然后选择file>Project Structure>Signing configs>Automatically generate signature进行自动签名
连接开发板,点击右上角的debug按钮
点击下方log按钮,在搜索栏内设置过滤白名单,点击设备上显示的open按钮,查看返回信息