zl程序教程

您现在的位置是:首页 >  Javascript

当前栏目

Bearpi-Micro深入解析通过JS应用控制LED灯

2023-02-25 18:03:11 时间

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​

一、前言

最近跑了一遍Bearpi-Micro编写点亮LED灯程序的Demo,深入了解了如何在开发板上运行一个控制LED灯的程序,达到能关闭灯、开启灯以及翻转灯的状态,南向如何编写JS API接口提供驱动服务给北向应用使用。突发奇想,发现了官方给出的点灯应用中的一个不足,并进行优化。
参考文章:​​编写通过JS应用控制LED灯​

二、(南向)深入解析通过JS应用控制LED灯

1.前提

请确保已经完成编写一个点亮LED灯程序实验,因为本实验将依赖编写一个点亮LED灯程序实验中开发的驱动,以下教程篇幅较长,请耐心仔细阅读。

2.JS API接口开发

注:以下代码为主要代码的剖析,详细完整的代码可查看参考文章​​编写通过JS应用控制LED灯​​。

(1)添加控制LED灯的JS API接口

修改foundation\ace\ace_engine_lite\frameworks\src\core\modules\app_module.h,加入toggleLed JS API。

static JSIValue ToggleLed(const JSIValue thisVal, const JSIValue* args, uint8_t argsNum);

("##start##“和”##end##"仅用来标识位置,添加完配置后删除这两行)。

void InitAppModule(JSIValue exports)
{
JSI::SetModuleAPI(exports, "getInfo", AppModule::GetInfo);
JSI::SetModuleAPI(exports, "terminate", AppModule::Terminate);
##start##
JSI::SetModuleAPI(exports, "ledcontrol", AppModule::ToggleLed);
##end##
#ifdef FEATURE_SCREEN_ON_VISIBLE
JSI::SetModuleAPI(exports, "screenOnVisible", AppModule::ScreenOnVisible);
#endif
}

解析:在头文件中封装好JS API接口函数。
提供给北向的接口名为:ledcontrol ,南向业务代码函数为ToggleLed 。

(2)编写控制LED灯c++ 业务代码

在foundation\ace\ace_engine_lite\frameworks\src\core\modules\app_module.cpp中加入控制LED灯c++ 业务代码
注:以下代码仅为部分重要代码。

                 主函数ToggleLed将会调用的GpioWriteRead函数?
static int GpioWriteRead(struct HdfIoService *serv, int32_t eventData, int32_t *val)    
//传入驱动服务(已在前面的宏定义),命令eventData,待返回的数据指针val
{
int ret = HDF_FAILURE;
struct HdfSBuf *data = HdfSBufObtainDefaultSize(); //获取256字节的容量
struct HdfSBuf *reply = HdfSBufObtainDefaultSize(); //获取256字节的容量

if (data == NULL || reply == NULL) {
HILOG_ERROR(HILOG_MODULE_ACE, "fail to obtain sbuf data\n");
return ret;
}

if (!HdfSbufWriteUint8(data, (uint8_t)eventData)) { //将8位无符号整数eventData写入data
HILOG_ERROR(HILOG_MODULE_ACE, "fail to write sbuf\n");
HdfSBufRecycle(data);
HdfSBufRecycle(reply);
return ret;
}

ret = serv->dispatcher->Dispatch(&serv->object, LED_WRITE_READ, data,reply); //调度驱动服务函数,传入命令LED_WRITE_READ,传入到驱动的数据指针data,驱动待返回的数据指针reply
if (ret != HDF_SUCCESS) {
HILOG_ERROR(HILOG_MODULE_ACE, "fail to send service call\n");
HdfSBufRecycle(data);
HdfSBufRecycle(reply);
return ret;
}
if (!HdfSbufReadInt32(reply, val)) { //读取32位整型数reply的值赋给val;如果reply的值为NULL则获取驱动服务的回复失败
HILOG_ERROR(HILOG_MODULE_ACE, "fail to get service call reply\n");
ret = HDF_ERR_INVALID_OBJECT;
HdfSBufRecycle(data);
HdfSBufRecycle(reply);
return ret;
}
HILOG_ERROR(HILOG_MODULE_ACE, "Get reply is: %d\n", val); //终端打印消息

HdfSBufRecycle(data); //释放内存data
HdfSBufRecycle(reply); //释放内存reply
return ret;
}

解析:已在代码中注释。
重要的:Dispatch函数,调度驱动服务呼叫,需要传入4个参数:*service-驱动服务对象的指针,cmdId-函数的命令字,*data-想要传入驱动的数据指针,*reply-驱动待回复的数据指针。

接口ToggleLed主函数:

