很榮幸能參與到這次#DAYU200體驗官#的活動,感謝51CTO,感謝潤和。
本文主要分享如何在DAYU200上使用NAPI打通南向和北向,實作RGB LED 彩燈控制器的過程。
示範視訊如連結視訊連結
1. 開發環境
此實驗的主要開發環境如下:
DevEco Studio版本:DevEco Studio 3.0.0.900
OpenHarmony版本:OpenHarmony 3.1 Release
napi_generator版本:napi_generator_20220319.tar.gz
(本人電腦環境WIN11+WSL2+Ubuntu20.04)
2 南向部分
2.1 移除原來的控制
預設的彩燈是有顔色的,這個是因為在OpenHarmony 3.1 Release版本中battery_manager中有led_service會控制彩燈,不然控制上會有沖突,需要先移除,修改方式如下
在/base/powermgr/battery_manager/bundle.json中删除以下這行
"//base/powermgr/battery_manager/charger:led_service",
2.2 NAPI 元件的實作
2.2.1 NAPI元件簡介
NAPI(Native API)元件是一套對外接口基于Node.js N-API規範開發的原生子產品擴充開發架構。
圖 1 NAPI元件架構圖

使用場景:
NAPI适合封裝IO、CPU密集型、OS底層等能力并對外暴露JS接口,通過NAPI可以實作JS與C/C++代碼互相通路。我們可以通過NAPI接口建構例如網絡通信、序列槽通路、多媒體解碼、傳感器資料收集等子產品。
詳細介紹NAPI元件簡介
2.2.2 NAPI架構生成工具簡介
NAPI架構代碼生成工具(napi_generator),它可以根據使用者指定路徑下的ts(typescript)接口檔案一鍵生成NAPI架構代碼、業務代碼架構、GN檔案等。在開發JS應用與NAPI間接口時,底層架構開發者無需關注Nodejs文法、C++與JS之間的資料類型轉換等上層應用轉換邏輯,隻關注底層業務邏輯即可,專業的人做專業的事,進而可以大大提高開發效率。目前工具支援可執行檔案、VS Code插件、IntelliJ插件三種入口。
工具使用說明
下載下傳連結
目前工具還在持續開發完善中,詳細資訊查閱NAPI架構代碼生成工具版本規劃
2.2.3 NAPI接口@ohos.dayuled.d.ts的定義
在 napi_generator 下有@ohos.napitest.d.ts 可以參考
或者安裝完DevEco Studio 3.0.0.900且安裝SDK找到以下路徑,裡面有d.ts檔案參考
C:\Users\xxx(你的window使用者名)\AppData\Local\OpenHarmony\Sdk\ets\3.1.6.5\api
C:\Users\xxx(你的window使用者名\AppData\Local\OpenHarmony\Sdk\js\3.1.6.5\api
目前napi_generator 還在持續開發中不确定哪些資料類型能支援,需要自行驗證。
本案例定義@ohos.dayuled.d.ts如下
declare namespace dayuled {
/**
* Checks whether the screen of a device is on or off.
*
* @return Returns true if the screen is on; returns false otherwise.
* @since 7
*/
function redStatusChange(status: number): void;
function greenStatusChange(status: number): void;
function blueStatusChange(status: number): void;
function ledRGBStatusChange(r: number, g: number, b: number): void;
}
export default dayuled;
前三函數分别控制RGB燈的開關,最後一個可以同時控制RGB燈的開關
2.2.4 使用 napi_generator 生成架構
napi_generator下載下傳連結
從以上連結可以下載下傳到
│ | ├── generator.jar # IntelliJ插件
│ │ |── napi_generator-linux # Linux可執行程式
│ │ |── napi_generator-win.exe # Windows可執行程式
| | └── napi_generator-macos # Mac可執行程式
我這邊驗證了使用Linux和Windows可執行程式可以生成架構代碼。
使用方法将工具和.d.ts檔案及需要import的d.ts檔案也放入到待轉換的d.ts檔案相同的目錄下(本案例中沒有import其他d.ts檔案)。
以下以ubuntu環境介紹,檔案放置如以下
soon@SOON_NB16:~/napi/napi_generator/dayulight_nocallback$ tree
.
├── @ohos.dayuled.d.ts
└── napi_generator-linux
生成指令
./napi_generator-linux -f @ohos.dayuled.d.ts
生成成功後會有success提示,且生成檔案如下
soon@SOON_NB16:~/napi/napi_generator/dayulight_nocallback$ ./napi_generator-linux -f @ohos.dayuled.d.ts
success
soon@SOON_NB16:~/napi/napi_generator/dayulight_nocallback$ tree
.
├── @ohos.dayuled.d.ts
├── BUILD.gn
├── binding.gyp
├── dayuled.cpp
├── dayuled.h
├── dayuled_middle.cpp
├── napi_generator-linux
├── test.sh
├── x_napi_tool.cpp
└── x_napi_tool.h
0 directories, 10 files
soon@SOON_NB16:~/napi/napi_generator/dayulight_nocallback$
詳細說明參考NAPI架構生成工具使用說明
2.2.5 NAPI架構生成代碼內建到OpenHarmony子產品位置
子產品目錄理論上可以建立在OpenHarmony代碼庫的任何地方,例如在foundation目錄下建立dayuled。在 dayuled 目錄下,把之前用可執行檔案或者插件轉換出來的檔案全部拷貝到該目錄下。如以下
soon@SOON_NB16:~/ohos310/foundation/dayuled$ tree
.
├── @ohos.dayuled.d.ts
├── BUILD.gn
├── binding.gyp
├── dayuled.cpp
├── dayuled.h
├── dayuled_middle.cpp
├── napi_generator-linux
├── ohos.build
├── test.sh
├── x_napi_tool.cpp
└── x_napi_tool.h
0 directories, 11 files
soon@SOON_NB16:~/ohos310/foundation/dayuled$
2.2.6 編譯修改點
2.2.6.1 修改build.gn檔案
import("//build/ohos.gni")
ohos_shared_library("dayuled")
{
sources = [
"dayuled_middle.cpp",
"dayuled.cpp",
"x_napi_tool.cpp",
]
include_dirs = [
".",
"//third_party/node/src",
"//base/hiviewdfx/hilog/interfaces/native/innerkits/include",
]
deps=[
"//foundation/ace/napi:ace_napi",
"//base/hiviewdfx/hilog/interfaces/native/innerkits:libhilog",
]
public_deps = [
"//base/hiviewdfx/hilog/interfaces/native/innerkits:libhilog",
]
remove_configs = [ "//build/config/compiler:no_rtti" ]
cflags=[
]
cflags_cc=[
"-frtti",
]
ldflags = [
]
relative_install_dir = "module"
part_name = "dayuled_interface"
subsystem_name = "dayuled"
}
主要修改内容是添加hilog相關依賴、part_name和subsystem_name修改
2.2.6.2 修改ohos.build檔案
建立一個檔案ohos.build并修改如下,其中module_list選項中的"//foundation/dayuled"指的是 dayuled 目錄,":dayuled"指的是上面BUILD.gn中的目标ohos_shared_library("dayuled")。
{
"subsystem": "dayuled",
"parts": {
"dayuled_interface": {
"module_list": [
"//foundation/dayuled:dayuled"
],
"test_list": []
}
}
}
2.2.6.3 增加子系統
在源碼/build/subsystem_config.json中增加子系統選項。如下所示:
,
"dayuled": {
"project": "hmf/dayuled",
"path": "foundation/dayuled",
"name": "dayuled",
"dir": "foundation"
}
2.2.6.4 添加功能子產品
在産品配置中添加上述子系統的功能子產品,編譯到産品産出檔案中,例如在源碼productdefine/common/products/rk3568.json中增加part選項,其中 dayuled 就是上面填的part_name,dayuled_interface 就是上面填的subsystem_name。
,
"dayuled:dayuled_interface":{},
以上步驟參考NAPI架構生成代碼內建到OpenHarmony的方法
2.2.6.5 修改編譯錯誤
工具還有一些問題需要手動修改下foundation/dayuled/dayuled_middle.cpp才能編譯通過
主要修改 以下兩點
修改1
number_c_to_js沒有用到需要注釋相關内容
報錯資訊
修改方式
/*
static napi_value number_c_to_js(XNapiTool *pxt, const std::type_info &n, void *num)
{
if (n == typeid(int32_t))
return pxt->SwapC2JsInt32(*(int32_t *)num);
else if (n == typeid(uint32_t))
return pxt->SwapC2JsUint32(*(uint32_t *)num);
else if (n == typeid(int64_t))
return pxt->SwapC2JsInt64(*(int64_t *)num);
else if (n == typeid(double_t))
return pxt->SwapC2JsDouble(*(double_t *)num);
return nullptr;
}
#define NUMBER_C_2_JS(pxt, n) \
number_c_to_js(pxt, typeid(n), &n)
*/
修改2因為沒有callback是以下相關函數vio->out要移除
報錯資訊
修改方式
redStatusChange(vio->in0, vio->out);
greenStatusChange(vio->in0, vio->out);
blueStatusChange(vio->in0, vio->out);
ledRGBStatusChange(vio->in0, vio->in1, vio->in2, vio->out);
改為
redStatusChange(vio->in0);
greenStatusChange(vio->in0);
blueStatusChange(vio->in0);
ledRGBStatusChange(vio->in0, vio->in1, vio->in2);
2.2.7 RGB LED控制代碼的實作
之前想通過調用"light_if.h"但是一直沒有調通,目前是參考/base/powermgr/battery_manager/charger/led/battery_led.cpp 用echo的方式實作。
2.2.7.1 RGB LED指令開關控制方式
實際測試使用以下指令可以控制RGB LED的開關,但是綠色和藍色是相反的(後面說明如何修改)。
開關紅燈
# echo 0 > /sys/class/leds/red/brightness
# echo 1 > /sys/class/leds/red/brightness
開關藍燈
# echo 0 > /sys/class/leds/green/brightness
# echo 1 > /sys/class/leds/green/brightness
開關綠燈
# echo 1 > /sys/class/leds/blue/brightness
# echo 0 > /sys/class/leds/blue/brightness
2.2.7.2 在 dayuled.cpp中實作RGB LED 控制代碼
主要控制代碼如下
#include "dayuled.h"
#include "utils/log.h"
#include <fstream>
#include <memory>
#include <cstring>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
namespace dayuled {
const std::string LEDS_BASE_PATH = "/sys/class/leds";
std::vector<std::string> g_ledsNodeName;
std::string g_redLedsNode = "red";
std::string g_greenLedsNode = "green";
std::string g_blueLedsNode = "blue";
int redStatus = 0;
int greenStatus = 0;
int blueStatus = 0;
void WriteLedInfoToSys(const int redbrightness, const int greenbrightness, const int bluebrightness)
{
HILOG_INFO("[SOON] %{public}s enter", __func__);
FILE* file = nullptr;
std::string redLedPath = LEDS_BASE_PATH + "/" + g_redLedsNode + "/" + "brightness";
std::string greenLedPath = LEDS_BASE_PATH + "/" + g_greenLedsNode + "/" + "brightness";
std::string blueLedPath = LEDS_BASE_PATH + "/" + g_blueLedsNode + "/" + "brightness";
HILOG_INFO("[SOON] %{public}s: redLedPath is %{public}s, greenLedPath is %{public}s, blueLedPath is %{public}s", __func__,
redLedPath.c_str(), greenLedPath.c_str(), blueLedPath.c_str());
file = fopen(redLedPath.c_str(), "w");
if (file == nullptr) {
HILOG_INFO("[SOON] %{public}s: red led file open failed. redLedPath is %{public}s", __func__, redLedPath.c_str());
return;
}
int ret = fprintf(file, "%d\n", redbrightness);
if (ret < 0) {
HILOG_INFO("[SOON] %{public}s: red led file fHILOG_INFO failed.", __func__);
}
ret = fclose(file);
if (ret < 0) {
return;
}
file = fopen(greenLedPath.c_str(), "w");
if (file == nullptr) {
HILOG_INFO("[SOON] %{public}s: green led file open failed. greenLedPath is %{public}s", __func__, greenLedPath.c_str());
return;
}
ret = fprintf(file, "%d\n", greenbrightness);
if (ret < 0) {
HILOG_INFO("[SOON] %{public}s: green led file fHILOG_INFO failed.", __func__);
}
ret = fclose(file);
if (ret < 0) {
return;
}
file = fopen(blueLedPath.c_str(), "w");
if (file == nullptr) {
HILOG_INFO("[SOON] %{public}s: blue led file open failed.", __func__);
return;
}
ret = fprintf(file, "%d\n", bluebrightness);
if (ret < 0) {
HILOG_INFO("[SOON] %{public}s: blue led file fHILOG_INFO failed. blueLedPath is %{public}s", __func__, blueLedPath.c_str());
}
ret = fclose(file);
if (ret < 0) {
return;
}
HILOG_INFO("[SOON] %{public}s exit", __func__);
return;
}
bool redStatusChange(NUMBER_TYPE_1 &status)
{
HILOG_INFO("[SOON] redStatusChange: enter status=%{public}d redStatus=%{public}d greenStatus=%{public}d blueStatus=%{public}d",status, redStatus, greenStatus, blueStatus);
redStatus = status;
WriteLedInfoToSys(redStatus, greenStatus, blueStatus);
HILOG_INFO("[SOON] redStatusChange: end ");
return true;
}
bool greenStatusChange(NUMBER_TYPE_2 &status)
{
HILOG_INFO("[SOON] greenStatusChange: enter status=%{public}d redStatus=%{public}d greenStatus=%{public}d blueStatus=%{public}d",status, redStatus, greenStatus, blueStatus);
greenStatus = status;
WriteLedInfoToSys(redStatus, greenStatus, blueStatus);
HILOG_INFO("[SOON] greenStatusChange: end ");
return true;
}
bool blueStatusChange(NUMBER_TYPE_3 &status)
{
HILOG_INFO("[SOON] blueStatusChange: enter status=%{public}d redStatus=%{public}d greenStatus=%{public}d blueStatus=%{public}d",status, redStatus, greenStatus, blueStatus);
blueStatus = status;
WriteLedInfoToSys(redStatus, greenStatus, blueStatus);
HILOG_INFO("[SOON] blueStatusChange: end ");
return true;
}
bool ledRGBStatusChange(NUMBER_TYPE_4 &r, NUMBER_TYPE_5 &g, NUMBER_TYPE_6 &b)
{
HILOG_INFO("[SOON] ledRGBStatusChange: enter redStatus=%{public}d greenStatus=%{public}d blueStatus=%{public}d", r, g, b);
WriteLedInfoToSys(r, g, b);
HILOG_INFO("[SOON] ledRGBStatusChange: end ");
return true;
}
}
2.2.7.3 brightness節點權限的修改
這一步我不确定有沒有更好的方式,如果有大佬知道請留言指導,謝謝!
異常log如下,因為沒有w權限導緻打開檔案失敗
[dayuled.cpp(WriteLedInfoToSys)] [SOON] WriteLedInfoToSys: red led file open failed. redLedPath is /sys/class/leds/red/brightness
目前我的解決方式是修改base/startup/init_lite/services/etc/init.cfg中約379行後添權重限修改。
"chown system system /sys/class/leds/red/brightness",
"chown system system /sys/class/leds/green/brightness",
"chown system system /sys/class/leds/blue/brightness",
這個位置下方添加
"chmod 0666 /sys/class/leds/red/brightness",
"chmod 0666 /sys/class/leds/green/brightness",
"chmod 0666 /sys/class/leds/blue/brightness",
修改RGB LED brightness節點的權限為0666
2.2.7.4 修改綠色LED和藍色LED控制控制相反問題
此處直接修改kernel/linux/patches/linux-5.10/rk3568_patch/kernel.patch中rk3568-toybrick-x10.dtsi以下部分将綠色和藍色GPIO對調。
+++ b/linux-5.10/rk3568_patch/kernel.patch
@@ -96124,7 +96124,7 @@ index 000000000..14d80d46b
+ gpio_leds: gpio-leds {
+ compatible = "gpio-leds";
+ led@1 {
-+ gpios = <&gpio4 RK_PC2 GPIO_ACTIVE_HIGH>;
++ gpios = <&gpio4 RK_PC5 GPIO_ACTIVE_HIGH>;
+ label = "blue"; // Blue LED
+ retain-state-suspended;
+ };
@@ -96136,7 +96136,7 @@ index 000000000..14d80d46b
+ };
+
+ led@3 {
-+ gpios = <&gpio4 RK_PC5 GPIO_ACTIVE_HIGH>;
++ gpios = <&gpio4 RK_PC2 GPIO_ACTIVE_HIGH>;
+ label = "green"; // Green LED
+ retain-state-suspended;
+ };
2.2.8 編譯驗證
在做完以上修改後建議執行一次全編譯并用燒錄整包固件的方式更新,後續調試再使用增量編譯方式替換so。具體說明如下
2.2.8.1 全編譯驗證完整燒錄方式
使用hb指令如下
hb set
選擇rk3568,不是DAYU
hb build -f
需要帶-f參數執行全編譯
編譯成功後拷貝out/rk3568/packages/phone/images目錄到window下燒錄,具體燒錄方式此處不做展開,可以看論壇其他文章。
2.2.8.2 增量編譯替換so驗證方式
如果隻是修改dayuled檔案夾裡面的内容可以用這個方式來節省開發時間。具體說明如下
因為之前使用過hb set 選了rk3568此時可以不用重新執行hb set
隻需要在儲存你要的修改之後執行以下指令
hb build
不帶參數預設增量編譯。
編譯成功後會在以下路徑生成libdayuled.z.so,目錄如下所示
out/rk3568/dayuled/dayuled_interface/libdayuled.z.so
或者
out/rk3568/packages/phone/system/lib/module/libdayuled.z.so
這兩個位置的libdayuled.z.so是一樣的
将這個libdayuled.z.so拷貝到C:\Users\XXX(你的使用者名)\AppData\Local\OpenHarmony\Sdk\toolchains\3.1.6.5(這個路徑是DevEco Studio 3.0.0.900 SDK 安裝路徑)或者你放hdc_std工具的路徑,
将OTG資料線與開發闆相連并執行以下操作
在C:\Users\XXX(你的使用者名)\AppData\Local\OpenHarmony\Sdk\toolchains\3.1.6.5輸入CMD,執行以下指令即可替換so
C:\Users\soonl\AppData\Local\OpenHarmony\Sdk\toolchains\3.1.6.5>hdc_std shell
# mount -o remount,rw /
# exit
C:\Users\soonl\AppData\Local\OpenHarmony\Sdk\toolchains\3.1.6.5>hdc_std file send libdayuled.z.so /system/lib/module/
FileTransfer finish, Size:21876 time:21ms rate:1041.71kB/s
C:\Users\soonl\AppData\Local\OpenHarmony\Sdk\toolchains\3.1.6.5>
操作圖檔見GIF
以上南向部分代碼修改整理在附件ohos310_dayuled_napi.zip 中。
3 北向部分
要點是要将@ohos.dayuled.d.ts手動放到C:\Users\xxx(你的使用者名)\AppData\Local\OpenHarmony\Sdk\ets\3.1.6.5\api路徑下,這樣打包的時候才不會報錯。
3.1 将@ohos.dayuled.d.ts手動放到api下
在沒有放@ohos.dayuled.d.ts的情況下去import dayuled from '@ohos.dayuled';會報“Cannot find module '@ohos.dayuled' or its corresponding type declarations.
”,報錯截圖如下
将@ohos.dayuled.d.ts手動放到C:\Users\xxx(你的使用者名)\AppData\Local\OpenHarmony\Sdk\ets\3.1.6.5\api路徑下就可以正常,如果找不到這個sdk路徑那可能是你的DevEco Studio版本不是DevEco Studio 3.0.0.900,或者你有修改了Sdk的路徑。
3.2 ets主要代碼
頁面隻有簡單的文本和四個Toggle的switch預覽截圖如下
entry/src/main/ets/MainAbility/pages/index.ets主要代碼如下,entry完整代碼見附件
import dayuled from '@ohos.dayuled';
@Entry
@Component
struct Index {
@State message: string = 'RGB LED 控制'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Text(`紅色 LED`).flexGrow(1).fontSize('30lpx')
Toggle({
type: ToggleType.Switch,
})
.onChange((res) => {
console.log("[SOON] Red Toggle=" + res.toString())
if(res == true){
dayuled.redStatusChange(1);
}else{
dayuled.redStatusChange(0);
}
console.log("[SOON] Red End")
})
.padding(0)
.width(100)
.height(60)
.backgroundColor(Color.Gray)
.borderRadius(20)
.selectedColor(Color.Red)
Text(`綠色 LED`).flexGrow(1).fontSize('30lpx')
Toggle({
type: ToggleType.Switch,
})
.onChange((res) => {
console.log("[SOON] Green Toggle=" + res.toString())
if(res == true){
dayuled.greenStatusChange(1);
} else {
dayuled.greenStatusChange(0);
}
console.log("[SOON] Green End")
})
.padding(0)
.width(100)
.height(60)
.backgroundColor(Color.Gray)
.borderRadius(20)
.selectedColor(Color.Green)
Text(`藍色 LED`).flexGrow(1).fontSize('30lpx')
Toggle({
type: ToggleType.Switch,
})
.onChange((res) => {
console.log("[SOON] Blue Toggle=" + res.toString())
if(res == true){
dayuled.blueStatusChange(1);
} else {
dayuled.blueStatusChange(0);
}
console.log("[SOON] BLUE End");
})
.padding(0)
.width(100)
.height(60)
.backgroundColor(Color.Gray)
.borderRadius(20)
.selectedColor(Color.Blue)
Text(`全部 LED`).flexGrow(1).fontSize('30lpx')
Toggle({
type: ToggleType.Switch,
})
.onChange((res) => {
console.log("[SOON] All Toggle=" + res.toString())
if(res == true){
dayuled.ledRGBStatusChange(1, 1, 1);
} else {
dayuled.ledRGBStatusChange(0, 0, 0);
}
console.log("[SOON] All End");
})
.padding(0)
.width(100)
.height(60)
.backgroundColor(Color.Gray)
.borderRadius(20)
.selectedColor(Color.White)
}
.width('100%')
}
.height('100%')
}
}
3.3 簽名打包HAP
在OTG連接配接DAYU200開發闆後,使用DevEco Studio 3.0.0.900 的自動簽名即可。操作步驟見動圖
以上HAP關鍵代碼整理如附件dayulight_hap.zip
4 HAP內建打包
直接将HAP內建到了固件中打包的主要步驟如下。
4.1 拷貝簽名後的HAP到applications/standard/hap/下
從應用目錄拷貝簽名後的hap
即将
entry/build/default/outputs/default/entry-default-signed.hap
拷貝到源碼以下路徑并重命名為你想要的名稱,如Dayulight.hap
applications/standard/hap/Dayulight.hap
4.2 修改applications/standard/hap/BUILD.gn
添加以下内容
ohos_prebuilt_etc("dayulight_hap") {
source = "Dayulight.hap"
module_install_dir = "app"
part_name = "prebuilt_hap"
subsystem_name = "applications"
}
group("hap") {
......
else if (defined(product_name) && product_name == "rk3568") {
......
deps += [ "//applications/standard/hap:dayulight_hap" ]
}
......
}
修改完成後進行增量編譯,重燒image(我編譯的image打包如附件DayuLight_images.zip)即可看到包含了DAYU Light這個應用。
5 相關源碼
如附件,且已開源至Gitee,點選DAYULIGHT跳轉
位址如下:
https://gitee.com/soonliao/dayu-light
以上在DAYU200上使用NAPI打通南向和北向,實作RGB LED 彩燈控制器的分享,感謝閱讀!
參考連結:
NAPI元件簡介
napi_generator工具使用說明
NAPI架構生成代碼內建到OpenHarmony的方法
附件連結
dayulight_hap.zip(https://ost.51cto.com/resource/2030)
ohos310_dayuled_napi.zip(https://ost.51cto.com/resource/2031)
DayuLight_images.zip(https://ost.51cto.com/resource/2032)