JSIValue AppModule::ToggleLed(const JSIValue thisVal,const JSIValue *args,uint8_t argsNum) //南向接口具体内容,控制LED灯c++ 业务代码
{
HILOG_ERROR(HILOG_MODULE_ACE, "led button pressed.");
struct HdfIoService *serv = HdfIoServiceBind(LED_SERVICE); //绑定HDF驱动中的LED服务
if (serv == NULL) {
HILOG_ERROR(HILOG_MODULE_ACE, "fail to get service2 %s\n", LED_SERVICE);
return JSI::CreateUndefined();
}
if ((args == nullptr) || (argsNum == 0) || (JSI::ValueIsUndefined(args[0]))) {
return JSI::CreateUndefined();
}
JSIValue success = JSI::GetNamedProperty(args[0], CB_SUCCESS); //获取北向接口中的属性
JSIValue fail = JSI::GetNamedProperty(args[0], CB_FAIL);
JSIValue complete = JSI::GetNamedProperty(args[0], CB_COMPLETE);
int32_t num = (int32_t)JSI::GetNumberProperty(args[0], "code"); //获取北向下发的命令code
int32_t replyData = 0;
if (GpioWriteRead(serv, num,&replyData)) { //使用上文定义的GpioWriteRead函数,传入驱动服务名,命令num,待返回的数据指针
HILOG_ERROR(HILOG_MODULE_ACE, "fail to send event\n");
JSI::CallFunction(fail, thisVal, nullptr, 0);
JSI::CallFunction(complete, thisVal, nullptr, 0);
JSI::ReleaseValueList(success, fail, complete);
return JSI::CreateUndefined();
}
JSIValue result = JSI::CreateObject();
JSI::SetNumberProperty(result, "led_status", replyData); //将replyData的值赋给result
JSIValue argv[ARGC_ONE] = {result};
JSI::CallFunction(success, thisVal, argv, ARGC_ONE);
JSI::CallFunction(complete, thisVal, nullptr, 0);
JSI::ReleaseValueList(success, fail, complete, result);
HdfIoServiceRecycle(serv); //销毁led的驱动程序服务对象以释放不再需要的资源。
return JSI::CreateUndefined();
}

解析:获取北向接口中的属性success、fail、complete、code。

JSIValue success = JSI::GetNamedProperty(args[0], CB_SUCCESS); //获取北向接口中的属性
JSIValue fail = JSI::GetNamedProperty(args[0], CB_FAIL);
JSIValue complete = JSI::GetNamedProperty(args[0], CB_COMPLETE);
int32_t num = (int32_t)JSI::GetNumberProperty(args[0], "code"); //获取北向下发的命令code

#冲刺创作新星#【FFH】Bearpi-Micro深入解析通过JS应用控制LED灯-开源基础软件社区

几个功能相似的赋值函数。

JSI::SetNumberProperty(result, "led_status", replyData); //将replyData的值赋给result
HdfSbufWriteUint8(data, (uint8_t)eventData) //将8位无符号整数eventData写入data
HdfSbufReadInt32(reply, val)//读取32位整型数reply的值赋给val

主要流程:北向应用调用ledcontrol接口,南向ToggleLed获取ledcontrol接口中的属性,并将属性值通过驱动服务中的Dispatch函数传递进驱动任务中,驱动给出返回值。

三、样例效果

点击应用的开关灯,嗯!挺流畅的。但是,如果我只打开北向应用,通过南向命令行终端打开LED灯,北向应用中灯的状态并不会发生改变。

南北向没有达到完美的联动效果。。。

#冲刺创作新星#【FFH】Bearpi-Micro深入解析通过JS应用控制LED灯-开源基础软件社区

四、优化官方的应用样例

分析:
从上文的剖析中可以看出南北向的接口似乎没有优化的地方,北向调用接口,南向就返回灯的状态值给北向。但是我们通过南向命令行终端执行应用程序的时候虽然也使用到了LED的驱动服务,但是并没有将灯的状态值反馈给北向(因为北向没有调用接口,自然就不会有返回值)。
思路:
既然知道了问题可能出在北向的应用方面,那么就着手尝试修改/增加一下,让北向应用中灯的图片一直调用接口就能一直得到返回的状态值。既然需要用到接口就得传参数。经过实验发现,不管传入命令code=0(关灯)、1(开灯)、2(转换灯状态),效果都极不理想。于是乎深入底层了解LED的驱动服务。

以下为主要代码段

// Dispatch是用来处理用户态发下来的消息
int32_t LedDriverDispatch(struct HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)
{//在这里cmdCode一直都是1,data表示传入的开关灯命令,reply表示将要返回灯的状态值
uint8_t contrl;
HDF_LOGE("Led driver dispatch");
if (client == NULL || client->device == NULL)
{
HDF_LOGE("Led driver device is NULL");
return HDF_ERR_INVALID_OBJECT;
}

switch (cmdCode)
{
/* 接收到用户态发来的LED_WRITE_READ命令 */
case LED_WRITE_READ:
/* 读取data里的数据,赋值给contrl */
HdfSbufReadUint8(data,&contrl);
switch (contrl) //在这里判断传递进来的命令
{
/* 开灯 */
case LED_ON: //*data=1,开灯
GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_LOW);
status = 1;
break;
/* 关灯 */
case LED_OFF: //*data=0,关灯
GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_HIGH);
status = 0;
break;
/* 状态翻转 */
case LED_TOGGLE: //*data=2,转换灯
if(status == 0)
{
GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_LOW);
status = 1;
}
else
{
GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_HIGH);
status = 0;
}
break;
default: //*data=其他值时不做操作
break;
}
/* 把LED的状态值写入reply, 可被带至用户程序 */
if (!HdfSbufWriteInt32(reply, status))
{
HDF_LOGE("replay is fail");
return HDF_FAILURE;
}
break;
default:
break;
}
return HDF_SUCCESS;
}

解决方案

既然如此如果我们在北向传入其他命令值,比如3,它同样会调用LED的驱动服务,同样会返回灯的状态值,只是没有进行其他操作。所以在北向可以多写一个函数为getstatus()?,发送命令3,单纯只为获得灯的状态值。

getstatus(){
//if(this.statu=='0')this.statu='1'
//else this.statu='0'

try{
let that=this
app.ledcontrol({
code:3,
success(res){
that.statu = res.led_status
},
fail(res,code){

},
complete(){

}
})
}catch{
this.openstatus="调用错误"
}
},

效果展示

南向点灯的同时,北向的应用也会实时更新灯的状态。完美!

#冲刺创作新星#【FFH】Bearpi-Micro深入解析通过JS应用控制LED灯-开源基础软件社区

​想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com​​。